From c8a98a670cf0157e84d80d5fffc7ddace3ff2a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 4 May 2021 11:06:52 +0200 Subject: [PATCH] =?UTF-8?q?Review=20AB11194=20=E2=80=94=20Improve=20media?= =?UTF-8?q?=20selector=20UX=20(#10157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * set input file accept * Use PreValue file extensions for limiting the files to be chosen in file input * Current state for Warren to review * This should fix up what you need Niels * update csproj * use empty string if fileExtensions is undefined * public interface * initial work * local crops * translations * translation correction * fix misspeling * some progress * filter media picker * align media card grid items correctly * responsive media cropper * always be able to scale 3 times smallest scale * making image cropper property editor responsive * scroll to scale * adjust slider look * rearrange parts of mediaentryeditor * test helper * styling * move controls inside umb-image-crop * seperate umg-cropper-gravity styling * corrected layout * more ui refinement * keep the idea of mandatory out for now. * remove double ; * removed testing code * JSON Property Value Convertor now has an array of property editors to exclude * Property Value Convertor for Media Picker 3 aka Media Picker with Local Crops * Experimenting on best approach to retrieve local crop in razor view when iterating over picked media items * Update ValueConvertor to use ImageCropperValue as part of the model for views as alot of existing CropUrls can then use it * Update extension methods to take an ImageCropperValue model (localCropData) * Forgot to update CSProj for new ValueConvertor * New GetCropUrl @Url.GetCropUrl(crop.Alias, media.LocalCrops) as oppposed to @Url.GetCropUrl(media.LocalCrops, cropAlias:crop.Alias, useCropDimensions: true) * Remove dupe item in CSProj * Use a contains as an opposed to Array.IndexOf * various corrections, SingleMode based on max 1, remove double checkerBackground, enforce validation for Crops, changed error indication * mediapicker v3 * correct version * fixing file ext label text color * clipboard features for MediaPicker v3 * highlight not allowed types * highlight trashed as an error * Media Types Video, Sound, Document and Vector Image * Rename to Audio and VectorGraphics * Add (SVG) in the name for Vector Graphics * adding CSV to Documents * remove this commented code. * remove this commented code * number range should not go below 0, at-least as default until we make that configurable. * use min not ng-min * description for local crops * Error/Limits highlighting reactive * visual adjustments * Enabling opening filtered folders + corrected select hover states * Varous fixes to resolve issues with unit tests. * Refactor MediaType Documents to only contain Article file type * mark as build-in * predefined MediaPicker3 DataTypes, renaming v2 to "old" * set scale bar current value after min and max has been set * added missing } * update when focal point is dragged * adjusted styling for Image Cropper property editor * correcting comment * remove todo - message for trashed media items works * Changed parameter ordering * Introduced new extension method on MediaWithCrops to get croppings urls in with full path * Reintroducing Single Item Mode * use Multiple instead of SingleMode * renaming and adding multiple to preconfigured datatypes * Change existing media picker to use the Clipboard type MEDIA, enabling shared functionality. * clean up unused clipboard parts * adjusted to new amount * correcting test * Fix unit test * Move MediaWithCrops to separate file and move to Core.Models * parseContentForPaste * clean up * ensure crops is an array. * actively enable focal points, so we dont set focal points that aren't used. * only accept files that matches file extensions from Umbraco Settings * Cleanup * Add references from MediaPicker3 to media * corrections from various feedback * remove comment * correct wording * use windowResizeListener * Show Media Type Selector if there is multiple types that matches the current upload batch * clean-up and refactoring * auto pick option Co-authored-by: Warren Buckley Co-authored-by: Niels Lyngsø Co-authored-by: Mads Rasmussen Co-authored-by: Andy Butland Co-authored-by: Bjarke Berg Co-authored-by: Sebastiaan Janssen Co-authored-by: Elitsa Marinovska --- .../upload/umbfiledropzone.directive.js | 91 +++++++++++-------- .../services/mediatypehelper.service.js | 43 ++++++--- src/Umbraco.Web.UI/Umbraco/config/lang/da.xml | 1 + src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 1 + .../Umbraco/config/lang/en_us.xml | 1 + 5 files changed, 87 insertions(+), 50 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 7f405eb28c..79dfee059e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -20,7 +20,7 @@ TODO angular.module("umbraco.directives") .directive('umbFileDropzone', - function ($timeout, Upload, localizationService, umbRequestHelper, overlayService) { + function ($timeout, Upload, localizationService, umbRequestHelper, overlayService, mediaHelper, mediaTypeHelper) { return { restrict: 'E', replace: true, @@ -88,21 +88,12 @@ angular.module("umbraco.directives") }); scope.queue = []; } - // One allowed type - if (scope.acceptedMediatypes && scope.acceptedMediatypes.length === 1) { - // Standard setup - set alias to auto select to let the server best decide which media type to use - if (scope.acceptedMediatypes[0].alias === 'Image') { - scope.contentTypeAlias = "umbracoAutoSelect"; - } else { - scope.contentTypeAlias = scope.acceptedMediatypes[0].alias; - } + // 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(); } - // More than one, open dialog - if (scope.acceptedMediatypes && scope.acceptedMediatypes.length > 1) { - _chooseMediaType(); - } } } @@ -146,8 +137,8 @@ angular.module("umbraco.directives") // set percentage property on file file.uploadProgress = progressPercentage; // set uploading status on file - file.uploadStatus = "uploading"; - } + file.uploadStatus = "uploading"; + } }) .success(function(data, status, headers, config) { if (data.notifications && data.notifications.length > 0) { @@ -195,35 +186,61 @@ angular.module("umbraco.directives") }); } - function _chooseMediaType() { + function _requestChooseMediaTypeDialog() { - const dialog = { - view: "itempicker", - filter: scope.acceptedMediatypes.length > 15, - availableItems: scope.acceptedMediatypes, - submit: function (model) { - scope.contentTypeAlias = model.selectedItem.alias; - _processQueueItem(); + if (scope.acceptedMediatypes.length === 1) { + // if only one accepted type, then we wont ask to choose. + return false; + } - overlayService.close(); - }, - close: function () { + var uploadFileExtensions = scope.queue.map(file => mediaHelper.getFileExtension(file.name)); - scope.queue.map(function (file) { - file.uploadStatus = "error"; - file.serverErrorMessage = "Cannot upload this file, no mediatype selected"; - scope.rejected.push(file); - }); - scope.queue = []; + var filteredMediaTypes = mediaTypeHelper.getTypeAcceptingFileExtensions(scope.acceptedMediatypes, uploadFileExtensions); - overlayService.close(); - } - }; + var mediaTypesNotFile = filteredMediaTypes.filter(mediaType => mediaType.alias !== "File"); - localizationService.localize("defaultdialogs_selectMediaType").then(value => { - dialog.title = value; + if (mediaTypesNotFile.length <= 1) { + // if only one or less accepted types when we have filtered type 'file' out, then we wont ask to choose. + return false; + } + + + localizationService.localizeMany(["defaultdialogs_selectMediaType", "mediaType_autoPickMediaType"]).then(function (translations) { + + filteredMediaTypes.push({ + alias: "umbracoAutoSelect", + name: translations[1], + icon: "icon-wand" + }); + + const dialog = { + view: "itempicker", + filter: filteredMediaTypes.length > 8, + availableItems: filteredMediaTypes, + submit: function (model) { + scope.contentTypeAlias = model.selectedItem.alias; + _processQueueItem(); + + overlayService.close(); + }, + close: function () { + + scope.queue.map(function (file) { + file.uploadStatus = "error"; + file.serverErrorMessage = "No files uploaded, no mediatype selected"; + scope.rejected.push(file); + }); + scope.queue = []; + + overlayService.close(); + } + }; + + dialog.title = translations[0]; overlayService.open(dialog); }); + + return true;// yes, we did open the choose-media dialog, therefor we return true. } scope.handleFiles = function(files, event) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js index a347279fdb..f6ac16a9bc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediatypehelper.service.js @@ -23,15 +23,15 @@ function mediaTypeHelper(mediaTypeResource, $q) { getAllowedImagetypes: function (mediaId){ // TODO: This is horribly inneficient - why make one request per type!? - //This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing + //This should make a call to c# to get exactly what it's looking for instead of returning every single media type and doing //some filtering on the client side. - //This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice + //This is also called multiple times when it's not needed! Example, when launching the media picker, this will be called twice //which means we'll be making at least 6 REST calls to fetch each media type // Get All allowedTypes return mediaTypeResource.getAllowedTypes(mediaId) .then(function(types){ - + var allowedQ = types.map(function(type){ return mediaTypeResource.getById(type.id); }); @@ -39,16 +39,8 @@ function mediaTypeHelper(mediaTypeResource, $q) { // Get full list return $q.all(allowedQ).then(function(fullTypes){ - // Find all the media types with an Image Cropper property editor - var filteredTypes = mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper']); - - // If there is only one media type with an Image Cropper we will return this one - if(filteredTypes.length === 1) { - return filteredTypes; - // If there is more than one Image cropper, custom media types have been added, and we return all media types with and Image cropper or UploadField - } else { - return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']); - } + // Find all the media types with an Image Cropper or Upload Field property editor + return mediaTypeHelperService.getTypeWithEditor(fullTypes, ['Umbraco.ImageCropper', 'Umbraco.UploadField']); }); }); @@ -68,6 +60,31 @@ function mediaTypeHelper(mediaTypeResource, $q) { } }); + }, + + getTypeAcceptingFileExtensions: function (mediaTypes, fileExtensions) { + return mediaTypes.filter(mediaType => { + var uploadProperty; + mediaType.groups.forEach(group => { + var foundProperty = group.properties.find(property => property.alias === "umbracoFile"); + if(foundProperty) { + uploadProperty = foundProperty; + } + }); + if(uploadProperty) { + var acceptedFileExtensions; + if(uploadProperty.editor === "Umbraco.ImageCropper") { + acceptedFileExtensions = Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes; + } else if(uploadProperty.editor === "Umbraco.UploadField") { + acceptedFileExtensions = (uploadProperty.config.fileExtensions && uploadProperty.config.fileExtensions.length > 0) ? uploadProperty.config.fileExtensions.map(x => x.value) : null; + } + if(acceptedFileExtensions && acceptedFileExtensions.length > 0) { + return fileExtensions.length === fileExtensions.filter(fileExt => acceptedFileExtensions.includes(fileExt)).length; + } + return true; + } + return false; + }); } }; diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml index 4abcdf8a40..62e3ad6b64 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/da.xml @@ -343,6 +343,7 @@ Kopiering af medietypen fejlede Flytning af medietypen fejlede + Auto vælg Kopiering af medlemstypen fejlede diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index cbb6902d74..7172e44b36 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -362,6 +362,7 @@ Failed to copy media type Failed to move media type + Auto pick Failed to copy member type diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 590a248393..a159b78b83 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -369,6 +369,7 @@ Failed to copy media type Failed to move media type + Auto pick Failed to copy member type