Merge branch 'release/11.0' into v11/merge_release_into_dev
# Conflicts: # src/JsonSchema/AppSettings.cs # src/Umbraco.Core/Configuration/Models/MarketplaceSettings.cs # src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/blockgridui.less # src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbblockgridentries.component.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockgrid/umbraco-blockgridlayout-flexbox.css # version.json
This commit is contained in:
@@ -291,11 +291,6 @@ function dependencies() {
|
||||
"./node_modules/@umbraco-ui/uui-css/dist/uui-text.css"
|
||||
],
|
||||
"base": "./node_modules/@umbraco-ui"
|
||||
},
|
||||
{
|
||||
"name": "sortablejs",
|
||||
"src": ["./node_modules/sortablejs/Sortable.min.js"],
|
||||
"base": "./node_modules/sortablejs"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
11
src/Umbraco.Web.UI.Client/package-lock.json
generated
11
src/Umbraco.Web.UI.Client/package-lock.json
generated
@@ -38,7 +38,6 @@
|
||||
"moment": "2.29.4",
|
||||
"ng-file-upload": "12.2.13",
|
||||
"nouislider": "15.6.1",
|
||||
"sortablejs": "1.15.0",
|
||||
"spectrum-colorpicker2": "2.0.9",
|
||||
"tinymce": "6.2.0",
|
||||
"typeahead.js": "0.11.1",
|
||||
@@ -15132,11 +15131,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
@@ -28990,11 +28984,6 @@
|
||||
"sort-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"sortablejs": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
|
||||
"integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w=="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
"moment": "2.29.4",
|
||||
"ng-file-upload": "12.2.13",
|
||||
"nouislider": "15.6.1",
|
||||
"sortablejs": "1.15.0",
|
||||
"spectrum-colorpicker2": "2.0.9",
|
||||
"tinymce": "6.2.0",
|
||||
"typeahead.js": "0.11.1",
|
||||
|
||||
@@ -71,7 +71,11 @@
|
||||
min-height: 48px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
.blockelement-gridinlineblock-editor > slot {
|
||||
--umb-block-grid--inline-editor--pointer-events--condition: var(--umb-block-grid--dragging-mode) none;
|
||||
pointer-events: var(--umb-block-grid--inline-editor--pointer-events--condition, auto);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -100,6 +104,6 @@
|
||||
|
||||
<slot name="{{vm.propertySlotName}}"></slot>
|
||||
|
||||
<umb-block-grid-render-area-slots></umb-block-grid-render-area-slots>
|
||||
<umb-block-grid-render-area-slots ng-if="block.layout.areas.length > 0"></umb-block-grid-render-area-slots>
|
||||
|
||||
</div>
|
||||
@@ -188,7 +188,8 @@ 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--condition: var(--umb-block-grid--dragging-mode) 0;
|
||||
--umb-block-grid--block-ui-opacity: var(--umb-block-grid--block-ui-opacity--code, 1);
|
||||
|
||||
> .umb-block-grid__block--context {
|
||||
opacity: var(--umb-block-grid--block-ui-opacity);
|
||||
@@ -222,8 +223,8 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
|
||||
.umb-block-grid__block--context {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: 0;
|
||||
top: -21px;
|
||||
right: -1px;
|
||||
font-size: 12px;
|
||||
z-index: 4;
|
||||
display: var(--umb-block-grid--block-ui-display, flex);
|
||||
@@ -244,6 +245,9 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
padding-top: 1px;
|
||||
background-color: #3544B1;
|
||||
color: white;
|
||||
border-top: rgba(255, 255, 255, .7) 1px solid;
|
||||
border-left: rgba(255, 255, 255, .7) 1px solid;
|
||||
border-right: rgba(255, 255, 255, .7) 1px solid;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
display: inline-block;
|
||||
@@ -366,6 +370,7 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
border: @blueDark solid 1px;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, .7);
|
||||
opacity: 0;
|
||||
transition: opacity 120ms;
|
||||
}
|
||||
@@ -412,15 +417,17 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
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));
|
||||
|
||||
--umb-block-grid__block--inline-create-button-display--condition: var(--umb-block-grid--dragging-mode) none;
|
||||
display: var(--umb-block-grid__block--inline-create-button-display--condition);
|
||||
}
|
||||
.umb-block-grid__block--inline-create-button.--above {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
width: var(--umb-block-grid-editor--inline-create-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 {
|
||||
.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;
|
||||
}
|
||||
@@ -428,7 +435,7 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
/* If at root, and full-width then become 40px wider: */
|
||||
--calc: clamp(0, calc(var(--umb-block-grid--item-column-span) - (var(--umb-block-grid--grid-columns)-1)), 1);
|
||||
left: calc(-20px * var(--calc));
|
||||
width: calc(100% + 40px * var(--calc));
|
||||
width: calc(var(--umb-block-grid-editor--inline-create-width, 100%) + 40px * var(--calc));
|
||||
}
|
||||
|
||||
.umb-block-grid__block--inline-create-button.--after {
|
||||
@@ -454,6 +461,10 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
/* Move inline create button slightly up, to avoid collision with others*/
|
||||
margin-bottom: -7px;
|
||||
margin-top: -13px;
|
||||
|
||||
/** Avoid showing last-inline-create in dragging-mode */
|
||||
--umb-block-grid__block--last-inline-create-button-display--condition: var(--umb-block-grid--dragging-mode) none;
|
||||
display: var(--umb-block-grid__block--last-inline-create-button-display--condition);
|
||||
}
|
||||
|
||||
|
||||
@@ -553,6 +564,7 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
|
||||
.umb-block-grid__area-actions {
|
||||
grid-column: span var(--umb-block-grid--area-column-span);
|
||||
flex-grow: 1;
|
||||
opacity: calc(var(--umb-block-grid--hint-area-ui, 0) * .5 + var(--umb-block-grid--show-area-ui, 0));
|
||||
transition: opacity 120ms;
|
||||
|
||||
@@ -590,10 +602,12 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__layout-item-placeholder {
|
||||
background: transparent;
|
||||
border-radius: 3px;
|
||||
|
||||
box-sizing: border-box;
|
||||
border: solid 1px;
|
||||
border-color: rgba(@blueDark, .5);
|
||||
border-radius: 3px;
|
||||
@@ -601,9 +615,11 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__layout-item-placeholder > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.umb-block-grid__layout-item-placeholder::before {
|
||||
content: '';
|
||||
position:absolute;
|
||||
@@ -611,6 +627,7 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
inset: 0;
|
||||
border-radius: 3px;
|
||||
background-color: white;
|
||||
pointer-events:none;
|
||||
}
|
||||
.umb-block-grid__layout-item-placeholder::after {
|
||||
content: '';
|
||||
@@ -618,6 +635,7 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
z-index:1;
|
||||
inset: 0;
|
||||
border-radius: 3px;
|
||||
pointer-events:none;
|
||||
|
||||
transition: background-color 240ms ease-in;
|
||||
animation: umb-block-grid__placeholder__pulse 400ms ease-in-out alternate infinite;
|
||||
@@ -630,7 +648,12 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
|
||||
.umb-block-grid__area {
|
||||
position: relative;
|
||||
--umb-block-grid--show-area-ui: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
--umb-block-grid__area--show-area-ui--condition: var(--umb-block-grid--dragging-mode) 1;
|
||||
--umb-block-grid--show-area-ui: var(--umb-block-grid__area--show-area-ui--condition, 0);
|
||||
}
|
||||
.umb-block-grid__area:focus,
|
||||
.umb-block-grid__area:focus-within,
|
||||
@@ -654,10 +677,9 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
/* 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;
|
||||
box-shadow: 0 0 0 1px rgba(255, 255, 255, .7), inset 0 0 0 1px rgba(255, 255, 255, .7);
|
||||
|
||||
}
|
||||
.umb-block-grid__area:has( .umb-block-grid__layout-item-placeholder )::after {
|
||||
opacity: 1;
|
||||
@@ -674,6 +696,9 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
100% { border-color: rgba(@blueDark, 0.66); }
|
||||
}
|
||||
}
|
||||
.umb-block-grid__area > ng-form {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.umb-block-grid__scalebox-backdrop {
|
||||
position: absolute;
|
||||
@@ -740,4 +765,4 @@ ng-form.ng-invalid > .umb-block-grid__block:not(.--active) > .umb-block-grid__bl
|
||||
color: @errorText;
|
||||
border-radius: 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, .2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
value: {min:vm.area.minAllowed, max:vm.area.maxAllowed}
|
||||
}
|
||||
|
||||
unsubscribe.push($scope.$watch('vm.area.alias', (newVal, oldVal) => {
|
||||
unsubscribe.push($scope.$watch('vm.area.alias', (newVal) => {
|
||||
$scope.model.updateTitle();
|
||||
if($scope.blockGridBlockConfigurationAreaForm.alias) {
|
||||
$scope.blockGridBlockConfigurationAreaForm.alias.$setValidity("alias", $scope.model.otherAreaAliases.indexOf(newVal) === -1);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</umb-editor-header>
|
||||
|
||||
<umb-editor-container>
|
||||
<div class="form-vertical umb-block-grid-block-configuration-layout">
|
||||
<div class="form-vertical">
|
||||
|
||||
<div class="umb-group-panel">
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
"groupKey": null
|
||||
};
|
||||
|
||||
|
||||
function BlockConfigurationController($scope, $element, $http, elementTypeResource, overlayService, localizationService, editorService, eventsService, udiService, dataTypeResource, umbRequestHelper) {
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
var vm = this;
|
||||
const vm = this;
|
||||
|
||||
vm.openBlock = null;
|
||||
vm.showSampleDataCTA = false;
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
if (blockGroupModel.value == null) {
|
||||
blockGroupModel.value = [];
|
||||
}
|
||||
|
||||
vm.blockGroups = blockGroupModel.value;
|
||||
|
||||
if (!$scope.model.value) {
|
||||
@@ -72,12 +73,10 @@
|
||||
});
|
||||
|
||||
loadElementTypes();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function loadElementTypes() {
|
||||
return elementTypeResource.getAll().then(function (elementTypes) {
|
||||
return elementTypeResource.getAll().then(elementTypes => {
|
||||
vm.elementTypes = elementTypes;
|
||||
});
|
||||
}
|
||||
@@ -90,13 +89,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribe.push(eventsService.on("editors.documentType.saved", updateUsedElementTypes));
|
||||
|
||||
function removeReferencesToElementTypeKey(contentElementTypeKey) {
|
||||
// Clean up references to this one:
|
||||
$scope.model.value.forEach(blockType => {
|
||||
blockType.areas.forEach(area => {
|
||||
area.specifiedAllowance = area.specifiedAllowance?.filter(allowance =>
|
||||
area.specifiedAllowance = area.specifiedAllowance?.filter(allowance =>
|
||||
allowance.elementTypeKey !== contentElementTypeKey
|
||||
) || [];
|
||||
});
|
||||
@@ -107,7 +107,7 @@
|
||||
// Clean up references to this one:
|
||||
$scope.model.value.forEach(blockType => {
|
||||
blockType.areas.forEach(area => {
|
||||
area.specifiedAllowance = area.specifiedAllowance?.filter(allowance =>
|
||||
area.specifiedAllowance = area.specifiedAllowance?.filter(allowance =>
|
||||
allowance.groupKey !== groupKey
|
||||
) || [];
|
||||
});
|
||||
@@ -134,27 +134,29 @@
|
||||
|
||||
vm.removeBlockByIndex = function (index) {
|
||||
const blockType = $scope.model.value[index];
|
||||
if(blockType) {
|
||||
if (blockType) {
|
||||
$scope.model.value.splice(index, 1);
|
||||
removeReferencesToElementTypeKey(blockType.contentElementTypeKey);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
axis: '',
|
||||
tolerance: "pointer",
|
||||
opacity: 0.7,
|
||||
scroll: true
|
||||
};
|
||||
|
||||
vm.groupSortableOptions = {
|
||||
...defaultOptions,
|
||||
...defaultOptions,
|
||||
axis: 'y',
|
||||
handle: '.__handle',
|
||||
items: ".umb-block-card-group",
|
||||
cursor: "grabbing",
|
||||
placeholder: 'umb-block-card-group --sortable-placeholder'
|
||||
};
|
||||
|
||||
vm.blockSortableOptions = {
|
||||
...defaultOptions,
|
||||
...defaultOptions,
|
||||
"ui-floating": true,
|
||||
connectWith: ".umb-block-card-grid",
|
||||
items: "umb-block-card",
|
||||
@@ -170,7 +172,6 @@
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
vm.getAvailableElementTypes = function () {
|
||||
return vm.elementTypes.filter(function (type) {
|
||||
return !$scope.model.value.find(function (entry) {
|
||||
@@ -201,18 +202,18 @@
|
||||
if (node.metaData.isElement === true) {
|
||||
var key = udiService.getKey(node.udi);
|
||||
// If a Block with this ElementType as content already exists, we will emit it as a possible option.
|
||||
return $scope.model.value.find(function (entry) {
|
||||
return $scope.model.value.find(function(entry) {
|
||||
return key === entry.contentElementTypeKey;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
filterCssClass: "not-allowed",
|
||||
select: function (node) {
|
||||
select: function(node) {
|
||||
vm.addBlockFromElementTypeKey(udiService.getKey(node.udi), groupKey);
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
close: function() {
|
||||
editorService.close();
|
||||
},
|
||||
extraActions: [
|
||||
@@ -241,14 +242,13 @@
|
||||
infiniteMode: true,
|
||||
noTemplate: true,
|
||||
isElement: true,
|
||||
noTemplate: true,
|
||||
submit: function (model) {
|
||||
loadElementTypes().then( function () {
|
||||
submit: function(model) {
|
||||
loadElementTypes().then(function() {
|
||||
callback(model.documentTypeKey);
|
||||
});
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
close: function() {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
@@ -262,24 +262,19 @@
|
||||
$scope.model.value.push(blockType);
|
||||
|
||||
vm.openBlockOverlay(blockType);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
vm.openBlockOverlay = function (block, openAreas) {
|
||||
|
||||
var elementType = vm.getElementTypeByKey(block.contentElementTypeKey);
|
||||
|
||||
if(elementType) {
|
||||
if (elementType) {
|
||||
localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [elementType.name]).then(function (data) {
|
||||
|
||||
var clonedBlockData = Utilities.copy(block);
|
||||
vm.openBlock = block;
|
||||
|
||||
var overlayModel = {
|
||||
const overlayModel = {
|
||||
block: clonedBlockData,
|
||||
allBlockTypes: $scope.model.value,
|
||||
allBlockGroups: vm.blockGroups,
|
||||
@@ -288,11 +283,11 @@
|
||||
title: data,
|
||||
openAreas: openAreas,
|
||||
view: "views/propertyeditors/blockgrid/prevalue/blockgrid.blockconfiguration.overlay.html",
|
||||
size: "large",
|
||||
size: "medium",
|
||||
submit: function(overlayModel) {
|
||||
loadElementTypes()// lets load elementType again, to ensure we are up to date.
|
||||
TransferProperties(overlayModel.block, block);// transfer properties back to block object. (Doing this cause we dont know if block object is added to model jet, therefor we cant use index or replace the object.)
|
||||
|
||||
|
||||
overlayModel.close();
|
||||
},
|
||||
close: function() {
|
||||
@@ -306,14 +301,24 @@
|
||||
|
||||
});
|
||||
} else {
|
||||
alert("Cannot be edited cause ElementType does not exist.");
|
||||
|
||||
const overlay = {
|
||||
close: () => {
|
||||
overlayService.close()
|
||||
}
|
||||
};
|
||||
|
||||
localizationService.localize("blockEditor_elementTypeDoesNotExist").then(data => {
|
||||
overlay.content = data;
|
||||
overlayService.open(overlay);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
vm.requestRemoveGroup = function(blockGroup) {
|
||||
if(blockGroup.key) {
|
||||
if (blockGroup.key) {
|
||||
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockGroupMessage", "blockEditor_confirmDeleteBlockGroupNotice"]).then(function (data) {
|
||||
overlayService.confirmDelete({
|
||||
title: data[0],
|
||||
@@ -332,7 +337,7 @@
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -355,10 +360,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
dataTypeResource.getAll().then(function(dataTypes) {
|
||||
if(dataTypes.filter(x => x.alias === "Umbraco.BlockGrid").length === 0) {
|
||||
if (dataTypes.filter(x => x.alias === "Umbraco.BlockGrid").length === 0) {
|
||||
vm.showSampleDataCTA = true;
|
||||
}
|
||||
});
|
||||
@@ -379,7 +382,7 @@
|
||||
};
|
||||
vm.blockGroups.push(sampleGroup);
|
||||
}
|
||||
|
||||
|
||||
function initSampleBlock(udi, groupKey, options) {
|
||||
const key = udiService.getKey(udi);
|
||||
if ($scope.model.value.find(X => X.contentElementTypeKey === key) === undefined) {
|
||||
@@ -387,7 +390,7 @@
|
||||
$scope.model.value.push(blockType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
initSampleBlock(data.umbBlockGridDemoHeadlineBlock, sampleGroup.key, {"label": "Headline ({{headline | truncate:true:36}})", "view": "~/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoHeadlineBlock.html"});
|
||||
initSampleBlock(data.umbBlockGridDemoImageBlock, sampleGroup.key, {"label": "Image", "view": "~/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoImageBlock.html"});
|
||||
initSampleBlock(data.umbBlockGridDemoRichTextBlock, sampleGroup.key, { "label": "Rich Text ({{richText | ncRichText | truncate:true:36}})", "view": "~/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoRichTextBlock.html"});
|
||||
@@ -412,10 +415,10 @@
|
||||
}
|
||||
];
|
||||
initSampleBlock(data.umbBlockGridDemoTwoColumnLayoutBlock, sampleGroup.key, {"label": "Two Column Layout", "view": "~/App_Plugins/Umbraco.BlockGridEditor.DefaultCustomViews/umbBlockGridDemoTwoColumnLayoutBlock.html", "allowInAreas": false, "areas": twoColumnLayoutAreas});
|
||||
|
||||
|
||||
vm.showSampleDataCTA = false;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
var elementTypeId = elementType.id;
|
||||
const editor = {
|
||||
id: elementTypeId,
|
||||
submit: function (model) {
|
||||
submit: function () {
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<div ng-if="vm.activeTab.alias === 'block'" class="form-vertical umb-block-grid-block-configuration-layout">
|
||||
<div ng-if="vm.activeTab.alias === 'block'" class="form-vertical">
|
||||
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel">
|
||||
<div class="umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<localize key="general_General">General</localize>
|
||||
@@ -80,124 +80,9 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<div>
|
||||
<localize key="blockEditor_sizeOptions">Size options</localize>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_sizeOptionsHelp">Define one or more size options, this enables resizing of the Block</localize>
|
||||
</umb-property-info-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-group-panel__content">
|
||||
|
||||
<button ng-if="!vm.showSizeOptions" type="button" class="btn-reset __settings-input --noValue umb-outline" style="height: 80px;" ng-click="vm.showSizeOptions = true">
|
||||
<localize key="blockEditor_showSizeOptions">Show size options</localize>
|
||||
</button>
|
||||
|
||||
<!-- Column span options -->
|
||||
<div ng-if="vm.showSizeOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="addNewColumnSpan"><localize key="blockEditor_allowedBlockColumns">Available column spans</localize></label>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_allowedBlockColumnsHelp">Define the different number of layout columns this block is allowed to span across.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-block-grid-column-editor model="vm.block.columnSpanOptions" block="vm.block" grid-columns="model.gridColumns"></umb-block-grid-column-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- min/max Row span options -->
|
||||
<div ng-if="vm.showSizeOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="label"><localize key="blockEditor_allowedBlockRows">Available row spans</localize></label>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_allowedBlockRowsHelp">Define the range of layout rows this block is allowed to span across.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-property-editor name="rowMinMaxModel" model="vm.rowMinMaxModel" is-pre-value="true"></umb-property-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<localize key="blockEditor_headlineCatalogueAppearance">Catalogue appearance</localize>
|
||||
</div>
|
||||
|
||||
<div class="umb-group-panel__content">
|
||||
|
||||
<button ng-if="!vm.showAppearanceOptions" type="button" class="btn-reset __settings-input --noValue umb-outline" style="height: 80px;" ng-click="vm.showAppearanceOptions = true">
|
||||
<localize key="blockEditor_showAppearanceOptions">Show catalogue appearance</localize>
|
||||
</button>
|
||||
|
||||
<!-- backgroundColor -->
|
||||
<div ng-if="vm.showAppearanceOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="backgroundColor"><localize key="blockEditor_labelBackgroundColor">Background Color</localize></label>
|
||||
<div class="controls">
|
||||
<umb-color-picker
|
||||
ng-model="vm.block.backgroundColor"
|
||||
options="vm.colorPickerOptions"
|
||||
on-change="vm.changeBackgroundColor(color)">
|
||||
</umb-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- iconColor -->
|
||||
<div ng-if="vm.showAppearanceOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="iconColor"><localize key="blockEditor_labelIconColor">Icon Color</localize></label>
|
||||
<div class="controls">
|
||||
<umb-color-picker
|
||||
ng-model="vm.block.iconColor"
|
||||
options="vm.colorPickerOptions"
|
||||
on-change="vm.changeIconColor(color)">
|
||||
</umb-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- thumbnail -->
|
||||
<div ng-if="vm.showAppearanceOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="iconcolor"><localize key="blockEditor_thumbnail">Thumbnail</localize></label>
|
||||
<div class="controls">
|
||||
<div class="__settings-input --hasValue" ng-if="vm.block.thumbnail !== null" >
|
||||
|
||||
<umb-node-preview icon="'icon-document'" name="vm.block.thumbnail"></umb-node-preview>
|
||||
<div class="__control-actions">
|
||||
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.removeThumbnailForBlock(vm.block)">
|
||||
<umb-icon icon="icon-wrong" class="icon"></umb-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-reset __settings-input --noValue --hideText umb-outline" ng-if="vm.block.thumbnail === null" ng-click="vm.addThumbnailForBlock(vm.block)">
|
||||
<localize key="blockEditor_addThumbnail">Add thumbnail</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel">
|
||||
<div class="umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<localize key="blockEditor_headlineAllowance">Permissions</localize>
|
||||
@@ -244,11 +129,61 @@
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<div>
|
||||
<localize key="blockEditor_sizeOptions">Size options</localize>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_sizeOptionsHelp">Define one or more size options, this enables resizing of the Block</localize>
|
||||
</umb-property-info-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-group-panel__content">
|
||||
|
||||
<button ng-if="!vm.showSizeOptions" type="button" class="btn-reset __settings-input --noValue umb-outline" style="height: 80px;" ng-click="vm.showSizeOptions = true">
|
||||
<localize key="blockEditor_showSizeOptions">Show size options</localize>
|
||||
</button>
|
||||
|
||||
<!-- Column span options -->
|
||||
<div ng-if="vm.showSizeOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="addNewColumnSpan"><localize key="blockEditor_allowedBlockColumns">Available column spans</localize></label>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_allowedBlockColumnsHelp">Define the different number of layout columns this block is allowed to span across.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-block-grid-column-editor model="vm.block.columnSpanOptions" block="vm.block" grid-columns="model.gridColumns"></umb-block-grid-column-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- min/max Row span options -->
|
||||
<div ng-if="vm.showSizeOptions" class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="label"><localize key="blockEditor_allowedBlockRows">Available row spans</localize></label>
|
||||
<umb-property-info-button button-title-key="general_readMore">
|
||||
<localize key="blockEditor_allowedBlockRowsHelp">Define the range of layout rows this block is allowed to span across.</localize>
|
||||
</umb-property-info-button>
|
||||
<div class="controls">
|
||||
<umb-property-editor name="rowMinMaxModel" model="vm.rowMinMaxModel" is-pre-value="true"></umb-property-editor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="vm.activeTab.alias === 'areas'">
|
||||
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel --span-two-cols">
|
||||
<div class="umb-group-panel">
|
||||
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
@@ -288,7 +223,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="vm.activeTab.alias === 'advance'">
|
||||
<div class="umb-group-panel umb-block-grid-block-configuration__umb-group-panel">
|
||||
<div class="umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<localize key="blockEditor_headlineAdvanced">Advanced</localize>
|
||||
@@ -384,6 +319,66 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="umb-group-panel">
|
||||
|
||||
<div class="umb-group-panel__header">
|
||||
<localize key="blockEditor_headlineCatalogueAppearance">Catalogue appearance</localize>
|
||||
</div>
|
||||
|
||||
<div class="umb-group-panel__content">
|
||||
|
||||
<!-- backgroundColor -->
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="backgroundColor"><localize key="blockEditor_labelBackgroundColor">Background Color</localize></label>
|
||||
<div class="controls">
|
||||
<umb-color-picker
|
||||
ng-model="vm.block.backgroundColor"
|
||||
options="vm.colorPickerOptions"
|
||||
on-change="vm.changeBackgroundColor(color)">
|
||||
</umb-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- iconColor -->
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="iconColor"><localize key="blockEditor_labelIconColor">Icon Color</localize></label>
|
||||
<div class="controls">
|
||||
<umb-color-picker
|
||||
ng-model="vm.block.iconColor"
|
||||
options="vm.colorPickerOptions"
|
||||
on-change="vm.changeIconColor(color)">
|
||||
</umb-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- thumbnail -->
|
||||
<div class="control-group umb-control-group -no-border">
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label" for="iconcolor"><localize key="blockEditor_thumbnail">Thumbnail</localize></label>
|
||||
<div class="controls">
|
||||
<div class="__settings-input --hasValue" ng-if="vm.block.thumbnail !== null" >
|
||||
|
||||
<umb-node-preview icon="'icon-document'" name="vm.block.thumbnail"></umb-node-preview>
|
||||
<div class="__control-actions">
|
||||
<button type="button" class="btn-reset __control-actions-btn --remove umb-outline" ng-click="vm.removeThumbnailForBlock(vm.block)">
|
||||
<umb-icon icon="icon-wrong" class="icon"></umb-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn-reset __settings-input --noValue --hideText umb-outline" ng-if="vm.block.thumbnail === null" ng-click="vm.addThumbnailForBlock(vm.block)">
|
||||
<localize key="blockEditor_addThumbnail">Add thumbnail</localize>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</umb-editor-container>
|
||||
@@ -413,4 +408,4 @@
|
||||
</umb-editor-footer>
|
||||
</umb-editor-view>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,5 @@
|
||||
.umb-block-grid-block-configuration-overlay {
|
||||
|
||||
.umb-block-grid-block-configuration-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
grid-gap: 0 20px;
|
||||
grid-auto-flow: row;
|
||||
grid-auto-rows: minmax(50px, auto);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid-block-configuration__umb-group-panel {
|
||||
@media (max-width: 1024px) {
|
||||
grid-column: span 2;
|
||||
}
|
||||
&.--span-two-cols {
|
||||
grid-column: span 2;
|
||||
}
|
||||
&.--span-two-rows {
|
||||
grid-row: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-node-preview {
|
||||
flex-grow: 1;
|
||||
|
||||
@@ -2,33 +2,40 @@
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<div ng-show="vm.loading !== true" class="umb-block-grid-area-editor__grid-wrapper" style="--umb-block-grid--block-grid-columns: {{vm.block.areaGridColumns || vm.rootLayoutColumns}}">
|
||||
<div ng-if="vm.loading === false"
|
||||
class="umb-block-grid-area-editor__grid-wrapper"
|
||||
style="--umb-block-grid--block-grid-columns: {{vm.block.areaGridColumns || vm.rootLayoutColumns}}"
|
||||
umb-block-grid-sorter="::vm.sorterOptions"
|
||||
umb-block-grid-sorter-model="vm.model">
|
||||
|
||||
<umb-block-grid-configuration-area-entry ng-repeat="area in vm.model track by area.key"
|
||||
class="umb-block-grid-area-editor__area"
|
||||
ng-class="{'--isOpen':vm.openArea === area}"
|
||||
area="area"
|
||||
data-area-key="{{area.key}}"
|
||||
data-col-span="{{area.columnSpan}}"
|
||||
data-row-span="{{area.rowSpan}}"
|
||||
style="
|
||||
--umb-block-grid--grid-column: {{area.columnSpan}};
|
||||
--umb-block-grid--area-column-span: {{area.columnSpan}};
|
||||
--umb-block-grid--area-row-span: {{area.rowSpan}};
|
||||
"
|
||||
area="area"
|
||||
ng-click="vm.editArea(area)"
|
||||
on-edit="vm.editArea(area)"
|
||||
on-delete="vm.requestDeleteArea(area)"
|
||||
>
|
||||
</umb-block-grid-configuration-area-entry>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
ng-disabled="vm.disabled"
|
||||
class="btn-reset umb-block-grid-area-editor__create-button umb-outline"
|
||||
ng-click="vm.onNewAreaClick()">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
ng-disabled="vm.disabled"
|
||||
class="btn-reset umb-block-grid-area-editor__create-button umb-outline"
|
||||
ng-click="vm.onNewAreaClick()">
|
||||
<localize key="general_add">Add</localize>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -217,6 +217,9 @@ Grid part:
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height:50px;
|
||||
width:100%;
|
||||
|
||||
color: @ui-action-discreet-type;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div class="__border"></div>
|
||||
<button type="button" class="btn-reset" ng-click="vm.onClickAdd()">
|
||||
<span class="sr-only">
|
||||
<localize key="TODO spell out the action">Add</localize>
|
||||
<localize key="blockEditor_addColumnSpanOption" tokens="[vm.columnSpanOption.columnSpan]">Add span option</localize>
|
||||
{{vm.columnSpanOption.columnSpan}}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
|
||||
<div class="umb-block-grid__area--actions">
|
||||
|
||||
<button type="button" class="btn-reset umb-outline action --edit" localize="title" title="TODO"
|
||||
<button type="button" class="btn-reset umb-outline action --edit" localize="title" title="blockEditor_configureArea"
|
||||
ng-click="vm.onEditClick($event);">
|
||||
<umb-icon icon="icon-edit" class="icon"></umb-icon>
|
||||
<span class="sr-only">
|
||||
<localize key="general_edit">Edit</localize>
|
||||
<localize key="blockEditor_configureArea">Edit</localize>
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" class="btn-reset umb-outline action --delete" localize="title" title="TODO"
|
||||
<button type="button" class="btn-reset umb-outline action --delete" localize="title" title="blockEditor_deleteArea"
|
||||
ng-click="vm.onDeleteClick($event);">
|
||||
<umb-icon icon="icon-trash" class="icon"></umb-icon>
|
||||
<span class="sr-only">
|
||||
<localize key="general_delete">Delete</localize>
|
||||
<localize key="blockEditor_deleteArea">Delete</localize>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function BlockGridAreaAllowanceController($scope, $element, assetsService, localizationService, editorService) {
|
||||
function BlockGridAreaAllowanceController($scope) {
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
|
||||
@@ -1,6 +1,44 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
|
||||
// Utils:
|
||||
|
||||
function getInterpolatedIndexOfPositionInWeightMap(target, weights) {
|
||||
const map = [0];
|
||||
weights.reduce((a, b, i) => { return map[i+1] = a+b; }, 0);
|
||||
const foundValue = map.reduce((a, b) => {
|
||||
let aDiff = Math.abs(a - target);
|
||||
let bDiff = Math.abs(b - target);
|
||||
|
||||
if (aDiff === bDiff) {
|
||||
return a < b ? a : b;
|
||||
} else {
|
||||
return bDiff < aDiff ? b : a;
|
||||
}
|
||||
})
|
||||
const foundIndex = map.indexOf(foundValue);
|
||||
const targetDiff = (target-foundValue);
|
||||
let interpolatedIndex = foundIndex;
|
||||
if (targetDiff < 0 && foundIndex === 0) {
|
||||
// Don't adjust.
|
||||
} else if (targetDiff > 0 && foundIndex === map.length-1) {
|
||||
// Don't adjust.
|
||||
} else {
|
||||
const foundInterpolationWeight = weights[targetDiff >= 0 ? foundIndex : foundIndex-1];
|
||||
interpolatedIndex += foundInterpolationWeight === 0 ? interpolatedIndex : (targetDiff/foundInterpolationWeight)
|
||||
}
|
||||
return interpolatedIndex;
|
||||
}
|
||||
|
||||
function getAccumulatedValueOfIndex(index, weights) {
|
||||
let i = 0, len = Math.min(index, weights.length), calc = 0;
|
||||
while(i<len) {
|
||||
calc += weights[i++];
|
||||
}
|
||||
return calc;
|
||||
}
|
||||
|
||||
function TransferProperties(fromObject, toObject) {
|
||||
for (var p in fromObject) {
|
||||
toObject[p] = fromObject[p];
|
||||
@@ -22,7 +60,7 @@
|
||||
controller: BlockGridAreaController,
|
||||
controllerAs: "vm",
|
||||
bindings: {
|
||||
model: "=",
|
||||
model: "<",
|
||||
block: "<",
|
||||
allBlockTypes: "<",
|
||||
allBlockGroups: "<",
|
||||
@@ -34,7 +72,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function BlockGridAreaController($scope, $element, assetsService, localizationService, editorService, overlayService) {
|
||||
function BlockGridAreaController($scope, localizationService, editorService, overlayService) {
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
@@ -45,46 +83,79 @@
|
||||
vm.$onInit = function() {
|
||||
|
||||
vm.rootLayoutColumns = vm.gridColumns;
|
||||
|
||||
assetsService.loadJs('lib/sortablejs/Sortable.min.js', $scope).then(onLoaded);
|
||||
};
|
||||
|
||||
function onLoaded() {
|
||||
vm.loading = false;
|
||||
initializeSortable();
|
||||
}
|
||||
vm.loading = false;
|
||||
|
||||
};
|
||||
|
||||
function initializeSortable() {
|
||||
|
||||
function _sync(evt) {
|
||||
|
||||
const oldIndex = evt.oldIndex,
|
||||
newIndex = evt.newIndex;
|
||||
|
||||
vm.model.splice(newIndex, 0, vm.model.splice(oldIndex, 1)[0]);
|
||||
|
||||
vm.sorterOptions = {
|
||||
resolveVerticalDirection: resolveVerticalDirection,
|
||||
compareElementToModel: (el, modelEntry) => modelEntry.key === el.dataset.areaKey,
|
||||
querySelectModelToElement: (container, modelEntry) => container.querySelector(`[data-area-key='${modelEntry.key}']`),
|
||||
itemHasNestedContainersResolver: () => false,// We never have nested in this case.
|
||||
containerSelector: ".umb-block-grid-area-editor__grid-wrapper",
|
||||
itemSelector: ".umb-block-grid-area-editor__area",
|
||||
placeholderClass: "umb-block-grid-area-editor__area-placeholder",
|
||||
onSync: onSortSync
|
||||
}
|
||||
|
||||
const gridContainerEl = $element[0].querySelector('.umb-block-grid-area-editor__grid-wrapper');
|
||||
function onSortSync() {
|
||||
$scope.$evalAsync();
|
||||
setDirty();
|
||||
}
|
||||
|
||||
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",
|
||||
onAdd: function (evt) {
|
||||
_sync(evt);
|
||||
$scope.$evalAsync();
|
||||
},
|
||||
onUpdate: function (evt) {
|
||||
_sync(evt);
|
||||
$scope.$evalAsync();
|
||||
function resolveVerticalDirection(data) {
|
||||
|
||||
/** We need some data about the grid to figure out if there is room to be placed next to the found element */
|
||||
const approvedContainerComputedStyles = getComputedStyle(data.containerElement);
|
||||
const gridColumnGap = Number(approvedContainerComputedStyles.columnGap.split("px")[0]) || 0;
|
||||
const gridColumnNumber = vm.rootLayoutColumns;
|
||||
|
||||
const foundElColumns = parseInt(data.relatedElement.dataset.colSpan, 10);
|
||||
const currentElementColumns = data.item.columnSpan;
|
||||
|
||||
if(currentElementColumns >= gridColumnNumber) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: setDirty if sort has happend.
|
||||
|
||||
// Get grid template:
|
||||
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0).map((n, i, list) => list.length === i ? n : n + gridColumnGap);
|
||||
|
||||
// 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 = data.containerRect.width;
|
||||
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
|
||||
if(missingColumnWidth > 0) {
|
||||
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
|
||||
approvedContainerGridColumns.push(missingColumnWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let offsetPlacement = 0;
|
||||
/* If placeholder is in this same line, we want to assume that it will offset the placement of the found element,
|
||||
which provides more potential space for the item to drop at.
|
||||
This is relevant in this calculation where we look at the space to determine if its a vertical or horizontal drop in relation to the found element.
|
||||
*/
|
||||
if(data.placeholderIsInThisRow && data.elementRect.left < data.relatedRect.left) {
|
||||
offsetPlacement = -(data.elementRect.width + gridColumnGap);
|
||||
}
|
||||
|
||||
const relatedStartX = Math.max(data.relatedRect.left - data.containerRect.left + offsetPlacement, 0);
|
||||
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
|
||||
|
||||
// If the found related element does not have enough room after which for the current element, then we go vertical mode:
|
||||
return (relatedStartCol + (data.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -93,7 +164,6 @@
|
||||
}
|
||||
|
||||
vm.requestDeleteArea = function (area) {
|
||||
// TODO: Translations
|
||||
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockAreaMessage", "blockEditor_confirmDeleteBlockAreaNotice"]).then(function (data) {
|
||||
overlayService.confirmDelete({
|
||||
title: data[0],
|
||||
@@ -145,8 +215,6 @@
|
||||
|
||||
vm.openArea = null;
|
||||
vm.openAreaOverlay = function (area) {
|
||||
|
||||
// TODO: use the right localization key:
|
||||
localizationService.localize("blockEditor_blockConfigurationOverlayTitle").then(function (localized) {
|
||||
|
||||
var clonedAreaData = Utilities.copy(area);
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function BlockGridColumnController($scope) {
|
||||
|
||||
//var unsubscribe = [];
|
||||
function BlockGridColumnController() {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@@ -58,12 +56,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*$scope.$on("$destroy", function () {
|
||||
for (const subscription of unsubscribe) {
|
||||
subscription();
|
||||
}
|
||||
});*/
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
*
|
||||
* Note for new backoffice: there is a lot of similarities between the Area configuration and the Block entry, as they both share Grid scaling features.
|
||||
* TODO: Can we already as part of this PR make it shared as a dictionary or something?
|
||||
*/
|
||||
|
||||
|
||||
@@ -100,6 +98,13 @@
|
||||
|
||||
function updateGridLayoutData() {
|
||||
|
||||
if(!layoutContainer) {
|
||||
layoutContainer = $element[0].closest('.umb-block-grid-area-editor__grid-wrapper');
|
||||
if(!layoutContainer) {
|
||||
console.error($element[0], 'could not find area-container');
|
||||
}
|
||||
}
|
||||
|
||||
const computedStyles = window.getComputedStyle(layoutContainer);
|
||||
|
||||
gridColumns = computedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x));
|
||||
@@ -126,12 +131,6 @@
|
||||
window.addEventListener('mouseup', vm.onMouseUp);
|
||||
window.addEventListener('mouseleave', vm.onMouseUp);
|
||||
|
||||
|
||||
layoutContainer = $element[0].closest('.umb-block-grid-area-editor__grid-wrapper');
|
||||
if(!layoutContainer) {
|
||||
console.error($element[0], 'could not find area-container');
|
||||
}
|
||||
|
||||
updateGridLayoutData();
|
||||
|
||||
scaleBoxBackdropEl = document.createElement('div');
|
||||
@@ -217,6 +216,8 @@
|
||||
|
||||
vm.scaleHandlerKeyUp = function($event) {
|
||||
|
||||
updateGridLayoutData();
|
||||
|
||||
let addCol = 0;
|
||||
let addRow = 0;
|
||||
|
||||
@@ -236,7 +237,7 @@
|
||||
}
|
||||
|
||||
// Todo: Ensure value fit with configuration.
|
||||
vm.area.columnSpan = Math.max(vm.area.columnSpan + addCol, 1);
|
||||
vm.area.columnSpan = Math.min(Math.max(vm.area.columnSpan + addCol, 1), gridColumns.length);
|
||||
vm.area.rowSpan = Math.max(vm.area.rowSpan + addRow, 1);
|
||||
|
||||
$event.originalEvent.stopPropagation();
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<div
|
||||
class="umb-block-grid__layout-container"
|
||||
ng-class="{
|
||||
'--not-allowing-drop': vm.showNotAllowedUI,
|
||||
'--droppable-indication': vm.droppableIndication
|
||||
'--not-allowing-drop': vm.showNotAllowedUI
|
||||
}"
|
||||
umb-block-grid-sorter="::vm.sorterOptions"
|
||||
umb-block-grid-sorter-model="vm.entries"
|
||||
>
|
||||
<umb-block-grid-entry ng-repeat="layoutEntry in vm.entries track by layoutEntry.$block.key"
|
||||
class="umb-block-grid__layout-item"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<uui-button-inline-create
|
||||
ng-if="!vm.blockEditorApi.readonly && !vm.hideInlineCreateAbove"
|
||||
class="umb-block-grid__block--inline-create-button --above"
|
||||
style="width: {{vm.inlineCreateAboveWidth}};"
|
||||
style="--umb-block-grid-editor--inline-create-width: {{vm.inlineCreateAboveWidth}};"
|
||||
ng-class="{'--at-root': vm.depth === '0'}"
|
||||
ng-click="vm.clickInlineCreateAbove()"
|
||||
ng-mouseover="vm.mouseOverInlineCreate()"
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
createFlow: false
|
||||
};
|
||||
vm.sortMode = false;
|
||||
vm.sortModeView = DefaultViewFolderPath + "gridsortblock/gridsortblock.editor.html";;
|
||||
vm.sortModeView = DefaultViewFolderPath + "gridsortblock/gridsortblock.editor.html";
|
||||
|
||||
localizationService.localizeMany(["grid_addElement", "content_createEmpty", "blockEditor_addThis"]).then(function (data) {
|
||||
vm.labels.grid_addElement = data[0];
|
||||
@@ -166,16 +166,16 @@
|
||||
$element[0].addEventListener("UmbBlockGrid_RemoveProperty", vm.onRemoveProxyProperty);
|
||||
|
||||
//listen for form validation changes
|
||||
vm.valFormManager.onValidationStatusChanged(function (evt, args) {
|
||||
vm.valFormManager.onValidationStatusChanged(function () {
|
||||
vm.showValidation = vm.valFormManager.showValidation;
|
||||
});
|
||||
//listen for the forms saving event
|
||||
unsubscribe.push($scope.$on("formSubmitting", function (ev, args) {
|
||||
unsubscribe.push($scope.$on("formSubmitting", function () {
|
||||
vm.showValidation = true;
|
||||
}));
|
||||
|
||||
//listen for the forms saved event
|
||||
unsubscribe.push($scope.$on("formSubmitted", function (ev, args) {
|
||||
unsubscribe.push($scope.$on("formSubmitted", function () {
|
||||
vm.showValidation = false;
|
||||
}));
|
||||
|
||||
@@ -266,13 +266,13 @@
|
||||
// Create Model Object, to manage our data for this Block Editor.
|
||||
modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, scopeOfExistence, $scope);
|
||||
|
||||
$q.all([modelObject.load(), assetsService.loadJs('lib/sortablejs/Sortable.min.js', $scope)]).then(onLoaded);
|
||||
modelObject.load().then(onLoaded);
|
||||
|
||||
};
|
||||
|
||||
// Called when we save the value, the server may return an updated data and our value is re-synced
|
||||
// we need to deal with that here so that our model values are all in sync so we basically re-initialize.
|
||||
function onServerValueChanged(newVal, oldVal) {
|
||||
function onServerValueChanged(newVal) {
|
||||
|
||||
// We need to ensure that the property model value is an object, this is needed for modelObject to receive a reference and keep that updated.
|
||||
if (typeof newVal !== 'object' || newVal === null) {// testing if we have null or undefined value or if the value is set to another type than Object.
|
||||
@@ -757,7 +757,7 @@
|
||||
function deleteAllBlocks() {
|
||||
while(vm.layout.length) {
|
||||
deleteBlock(vm.layout[0].$block);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function activateBlock(blockObject) {
|
||||
@@ -777,8 +777,8 @@
|
||||
*/
|
||||
|
||||
var wasNotActiveBefore = blockObject.active !== true;
|
||||
|
||||
// don't open the editor overlay if block has hidden its content editor in overlays and we are requesting to open content, not settings.
|
||||
|
||||
// don't open the editor overlay if block has hidden its content editor in overlays and we are requesting to open content, not settings.
|
||||
if (openSettings !== true && blockObject.hideContentInOverlay === true) {
|
||||
return;
|
||||
}
|
||||
@@ -875,7 +875,7 @@
|
||||
|
||||
}
|
||||
vm.requestShowClipboard = requestShowClipboard;
|
||||
function requestShowClipboard(parentBlock, areaKey, createIndex, mouseEvent) {
|
||||
function requestShowClipboard(parentBlock, areaKey, createIndex) {
|
||||
showCreateDialog(parentBlock, areaKey, createIndex, true);
|
||||
}
|
||||
|
||||
@@ -983,7 +983,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
blockPickerModel.clickClearClipboard = function ($event) {
|
||||
blockPickerModel.clickClearClipboard = function () {
|
||||
clipboardService.clearEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, availableContentTypesAliases);
|
||||
clipboardService.clearEntriesOfType(clipboardService.TYPES.BLOCK, availableContentTypesAliases);
|
||||
};
|
||||
@@ -994,7 +994,7 @@
|
||||
// open block picker overlay
|
||||
editorService.open(blockPickerModel);
|
||||
|
||||
};
|
||||
}
|
||||
function userFlowWhenBlockWasCreated(parentBlock, areaKey, createIndex) {
|
||||
var blockObject;
|
||||
|
||||
@@ -1045,7 +1045,7 @@
|
||||
vm.clipboardItems.push(pasteEntry);
|
||||
});
|
||||
|
||||
var entriesForPaste = clipboardService.retrieveEntriesOfType(clipboardService.TYPES.BLOCK, vm.availableContentTypesAliases);
|
||||
entriesForPaste = clipboardService.retrieveEntriesOfType(clipboardService.TYPES.BLOCK, vm.availableContentTypesAliases);
|
||||
entriesForPaste.forEach(function (entry) {
|
||||
var pasteEntry = {
|
||||
type: clipboardService.TYPES.BLOCK,
|
||||
@@ -1126,7 +1126,7 @@
|
||||
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, contentNodeName]).then(function (localizedLabel) {
|
||||
clipboardService.copyArray(clipboardService.TYPES.BLOCK, aliases, elementTypesToCopy, localizedLabel, contentNodeIcon || "icon-thumbnail-list", vm.model.id);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function gatherNestedBlocks(block) {
|
||||
const nested = [];
|
||||
@@ -1348,14 +1348,14 @@
|
||||
vm.startDraggingMode = startDraggingMode;
|
||||
function startDraggingMode() {
|
||||
|
||||
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 1);
|
||||
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", ' ');
|
||||
firstLayoutContainer.style.minHeight = firstLayoutContainer.getBoundingClientRect().height + "px";
|
||||
|
||||
}
|
||||
vm.exitDraggingMode = exitDraggingMode;
|
||||
function exitDraggingMode() {
|
||||
|
||||
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 0);
|
||||
document.documentElement.style.setProperty("--umb-block-grid--dragging-mode", 'initial');
|
||||
firstLayoutContainer.style.minHeight = "";
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
// Utils:
|
||||
|
||||
function getInterpolatedIndexOfPositionInWeightMap(target, weights) {
|
||||
const map = [0];
|
||||
@@ -9,7 +10,7 @@
|
||||
const foundValue = map.reduce((a, b) => {
|
||||
let aDiff = Math.abs(a - target);
|
||||
let bDiff = Math.abs(b - target);
|
||||
|
||||
|
||||
if (aDiff === bDiff) {
|
||||
return a < b ? a : b;
|
||||
} else {
|
||||
@@ -30,9 +31,12 @@
|
||||
return interpolatedIndex;
|
||||
}
|
||||
|
||||
|
||||
function isWithinRect(x, y, rect, modifier) {
|
||||
return (x > rect.left - modifier && x < rect.right + modifier && y > rect.top - modifier && y < rect.bottom + modifier);
|
||||
function getAccumulatedValueOfIndex(index, weights) {
|
||||
let i = 0, len = Math.min(index, weights.length), calc = 0;
|
||||
while(i<len) {
|
||||
calc += weights[i++];
|
||||
}
|
||||
return calc;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +47,7 @@
|
||||
* @description
|
||||
* renders all blocks for a given list for the block grid editor
|
||||
*/
|
||||
|
||||
|
||||
angular
|
||||
.module("umbraco")
|
||||
.component("umbBlockGridEntries", {
|
||||
@@ -64,40 +68,36 @@
|
||||
}
|
||||
);
|
||||
|
||||
function BlockGridEntriesController($element, $scope, $timeout) {
|
||||
function BlockGridEntriesController($element, $scope) {
|
||||
|
||||
const unsubscribe = [];
|
||||
const vm = this;
|
||||
vm.showNotAllowedUI = false;
|
||||
|
||||
vm.invalidAmount = false;
|
||||
vm.areaConfig = null;
|
||||
vm.locallyAvailableBlockTypes = 0;
|
||||
vm.invalidBlockTypes = [];
|
||||
|
||||
vm.movingLayoutEntry = null;
|
||||
vm.layoutColumnsInt = 0;
|
||||
vm.containedPropertyEditorProxies = [];
|
||||
vm.showNotAllowedUI = false;
|
||||
let currentContainedPropertyEditorProxies = [];
|
||||
|
||||
vm.$onInit = function () {
|
||||
initializeSortable();
|
||||
|
||||
initializeSorter();
|
||||
|
||||
if(vm.parentBlock) {
|
||||
vm.areaConfig = vm.parentBlock.config.areas.find(area => area.key === vm.areaKey);
|
||||
}
|
||||
|
||||
|
||||
vm.locallyAvailableBlockTypes = vm.blockEditorApi.internal.getAllowedTypesOf(vm.parentBlock, vm.areaKey);
|
||||
|
||||
|
||||
unsubscribe.push($scope.$watch('vm.entries', onLocalAmountOfBlocksChanged, true));
|
||||
};
|
||||
|
||||
unsubscribe.push($scope.$watch("layoutColumns", (newVal, oldVal) => {
|
||||
vm.layoutColumnsInt = parseInt(vm.layoutColumns, 10);
|
||||
}));
|
||||
|
||||
function onLocalAmountOfBlocksChanged() {
|
||||
|
||||
if (vm.entriesForm && vm.areaConfig) {
|
||||
|
||||
|
||||
var isMinRequirementGood = vm.entries.length >= vm.areaConfig.minAllowed;
|
||||
vm.entriesForm.areaMinCount.$setValidity("areaMinCount", isMinRequirementGood);
|
||||
|
||||
@@ -126,12 +126,12 @@
|
||||
'maxRequirement': maxAllowed
|
||||
});
|
||||
}
|
||||
} else
|
||||
} else
|
||||
// For specific elementTypes:
|
||||
if(allowance.elementTypeKey) {
|
||||
|
||||
const amount = vm.entries.filter(entry => entry.$block.data.contentTypeKey === allowance.elementTypeKey).length;
|
||||
|
||||
|
||||
if(amount < minAllowed || (maxAllowed > 0 && amount > maxAllowed)) {
|
||||
vm.invalidBlockTypes.push({
|
||||
'key': allowance.elementTypeKey,
|
||||
@@ -153,254 +153,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vm.notifyVisualUpdate = function () {
|
||||
// Used by umb block grid entries component, to trigger other blocks to update.
|
||||
vm.notifyVisualUpdate = notifyVisualUpdate;
|
||||
function notifyVisualUpdate() {
|
||||
$scope.$broadcast("blockGridEditorVisualUpdate", {areaKey: vm.areaKey});
|
||||
}
|
||||
|
||||
vm.acceptBlock = function(contentTypeKey) {
|
||||
return vm.blockEditorApi.internal.isElementTypeKeyAllowedAt(vm.parentBlock, vm.areaKey, contentTypeKey);
|
||||
function removeAllContainedPropertyEditorProxies() {
|
||||
currentContainedPropertyEditorProxies.forEach(slotName => {
|
||||
removePropertyEditorProxies(slotName);
|
||||
});
|
||||
}
|
||||
function removePropertyEditorProxies(slotName) {
|
||||
const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': slotName}});
|
||||
$element[0].dispatchEvent(event);
|
||||
}
|
||||
|
||||
vm.getLayoutEntryByIndex = function(index) {
|
||||
return vm.blockEditorApi.internal.getLayoutEntryByIndex(vm.parentBlock, vm.areaKey, index);
|
||||
}
|
||||
|
||||
vm.showNotAllowed = function() {
|
||||
vm.showNotAllowedUI = true;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
vm.hideNotAllowed = function() {
|
||||
vm.showNotAllowedUI = false;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
|
||||
var revertIndicateDroppableTimeout;
|
||||
vm.revertIndicateDroppable = function() {
|
||||
revertIndicateDroppableTimeout = $timeout(() => {
|
||||
vm.droppableIndication = false;
|
||||
}, 2000);
|
||||
}
|
||||
vm.indicateDroppable = function() {
|
||||
if (revertIndicateDroppableTimeout) {
|
||||
$timeout.cancel(revertIndicateDroppableTimeout);
|
||||
revertIndicateDroppableTimeout = null;
|
||||
}
|
||||
vm.droppableIndication = true;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
|
||||
function initializeSortable() {
|
||||
function resolveVerticalDirection(data) {
|
||||
|
||||
const gridLayoutContainerEl = $element[0].querySelector('.umb-block-grid__layout-container');
|
||||
var _lastIndicationContainerVM = null;
|
||||
/** We need some data about the grid to figure out if there is room to be placed next to the found element */
|
||||
const approvedContainerComputedStyles = getComputedStyle(data.containerElement);
|
||||
const gridColumnGap = Number(approvedContainerComputedStyles.columnGap.split("px")[0]) || 0;
|
||||
const gridColumnNumber = parseInt(approvedContainerComputedStyles.getPropertyValue("--umb-block-grid--grid-columns"), 10);
|
||||
|
||||
var targetRect = null;
|
||||
var relatedEl = null;
|
||||
var ghostEl = null;
|
||||
var ghostRect = null;
|
||||
var dragX = 0;
|
||||
var dragY = 0;
|
||||
var dragOffsetX = 0;
|
||||
const foundElColumns = parseInt(data.relatedElement.dataset.colSpan, 10);
|
||||
const currentElementColumns = data.item.columnSpan;
|
||||
|
||||
var approvedContainerEl = null;
|
||||
|
||||
// Setup DOM method for communication between sortables:
|
||||
gridLayoutContainerEl['Sortable:controller'] = () => {
|
||||
return vm;
|
||||
};
|
||||
|
||||
var nextSibling;
|
||||
|
||||
function _removePropertyProxy(eventTarget, slotName) {
|
||||
const event = new CustomEvent("UmbBlockGrid_RemoveProperty", {composed: true, bubbles: true, detail: {'slotName': slotName}});
|
||||
eventTarget.dispatchEvent(event);
|
||||
if(currentElementColumns >= gridColumnNumber) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
const oldIndex = evt.oldIndex,
|
||||
newIndex = evt.newIndex;
|
||||
|
||||
// If not the same gridLayoutContainerEl, then test for transfer option:
|
||||
if (gridLayoutContainerEl !== evt.from) {
|
||||
const fromCtrl = evt.from['Sortable:controller']();
|
||||
const prevEntries = fromCtrl.entries;
|
||||
const syncEntry = prevEntries[oldIndex];
|
||||
|
||||
// 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]);
|
||||
}
|
||||
else {
|
||||
prevEntries.splice(oldIndex, 1);
|
||||
}
|
||||
vm.entries.splice(newIndex, 0, syncEntry);
|
||||
|
||||
const contextColumns = vm.blockEditorApi.internal.getContextColumns(vm.parentBlock, vm.areaKey);
|
||||
|
||||
// if colSpan is lower than contextColumns, and we do have some columnSpanOptions:
|
||||
if (syncEntry.columnSpan < contextColumns && syncEntry.$block.config.columnSpanOptions.length > 0) {
|
||||
// then check if the colSpan is a columnSpanOption, if NOT then reset to contextColumns.
|
||||
const found = syncEntry.$block.config.columnSpanOptions.find(option => option.columnSpan === syncEntry.columnSpan);
|
||||
if(!found) {
|
||||
syncEntry.columnSpan = contextColumns;
|
||||
}
|
||||
} else {
|
||||
syncEntry.columnSpan = contextColumns;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
vm.entries.splice(newIndex, 0, vm.entries.splice(oldIndex, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function _indication(contextVM, movingEl) {
|
||||
|
||||
// Remove old indication:
|
||||
if(_lastIndicationContainerVM !== contextVM && _lastIndicationContainerVM !== null) {
|
||||
_lastIndicationContainerVM.hideNotAllowed();
|
||||
_lastIndicationContainerVM.revertIndicateDroppable();
|
||||
}
|
||||
_lastIndicationContainerVM = contextVM;
|
||||
|
||||
if(contextVM.acceptBlock(movingEl.dataset.contentElementTypeKey) === true) {
|
||||
_lastIndicationContainerVM.hideNotAllowed();
|
||||
_lastIndicationContainerVM.indicateDroppable();// This block is accepted so we will indicate a good drop.
|
||||
return true;
|
||||
}
|
||||
|
||||
contextVM.showNotAllowed();// This block is not accepted to we will indicate that its not allowed.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _moveGhostElement() {
|
||||
|
||||
rqaId = null;
|
||||
if(!ghostEl) {
|
||||
return;
|
||||
}
|
||||
if(!approvedContainerEl) {
|
||||
console.error("Cancel cause had no approvedContainerEl", approvedContainerEl)
|
||||
return;
|
||||
}
|
||||
|
||||
ghostRect = ghostEl.getBoundingClientRect();
|
||||
|
||||
const insideGhost = isWithinRect(dragX, dragY, ghostRect);
|
||||
if (insideGhost) {
|
||||
return;
|
||||
}
|
||||
|
||||
var approvedContainerRect = approvedContainerEl.getBoundingClientRect();
|
||||
|
||||
const approvedContainerHasItems = approvedContainerEl.querySelector('.umb-block-grid__layout-item:not(.umb-block-grid__layout-item-placeholder)');
|
||||
if(!approvedContainerHasItems && isWithinRect(dragX, dragY, approvedContainerRect, 20) || approvedContainerHasItems && isWithinRect(dragX, dragY, approvedContainerRect, -10)) {
|
||||
// we are good...
|
||||
} else {
|
||||
var parentContainer = approvedContainerEl.parentNode.closest('.umb-block-grid__layout-container');
|
||||
if(parentContainer) {
|
||||
|
||||
if(parentContainer['Sortable:controller']().sortGroupIdentifier === vm.sortGroupIdentifier) {
|
||||
approvedContainerEl = parentContainer;
|
||||
approvedContainerRect = approvedContainerEl.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gather elements on the same row.
|
||||
let elementInSameRow = [];
|
||||
const containerElements = Array.from(approvedContainerEl.children);
|
||||
for (const el of containerElements) {
|
||||
const elRect = el.getBoundingClientRect();
|
||||
// gather elements on the same row.
|
||||
if(dragY >= elRect.top && dragY <= elRect.bottom && el !== ghostEl) {
|
||||
elementInSameRow.push({el: el, rect:elRect});
|
||||
}
|
||||
}
|
||||
|
||||
let lastDistance = 99999;
|
||||
let foundRelatedEl = null;
|
||||
let placeAfter = false;
|
||||
elementInSameRow.forEach( sameRow => {
|
||||
const centerX = (sameRow.rect.left + (sameRow.rect.width*.5));
|
||||
let distance = Math.abs(dragX - centerX);
|
||||
if(distance < lastDistance) {
|
||||
foundRelatedEl = sameRow.el;
|
||||
lastDistance = Math.abs(distance);
|
||||
placeAfter = dragX > centerX;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundRelatedEl === ghostEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundRelatedEl) {
|
||||
|
||||
|
||||
let newIndex = containerElements.indexOf(foundRelatedEl);
|
||||
|
||||
const foundRelatedElRect = foundRelatedEl.getBoundingClientRect();
|
||||
|
||||
// Ghost is already on same line and we are not hovering the related element?
|
||||
const ghostCenterY = ghostRect.top + (ghostRect.height*.5);
|
||||
const isInsideFoundRelated = isWithinRect(dragX, dragY, foundRelatedElRect, 0);
|
||||
|
||||
|
||||
if (isInsideFoundRelated && foundRelatedEl.classList.contains('--has-areas')) {
|
||||
// If mouse is on top of an area, then make that the new approvedContainer?
|
||||
const blockView = foundRelatedEl.querySelector('.umb-block-grid__block--view');
|
||||
const subLayouts = blockView.querySelectorAll('.umb-block-grid__layout-container');
|
||||
for (const subLayout of subLayouts) {
|
||||
const subLayoutRect = subLayout.getBoundingClientRect();
|
||||
const hasItems = subLayout.querySelector('.umb-block-grid__layout-item:not(.umb-block-grid__layout-item-placeholder)');
|
||||
// gather elements on the same row.
|
||||
if(!hasItems && isWithinRect(dragX, dragY, subLayoutRect, 20) || hasItems && isWithinRect(dragX, dragY, subLayoutRect, -10)) {
|
||||
|
||||
var subVm = subLayout['Sortable:controller']();
|
||||
if(subVm.sortGroupIdentifier === vm.sortGroupIdentifier) {
|
||||
approvedContainerEl = subLayout;
|
||||
_moveGhostElement();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ghostCenterY > foundRelatedElRect.top && ghostCenterY < foundRelatedElRect.bottom && !isInsideFoundRelated) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerVM = approvedContainerEl['Sortable:controller']();
|
||||
if(_indication(containerVM, ghostEl) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let verticalDirection = false;
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
const approvedContainerGridColumns = approvedContainerComputedStyles.gridTemplateColumns.trim().split("px").map(x => Number(x)).filter(n => n > 0).map((n, i, list) => list.length === i ? n : n + gridColumnGap);
|
||||
|
||||
// ensure all columns are there.
|
||||
// This will also ensure handling non-css-grid mode,
|
||||
@@ -409,184 +196,127 @@
|
||||
const amountOfUnknownColumns = gridColumnNumber-amountOfColumnsInWeightMap;
|
||||
if(amountOfUnknownColumns > 0) {
|
||||
let accumulatedValue = getAccumulatedValueOfIndex(amountOfColumnsInWeightMap, approvedContainerGridColumns) || 0;
|
||||
const layoutWidth = approvedContainerRect.width;
|
||||
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;
|
||||
const layoutWidth = data.containerRect.width;
|
||||
const missingColumnWidth = (layoutWidth-accumulatedValue)/amountOfUnknownColumns;if(missingColumnWidth > 0) {
|
||||
while(amountOfColumnsInWeightMap++ < gridColumnNumber) {
|
||||
approvedContainerGridColumns.push(missingColumnWidth);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
let offsetPlacement = 0;
|
||||
/* If placeholder is in this same line, we want to assume that it will offset the placement of the found element,
|
||||
which provides more potential space for the item to drop at.
|
||||
This is relevant in this calculation where we look at the space to determine if its a vertical or horizontal drop in relation to the found element.
|
||||
*/
|
||||
if(data.placeholderIsInThisRow && data.elementRect.left < data.relatedRect.left) {
|
||||
offsetPlacement = -(data.elementRect.width + gridColumnGap);
|
||||
}
|
||||
|
||||
const relatedStartX = foundRelatedElRect.left - approvedContainerRect.left;
|
||||
const relatedStartX = Math.max(data.relatedRect.left - data.containerRect.left + offsetPlacement, 0);
|
||||
const relatedStartCol = Math.round(getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns));
|
||||
|
||||
if(relatedStartCol + relatedColumns + ghostColumns > gridColumnNumber) {
|
||||
verticalDirection = true;
|
||||
}
|
||||
|
||||
if (verticalDirection) {
|
||||
placeAfter = (dragY > foundRelatedElRect.top + (foundRelatedElRect.height*.5));
|
||||
}
|
||||
|
||||
// If the found related element does not have enough room after which for the current element, then we go vertical mode:
|
||||
return (relatedStartCol + (data.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber);
|
||||
}
|
||||
|
||||
const nextEl = containerElements[(placeAfter ? newIndex+1 : newIndex)];
|
||||
if (nextEl) {
|
||||
approvedContainerEl.insertBefore(ghostEl, nextEl);
|
||||
} else {
|
||||
approvedContainerEl.appendChild(ghostEl);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If above or below container, we will go first or last.
|
||||
const containerVM = approvedContainerEl['Sortable:controller']();
|
||||
if(_indication(containerVM, ghostEl) === false) {
|
||||
return;
|
||||
}
|
||||
if(dragY < approvedContainerRect.top) {
|
||||
const firstEl = containerElements[0];
|
||||
if (firstEl) {
|
||||
approvedContainerEl.insertBefore(ghostEl, firstEl);
|
||||
} else {
|
||||
approvedContainerEl.appendChild(ghostEl);
|
||||
}
|
||||
} else if(dragY > approvedContainerRect.bottom) {
|
||||
approvedContainerEl.appendChild(ghostEl);
|
||||
}
|
||||
|
||||
function initializeSorter() {
|
||||
vm.sorterOptions = {
|
||||
ownerVM: vm,
|
||||
resolveVerticalDirection: resolveVerticalDirection,
|
||||
dataTransferResolver: (dataTransfer, item) => {dataTransfer.setData("text/plain", item.$block.label)}, // (Optional) Append OS data to the moved item.
|
||||
compareElementToModel: (el, modelEntry) => modelEntry.contentUdi === el.dataset.elementUdi,
|
||||
querySelectModelToElement: (container, modelEntry) => container.querySelector(`[data-element-udi='${modelEntry.contentUdi}']`),
|
||||
itemHasNestedContainersResolver: (foundEl) => foundEl.classList.contains('--has-areas'), // (Optional) improve performance for recognizing if an items has inner containers.
|
||||
identifier: "BlockGridEditor_"+vm.blockEditorApi.internal.uniqueEditorKey,
|
||||
boundarySelector: ".umb-block-grid__area", // (Optional) Used for extended boundary between containers.
|
||||
containerSelector: ".umb-block-grid__layout-container", // Used for connecting with others
|
||||
itemSelector: ".umb-block-grid__layout-item",
|
||||
draggableSelector: ".umb-block-grid__block--view",
|
||||
placeholderClass: "umb-block-grid__layout-item-placeholder",
|
||||
ghostClass: "umb-block-grid__layout-item-ghost",
|
||||
onStart: onSortStart,
|
||||
onEnd: onSortEnd,
|
||||
onContainerChange: onSortContainerChange,
|
||||
onSync: onSortSync,
|
||||
onDisallowed: onSortDisallowed,
|
||||
onAllowed: onSortAllowed,
|
||||
onRequestDrop: onSortRequestDrop
|
||||
}
|
||||
}
|
||||
|
||||
var rqaId = null
|
||||
function _onDragMove(evt) {
|
||||
var currentItemColumnSpanTarget;
|
||||
function onSortStart(data) {
|
||||
currentItemColumnSpanTarget = data.item.columnSpan;
|
||||
|
||||
const clientX = (evt.touches ? evt.touches[0] : evt).clientX;
|
||||
const clientY = (evt.touches ? evt.touches[1] : evt).clientY;
|
||||
if(vm.movingLayoutEntry && targetRect && ghostRect && clientX !== 0 && clientY !== 0) {
|
||||
// Gather containedPropertyEditorProxies from this element.
|
||||
currentContainedPropertyEditorProxies = Array.from(data.element.querySelectorAll('slot[data-is-property-editor-proxy]')).map(x => x.getAttribute('name'));
|
||||
vm.blockEditorApi.internal.startDraggingMode();
|
||||
}
|
||||
|
||||
if(dragX === clientX && dragY === clientY) {
|
||||
return;
|
||||
}
|
||||
dragX = clientX;
|
||||
dragY = clientY;
|
||||
|
||||
ghostRect = ghostEl.getBoundingClientRect();
|
||||
function onSortEnd() {
|
||||
vm.blockEditorApi.internal.exitDraggingMode();
|
||||
currentContainedPropertyEditorProxies = [];
|
||||
notifyVisualUpdate();
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
|
||||
const insideGhost = isWithinRect(dragX, dragY, ghostRect, 0);
|
||||
|
||||
if (!insideGhost) {
|
||||
if(rqaId === null) {
|
||||
rqaId = requestAnimationFrame(_moveGhostElement);
|
||||
}
|
||||
function getColumnSpanForContext(currentColumnSpan, columnSpanOptions, contextColumns) {
|
||||
if (columnSpanOptions.length > 0) {
|
||||
const availableOptions = columnSpanOptions.filter(option => option.columnSpan <= contextColumns);
|
||||
if(availableOptions.length > 0) {
|
||||
const closestColumnSpan = availableOptions.map(x => x.columnSpan).reduce(
|
||||
(prev, curr) => {
|
||||
return Math.abs(curr - currentColumnSpan) < Math.abs(prev - currentColumnSpan) ? curr : prev
|
||||
}, 99999
|
||||
);
|
||||
if(closestColumnSpan) {
|
||||
return closestColumnSpan;
|
||||
}
|
||||
}
|
||||
}
|
||||
return contextColumns;
|
||||
}
|
||||
|
||||
vm.sortGroupIdentifier = "BlockGridEditor_"+vm.blockEditorApi.internal.uniqueEditorKey;
|
||||
function onSortContainerChange(data) {
|
||||
const contextColumns = vm.blockEditorApi.internal.getContextColumns(data.ownerVM.parentBlock, data.ownerVM.areaKey);
|
||||
data.item.columnSpan = getColumnSpanForContext(currentItemColumnSpanTarget, data.item.$block.config.columnSpanOptions, contextColumns);
|
||||
}
|
||||
|
||||
const sortable = Sortable.create(gridLayoutContainerEl, {
|
||||
group: vm.sortGroupIdentifier,
|
||||
sort: true,
|
||||
animation: 0,
|
||||
cancel: '',
|
||||
draggable: ".umb-block-grid__layout-item",
|
||||
ghostClass: "umb-block-grid__layout-item-placeholder",
|
||||
swapThreshold: .4,
|
||||
dragoverBubble: true,
|
||||
emptyInsertThreshold: 40,
|
||||
function onSortSync(data) {
|
||||
if (data.fromController !== data.toController) {
|
||||
removeAllContainedPropertyEditorProxies();
|
||||
}
|
||||
$scope.$evalAsync();
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
}
|
||||
|
||||
scrollSensitivity: 50,
|
||||
scrollSpeed: 16,
|
||||
scroll: true,
|
||||
forceAutoScrollFallback: true,
|
||||
function onSortDisallowed() {
|
||||
vm.showNotAllowedUI = true;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
function onSortAllowed() {
|
||||
vm.showNotAllowedUI = false;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
function onSortRequestDrop(data) {
|
||||
return vm.blockEditorApi.internal.isElementTypeKeyAllowedAt(vm.parentBlock, vm.areaKey, data.item.$block.config.contentElementTypeKey);
|
||||
}
|
||||
|
||||
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;
|
||||
if (gridLayoutContainerEl !== evt.to) {
|
||||
contextVM = evt.to['Sortable:controller']();
|
||||
}
|
||||
|
||||
approvedContainerEl = evt.to;
|
||||
|
||||
const oldIndex = evt.oldIndex;
|
||||
vm.movingLayoutEntry = contextVM.getLayoutEntryByIndex(oldIndex);
|
||||
|
||||
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();
|
||||
|
||||
const clientX = (evt.originalEvent.touches ? evt.originalEvent.touches[0] : evt.originalEvent).clientX;
|
||||
dragOffsetX = clientX - ghostRect.left;
|
||||
|
||||
window.addEventListener('drag', _onDragMove);
|
||||
window.addEventListener('dragover', _onDragMove);
|
||||
|
||||
$scope.$evalAsync();
|
||||
},
|
||||
// Called by any change to the list (add / update / remove)
|
||||
onMove: function (evt) {
|
||||
relatedEl = evt.related;
|
||||
targetRect = evt.to.getBoundingClientRect();
|
||||
ghostRect = evt.draggedRect;
|
||||
|
||||
// Disable SortableJS from handling the drop, instead we will use our own.
|
||||
return false;
|
||||
},
|
||||
// When an change actually was made, after drop has occurred:
|
||||
onSort: function (evt) {
|
||||
vm.blockEditorApi.internal.setDirty();
|
||||
},
|
||||
|
||||
onAdd: function (evt) {
|
||||
_sync(evt);
|
||||
$scope.$evalAsync();
|
||||
},
|
||||
onUpdate: function (evt) {
|
||||
_sync(evt);
|
||||
$scope.$evalAsync();
|
||||
},
|
||||
onEnd: function(evt) {
|
||||
if(rqaId !== null) {
|
||||
cancelAnimationFrame(rqaId);
|
||||
}
|
||||
window.removeEventListener('drag', _onDragMove);
|
||||
window.removeEventListener('dragover', _onDragMove);
|
||||
vm.blockEditorApi.internal.exitDraggingMode();
|
||||
|
||||
// ensure not-allowed indication is removed.
|
||||
if(_lastIndicationContainerVM) {
|
||||
_lastIndicationContainerVM.hideNotAllowed();
|
||||
_lastIndicationContainerVM.revertIndicateDroppable();
|
||||
_lastIndicationContainerVM = null;
|
||||
}
|
||||
|
||||
approvedContainerEl = null;
|
||||
vm.movingLayoutEntry = null;
|
||||
targetRect = null;
|
||||
ghostRect = null;
|
||||
ghostEl = null;
|
||||
relatedEl = null;
|
||||
vm.containedPropertyEditorProxies = [];
|
||||
|
||||
vm.notifyVisualUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
sortable.destroy();
|
||||
for (const subscription of unsubscribe) {
|
||||
subscription();
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
$scope.$on('$destroy', function () {
|
||||
for (const subscription of unsubscribe) {
|
||||
subscription();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
unsubscribe.push($scope.$watch("depth", (newVal, oldVal) => {
|
||||
unsubscribe.push($scope.$watch("depth", () => {
|
||||
vm.childDepth = parseInt(vm.depth) + 1;
|
||||
}));
|
||||
|
||||
|
||||
@@ -0,0 +1,717 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function isWithinRect(x, y, rect, modifier) {
|
||||
return (x > rect.left - modifier && x < rect.right + modifier && y > rect.top - modifier && y < rect.bottom + modifier);
|
||||
}
|
||||
|
||||
|
||||
function getParentScrollElement(el, includeSelf) {
|
||||
// skip to window
|
||||
if (!el || !el.getBoundingClientRect) return null;
|
||||
var elem = el;
|
||||
var gotSelf = false;
|
||||
|
||||
while(elem) {
|
||||
|
||||
// we don't need to get elem css if it isn't even overflowing in the first place (performance)
|
||||
if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) {
|
||||
var elemCSS = getComputedStyle(elem);
|
||||
|
||||
if (
|
||||
elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')
|
||||
||
|
||||
elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll')
|
||||
) {
|
||||
if (!elem.getBoundingClientRect || elem === document.body) return null;
|
||||
if (gotSelf || includeSelf) return elem;
|
||||
gotSelf = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(elem.parentNode === document) {
|
||||
return null;
|
||||
} else if(elem.parentNode instanceof DocumentFragment) {
|
||||
elem = elem.parentNode.host;
|
||||
} else {
|
||||
elem = elem.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const DefaultConfig = {
|
||||
compareElementToModel: (el, modelEntry) => modelEntry.contentUdi === el.dataset.elementUdi,
|
||||
querySelectModelToElement: (container, modelEntry) => container.querySelector(`[data-element-udi='${modelEntry.contentUdi}']`),
|
||||
identifier: "UmbBlockGridSorter",
|
||||
containerSelector: "ol", // To find container and to connect with others.
|
||||
ignorerSelector: "a, img, iframe",
|
||||
itemSelector: "li",
|
||||
placeholderClass: "umb-drag-placeholder"
|
||||
}
|
||||
|
||||
|
||||
|
||||
function UmbBlockGridSorter() {
|
||||
|
||||
function link(scope, element) {
|
||||
|
||||
|
||||
let observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(addedNode) {
|
||||
if (addedNode.matches && addedNode.matches(scope.config.itemSelector)) {
|
||||
setupItem(addedNode);
|
||||
}
|
||||
});
|
||||
mutation.removedNodes.forEach(function(removedNode) {
|
||||
if (removedNode.matches && removedNode.matches(scope.config.itemSelector)) {
|
||||
destroyItem(removedNode);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let vm = {};
|
||||
|
||||
const config = {...DefaultConfig, ...scope.config};
|
||||
|
||||
vm.identifier = config.identifier;
|
||||
vm.ownerVM = config.ownerVM || null;
|
||||
|
||||
let scrollElement = null;
|
||||
|
||||
let containerEl = config.containerSelector ? element[0].closest(config.containerSelector) : element[0];
|
||||
if (!containerEl) {
|
||||
console.error("Could not initialize umb block grid sorter.", element[0])
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
containerEl['umbBlockGridSorter:vm'] = () => {
|
||||
return vm;
|
||||
};
|
||||
containerEl.addEventListener('dragover', preventDragOver);
|
||||
|
||||
observer.observe(containerEl, {childList: true, subtree: false});
|
||||
}
|
||||
init();
|
||||
|
||||
function preventDragOver(e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function setupItem(element) {
|
||||
|
||||
setupIgnorerElements(element);
|
||||
|
||||
element.draggable = true;
|
||||
element.addEventListener('dragstart', handleDragStart);
|
||||
}
|
||||
|
||||
function destroyItem(element) {
|
||||
|
||||
destroyIgnorerElements(element);
|
||||
|
||||
element.removeEventListener('dragstart', handleDragStart);
|
||||
}
|
||||
|
||||
function setupIgnorerElements(element) {
|
||||
config.ignorerSelector.split(',').forEach(function (criteria) {
|
||||
element.querySelectorAll(criteria.trim()).forEach(setupPreventEvent);
|
||||
});
|
||||
}
|
||||
function destroyIgnorerElements(element) {
|
||||
config.ignorerSelector.split(',').forEach(function (criteria) {
|
||||
element.querySelectorAll(criteria.trim()).forEach(destroyPreventEvent);
|
||||
});
|
||||
}
|
||||
function setupPreventEvent(element) {
|
||||
element.draggable = false
|
||||
}
|
||||
function destroyPreventEvent(element) {
|
||||
element.removeAttribute('draggable');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let currentContainerElement = containerEl;
|
||||
let currentContainerVM = vm;
|
||||
|
||||
let rqaId = null;
|
||||
let currentItem = null;
|
||||
let currentElement = null;
|
||||
let currentDragElement = null;
|
||||
let currentDragRect = null;
|
||||
let dragX = 0;
|
||||
let dragY = 0;
|
||||
|
||||
function handleDragStart(event) {
|
||||
|
||||
if(currentElement) {
|
||||
handleDragEnd();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.dataTransfer.effectAllowed = "move";// copyMove when we enhance the drag with clipboard data.
|
||||
event.dataTransfer.dropEffect = "none";// visual feedback when dropped.
|
||||
|
||||
if(!scrollElement) {
|
||||
scrollElement = getParentScrollElement(containerEl, true);
|
||||
}
|
||||
|
||||
const element = event.target.closest(config.itemSelector);
|
||||
|
||||
currentElement = element;
|
||||
currentDragElement = config.draggableSelector ? currentElement.querySelector(config.draggableSelector) : currentElement;
|
||||
currentDragRect = currentDragElement.getBoundingClientRect();
|
||||
currentItem = vm.getItemOfElement(currentElement);
|
||||
if(!currentItem) {
|
||||
console.error("Could not find item related to this element.");
|
||||
return;
|
||||
}
|
||||
|
||||
currentElement.style.transform = 'translateZ(0)';// Solves problem with FireFox and ShadowDom in the drag-image.
|
||||
|
||||
if (config.dataTransferResolver) {
|
||||
config.dataTransferResolver(event.dataTransfer, currentItem);
|
||||
}
|
||||
|
||||
if (config.onStart) {
|
||||
config.onStart({item: currentItem, element: currentElement});
|
||||
}
|
||||
|
||||
window.addEventListener('dragover', handleDragMove);
|
||||
window.addEventListener('dragend', handleDragEnd);
|
||||
|
||||
// We must wait one frame before changing the look of the block.
|
||||
rqaId = requestAnimationFrame(() => {// It should be okay to use the same refId, as the move does not or is okay not to happen on first frame/drag-move.
|
||||
rqaId = null;
|
||||
currentElement.style.transform = '';
|
||||
currentElement.classList.add(config.placeholderClass);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
|
||||
if(!currentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.removeEventListener('dragover', handleDragMove);
|
||||
window.removeEventListener('dragend', handleDragEnd);
|
||||
currentElement.style.transform = '';
|
||||
currentElement.classList.remove(config.placeholderClass);
|
||||
|
||||
stopAutoScroll();
|
||||
removeAllowIndication();
|
||||
|
||||
if(currentContainerVM.sync(currentElement, vm) === false) {
|
||||
// Sync could not succeed, might be because item is not allowed here.
|
||||
|
||||
currentContainerVM = vm;
|
||||
if (config.onContainerChange) {
|
||||
config.onContainerChange({item: currentItem, element: currentElement, ownerVM: currentContainerVM.ownerVM});
|
||||
}
|
||||
|
||||
// Lets move the Element back to where it came from:
|
||||
const movingItemIndex = scope.model.indexOf(currentItem);
|
||||
if(movingItemIndex < scope.model.length-1) {
|
||||
const afterItem = scope.model[movingItemIndex+1];
|
||||
const afterEl = config.querySelectModelToElement(containerEl, afterItem);
|
||||
containerEl.insertBefore(currentElement, afterEl);
|
||||
} else {
|
||||
containerEl.appendChild(currentElement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (config.onEnd) {
|
||||
config.onEnd({item: currentItem, element: currentElement});
|
||||
}
|
||||
|
||||
if(rqaId) {
|
||||
cancelAnimationFrame(rqaId);
|
||||
}
|
||||
|
||||
currentContainerElement = containerEl;
|
||||
currentContainerVM = vm;
|
||||
|
||||
rqaId = null
|
||||
currentItem = null;
|
||||
currentElement = null;
|
||||
currentDragElement = null;
|
||||
currentDragRect = null;
|
||||
dragX = 0;
|
||||
dragY = 0;
|
||||
|
||||
}
|
||||
|
||||
function handleDragMove(event) {
|
||||
|
||||
if(!currentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clientX = (event.touches ? event.touches[0] : event).clientX;
|
||||
const clientY = (event.touches ? event.touches[1] : event).clientY;
|
||||
if(clientX !== 0 && clientY !== 0) {
|
||||
|
||||
|
||||
if(dragX === clientX && dragY === clientY) {
|
||||
return;
|
||||
}
|
||||
dragX = clientX;
|
||||
dragY = clientY;
|
||||
|
||||
handleAutoScroll(dragX, dragY);
|
||||
|
||||
currentDragRect = currentDragElement.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(dragX, dragY, currentDragRect, 0);
|
||||
if (!insideCurrentRect) {
|
||||
if(rqaId === null) {
|
||||
rqaId = requestAnimationFrame(moveCurrentElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function moveCurrentElement() {
|
||||
|
||||
rqaId = null;
|
||||
if(!currentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentElementRect = currentElement.getBoundingClientRect();
|
||||
const insideCurrentRect = isWithinRect(dragX, dragY, currentElementRect);
|
||||
if (insideCurrentRect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have a boundarySelector, try it, if we didn't get anything fall back to currentContainerElement.
|
||||
var currentBoundaryElement = (config.boundarySelector ? currentContainerElement.closest(config.boundarySelector) : currentContainerElement) || currentContainerElement;
|
||||
|
||||
var currentBoundaryRect = currentBoundaryElement.getBoundingClientRect();
|
||||
|
||||
const currentContainerHasItems = currentContainerVM.hasOtherItemsThan(currentItem);
|
||||
|
||||
// if empty we will be move likely to accept an item (add 20px to the bounding box)
|
||||
// If we have items we must be 10 within the container to accept the move.
|
||||
const offsetEdge = currentContainerHasItems ? -10 : 20;
|
||||
if(!isWithinRect(dragX, dragY, currentBoundaryRect, offsetEdge)) {
|
||||
// we are outside the current container boundary, so lets see if there is a parent we can move.
|
||||
var parentContainer = currentContainerElement.parentNode.closest(config.containerSelector);
|
||||
if(parentContainer) {
|
||||
const parentContainerVM = parentContainer['umbBlockGridSorter:vm']();
|
||||
if(parentContainerVM.identifier === vm.identifier) {
|
||||
currentContainerElement = parentContainer;
|
||||
currentContainerVM = parentContainerVM;
|
||||
if (config.onContainerChange) {
|
||||
config.onContainerChange({item: currentItem, element: currentElement, ownerVM: currentContainerVM.ownerVM});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// We want to retrieve the children of the container, every time to ensure we got the right order and index
|
||||
const orderedContainerElements = Array.from(currentContainerElement.children);
|
||||
|
||||
var currentContainerRect = currentContainerElement.getBoundingClientRect();
|
||||
|
||||
|
||||
// gather elements on the same row.
|
||||
let elementsInSameRow = [];
|
||||
let placeholderIsInThisRow = false;
|
||||
for (const el of orderedContainerElements) {
|
||||
const elRect = el.getBoundingClientRect();
|
||||
// gather elements on the same row.
|
||||
if(dragY >= elRect.top && dragY <= elRect.bottom) {
|
||||
|
||||
const dragElement = config.draggableSelector ? el.querySelector(config.draggableSelector) : el;
|
||||
const dragElementRect = dragElement.getBoundingClientRect();
|
||||
if(el !== currentElement) {
|
||||
elementsInSameRow.push({el:el, dragRect:dragElementRect});
|
||||
} else {
|
||||
placeholderIsInThisRow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastDistance = 99999;
|
||||
let foundEl = null;
|
||||
let foundElDragRect = null;
|
||||
let placeAfter = false;
|
||||
elementsInSameRow.forEach( sameRow => {
|
||||
const centerX = (sameRow.dragRect.left + (sameRow.dragRect.width*.5));
|
||||
let distance = Math.abs(dragX - centerX);
|
||||
if(distance < lastDistance) {
|
||||
foundEl = sameRow.el;
|
||||
foundElDragRect = sameRow.dragRect;
|
||||
lastDistance = Math.abs(distance);
|
||||
placeAfter = dragX > centerX;
|
||||
}
|
||||
});
|
||||
|
||||
// If we are on top or closest to our self, we should not do anything.
|
||||
if (foundEl === currentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundEl) {
|
||||
|
||||
const isInsideFound = isWithinRect(dragX, dragY, foundElDragRect, 0);
|
||||
|
||||
// If we are inside the found element, lets look for sub containers.
|
||||
// use the itemHasNestedContainersResolver, if not configured fallback to looking for the existence of a container via DOM.
|
||||
if (isInsideFound &&
|
||||
config.itemHasNestedContainersResolver ? config.itemHasNestedContainersResolver(foundEl) : foundEl.querySelector(config.containerSelector)) {
|
||||
|
||||
// Find all sub containers:
|
||||
const subLayouts = foundEl.querySelectorAll(config.containerSelector);
|
||||
for (const subLayoutEl of subLayouts) {
|
||||
|
||||
// Use boundary element or fallback to container element.
|
||||
var subBoundaryElement = (config.boundarySelector ? subLayoutEl.closest(config.boundarySelector) : subLayoutEl) || subLayoutEl;
|
||||
var subBoundaryRect = subBoundaryElement.getBoundingClientRect();
|
||||
|
||||
const subContainerHasItems = subLayoutEl.querySelector(config.itemSelector+':not(.'+config.placeholderClass+')');
|
||||
// gather elements on the same row.
|
||||
const subOffsetEdge = subContainerHasItems ? -10 : 20;
|
||||
if(isWithinRect(dragX, dragY, subBoundaryRect, subOffsetEdge)) {
|
||||
|
||||
var subVm = subLayoutEl['umbBlockGridSorter:vm']();
|
||||
if(subVm.identifier === vm.identifier) {
|
||||
currentContainerElement = subLayoutEl;
|
||||
currentContainerVM = subVm;
|
||||
if (config.onContainerChange) {
|
||||
config.onContainerChange({item: currentItem, element: currentElement, ownerVM: currentContainerVM.ownerVM});
|
||||
}
|
||||
moveCurrentElement();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Indication if drop is good:
|
||||
if(updateAllowIndication(currentContainerVM, currentItem) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let verticalDirection = scope.config.resolveVerticalDirection ? scope.config.resolveVerticalDirection({
|
||||
containerElement: currentContainerElement,
|
||||
containerRect: currentContainerRect,
|
||||
item: currentItem,
|
||||
element: currentElement,
|
||||
elementRect: currentElementRect,
|
||||
relatedElement: foundEl,
|
||||
relatedRect: foundElDragRect,
|
||||
placeholderIsInThisRow: placeholderIsInThisRow,
|
||||
horizontalPlaceAfter: placeAfter
|
||||
}) : true;
|
||||
|
||||
if (verticalDirection) {
|
||||
placeAfter = (dragY > foundElDragRect.top + (foundElDragRect.height*.5));
|
||||
}
|
||||
|
||||
if(verticalDirection) {
|
||||
|
||||
let el;
|
||||
if(placeAfter === false) {
|
||||
let lastLeft = foundElDragRect.left;
|
||||
elementsInSameRow.findIndex((x) => {
|
||||
if(x.dragRect.left < lastLeft) {
|
||||
lastLeft = x.dragRect.left;
|
||||
el = x.el;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let lastRight = foundElDragRect.right;
|
||||
elementsInSameRow.findIndex((x) => {
|
||||
if(x.dragRect.right > lastRight) {
|
||||
lastRight = x.dragRect.right;
|
||||
el = x.el;
|
||||
}
|
||||
});
|
||||
}
|
||||
if(el) {
|
||||
foundEl = el;
|
||||
}
|
||||
}
|
||||
|
||||
const foundElIndex = orderedContainerElements.indexOf(foundEl);
|
||||
const placeAt = (placeAfter ? foundElIndex+1 : foundElIndex);
|
||||
|
||||
move(orderedContainerElements, placeAt);
|
||||
|
||||
return;
|
||||
}
|
||||
// We skipped the above part cause we are above or below container:
|
||||
|
||||
// Indication if drop is good:
|
||||
if(updateAllowIndication(currentContainerVM, currentItem) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(dragY < currentContainerRect.top) {
|
||||
move(orderedContainerElements, 0);
|
||||
} else if(dragY > currentContainerRect.bottom) {
|
||||
move(orderedContainerElements, -1);
|
||||
}
|
||||
}
|
||||
|
||||
function move(orderedContainerElements, newElIndex) {
|
||||
|
||||
newElIndex = newElIndex === -1 ? orderedContainerElements.length : newElIndex;
|
||||
|
||||
const placeBeforeElement = orderedContainerElements[newElIndex];
|
||||
if (placeBeforeElement) {
|
||||
// We do not need to move this, if the element to be placed before is it self.
|
||||
if(placeBeforeElement !== currentElement) {
|
||||
currentContainerElement.insertBefore(currentElement, placeBeforeElement);
|
||||
}
|
||||
} else {
|
||||
currentContainerElement.appendChild(currentElement);
|
||||
}
|
||||
|
||||
if(config.onChange) {
|
||||
config.onChange({element: currentElement, item: currentItem, ownerVM: currentContainerVM.ownerVM});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Removes an element from container and returns its items-data entry */
|
||||
vm.getItemOfElement = function (element) {
|
||||
if(!element) {
|
||||
return null;
|
||||
}
|
||||
return scope.model.find(entry => config.compareElementToModel(element, entry));
|
||||
}
|
||||
vm.removeItem = function (item) {
|
||||
if(!item) {
|
||||
return null;
|
||||
}
|
||||
const oldIndex = scope.model.indexOf(item);
|
||||
if(oldIndex !== -1) {
|
||||
return scope.model.splice(oldIndex, 1)[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
vm.hasOtherItemsThan = function(item) {
|
||||
return scope.model.filter(x => x !== item).length > 0;
|
||||
}
|
||||
|
||||
vm.sync = function(element, fromVm) {
|
||||
|
||||
const movingItem = fromVm.getItemOfElement(element);
|
||||
if(!movingItem) {
|
||||
console.error("Could not find item of sync item")
|
||||
return false;
|
||||
}
|
||||
if(vm.notifyRequestDrop({item: movingItem}) === false) {
|
||||
return false;
|
||||
}
|
||||
if(fromVm.removeItem(movingItem) === null) {
|
||||
console.error("Sync could not remove item")
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Find next element, to then find the index of that element in items-data, to use as a safe reference to where the item will go in our items-data.
|
||||
* This enables the container to contain various other elements and as well having these elements change while sorting is occurring.
|
||||
*/
|
||||
|
||||
// find next valid element (This assumes the next element in DOM is presented in items-data, aka. only moving one item between each sync)
|
||||
let nextEl;
|
||||
let loopEl = element;
|
||||
while((loopEl = loopEl.nextElementSibling)) {
|
||||
if(loopEl.matches && loopEl.matches(config.itemSelector)) {
|
||||
nextEl = loopEl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let newIndex = scope.model.length;
|
||||
if(nextEl) {
|
||||
// We had a reference element, we want to get the index of it.
|
||||
// This is problem if a item is being moved forward?
|
||||
newIndex = scope.model.findIndex(entry => config.compareElementToModel(nextEl, entry));
|
||||
}
|
||||
|
||||
scope.model.splice(newIndex, 0, movingItem);
|
||||
|
||||
const eventData = {item: movingItem, fromController:fromVm, toController:vm};
|
||||
if(fromVm !== vm) {
|
||||
fromVm.notifySync(eventData);
|
||||
}
|
||||
vm.notifySync(eventData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var _lastIndicationContainerVM = null;
|
||||
function updateAllowIndication(contextVM, item) {
|
||||
|
||||
// Remove old indication:
|
||||
if(_lastIndicationContainerVM !== null && _lastIndicationContainerVM !== contextVM) {
|
||||
_lastIndicationContainerVM.notifyAllowed();
|
||||
}
|
||||
_lastIndicationContainerVM = contextVM;
|
||||
|
||||
if(contextVM.notifyRequestDrop({item: item}) === true) {
|
||||
contextVM.notifyAllowed();
|
||||
return true;
|
||||
}
|
||||
|
||||
contextVM.notifyDisallowed();// This block is not accepted to we will indicate that its not allowed.
|
||||
return false;
|
||||
}
|
||||
function removeAllowIndication() {
|
||||
|
||||
// Remove old indication:
|
||||
if(_lastIndicationContainerVM !== null) {
|
||||
_lastIndicationContainerVM.notifyAllowed();
|
||||
}
|
||||
_lastIndicationContainerVM = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let autoScrollRAF;
|
||||
let autoScrollEl;
|
||||
const autoScrollSensitivity = 50;
|
||||
const autoScrollSpeed = 16;
|
||||
let autoScrollX = 0;
|
||||
let autoScrollY = 0;
|
||||
|
||||
function handleAutoScroll(clientX, clientY) {
|
||||
|
||||
let scrollRect = null;
|
||||
if (scrollElement) {
|
||||
autoScrollEl = scrollElement;
|
||||
scrollRect = scrollElement.getBoundingClientRect();
|
||||
} else {
|
||||
autoScrollEl = document.scrollingElement || document.documentElement;
|
||||
scrollRect = {top: 0,
|
||||
left: 0,
|
||||
bottom: window.innerHeight,
|
||||
right: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
width: window.innerWidth
|
||||
}
|
||||
}
|
||||
|
||||
const scrollWidth = autoScrollEl.scrollWidth;
|
||||
const scrollHeight = autoScrollEl.scrollHeight;
|
||||
const canScrollX = scrollRect.width < scrollWidth;
|
||||
const canScrollY = scrollRect.height < scrollHeight;
|
||||
const scrollPosX = autoScrollEl.scrollLeft;
|
||||
const scrollPosY = autoScrollEl.scrollTop;
|
||||
|
||||
cancelAnimationFrame(autoScrollRAF);
|
||||
|
||||
if(canScrollX || canScrollY) {
|
||||
|
||||
autoScrollX = (Math.abs(scrollRect.right - clientX) <= autoScrollSensitivity && scrollPosX + scrollRect.width < scrollWidth) - (Math.abs(scrollRect.left - clientX) <= autoScrollSensitivity && !!scrollPosX);
|
||||
autoScrollY = (Math.abs(scrollRect.bottom - clientY) <= autoScrollSensitivity && scrollPosY + scrollRect.height < scrollHeight) - (Math.abs(scrollRect.top - clientY) <= autoScrollSensitivity && !!scrollPosY);
|
||||
autoScrollRAF = requestAnimationFrame(performAutoScroll);
|
||||
}
|
||||
}
|
||||
function performAutoScroll() {
|
||||
autoScrollEl.scrollLeft += autoScrollX * autoScrollSpeed;
|
||||
autoScrollEl.scrollTop += autoScrollY * autoScrollSpeed;
|
||||
autoScrollRAF = requestAnimationFrame(performAutoScroll);
|
||||
}
|
||||
function stopAutoScroll() {
|
||||
cancelAnimationFrame(autoScrollRAF);
|
||||
autoScrollRAF = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
vm.notifySync = function(data) {
|
||||
if(config.onSync) {
|
||||
config.onSync(data);
|
||||
}
|
||||
}
|
||||
vm.notifyDisallowed = function() {
|
||||
if(config.onDisallowed) {
|
||||
config.onDisallowed();
|
||||
}
|
||||
}
|
||||
vm.notifyAllowed = function() {
|
||||
if(config.onAllowed) {
|
||||
config.onAllowed();
|
||||
}
|
||||
}
|
||||
vm.notifyRequestDrop = function(data) {
|
||||
if(config.onRequestDrop) {
|
||||
return config.onRequestDrop(data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
scope.$on('$destroy', () => {
|
||||
|
||||
if(currentElement) {
|
||||
handleDragEnd()
|
||||
}
|
||||
|
||||
_lastIndicationContainerVM = null;
|
||||
|
||||
containerEl['umbBlockGridSorter:vm'] = null
|
||||
containerEl.removeEventListener('dragover', preventDragOver);
|
||||
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
containerEl = null;
|
||||
scrollElement = null;
|
||||
vm = null;
|
||||
});
|
||||
}
|
||||
|
||||
var directive = {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
config: '=umbBlockGridSorter',
|
||||
model: '=umbBlockGridSorterModel'
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbBlockGridSorter', UmbBlockGridSorter);
|
||||
|
||||
})();
|
||||
@@ -1,14 +1,15 @@
|
||||
/** Example of how a grid layout stylehseet could be done with Flex box: */
|
||||
|
||||
|
||||
.umb-block-grid__layout-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--umb-block-grid--row-gap, 0) var(--umb-block-grid--column-gap, 0);
|
||||
}
|
||||
.umb-block-grid__layout-item {
|
||||
position: relative;
|
||||
--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%);
|
||||
width: calc(var(--umb-block-grid__layout-item-calc) * 100% - (1 - var(--umb-block-grid__layout-item-calc)) * var(--umb-block-grid--column-gap, 0px));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +18,15 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: var(--umb-block-grid--areas-row-gap, 0) var(--umb-block-grid--areas-column-gap, 0);
|
||||
}
|
||||
.umb-block-grid__area {
|
||||
position: relative;
|
||||
--umb-block-grid__area-calc: calc(var(--umb-block-grid--area-column-span) / var(--umb-block-grid--area-grid-columns, 1));
|
||||
width: calc(var(--umb-block-grid__area-calc) * 100%);
|
||||
width: calc(var(--umb-block-grid__area-calc) * 100% - (1 - var(--umb-block-grid__area-calc)) * var(--umb-block-grid--areas-column-gap, 0px));
|
||||
}
|
||||
|
||||
|
||||
.umb-block-grid__actions {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
.umb-block-grid__area {
|
||||
position: relative;
|
||||
/* For small devices we scale columnSpan by three, to make everything bigger than 1/3 take full width: */
|
||||
grid-column-end: span min(calc(var(--umb-block-grid--item-column-span, 1) * 3), var(--umb-block-grid--grid-columns));
|
||||
grid-column-end: span min(calc(var(--umb-block-grid--area-column-span, 1) * 3), var(--umb-block-grid--grid-columns));
|
||||
grid-row: span var(--umb-block-grid--area-row-span, 1);
|
||||
}
|
||||
|
||||
@media (min-width:1024px) {
|
||||
.umb-block-grid__layout-item {
|
||||
grid-column-end: span var(--umb-block-grid--item-column-span, 1);
|
||||
grid-column-end: span min(var(--umb-block-grid--item-column-span, 1), var(--umb-block-grid--grid-columns));
|
||||
}
|
||||
.umb-block-grid__area {
|
||||
grid-column-end: span var(--umb-block-grid--area-column-span, 1);
|
||||
|
||||
@@ -29,7 +29,6 @@ module.exports = function (config) {
|
||||
'lib/umbraco/Extensions.js',
|
||||
'node_modules/lazyload-js/LazyLoad.min.js',
|
||||
'node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js',
|
||||
'node_modules/sortablejs/Sortable.min.js',
|
||||
|
||||
//app bootstrap and loader
|
||||
'test/config/app.unit.js',
|
||||
|
||||
Reference in New Issue
Block a user