From 4bd263e7bcc5fb09678e2a552a06cae2913bce45 Mon Sep 17 00:00:00 2001 From: Claus Date: Mon, 16 Oct 2017 13:52:17 +0200 Subject: [PATCH] U4-10524 Show warning in UI for the media/content picker that is referencing a trashed or deleted item U4-10533 Show warning in UI for the link picker that is referencing a trashed or deleted item cherrypicked from dev-v7. --- .../src/less/property-editors.less | 149 +++++++++--------- .../contentpicker/contentpicker.controller.js | 12 +- .../contentpicker/contentpicker.html | 3 + .../mediapicker/mediapicker.controller.js | 56 +++++-- .../mediapicker/mediapicker.html | 70 ++++---- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 9 ++ src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 9 ++ .../umbraco/config/lang/en_us.xml | 9 ++ .../Models/ContentEditing/EntityBasic.cs | 3 - .../Models/Mapping/EntityModelMapper.cs | 2 +- 10 files changed, 189 insertions(+), 133 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 2388aae2b2..723a365eb0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -126,21 +126,21 @@ ul.color-picker li a { // Media picker // -------------------------------------------------- .umb-mediapicker .add-link { - display: inline-block; - height: 120px; - width: 120px; - text-align: center; - color: @gray-8; - border: 2px @gray-8 dashed; - line-height: 120px; - text-decoration: none; + display: flex; + justify-content:center; + align-items:center; + width: 120px; + text-align: center; + color: @gray-8; + border: 2px @gray-8 dashed; + text-decoration: none; - transition: all 150ms ease-in-out; + transition: all 150ms ease-in-out; - &:hover { - color: @turquoise-d1; - border-color: @turquoise; - } + &:hover { + color: @turquoise-d1; + border-color: @turquoise; + } } .umb-mediapicker .picked-image { @@ -207,11 +207,10 @@ ul.color-picker li a { .umb-mediapicker .umb-sortable-thumbnails li { flex-direction: column; - margin: 0; + margin: 0 5px 0 0; padding: 5px; } - .umb-sortable-thumbnails li:hover a { display: flex; justify-content: center; @@ -219,16 +218,20 @@ ul.color-picker li a { } .umb-sortable-thumbnails li img { - max-width:100%; - max-height:100%; - margin:auto; - display:block; - background-image: url(../img/checkered-background.png); + max-width:100%; + max-height:100%; + margin:auto; + display:block; + background-image: url(../img/checkered-background.png); } -.umb-sortable-thumbnails li img.noScale{ - max-width: none !important; - max-height: none !important; +.umb-sortable-thumbnails li img.trashed { + opacity:0.3; +} + +.umb-sortable-thumbnails li img.noScale { + max-width: none !important; + max-height: none !important; } .umb-sortable-thumbnails .umb-icon-holder { @@ -254,8 +257,8 @@ ul.color-picker li a { } .umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { - opacity: 1; - visibility: visible; + opacity: 1; + visibility: visible; } .umb-sortable-thumbnails .umb-sortable-thumbnails__action { @@ -285,27 +288,27 @@ ul.color-picker li a { // ------------------------------------------------- .umb-cropper{ - position: relative; + position: relative; } .umb-cropper img, .umb-cropper-gravity img{ - position: relative; - max-width: 100%; - height: auto; - top: 0; - left: 0; + position: relative; + max-width: 100%; + height: auto; + top: 0; + left: 0; } .umb-cropper img { - max-width: none; + max-width: none; } .umb-cropper .overlay, .umb-cropper-gravity .overlay { - top: 0; - left: 0; - cursor: move; - z-index: 6001; - position: absolute; + top: 0; + left: 0; + cursor: move; + z-index: 6001; + position: absolute; } .umb-cropper .viewport{ @@ -317,43 +320,43 @@ ul.color-picker li a { } .umb-cropper-gravity .viewport{ - overflow: hidden; - position: relative; - width: 100%; - height: 100%; + overflow: hidden; + position: relative; + width: 100%; + height: 100%; } .umb-cropper .viewport:after { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5999; - -moz-opacity: .75; - opacity: .75; - filter: alpha(opacity=7); - -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5999; + -moz-opacity: .75; + opacity: .75; + filter: alpha(opacity=7); + -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); } .umb-cropper-gravity .overlay{ - width: 14px; - height: 14px; - text-align: center; - border-radius: 20px; - background: @turquoise; - border: 3px solid @white; - opacity: 0.8; + width: 14px; + height: 14px; + text-align: center; + border-radius: 20px; + background: @turquoise; + border: 3px solid @white; + opacity: 0.8; } .umb-cropper-gravity .overlay i { - font-size: 26px; - line-height: 26px; - opacity: 0.8 !important; + font-size: 26px; + line-height: 26px; + opacity: 0.8 !important; } .umb-cropper .crop-container { @@ -361,16 +364,16 @@ ul.color-picker li a { } .umb-cropper .crop-slider { - padding: 10px; - border-top: 1px solid @gray-10; - margin-top: 10px; - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - @media (min-width: 769px) { + padding: 10px; + border-top: 1px solid @gray-10; + margin-top: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + @media (min-width: 769px) { padding: 10px 50px 10px 50px; - } + } } .umb-cropper .crop-slider i { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index 9b3316ec1a..229ec614de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it -function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper) { +function contentPickerController($scope, entityResource, editorState, iconHelper, $routeParams, angularHelper, navigationService, $location, miniEditorHelper, localizationService) { var unsubscribe; @@ -154,7 +154,6 @@ function contentPickerController($scope, entityResource, editorState, iconHelper } } - if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query dialogOptions.startNodeId = -1; @@ -287,8 +286,12 @@ function contentPickerController($scope, entityResource, editorState, iconHelper entityResource.getUrl(entity.id, entityType).then(function(data){ // update url angular.forEach($scope.renderModel, function(item){ - if(item.id === entity.id) { - item.url = data; + if (item.id === entity.id) { + if (entity.trashed) { + item.url = localizationService.dictionary.general_recycleBin; + } else { + item.url = data; + } } }); }); @@ -330,6 +333,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper "icon": item.icon, "path": item.path, "url": item.url, + "trashed": item.trashed, "published": (item.metaData && item.metaData.IsPublished === false && entityType === "Document") ? false : true // only content supports published/unpublished content so we set everything else to published so the UI looks correct }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html index 75cc74f02d..1755840cc8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.html @@ -1,5 +1,8 @@
+

+

+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 6652c52cc0..21a0717779 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location) { + function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location, localizationService) { //check the pre-values for multi-picker var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; @@ -25,17 +25,44 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // the mediaResource has server side auth configured for which the user must have // access to the media section, if they don't they'll get auth errors. The entityResource // acts differently in that it allows access if the user has access to any of the apps that - // might require it's use. Therefore we need to use the metatData property to get at the thumbnail + // might require it's use. Therefore we need to use the metaData property to get at the thumbnail // value. - entityResource.getByIds(ids, "Media").then(function (medias) { + entityResource.getByIds(ids, "Media").then(function(medias) { - _.each(medias, function (media, i) { + // The service only returns item results for ids that exist (deleted items are silently ignored). + // This results in the picked items value to be set to contain only ids of picked items that could actually be found. + // Since a referenced item could potentially be restored later on, instead of changing the selected values here based + // on whether the items exist during a save event - we should keep "placeholder" items for picked items that currently + // could not be fetched. This will preserve references and ensure that the state of an item does not differ depending + // on whether it is simply resaved or not. + // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders + // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. + + medias = _.map(ids, + function(id) { + var found = _.find(medias, + function(m) { + return m.udi === id || m.id === id; + }); + if (found) { + return found; + } else { + return { + name: localizationService.dictionary.mediaPicker_deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); - //only show non-trashed items - if (media.parentId >= -1) { - - if (!media.thumbnail) { + _.each(medias, + function(media, i) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } @@ -43,12 +70,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl if ($scope.model.config.idType === "udi") { $scope.ids.push(media.udi); - } - else { + } else { $scope.ids.push(media.id); } - } - }); + }); $scope.sync(); }); @@ -80,8 +105,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl submit: function(model) { _.each(model.selectedImages, function(media, i) { - - if (!media.thumbnail) { + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } @@ -99,10 +124,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.mediaPickerOverlay.show = false; $scope.mediaPickerOverlay = null; - } }; - }; $scope.sortableOptions = { @@ -140,5 +163,4 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl //update the display val again if it has changed from the server setupViewModel(); }; - }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index f3aab992dd..70dd9ed2e5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -1,47 +1,47 @@
-
    -
  • +

    +

    - - +
    +
      +
    • - - - + + - - - - {{image.name}} - + + + - + + + + {{image.name}} + -
    • -
    + +
  • - +
+ + + +
- - + +
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index d01bea3d36..d64e8b82ed 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -784,6 +784,15 @@ Mange hilsner fra Umbraco robotten Du har ikke konfigureret nogen godkendte farver + + Du har valgt et dokument som er slettet eller lagt i papirkurven + Du har valgt dokumenter som er slettede eller lagt i papirkurven + + + Du har valgt et medie som er slettet eller lagt i papirkurven + Du har valgt medier som er slettede eller lagt i papirkurven + Slettet medie + indtast eksternt link vælg en intern side diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 1b71237ea4..4169c88335 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -952,6 +952,15 @@ To manage your website, simply open the Umbraco back office and start adding con You have not configured any approved colours + + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Deleted item + enter external link choose internal page 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 c271ce8f65..13e0ebdad5 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1011,6 +1011,15 @@ To manage your website, simply open the Umbraco back office and start adding con You have not configured any approved colors + + You have picked a content item currently deleted or in the recycle bin + You have picked content items currently deleted or in the recycle bin + + + You have picked a media item currently deleted or in the recycle bin + You have picked media items currently deleted or in the recycle bin + Deleted item + enter external link choose internal page diff --git a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs index 9f6e5b28da..12afceea05 100644 --- a/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/EntityBasic.cs @@ -2,10 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Models.Validation; diff --git a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs index 5be9d550e5..3d825ab055 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityModelMapper.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap() .ForMember(x => x.Udi, expression => expression.MapFrom(x => Udi.Create(UmbracoObjectTypesExtensions.GetUdiType(x.NodeObjectTypeId), x.Key))) .ForMember(basic => basic.Icon, expression => expression.MapFrom(entity => entity.ContentTypeIcon)) - .ForMember(dto => dto.Trashed, expression => expression.Ignore()) + .ForMember(dto => dto.Trashed, expression => expression.MapFrom(x => x.Trashed)) .ForMember(x => x.Alias, expression => expression.Ignore()) .AfterMap((entity, basic) => {