Merge branch 'v11/dev' into v11/contrib
# Conflicts: # .gitignore # src/Umbraco.Cms.ManagementApi/ManagementApiComposer.cs # src/Umbraco.Cms.ManagementApi/OpenApi.json
This commit is contained in:
@@ -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: "<?"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
* A component to render the property action toggle
|
||||
*/
|
||||
|
||||
function umbPropertyActionsController(keyboardService, localizationService) {
|
||||
function umbPropertyActionsController(keyboardService, localizationService, $scope) {
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
var vm = this;
|
||||
|
||||
@@ -56,6 +58,9 @@
|
||||
}
|
||||
|
||||
function onDestroy() {
|
||||
for (var i = 0; i < unsubscribe.length; i++) {
|
||||
unsubscribe[i]();
|
||||
}
|
||||
if (vm.isOpen === true) {
|
||||
destroyDropDown();
|
||||
}
|
||||
@@ -72,26 +77,36 @@
|
||||
vm.labels.openText = values[0];
|
||||
vm.labels.closeText = values[1];
|
||||
});
|
||||
|
||||
unsubscribe.push($scope.$watchCollection("vm.actions",
|
||||
function (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
updateActions();
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
function onChanges(simpleChanges) {
|
||||
if (simpleChanges.actions) {
|
||||
|
||||
let actions = simpleChanges.actions.currentValue || [];
|
||||
|
||||
Utilities.forEach(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;
|
||||
}
|
||||
});
|
||||
updateActions();
|
||||
}
|
||||
}
|
||||
|
||||
function updateActions() {
|
||||
|
||||
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 && action.icon.indexOf('icon-') !== 0 ? 'icon-' : '') + action.icon;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var umbPropertyActionsComponent = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -347,18 +347,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
plugins.push("autoresize");
|
||||
|
||||
var modeInline = false;
|
||||
var toolbar = args.toolbar.join(" ");
|
||||
var toolbarActions = args.toolbar.join(" ");
|
||||
var toolbar = toolbarActions;
|
||||
var quickbar = toolbarActions;
|
||||
|
||||
// Based on mode set
|
||||
// classic = Theme: modern, inline: false
|
||||
// inline = Theme: modern, inline: true,
|
||||
// distraction-free = Theme: inlite, inline: true
|
||||
if (args.mode === "inline") {
|
||||
modeInline = true;
|
||||
}
|
||||
else if (args.mode === "distraction-free") {
|
||||
// distraction-free = Same as inline - kept for legacy reasons due to older versions of Umbraco having this as a mode
|
||||
if (args.mode === "inline" || args.mode === 'distraction-free') {
|
||||
modeInline = true;
|
||||
toolbar = false;
|
||||
plugins.push('quickbars');
|
||||
}
|
||||
|
||||
//create a baseline Config to extend upon
|
||||
@@ -379,6 +379,8 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
|
||||
|
||||
//this would be for a theme other than inlite
|
||||
toolbar: toolbar,
|
||||
quickbars_insert_toolbar: quickbar,
|
||||
quickbars_selection_toolbar: quickbar,
|
||||
|
||||
body_class: "umb-rte",
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
.umb-rte.--initialized .umb-rte-editor-con {
|
||||
height:auto;
|
||||
min-height: 100px;
|
||||
min-height: 95px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@@ -140,6 +140,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tox {
|
||||
&.tox-tinymce-aux {
|
||||
z-index: 65535;
|
||||
}
|
||||
}
|
||||
|
||||
.tox-tinymce-inline {
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="umb-property">
|
||||
<ng-form name="propertyForm">
|
||||
<div class="control-group umb-control-group"
|
||||
ng-class="{'hidelabel':vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
|
||||
ng-class="{'hidelabel':vm.hideLabel || vm.property.hideLabel, '--label-on-top':vm.property.labelOnTop, 'umb-control-group__listview': vm.property.alias === '_umb_containerView'}">
|
||||
|
||||
<val-property-msg></val-property-msg>
|
||||
|
||||
<div class="umb-el-wrap">
|
||||
|
||||
<div class="control-header" ng-hide="vm.property.hideLabel === true">
|
||||
<div class="control-header" ng-hide="(vm.hideLabel || vm.property.hideLabel) === true">
|
||||
|
||||
<label data-element="property-label-{{vm.property.alias}}" class="control-label" for="{{vm.property.alias}}" ng-attr-title="{{vm.controlLabelTitle}}">{{vm.property.label}}<span ng-if="vm.property.validation.mandatory || vm.property.ncMandatory"><strong class="umb-control-required">*</strong></span></label>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="blockelement-gridblock-editor" ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
|
||||
@@ -64,6 +81,6 @@
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
|
||||
<slot name="area-container" part="area-container"></slot>
|
||||
<umb-block-grid-render-area-slots ng-if="block.layout.areas.length > 0"></umb-block-grid-render-area-slots>
|
||||
|
||||
</div>
|
||||
@@ -1,27 +1,71 @@
|
||||
(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];
|
||||
|
||||
console.log(document.styleSheets)
|
||||
if (vm.property) {
|
||||
vm.propertySlotName = "umbBlockGridProxy_" + vm.property.alias + "_" + String.CreateGuid();
|
||||
|
||||
propertyEditorElement = $('<div slot="{{vm.propertySlotName}}"></div>');
|
||||
propertyEditorElement.html(
|
||||
`
|
||||
<umb-property
|
||||
data-element="grid-block-property-{{vm.property.alias}}"
|
||||
property="vm.property"
|
||||
node="$scope.block.content"
|
||||
hide-label="true">
|
||||
|
||||
for (const stylesheet of document.styleSheets) {
|
||||
<umb-property-editor
|
||||
model="vm.property"
|
||||
preview="$scope.api.internal.readonly"
|
||||
ng-attr-readonly="{{$scope.api.internal.readonly || undefined}}">
|
||||
</umb-property-editor>
|
||||
|
||||
console.log(stylesheet);
|
||||
const styleEl = document.createElement('link');
|
||||
styleEl.setAttribute('rel', 'stylesheet');
|
||||
styleEl.setAttribute('type', stylesheet.type);
|
||||
styleEl.setAttribute('href', stylesheet.href);
|
||||
</umb-property>
|
||||
`
|
||||
);
|
||||
|
||||
$element[0].addEventListener('umb-rte-focus', onRteFocus);
|
||||
$element[0].addEventListener('umb-rte-blur', onRteBlur);
|
||||
|
||||
host.appendChild(styleEl);
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function onRteFocus() {
|
||||
$element[0].classList.add('umb-block-grid--force-focus');
|
||||
}
|
||||
function onRteBlur() {
|
||||
$element[0].classList.remove('umb-block-grid--force-focus');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.PropertyEditors.BlockEditor.GridInlineBlockEditor", GridInlineBlockEditor);
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
<div class="blockelement-inlineblock-editor"
|
||||
ng-controller="Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor as vm"
|
||||
ng-class="{ '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
|
||||
<button type="button" class="btn-reset umb-outline blockelement__draggable-element"
|
||||
ng-click="vm.openBlock(block)"
|
||||
ng-focus="block.focus">
|
||||
<span class="caret"></span>
|
||||
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
|
||||
<span class="name">{{block.label}}</span>
|
||||
</button>
|
||||
<div class="blockelement-inlineblock-editor__inner" ng-class="{'--singleGroup':block.content.variants[0].tabs.length === 1}" ng-if="block.active === true">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.umb-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
@@ -42,52 +24,82 @@
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.blockelement-gridinlineblock-editor > button {
|
||||
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
cursor: pointer;
|
||||
color: #1b264f;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
padding: 0 20px;
|
||||
user-select: none;
|
||||
border: none;
|
||||
transition: border-color 120ms, background-color 120ms;
|
||||
box-sizing: border-box;
|
||||
transition: min-height 120ms;
|
||||
}
|
||||
.blockelement-gridinlineblock-editor > button:hover {
|
||||
color: #2152A3;
|
||||
}
|
||||
.blockelement-gridinlineblock-editor .icon {
|
||||
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label > .icon {
|
||||
font-size: 22px;
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.blockelement-gridinlineblock-editor > button > span {
|
||||
.blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label > span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-top:2px;
|
||||
padding-bottom:2px;
|
||||
}
|
||||
|
||||
.blockelement-gridinlineblock-editor > button {
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms, background-color 120ms;
|
||||
}
|
||||
.blockelement-gridinlineblock-editor > button:hover {
|
||||
color: #2152A3;
|
||||
}
|
||||
|
||||
|
||||
:host .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.blockelement-gridinlineblock-editor.umb-block-grid--force-focus > .blockelement-gridinlineblock-label,
|
||||
:host(:focus) .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label,
|
||||
:host(:focus-within) .blockelement-gridinlineblock-editor > .blockelement-gridinlineblock-label {
|
||||
min-height: 48px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div
|
||||
class="blockelement-gridinlineblock-editor"
|
||||
ng-controller="Umbraco.PropertyEditors.BlockEditor.GridInlineBlockEditor as vm"
|
||||
ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
|
||||
<button type="button"
|
||||
|
||||
<button
|
||||
ng-if="!block.config.forceHideContentEditorInOverlay"
|
||||
type="button"
|
||||
ng-click="block.edit()"
|
||||
ng-focus="block.focus"
|
||||
class="blockelement-gridinlineblock-label"
|
||||
val-server-property-class="">
|
||||
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
<div
|
||||
ng-if="block.config.forceHideContentEditorInOverlay"
|
||||
class="blockelement-gridinlineblock-label"
|
||||
val-server-property-class="">
|
||||
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
|
||||
<span>{{block.label}}</span>
|
||||
</div>
|
||||
|
||||
<umb-element-editor-content model="block.content"></umb-element-editor-content>
|
||||
<slot name="{{vm.propertySlotName}}"></slot>
|
||||
|
||||
<slot name="area-container" part="area-container"></slot>
|
||||
<umb-block-grid-render-area-slots></umb-block-grid-render-area-slots>
|
||||
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<style>
|
||||
@keyframes umb-icon-jump {
|
||||
0%, 100% { transform: rotate(6deg); }
|
||||
50% { transform: rotate(-6deg); }
|
||||
}
|
||||
|
||||
.umb-icon {
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
flex-shrink: 0;
|
||||
|
||||
animation: umb-icon-jump 1s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.umb-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
animation: inherit;
|
||||
}
|
||||
|
||||
.blockelement-gridsortblock-editor {
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
height: 100%;/* Add this to fill the cell fully */
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #E9E9EB;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.blockelement-gridsortblock-editor > button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
cursor: pointer;
|
||||
color: #1b264f;
|
||||
background-color: white;
|
||||
text-align: left;
|
||||
padding: 0 20px;
|
||||
user-select: none;
|
||||
border: none;
|
||||
transition: border-color 120ms, background-color 120ms;
|
||||
}
|
||||
.blockelement-gridsortblock-editor > button:hover {
|
||||
color: #2152A3;
|
||||
}
|
||||
.blockelement-gridsortblock-editor .icon {
|
||||
font-size: 22px;
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.blockelement-gridsortblock-editor > button > span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
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);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="blockelement-gridsortblock-editor" ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }">
|
||||
<button type="button"
|
||||
ng-click="block.showContent ? block.edit() : null"
|
||||
ng-focus="block.focus"
|
||||
val-server-property-class="">
|
||||
<umb-icon icon="{{block.content.icon}}" class="icon"></umb-icon>
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
|
||||
<umb-block-grid-render-area-slots ng-if="block.layout.areas.length > 0"></umb-block-grid-render-area-slots>
|
||||
|
||||
</div>
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -21,13 +21,16 @@
|
||||
.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 {
|
||||
z-index: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,52 +52,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 {
|
||||
@@ -115,11 +78,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
}
|
||||
> .umb-block-grid__force-left,
|
||||
> .umb-block-grid__force-right {
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
}
|
||||
> .umb-block-grid__block--context {
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
@@ -136,7 +94,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 {
|
||||
@@ -185,10 +142,6 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
|
||||
> .umb-block-grid__scale-label {
|
||||
opacity: 1;
|
||||
}
|
||||
> .umb-block-grid__force-left,
|
||||
> .umb-block-grid__force-right {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** make sure to hide child block ui: */
|
||||
@@ -205,24 +158,23 @@ ng-form.ng-invalid-val-server-match-content > .umb-block-grid__block:not(.--acti
|
||||
> .umb-block-grid__block--actions {
|
||||
display: none;
|
||||
}
|
||||
> .umb-block-grid__force-left,
|
||||
> .umb-block-grid__force-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.--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 +188,30 @@ 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);
|
||||
}
|
||||
> .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 +225,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;
|
||||
@@ -409,127 +353,6 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
}
|
||||
}
|
||||
|
||||
.umb-block-grid__force-left,
|
||||
.umb-block-grid__force-right {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
height: 30px;
|
||||
width: 15px;
|
||||
margin-top:-15px;
|
||||
background-color: transparent;
|
||||
color: @blueDark;
|
||||
border: 1px solid rgba(255, 255, 255, .2);
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
display: var(--umb-block-grid--block-ui-display, flex);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
pointer-events: all;
|
||||
|
||||
opacity: 0;
|
||||
transition: background-color 120ms, border-color 120ms, color 120ms, opacity 120ms;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: transform 120ms ease-in-out, opacity 120ms;
|
||||
::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color:currentColor;
|
||||
width:2px;
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
transition: transform 120ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: @blueDark;
|
||||
background-color: @white;
|
||||
}
|
||||
&:hover,
|
||||
&.--active {
|
||||
.icon {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
::before {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.--active {
|
||||
background-color: @blueDark;
|
||||
color: white;
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-block-grid__force-left {
|
||||
left: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
.icon {
|
||||
transform: translateX(3px);
|
||||
::before {
|
||||
left: 2px;
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.--active {
|
||||
border-left-color: @blueDark;
|
||||
}
|
||||
}
|
||||
.umb-block-grid__force-right {
|
||||
right: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
.icon {
|
||||
margin-right: 1px;
|
||||
transform: translateX(-3px);
|
||||
::before {
|
||||
right: 2px;
|
||||
transform: translateX(3px);
|
||||
}
|
||||
}
|
||||
&:hover,
|
||||
&.--active {
|
||||
border-right-color: @blueDark;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 +409,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 +417,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 +430,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 +448,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 +570,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 +582,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;
|
||||
@@ -814,36 +626,6 @@ umb-block-grid-block {
|
||||
100% { background-color: rgba(@blueMidLight, 0.22); }
|
||||
}
|
||||
}
|
||||
.umb-block-grid__layout-item-placeholder .indicateForceLeft,
|
||||
.umb-block-grid__layout-item-placeholder .indicateForceRight {
|
||||
position:absolute;
|
||||
|
||||
z-index: 2;
|
||||
height: 100%;
|
||||
width: 15px;
|
||||
|
||||
background-color: @blueDark;
|
||||
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
display: block !important;
|
||||
|
||||
animation: umb-block-grid__indicateForce__pulse 400ms ease-in-out alternate infinite;
|
||||
}
|
||||
|
||||
.umb-block-grid__layout-item-placeholder .indicateForceLeft {
|
||||
left:0;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='10'><polygon points='0,0 2,0 2,5 6,1 6,9 2,5 2,10 0,10' style='fill:white;'/></svg>");
|
||||
}
|
||||
.umb-block-grid__layout-item-placeholder .indicateForceRight {
|
||||
right:0;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='10'><polygon points='8,0 6,0 6,5 2,1 2,9 6,5 6,10 8,10' style='fill:white;'/></svg>");
|
||||
}
|
||||
|
||||
@keyframes umb-block-grid__indicateForce__pulse {
|
||||
0% { background-color: rgba(@blueDark, 1); }
|
||||
100% { background-color: rgba(@blueDark, 0.5); }
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__area {
|
||||
@@ -859,16 +641,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,31 +684,10 @@ 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'*/
|
||||
@@ -934,8 +698,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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,11 +27,18 @@
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="alias"><localize key="general_alias">Alias</localize></label>
|
||||
<strong class="umb-control-required">*</strong>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_areaAliasHelp">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"] { ... }</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<input type="text" name="alias" ng-model="vm.area.alias" style="width:100%" umb-auto-focus/>
|
||||
<input type="text" name="alias" ng-model="vm.area.alias" val-server="alias" style="width:100%" required umb-auto-focus/>
|
||||
</div>
|
||||
<div ng-messages="blockGridBlockConfigurationAreaForm.alias.$error" class="red">
|
||||
<div ng-message="alias">
|
||||
<localize key="blockEditor_areaAliasIsNotUnique" tokens="[vm.area.alias]" watch-tokens="true">This Areas Alias must be unique compared to the other Areas of this Block.</localize>
|
||||
</div>
|
||||
<span ng-message="valServer" ng-bind-html="blockGridBlockConfigurationAreaForm.alias.errorMsg"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<umb-button action="vm.setupSample()" button-style="primary" state="vm.sampleButtonState" label-key="blockEditor_getSampleButton" type="button"></umb-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-block-card-group">
|
||||
|
||||
<div class="umb-block-card-grid" ui-sortable="vm.blockSortableOptions" ng-model="model.value">
|
||||
@@ -30,9 +31,14 @@
|
||||
</div>
|
||||
</umb-block-card>
|
||||
|
||||
<button id="{{model.alias}}" type="button" class="btn-reset __add-button" ng-click="vm.openAddDialog()">
|
||||
<uui-button id="{{model.alias}}" look="{{(model.value.length === 0 && vm.blockGroups.length === 0) ? 'primary' : 'placeholder'}}" color="primary" ng-click="vm.openAddDialog()">
|
||||
<localize key="blockEditor_addBlockType">Add Block</localize>
|
||||
</button>
|
||||
</uui-button>
|
||||
|
||||
<uui-button ng-if="!vm.showSampleDataCTA && model.value.length === 0 && vm.blockGroups.length === 0" look="placeholder" color="primary" ng-click="vm.setupSample()">
|
||||
<localize key="blockEditor_getSampleHeadline">Install demo Blocks</localize>
|
||||
</uui-button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -82,9 +88,9 @@
|
||||
</div>
|
||||
</umb-block-card>
|
||||
|
||||
<button type="button" class="btn-reset __add-button" ng-click="vm.openAddDialog(blockGroup.key)">
|
||||
<uui-button id="{{model.alias}}" look="placeholder" color="primary" ng-click="vm.openAddDialog(blockGroup.key)">
|
||||
<localize key="blockEditor_addBlockType">Add Block</localize>
|
||||
</button>
|
||||
</uui-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -355,12 +355,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- inlineEditing -->
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label ng-attr-disabled="{{(vm.block.view !== null) || undefined}}" class="control-label" for="inlineEditing"><localize key="blockEditor_gridInlineEditing">Inline editing</localize></label>
|
||||
<umb-property-info-button ng-if="vm.block.view === null" button-title-key="general_readMore">
|
||||
<localize key="blockEditor_gridInlineEditingHelp">Hide the content edit button and the content editor from the Block Editor overlay.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-toggle checked="(vm.block.view === null ? vm.block.inlineEditing : false)" disabled="vm.block.view !== null" on-click="vm.block.inlineEditing = vm.block.inlineEditing != true"></umb-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- forceHideContentEditorInOverlay -->
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="forceHideContentEditorInOverlay"><localize key="blockEditor_forceHideContentEditor">Hide content editor</localize></label>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_forceHideContentEditorHelp">Define the range of layout rows this block is allowed to span across.</localize>
|
||||
<localize key="blockEditor_forceHideContentEditorHelp">Hide the content edit button and the content editor from the Block Editor overlay.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-toggle checked="vm.block.forceHideContentEditorInOverlay" on-click="vm.block.forceHideContentEditorInOverlay = vm.block.forceHideContentEditorInOverlay != true"></umb-toggle>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="umb-block-grid-block-configuration controls" ng-controller="Umbraco.PropertyEditors.BlockGrid.GroupConfigurationController as vm">
|
||||
<button type="button" class="btn-reset __add-button" ng-click="vm.addGroup($event)">
|
||||
<div class="controls" ng-controller="Umbraco.PropertyEditors.BlockGrid.GroupConfigurationController as vm">
|
||||
<uui-button look="placeholder" color="primary" ng-click="vm.addGroup($event)" style="font-weight: 700; width: 100%;">
|
||||
<localize key="blockEditor_addBlockGroup">Add group</localize>
|
||||
</button>
|
||||
</uui-button>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<div ng-show="vm.loading !== true" class="__list" ng-class="{'--disabled': vm.disabled}">
|
||||
|
||||
<div ng-show="vm.loading !== true" class="__list">
|
||||
|
||||
<div ng-repeat="allowance in vm.model track by allowance.$key" class="umb-block-grid-area-allowance-editor__entry">
|
||||
<select
|
||||
ng-model="allowance.$chosenValue"
|
||||
ng-disabled="vm.disabled"
|
||||
localize="title"
|
||||
title="blockEditor_pickSpecificAllowance"
|
||||
required
|
||||
@@ -19,7 +17,6 @@
|
||||
</select>
|
||||
<input
|
||||
type="number"
|
||||
ng-disabled="vm.disabled"
|
||||
name="label"
|
||||
min="0"
|
||||
ng-max="model.value.max"
|
||||
@@ -31,7 +28,6 @@
|
||||
<span>–</span>
|
||||
<input
|
||||
type="number"
|
||||
ng-disabled="vm.disabled"
|
||||
name="label"
|
||||
ng-model="allowance.maxAllowed"
|
||||
placeholder="∞"
|
||||
@@ -44,8 +40,7 @@
|
||||
class="btn-reset umb-outline"
|
||||
localize="title"
|
||||
title="actions_delete"
|
||||
ng-click="vm.deleteAllowance(allowance);"
|
||||
ng-disabled="vm.disabled">
|
||||
ng-click="vm.deleteAllowance(allowance);">
|
||||
<umb-icon icon="icon-trash" class="icon"></umb-icon>
|
||||
<span class="sr-only">
|
||||
<localize key="actions_delete">Delete</localize>
|
||||
@@ -56,12 +51,15 @@
|
||||
|
||||
<button
|
||||
type="button"
|
||||
ng-disabled="vm.disabled"
|
||||
class="btn-reset umb-block-grid-area-editor__create-button umb-outline"
|
||||
ng-click="vm.onNewAllowanceClick()">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
|
||||
<div ng-if="vm.model.length === 0" class="__empty-label">
|
||||
<localize key="blockEditor_areaAllowedBlocksEmpty">When empty all Blocks allowed for Areas can be created.</localize>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
data-element-udi="{{layoutEntry.contentUdi}}"
|
||||
data-col-span="{{layoutEntry.columnSpan}}"
|
||||
data-row-span="{{layoutEntry.rowSpan}}"
|
||||
ng-attr-data-force-left="{{layoutEntry.forceLeft || undefined}}"
|
||||
ng-attr-data-force-right="{{layoutEntry.forceRight || undefined}}"
|
||||
style="
|
||||
--umb-block-grid--item-column-span: {{layoutEntry.columnSpan}};
|
||||
--umb-block-grid--item-row-span: {{layoutEntry.rowSpan}};
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
data-element-udi="{{layoutEntry.contentUdi}}"
|
||||
data-col-span="{{layoutEntry.columnSpan}}"
|
||||
data-row-span="{{layoutEntry.rowSpan}}"
|
||||
ng-attr-data-force-left="{{layoutEntry.forceLeft || undefined}}"
|
||||
ng-attr-data-force-right="{{layoutEntry.forceRight || undefined}}"
|
||||
style="
|
||||
--umb-block-grid--item-column-span: {{layoutEntry.columnSpan}};
|
||||
--umb-block-grid--item-row-span: {{layoutEntry.rowSpan}};
|
||||
@@ -60,7 +58,7 @@
|
||||
|
||||
<uui-button-inline-create
|
||||
ng-if="!vm.blockEditorApi.readonly && vm.depth !== '0' && !(vm.entries.length === 0 || !vm.entriesForm.areaMinCount.$valid)"
|
||||
class="umb-block-grid__block--after-inline-create-button"
|
||||
class="umb-block-grid__block--last-inline-create-button"
|
||||
ng-mouseover="vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey)"
|
||||
ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)"
|
||||
ng-click="vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.entries.length, $event)">
|
||||
@@ -119,7 +117,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.</localize>
|
||||
>%0% must be present between %2%–%3% times.</localize>
|
||||
</div>
|
||||
</div>
|
||||
<span ng-message="valServer" ng-bind-html="vm.entriesForm.areaTypeRequirements.errorMsg"></span>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<uui-button-inline-create
|
||||
ng-if="!vm.blockEditorApi.readonly"
|
||||
ng-if="!vm.blockEditorApi.readonly && !vm.hideInlineCreateAbove"
|
||||
class="umb-block-grid__block--inline-create-button --above"
|
||||
style="width: {{vm.inlineCreateAboveWidth}};"
|
||||
ng-class="{'--at-root': vm.depth === '0'}"
|
||||
ng-click="vm.blockEditorApi.requestShowCreate(vm.parentBlock, vm.areaKey, vm.index, $event)"
|
||||
ng-mouseover="vm.blockEditorApi.internal.showAreaHighlight(vm.parentBlock, vm.areaKey)"
|
||||
ng-mouseleave="vm.blockEditorApi.internal.hideAreaHighlight(vm.parentBlock, vm.areaKey)">
|
||||
ng-click="vm.clickInlineCreateAbove()"
|
||||
ng-mouseover="vm.mouseOverInlineCreate()"
|
||||
ng-mouseleave="vm.mouseOutInlineCreate()">
|
||||
</uui-button-inline-create>
|
||||
|
||||
<ng-form name="vm.blockForm" val-server-match="{ 'contains' : { 'valServerMatchContent': vm.layoutEntry.$block.content.key, 'valServerMatchSettings': vm.layoutEntry.$block.settings.key } }">
|
||||
@@ -29,9 +30,15 @@
|
||||
parent-form="vm.blockForm"
|
||||
style="--umb-block-grid--area-grid-columns: {{vm.areaGridColumns}}"
|
||||
>
|
||||
<slot
|
||||
ng-repeat="proxyProp in vm.proxyProperties track by proxyProp.slotName"
|
||||
data-is-property-editor-proxy
|
||||
name="{{proxyProp.slotName}}"
|
||||
slot="{{proxyProp.slotName}}">
|
||||
</slot>
|
||||
<umb-block-grid-entries
|
||||
slot="area-container"
|
||||
ng-repeat="areaEntry in vm.layoutEntry.areas track by areaEntry.key"
|
||||
slot="{{::areaEntry.$config.alias}}"
|
||||
class="umb-block-grid__area"
|
||||
ng-class="{'--highlight': areaEntry.$highlight}"
|
||||
data-area-col-span="{{::areaEntry.$config.columnSpan}}"
|
||||
@@ -58,7 +65,6 @@
|
||||
</umb-block-grid-block>
|
||||
|
||||
<div class="umb-block-grid__block--validation-border"></div>
|
||||
<!--<div class="umb-block-grid__block--validation-badge">!</div>-->
|
||||
|
||||
<div class="umb-block-grid__block--context">
|
||||
<div class="__context-bar">
|
||||
@@ -127,39 +133,7 @@
|
||||
</div>
|
||||
|
||||
<button
|
||||
ng-if="vm.blockEditorApi.readonly !== true && (vm.layoutColumnsInt !== vm.layoutEntry.columnSpan || (vm.layoutColumnsInt === vm.layoutEntry.columnSpan && vm.layoutEntry.forceLeft))"
|
||||
type="button"
|
||||
aria-labelledby="forceLeftLabel"
|
||||
title="@blockEditor_forceLeftButton"
|
||||
localize="title"
|
||||
class="umb-block-grid__force-left"
|
||||
ng-class="{ '--active': vm.layoutEntry.forceLeft }"
|
||||
ng-click="vm.toggleForceLeft()">
|
||||
<span class="sr-only">
|
||||
<localize ng-if="!vm.layoutEntry.forceLeft" id="forceLeftLabel" key="blockEditor_forceLeftLabel">Force placement at left side</localize>
|
||||
<localize ng-if="vm.layoutEntry.forceLeft" id="forceLeftLabel" key="blockEditor_unforceLeftLabel">Remove forced placement at left side</localize>
|
||||
</span>
|
||||
<umb-icon icon="icon-navigation-left" class="icon"></umb-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
ng-if="vm.blockEditorApi.readonly !== true && (vm.layoutColumnsInt !== vm.layoutEntry.columnSpan || (vm.layoutColumnsInt === vm.layoutEntry.columnSpan && vm.layoutEntry.forceRight))"
|
||||
type="button"
|
||||
aria-labelledby="forceRightLabel"
|
||||
title="@blockEditor_forceRightButton"
|
||||
localize="title"
|
||||
class="umb-block-grid__force-right"
|
||||
ng-class="{ '--active': vm.layoutEntry.forceRight }"
|
||||
ng-click="vm.toggleForceRight()">
|
||||
<span class="sr-only">
|
||||
<localize ng-if="!vm.layoutEntry.forceRight" id="forceRightLabel" key="blockEditor_forceRightLabel">Force placement at right side</localize>
|
||||
<localize ng-if="vm.layoutEntry.forceRight" id="forceRightLabel" key="blockEditor_unforceRightLabel">Remove forced placement at right side</localize>
|
||||
</span>
|
||||
<umb-icon icon="icon-navigation-right" class="icon"></umb-icon>
|
||||
</button>
|
||||
|
||||
<button
|
||||
ng-if="::!vm.blockEditorApi.readonly && (vm.layoutEntry.$block.config.columnSpanOptions.length > 1 || (vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan))"
|
||||
ng-if="::!vm.blockEditorApi.readonly && vm.canScale"
|
||||
type="button"
|
||||
title="@blockEditor_scaleHandlerButtonTitle"
|
||||
localize="title"
|
||||
@@ -167,7 +141,7 @@
|
||||
ng-mousedown="vm.scaleHandlerMouseDown($event)"
|
||||
ng-keyup="vm.scaleHandlerKeyUp($event)">
|
||||
</button>
|
||||
<div ng-if="::!vm.blockEditorApi.readonly && (vm.layoutEntry.$block.config.columnSpanOptions.length > 1 || (vm.layoutEntry.$block.config.rowMinSpan && vm.layoutEntry.$block.config.rowMaxSpan && vm.layoutEntry.$block.config.rowMaxSpan !== vm.layoutEntry.$block.config.rowMinSpan))"
|
||||
<div ng-if="::!vm.blockEditorApi.readonly && vm.canScale"
|
||||
class="umb-block-grid__scale-label">
|
||||
{{vm.layoutEntry.columnSpan}} x {{vm.layoutEntry.rowSpan}}
|
||||
</div>
|
||||
@@ -180,10 +154,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>
|
||||
</uui-button-inline-create>
|
||||
<div ng-if="vm.hideInlineCreateAfter"
|
||||
class="umb-block-grid__block--inline-create-button --after --detector" ng-mouseover="vm.mouseOverInlineCreateAfter()">
|
||||
</div>
|
||||
</uui-button-inline-create>
|
||||
@@ -4,7 +4,19 @@
|
||||
|
||||
<div class="umb-block-grid__wrapper" ng-style="vm.editorWrapperStyles">
|
||||
|
||||
<div ng-if="vm.loading !== true">
|
||||
<umb-editor-sub-header ng-if="vm.sortMode" appearance="blue">
|
||||
<umb-editor-sub-header-content-right>
|
||||
<umb-button
|
||||
type="button"
|
||||
icon="icon-delete"
|
||||
button-style="primary"
|
||||
label-key="blockEditor_actionExitSortMode"
|
||||
action="vm.exitSortMode()">
|
||||
</umb-button>
|
||||
</umb-editor-sub-header-content-right>
|
||||
</umb-editor-sub-header>
|
||||
|
||||
<div ng-show="vm.loading !== true">
|
||||
|
||||
<umb-block-grid-root
|
||||
grid-columns="{{::vm.gridColumns}}"
|
||||
@@ -12,11 +24,12 @@
|
||||
stylesheet="{{::vm.layoutStylesheet}}"
|
||||
block-editor-api="vm.blockEditorApi"
|
||||
property-editor-form="vm.propertyForm"
|
||||
entries="vm.layout">
|
||||
entries="vm.layout"
|
||||
loading="vm.loading"
|
||||
>
|
||||
</umb-block-grid-root>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input type="hidden" name="minCount" ng-model="vm.layout" val-server="minCount" />
|
||||
<input type="hidden" name="maxCount" ng-model="vm.layout" val-server="maxCount" />
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
.umb-block-grid__wrapper {
|
||||
position: relative;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.umb-block-grid__wrapper .umb-rte {
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div part="area-container">
|
||||
<slot ng-repeat="area in ::block.layout.areas" name="{{::area.$config.alias}}"></slot>
|
||||
</div>
|
||||
@@ -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,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
layoutEntry.columnSpan = closestColumnSpanOption(layoutEntry.columnSpan, relevantColumnSpanOptions, contextColumns)?.columnSpan || contextColumns;
|
||||
}
|
||||
}
|
||||
|
||||
// if no rowSpan, then we set one:
|
||||
if (!layoutEntry.rowSpan) {
|
||||
layoutEntry.rowSpan = 1;
|
||||
@@ -375,12 +445,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 +500,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 +850,8 @@
|
||||
vm.requestShowCreate = requestShowCreate;
|
||||
function requestShowCreate(parentBlock, areaKey, createIndex, mouseEvent, options) {
|
||||
|
||||
vm.hideAreaHighlight(parentBlock, areaKey);
|
||||
|
||||
if (vm.blockTypePickerIsOpen === true) {
|
||||
return;
|
||||
}
|
||||
@@ -1254,6 +1328,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 +1384,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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<div
|
||||
style="display:contents;"
|
||||
ng-class="{'show-validation': vm.blockEditorApi.internal.showValidation}"
|
||||
ng-include="'${model.view}'"></div>
|
||||
ng-include="api.internal.sortMode ? api.internal.sortModeView : '${model.view}'"></div>
|
||||
`;
|
||||
$compile(shadowRoot)($scope);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -198,9 +203,6 @@
|
||||
var dragY = 0;
|
||||
var dragOffsetX = 0;
|
||||
|
||||
var ghostElIndicateForceLeft = null;
|
||||
var ghostElIndicateForceRight = null;
|
||||
|
||||
var approvedContainerEl = null;
|
||||
|
||||
// Setup DOM method for communication between sortables:
|
||||
@@ -210,6 +212,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 +229,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 +245,6 @@
|
||||
else {
|
||||
prevEntries.splice(oldIndex, 1);
|
||||
}
|
||||
|
||||
vm.entries.splice(newIndex, 0, syncEntry);
|
||||
|
||||
const contextColumns = vm.blockEditorApi.internal.getContextColumns(vm.parentBlock, vm.areaKey);
|
||||
@@ -246,12 +259,6 @@
|
||||
} else {
|
||||
syncEntry.columnSpan = contextColumns;
|
||||
}
|
||||
|
||||
if(syncEntry.columnSpan === contextColumns) {
|
||||
// If we are full width, then reset forceLeft/right.
|
||||
syncEntry.forceLeft = false;
|
||||
syncEntry.forceRight = false;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
@@ -261,6 +268,7 @@
|
||||
|
||||
function _indication(contextVM, movingEl) {
|
||||
|
||||
// Remove old indication:
|
||||
if(_lastIndicationContainerVM !== contextVM && _lastIndicationContainerVM !== null) {
|
||||
_lastIndicationContainerVM.hideNotAllowed();
|
||||
_lastIndicationContainerVM.revertIndicateDroppable();
|
||||
@@ -269,7 +277,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;
|
||||
}
|
||||
|
||||
@@ -382,55 +390,40 @@
|
||||
}
|
||||
|
||||
let verticalDirection = false;
|
||||
if (ghostEl.dataset.forceLeft) {
|
||||
placeAfter = true;
|
||||
} else if (ghostEl.dataset.forceRight) {
|
||||
placeAfter = true;
|
||||
} else {
|
||||
|
||||
// TODO: move calculations out so they can be persisted a bit longer?
|
||||
//const approvedContainerRect = approvedContainerEl.getBoundingClientRect();
|
||||
const approvedContainerComputedStyles = getComputedStyle(approvedContainerEl);
|
||||
const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue("--umb-block-grid--grid-columns"), 10);
|
||||
|
||||
// if the related element is forceLeft and we are in the left side, we will set vertical direction, to correct placeAfter.
|
||||
if (foundRelatedEl.dataset.forceLeft && placeAfter === false) {
|
||||
verticalDirection = true;
|
||||
} else
|
||||
// if the related element is forceRight and we are in the right side, we will set vertical direction, to correct placeAfter.
|
||||
if (foundRelatedEl.dataset.forceRight && placeAfter === true) {
|
||||
verticalDirection = true;
|
||||
} else {
|
||||
const relatedColumns = parseInt(foundRelatedEl.dataset.colSpan, 10);
|
||||
const ghostColumns = parseInt(ghostEl.dataset.colSpan, 10);
|
||||
|
||||
// TODO: move calculations out so they can be persisted a bit longer?
|
||||
//const approvedContainerRect = approvedContainerEl.getBoundingClientRect();
|
||||
const approvedContainerComputedStyles = getComputedStyle(approvedContainerEl);
|
||||
const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue("--umb-block-grid--grid-columns"), 10);
|
||||
// Get grid template:
|
||||
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0);
|
||||
|
||||
const relatedColumns = parseInt(foundRelatedEl.dataset.colSpan, 10);
|
||||
const ghostColumns = parseInt(ghostEl.dataset.colSpan, 10);
|
||||
|
||||
// Get grid template:
|
||||
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0);
|
||||
|
||||
// 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)
|
||||
let amountOfColumnsInWeightMap = approvedContainerGridColumns.length;
|
||||
const amountOfUnknownColumns = gridColumnNumber-amountOfColumnsInWeightMap;
|
||||
if(amountOfUnknownColumns > 0) {
|
||||
let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
|
||||
const layoutWidth = approvedContainerRect.width;
|
||||
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
|
||||
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
|
||||
approvedContainerGridColumns.push(missingColumnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const relatedStartX = foundRelatedElRect.left - approvedContainerRect.left;
|
||||
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
|
||||
|
||||
if(relatedStartCol + relatedColumns + ghostColumns > gridColumnNumber) {
|
||||
verticalDirection = true;
|
||||
}
|
||||
// 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)
|
||||
let amountOfColumnsInWeightMap = approvedContainerGridColumns.length;
|
||||
const amountOfUnknownColumns = gridColumnNumber-amountOfColumnsInWeightMap;
|
||||
if(amountOfUnknownColumns > 0) {
|
||||
let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
|
||||
const layoutWidth = approvedContainerRect.width;
|
||||
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
|
||||
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
|
||||
approvedContainerGridColumns.push(missingColumnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const relatedStartX = foundRelatedElRect.left - approvedContainerRect.left;
|
||||
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
|
||||
|
||||
if(relatedStartCol + relatedColumns + ghostColumns > gridColumnNumber) {
|
||||
verticalDirection = true;
|
||||
}
|
||||
|
||||
if (verticalDirection) {
|
||||
placeAfter = (dragY > foundRelatedElRect.top + (foundRelatedElRect.height*.5));
|
||||
}
|
||||
@@ -485,56 +478,6 @@
|
||||
rqaId = requestAnimationFrame(_moveGhostElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(vm.movingLayoutEntry.columnSpan !== vm.layoutColumnsInt) {
|
||||
|
||||
const oldForceLeft = vm.movingLayoutEntry.forceLeft;
|
||||
const oldForceRight = vm.movingLayoutEntry.forceRight;
|
||||
|
||||
var newValue = (dragX < targetRect.left);
|
||||
if(newValue !== oldForceLeft) {
|
||||
vm.movingLayoutEntry.forceLeft = newValue;
|
||||
if(oldForceRight) {
|
||||
vm.movingLayoutEntry.forceRight = false;
|
||||
if(ghostElIndicateForceRight) {
|
||||
ghostEl.removeChild(ghostElIndicateForceRight);
|
||||
ghostElIndicateForceRight = null;
|
||||
}
|
||||
}
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated
|
||||
$scope.$evalAsync();
|
||||
|
||||
// Append element for indication, as angularJS lost connection:
|
||||
if(newValue === true) {
|
||||
ghostElIndicateForceLeft = document.createElement("div");
|
||||
ghostElIndicateForceLeft.className = "indicateForceLeft";
|
||||
ghostEl.appendChild(ghostElIndicateForceLeft);
|
||||
} else if(ghostElIndicateForceLeft) {
|
||||
ghostEl.removeChild(ghostElIndicateForceLeft);
|
||||
ghostElIndicateForceLeft = null;
|
||||
}
|
||||
}
|
||||
|
||||
newValue = (dragX > targetRect.right) && (vm.movingLayoutEntry.forceLeft !== true);
|
||||
if(newValue !== oldForceRight) {
|
||||
vm.movingLayoutEntry.forceRight = newValue;
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
vm.movingLayoutEntry.$block.__scope.$evalAsync();// needed for the block to be updated
|
||||
$scope.$evalAsync();
|
||||
|
||||
// Append element for indication, as angularJS lost connection:
|
||||
if(newValue === true) {
|
||||
ghostElIndicateForceRight = document.createElement("div");
|
||||
ghostElIndicateForceRight.className = "indicateForceRight";
|
||||
ghostEl.appendChild(ghostElIndicateForceRight);
|
||||
} else if(ghostElIndicateForceRight) {
|
||||
ghostEl.removeChild(ghostElIndicateForceRight);
|
||||
ghostElIndicateForceRight = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +500,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;
|
||||
@@ -568,15 +515,9 @@
|
||||
|
||||
const oldIndex = evt.oldIndex;
|
||||
vm.movingLayoutEntry = contextVM.getLayoutEntryByIndex(oldIndex);
|
||||
if(vm.movingLayoutEntry.forceLeft || vm.movingLayoutEntry.forceRight) {
|
||||
// if one of these where true before, then we made a change here:
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
}
|
||||
vm.movingLayoutEntry.forceLeft = false;
|
||||
vm.movingLayoutEntry.forceRight = false;
|
||||
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 +528,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,16 +558,7 @@
|
||||
}
|
||||
window.removeEventListener('drag', _onDragMove);
|
||||
window.removeEventListener('dragover', _onDragMove);
|
||||
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0);
|
||||
|
||||
if(ghostElIndicateForceLeft) {
|
||||
ghostEl.removeChild(ghostElIndicateForceLeft);
|
||||
ghostElIndicateForceLeft = null;
|
||||
}
|
||||
if(ghostElIndicateForceRight) {
|
||||
ghostEl.removeChild(ghostElIndicateForceRight);
|
||||
ghostElIndicateForceRight = null;
|
||||
}
|
||||
vm.blockEditorApi.internal.exitDraggingMode();
|
||||
|
||||
// ensure not-allowed indication is removed.
|
||||
if(_lastIndicationContainerVM) {
|
||||
@@ -643,6 +573,9 @@
|
||||
ghostRect = null;
|
||||
ghostEl = null;
|
||||
relatedEl = null;
|
||||
vm.containedPropertyEditorProxies = [];
|
||||
|
||||
vm.notifyVisualUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -44,23 +44,25 @@
|
||||
}
|
||||
|
||||
function closestColumnSpanOption(target, map, max) {
|
||||
const result = map.reduce((a, b) => {
|
||||
if (a.columnSpan > max) {
|
||||
return b;
|
||||
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;
|
||||
}
|
||||
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 max;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,11 +88,17 @@
|
||||
areaKey: "<",
|
||||
propertyEditorForm: "<?",
|
||||
depth: "@"
|
||||
},
|
||||
require: {
|
||||
umbBlockGridEntries: "?^^umbBlockGridEntries"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function BlockGridEntryController($scope, $element) {
|
||||
function BlockGridEntryController($scope, $element, $timeout) {
|
||||
|
||||
let updateInlineCreateTimeout;
|
||||
let updateInlineCreateRaf;
|
||||
|
||||
const unsubscribe = [];
|
||||
const vm = this;
|
||||
@@ -98,10 +106,37 @@
|
||||
vm.isHoveringArea = false;
|
||||
vm.isScaleMode = false;
|
||||
vm.layoutColumnsInt = 0;
|
||||
vm.inlineCreateAboveWidth = "";
|
||||
vm.hideInlineCreateAbove = true;
|
||||
vm.hideInlineCreateAfter = true;
|
||||
vm.canScale = false;
|
||||
|
||||
vm.proxyProperties = [];
|
||||
vm.onAppendProxyProperty = (event) => {
|
||||
// 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.
|
||||
@@ -139,27 +190,16 @@
|
||||
vm.mouseLeaveArea = function() {
|
||||
vm.isHoveringArea = false;
|
||||
}
|
||||
vm.toggleForceLeft = function() {
|
||||
vm.layoutEntry.forceLeft = !vm.layoutEntry.forceLeft;
|
||||
if(vm.layoutEntry.forceLeft) {
|
||||
vm.layoutEntry.forceRight = false;
|
||||
}
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
}
|
||||
vm.toggleForceRight = function() {
|
||||
vm.layoutEntry.forceRight = !vm.layoutEntry.forceRight;
|
||||
if(vm.layoutEntry.forceRight) {
|
||||
vm.layoutEntry.forceLeft = false;
|
||||
}
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
}
|
||||
|
||||
// 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 +211,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 +222,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 +237,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 +283,28 @@
|
||||
gridRows.push(50);
|
||||
gridRows.push(50);
|
||||
gridRows.push(50);
|
||||
gridRows.push(50);
|
||||
gridRows.push(50);
|
||||
}
|
||||
|
||||
vm.scaleHandlerMouseDown = function($event) {
|
||||
$event.originalEvent.preventDefault();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
layoutContainer = $element[0].closest('.umb-block-grid__layout-container');
|
||||
if(!layoutContainer) {
|
||||
console.error($element[0], 'could not find parent layout-container');
|
||||
}
|
||||
|
||||
const layoutContainerRect = layoutContainer.getBoundingClientRect();
|
||||
const layoutItemRect = $element[0].getBoundingClientRect();
|
||||
updateGridLayoutData(layoutContainerRect, layoutItemRect);
|
||||
updateGridLayoutData(layoutContainerRect, layoutItemRect, true);
|
||||
|
||||
|
||||
scaleBoxBackdropEl = document.createElement('div');
|
||||
@@ -256,7 +316,6 @@
|
||||
|
||||
const layoutContainerRect = layoutContainer.getBoundingClientRect();
|
||||
const layoutItemRect = $element[0].getBoundingClientRect();
|
||||
updateGridLayoutData(layoutContainerRect, layoutItemRect);
|
||||
|
||||
|
||||
const startX = layoutItemRect.left - layoutContainerRect.left;
|
||||
@@ -266,6 +325,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 +346,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 +364,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 +410,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 +425,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 +457,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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -20,7 +20,8 @@
|
||||
stylesheet: "@",
|
||||
blockEditorApi: "<",
|
||||
propertyEditorForm: "<?",
|
||||
entries: "<"
|
||||
entries: "<",
|
||||
loading: "<"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -41,6 +42,7 @@
|
||||
}
|
||||
</style>
|
||||
<div
|
||||
ng-if="vm.loading !== true"
|
||||
class="umb-block-grid"
|
||||
ng-class="{'show-validation': vm.blockEditorApi.internal.showValidation}"
|
||||
data-grid-columns="${vm.gridColumns}"
|
||||
|
||||
@@ -10,18 +10,6 @@
|
||||
--umb-block-grid__layout-item-calc: calc(var(--umb-block-grid--item-column-span) / var(--umb-block-grid--grid-columns));
|
||||
width: calc(var(--umb-block-grid__layout-item-calc) * 100%);
|
||||
}
|
||||
.umb-block-grid__layout-item[data-force-left] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
.umb-block-grid__layout-item[data-force-left]::before {
|
||||
content: '';
|
||||
flex-basis: 100%;
|
||||
height: 0;
|
||||
}
|
||||
.umb-block-grid__layout-item[data-force-right] {
|
||||
margin-left: auto;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) {
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--umb-block-grid--grid-columns, 1), minmax(0, 1fr));
|
||||
grid-gap: 0px;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: minmax(50px, min-content);
|
||||
|
||||
column-gap: var(--umb-block-grid--column-gap, 0);
|
||||
row-gap: var(--umb-block-grid--row-gap, 0);
|
||||
}
|
||||
.umb-block-grid__layout-item {
|
||||
position: relative;
|
||||
@@ -12,22 +14,17 @@
|
||||
grid-column-end: span min(calc(var(--umb-block-grid--item-column-span, 1) * 3), var(--umb-block-grid--grid-columns));
|
||||
grid-row: span var(--umb-block-grid--item-row-span, 1);
|
||||
}
|
||||
.umb-block-grid__layout-item[data-force-left] {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
.umb-block-grid__layout-item[data-force-right] {
|
||||
grid-column-start: calc(1 + var(--umb-block-grid--grid-columns) - var(--umb-block-grid--item-column-span));
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--umb-block-grid--area-grid-columns, var(--umb-block-grid--grid-columns, 1)), minmax(0, 1fr));
|
||||
grid-gap: 0px;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: minmax(50px, min-content);
|
||||
width: 100%;
|
||||
|
||||
column-gap: var(--umb-block-grid--areas-column-gap, 0);
|
||||
row-gap: var(--umb-block-grid--areas-row-gap, 0);
|
||||
}
|
||||
.umb-block-grid__area {
|
||||
position: relative;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
angular.module("umbraco")
|
||||
.controller("Umbraco.PropertyEditors.RTEController",
|
||||
function ($scope, $q, assetsService, $timeout, tinyMceService, angularHelper, tinyMceAssets) {
|
||||
function ($scope, $q, assetsService, $timeout, tinyMceService, angularHelper, tinyMceAssets, $element) {
|
||||
|
||||
// TODO: A lot of the code below should be shared between the grid rte and the normal rte
|
||||
|
||||
var unsubscribe = [];
|
||||
$scope.isLoading = true;
|
||||
|
||||
//To id the html textarea we need to use the datetime ticks because we can have multiple rte's per a single property alias
|
||||
@@ -19,9 +20,9 @@ angular.module("umbraco")
|
||||
var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null;
|
||||
var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null;
|
||||
|
||||
$scope.containerWidth = editorConfig.mode === "distraction-free" ? (width ? width : "auto") : "auto";
|
||||
$scope.containerHeight = editorConfig.mode === "distraction-free" ? (height ? height : "auto") : "auto";
|
||||
$scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit";
|
||||
$scope.containerWidth = "auto";
|
||||
$scope.containerHeight = "auto";
|
||||
$scope.containerOverflow = "inherit";
|
||||
|
||||
var promises = [];
|
||||
|
||||
@@ -73,6 +74,12 @@ angular.module("umbraco")
|
||||
$scope.isLoading = false;
|
||||
});
|
||||
});
|
||||
tinyMceEditor.on("focus", function () {
|
||||
$element[0].dispatchEvent(new CustomEvent('umb-rte-focus', {composed: true, bubbles: true}));
|
||||
});
|
||||
tinyMceEditor.on("blur", function () {
|
||||
$element[0].dispatchEvent(new CustomEvent('umb-rte-blur', {composed: true, bubbles: true}));
|
||||
});
|
||||
|
||||
//initialize the standard editor functionality for Umbraco
|
||||
tinyMceService.initializeEditor({
|
||||
@@ -96,11 +103,11 @@ angular.module("umbraco")
|
||||
}, 150);
|
||||
|
||||
//listen for formSubmitting event (the result is callback used to remove the event subscription)
|
||||
var unsubscribe = $scope.$on("formSubmitting", function () {
|
||||
unsubscribe.push($scope.$on("formSubmitting", function () {
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
|
||||
$scope.model.value = tinyMceEditor.getContent();
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
$scope.focus = function () {
|
||||
tinyMceEditor.focus();
|
||||
@@ -110,8 +117,13 @@ angular.module("umbraco")
|
||||
// NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom
|
||||
// element might still be there even after the modal has been hidden.
|
||||
$scope.$on('$destroy', function () {
|
||||
unsubscribe();
|
||||
for (var i = 0; i < unsubscribe.length; i++) {
|
||||
unsubscribe[i]();
|
||||
}
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null) {
|
||||
if($element) {
|
||||
$element[0]?.dispatchEvent(new CustomEvent('blur', {composed: true, bubbles: true}));
|
||||
}
|
||||
tinyMceEditor.destroy()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,6 +22,10 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController",
|
||||
if (!$scope.model.value.mode) {
|
||||
$scope.model.value.mode = "classic";
|
||||
}
|
||||
else if ($scope.model.value.mode === 'distraction-free') {
|
||||
// Due to legacy reasons, the older 'distraction-free' mode is kept and remapped to 'inline'
|
||||
$scope.model.value.mode = 'inline';
|
||||
}
|
||||
|
||||
tinyMceService.configuration().then(function(config){
|
||||
$scope.tinyMceConfig = config;
|
||||
|
||||
@@ -40,9 +40,8 @@
|
||||
<umb-control-group label="Mode" description="Select mode">
|
||||
<div class="vertical-align-items">
|
||||
<select ng-model="model.value.mode">
|
||||
<option value="classic">Classic</option> <!-- Theme:silver & inline:false-->
|
||||
<option value="inline">Inline</option> <!-- Theme:silver & inline:true-->
|
||||
<option value="distraction-free">Distraction Free</option> <!-- Theme:silver & toolbars:null & inline:true -->
|
||||
<option value="classic">Classic</option> <!-- Theme:modern & inline:false-->
|
||||
<option value="inline">Inline</option> <!-- Theme:modern & inline:true-->
|
||||
</select>
|
||||
</div>
|
||||
</umb-control-group>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
describe('RTE controller tests', function () {
|
||||
var scope, controllerFactory;
|
||||
var scope, controllerFactory, element;
|
||||
|
||||
//mock tinymce globals
|
||||
if ((typeof tinymce) === "undefined") {
|
||||
@@ -23,6 +23,7 @@ describe('RTE controller tests', function () {
|
||||
controllerFactory = $controller;
|
||||
scope = $rootScope.$new();
|
||||
scope.model = {value: "<p>hello</p>"};
|
||||
element = $("<div></div>");
|
||||
}));
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user