Add support for media saving messages in dropzone (#11304)
* WIP Waiting for a response on the issue about how to proceed * Support messages in dropzone Update dropzone to allow the showing of messages that may be added in a media saving notification / handler * Remove test code Remove code used for testing * Sort usings Remove unused using * Fix ordering Ordering of files when they were being processed was backwards / out of order * Add button to "okay" all messages PR Feedback to add a button to dismiss all of the messages all at once. Fixing a referencing issue with `currentFile` Co-authored-by: Michael Latouche <michael@crossingpaths.be>
This commit is contained in:
@@ -31,7 +31,7 @@ angular.module("umbraco.directives")
|
||||
propertyAlias: '@',
|
||||
accept: '@',
|
||||
maxFileSize: '@',
|
||||
|
||||
|
||||
compact: '@',
|
||||
hideDropzone: '@',
|
||||
acceptedMediatypes: '=',
|
||||
@@ -42,9 +42,10 @@ angular.module("umbraco.directives")
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.queue = [];
|
||||
scope.done = [];
|
||||
scope.rejected = [];
|
||||
scope.totalQueued = 0;
|
||||
scope.currentFile = undefined;
|
||||
scope.processed = [];
|
||||
scope.totalMessages = 0;
|
||||
|
||||
function _filterFile(file) {
|
||||
var ignoreFileNames = ['Thumbs.db'];
|
||||
@@ -65,51 +66,50 @@ angular.module("umbraco.directives")
|
||||
function _filesQueued(files, event) {
|
||||
//Push into the queue
|
||||
Utilities.forEach(files, file => {
|
||||
if (_filterFile(file) === true) {
|
||||
|
||||
if (file.$error) {
|
||||
scope.rejected.push(file);
|
||||
} else {
|
||||
scope.queue.push(file);
|
||||
}
|
||||
if (_filterFile(file) === true) {
|
||||
file.messages = [];
|
||||
scope.queue.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
//when queue is done, kick the uploader
|
||||
if (!scope.working) {
|
||||
// Upload not allowed
|
||||
if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) {
|
||||
files.map(file => {
|
||||
file.uploadStatus = "error";
|
||||
file.serverErrorMessage = "File type is not allowed here";
|
||||
scope.rejected.push(file);
|
||||
});
|
||||
scope.queue = [];
|
||||
}
|
||||
// If we have Accepted Media Types, we will ask to choose Media Type, if Choose Media Type returns false, it only had one choice and therefor no reason to
|
||||
if (scope.acceptedMediatypes && _requestChooseMediaTypeDialog() === false) {
|
||||
scope.contentTypeAlias = "umbracoAutoSelect";
|
||||
|
||||
_processQueueItem();
|
||||
}
|
||||
// Upload not allowed
|
||||
if (!scope.acceptedMediatypes || !scope.acceptedMediatypes.length) {
|
||||
files.map(file => {
|
||||
file.messages.push({message: "File type is not allowed here", type: "Error"});
|
||||
});
|
||||
}
|
||||
|
||||
// If we have Accepted Media Types, we will ask to choose Media Type, if Choose Media Type returns false, it only had one choice and therefor no reason to
|
||||
if (scope.acceptedMediatypes && _requestChooseMediaTypeDialog() === false) {
|
||||
scope.contentTypeAlias = "umbracoAutoSelect";
|
||||
}
|
||||
|
||||
// Add the processed length, as we might be uploading in stages
|
||||
scope.totalQueued = scope.queue.length + scope.processed.length;
|
||||
|
||||
_processQueueItems();
|
||||
}
|
||||
|
||||
function _processQueueItem() {
|
||||
if (scope.queue.length > 0) {
|
||||
function _processQueueItems() {
|
||||
// if we have processed all files, either by successful
|
||||
// upload, or attending to all messages, we deem the
|
||||
// action complete, else continue processing files
|
||||
scope.totalMessages = scope.processed.filter(e => e.messages.length > 0).length;
|
||||
if (scope.totalQueued === scope.processed.length) {
|
||||
if (scope.totalMessages === 0) {
|
||||
if (scope.filesUploaded) {
|
||||
//queue is empty, trigger the done action
|
||||
scope.filesUploaded(scope.done);
|
||||
}
|
||||
//auto-clear the done queue after 3 secs
|
||||
var currentLength = scope.processed.length;
|
||||
$timeout(function() {
|
||||
scope.processed.splice(0, currentLength);
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
scope.currentFile = scope.queue.shift();
|
||||
_upload(scope.currentFile);
|
||||
} else if (scope.done.length > 0) {
|
||||
if (scope.filesUploaded) {
|
||||
//queue is empty, trigger the done action
|
||||
scope.filesUploaded(scope.done);
|
||||
}
|
||||
|
||||
//auto-clear the done queue after 3 secs
|
||||
var currentLength = scope.done.length;
|
||||
$timeout(function() {
|
||||
scope.done.splice(0, currentLength);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,55 +134,36 @@ angular.module("umbraco.directives")
|
||||
var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10);
|
||||
// set percentage property on file
|
||||
file.uploadProgress = progressPercentage;
|
||||
// set uploading status on file
|
||||
file.uploadStatus = "uploading";
|
||||
}
|
||||
})
|
||||
.success(function(data, status, headers, config) {
|
||||
if (data.notifications && data.notifications.length > 0) {
|
||||
// set error status on file
|
||||
file.uploadStatus = "error";
|
||||
// Throw message back to user with the cause of the error
|
||||
file.serverErrorMessage = data.notifications[0].message;
|
||||
// Put the file in the rejected pool
|
||||
scope.rejected.push(file);
|
||||
} else {
|
||||
// set done status on file
|
||||
file.uploadStatus = "done";
|
||||
file.uploadProgress = 100;
|
||||
// set date/time for when done - used for sorting
|
||||
file.doneDate = new Date();
|
||||
// Put the file in the done pool
|
||||
scope.done.push(file);
|
||||
}
|
||||
.success(function (data, status, headers, config) {
|
||||
// Set server messages
|
||||
file.messages = data.notifications;
|
||||
scope.processed.push(file);
|
||||
//after processing, test if everything is done
|
||||
scope.currentFile = undefined;
|
||||
//after processing, test if everthing is done
|
||||
_processQueueItem();
|
||||
_processQueueItems();
|
||||
})
|
||||
.error(function(evt, status, headers, config) {
|
||||
// set status done
|
||||
file.uploadStatus = "error";
|
||||
//if the service returns a detailed error
|
||||
if (evt.InnerException) {
|
||||
file.serverErrorMessage = evt.InnerException.ExceptionMessage;
|
||||
file.messages.push({ message: evt.InnerException.ExceptionMessage, type: "Error" });
|
||||
//Check if its the common "too large file" exception
|
||||
if (evt.InnerException.StackTrace &&
|
||||
evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) {
|
||||
file.serverErrorMessage = "File too large to upload";
|
||||
file.messages.push({ message: "File too large to upload", type: "Error" });
|
||||
}
|
||||
} else if (evt.Message) {
|
||||
file.serverErrorMessage = evt.Message;
|
||||
} else if (evt && typeof evt === 'string') {
|
||||
file.serverErrorMessage = evt;
|
||||
file.messages.push({message: evt.Message, type: "Error"});
|
||||
} else if (evt && typeof evt === "string") {
|
||||
file.messages.push({message: evt, type: "Error"});
|
||||
}
|
||||
// If file not found, server will return a 404 and display this message
|
||||
if (status === 404) {
|
||||
file.serverErrorMessage = "File not found";
|
||||
file.messages.push({message: "File not found", type: "Error"});
|
||||
}
|
||||
//after processing, test if everthing is done
|
||||
scope.rejected.push(file);
|
||||
scope.currentFile = undefined;
|
||||
_processQueueItem();
|
||||
_processQueueItems();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -224,16 +205,14 @@ angular.module("umbraco.directives")
|
||||
availableItems: filteredMediaTypes,
|
||||
submit: function (model) {
|
||||
scope.contentTypeAlias = model.selectedItem.alias;
|
||||
_processQueueItem();
|
||||
_processQueueItems();
|
||||
|
||||
overlayService.close();
|
||||
},
|
||||
close: function () {
|
||||
|
||||
scope.queue.map(function (file) {
|
||||
file.uploadStatus = "error";
|
||||
file.serverErrorMessage = "No files uploaded, no mediatype selected";
|
||||
scope.rejected.push(file);
|
||||
file.messages.push({message:"No files uploaded, no mediatype selected", type: "Error"});
|
||||
});
|
||||
scope.queue = [];
|
||||
|
||||
@@ -245,14 +224,26 @@ angular.module("umbraco.directives")
|
||||
overlayService.open(dialog);
|
||||
});
|
||||
|
||||
return true;// yes, we did open the choose-media dialog, therefor we return true.
|
||||
return true; // yes, we did open the choose-media dialog, therefore we return true.
|
||||
}
|
||||
|
||||
scope.dismissMessages = function (file) {
|
||||
file.messages = [];
|
||||
_processQueueItems();
|
||||
}
|
||||
|
||||
scope.dismissAllMessages = function () {
|
||||
Utilities.forEach(scope.processed, file => {
|
||||
file.messages = [];
|
||||
});
|
||||
_processQueueItems();
|
||||
}
|
||||
|
||||
scope.handleFiles = function(files, event, invalidFiles) {
|
||||
const allFiles = [...files, ...invalidFiles];
|
||||
|
||||
// add unique key for each files to use in ng-repeats
|
||||
allFiles.forEach(file => {
|
||||
Utilities.forEach(allFiles, file => {
|
||||
file.key = String.CreateGuid();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
.umb-file-dropzone {
|
||||
|
||||
// drop zone
|
||||
// tall and small version - animate height
|
||||
.dropzone {
|
||||
@@ -21,17 +20,16 @@
|
||||
|
||||
&.is-small {
|
||||
height: 100px;
|
||||
|
||||
.illustration {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.drag-over {
|
||||
border: 1px dashed @gray-1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// center the content of the drop zone
|
||||
.content {
|
||||
position: absolute;
|
||||
@@ -41,8 +39,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
// file select link
|
||||
.file-select {
|
||||
background: transparent;
|
||||
@@ -54,11 +50,10 @@
|
||||
margin-top: 10px;
|
||||
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
text-decoration: none;
|
||||
color: @ui-action-discreet-type-hover;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
// uploading / uploaded file list
|
||||
.file-list {
|
||||
list-style: none;
|
||||
@@ -67,12 +62,10 @@
|
||||
padding: 10px 20px;
|
||||
|
||||
.file {
|
||||
//border-bottom: 1px dashed @orange;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
border-top: 1px solid @gray-8;
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
@@ -80,13 +73,21 @@
|
||||
&.ng-enter {
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
|
||||
&.ng-leave {
|
||||
animation: fadeOut 2s;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
color: @gray-3;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-messages, .file-messages span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -95,25 +96,11 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
&.ng-enter {
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
&.ng-leave {
|
||||
animation: fadeIn 0.5s;
|
||||
}
|
||||
}
|
||||
.ok-all {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// progress bars
|
||||
// could be moved to its own less file
|
||||
.file-progress {
|
||||
@@ -131,5 +118,4 @@
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<div data-element="dropzone" class="umb-file-dropzone">
|
||||
|
||||
<ng-form name="uploadForm" umb-isolate-form>
|
||||
|
||||
<!-- Drag and drop files area -->
|
||||
<div ngf-drop
|
||||
ng-hide="hideDropzone === 'true'"
|
||||
ng-model="filesHolder"
|
||||
ngf-change="handleFiles($files, $event, $invalidFiles)"
|
||||
class="dropzone"
|
||||
ngf-drag-over-class="'drag-over'"
|
||||
ngf-multiple="true"
|
||||
ngf-allow-dir="true"
|
||||
ngf-pattern="{{ accept }}"
|
||||
ngf-max-size="{{ maxFileSize }}"
|
||||
ng-class="{'is-small': compact !=='false' || (done.length + queue.length) > 0 }">
|
||||
ng-hide="hideDropzone === 'true'"
|
||||
ng-model="filesHolder"
|
||||
ngf-change="handleFiles($files, $event, $invalidFiles)"
|
||||
class="dropzone"
|
||||
ngf-drag-over-class="'drag-over'"
|
||||
ngf-multiple="true"
|
||||
ngf-allow-dir="true"
|
||||
ngf-pattern="{{ accept }}"
|
||||
ngf-max-size="{{ maxFileSize }}"
|
||||
ng-class="{'is-small': compact !=='false' || (processed.length + queue.length) > 0 }">
|
||||
|
||||
<div class="content" >
|
||||
<div class="content">
|
||||
<p>
|
||||
<localize key="media_dragAndDropYourFilesIntoTheArea">Drag and drop your file(s) into the area</localize>
|
||||
</p>
|
||||
@@ -24,82 +23,69 @@
|
||||
<img class="illustration" src="assets/img/uploader/upload-illustration.svg" alt="" draggable="false" />
|
||||
|
||||
<!-- Select files -->
|
||||
<button
|
||||
type="button"
|
||||
data-element="button-uploadMedia"
|
||||
class="file-select"
|
||||
ngf-select
|
||||
ng-model="filesHolder"
|
||||
ngf-change="handleFiles($files, $event, $invalidFiles)"
|
||||
ngf-multiple="true"
|
||||
ngf-pattern="{{ accept }}"
|
||||
ngf-max-size="{{ maxFileSize }}">
|
||||
- <localize key="media_orClickHereToUpload">or click here to choose files</localize>
|
||||
<button type="button"
|
||||
data-element="button-uploadMedia"
|
||||
class="file-select"
|
||||
ngf-select
|
||||
ng-model="filesHolder"
|
||||
ngf-change="handleFiles($files, $event, $invalidFiles)"
|
||||
ngf-multiple="true"
|
||||
ngf-pattern="{{ accept }}"
|
||||
ngf-max-size="{{ maxFileSize }}">
|
||||
- <localize key="media_orClickHereToUpload">or click here to choose files</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List of uploading/uploaded files -->
|
||||
<ul class="file-list" ng-show="done.length > 0 || queue.length > 0 || rejected.length > 0 || filesHolder.length > 0">
|
||||
<ul class="file-list" ng-show="queue.length > 0 || processed.length > 0 || filesHolder.length > 0">
|
||||
|
||||
<!-- make list sort order the same as photo grid. The last uploaded photo in the top -->
|
||||
<li class="file" ng-repeat="file in done track by file.key">
|
||||
|
||||
<!-- file name -->
|
||||
<div class="file-description">{{ file.name }}</div>
|
||||
<li class="file" ng-if="totalMessages > 1">
|
||||
<div class="file-description">
|
||||
<!-- Okay all -->
|
||||
<button class="btn btn-primary ok-all" type="button" ng-click="dismissAllMessages()">
|
||||
<localize key="general_ok">Ok</localize> (<localize key="general_all">All</localize>)
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- upload success -->
|
||||
<div class="file-icon" ng-if="file.uploadStatus == 'done'">
|
||||
<li class="file" ng-repeat="file in processed track by file.key">
|
||||
|
||||
<div class="file-description">
|
||||
<div>
|
||||
<span>{{ file.name }}</span>
|
||||
<span ng-if="file.messages.length > 0 || file.$error" class="file-messages">
|
||||
<span class="errorMessage color-red" ng-repeat="message in file.messages">{{message.header}}: {{message.message}}</span>
|
||||
<span ng-if="file.$error === 'pattern'" class="errorMessage color-red"><localize key="media_disallowedFileType"></localize></span>
|
||||
<span ng-if="file.$error === 'maxSize'" class="errorMessage color-red"><localize key="media_maxFileSize"></localize> "{{maxFileSize}}"</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- upload success -->
|
||||
<span ng-if="file.messages.length === 0">
|
||||
<umb-icon icon="icon-check" class="icon color-green"></umb-icon>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<!-- requires user input -->
|
||||
<button class="btn btn-primary" type="button" ng-click="dismissMessages(file)" ng-if="file.messages.length > 0">
|
||||
<localize key="general_ok">Ok</localize>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="file" ng-if="currentFile">
|
||||
|
||||
<!-- file name -->
|
||||
<div class="file-name">{{currentFile.name}}</div>
|
||||
|
||||
<!-- upload progress bar -->
|
||||
<div class="file-name">{{currentFile.name}} {{currentFile.uploadProgress + '%'}}</div>
|
||||
<div class="file-progress">
|
||||
<span class="file-progress-indicator"
|
||||
ng-style="{'width': currentFile.uploadProgress + '%'}"></span>
|
||||
<span class="file-progress-indicator" ng-style="{'width': currentFile.uploadProgress + '%'}"></span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- make list sort order the same as photo grid. The last uploaded photo in the top -->
|
||||
<li class="file" ng-repeat="queued in queue track by queued.key">
|
||||
|
||||
<!-- file name -->
|
||||
<div class="file-name">{{queued.name}}</div>
|
||||
</li>
|
||||
|
||||
<li class="file" ng-repeat="file in rejected track by file.key">
|
||||
|
||||
<!-- file name -->
|
||||
<div class="file-description">
|
||||
|
||||
<strong>{{file.name}}</strong>
|
||||
|
||||
<span class="file-error" ng-if="file.$error">
|
||||
<span ng-if="file.$error === 'pattern'" class="errorMessage color-red"><localize key="media_disallowedFileType">Cannot upload this file, it does not have an approved file type</localize></span>
|
||||
<span ng-if="file.$error === 'maxSize'" class="errorMessage color-red"><localize key="media_maxFileSize">Max file size is</localize> "{{maxFileSize}}"</span>
|
||||
</span>
|
||||
|
||||
<span class="file-error" ng-if="file.serverErrorMessage">
|
||||
<span class="errorMessage color-red">{{file.serverErrorMessage}}</span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- upload error -->
|
||||
<div class="file-icon">
|
||||
<umb-icon icon="icon-delete" class="icon color-red"></umb-icon>
|
||||
</div>
|
||||
|
||||
<li class="file" ng-repeat="file in queue track by file.key">
|
||||
<div class="file-description">{{ file.name }}</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</ng-form>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user