Skip to content

Commit 6c1c851

Browse files
Fix: Improve sorter placement algorithm (#18021)
* improve sorting algorithm * fix block type input * make confirm modal localizable * rename method * clean up * clean up * improve code * Fix creating Block Types in Groups * remove #moveData * lint fixes * remove unused --------- Co-authored-by: Mads Rasmussen <[email protected]>
1 parent 4353027 commit 6c1c851

File tree

11 files changed

+332
-197
lines changed

11 files changed

+332
-197
lines changed

src/Umbraco.Web.UI.Client/src/assets/lang/en.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2484,8 +2484,8 @@ export default {
24842484
confirmDeleteBlockTypeMessage: 'Are you sure you want to delete the block configuration <strong>%0%</strong>?',
24852485
confirmDeleteBlockTypeNotice:
24862486
'The content of this block will still be present, editing of this content\n will no longer be available and will be shown as unsupported content.\n ',
2487-
confirmDeleteBlockGroupMessage:
2488-
'Are you sure you want to delete group <strong>%0%</strong> and all the Block configurations of this?',
2487+
confirmDeleteBlockGroupTitle: 'Delete group?',
2488+
confirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',
24892489
confirmDeleteBlockGroupNotice:
24902490
'The content of these Blocks will still be present, editing of this content\n will no longer be available and will be shown as unsupported content.\n ',
24912491
blockConfigurationOverlayTitle: "Configuration of '%0%'",

src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
1010
import { html, customElement, state, repeat, css, property, nothing } from '@umbraco-cms/backoffice/external/lit';
1111
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
1212
import '../block-grid-entry/index.js';
13-
import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs } from '@umbraco-cms/backoffice/sorter';
13+
import {
14+
UmbSorterController,
15+
type UmbSorterConfig,
16+
type UmbSorterResolvePlacementArgs,
17+
} from '@umbraco-cms/backoffice/sorter';
1418
import {
1519
UmbFormControlMixin,
1620
UmbFormControlValidator,
@@ -23,7 +27,9 @@ import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models';
2327
* @param args
2428
* @returns { null | true }
2529
*/
26-
function resolvePlacementAsGrid(args: resolvePlacementArgs<UmbBlockGridLayoutModel, UmbBlockGridEntryElement>) {
30+
function resolvePlacementAsBlockGrid(
31+
args: UmbSorterResolvePlacementArgs<UmbBlockGridLayoutModel, UmbBlockGridEntryElement>,
32+
) {
2733
// If this has areas, we do not want to move, unless we are at the edge
2834
if (args.relatedModel.areas?.length > 0 && isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) {
2935
return null;
@@ -80,9 +86,16 @@ function resolvePlacementAsGrid(args: resolvePlacementArgs<UmbBlockGridLayoutMod
8086
const relatedStartCol = Math.round(
8187
getInterpolatedIndexOfPositionInWeightMap(relatedStartX, approvedContainerGridColumns),
8288
);
83-
8489
// If the found related element does not have enough room after which for the current element, then we go vertical mode:
85-
return relatedStartCol + (args.horizontalPlaceAfter ? foundElColumns : 0) + currentElementColumns > gridColumnNumber;
90+
const verticalDirection = relatedStartCol + foundElColumns + currentElementColumns > gridColumnNumber;
91+
return verticalDirection;
92+
/*
93+
let placeAfter = args.horizontalPlaceAfter;
94+
95+
return {
96+
verticalDirection,
97+
placeAfter,
98+
};*/
8699
}
87100

88101
// --------------------------
@@ -96,7 +109,7 @@ const SORTER_CONFIG: UmbSorterConfig<UmbBlockGridLayoutModel, UmbBlockGridEntryE
96109
getUniqueOfModel: (modelEntry) => {
97110
return modelEntry.contentKey;
98111
},
99-
resolvePlacement: resolvePlacementAsGrid,
112+
resolvePlacement: resolvePlacementAsBlockGrid,
100113
identifier: 'block-grid-editor',
101114
itemSelector: 'umb-block-grid-entry',
102115
containerSelector: '.umb-block-grid__layout-container',

src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts

Lines changed: 57 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '../../../block-type/index.js';
2-
import '../../../block-type/components/input-block-type/index.js';
3-
import {
4-
type UmbPropertyEditorUiElement,
5-
UmbPropertyValueChangeEvent,
6-
type UmbPropertyEditorConfigCollection,
1+
import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
2+
import type {
3+
UmbPropertyEditorUiElement,
4+
UmbPropertyEditorConfigCollection,
75
} from '@umbraco-cms/backoffice/property-editor';
86
import {
97
html,
@@ -30,6 +28,8 @@ import {
3028
} from '@umbraco-cms/backoffice/property';
3129
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
3230
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
31+
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
32+
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
3333

3434
interface MappedGroupWithBlockTypes extends UmbBlockGridTypeGroupType {
3535
blocks: Array<UmbBlockTypeWithGroupKey>;
@@ -43,7 +43,6 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
4343
extends UmbLitElement
4444
implements UmbPropertyEditorUiElement
4545
{
46-
#moveData?: Array<UmbBlockTypeWithGroupKey>;
4746
#sorter = new UmbSorterController<MappedGroupWithBlockTypes, HTMLElement>(this, {
4847
getUniqueOfElement: (element) => element.getAttribute('data-umb-group-key'),
4948
getUniqueOfModel: (modelEntry) => modelEntry.key!,
@@ -104,8 +103,14 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
104103

105104
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => {
106105
this.#datasetContext = context;
107-
//this.#observeBlocks();
108-
this.#observeBlockGroups();
106+
this.observe(
107+
await this.#datasetContext.propertyValueByAlias('blockGroups'),
108+
(value) => {
109+
this.#blockGroups = (value as Array<UmbBlockGridTypeGroupType>) ?? [];
110+
this.#mapValuesToBlockGroups();
111+
},
112+
'_observeBlockGroups',
113+
);
109114
});
110115

111116
this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(
@@ -119,24 +124,6 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
119124
});
120125
}
121126

122-
async #observeBlockGroups() {
123-
if (!this.#datasetContext) return;
124-
this.observe(await this.#datasetContext.propertyValueByAlias('blockGroups'), (value) => {
125-
this.#blockGroups = (value as Array<UmbBlockGridTypeGroupType>) ?? [];
126-
this.#mapValuesToBlockGroups();
127-
});
128-
}
129-
// TODO: No need for this, we just got the value via the value property.. [NL]
130-
/*
131-
async #observeBlocks() {
132-
if (!this.#datasetContext) return;
133-
this.observe(await this.#datasetContext.propertyValueByAlias('blocks'), (value) => {
134-
this.value = (value as Array<UmbBlockTypeWithGroupKey>) ?? [];
135-
this.#mapValuesToBlockGroups();
136-
});
137-
}
138-
*/
139-
140127
#mapValuesToBlockGroups() {
141128
if (!this.#blockGroups) return;
142129
// Map blocks that are not in any group, or in a group that does not exist
@@ -152,63 +139,60 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
152139
this.#sorter.setModel(this._groupsWithBlockTypes);
153140
}
154141

155-
#onDelete(e: CustomEvent, groupKey?: string) {
156-
const updatedValues = (e.target as UmbInputBlockTypeElement).value.map((value) => ({ ...value, groupKey }));
157-
const filteredValues = this.#value.filter((value) => value.groupKey !== groupKey);
158-
this.value = [...filteredValues, ...updatedValues];
159-
this.dispatchEvent(new UmbPropertyValueChangeEvent());
160-
}
161-
162-
async #onChange(e: CustomEvent) {
142+
async #onChange(e: Event, groupKey?: string) {
163143
e.stopPropagation();
164144
const element = e.target as UmbInputBlockTypeElement;
165-
const value = element.value;
166-
167-
if (!e.detail?.moveComplete) {
168-
// Container change, store data of the new group...
169-
const newGroupKey = element.getAttribute('data-umb-group-key');
170-
const movedItem = e.detail?.item as UmbBlockTypeWithGroupKey;
171-
// Check if item moved back to original group...
172-
if (movedItem.groupKey === newGroupKey) {
173-
this.#moveData = undefined;
174-
} else {
175-
this.#moveData = value.map((block) => ({ ...block, groupKey: newGroupKey }));
176-
}
177-
} else if (e.detail?.moveComplete) {
178-
// Move complete, get the blocks that were in an untouched group
179-
const blocks = this.#value
180-
.filter((block) => !value.find((value) => value.contentElementTypeKey === block.contentElementTypeKey))
181-
.filter(
182-
(block) => !this.#moveData?.find((value) => value.contentElementTypeKey === block.contentElementTypeKey),
183-
);
184-
185-
this.value = this.#moveData ? [...blocks, ...value, ...this.#moveData] : [...blocks, ...value];
186-
this.dispatchEvent(new UmbPropertyValueChangeEvent());
187-
this.#moveData = undefined;
145+
const value = element.value.map((x) => ({ ...x, groupKey }));
146+
147+
if (groupKey) {
148+
// Update the specific group:
149+
this._groupsWithBlockTypes = this._groupsWithBlockTypes.map((group) => {
150+
if (group.key === groupKey) {
151+
return { ...group, blocks: value };
152+
}
153+
return group;
154+
});
155+
} else {
156+
// Update the not grouped blocks:
157+
this._notGroupedBlockTypes = value;
188158
}
159+
160+
this.#updateValue();
161+
}
162+
163+
#updateValue() {
164+
this.value = [...this._notGroupedBlockTypes, ...this._groupsWithBlockTypes.flatMap((group) => group.blocks)];
165+
this.dispatchEvent(new UmbChangeEvent());
166+
}
167+
168+
#updateBlockGroupsValue(groups: Array<UmbBlockGridTypeGroupType>) {
169+
this.#datasetContext?.setPropertyValue('blockGroups', groups);
189170
}
190171

191172
#onCreate(e: CustomEvent, groupKey?: string) {
192173
const selectedElementType = e.detail.contentElementTypeKey;
193174
if (selectedElementType) {
194-
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + (groupKey ?? null));
175+
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + (groupKey ?? 'null'));
195176
}
196177
}
197178

198179
// TODO: Implement confirm dialog [NL]
199-
#deleteGroup(groupKey: string) {
200-
// TODO: make one method for updating the blockGroupsDataSetValue: [NL]
201-
// This one that deletes might require the ability to parse what to send as an argument to the method, then a filtering can occur before.
202-
this.#datasetContext?.setPropertyValue(
203-
'blockGroups',
204-
this.#blockGroups?.filter((group) => group.key !== groupKey),
205-
);
206-
180+
async #deleteGroup(groupKey: string) {
181+
const groupName = this.#blockGroups?.find((group) => group.key === groupKey)?.name ?? '';
182+
await umbConfirmModal(this, {
183+
headline: '#blockEditor_confirmDeleteBlockGroupTitle',
184+
content: this.localize.term('#blockEditor_confirmDeleteBlockGroupMessage', [groupName]),
185+
color: 'danger',
186+
confirmLabel: '#general_delete',
187+
});
207188
// If a group is deleted, Move the blocks to no group:
208189
this.value = this.#value.map((block) => (block.groupKey === groupKey ? { ...block, groupKey: undefined } : block));
190+
if (this.#blockGroups) {
191+
this.#updateBlockGroupsValue(this.#blockGroups.filter((group) => group.key !== groupKey));
192+
}
209193
}
210194

211-
#changeGroupName(e: UUIInputEvent, groupKey: string) {
195+
#onGroupNameChange(e: UUIInputEvent, groupKey: string) {
212196
const groupName = e.target.value as string;
213197
// TODO: make one method for updating the blockGroupsDataSetValue: [NL]
214198
this.#datasetContext?.setPropertyValue(
@@ -224,9 +208,8 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
224208
.propertyAlias=${this._alias}
225209
.value=${this._notGroupedBlockTypes}
226210
.workspacePath=${this._workspacePath}
227-
@change=${this.#onChange}
228-
@create=${(e: CustomEvent) => this.#onCreate(e, undefined)}
229-
@delete=${(e: CustomEvent) => this.#onDelete(e, undefined)}></umb-input-block-type>`
211+
@change=${(e: Event) => this.#onChange(e, undefined)}
212+
@create=${(e: CustomEvent) => this.#onCreate(e, undefined)}></umb-input-block-type>`
230213
: ''}
231214
${repeat(
232215
this._groupsWithBlockTypes,
@@ -239,9 +222,8 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
239222
.propertyAlias=${this._alias + '_' + group.key}
240223
.value=${group.blocks}
241224
.workspacePath=${this._workspacePath}
242-
@change=${this.#onChange}
243-
@create=${(e: CustomEvent) => this.#onCreate(e, group.key)}
244-
@delete=${(e: CustomEvent) => this.#onDelete(e, group.key)}></umb-input-block-type>
225+
@change=${(e: Event) => this.#onChange(e, group.key)}
226+
@create=${(e: CustomEvent) => this.#onCreate(e, group.key)}></umb-input-block-type>
245227
</div>`,
246228
)}
247229
</div>`;
@@ -253,7 +235,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
253235
auto-width
254236
label="Group"
255237
.value=${groupName ?? ''}
256-
@change=${(e: UUIInputEvent) => this.#changeGroupName(e, groupKey)}>
238+
@change=${(e: UUIInputEvent) => this.#onGroupNameChange(e, groupKey)}>
257239
<uui-button compact slot="append" label="delete" @click=${() => this.#deleteGroup(groupKey)}>
258240
<uui-icon name="icon-trash"></uui-icon>
259241
</uui-button>

src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import { css, html, customElement, property, state, repeat } from '@umbraco-cms/
66
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
77
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
88
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
9-
import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
9+
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
1010
import {
1111
UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT,
1212
UMB_DOCUMENT_TYPE_PICKER_MODAL,
13+
type UmbDocumentTypePickerModalData,
14+
type UmbDocumentTypePickerModalValue,
1315
} from '@umbraco-cms/backoffice/document-type';
14-
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
16+
import { UmbSorterController, UmbSorterResolvePlacementAsGrid } from '@umbraco-cms/backoffice/sorter';
1517
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
1618

1719
import '../block-type-card/index.js';
@@ -27,32 +29,40 @@ export class UmbInputBlockTypeElement<
2729
itemSelector: 'umb-block-type-card',
2830
identifier: 'umb-block-type-sorter',
2931
containerSelector: '#blocks',
30-
onChange: ({ model }) => {
31-
this._items = model;
32+
resolvePlacement: UmbSorterResolvePlacementAsGrid,
33+
onContainerChange: ({ item, model }) => {
34+
this.dispatchEvent(new CustomEvent('container-change', { detail: { item, model } }));
3235
},
33-
onContainerChange: ({ model, item }) => {
34-
this._items = model;
35-
this.dispatchEvent(new CustomEvent('change', { detail: { item } }));
36+
onChange: ({ model }) => {
37+
this._value = model;
38+
this.dispatchEvent(new UmbChangeEvent());
3639
},
37-
onEnd: () => {
40+
/*onEnd: () => {
3841
// TODO: Investigate if onEnd is called when a container move has been performed, if not then I would say it should be. [NL]
39-
this.dispatchEvent(new CustomEvent('change', { detail: { moveComplete: true } }));
40-
},
42+
this.dispatchEvent(new UmbChangeEvent());
43+
},*/
4144
});
45+
#elementPickerModal: UmbModalRouteRegistrationController<
46+
UmbDocumentTypePickerModalData,
47+
UmbDocumentTypePickerModalValue
48+
>;
4249

4350
@property({ type: Array, attribute: false })
4451
public set value(items) {
45-
this._items = items ?? [];
46-
this.#sorter.setModel(this._items);
52+
this._value = items ?? [];
53+
// Make sure the block types are unique on contentTypeElementKey:
54+
this._value = this._value.filter(
55+
(value, index, self) => self.findIndex((x) => x.contentElementTypeKey === value.contentElementTypeKey) === index,
56+
);
57+
this.#sorter.setModel(this._value);
4758
}
4859
public get value() {
49-
return this._items;
60+
return this._value;
5061
}
5162

52-
/** @deprecated will be removed in v17 */
5363
@property({ type: String })
5464
public set propertyAlias(value: string | undefined) {
55-
//this.#elementPickerModal.setUniquePathValue('propertyAlias', value);
65+
this.#elementPickerModal.setUniquePathValue('propertyAlias', value);
5666
}
5767
public get propertyAlias(): string | undefined {
5868
return undefined;
@@ -65,7 +75,7 @@ export class UmbInputBlockTypeElement<
6575
private _pickerPath?: string;
6676

6777
@state()
68-
private _items: Array<BlockType> = [];
78+
private _value: Array<BlockType> = [];
6979

7080
// TODO: Seems no need to have these initially, then can be retrieved inside the `create` method. [NL]
7181
#datasetContext?: UmbPropertyDatasetContext;
@@ -84,7 +94,8 @@ export class UmbInputBlockTypeElement<
8494
);
8595
});
8696

87-
new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_PICKER_MODAL)
97+
this.#elementPickerModal = new UmbModalRouteRegistrationController(this, UMB_DOCUMENT_TYPE_PICKER_MODAL)
98+
.addUniquePaths(['propertyAlias'])
8899
.onSetup(() => {
89100
return {
90101
data: {
@@ -123,8 +134,8 @@ export class UmbInputBlockTypeElement<
123134
}
124135

125136
deleteItem(contentElementTypeKey: string) {
126-
this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
127-
this.dispatchEvent(new UmbDeleteEvent());
137+
this._value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
138+
this.dispatchEvent(new UmbChangeEvent());
128139
}
129140

130141
async #onRequestDelete(item: BlockType) {

src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { UmbRefItemElement } from '@umbraco-cms/backoffice/components';
21
import type {
32
UmbEntityCreateOptionActionListModalData,
43
UmbEntityCreateOptionActionListModalValue,
54
} from './entity-create-option-action-list-modal.token.js';
5+
import { UmbRefItemElement } from '@umbraco-cms/backoffice/components';
66
import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';
77
import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
88
import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';

0 commit comments

Comments
 (0)