diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html index 523b0fe7d3f1..495c704e287e 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html @@ -1,11 +1,4 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html index dbe157cf84b2..b3362fcda94e 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html @@ -1,13 +1,5 @@
-
- \ No newline at end of file + \ No newline at end of file diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html index ccc3af22e841..bc290fe8fb3f 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html @@ -1,50 +1,3 @@ -
- - +
\ No newline at end of file diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 387f1ef652be..049b617f930f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -2201,6 +2201,8 @@ Mange hilsner fra Umbraco robotten Avanceret Skjul indholdseditoren Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet + Direkte redigering + Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet. Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem? Annuller oprettelse? @@ -2218,6 +2220,7 @@ Mange hilsner fra Umbraco robotten Tillad kun specifikke blok-typer Tilladte blok-typer Vælg de blok-typer, der er tilladt i dette område, og evt. også hvor mange af hver type, redaktørerne skal tilføje til området. + Når denne er tom er alle block-typer tilladt for områder tilladt. Er du sikker på, at du vil slette dette område? Alle blokke, der er oprettet i dette område, vil blive slettet. Layout-opsætning @@ -2255,9 +2258,9 @@ Mange hilsner fra Umbraco robotten Tilføj katalog udseende Tilføj Blok Tilføj gruppe - Tilføj gruppe eller block - Sæt minimum krav for denne tilladelse - Set maksimum krav for denne tilladelse + Tilføj gruppe eller Blok + Sæt minimum krav + Sæt maksimum krav Blok Blok Indstillinger @@ -2267,6 +2270,9 @@ Mange hilsner fra Umbraco robotten Installer demo konfiguration Dette indeholder Blokke for Overskrift, Beriget-Tekst, Billede og To-Koloners-Layout.]]> Installer + Sortings tilstand + Afslut sortings tilstand + Dette område alias skal være unikt sammenlignet med andre områder af denne Blok. Hvad er Indholdsskabeloner? diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 82311e4311ff..99428a8837ad 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -2747,7 +2747,9 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Settings Advanced Hide content editor - Hide the content edit button and the content editor from the Block Editor overlay + Hide the content edit button and the content editor from the Block Editor overlay. + Inline editing + Enables inline editing for the first Property. Additional properties can be edited in the overlay. You have made changes to this content. Are you sure you want to discard them? Discard creation? @@ -2779,6 +2781,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Make this block available in the root of the layout. Allow in areas Make this block available within other Blocks. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. @@ -2805,17 +2808,20 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Block Add group Pick group or Block - Set minimum requirement for this allowance - Set maximum requirement for this allowance + Set a minimum requirement + Set a maximum requirement Block Block Settings Areas Advanced - Allowance + Permissions Install Sample Configuration Install + Sort mode + End sort mode + This Areas Alias must be unique compared to the other Areas of this Block. What are Content Templates? diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index c0bc8763f7d6..483219eeb842 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2851,6 +2851,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Advanced Hide content editor Hide the content edit button and the content editor from the Block Editor overlay + Inline editing + Enables inline editing for the first Property. Additional properties can be edited in the overlay. You have made changes to this content. Are you sure you want to discard them? Discard creation? @@ -2882,6 +2884,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Make this block available in the root of the layout. Allow in areas Make this block available within other Blocks. + When empty all Blocks allowed for Areas can be created. Areas Grid Columns for Areas Define how many columns that will be available for areas. If not defined, the number of columns defined for the entire layout will be used. @@ -2908,17 +2911,20 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Block Add group Pick group or Block - Set minimum requirement for this allowance - Set maximum requirement for this allowance + Set a minimum requirement + Set a maximum requirement Block Block Settings Areas Advanced - Allowance + permissions Install Sample Configuration Install + Sort mode + End sort mode + This Areas Alias must be unique compared to the other Areas of this Block. What are Content Templates? diff --git a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs index 0a5bacad149a..f635eb632493 100644 --- a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs @@ -40,6 +40,9 @@ public class BlockGridBlockConfiguration : IBlockConfiguration [DataMember(Name ="columnSpanOptions")] public BlockGridColumnSpanOption[] ColumnSpanOptions { get; set; } = Array.Empty(); + [DataMember(Name ="rowMinSpan")] + public int? RowMinSpan { get; set; } + [DataMember(Name ="rowMaxSpan")] public int? RowMaxSpan { get; set; } @@ -82,6 +85,9 @@ public class BlockGridBlockConfiguration : IBlockConfiguration [DataMember(Name ="editorSize")] public string? EditorSize { get; set; } + [DataMember(Name ="inlineEditing")] + public bool InlineEditing { get; set; } + [DataMember(Name ="forceHideContentEditorInOverlay")] public bool ForceHideContentEditorInOverlay { get; set; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index 702cd5aeda92..5a30f81d4b1d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -24,7 +24,8 @@ // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) propertyAlias: "@", showInherit: "<", - inheritsFrom: "<" + inheritsFrom: "<", + hideLabel: " { + Utilities.forEach(vm.actions || [], action => { - if (action.labelKey) { - localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => { - action.label = data; - }); - - action.useLegacyIcon = action.useLegacyIcon === false ? false : true; - action.icon = (action.useLegacyIcon ? 'icon-' : '') + action.icon; - } - }); - } + if (action.labelKey) { + localizationService.localize(action.labelKey, (action.labelTokens || []), action.label).then(data => { + action.label = data; + }); + + action.useLegacyIcon = action.useLegacyIcon === false ? false : true; + action.icon = (action.useLegacyIcon && action.icon.indexOf('icon-') !== 0 ? 'icon-' : '') + action.icon; + } + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js new file mode 100644 index 000000000000..be0338093cf5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/mediaItemResolver.filter.js @@ -0,0 +1,65 @@ +(function () { + "use strict"; + + function mediaItemResolverFilterService(mediaResource, eventsService) { + + var mediaKeysRequest = []; + var mediaItemCache = []; + + var service = { + + getByKey: function (key) { + // Is it cached, then get that: + const cachedMediaItem = mediaItemCache.find(cache => key === cache.key); + if(cachedMediaItem) { + return cachedMediaItem; + } + + // check its not already being loaded, and then start loading: + if(mediaKeysRequest.indexOf(key) === -1) { + mediaKeysRequest.push(key); + mediaResource.getById(key).then(function (mediaItem) { + if(mediaItem) { + mediaItemCache.push(mediaItem); + } + }); + } + + return null; + } + }; + + eventsService.on("editors.media.saved", function (name, args) { + const index = mediaItemCache.findIndex(cache => cache.key === args.media.key); + if(index !== -1) { + mediaItemCache[index] = args.media; + } + }); + + return service; + + } + + angular.module("umbraco.filters").factory("mediaItemResolverFilterService", mediaItemResolverFilterService); + + + // Filter loads Media Item Model from a Media Key. + // Usage: {{ myMediaProperty[0].mediaKey | mediaItemResolver }} + angular.module("umbraco.filters").filter("mediaItemResolver", function (mediaItemResolverFilterService) { + + mediaItemResolverFilter.$stateful = true; + function mediaItemResolverFilter(input) { + + // Check we have a value at all + if (typeof input === 'string' && input.length > 0) { + return mediaItemResolverFilterService.getByKey(input); + } + + return null; + } + + return mediaItemResolverFilter; + + }); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less index 0dd7bfc7f4c5..4eaf00724ca3 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/subheader/umb-editor-sub-header.less @@ -20,6 +20,12 @@ background-color: @white; border-color: @white; } +.umb-editor-sub-header--blue { + background-color: @ui-selected-border; + border-color: @ui-selected-border; + color: @white; + border-radius: 3px; +} .umb-editor-sub-header.--state-selection { padding-left: 10px; diff --git a/src/Umbraco.Web.UI.Client/src/less/rte.less b/src/Umbraco.Web.UI.Client/src/less/rte.less index 8ff4d128ed01..ed25ce4e908e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/rte.less +++ b/src/Umbraco.Web.UI.Client/src/less/rte.less @@ -20,7 +20,7 @@ } .umb-rte.--initialized .umb-rte-editor-con { height:auto; - min-height: 100px; + min-height: 95px; visibility: visible; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index f00ba725b251..cc41896a0f31 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -1,13 +1,13 @@
+ ng-class="{'hidelabel':vm.hideLabel || vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html index a4efb4171740..710dd15e5374 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridblock/gridblock.editor.html @@ -53,6 +53,23 @@ padding-top:2px; padding-bottom:2px; } + + :host { + --inherited--column-gap: var(--umb-block-grid--column-gap, 10px); + --inherited--row-gap: var(--umb-block-grid--row-gap, 10px); + --inherited--areas-column-gap: var(--umb-block-grid--areas-column-gap, 10px); + --inherited--areas-row-gap: var(--umb-block-grid--areas-row-gap, 10px); + } + + [part='area-container'] { + box-sizing: border-box; + padding: 10px; + --umb-block-grid--column-gap: var(--inherited--column-gap, 10px); + --umb-block-grid--row-gap: var(--inherited--row-gap, 10px); + --umb-block-grid--areas-column-gap: var(--inherited--areas-column-gap, 10px); + --umb-block-grid--areas-row-gap: var(--inherited--areas-row-gap, 10px); + } +
@@ -64,6 +81,6 @@ {{block.label}} - +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js index 2a77e81b5cb2..3d55ea1836ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.controller.js @@ -1,25 +1,69 @@ (function () { 'use strict'; - function GridInlineBlockEditor($scope, $element) { + function GridInlineBlockEditor($scope, $compile, $element) { const vm = this; + var propertyEditorElement; + vm.$onInit = function() { - const host = $element[0].getRootNode(); + + vm.property = $scope.block.content.variants[0].tabs[0]?.properties[0]; + + if (vm.property) { + vm.propertySlotName = "umbBlockGridProxy_" + vm.property.alias + "_" + String.CreateGuid(); + + propertyEditorElement = $('
'); + propertyEditorElement.html( + ` + - console.log(document.styleSheets) + + - for (const stylesheet of document.styleSheets) { + + ` + ); + + $element[0].addEventListener('umb-rte-focus', onRteFocus); + $element[0].addEventListener('umb-rte-blur', onRteBlur); + + const connectedCallback = () => { + + $compile(propertyEditorElement)($scope) + }; + + const event = new CustomEvent("UmbBlockGrid_AppendProperty", {composed: true, bubbles: true, detail: {'property': propertyEditorElement[0], 'contentUdi': $scope.block.layout.contentUdi, 'slotName': vm.propertySlotName, 'connectedCallback':connectedCallback}}); + + $element[0].dispatchEvent(event); + + } + } - console.log(stylesheet); - const styleEl = document.createElement('link'); - styleEl.setAttribute('rel', 'stylesheet'); - styleEl.setAttribute('type', stylesheet.type); - styleEl.setAttribute('href', stylesheet.href); + function onRteFocus() { + $element[0].classList.add('umb-block-grid--force-focus'); + } + function onRteBlur() { + $element[0].classList.remove('umb-block-grid--force-focus'); + } - host.appendChild(styleEl); + vm.$onDestroy = function() { + if (vm.property) { + const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': vm.propertySlotName}}); + $element[0].dispatchEvent(event); } + + $element[0].removeEventListener('umb-rte-focus', onRteFocus); + $element[0].removeEventListener('umb-rte-blur', onRteBlur); + propertyEditorElement = null; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html index ca12c9dd9846..3a67a1be8838 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.html @@ -1,24 +1,6 @@ -
- -
- -
-
- - - - -
- +
+ + {{block.label}} +
- + - +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less deleted file mode 100644 index b8ffcff3ec73..000000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridinlineblock/gridinlineblock.editor.less +++ /dev/null @@ -1,173 +0,0 @@ -.blockelement-inlineblock-editor { - display: block; - margin-bottom: 4px; - margin-top: 4px; - border: 1px solid @gray-9; - border-radius: @baseBorderRadius; - transition: border-color 120ms, background-color 120ms; - - .umb-block-list__block:not(.--active) &:hover { - border-color: @gray-8; - } - - .umb-editor-tab-bar { - margin: 0; - position: static; - padding: 0; - } - - > button { - width: 100%; - min-height: 48px; - cursor: pointer; - color: @ui-action-discreet-type; - text-align: left; - padding-left: 10px; - padding-bottom: 2px; - user-select: none; - background-color: white; - - .caret { - vertical-align: middle; - transform: rotate(-90deg); - transition: transform 80ms ease-out; - } - - .icon { - font-size: 1.1rem; - display: inline-block; - vertical-align: middle; - } - - span.name { - position: relative; - display: inline-block; - vertical-align: middle; - } - - &:hover { - color: @ui-action-discreet-type-hover; - border-color: @gray-8; - } - } - - ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & { - > button { - color: @formErrorText; - - .show-validation-type-warning & { - color: @formWarningText; - } - - span.caret { - border-top-color: @formErrorText; - - .show-validation-type-warning & { - border-top-color: @formWarningText; - } - } - } - } - - ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & { - > button { - span.name { - &::after { - content: "!"; - text-align: center; - position: absolute; - top: -6px; - right: -15px; - min-width: 10px; - color: @white; - background-color: @ui-active-type; - border: 2px solid @white; - border-radius: 50%; - font-size: 10px; - font-weight: bold; - padding: 2px; - line-height: 10px; - background-color: @formErrorText; - - .show-validation-type-warning & { - background-color: @formWarningText; - } - - font-weight: 900; - animation-duration: 1.4s; - animation-iteration-count: infinite; - animation-name: blockelement-inlineblock-editor--badge-bounce; - animation-timing-function: ease; - - @keyframes blockelement-inlineblock-editor--badge-bounce { - 0% { - transform: translateY(0); - } - - 20% { - transform: translateY(-4px); - } - - 40% { - transform: translateY(0); - } - - 55% { - transform: translateY(-2px); - } - - 70% { - transform: translateY(0); - } - - 100% { - transform: translateY(0); - } - } - } - } - } - } -} - -.umb-block-list__block.--active { - border-color: @gray-8; - box-shadow: 0 0 2px 0px rgba(0, 0, 0, 0.05); - - > .umb-block-list__block--content { - > .umb-block-list__block--view { - > .blockelement-inlineblock-editor { - > button { - > .caret { - transform: rotate(0deg); - } - } - } - } - } -} - -.blockelement-inlineblock-editor__inner { - border-top: 1px solid @gray-8; - background-color: @gray-12; - - > * > * > * > .umb-group-panel { - background-color: transparent; - box-shadow: none; - margin-top: 10px; - margin-bottom: 0; - > .umb-group-panel__content .umb-property { - margin-bottom: 20px; - } - } - .umb-group-panel + .umb-group-panel { - margin-top: 20px; - } - &.--singleGroup > * > * > * > .umb-group-panel { - margin-top: 0; - > .umb-group-panel__header { - display: none; - } - } - -} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html new file mode 100644 index 000000000000..bd6eebfac665 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/gridsortblock/gridsortblock.editor.html @@ -0,0 +1,92 @@ + + +
+ + + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js deleted file mode 100644 index 2dc717d8ef57..000000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/heroblock/heroblock.editor.controller.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - 'use strict'; - - function HeroBlockEditor($scope, mediaResource, mediaHelper) { - - var unsubscribe = []; - - const bc = this; - - $scope.$watch("block.data.image", function(newValue, oldValue) { - if (newValue !== oldValue) { - bc.retrieveMedia(); - } - }, true); - - bc.retrieveMedia = function() { - - if($scope.block.data.image && $scope.block.data.image.length > 0) { - mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) { - - var mediaPath = mediaEntity.mediaLink; - - //set a property on the 'scope' for the returned media object. - bc.mediaName = mediaEntity.name; - bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath); - bc.imageSource = mediaPath; - }); - } - } - - bc.retrieveMedia(); - - - - $scope.$on("$destroy", function () { - for (const subscription of unsubscribe) { - subscription(); - } - }); - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.HeroBlockEditor", HeroBlockEditor); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js deleted file mode 100644 index 2f79f3b9f1c3..000000000000 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridentryeditors/mediablock/mediablock.editor.controller.js +++ /dev/null @@ -1,47 +0,0 @@ -(function () { - 'use strict'; - - function MediaBlockEditor($scope, mediaResource, mediaHelper) { - - var unsubscribe = []; - - const bc = this; - - $scope.$watch("block.data.image", function(newValue, oldValue) { - if (newValue !== oldValue) { - bc.retrieveMedia(); - } - }, true); - - bc.retrieveMedia = function() { - - if($scope.block.data.image && $scope.block.data.image.length > 0) { - mediaResource.getById($scope.block.data.image[0].mediaKey).then(function (mediaEntity) { - - var mediaPath = mediaEntity.mediaLink; - - //set a property on the 'scope' for the returned media object - bc.icon = mediaEntity.contentType.icon; - bc.mediaName = mediaEntity.name; - bc.fileExtension = mediaHelper.getFileExtension(mediaPath); - bc.isImage = mediaHelper.detectIfImageByExtension(mediaPath); - bc.imageSource = mediaHelper.getThumbnailFromPath(mediaPath); - }); - } - } - - bc.retrieveMedia(); - - - - $scope.$on("$destroy", function () { - for (const subscription of unsubscribe) { - subscription(); - } - }); - - } - - angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.MediaBlockEditor", MediaBlockEditor); - -})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less index 6cfdb05482fe..38ff8086c62a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less @@ -21,13 +21,18 @@ .umb-block-grid__layout-item { position: relative; &:hover { - z-index: 3; -/* - > .umb-block-grid__force-left, - > .umb-block-grid__force-right { + + > ng-form > .umb-block-grid__block--context { z-index: 4; } - */ + + > ng-form > .umb-block-grid__block--inline-create-button, + > ng-form > .umb-block-grid__block--validation-border, + > ng-form > .umb-block-grid__block--actions, + > ng-form > .umb-block-grid__force-left, + > ng-form > .umb-block-grid__force-right { + z-index: 3; + } } } @@ -49,52 +54,12 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti pointer-events: none; } -/*.umb-block-grid__block--validation-badge { - display:none; -} -ng-form.ng-invalid-val-server-match-settings > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge, -ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--validation-badge { - display:block; - text-align: center; - position: absolute; - top: -9px; - right: -9px; - min-width: 10px; - color: @white; - border: 2px solid @white; - border-radius: 50%; - font-size: 10px; - font-weight: bold; - padding: 2px; - line-height: 10px; - background-color: @formErrorText; - .show-validation-type-warning & { - background-color: @formWarningText; - } - font-weight: 900; - pointer-events: none; - - animation-duration: 1.4s; - animation-iteration-count: infinite; - animation-name: blockelement-inlineblock-editor--badge-bounce; - animation-timing-function: ease; - @keyframes blockelement-inlineblock-editor--badge-bounce { - 0% { transform: translateY(0); } - 20% { transform: translateY(-4px); } - 40% { transform: translateY(0); } - 55% { transform: translateY(-2px); } - 70% { transform: translateY(0); } - 100% { transform: translateY(0); } - } -} -*/ - .umb-block-grid__block { position: relative; width: 100%; height: 100%; - --umb-block-grid__block--show-ui: 0;// Publicly available. + --umb-block-grid--block-ui-opacity: 0; --umb-block-grid--hint-area-ui: 0; &::after { @@ -136,7 +101,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti --umb-block-grid--hint-area-ui: 1; &::after { - /*border-color: @blueDark;*/ display: var(--umb-block-grid--block-ui-display, block); animation: umb-block-grid__block__border-pulse 400ms ease-in-out alternate infinite; @keyframes umb-block-grid__block__border-pulse { @@ -211,18 +175,21 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti } } &.--block-ui-visible { + > .umb-block-grid__block--context { /* take full width to prevent interaction with elements behind.*/ left: 0; } .umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) { --umb-block-grid--block-ui-display: none; - .umb-block-grid__layout-item { - pointer-events: none; - } - .umb-block-grid__block { - pointer-events: none; - } + pointer-events: none; + } + .umb-block-grid__layout-item { + pointer-events: none; + } + .umb-block-grid__block { + pointer-events: none; + --umb-block-grid--block-ui-opacity: 0; } } @@ -236,48 +203,34 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti &.--active { /** Avoid displaying hover when dragging-mode */ - --umb-block-grid--block_ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); + --umb-block-grid--block-ui-opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); > .umb-block-grid__block--context { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } &:not(.--scale-mode) { > .umb-block-grid__block--actions { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } > umb-block-grid-block > umb-block-grid-entries > .umb-block-grid__layout-container > .umb-block-grid__area-actions { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } } > .umb-block-grid__scale-handler { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } > .umb-block-grid__force-left, > .umb-block-grid__force-right { - opacity: var(--umb-block-grid--block_ui-opacity); + opacity: var(--umb-block-grid--block-ui-opacity); } } - - /* - &.--show-validation { - ng-form.ng-invalid-val-server-match-content > & { - border: 2px solid @formErrorText; - border-radius: @baseBorderRadius; - } - } - */ } ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--actions { opacity: 1; } -/* -ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__block--context { - opacity: 1; -} -*/ .umb-block-grid__block--view { height: 100%; @@ -291,10 +244,20 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl top: -20px; right: 0; font-size: 12px; - z-index: 2; + z-index: 4; display: var(--umb-block-grid--block-ui-display, flex); justify-content: end; + /** prevent interaction with inline-create button just beneath the context-bar: */ + ::after { + content: ''; + position: absolute; + top: 100%; + left: 0; + right: 0; + height: 12px; + } + .__context-bar { padding: 0 9px; padding-top: 1px; @@ -509,27 +472,6 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl } } -/* -umb-block-grid-block { - - > div { - position: relative; - width: 100%; - min-height: @umb-block-grid__item_minimum_height; - background-color: @white; - border-radius: @baseBorderRadius; - box-sizing: border-box; - } - -} -*/ - -/* -.blockelement__draggable-element { - cursor: grab; -} -*/ - .umb-block-grid__scale-handler { cursor: nwse-resize; @@ -586,7 +528,7 @@ umb-block-grid-block { .umb-block-grid__block--inline-create-button { top: 0px; position: absolute; - z-index: 1; + z-index: 1; /** overwritten for the first one of an area. */ /** Avoid showing inline-create in dragging-mode */ opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); @@ -594,6 +536,12 @@ umb-block-grid-block { .umb-block-grid__block--inline-create-button.--above { left: 0; width: 100%; + + top: calc(var(--umb-block-grid--row-gap, 0px) * -0.5); +} +.umb-block-grid__layout-item:first-of-type .umb-block-grid__block--inline-create-button.--above { + /* Do not use row-gap if the first one. */ + top: 0; } .umb-block-grid__block--inline-create-button.--above.--at-root { /* If at root, and full-width then become 40px wider: */ @@ -601,14 +549,9 @@ umb-block-grid-block { left: calc(-20px * var(--calc)); width: calc(100% + 40px * var(--calc)); } + .umb-block-grid__block--inline-create-button.--after { - right: 1px; -} -.umb-block-grid__block--inline-create-button.--after.--detector { - width: 10px; - margin-right: -10px; - height: 100%; - z-index: 0; + right: calc(1px - (var(--umb-block-grid--column-gap, 0px) * 0.5)); } .umb-block-grid__block--inline-create-button.--after.--at-root { /* If at root, and full-width then move a little out to the right: */ @@ -624,8 +567,8 @@ umb-block-grid-block { pointer-events: none; } -.umb-block-grid__block--after-inline-create-button { - z-index:2; +.umb-block-grid__block--last-inline-create-button { + z-index:4; width: 100%; /* Move inline create button slightly up, to avoid collision with others*/ margin-bottom: -7px; @@ -746,13 +689,11 @@ umb-block-grid-block { align-items: center; justify-content: center; - /* TODO: dont use --umb-text-color, its temporary to inherit UI */ - color: var(--umb-text-color, @ui-action-discreet-type); + color: var(--umb-block-grid--text-color, @ui-action-discreet-type); font-weight: bold; padding: 5px 15px; - /* TODO: dont use --umb-text-color, its temporary to inherit UI */ - border: 1px dashed var(--umb-text-color, @ui-action-discreet-border); + border: 1px dashed var(--umb-block-grid--text-color, @ui-action-discreet-border); border-radius: @baseBorderRadius; box-sizing: border-box; @@ -760,24 +701,14 @@ umb-block-grid-block { height: 100%; &:hover { - color: var(--umb-text-color, @ui-action-discreet-type-hover); - border-color: var(--umb-text-color, @ui-action-discreet-border-hover); + color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-type-hover); + border-color: var(--umb-block-grid--text-color-hover, @ui-action-discreet-border-hover); text-decoration: none; z-index: 1; } } } - -/** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/ -/* -.umb-block-grid__layout-container.--droppable-indication { - .umb-block-grid__area-actions { - display: none; - } -} -*/ - .umb-block-grid__layout-item-placeholder { background: transparent; border-radius: 3px; @@ -859,16 +790,19 @@ umb-block-grid-block { content: ''; position: absolute; inset: 0; - /* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */ - top:2px; - bottom: 2px; + top:0; + bottom: 0; border-radius: 3px; border: 1px solid rgba(@gray-5, 0.3); pointer-events: none; opacity: var(--umb-block-grid--show-area-ui, 0); transition: opacity 240ms; + z-index:3; } .umb-block-grid__area.--highlight::after { + /* Moved slightly in to align with the inline-create button, which is moved slightly in to avoid collision with other create buttons. */ + top:2px; + bottom: 2px; /** Avoid displaying highlight when in dragging-mode */ opacity: calc(1 - var(--umb-block-grid--dragging-mode, 0)); border-color: @blueDark; @@ -899,32 +833,11 @@ umb-block-grid-block { z-index: 1; cursor: nwse-resize; } -/* -.umb-block-grid__scalebox { - position: absolute; - top:0; - left:0; - z-index: 10; - cursor: nwse-resize; - transition: background-color 240ms ease-in; - animation: umb-block-grid__scalebox__pulse 400ms ease-in-out alternate infinite; - @keyframes umb-block-grid__scalebox__pulse { - 0% { background-color: rgba(@blueMidLight, 0.33); } - 100% { background-color: rgba(@blueMidLight, 0.22); } - } -} -*/ -/* -.umb-block-grid__layout-container { - -} -*/ - /** make sure block with areas stay on top, so they don't look like they are 'not-allowed'*/ .umb-block-grid__layout-container.--not-allowing-drop { @@ -934,8 +847,10 @@ umb-block-grid-block { } .umb-block-grid__layout-container .umb-block-grid__layout-item:not([depth='0']):first-of-type .umb-block-grid__block--inline-create-button.--above { - /* Move first above inline create button slightly up, to avoid collision with others*/ + /* Move the above inline create button slightly down, to avoid collision with others*/ margin-top: -7px; + + z-index:4; } .umb-block-grid__not-allowed-box { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js index 6a466cf0527d..f20f14f227de 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.controller.js @@ -25,13 +25,25 @@ value: {min:vm.area.minAllowed, max:vm.area.maxAllowed} } + unsubscribe.push($scope.$watch('vm.area.alias', (newVal, oldVal) => { + $scope.model.updateTitle(); + if($scope.blockGridBlockConfigurationAreaForm.alias) { + $scope.blockGridBlockConfigurationAreaForm.alias.$setValidity("alias", $scope.model.otherAreaAliases.indexOf(newVal) === -1); + } + })); + vm.submit = function() { + if($scope.blockGridBlockConfigurationAreaForm.$valid === false) { + $scope.submitButtonState = "error"; + return; + } if ($scope.model && $scope.model.submit) { // Transfer minMaxModel to area: vm.area.minAllowed = vm.minMaxModel.value.min; vm.area.maxAllowed = vm.minMaxModel.value.max; + $scope.submitButtonState = "success"; $scope.model.submit($scope.model); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html index cccceb7afb91..90918d71e130 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.area.overlay.html @@ -27,11 +27,18 @@
+ * The alias will be printed by GetBlockGridHTML(), use the alias to target the Element representing this area. Ex. .umb-block-grid__area[data-area-alias="MyAreaAlias"] { ... }
- + +
+
+
+ This Areas Alias must be unique compared to the other Areas of this Block. +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html index bfb9fdc90c44..e78d94d4863d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.html @@ -6,6 +6,7 @@

Install Sample Configuration

+
@@ -30,9 +31,14 @@

Install Sample Configuration - + + + + Install demo Blocks + +

@@ -82,9 +88,9 @@

Install Sample Configuration - +

diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less index aa09b5238edc..43151fcabb4b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.less @@ -2,29 +2,10 @@ margin-bottom: 20px; - .__add-button { - position: relative; - display: inline-flex; - width: 100%; - height: 100%; - margin-right: 20px; - margin-bottom: 20px; - - color: @ui-action-discreet-type; - border: 1px dashed @ui-action-discreet-border; - border-radius: @doubleBorderRadius; - - align-items: center; - justify-content: center; - - padding: 5px 15px; - box-sizing: border-box; - font-weight: bold; - } - - .__add-button:hover { - color: @ui-action-discreet-type-hover; - border-color: @ui-action-discreet-border-hover; + uui-button { + font-weight: 700; + --uui-button-border-radius: 6px; + min-height: 80px; } .__get-sample-box { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html index 4b05e4ad43c3..27c1a2f0062e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html @@ -355,12 +355,25 @@
+ +
+
+ + + Hide the content edit button and the content editor from the Block Editor overlay. + +
+ +
+
+
+
- Define the range of layout rows this block is allowed to span across. + Hide the content edit button and the content editor from the Block Editor overlay.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html index 86c975639cd4..25a7282ca2ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.groupconfiguration.html @@ -1,5 +1,5 @@ -
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html index c288afc3ff1d..55ceb941bbab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.html @@ -2,13 +2,11 @@ -
- +
+ ng-click="vm.deleteAllowance(allowance);"> Delete @@ -56,12 +51,15 @@ +
+ When empty all Blocks allowed for Areas can be created. +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less index 9389906facde..2ce5a2ef16d9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umb-block-grid-area-allowance-editor.less @@ -5,8 +5,11 @@ width: 100%; } -.umb-block-grid-area-allowance-editor .__list.--disabled { - +.umb-block-grid-area-allowance-editor .__empty-label { + font-size: 12px; + color: @gray-6; + line-height: 1.5em; + padding-top: 5px; } .umb-block-grid-area-allowance-editor__entry { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js index e8ae592d8f3c..9126a6cc5435 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/umbBlockGridAreaEditor.component.js @@ -56,15 +56,32 @@ function initializeSortable() { - const gridLayoutContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper'); + function _sync(evt) { - const sortable = Sortable.create(gridLayoutContainerEl, { + const oldIndex = evt.oldIndex, + newIndex = evt.newIndex; + + vm.model.splice(newIndex, 0, vm.model.splice(oldIndex, 1)[0]); + + } + + const gridContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper'); + + const sortable = Sortable.create(gridContainerEl, { sort: true, // sorting inside list animation: 150, // ms, animation speed moving items when sorting, `0` — without animation easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation. Defaults to null. See https://easings.net/ for examples. cancel: '', draggable: ".umb-block-grid-area-editor__area", // Specifies which items inside the element should be draggable - ghostClass: "umb-block-grid-area-editor__area-placeholder" + ghostClass: "umb-block-grid-area-editor__area-placeholder", + onAdd: function (evt) { + _sync(evt); + $scope.$evalAsync(); + }, + onUpdate: function (evt) { + _sync(evt); + $scope.$evalAsync(); + } }); // TODO: setDirty if sort has happend. @@ -130,14 +147,23 @@ vm.openAreaOverlay = function (area) { // TODO: use the right localization key: - localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [area.alias]).then(function (localized) { + localizationService.localize("blockEditor_blockConfigurationOverlayTitle").then(function (localized) { var clonedAreaData = Utilities.copy(area); vm.openArea = area; + function updateTitle() { + overlayModel.title = localizationService.tokenReplace(localized, [clonedAreaData.alias]); + } + + const areaIndex = vm.model.indexOf(area); + const otherAreas = [...vm.model]; + otherAreas.splice(areaIndex, 1); + var overlayModel = { + otherAreaAliases: otherAreas.map(x => x.alias), area: clonedAreaData, - title: localized, + updateTitle: updateTitle, allBlockTypes: vm.allBlockTypes, allBlockGroups: vm.allBlockGroups, loadedElementTypes: vm.loadedElementTypes, @@ -154,6 +180,8 @@ } }; + updateTitle(); + // open property settings editor editorService.open(overlayModel); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html index b4ef7b926650..301208c6f20d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entries.html @@ -60,7 +60,7 @@ @@ -119,7 +119,7 @@ key="{{(invalidBlockType.amount < invalidBlockType.minRequirement) ? 'blockEditor_areaValidationEntriesShort' : 'blockEditor_areaValidationEntriesExceed'}}" tokens="[invalidBlockType.name, invalidBlockType.amount, invalidBlockType.minRequirement, invalidBlockType.maxRequirement]" watch-tokens="true" - >%0% must be present between %2% – %3% times. + >%0% must be present between %2%–%3% times.
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html index 9c8860eaa563..e3f439a8917b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-entry.html @@ -1,10 +1,11 @@ + ng-click="vm.clickInlineCreateAbove()" + ng-mouseover="vm.mouseOverInlineCreate()" + ng-mouseleave="vm.mouseOutInlineCreate()"> @@ -29,9 +30,15 @@ parent-form="vm.blockForm" style="--umb-block-grid--area-grid-columns: {{vm.areaGridColumns}}" > + +
-
@@ -159,7 +165,7 @@ -
{{vm.layoutEntry.columnSpan}} x {{vm.layoutEntry.rowSpan}}
@@ -180,10 +186,7 @@ class="umb-block-grid__block--inline-create-button --after" ng-class="{'--at-root': vm.depth === '0'}" ng-click="vm.clickInlineCreateAfter($event)" - ng-mouseover="vm.mouseOverInlineCreateAfter()" - ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)" + ng-mouseover="vm.mouseOverInlineCreate()" + ng-mouseleave="vm.mouseOutInlineCreate()" vertical> - -
-
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html index 767be6d55930..834f55e685bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.html @@ -4,7 +4,19 @@
-
+ + + + + + + +
+ entries="vm.layout" + loading="vm.loading" + >
- diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less index be3d1cc9ec03..d06b6377c37b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-property-editor.less @@ -1,4 +1,8 @@ .umb-block-grid__wrapper { position: relative; max-width: 1200px; +} + +.umb-block-grid__wrapper .umb-rte { + max-width: 100%; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html new file mode 100644 index 000000000000..e8d53c74575b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js index 57ddea9757f5..829fc918b6a4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbBlockGridPropertyEditor.component.js @@ -13,6 +13,31 @@ return null; } + function closestColumnSpanOption(target, map, max) { + if(map.length > 0) { + const result = map.reduce((a, b) => { + if (a.columnSpan > max) { + return b; + } + let aDiff = Math.abs(a.columnSpan - target); + let bDiff = Math.abs(b.columnSpan - target); + + if (aDiff === bDiff) { + return a.columnSpan < b.columnSpan ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + if(result) { + return result; + } + } + return null; + } + + + const DefaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/"; + /** * @ngdoc directive @@ -44,14 +69,20 @@ var unsubscribe = []; var modelObject; + var gridRootEl; // Property actions: + var propertyActions = null; + var enterSortModeAction = null; + var exitSortModeAction = null; var copyAllBlocksAction = null; var deleteAllBlocksAction = null; var liveEditing = true; var shadowRoot; + var firstLayoutContainer; + var vm = this; @@ -107,6 +138,8 @@ vm.options = { createFlow: false }; + vm.sortMode = false; + vm.sortModeView = DefaultViewFolderPath + "gridsortblock/gridsortblock.editor.html";; localizationService.localizeMany(["grid_addElement", "content_createEmpty", "blockEditor_addThis"]).then(function (data) { vm.labels.grid_addElement = data[0]; @@ -114,8 +147,23 @@ vm.labels.blockEditor_addThis = data[2] }); + vm.onAppendProxyProperty = (event) => { + event.stopPropagation(); + gridRootEl.appendChild(event.detail.property); + event.detail.connectedCallback(); + }; + vm.onRemoveProxyProperty = (event) => { + event.stopPropagation(); + const el = gridRootEl.querySelector(`:scope > [slot='${event.detail.slotName}']`); + gridRootEl.removeChild(el); + }; + vm.$onInit = function() { + gridRootEl = $element[0].querySelector('umb-block-grid-root'); + + $element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); //listen for form validation changes vm.valFormManager.onValidationStatusChanged(function (evt, args) { @@ -177,6 +225,19 @@ scopeOfExistence = vm.umbElementEditorContent.getScope(); } + enterSortModeAction = { + labelKey: 'blockEditor_actionEnterSortMode', + icon: 'navigation-vertical', + method: enableSortMode, + isDisabled: false + }; + exitSortModeAction = { + labelKey: 'blockEditor_actionExitSortMode', + icon: 'navigation-vertical', + method: exitSortMode, + isDisabled: false + }; + copyAllBlocksAction = { labelKey: "clipboard_labelForCopyAllEntries", labelTokens: [vm.model.label], @@ -187,13 +248,13 @@ deleteAllBlocksAction = { labelKey: 'clipboard_labelForRemoveAllEntries', - labelTokens: [], icon: 'trash', method: requestDeleteAllBlocks, isDisabled: true }; - var propertyActions = [ + propertyActions = [ + enterSortModeAction, copyAllBlocksAction, deleteAllBlocksAction ]; @@ -223,7 +284,6 @@ } - function onLoaded() { // Store a reference to the layout model, because we need to maintain this model. @@ -241,6 +301,7 @@ window.requestAnimationFrame(() => { shadowRoot = $element[0].querySelector('umb-block-grid-root').shadowRoot; + firstLayoutContainer = shadowRoot.querySelector('.umb-block-grid__layout-container'); }) } @@ -314,21 +375,31 @@ } } - // if no columnSpan, then we set one: - if (!layoutEntry.columnSpan) { + // Ensure Areas are ordered like the area configuration is: + layoutEntry.areas.sort((left, right) => { + return block.config.areas?.findIndex(config => config.key === left.key) < block.config.areas?.findIndex(config => config.key === right.key) ? -1 : 1; + }); - const contextColumns = getContextColumns(parentBlock, areaKey) - if (block.config.columnSpanOptions.length > 0) { - // set columnSpan to minimum allowed span for this BlockType: - const minimumColumnSpan = block.config.columnSpanOptions.reduce((prev, option) => Math.min(prev, option.columnSpan), vm.gridColumns); + const contextColumns = getContextColumns(parentBlock, areaKey); + const relevantColumnSpanOptions = block.config.columnSpanOptions.filter(option => option.columnSpan <= contextColumns); - // If minimumColumnSpan is larger than contextColumns, then we will make it fit within context anyway: - layoutEntry.columnSpan = Math.min(minimumColumnSpan, contextColumns) + // if no columnSpan or no columnSpanOptions configured, then we set(or rewrite) one: + if (!layoutEntry.columnSpan || layoutEntry.columnSpan > contextColumns || relevantColumnSpanOptions.length === 0) { + if (relevantColumnSpanOptions.length > 0) { + // Find greatest columnSpanOption within contextColumns, or fallback to contextColumns. + layoutEntry.columnSpan = relevantColumnSpanOptions.reduce((prev, option) => Math.max(prev, option.columnSpan), 0) || contextColumns; } else { layoutEntry.columnSpan = contextColumns; } + } else { + // Check that columnSpanOption still is available or equal contextColumns, or find closest option fitting: + if (relevantColumnSpanOptions.find(option => option.columnSpan === layoutEntry.columnSpan) === undefined || layoutEntry.columnSpan !== contextColumns) { + console.log(layoutEntry.columnSpan, closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns); + layoutEntry.columnSpan = closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns; + } } + // if no rowSpan, then we set one: if (!layoutEntry.rowSpan) { layoutEntry.rowSpan = 1; @@ -375,12 +446,12 @@ function applyDefaultViewForBlock(block) { - var defaultViewFolderPath = "views/propertyeditors/blockgrid/blockgridentryeditors/"; - if (block.config.unsupported === true) { - block.view = defaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html"; + block.view = DefaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html"; + } else if (block.config.inlineEditing) { + block.view = DefaultViewFolderPath + "gridinlineblock/gridinlineblock.editor.html"; } else { - block.view = defaultViewFolderPath + "gridblock/gridblock.editor.html"; + block.view = DefaultViewFolderPath + "gridblock/gridblock.editor.html"; } } @@ -430,9 +501,11 @@ block.showCopy = vm.supportCopy && block.config.contentElementTypeKey != null; block.blockUiVisibility = false; - block.showBlockUI = function () { + block.showBlockUI = () => { delete block.__timeout; - shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"}); + $timeout(() => { + shadowRoot.querySelector('*[data-element-udi="'+block.layout.contentUdi+'"] > ng-form > .umb-block-grid__block > .umb-block-grid__block--context').scrollIntoView({block: "nearest", inline: "nearest", behavior: "smooth"}); + }, 100); block.blockUiVisibility = true; }; block.onMouseLeave = function () { @@ -778,6 +851,8 @@ vm.requestShowCreate = requestShowCreate; function requestShowCreate(parentBlock, areaKey, createIndex, mouseEvent, options) { + vm.hideAreaHighlight(parentBlock, areaKey); + if (vm.blockTypePickerIsOpen === true) { return; } @@ -1254,6 +1329,38 @@ } } + function enableSortMode() { + vm.sortMode = true; + propertyActions.splice(propertyActions.indexOf(enterSortModeAction), 1, exitSortModeAction); + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); + } + } + + vm.exitSortMode = exitSortMode; + function exitSortMode() { + vm.sortMode = false; + propertyActions.splice(propertyActions.indexOf(exitSortModeAction), 1, enterSortModeAction); + if (vm.umbProperty) { + vm.umbProperty.setPropertyActions(propertyActions); + } + } + + vm.startDraggingMode = startDraggingMode; + function startDraggingMode() { + + document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1); + firstLayoutContainer.style.minHeight = firstLayoutContainer.getBoundingClientRect().height + "px"; + + } + vm.exitDraggingMode = exitDraggingMode; + function exitDraggingMode() { + + document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0); + firstLayoutContainer.style.minHeight = ""; + + } + function onAmountOfBlocksChanged() { // enable/disable property actions @@ -1278,9 +1385,16 @@ unsubscribe.push($scope.$watch(() => vm.layout.length, onAmountOfBlocksChanged)); $scope.$on("$destroy", function () { + + $element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + for (const subscription of unsubscribe) { subscription(); } + + firstLayoutContainer = null; + gridRootEl = null; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js index 2c4a4bb26219..1a918638cad2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridblock.component.js @@ -58,7 +58,7 @@
+ ng-include="api.internal.sortMode ? api.internal.sortModeView : '${model.view}'">
`; $compile(shadowRoot)($scope); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js index c1e2c43619c5..3f597292e871 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js @@ -76,6 +76,7 @@ vm.movingLayoutEntry = null; vm.layoutColumnsInt = 0; + vm.containedPropertyEditorProxies = []; vm.$onInit = function () { initializeSortable(); @@ -93,7 +94,6 @@ vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10); })); - function onLocalAmountOfBlocksChanged() { if (vm.entriesForm && vm.areaConfig) { @@ -153,6 +153,11 @@ } } + + vm.notifyVisualUpdate = function () { + $scope.$broadcast("blockGridEditorVisualUpdate", {areaKey: vm.areaKey}); + } + vm.acceptBlock = function(contentTypeKey) { return vm.blockEditorApi.internal.isElementTypeKeyAllowedAt(vm.parentBlock, vm.areaKey, contentTypeKey); } @@ -210,6 +215,11 @@ var nextSibling; + function _removePropertyProxy(eventTarget, slotName) { + const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': slotName}}); + eventTarget.dispatchEvent(event); + } + // Borrowed concept from, its not identical as more has been implemented: https://github.com/SortableJS/angular-legacy-sortablejs/blob/master/angular-legacy-sortable.js function _sync(evt) { @@ -222,8 +232,15 @@ const prevEntries = fromCtrl.entries; const syncEntry = prevEntries[oldIndex]; - // Perform the transfer: + // Make sure Property Editor Proxies are destroyed, as we need to establish new when moving context: + + // unregister all property editor proxies via events: + fromCtrl.containedPropertyEditorProxies.forEach(slotName => { + _removePropertyProxy(evt.from, slotName); + }); + + // Perform the transfer: if (Sortable.active && Sortable.active.lastPullMode === 'clone') { syncEntry = Utilities.copy(syncEntry); prevEntries.splice(Sortable.utils.index(evt.clone, sortable.options.draggable), 0, prevEntries.splice(oldIndex, 1)[0]); @@ -231,7 +248,6 @@ else { prevEntries.splice(oldIndex, 1); } - vm.entries.splice(newIndex, 0, syncEntry); const contextColumns = vm.blockEditorApi.internal.getContextColumns(vm.parentBlock, vm.areaKey); @@ -261,6 +277,7 @@ function _indication(contextVM, movingEl) { + // Remove old indication: if(_lastIndicationContainerVM !== contextVM && _lastIndicationContainerVM !== null) { _lastIndicationContainerVM.hideNotAllowed(); _lastIndicationContainerVM.revertIndicateDroppable(); @@ -269,7 +286,7 @@ if(contextVM.acceptBlock(movingEl.dataset.contentElementTypeKey) === true) { _lastIndicationContainerVM.hideNotAllowed(); - _lastIndicationContainerVM.indicateDroppable();// This block is accepted to we will indicate a good drop. + _lastIndicationContainerVM.indicateDroppable();// This block is accepted so we will indicate a good drop. return true; } @@ -557,6 +574,10 @@ forceAutoScrollFallback: true, onStart: function (evt) { + + // TODO: This does not work correctly jet with SortableJS. With the replacement we should be able to call this before DOM is changed. + vm.blockEditorApi.internal.startDraggingMode(); + nextSibling = evt.from === evt.item.parentNode ? evt.item.nextSibling : evt.clone.nextSibling; var contextVM = vm; @@ -577,6 +598,7 @@ vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated ghostEl = evt.item; + vm.containedPropertyEditorProxies = Array.from(ghostEl.querySelectorAll('slot[data-is-property-editor-proxy]')).map(x => x.getAttribute('name')); targetRect = evt.to.getBoundingClientRect(); ghostRect = ghostEl.getBoundingClientRect(); @@ -587,8 +609,6 @@ window.addEventListener('drag', _onDragMove); window.addEventListener('dragover', _onDragMove); - document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1); - $scope.$evalAsync(); }, // Called by any change to the list (add / update / remove) @@ -619,7 +639,7 @@ } window.removeEventListener('drag', _onDragMove); window.removeEventListener('dragover', _onDragMove); - document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0); + vm.blockEditorApi.internal.exitDraggingMode(); if(ghostElIndicateForceLeft) { ghostEl.removeChild(ghostElIndicateForceLeft); @@ -643,6 +663,9 @@ ghostRect = null; ghostEl = null; relatedEl = null; + vm.containedPropertyEditorProxies = []; + + vm.notifyVisualUpdate(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js index a7c45ecdd8cf..d5b3ce54745e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentry.component.js @@ -44,23 +44,25 @@ } function closestColumnSpanOption(target, map, max) { - const result = map.reduce((a, b) => { - if (a.columnSpan > max) { - return b; - } - let aDiff = Math.abs(a.columnSpan - target); - let bDiff = Math.abs(b.columnSpan - target); - - if (aDiff === bDiff) { - return a.columnSpan < b.columnSpan ? a : b; - } else { - return bDiff < aDiff ? b : a; + if(map.length > 0) { + const result = map.reduce((a, b) => { + if (a.columnSpan > max) { + return b; + } + let aDiff = Math.abs(a.columnSpan - target); + let bDiff = Math.abs(b.columnSpan - target); + + if (aDiff === bDiff) { + return a.columnSpan < b.columnSpan ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + if(result) { + return result; } - }); - if(result) { - return result; } - return max; + return null; } @@ -86,11 +88,17 @@ areaKey: "<", propertyEditorForm: " { + // Only insert a proxy slot for the direct Block of this entry (as all the blocks share the same ShadowDom though they are slotted into each other when nested through areas.) + if (event.detail.contentUdi === vm.layoutEntry.contentUdi) { + vm.proxyProperties.push({ + slotName: event.detail.slotName + }); + $scope.$evalAsync(); + } + }; + vm.onRemoveProxyProperty = (event) => { + // Only react to proxies from the direct Block of this entry: + if (event.detail.contentUdi === vm.layoutEntry.contentUdi) { + const index = vm.proxyProperties.findIndex(x => x.slotName === event.detail.slotName); + if(index !== -1) { + vm.proxyProperties.splice(index, 1); + } + $scope.$evalAsync(); + } + }; vm.$onInit = function() { + $element[0].addEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + vm.childDepth = parseInt(vm.depth) + 1; if(vm.layoutEntry.$block.config.areaGridColumns) { @@ -110,13 +145,29 @@ vm.areaGridColumns = vm.blockEditorApi.internal.gridColumns.toString(); } - vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10) + vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10); + + vm.relevantColumnSpanOptions = vm.layoutEntry.$block.config.columnSpanOptions.filter(x => x.columnSpan <= vm.layoutColumnsInt).sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0)); + const hasRelevantColumnSpanOptions = vm.relevantColumnSpanOptions.length > 1; + const hasRowSpanOptions = vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan; + vm.canScale = (hasRelevantColumnSpanOptions || hasRowSpanOptions); + + unsubscribe.push(vm.layoutEntry.$block.__scope.$watch(() => vm.layoutEntry.$block.index, visualUpdateCallback)); + unsubscribe.push($scope.$on("blockGridEditorVisualUpdate", (evt, data) => {if(data.areaKey === vm.areaKey) { visualUpdateCallback()}})); + + updateInlineCreateTimeout = $timeout(updateInlineCreate, 500); $scope.$evalAsync(); } unsubscribe.push($scope.$watch("depth", (newVal, oldVal) => { vm.childDepth = parseInt(vm.depth) + 1; })); + + function visualUpdateCallback() { + cancelAnimationFrame(updateInlineCreateRaf); + updateInlineCreateRaf = requestAnimationFrame(updateInlineCreate); + } + /** * We want to only show the validation errors on the specific Block, not the parent blocks. * So we need to avoid having a Block as the parent to the Block Form. @@ -157,9 +208,12 @@ // Block sizing functionality: let layoutContainer = null; let gridColumns = null; + let columnGap = 0; + let rowGap = 0; let gridRows = null; + let lockedGridRows = 0; let scaleBoxBackdropEl = null; - + let raf = null; function getNewSpans(startX, startY, endX, endY) { @@ -171,7 +225,8 @@ let newColumnSpan = Math.max(blockEndCol-blockStartCol, 1); // Find nearest allowed Column: - newColumnSpan = closestColumnSpanOption(newColumnSpan , vm.layoutEntry.$block.config.columnSpanOptions, gridColumns.length - blockStartCol).columnSpan; + const bestColumnSpanOption = closestColumnSpanOption(newColumnSpan , vm.relevantColumnSpanOptions, vm.layoutColumnsInt - blockStartCol) + newColumnSpan = bestColumnSpanOption ? bestColumnSpanOption.columnSpan : vm.layoutColumnsInt; let newRowSpan = Math.round(Math.max(blockEndRow-blockStartRow, vm.layoutEntry.$block.config.rowMinSpan || 1)); if(vm.layoutEntry.$block.config.rowMaxSpan != null) { @@ -181,10 +236,14 @@ return {'columnSpan': newColumnSpan, 'rowSpan': newRowSpan, 'startCol': blockStartCol, 'startRow': blockStartRow}; } - function updateGridLayoutData(layoutContainerRect, layoutItemRect) { + function updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate) { const computedStyles = window.getComputedStyle(layoutContainer); + + columnGap = Number(computedStyles.columnGap.split("px")[0]) || 0; + rowGap = Number(computedStyles.rowGap.split("px")[0]) || 0; + gridColumns = computedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)); gridRows = computedStyles.gridTemplateRows.trim().split("px").map(x => Number(x)); @@ -192,6 +251,18 @@ gridColumns = gridColumns.filter(n => n > 0); gridRows = gridRows.filter(n => n > 0); + // We use this code to lock the templateRows, while scaling. otherwise scaling Rows is too crazy. + if(updateRowTemplate || gridRows.length > lockedGridRows) { + lockedGridRows = gridRows.length; + layoutContainer.style.gridTemplateRows = computedStyles.gridTemplateRows; + } + + // add gaps: + const gridColumnsLen = gridColumns.length; + gridColumns = gridColumns.map((n, i) => gridColumnsLen === i ? n : n + columnGap); + const gridRowsLen = gridRows.length; + gridRows = gridRows.map((n, i) => gridRowsLen === i ? n : n + rowGap); + // ensure all columns are there. // This will also ensure handling non-css-grid mode, // use container width divided by amount of columns( or the item width divided by its amount of columnSpan) @@ -226,25 +297,28 @@ gridRows.push(50); gridRows.push(50); gridRows.push(50); + gridRows.push(50); + gridRows.push(50); } vm.scaleHandlerMouseDown = function($event) { $event.originalEvent.preventDefault(); - vm.isScaleMode = true; - window.addEventListener('mousemove', vm.onMouseMove); - window.addEventListener('mouseup', vm.onMouseUp); - window.addEventListener('mouseleave', vm.onMouseUp); - - layoutContainer = $element[0].closest('.umb-block-grid__layout-container'); if(!layoutContainer) { console.error($element[0], 'could not find parent layout-container'); + return; } + vm.isScaleMode = true; + + window.addEventListener('mousemove', vm.onMouseMove); + window.addEventListener('mouseup', vm.onMouseUp); + window.addEventListener('mouseleave', vm.onMouseUp); + const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - updateGridLayoutData(layoutContainerRect, layoutItemRect); + updateGridLayoutData(layoutContainerRect, layoutItemRect, true); scaleBoxBackdropEl = document.createElement('div'); @@ -256,7 +330,6 @@ const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - updateGridLayoutData(layoutContainerRect, layoutItemRect); const startX = layoutItemRect.left - layoutContainerRect.left; @@ -266,6 +339,18 @@ const newSpans = getNewSpans(startX, startY, endX, endY); + const updateRowTemplate = vm.layoutEntry.columnSpan !== newSpans.columnSpan; + + if(updateRowTemplate) { + // If we like to update we need to first remove the lock, make the browser render onces and then update. + layoutContainer.style.gridTemplateRows = ""; + } + cancelAnimationFrame(raf); + raf = requestAnimationFrame(() => { + // As mentioned above we need to wait until the browser has rendered DOM without the lock of gridTemplateRows. + updateGridLayoutData(layoutContainerRect, layoutItemRect, updateRowTemplate); + }) + // update as we go: vm.layoutEntry.columnSpan = newSpans.columnSpan; vm.layoutEntry.rowSpan = newSpans.rowSpan; @@ -275,7 +360,13 @@ vm.onMouseUp = function(e) { - vm.isScaleMode = false; + cancelAnimationFrame(raf); + + // Remove listeners: + window.removeEventListener('mousemove', vm.onMouseMove); + window.removeEventListener('mouseup', vm.onMouseUp); + window.removeEventListener('mouseleave', vm.onMouseUp); + const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); @@ -287,22 +378,24 @@ const newSpans = getNewSpans(startX, startY, endX, endY); - // Remove listeners: - window.removeEventListener('mousemove', vm.onMouseMove); - window.removeEventListener('mouseup', vm.onMouseUp); - window.removeEventListener('mouseleave', vm.onMouseUp); + // release the lock of gridTemplateRows: layoutContainer.removeChild(scaleBoxBackdropEl); + layoutContainer.style.gridTemplateRows = ""; + vm.isScaleMode = false; // Clean up variables: layoutContainer = null; gridColumns = null; gridRows = null; + lockedGridRows = 0; scaleBoxBackdropEl = null; // Update block size: vm.layoutEntry.columnSpan = newSpans.columnSpan; vm.layoutEntry.rowSpan = newSpans.rowSpan; + + vm.umbBlockGridEntries.notifyVisualUpdate(); vm.blockEditorApi.internal.setDirty(); $scope.$evalAsync(); } @@ -331,8 +424,8 @@ } if(addColIndex !== 0) { - if (vm.layoutEntry.$block.config.columnSpanOptions.length > 0) { - const sortOptions = vm.layoutEntry.$block.config.columnSpanOptions.sort((a,b) => (a.columnSpan > b.columnSpan) ? 1 : ((b.columnSpan > a.columnSpan) ? -1 : 0)); + if (vm.relevantColumnSpanOptions.length > 0) { + const sortOptions = vm.relevantColumnSpanOptions; const currentColIndex = sortOptions.findIndex(x => x.columnSpan === vm.layoutEntry.columnSpan); const newColIndex = Math.min(Math.max(currentColIndex + addColIndex, 0), sortOptions.length-1); vm.layoutEntry.columnSpan = sortOptions[newColIndex].columnSpan; @@ -346,18 +439,30 @@ } vm.layoutEntry.rowSpan = newRowSpan; + vm.umbBlockGridEntries.notifyVisualUpdate(); vm.blockEditorApi.internal.setDirty(); $event.originalEvent.stopPropagation(); } + vm.clickInlineCreateAbove = function($event) { + if(vm.hideInlineCreateAbove === false) { + vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index, $event); + } + } vm.clickInlineCreateAfter = function($event) { if(vm.hideInlineCreateAfter === false) { vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index+1, $event, {'fitInRow': true}); } } - vm.mouseOverInlineCreateAfter = function() { - + vm.mouseOverInlineCreate = function() { + vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey); + } + vm.mouseOutInlineCreate = function() { + vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey); + } + + function updateInlineCreate() { layoutContainer = $element[0].closest('.umb-block-grid__layout-container'); if(!layoutContainer) { return; @@ -366,17 +471,39 @@ const layoutContainerRect = layoutContainer.getBoundingClientRect(); const layoutItemRect = $element[0].getBoundingClientRect(); - if(layoutItemRect.right > layoutContainerRect.right - 5) { + if(layoutContainerRect.width === 0) { + $timeout.cancel(updateInlineCreateTimeout); + vm.hideInlineCreateAbove = true; vm.hideInlineCreateAfter = true; + vm.inlineCreateAboveWidth = ""; + $scope.$evalAsync(); + updateInlineCreateTimeout = $timeout(updateInlineCreate, 500); return; } - vm.hideInlineCreateAfter = false; - vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey); + if(layoutItemRect.right > layoutContainerRect.right - 5) { + vm.hideInlineCreateAfter = true; + } else { + vm.hideInlineCreateAfter = false; + } + if(layoutItemRect.left > layoutContainerRect.left + 5) { + vm.hideInlineCreateAbove = true; + vm.inlineCreateAboveWidth = ""; + } else { + vm.inlineCreateAboveWidth = getComputedStyle(layoutContainer).width; + vm.hideInlineCreateAbove = false; + } + $scope.$evalAsync(); } $scope.$on("$destroy", function () { + + $timeout.cancel(updateInlineCreateTimeout); + + $element[0].removeEventListener("UmbBlockGrid_AppendProperty", vm.onAppendProxyProperty); + $element[0].removeEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty); + for (const subscription of unsubscribe) { subscription(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js new file mode 100644 index 000000000000..43837e9c5cf5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridrenderareaslots.directive.js @@ -0,0 +1,20 @@ +(function () { + 'use strict'; + + function UmbBlockGridRenderAreaSlots() { + + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/propertyeditors/blockgrid/umb-block-grid-render-area-slots.html', + scope: false + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbBlockGridRenderAreaSlots', UmbBlockGridRenderAreaSlots); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js index 130150a3ae33..fbda5cf92e10 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridroot.component.js @@ -20,7 +20,8 @@ stylesheet: "@", blockEditorApi: "<", propertyEditorForm: "
hello

"}; + element = $("
"); })); @@ -31,7 +32,8 @@ describe('RTE controller tests', function () { it('should define the default properties on construction', function () { controllerFactory('Umbraco.PropertyEditors.RTEController', { $scope: scope, - $routeParams: routeParams + $routeParams: routeParams, + $element: element }); });