From e52fe0794e22b0840ffd48b37f23249caed02475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 20:17:59 +0100 Subject: [PATCH 01/47] cherry picked work --- .../packages/core/sorter/sorter.controller.ts | 192 +++++++++++------- 1 file changed, 116 insertions(+), 76 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index fa1b7f6662..b9e6f7e3b5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -123,8 +123,9 @@ export class UmbSorterController implements UmbController { #rqaId?: number; #containerElement!: HTMLElement; - #currentContainerVM = this; + #currentContainerCtrl = this; #currentContainerElement: Element | null = null; + #useContainerShadowRoot?: boolean; #scrollElement?: Element | null; #currentElement?: HTMLElement; @@ -135,7 +136,7 @@ export class UmbSorterController implements UmbController { #dragX = 0; #dragY = 0; - private _lastIndicationContainerVM: UmbSorterController | null = null; + #lastIndicationContainerCtrl: UmbSorterController | null = null; public get controllerAlias() { return this.#config.identifier; @@ -185,6 +186,8 @@ export class UmbSorterController implements UmbController { ? this.#host.shadowRoot!.querySelector(this.#config.containerSelector) : this.#host) ?? this.#host; + this.#useContainerShadowRoot = this.#containerElement === this.#host; + if (this.#currentContainerElement === this.#containerElement) { this.#currentContainerElement = containerEl; } @@ -198,7 +201,10 @@ export class UmbSorterController implements UmbController { // TODO: Clean up?? this.#observer.disconnect(); - const containerElement = this.#containerElement.shadowRoot ?? this.#containerElement; + // Only look at the shadowRoot if the containerElement is host. + const containerElement = this.#useContainerShadowRoot + ? this.#containerElement.shadowRoot ?? this.#containerElement + : this.#containerElement; containerElement.querySelectorAll(this.#config.itemSelector).forEach((child) => { if (child.matches && child.matches(this.#config.itemSelector)) { this.setupItem(child as HTMLElement); @@ -226,7 +232,7 @@ export class UmbSorterController implements UmbController { if (!this.#config.disabledItemSelector || !element.matches(this.#config.disabledItemSelector)) { element.draggable = true; - element.addEventListener('dragstart', this.handleDragStart); + element.addEventListener('dragstart', this.#handleDragStart); } } @@ -235,12 +241,12 @@ export class UmbSorterController implements UmbController { destroyIgnorerElements(element, this.#config.ignorerSelector); } - element.removeEventListener('dragstart', this.handleDragStart); + element.removeEventListener('dragstart', this.#handleDragStart); } - handleDragStart = (event: DragEvent) => { + #handleDragStart = (event: DragEvent) => { if (this.#currentElement) { - this.handleDragEnd(); + this.#handleDragEnd(); } event.stopPropagation(); @@ -285,11 +291,11 @@ export class UmbSorterController implements UmbController { } if (this.#config.onStart) { - this.#config.onStart({ item: this.#currentItem!, element: this.#currentElement }); + this.#config.onStart({ item: this.#currentItem, element: this.#currentElement }); } - window.addEventListener('dragover', this.handleDragMove); - window.addEventListener('dragend', this.handleDragEnd); + window.addEventListener('dragover', this.#handleDragMove); + window.addEventListener('dragend', this.#handleDragEnd); // We must wait one frame before changing the look of the block. this.#rqaId = requestAnimationFrame(() => { @@ -302,23 +308,23 @@ export class UmbSorterController implements UmbController { }); }; - handleDragEnd = async () => { + #handleDragEnd = async () => { if (!this.#currentElement || !this.#currentItem) { return; } - window.removeEventListener('dragover', this.handleDragMove); - window.removeEventListener('dragend', this.handleDragEnd); + window.removeEventListener('dragover', this.#handleDragMove); + window.removeEventListener('dragend', this.#handleDragEnd); this.#currentElement.style.transform = ''; this.#currentElement.classList.remove(this.#config.placeholderClass); - this.stopAutoScroll(); + this.#stopAutoScroll(); this.removeAllowIndication(); - if ((await this.#currentContainerVM.sync(this.#currentElement, this)) === false) { + if ((await this.#currentContainerCtrl.sync(this.#currentElement, this)) === false) { // Sync could not succeed, might be because item is not allowed here. - this.#currentContainerVM = this; + this.#currentContainerCtrl = this; if (this.#config.onContainerChange) { this.#config.onContainerChange({ item: this.#currentItem, @@ -327,6 +333,8 @@ export class UmbSorterController implements UmbController { }); } + /* + TODO: Lets somehow reset, without moving elements [NL] // Lets move the Element back to where it came from: const movingItemIndex = this.#model.indexOf(this.#currentItem); if (movingItemIndex < this.#model.length - 1) { @@ -340,6 +348,7 @@ export class UmbSorterController implements UmbController { } else { this.#containerElement.appendChild(this.#currentElement); } + */ } if (this.#config.onEnd) { @@ -348,12 +357,12 @@ export class UmbSorterController implements UmbController { if (this.#rqaId) { cancelAnimationFrame(this.#rqaId); + this.#rqaId = undefined; } this.#currentContainerElement = this.#containerElement; - this.#currentContainerVM = this; + this.#currentContainerCtrl = this; - this.#rqaId = undefined; this.#currentItem = undefined; this.#currentElement = undefined; this.#currentDragElement = undefined; @@ -362,7 +371,7 @@ export class UmbSorterController implements UmbController { this.#dragY = 0; }; - handleDragMove = (event: DragEvent) => { + #handleDragMove = (event: DragEvent) => { if (!this.#currentElement) { return; } @@ -386,13 +395,13 @@ export class UmbSorterController implements UmbController { const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, this.#currentDragRect); if (!insideCurrentRect) { if (this.#rqaId === undefined) { - this.#rqaId = requestAnimationFrame(this.moveCurrentElement); + this.#rqaId = requestAnimationFrame(this.#moveCurrentElement); } } } }; - moveCurrentElement = () => { + #moveCurrentElement = () => { this.#rqaId = undefined; if (!this.#currentElement || !this.#currentContainerElement || !this.#currentItem) { return; @@ -404,7 +413,7 @@ export class UmbSorterController implements UmbController { return; } - // If we have a boundarySelector, try it, if we didn't get anything fall back to currentContainerElement. + // If we have a boundarySelector, try it. If we didn't get anything fall back to currentContainerElement: const currentBoundaryElement = (this.#config.boundarySelector ? this.#currentContainerElement.closest(this.#config.boundarySelector) @@ -412,24 +421,24 @@ export class UmbSorterController implements UmbController { const currentBoundaryRect = currentBoundaryElement.getBoundingClientRect(); - const currentContainerHasItems = this.#currentContainerVM.hasOtherItemsThan(this.#currentItem!); + const currentContainerHasItems = this.#currentContainerCtrl.hasOtherItemsThan(this.#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. + // If we have items we must be 10px within the container to accept the move. const offsetEdge = currentContainerHasItems ? -10 : 20; if (!isWithinRect(this.#dragX, this.#dragY, currentBoundaryRect, offsetEdge)) { // we are outside the current container boundary, so lets see if there is a parent we can move. const parentNode = this.#currentContainerElement.parentNode; - if (parentNode) { + if (parentNode && this.#config.containerSelector) { // TODO: support multiple parent shadowDOMs? - const parentContainer = this.#config.containerSelector - ? (parentNode as HTMLElement).closest(this.#config.containerSelector) - : null; + const parentContainer = (parentNode as ShadowRoot).host + ? (parentNode as ShadowRoot).host.closest(this.#config.containerSelector) + : (parentNode as HTMLElement).closest(this.#config.containerSelector); if (parentContainer) { - const parentContainerVM = (parentContainer as any)['__umbBlockGridSorterController'](); - if (parentContainerVM.unique === this.controllerAlias) { + const parentContainerCtrl = (parentContainer as any)['__umbBlockGridSorterController'](); + if (parentContainerCtrl.unique === this.controllerAlias) { this.#currentContainerElement = parentContainer as Element; - this.#currentContainerVM = parentContainerVM; + this.#currentContainerCtrl = parentContainerCtrl; if (this.#config.onContainerChange) { this.#config.onContainerChange({ item: this.#currentItem, @@ -442,12 +451,12 @@ export class UmbSorterController implements UmbController { } } + const containerElement = this.#useContainerShadowRoot + ? this.#currentContainerElement.shadowRoot ?? this.#currentContainerElement + : this.#currentContainerElement; + // We want to retrieve the children of the container, every time to ensure we got the right order and index - const orderedContainerElements = Array.from( - this.#currentContainerElement.shadowRoot - ? this.#currentContainerElement.shadowRoot.querySelectorAll(this.#config.itemSelector) - : this.#currentContainerElement.querySelectorAll(this.#config.itemSelector), - ); + const orderedContainerElements = Array.from(containerElement.querySelectorAll(this.#config.itemSelector)); const currentContainerRect = this.#currentContainerElement.getBoundingClientRect(); @@ -515,10 +524,10 @@ export class UmbSorterController implements UmbController { // gather elements on the same row. const subOffsetEdge = subContainerHasItems ? -10 : 20; if (isWithinRect(this.#dragX, this.#dragY, subBoundaryRect, subOffsetEdge)) { - const subVm = (subLayoutEl as any)['__umbBlockGridSorterController'](); - if (subVm.unique === this.controllerAlias) { + const subCtrl = (subLayoutEl as any)['__umbBlockGridSorterController'](); + if (subCtrl.unique === this.controllerAlias) { this.#currentContainerElement = subLayoutEl as HTMLElement; - this.#currentContainerVM = subVm; + this.#currentContainerCtrl = subCtrl; if (this.#config.onContainerChange) { this.#config.onContainerChange({ item: this.#currentItem, @@ -526,7 +535,7 @@ export class UmbSorterController implements UmbController { //ownerVM: this.#currentContainerVM.ownerVM, }); } - this.moveCurrentElement(); + this.#moveCurrentElement(); return; } } @@ -534,7 +543,7 @@ export class UmbSorterController implements UmbController { } // Indication if drop is good: - if (this.updateAllowIndication(this.#currentContainerVM, this.#currentItem) === false) { + if (this.updateAllowIndication(this.#currentContainerCtrl, this.#currentItem) === false) { return; } @@ -583,48 +592,79 @@ export class UmbSorterController implements UmbController { const foundElIndex = orderedContainerElements.indexOf(foundEl); const placeAt = placeAfter ? foundElIndex + 1 : foundElIndex; - this.move(orderedContainerElements, placeAt); + this.#move(orderedContainerElements, placeAt); return; } // We skipped the above part cause we are above or below container: // Indication if drop is good: - if (this.updateAllowIndication(this.#currentContainerVM, this.#currentItem) === false) { + if (this.updateAllowIndication(this.#currentContainerCtrl, this.#currentItem) === false) { return; } if (this.#dragY < currentContainerRect.top) { - this.move(orderedContainerElements, 0); + this.#move(orderedContainerElements, 0); } else if (this.#dragY > currentContainerRect.bottom) { - this.move(orderedContainerElements, -1); + this.#move(orderedContainerElements, -1); } }; - move(orderedContainerElements: Array, newElIndex: number) { + #move(orderedContainerElements: Array, newElIndex: number) { if (!this.#currentElement || !this.#currentItem || !this.#currentContainerElement) return; newElIndex = newElIndex === -1 ? orderedContainerElements.length : newElIndex; - const containerElement = this.#currentContainerElement.shadowRoot ?? this.#currentContainerElement; + const containerElement = this.#useContainerShadowRoot + ? this.#currentContainerElement.shadowRoot ?? this.#currentContainerElement + : this.#currentContainerElement; + + const localMove = this.#currentContainerElement === this.#containerElement; + let commentBefore; + let commentAfter; + if (localMove) { + commentBefore = this.#currentElement.previousSibling; + // nodeType 8 is a comment. + while (commentBefore && commentBefore.nodeType !== 8) { + commentBefore = commentBefore.previousSibling; + } + commentAfter = this.#currentElement.nextSibling; + // nodeType 8 is a comment. + while (commentAfter && commentAfter.nodeType !== 8) { + commentAfter = commentAfter.nextSibling; + } + } else { + console.log('THIS IS NOT A LOCAL MOVE, THIS CASE HAS TO BE HANDLED:'); + // TODO: We need to figure out what to do about non-local moves. [NL] + } 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 !== this.#currentElement) { containerElement.insertBefore(this.#currentElement, placeBeforeElement); + if (commentBefore) { + containerElement.insertBefore(commentBefore, this.#currentElement); + } + if (commentAfter) { + containerElement.insertBefore(commentAfter, this.#currentElement.nextSibling); + } } } else { + if (commentBefore) { + containerElement.appendChild(commentBefore); + } containerElement.appendChild(this.#currentElement); + if (commentAfter) { + containerElement.appendChild(commentAfter); + } } - if (this.#config.onChange) { - this.#config.onChange({ - element: this.#currentElement, - item: this.#currentItem, - //ownerVM: this.#currentContainerVM.ownerVM - }); - } + this.#config.onChange?.({ + element: this.#currentElement, + item: this.#currentItem, + //ownerVM: this.#currentContainerVM.ownerVM + }); } /** Management methods: */ @@ -656,8 +696,8 @@ export class UmbSorterController implements UmbController { return this.#model.filter((x) => x !== item).length > 0; } - public async sync(element: HTMLElement, fromVm: UmbSorterController) { - const movingItem = fromVm.getItemOfElement(element); + public async sync(element: HTMLElement, fromController: UmbSorterController) { + const movingItem = fromController.getItemOfElement(element); if (!movingItem) { console.error('Could not find item of sync item'); return false; @@ -665,7 +705,7 @@ export class UmbSorterController implements UmbController { if (this.notifyRequestDrop({ item: movingItem }) === false) { return false; } - if (fromVm.removeItem(movingItem) === null) { + if (fromController.removeItem(movingItem) === null) { console.error('Sync could not remove item'); return false; } @@ -707,36 +747,36 @@ export class UmbSorterController implements UmbController { this.#model.splice(newIndex, 0, movingItem); } - const eventData = { item: movingItem, fromController: fromVm, toController: this }; - if (fromVm !== this) { - fromVm.notifySync(eventData); + const eventData = { item: movingItem, fromController: fromController, toController: this }; + if (fromController !== this) { + fromController.notifySync(eventData); } this.notifySync(eventData); return true; } - updateAllowIndication(contextVM: UmbSorterController, item: T) { + updateAllowIndication(controller: UmbSorterController, item: T) { // Remove old indication: - if (this._lastIndicationContainerVM !== null && this._lastIndicationContainerVM !== contextVM) { - this._lastIndicationContainerVM.notifyAllowed(); + if (this.#lastIndicationContainerCtrl !== null && this.#lastIndicationContainerCtrl !== controller) { + this.#lastIndicationContainerCtrl.notifyAllowed(); } - this._lastIndicationContainerVM = contextVM; + this.#lastIndicationContainerCtrl = controller; - if (contextVM.notifyRequestDrop({ item: item }) === true) { - contextVM.notifyAllowed(); + if (controller.notifyRequestDrop({ item: item }) === true) { + controller.notifyAllowed(); return true; } - contextVM.notifyDisallowed(); // This block is not accepted to we will indicate that its not allowed. + controller.notifyDisallowed(); // This block is not accepted to we will indicate that its not allowed. return false; } removeAllowIndication() { // Remove old indication: - if (this._lastIndicationContainerVM !== null) { - this._lastIndicationContainerVM.notifyAllowed(); + if (this.#lastIndicationContainerCtrl !== null) { + this.#lastIndicationContainerCtrl.notifyAllowed(); } - this._lastIndicationContainerVM = null; + this.#lastIndicationContainerCtrl = null; } // TODO: Move auto scroll into its own class? @@ -786,15 +826,15 @@ export class UmbSorterController implements UmbController { ? -1 : 0; - this.#autoScrollRAF = requestAnimationFrame(this._performAutoScroll); + this.#autoScrollRAF = requestAnimationFrame(this.#performAutoScroll); } } - private _performAutoScroll = () => { + #performAutoScroll = () => { this.#autoScrollEl!.scrollLeft += this.autoScrollX * autoScrollSpeed; this.#autoScrollEl!.scrollTop += this.autoScrollY * autoScrollSpeed; - this.#autoScrollRAF = requestAnimationFrame(this._performAutoScroll); + this.#autoScrollRAF = requestAnimationFrame(this.#performAutoScroll); }; - private stopAutoScroll() { + #stopAutoScroll() { cancelAnimationFrame(this.#autoScrollRAF!); this.#autoScrollRAF = null; } @@ -824,10 +864,10 @@ export class UmbSorterController implements UmbController { destroy() { // Do something when host element is destroyed. if (this.#currentElement) { - this.handleDragEnd(); + this.#handleDragEnd(); } - this._lastIndicationContainerVM = null; + this.#lastIndicationContainerCtrl = null; // TODO: Clean up items?? this.#observer.disconnect(); From b052ac92cd2bfc621c5de935193a6c05a1e7d660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 21:15:30 +0100 Subject: [PATCH 02/47] example setup --- .../dashboard-with-property-dataset/index.ts | 2 +- .../sorter-with-two-containers/README.md | 5 + .../sorter-with-two-containers/index.ts | 15 +++ .../sorter-dashboard.ts | 112 ++++++++++++++++++ .../core/sorter/stories/sorter-controller.mdx | 8 +- 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md create mode 100644 src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts diff --git a/src/Umbraco.Web.UI.Client/examples/dashboard-with-property-dataset/index.ts b/src/Umbraco.Web.UI.Client/examples/dashboard-with-property-dataset/index.ts index bf689650a7..4c50c3a641 100644 --- a/src/Umbraco.Web.UI.Client/examples/dashboard-with-property-dataset/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/dashboard-with-property-dataset/index.ts @@ -3,7 +3,7 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ { type: 'dashboard', - name: 'Example Dataset Workspace View', + name: 'Example Dataset Dashboard', alias: 'example.dashboard.dataset', element: () => import('./dataset-dashboard.js'), weight: 900, diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md new file mode 100644 index 0000000000..4a1b15255a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md @@ -0,0 +1,5 @@ +# Property Dataset Dashboard Example + +This example demonstrates the essence of the Property Dataset. + +This dashboard implements such, to display a few selected Property Editors and bind the data back to the Dashboard. diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/index.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/index.ts new file mode 100644 index 0000000000..0771859fe9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/index.ts @@ -0,0 +1,15 @@ +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'dashboard', + name: 'Example Sorter Dashboard', + alias: 'example.dashboard.dataset', + element: () => import('./sorter-dashboard.js'), + weight: 900, + meta: { + label: 'Sorter example', + pathname: 'sorter-example', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts new file mode 100644 index 0000000000..b419a3fd8f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -0,0 +1,112 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, LitElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +type ModelEntryType = { + name: string; +}; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: ModelEntryType) => { + return element.getAttribute('data-name') === model.name; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: ModelEntryType) => { + return container.querySelector('data-name[' + modelEntry.name + ']'); + }, + identifier: 'string-that-identifies-all-example-sorters', + itemSelector: '.sorter-item', + containerSelector: '.sorter-container', +}; + +@customElement('example-sorter-dashboard') +export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { + @state() + _items: ModelEntryType[] = [ + { + name: 'Apple', + }, + { + name: 'Banana', + }, + { + name: 'Pear', + }, + { + name: 'Pineapple', + }, + { + name: 'Lemon', + }, + ]; + + #sorter = new UmbSorterController(this, { + ...SORTER_CONFIG, + performItemInsert: ({ item, newIndex }) => { + this._items.splice(newIndex, 0, item); + this.requestUpdate('_items'); + return true; + }, + performItemRemove: ({ item }) => { + const indexToMove = this._items.findIndex((x) => x.name === item.name); + this._items.splice(indexToMove, 1); + this.requestUpdate('_items'); + return true; + }, + }); + + constructor() { + super(); + this.#sorter.setModel(this._items); + } + + removeItem = (item: ModelEntryType) => { + this._items = this._items.filter((r) => r.name !== item.name); + this.#sorter.setModel(this._items); + }; + + render() { + return html` + +
+ ${repeat( + this._items, + (item) => item.name, + (item) => + html`
+ ${item.name} +
`, + )} +
+
+ `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + padding: var(--uui-size-layout-1); + } + + .sorter-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--uui-size-layout-0); + border: 1px solid var(--uui-color-border); + border-radius: var(--uui-border-radius); + margin-bottom: 3px; + } + `, + ]; +} + +export default ExampleSorterDashboard; + +declare global { + interface HTMLElementTagNameMap { + 'example-sorter-dashboard': ExampleSorterDashboard; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx index fdf53c52be..a9b5072380 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx @@ -176,11 +176,13 @@ export class MyElement extends UmbElementMixin(LitElement) { #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { - console.log(item, newIndex); - // Perform some logic here to calculate the new sortOrder & save it. + // Insert logic that updates the model, so the item gets the new index in the model. + return true; + }, + performItemRemove: () => { + // Insert logic that updates the model, so the item gets removed from the model. return true; }, - performItemRemove: () => true, }); } ``` From bbf03ad0402b7802fd932c495542eea825750008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 21:18:26 +0100 Subject: [PATCH 03/47] improve styling --- .../examples/sorter-with-two-containers/sorter-dashboard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index b419a3fd8f..702c03fa38 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -94,7 +94,7 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { display: flex; align-items: center; justify-content: space-between; - padding: var(--uui-size-layout-0); + padding: var(--uui-size-layout-1); border: 1px solid var(--uui-color-border); border-radius: var(--uui-border-radius); margin-bottom: 3px; From 94cf0fbdc9457fed78a4abb7a8243fa32f30b3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 21:39:35 +0100 Subject: [PATCH 04/47] console logs --- .../examples/sorter-with-two-containers/sorter-dashboard.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 702c03fa38..471446d531 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -44,12 +44,14 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { this._items.splice(newIndex, 0, item); + console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('_items'); return true; }, performItemRemove: ({ item }) => { const indexToMove = this._items.findIndex((x) => x.name === item.name); this._items.splice(indexToMove, 1); + console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('_items'); return true; }, From fe64c025fcf4aee8939dd29111a7e53ac844e7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 22:40:22 +0100 Subject: [PATCH 05/47] move data, not elements approach --- .../sorter-dashboard.ts | 4 + .../packages/core/sorter/sorter.controller.ts | 179 +++++------------- 2 files changed, 54 insertions(+), 129 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 471446d531..c064490239 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -17,6 +17,7 @@ const SORTER_CONFIG: UmbSorterConfig = { identifier: 'string-that-identifies-all-example-sorters', itemSelector: '.sorter-item', containerSelector: '.sorter-container', + placeholderClass: 'sorter-placeholder', }; @customElement('example-sorter-dashboard') @@ -101,6 +102,9 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { border-radius: var(--uui-border-radius); margin-bottom: 3px; } + .sorter-placeholder { + opacity: 0.2; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index b9e6f7e3b5..598ce08d76 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -114,7 +114,7 @@ export type UmbSorterConfig = Omit< * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. */ -export class UmbSorterController implements UmbController { +export class UmbSorterController implements UmbController { #host; #config: INTERNAL_UmbSorterConfig; #observer; @@ -123,7 +123,8 @@ export class UmbSorterController implements UmbController { #rqaId?: number; #containerElement!: HTMLElement; - #currentContainerCtrl = this; + + #currentContainerCtrl: UmbSorterController = this; #currentContainerElement: Element | null = null; #useContainerShadowRoot?: boolean; @@ -133,6 +134,7 @@ export class UmbSorterController implements UmbController { #currentDragRect?: DOMRect; #currentItem?: T | null; + #currentIndex?: number; #dragX = 0; #dragY = 0; @@ -284,6 +286,9 @@ export class UmbSorterController implements UmbController { return; } + // Get the current index of the item: + this.#currentIndex = this.#model.indexOf(this.#currentItem); + this.#currentElement.style.transform = 'translateZ(0)'; // Solves problem with FireFox and ShadowDom in the drag-image. if (this.#config.dataTransferResolver) { @@ -321,36 +326,6 @@ export class UmbSorterController implements UmbController { this.#stopAutoScroll(); this.removeAllowIndication(); - if ((await this.#currentContainerCtrl.sync(this.#currentElement, this)) === false) { - // Sync could not succeed, might be because item is not allowed here. - - this.#currentContainerCtrl = this; - if (this.#config.onContainerChange) { - this.#config.onContainerChange({ - item: this.#currentItem, - element: this.#currentElement, - //ownerVM: this.#currentContainerVM.ownerVM, - }); - } - - /* - TODO: Lets somehow reset, without moving elements [NL] - // Lets move the Element back to where it came from: - const movingItemIndex = this.#model.indexOf(this.#currentItem); - if (movingItemIndex < this.#model.length - 1) { - const afterItem = this.#model[movingItemIndex + 1]; - const afterEl = this.#config.querySelectModelToElement(this.#containerElement, afterItem); - if (afterEl) { - this.#containerElement.insertBefore(this.#currentElement, afterEl); - } else { - this.#containerElement.appendChild(this.#currentElement); - } - } else { - this.#containerElement.appendChild(this.#currentElement); - } - */ - } - if (this.#config.onEnd) { this.#config.onEnd({ item: this.#currentItem, element: this.#currentElement }); } @@ -395,24 +370,28 @@ export class UmbSorterController implements UmbController { const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, this.#currentDragRect); if (!insideCurrentRect) { if (this.#rqaId === undefined) { - this.#rqaId = requestAnimationFrame(this.#moveCurrentElement); + this.#rqaId = requestAnimationFrame(this.#updateDragMove); } } } }; - #moveCurrentElement = () => { + #updateDragMove = () => { this.#rqaId = undefined; if (!this.#currentElement || !this.#currentContainerElement || !this.#currentItem) { return; } + console.log('this.#currentElement', this.#currentElement); + const currentElementRect = this.#currentElement.getBoundingClientRect(); const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, currentElementRect); if (insideCurrentRect) { return; } + let toBeCurrentContainerCtrl: UmbSorterController | undefined = undefined; + // If we have a boundarySelector, try it. If we didn't get anything fall back to currentContainerElement: const currentBoundaryElement = (this.#config.boundarySelector @@ -438,7 +417,7 @@ export class UmbSorterController implements UmbController { const parentContainerCtrl = (parentContainer as any)['__umbBlockGridSorterController'](); if (parentContainerCtrl.unique === this.controllerAlias) { this.#currentContainerElement = parentContainer as Element; - this.#currentContainerCtrl = parentContainerCtrl; + toBeCurrentContainerCtrl = parentContainerCtrl; if (this.#config.onContainerChange) { this.#config.onContainerChange({ item: this.#currentItem, @@ -527,7 +506,7 @@ export class UmbSorterController implements UmbController { const subCtrl = (subLayoutEl as any)['__umbBlockGridSorterController'](); if (subCtrl.unique === this.controllerAlias) { this.#currentContainerElement = subLayoutEl as HTMLElement; - this.#currentContainerCtrl = subCtrl; + toBeCurrentContainerCtrl = subCtrl; if (this.#config.onContainerChange) { this.#config.onContainerChange({ item: this.#currentItem, @@ -535,7 +514,7 @@ export class UmbSorterController implements UmbController { //ownerVM: this.#currentContainerVM.ownerVM, }); } - this.#moveCurrentElement(); + this.#updateDragMove(); return; } } @@ -543,7 +522,9 @@ export class UmbSorterController implements UmbController { } // Indication if drop is good: - if (this.updateAllowIndication(this.#currentContainerCtrl, this.#currentItem) === false) { + if ( + this.updateAllowIndication(toBeCurrentContainerCtrl ?? this.#currentContainerCtrl, this.#currentItem) === false + ) { return; } @@ -590,81 +571,41 @@ export class UmbSorterController implements UmbController { } const foundElIndex = orderedContainerElements.indexOf(foundEl); - const placeAt = placeAfter ? foundElIndex + 1 : foundElIndex; - - this.#move(orderedContainerElements, placeAt); + const newIndex = placeAfter ? foundElIndex + 1 : foundElIndex; + this.#moveElementTo(toBeCurrentContainerCtrl, newIndex); return; } // We skipped the above part cause we are above or below container: // Indication if drop is good: - if (this.updateAllowIndication(this.#currentContainerCtrl, this.#currentItem) === false) { + if ( + this.updateAllowIndication(toBeCurrentContainerCtrl ?? this.#currentContainerCtrl, this.#currentItem) === false + ) { return; } if (this.#dragY < currentContainerRect.top) { - this.#move(orderedContainerElements, 0); + this.#moveElementTo(toBeCurrentContainerCtrl, 0); } else if (this.#dragY > currentContainerRect.bottom) { - this.#move(orderedContainerElements, -1); + this.#moveElementTo(toBeCurrentContainerCtrl, -1); } }; - #move(orderedContainerElements: Array, newElIndex: number) { - if (!this.#currentElement || !this.#currentItem || !this.#currentContainerElement) return; - - newElIndex = newElIndex === -1 ? orderedContainerElements.length : newElIndex; - - const containerElement = this.#useContainerShadowRoot - ? this.#currentContainerElement.shadowRoot ?? this.#currentContainerElement - : this.#currentContainerElement; - - const localMove = this.#currentContainerElement === this.#containerElement; - let commentBefore; - let commentAfter; - if (localMove) { - commentBefore = this.#currentElement.previousSibling; - // nodeType 8 is a comment. - while (commentBefore && commentBefore.nodeType !== 8) { - commentBefore = commentBefore.previousSibling; - } - commentAfter = this.#currentElement.nextSibling; - // nodeType 8 is a comment. - while (commentAfter && commentAfter.nodeType !== 8) { - commentAfter = commentAfter.nextSibling; - } - } else { - console.log('THIS IS NOT A LOCAL MOVE, THIS CASE HAS TO BE HANDLED:'); - // TODO: We need to figure out what to do about non-local moves. [NL] + async #moveElementTo(containerCtrl: UmbSorterController | undefined, newIndex: number) { + if (!this.#currentElement) { + return; } - 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 !== this.#currentElement) { - containerElement.insertBefore(this.#currentElement, placeBeforeElement); - if (commentBefore) { - containerElement.insertBefore(commentBefore, this.#currentElement); - } - if (commentAfter) { - containerElement.insertBefore(commentAfter, this.#currentElement.nextSibling); - } - } - } else { - if (commentBefore) { - containerElement.appendChild(commentBefore); - } - containerElement.appendChild(this.#currentElement); - if (commentAfter) { - containerElement.appendChild(commentAfter); - } - } + containerCtrl ??= this as UmbSorterController; - this.#config.onChange?.({ - element: this.#currentElement, - item: this.#currentItem, - //ownerVM: this.#currentContainerVM.ownerVM - }); + // If same container and same index, do nothing: + if (this.#currentContainerCtrl === containerCtrl && this.#currentIndex === newIndex) return; + + if (await containerCtrl.updateModel(newIndex, this.#currentElement, this.#currentContainerCtrl)) { + this.#currentContainerCtrl = containerCtrl; + this.#currentIndex = newIndex; + } } /** Management methods: */ @@ -678,26 +619,27 @@ export class UmbSorterController implements UmbController { public async removeItem(item: T) { if (!item) { - return null; + return false; } if (this.#config.performItemRemove) { - return await this.#config.performItemRemove({ item }); + return (await this.#config.performItemRemove({ item })) ?? false; } else { const oldIndex = this.#model.indexOf(item); if (oldIndex !== -1) { - return this.#model.splice(oldIndex, 1)[0]; + this.#model.splice(oldIndex, 1); + return true; } } - return null; + return false; } hasOtherItemsThan(item: T) { return this.#model.filter((x) => x !== item).length > 0; } - public async sync(element: HTMLElement, fromController: UmbSorterController) { - const movingItem = fromController.getItemOfElement(element); + public async updateModel(newIndex: number, element: HTMLElement, fromCtrl: UmbSorterController) { + const movingItem = fromCtrl.getItemOfElement(element); if (!movingItem) { console.error('Could not find item of sync item'); return false; @@ -705,39 +647,18 @@ export class UmbSorterController implements UmbController { if (this.notifyRequestDrop({ item: movingItem }) === false) { return false; } - if (fromController.removeItem(movingItem) === null) { + if ((await fromCtrl.removeItem(movingItem)) !== true) { 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: Element | null = null; - let loopEl: Element | null = element; - while ((loopEl = loopEl?.nextElementSibling)) { - if (loopEl.matches && loopEl.matches(this.#config.itemSelector)) { - nextEl = loopEl; - break; - } - } - - let newIndex = this.#model.length; - - const movingItemIndex = this.#model.indexOf(movingItem); + const localMove = fromCtrl === this; + const movingItemIndex = localMove ? this.#model.indexOf(movingItem) : -1; if (movingItemIndex !== -1 && movingItemIndex <= movingItemIndex) { newIndex--; } - if (nextEl) { - // We had a reference element, we want to get the index of it. - // This is might a problem if a item is being moved forward? (was also like this in the AngularJS version...) - newIndex = this.#model.findIndex((entry) => this.#config.compareElementToModel(nextEl! as HTMLElement, entry)); - } - if (this.#config.performItemInsert) { const result = await this.#config.performItemInsert({ item: movingItem, newIndex }); if (result === false) { @@ -747,9 +668,9 @@ export class UmbSorterController implements UmbController { this.#model.splice(newIndex, 0, movingItem); } - const eventData = { item: movingItem, fromController: fromController, toController: this }; - if (fromController !== this) { - fromController.notifySync(eventData); + const eventData = { item: movingItem, fromController: fromCtrl, toController: this }; + if (fromCtrl !== this) { + fromCtrl.notifySync(eventData); } this.notifySync(eventData); From cef01f54a383d3e7462cac748d0685b77af6367e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 11 Jan 2024 22:43:07 +0100 Subject: [PATCH 06/47] note for further work --- .../examples/sorter-with-two-containers/sorter-dashboard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index c064490239..9f8737a08e 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -78,6 +78,7 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { (item) => html`
${item.name} +
`, )} From 5d682929d7c9a8cf773b594532eb23f6c02b9c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 13:27:13 +0100 Subject: [PATCH 07/47] move into web components --- .../sorter-dashboard.ts | 92 +-------------- .../sorter-group.ts | 107 ++++++++++++++++++ .../sorter-with-two-containers/sorter-item.ts | 46 ++++++++ .../packages/core/sorter/sorter.controller.ts | 26 +++-- 4 files changed, 175 insertions(+), 96 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 9f8737a08e..a1a799fbc5 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -1,87 +1,13 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, LitElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; -import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; - -type ModelEntryType = { - name: string; -}; - -const SORTER_CONFIG: UmbSorterConfig = { - compareElementToModel: (element: HTMLElement, model: ModelEntryType) => { - return element.getAttribute('data-name') === model.name; - }, - querySelectModelToElement: (container: HTMLElement, modelEntry: ModelEntryType) => { - return container.querySelector('data-name[' + modelEntry.name + ']'); - }, - identifier: 'string-that-identifies-all-example-sorters', - itemSelector: '.sorter-item', - containerSelector: '.sorter-container', - placeholderClass: 'sorter-placeholder', -}; - +import './sorter-group.js'; @customElement('example-sorter-dashboard') export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { - @state() - _items: ModelEntryType[] = [ - { - name: 'Apple', - }, - { - name: 'Banana', - }, - { - name: 'Pear', - }, - { - name: 'Pineapple', - }, - { - name: 'Lemon', - }, - ]; - - #sorter = new UmbSorterController(this, { - ...SORTER_CONFIG, - performItemInsert: ({ item, newIndex }) => { - this._items.splice(newIndex, 0, item); - console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('_items'); - return true; - }, - performItemRemove: ({ item }) => { - const indexToMove = this._items.findIndex((x) => x.name === item.name); - this._items.splice(indexToMove, 1); - console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('_items'); - return true; - }, - }); - - constructor() { - super(); - this.#sorter.setModel(this._items); - } - - removeItem = (item: ModelEntryType) => { - this._items = this._items.filter((r) => r.name !== item.name); - this.#sorter.setModel(this._items); - }; - render() { return html` - -
- ${repeat( - this._items, - (item) => item.name, - (item) => - html`
- ${item.name} - -
`, - )} -
+ + `; } @@ -94,17 +20,9 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { padding: var(--uui-size-layout-1); } - .sorter-item { + .outer-wrapper { display: flex; - align-items: center; - justify-content: space-between; - padding: var(--uui-size-layout-1); - border: 1px solid var(--uui-color-border); - border-radius: var(--uui-border-radius); - margin-bottom: 3px; - } - .sorter-placeholder { - opacity: 0.2; + flex-direction: row; } `, ]; diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts new file mode 100644 index 0000000000..04d64ee479 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -0,0 +1,107 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, LitElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +import './sorter-item.js'; + +type ModelEntryType = { + name: string; +}; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: ModelEntryType) => { + return element.getAttribute('name') === model.name; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: ModelEntryType) => { + return container.querySelector('name[' + modelEntry.name + ']'); + }, + identifier: 'string-that-identifies-all-example-sorters', + itemSelector: 'example-sorter-item', + containerSelector: '.sorter-container', +}; + +@customElement('example-sorter-group') +export class ExampleSorterGroup extends UmbElementMixin(LitElement) { + @state() + _items: ModelEntryType[] = [ + { + name: 'Apple', + }, + { + name: 'Banana', + }, + { + name: 'Pear', + }, + { + name: 'Pineapple', + }, + { + name: 'Lemon', + }, + ]; + + #sorter = new UmbSorterController(this, { + ...SORTER_CONFIG, + performItemInsert: ({ item, newIndex }) => { + this._items.splice(newIndex, 0, item); + console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); + this.requestUpdate('_items'); + return true; + }, + performItemRemove: ({ item }) => { + const indexToMove = this._items.findIndex((x) => x.name === item.name); + this._items.splice(indexToMove, 1); + console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); + this.requestUpdate('_items'); + return true; + }, + }); + + constructor() { + super(); + this.#sorter.setModel(this._items); + } + + removeItem = (item: ModelEntryType) => { + this._items = this._items.filter((r) => r.name !== item.name); + this.#sorter.setModel(this._items); + }; + + render() { + return html` +
+ ${repeat( + this._items, + (item) => item.name, + (item) => + html` + + `, + )} +
+ `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: block; + } + + .sorter-placeholder { + opacity: 0.2; + } + `, + ]; +} + +export default ExampleSorterGroup; + +declare global { + interface HTMLElementTagNameMap { + 'example-sorter-group': ExampleSorterGroup; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts new file mode 100644 index 0000000000..d9d9c81aa7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts @@ -0,0 +1,46 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, LitElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +@customElement('example-sorter-item') +export class ExampleSorterItem extends UmbElementMixin(LitElement) { + @property({ type: String, reflect: true }) + name: string = ''; + + @property({ type: Boolean, reflect: true, attribute: 'drag-placeholder' }) + umbDragPlaceholder = false; + + render() { + return html` + ${this.name} + + `; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--uui-size-layout-1); + border: 1px solid var(--uui-color-border); + border-radius: var(--uui-border-radius); + margin-bottom: 3px; + } + :host[drag-placeholder] { + opacity: 0.2; + } + `, + ]; +} + +export default ExampleSorterItem; + +declare global { + interface HTMLElementTagNameMap { + 'example-sorter-item': ExampleSorterItem; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 598ce08d76..5fcc94a136 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -69,7 +69,8 @@ type INTERNAL_UmbSorterConfig = { disabledItemSelector?: string; containerSelector: string; ignorerSelector: string; - placeholderClass: string; + placeholderClass?: string; + placeholderAttr?: string; draggableSelector?: string; boundarySelector?: string; dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void; @@ -102,11 +103,8 @@ type INTERNAL_UmbSorterConfig = { }; // External type with some properties optional, as they have defaults: -export type UmbSorterConfig = Omit< - INTERNAL_UmbSorterConfig, - 'placeholderClass' | 'ignorerSelector' | 'containerSelector' -> & - Partial, 'placeholderClass' | 'ignorerSelector' | 'containerSelector'>>; +export type UmbSorterConfig = Omit, 'ignorerSelector' | 'containerSelector'> & + Partial, 'ignorerSelector' | 'containerSelector'>>; /** * @export @@ -149,7 +147,7 @@ export class UmbSorterController implements UmbController { // Set defaults: config.ignorerSelector ??= 'a, img, iframe'; - config.placeholderClass ??= '--umb-sorter-placeholder'; + config.placeholderAttr ??= 'drag-placeholder'; this.#config = config as INTERNAL_UmbSorterConfig; host.addController(this); @@ -308,7 +306,12 @@ export class UmbSorterController implements UmbController { this.#rqaId = undefined; if (this.#currentElement) { this.#currentElement.style.transform = ''; - this.#currentElement.classList.add(this.#config.placeholderClass); + if (this.#config.placeholderClass) { + this.#currentElement.classList.add(this.#config.placeholderClass); + } + if (this.#config.placeholderAttr) { + this.#currentElement.setAttribute(this.#config.placeholderAttr, ''); + } } }); }; @@ -321,7 +324,12 @@ export class UmbSorterController implements UmbController { window.removeEventListener('dragover', this.#handleDragMove); window.removeEventListener('dragend', this.#handleDragEnd); this.#currentElement.style.transform = ''; - this.#currentElement.classList.remove(this.#config.placeholderClass); + if (this.#config.placeholderClass) { + this.#currentElement.classList.remove(this.#config.placeholderClass); + } + if (this.#config.placeholderAttr) { + this.#currentElement.removeAttribute(this.#config.placeholderAttr); + } this.#stopAutoScroll(); this.removeAllowIndication(); From 49eff2bd2dc9ef84177086d07f032f54aba87d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 13:46:24 +0100 Subject: [PATCH 08/47] test material --- .../examples/sorter-with-two-containers/sorter-group.ts | 2 +- .../examples/sorter-with-two-containers/sorter-item.ts | 3 ++- .../src/packages/core/sorter/sorter.controller.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 04d64ee479..1db8013490 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -74,7 +74,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
${repeat( this._items, - (item) => item.name, + (item) => item, (item) => html` diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts index d9d9c81aa7..2f22b1c4c4 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts @@ -14,6 +14,7 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) { render() { return html` ${this.name} + `; } @@ -30,7 +31,7 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) { border-radius: var(--uui-border-radius); margin-bottom: 3px; } - :host[drag-placeholder] { + :host([drag-placeholder]) { opacity: 0.2; } `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 5fcc94a136..50ae7b19ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -147,7 +147,9 @@ export class UmbSorterController implements UmbController { // Set defaults: config.ignorerSelector ??= 'a, img, iframe'; - config.placeholderAttr ??= 'drag-placeholder'; + if (!config.placeholderClass && !config.placeholderAttr) { + config.placeholderAttr = 'drag-placeholder'; + } this.#config = config as INTERNAL_UmbSorterConfig; host.addController(this); From 18057b75f7fd40ba6d4dd7ea1ac2bcc61c7a9e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 15:52:27 +0100 Subject: [PATCH 09/47] model binding refactor --- .../sorter-dashboard.ts | 41 +++++++++++-- .../sorter-group.ts | 52 +++++++--------- .../sorter-with-two-containers/sorter-item.ts | 2 +- .../packages/core/sorter/sorter.controller.ts | 61 ++++++++++--------- .../stories/test-sorter-controller.element.ts | 1 + 5 files changed, 92 insertions(+), 65 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index a1a799fbc5..73bc865020 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -1,13 +1,47 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, LitElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import type { ModelEntryType } from './sorter-group.js'; + import './sorter-group.js'; @customElement('example-sorter-dashboard') export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { + groupOneItems: ModelEntryType[] = [ + { + name: 'Apple', + }, + { + name: 'Banana', + }, + { + name: 'Pear', + }, + { + name: 'Pineapple', + }, + { + name: 'Lemon', + }, + ]; + + groupTwoItems: ModelEntryType[] = [ + { + name: 'DXP', + }, + { + name: 'H5YR', + }, + { + name: 'UUI', + }, + ]; + render() { return html` - - + +
+ +
`; } @@ -22,7 +56,6 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { .outer-wrapper { display: flex; - flex-direction: row; } `, ]; diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 1db8013490..9460dfe91a 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -1,20 +1,21 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, LitElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, LitElement, repeat, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import './sorter-item.js'; +import ExampleSorterItem from './sorter-item.js'; -type ModelEntryType = { +export type ModelEntryType = { name: string; }; -const SORTER_CONFIG: UmbSorterConfig = { - compareElementToModel: (element: HTMLElement, model: ModelEntryType) => { - return element.getAttribute('name') === model.name; +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element, model) => { + return element.name === model.name; }, - querySelectModelToElement: (container: HTMLElement, modelEntry: ModelEntryType) => { - return container.querySelector('name[' + modelEntry.name + ']'); + querySelectModelToElement: (container, modelEntry) => { + return container.querySelector("example-sorter-item[name='" + modelEntry.name + "']"); }, identifier: 'string-that-identifies-all-example-sorters', itemSelector: 'example-sorter-item', @@ -23,37 +24,28 @@ const SORTER_CONFIG: UmbSorterConfig = { @customElement('example-sorter-group') export class ExampleSorterGroup extends UmbElementMixin(LitElement) { - @state() - _items: ModelEntryType[] = [ - { - name: 'Apple', - }, - { - name: 'Banana', - }, - { - name: 'Pear', - }, - { - name: 'Pineapple', - }, - { - name: 'Lemon', - }, - ]; + @property({ type: Array, attribute: false }) + public get items(): ModelEntryType[] { + return this._items; + } + public set items(value: ModelEntryType[]) { + this._items = value; + this.#sorter.setModel(this._items); + } + private _items: ModelEntryType[] = []; - #sorter = new UmbSorterController(this, { + #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { this._items.splice(newIndex, 0, item); - console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); + //console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('_items'); return true; }, performItemRemove: ({ item }) => { const indexToMove = this._items.findIndex((x) => x.name === item.name); this._items.splice(indexToMove, 1); - console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); + //console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('_items'); return true; }, @@ -61,7 +53,6 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { constructor() { super(); - this.#sorter.setModel(this._items); } removeItem = (item: ModelEntryType) => { @@ -74,7 +65,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) {
${repeat( this._items, - (item) => item, + (item, index) => item.name + '_ ' + index, (item) => html` @@ -89,6 +80,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { css` :host { display: block; + width: 100%; } .sorter-placeholder { diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts index 2f22b1c4c4..38ad5d12d1 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts @@ -14,7 +14,7 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) { render() { return html` ${this.name} - + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 50ae7b19ee..d159bf7f9f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -61,9 +61,9 @@ function destroyPreventEvent(element: Element) { element.removeAttribute('draggable'); } -type INTERNAL_UmbSorterConfig = { - compareElementToModel: (el: HTMLElement, modelEntry: T) => boolean; - querySelectModelToElement: (container: HTMLElement, modelEntry: T) => HTMLElement | null; +type INTERNAL_UmbSorterConfig = { + compareElementToModel: (el: ElementType, modelEntry: T) => boolean; + querySelectModelToElement: (container: HTMLElement, modelEntry: T) => ElementType | null; identifier: string; itemSelector: string; disabledItemSelector?: string; @@ -74,14 +74,14 @@ type INTERNAL_UmbSorterConfig = { draggableSelector?: string; boundarySelector?: string; dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void; - onStart?: (argument: { item: T; element: HTMLElement }) => void; - onChange?: (argument: { item: T; element: HTMLElement }) => void; - onContainerChange?: (argument: { item: T; element: HTMLElement }) => void; - onEnd?: (argument: { item: T; element: HTMLElement }) => void; + onStart?: (argument: { item: T; element: ElementType }) => void; + onChange?: (argument: { item: T; element: ElementType }) => void; + onContainerChange?: (argument: { item: T; element: ElementType }) => void; + onEnd?: (argument: { item: T; element: ElementType }) => void; onSync?: (argument: { item: T; - fromController: UmbSorterController; - toController: UmbSorterController; + fromController: UmbSorterController; + toController: UmbSorterController; }) => void; itemHasNestedContainersResolver?: (element: HTMLElement) => boolean; onDisallowed?: () => void; @@ -91,9 +91,9 @@ type INTERNAL_UmbSorterConfig = { containerElement: Element; containerRect: DOMRect; item: T; - element: HTMLElement; + element: ElementType; elementRect: DOMRect; - relatedElement: HTMLElement; + relatedElement: ElementType; relatedRect: DOMRect; placeholderIsInThisRow: boolean; horizontalPlaceAfter: boolean; @@ -103,8 +103,11 @@ type INTERNAL_UmbSorterConfig = { }; // External type with some properties optional, as they have defaults: -export type UmbSorterConfig = Omit, 'ignorerSelector' | 'containerSelector'> & - Partial, 'ignorerSelector' | 'containerSelector'>>; +export type UmbSorterConfig = Omit< + INTERNAL_UmbSorterConfig, + 'ignorerSelector' | 'containerSelector' +> & + Partial, 'ignorerSelector' | 'containerSelector'>>; /** * @export @@ -112,9 +115,9 @@ export type UmbSorterConfig = Omit, 'ignorerSelec * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. */ -export class UmbSorterController implements UmbController { +export class UmbSorterController implements UmbController { #host; - #config: INTERNAL_UmbSorterConfig; + #config: INTERNAL_UmbSorterConfig; #observer; #model: Array = []; @@ -122,12 +125,12 @@ export class UmbSorterController implements UmbController { #containerElement!: HTMLElement; - #currentContainerCtrl: UmbSorterController = this; + #currentContainerCtrl: UmbSorterController = this; #currentContainerElement: Element | null = null; #useContainerShadowRoot?: boolean; #scrollElement?: Element | null; - #currentElement?: HTMLElement; + #currentElement?: ElementType; #currentDragElement?: Element; #currentDragRect?: DOMRect; #currentItem?: T | null; @@ -136,13 +139,13 @@ export class UmbSorterController implements UmbController { #dragX = 0; #dragY = 0; - #lastIndicationContainerCtrl: UmbSorterController | null = null; + #lastIndicationContainerCtrl: UmbSorterController | null = null; public get controllerAlias() { return this.#config.identifier; } - constructor(host: UmbControllerHostElement, config: UmbSorterConfig) { + constructor(host: UmbControllerHostElement, config: UmbSorterConfig) { this.#host = host; // Set defaults: @@ -151,7 +154,7 @@ export class UmbSorterController implements UmbController { config.placeholderAttr = 'drag-placeholder'; } - this.#config = config as INTERNAL_UmbSorterConfig; + this.#config = config as INTERNAL_UmbSorterConfig; host.addController(this); //this.#currentContainerElement = host; @@ -278,7 +281,7 @@ export class UmbSorterController implements UmbController { return; } - this.#currentElement = element as HTMLElement; + this.#currentElement = element as ElementType; this.#currentDragRect = this.#currentDragElement.getBoundingClientRect(); this.#currentItem = this.getItemOfElement(this.#currentElement); if (!this.#currentItem) { @@ -392,15 +395,13 @@ export class UmbSorterController implements UmbController { return; } - console.log('this.#currentElement', this.#currentElement); - const currentElementRect = this.#currentElement.getBoundingClientRect(); const insideCurrentRect = isWithinRect(this.#dragX, this.#dragY, currentElementRect); if (insideCurrentRect) { return; } - let toBeCurrentContainerCtrl: UmbSorterController | undefined = undefined; + let toBeCurrentContainerCtrl: UmbSorterController | undefined = undefined; // If we have a boundarySelector, try it. If we didn't get anything fall back to currentContainerElement: const currentBoundaryElement = @@ -602,17 +603,17 @@ export class UmbSorterController implements UmbController { } }; - async #moveElementTo(containerCtrl: UmbSorterController | undefined, newIndex: number) { + async #moveElementTo(containerCtrl: UmbSorterController | undefined, newIndex: number) { if (!this.#currentElement) { return; } - containerCtrl ??= this as UmbSorterController; + containerCtrl ??= this as UmbSorterController; // If same container and same index, do nothing: if (this.#currentContainerCtrl === containerCtrl && this.#currentIndex === newIndex) return; - if (await containerCtrl.updateModel(newIndex, this.#currentElement, this.#currentContainerCtrl)) { + if (await containerCtrl.moveItemInModel(newIndex, this.#currentElement, this.#currentContainerCtrl)) { this.#currentContainerCtrl = containerCtrl; this.#currentIndex = newIndex; } @@ -620,7 +621,7 @@ export class UmbSorterController implements UmbController { /** Management methods: */ - public getItemOfElement(element: HTMLElement) { + public getItemOfElement(element: ElementType) { if (!element) { return null; } @@ -648,7 +649,7 @@ export class UmbSorterController implements UmbController { return this.#model.filter((x) => x !== item).length > 0; } - public async updateModel(newIndex: number, element: HTMLElement, fromCtrl: UmbSorterController) { + public async moveItemInModel(newIndex: number, element: ElementType, fromCtrl: UmbSorterController) { const movingItem = fromCtrl.getItemOfElement(element); if (!movingItem) { console.error('Could not find item of sync item'); @@ -687,7 +688,7 @@ export class UmbSorterController implements UmbController { return true; } - updateAllowIndication(controller: UmbSorterController, item: T) { + updateAllowIndication(controller: UmbSorterController, item: T) { // Remove old indication: if (this.#lastIndicationContainerCtrl !== null && this.#lastIndicationContainerCtrl !== controller) { this.#lastIndicationContainerCtrl.notifyAllowed(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts index 968dec1b9b..d01ba962c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts @@ -12,6 +12,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-sort-entry-id') === model.id; }, querySelectModelToElement: (container: HTMLElement, modelEntry: SortEntryType) => { + // TODO: This selector does not make sense: return container.querySelector('data-sort-entry-id=[' + modelEntry.id + ']'); }, identifier: 'test-sorter', From cf95cf38ee2c9673171a1a48530590f41b995685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 15:53:31 +0100 Subject: [PATCH 10/47] enable seond group --- .../examples/sorter-with-two-containers/sorter-dashboard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 73bc865020..7747317257 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -41,6 +41,7 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) {
+
`; From ba53fb876f796a6801b4186b894546643f804235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 18:16:32 +0100 Subject: [PATCH 11/47] refactoring --- .../sorter-group.ts | 2 +- .../sorter-with-two-containers/sorter-item.ts | 2 +- .../packages/core/sorter/sorter.controller.ts | 49 +++++++++++++------ 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 9460dfe91a..23ff6e433a 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -1,5 +1,5 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, LitElement, repeat, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, LitElement, repeat, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts index 38ad5d12d1..495370bb00 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-item.ts @@ -14,7 +14,7 @@ export class ExampleSorterItem extends UmbElementMixin(LitElement) { render() { return html` ${this.name} - + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index d159bf7f9f..e6ea2ff7e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -212,7 +212,7 @@ export class UmbSorterController { if (child.matches && child.matches(this.#config.itemSelector)) { - this.setupItem(child as HTMLElement); + this.setupItem(child as ElementType); } }); this.#observer.observe(containerElement, { @@ -230,7 +230,7 @@ export class UmbSorterController { + console.log('#drag start!'); + if (this.#currentElement) { this.#handleDragEnd(); } @@ -302,26 +329,23 @@ export class UmbSorterController { - // It should be okay to use the same rqaId, as the move does not or is okay not to happen on first frame/drag-move. + // It should be okay to use the same rqaId, as the move does not, or is okay not, to happen on first frame/drag-move. this.#rqaId = undefined; if (this.#currentElement) { this.#currentElement.style.transform = ''; - if (this.#config.placeholderClass) { - this.#currentElement.classList.add(this.#config.placeholderClass); - } - if (this.#config.placeholderAttr) { - this.#currentElement.setAttribute(this.#config.placeholderAttr, ''); - } + this.#setupPlaceholderStyle(); } }); }; #handleDragEnd = async () => { + console.log('#drag end!'); if (!this.#currentElement || !this.#currentItem) { return; } @@ -329,12 +353,7 @@ export class UmbSorterController Date: Fri, 12 Jan 2024 19:09:22 +0100 Subject: [PATCH 12/47] refactors --- .../sorter-group.ts | 10 +-- .../packages/core/sorter/sorter.controller.ts | 71 ++++++++++--------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 23ff6e433a..21cf7bfeec 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -37,16 +37,18 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { + const oldValue = [...this._items]; this._items.splice(newIndex, 0, item); //console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('_items'); + this.requestUpdate('items', oldValue); return true; }, performItemRemove: ({ item }) => { + const oldValue = [...this._items]; const indexToMove = this._items.findIndex((x) => x.name === item.name); this._items.splice(indexToMove, 1); //console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('_items'); + this.requestUpdate('items', oldValue); return true; }, }); @@ -64,8 +66,8 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { return html`
${repeat( - this._items, - (item, index) => item.name + '_ ' + index, + this.items, + (item) => item.name, (item) => html` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index e6ea2ff7e0..4a37a14a0d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -163,12 +163,12 @@ export class UmbSorterController { mutation.addedNodes.forEach((addedNode) => { if ((addedNode as HTMLElement).matches && (addedNode as HTMLElement).matches(this.#config.itemSelector)) { - this.setupItem(addedNode as HTMLElement); + this.setupItem(addedNode as ElementType); } }); mutation.removedNodes.forEach((removedNode) => { if ((removedNode as HTMLElement).matches && (removedNode as HTMLElement).matches(this.#config.itemSelector)) { - this.destroyItem(removedNode as HTMLElement); + this.destroyItem(removedNode as ElementType); } }); }); @@ -242,9 +242,8 @@ export class UmbSorterController { - console.log('#drag start!'); - - if (this.#currentElement) { - this.#handleDragEnd(); - } - - event.stopPropagation(); - if (event.dataTransfer) { - event.dataTransfer.effectAllowed = 'move'; // copyMove when we enhance the drag with clipboard data. - event.dataTransfer.dropEffect = 'none'; // visual feedback when dropped. - } - - if (!this.#scrollElement) { - this.#scrollElement = getParentScrollElement(this.#containerElement, true); - } - - const element = (event.target as HTMLElement).closest(this.#config.itemSelector); - - if (!element) return; + #setCurrentElement(element: ElementType) { + this.#currentElement = element; this.#currentDragElement = this.#config.draggableSelector ? element.querySelector(this.#config.draggableSelector) ?? undefined @@ -308,9 +289,33 @@ export class UmbSorterController { + const element = (event.target as HTMLElement).closest(this.#config.itemSelector); + if (!element) return; + + console.log('#drag start!'); + + if (this.#currentElement && this.#currentElement !== element) { + console.log('#drag start, calls END!!!!!'); + this.#handleDragEnd(); + } + + event.stopPropagation(); + if (event.dataTransfer) { + event.dataTransfer.effectAllowed = 'move'; // copyMove when we enhance the drag with clipboard data. + event.dataTransfer.dropEffect = 'none'; // visual feedback when dropped. + } + + if (!this.#scrollElement) { + this.#scrollElement = getParentScrollElement(this.#containerElement, true); + } + + this.#setCurrentElement(element as ElementType); + this.#currentDragRect = this.#currentDragElement?.getBoundingClientRect(); + this.#currentItem = this.getItemOfElement(this.#currentElement!); if (!this.#currentItem) { console.error('Could not find item related to this element.'); return; @@ -319,17 +324,16 @@ export class UmbSorterController { console.log('#drag end!'); + window.removeEventListener('dragover', this.#handleDragMove); + window.removeEventListener('dragend', this.#handleDragEnd); + if (!this.#currentElement || !this.#currentItem) { return; } - window.removeEventListener('dragover', this.#handleDragMove); - window.removeEventListener('dragend', this.#handleDragEnd); this.#currentElement.style.transform = ''; this.#removePlaceholderStyle(); From def68e986c6e6b541810e77d0ec9387dea2a9f0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 19:20:47 +0100 Subject: [PATCH 13/47] one method for local modes --- .../sorter-group.ts | 17 ++++++-- .../packages/core/sorter/sorter.controller.ts | 42 ++++++++++++++----- 2 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 21cf7bfeec..ce036e017b 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -37,20 +37,31 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { - const oldValue = [...this._items]; - this._items.splice(newIndex, 0, item); + const oldValue = this._items; + this.items = [...this._items]; + this.items.splice(newIndex, 0, item); //console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('items', oldValue); return true; }, performItemRemove: ({ item }) => { - const oldValue = [...this._items]; + const oldValue = this._items; const indexToMove = this._items.findIndex((x) => x.name === item.name); + this._items = [...this._items]; this._items.splice(indexToMove, 1); //console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); this.requestUpdate('items', oldValue); return true; }, + performItemMove: ({ item, newIndex }) => { + const oldValue = this._items; + const indexToMove = this._items.findIndex((x) => x.name === item.name); + this._items = [...this._items]; + this._items.splice(indexToMove, 1); + this._items.splice(newIndex, 0, item); + this.requestUpdate('items', oldValue); + return true; + }, }); constructor() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 4a37a14a0d..cd243c90da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -98,6 +98,7 @@ type INTERNAL_UmbSorterConfig = { placeholderIsInThisRow: boolean; horizontalPlaceAfter: boolean; }) => void; + performItemMove?: (argument: { item: T; newIndex: number }) => Promise | boolean; performItemInsert?: (argument: { item: T; newIndex: number }) => Promise | boolean; performItemRemove?: (argument: { item: T }) => Promise | boolean; }; @@ -682,29 +683,48 @@ export class UmbSorterController= movingItemIndex) { newIndex--; } - if (this.#config.performItemInsert) { - const result = await this.#config.performItemInsert({ item: movingItem, newIndex }); - if (result === false) { - return false; + if (localMove) { + // Local move: + + if (this.#config.performItemMove) { + const result = await this.#config.performItemMove({ item: movingItem, newIndex }); + if (result === false) { + return false; + } + } else { + throw new Error('performItemMove must be configured, until default fallback method is made.'); + //this.#model.splice(movingItemIndex, 1) + //this.#model.splice(newIndex, 0, movingItem); } } else { - this.#model.splice(newIndex, 0, movingItem); + // Not a local move: + + if ((await fromCtrl.removeItem(movingItem)) !== true) { + console.error('Sync could not remove item'); + return false; + } + + if (this.#config.performItemInsert) { + const result = await this.#config.performItemInsert({ item: movingItem, newIndex }); + if (result === false) { + return false; + } + } else { + this.#model.splice(newIndex, 0, movingItem); + } } const eventData = { item: movingItem, fromController: fromCtrl, toController: this }; - if (fromCtrl !== this) { + if (!localMove) { fromCtrl.notifySync(eventData); } this.notifySync(eventData); From e71a2b7bc14d55a2f2a4414c480e3f00d95c2656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 19:40:31 +0100 Subject: [PATCH 14/47] working! --- .../sorter-group.ts | 35 +++++++++++-------- .../packages/core/sorter/sorter.controller.ts | 28 +++++++++------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index ce036e017b..79d90f3cab 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -38,28 +38,34 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { ...SORTER_CONFIG, performItemInsert: ({ item, newIndex }) => { const oldValue = this._items; - this.items = [...this._items]; - this.items.splice(newIndex, 0, item); //console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('items', oldValue); + const newItems = [...this._items]; + newItems.splice(newIndex, 0, item); + this.items = newItems; + this.requestUpdate('_items', oldValue); return true; }, performItemRemove: ({ item }) => { const oldValue = this._items; - const indexToMove = this._items.findIndex((x) => x.name === item.name); - this._items = [...this._items]; - this._items.splice(indexToMove, 1); //console.log('removed', item.name, 'at', indexToMove, ' ', this._items.map((x) => x.name).join(', ')); - this.requestUpdate('items', oldValue); + const indexToMove = this._items.findIndex((x) => x.name === item.name); + const newItems = [...this._items]; + newItems.splice(indexToMove, 1); + this.items = newItems; + this.requestUpdate('_items', oldValue); return true; }, - performItemMove: ({ item, newIndex }) => { + performItemMove: ({ item, newIndex, oldIndex }) => { const oldValue = this._items; - const indexToMove = this._items.findIndex((x) => x.name === item.name); - this._items = [...this._items]; - this._items.splice(indexToMove, 1); - this._items.splice(newIndex, 0, item); - this.requestUpdate('items', oldValue); + //console.log('move', item.name, 'from', oldIndex, 'to', newIndex, ' ', this._items.map((x) => x.name).join(', ')); + const newItems = [...this._items]; + newItems.splice(oldIndex, 1); + if (oldIndex <= newIndex) { + newIndex--; + } + newItems.splice(newIndex, 0, item); + this.items = newItems; + this.requestUpdate('_items', oldValue); return true; }, }); @@ -69,8 +75,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { } removeItem = (item: ModelEntryType) => { - this._items = this._items.filter((r) => r.name !== item.name); - this.#sorter.setModel(this._items); + this.items = this._items.filter((r) => r.name !== item.name); }; render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index cd243c90da..b4b26f69de 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -243,8 +243,10 @@ export class UmbSorterController= movingItemIndex) { - newIndex--; - } - if (localMove) { // Local move: + // TODO: Maybe this should be replaceable/configurable: + const oldIndex = this.#model.indexOf(movingItem); + if (this.#config.performItemMove) { - const result = await this.#config.performItemMove({ item: movingItem, newIndex }); + const result = await this.#config.performItemMove({ item: movingItem, newIndex, oldIndex }); if (result === false) { return false; } } else { throw new Error('performItemMove must be configured, until default fallback method is made.'); - //this.#model.splice(movingItemIndex, 1) - //this.#model.splice(newIndex, 0, movingItem); + this.#model.splice(oldIndex, 1); + if (oldIndex <= newIndex) { + newIndex--; + } + this.#model.splice(newIndex, 0, movingItem); + + this.#config.modelChangedCallback?.(this.#model); } } else { // Not a local move: @@ -720,6 +725,7 @@ export class UmbSorterController Date: Fri, 12 Jan 2024 19:51:15 +0100 Subject: [PATCH 15/47] way more solid now --- .../sorter-group.ts | 11 ++--- .../packages/core/sorter/sorter.controller.ts | 40 +++++++------------ 2 files changed, 20 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 79d90f3cab..f62a5f0410 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -36,7 +36,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, - performItemInsert: ({ item, newIndex }) => { + /*performItemInsert: ({ item, newIndex }) => { const oldValue = this._items; //console.log('inserted', item.name, 'at', newIndex, ' ', this._items.map((x) => x.name).join(', ')); const newItems = [...this._items]; @@ -67,13 +67,14 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { this.items = newItems; this.requestUpdate('_items', oldValue); return true; + },*/ + onChange: (newModel) => { + const oldValue = this._items; + this.items = newModel; + this.requestUpdate('_items', oldValue); }, }); - constructor() { - super(); - } - removeItem = (item: ModelEntryType) => { this.items = this._items.filter((r) => r.name !== item.name); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index b4b26f69de..65ce46da5b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -75,14 +75,9 @@ type INTERNAL_UmbSorterConfig = { boundarySelector?: string; dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void; onStart?: (argument: { item: T; element: ElementType }) => void; - onChange?: (argument: { item: T; element: ElementType }) => void; + onChange?: (argument: { item: T; element: ElementType; model: Array }) => void; onContainerChange?: (argument: { item: T; element: ElementType }) => void; onEnd?: (argument: { item: T; element: ElementType }) => void; - onSync?: (argument: { - item: T; - fromController: UmbSorterController; - toController: UmbSorterController; - }) => void; itemHasNestedContainersResolver?: (element: HTMLElement) => boolean; onDisallowed?: () => void; onAllowed?: () => void; @@ -665,8 +660,10 @@ export class UmbSorterController Date: Fri, 12 Jan 2024 20:11:12 +0100 Subject: [PATCH 16/47] refactor --- .../sorter-group.ts | 4 +-- .../packages/core/sorter/sorter.controller.ts | 35 ++++++++----------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index f62a5f0410..70c077ee6f 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -68,9 +68,9 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { this.requestUpdate('_items', oldValue); return true; },*/ - onChange: (newModel) => { + onChange: ({ model }) => { const oldValue = this._items; - this.items = newModel; + this.items = model; this.requestUpdate('_items', oldValue); }, }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 65ce46da5b..f6c49d2869 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -75,7 +75,7 @@ type INTERNAL_UmbSorterConfig = { boundarySelector?: string; dataTransferResolver?: (dataTransfer: DataTransfer | null, currentItem: T) => void; onStart?: (argument: { item: T; element: ElementType }) => void; - onChange?: (argument: { item: T; element: ElementType; model: Array }) => void; + onChange?: (argument: { item: T; model: Array }) => void; onContainerChange?: (argument: { item: T; element: ElementType }) => void; onEnd?: (argument: { item: T; element: ElementType }) => void; itemHasNestedContainersResolver?: (element: HTMLElement) => boolean; @@ -93,7 +93,7 @@ type INTERNAL_UmbSorterConfig = { placeholderIsInThisRow: boolean; horizontalPlaceAfter: boolean; }) => void; - performItemMove?: (argument: { item: T; newIndex: number }) => Promise | boolean; + performItemMove?: (argument: { item: T; newIndex: number; oldIndex: number }) => Promise | boolean; performItemInsert?: (argument: { item: T; newIndex: number }) => Promise | boolean; performItemRemove?: (argument: { item: T }) => Promise | boolean; }; @@ -262,7 +262,6 @@ export class UmbSorterController { - console.log('#drag end!'); window.removeEventListener('dragover', this.#handleDragMove); window.removeEventListener('dragend', this.#handleDragEnd); @@ -663,24 +658,24 @@ export class UmbSorterController x !== item).length > 0; } public async moveItemInModel(newIndex: number, element: ElementType, fromCtrl: UmbSorterController) { - const movingItem = fromCtrl.getItemOfElement(element); - if (!movingItem) { + const item = fromCtrl.getItemOfElement(element); + if (!item) { console.error('Could not find item of sync item'); return false; } - if (this.notifyRequestDrop({ item: movingItem }) === false) { + if (this.notifyRequestDrop({ item }) === false) { return false; } @@ -690,10 +685,10 @@ export class UmbSorterController Date: Fri, 12 Jan 2024 20:12:26 +0100 Subject: [PATCH 17/47] clean up, works well at this stage --- .../examples/sorter-with-two-containers/sorter-group.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts index 70c077ee6f..4ce3125463 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-group.ts @@ -70,7 +70,7 @@ export class ExampleSorterGroup extends UmbElementMixin(LitElement) { },*/ onChange: ({ model }) => { const oldValue = this._items; - this.items = model; + this._items = model; this.requestUpdate('_items', oldValue); }, }); From f042153a50b02a75792b23cfa9abd87ae743c165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 20:43:47 +0100 Subject: [PATCH 18/47] work from start --- .../packages/core/sorter/sorter.controller.ts | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index f6c49d2869..2f2b73d6ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -129,7 +129,7 @@ export class UmbSorterController; host.addController(this); - //this.#currentContainerElement = host; - this.#observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((addedNode) => { @@ -189,7 +187,7 @@ export class UmbSorterController this.#config.compareElementToModel(element, entry)); } From 714f0bbe84b6e06acdc24bcf6a10ded9f68d052f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 21:32:17 +0100 Subject: [PATCH 19/47] clean up --- .../src/packages/core/sorter/sorter.controller.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 2f2b73d6ac..8c4923b9f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -436,7 +436,6 @@ export class UmbSorterController Date: Mon, 15 Jan 2024 09:19:25 +0100 Subject: [PATCH 20/47] add notice on what is supported --- .../examples/sorter-with-two-containers/README.md | 4 ++-- .../examples/sorter-with-two-containers/sorter-dashboard.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md index 4a1b15255a..3dc57f3788 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/README.md @@ -1,5 +1,5 @@ # Property Dataset Dashboard Example -This example demonstrates the essence of the Property Dataset. +This example demonstrates the how to setup the Sorter. -This dashboard implements such, to display a few selected Property Editors and bind the data back to the Dashboard. +This example can still NOT sort between two groups. This will come later. diff --git a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts index 7747317257..0e88318ab3 100644 --- a/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts +++ b/src/Umbraco.Web.UI.Client/examples/sorter-with-two-containers/sorter-dashboard.ts @@ -40,6 +40,7 @@ export class ExampleSorterDashboard extends UmbElementMixin(LitElement) { return html`
+
Notice this example still only support single group of Sorter.
From f69394fb7cb077a893bf60464c7037b739ffbfaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 15 Jan 2024 09:29:36 +0100 Subject: [PATCH 21/47] implement multiple-color-picker-input --- .../multiple-color-picker-input.element.ts | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts index 044004b46e..36ab21138a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts @@ -21,7 +21,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-sort-entry-id') === model.value; }, querySelectModelToElement: (container: HTMLElement, modelEntry: UmbSwatchDetails) => { - return container.querySelector('data-sort-entry-id=[' + modelEntry.value + ']'); + return container.querySelector('[data-sort-entry-id=' + modelEntry.value + ']'); }, identifier: 'Umb.SorterIdentifier.ColorEditor', itemSelector: 'umb-multiple-color-picker-item-input', @@ -35,21 +35,10 @@ const SORTER_CONFIG: UmbSorterConfig = { export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitElement) { #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, - - performItemInsert: (args) => { - const frozenArray = [...this.items]; - const indexToMove = frozenArray.findIndex((x) => x.value === args.item.value); - - frozenArray.splice(indexToMove, 1); - frozenArray.splice(args.newIndex, 0, args.item); - this.items = frozenArray; - - this.dispatchEvent(new UmbChangeEvent()); - - return true; - }, - performItemRemove: (args) => { - return true; + onChange: ({ model }) => { + const oldValue = this._items; + this._items = model; + this.requestUpdate('_items', oldValue); }, }); @@ -150,7 +139,7 @@ export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitE } #onAdd() { - this._items = [...this._items, { value: '', label: '' }]; + this.items = [...this._items, { value: '', label: '' }]; this.pristine = false; this.dispatchEvent(new UmbChangeEvent()); this.#focusNewItem(); @@ -180,7 +169,7 @@ export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitE #deleteItem(event: UmbDeleteEvent, itemIndex: number) { event.stopPropagation(); - this._items = this._items.filter((item, index) => index !== itemIndex); + this.items = this._items.filter((item, index) => index !== itemIndex); this.pristine = false; this.dispatchEvent(new UmbChangeEvent()); } From 775ceee4b18a5a4dc378bec1be197ae4a767d3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 15 Jan 2024 10:34:52 +0100 Subject: [PATCH 22/47] re-visit implementations --- .../input-multiple-text-string.element.ts | 21 +++++-------------- .../core/sorter/stories/sorter-controller.mdx | 16 +++++++++----- .../stories/test-sorter-controller.element.ts | 13 +++++++++--- ...-workspace-view-edit-properties.element.ts | 16 +++++++++++++- ...nt-type-workspace-view-edit-tab.element.ts | 1 + ...cument-type-workspace-view-edit.element.ts | 1 + ...-workspace-view-edit-properties.element.ts | 15 ++++++++++++- ...ia-type-workspace-view-edit-tab.element.ts | 1 + .../media-type-workspace-view-edit.element.ts | 1 + ...workspace-view-rich-text-editor.element.ts | 11 +++++++++- 10 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts index 88a9c656f2..3697da0646 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string.element.ts @@ -16,7 +16,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-sort-entry-id') === model.value; }, querySelectModelToElement: (container: HTMLElement, modelEntry: MultipleTextStringValueItem) => { - return container.querySelector('data-sort-entry-id=[' + modelEntry.value + ']'); + return container.querySelector('[data-sort-entry-id=' + modelEntry.value + ']'); }, identifier: 'Umb.SorterIdentifier.ColorEditor', itemSelector: 'umb-input-multiple-text-string-item', @@ -30,21 +30,10 @@ const SORTER_CONFIG: UmbSorterConfig = { export class UmbInputMultipleTextStringElement extends FormControlMixin(UmbLitElement) { #prevalueSorter = new UmbSorterController(this, { ...SORTER_CONFIG, - - performItemInsert: (args) => { - const frozenArray = [...this.items]; - const indexToMove = frozenArray.findIndex((x) => x.value === args.item.value); - - frozenArray.splice(indexToMove, 1); - frozenArray.splice(args.newIndex, 0, args.item); - this.items = frozenArray; - - this.dispatchEvent(new UmbChangeEvent()); - - return true; - }, - performItemRemove: (args) => { - return true; + onChange: ({ model }) => { + const oldValue = this._items; + this._items = model; + this.requestUpdate('_items', oldValue); }, }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx index a9b5072380..496f9061ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/sorter-controller.mdx @@ -51,20 +51,20 @@ The configuration has a lot of optional options, but the required ones are: - itemSelector - containerSelector -It can be set up as following: +It can be set up as follows: ```typescript import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; -type MySortEntryType = {...} -const awesomeModel: Array = [...] +type MySortEntryType = {...}; +const awesomeModel: Array = [...]; const MY_SORTER_CONFIG: UmbSorterConfig = { compareElementToModel: (element: HTMLElement, model: MySortEntryType) => { return element.getAttribute('data-sort-entry-id') === model.id; }, querySelectModelToElement: (container: HTMLElement, modelEntry: MySortEntryType) => { - return container.querySelector('data-sort-entry-id=[' + modelEntry.id + ']'); + return container.querySelector('[data-sort-entry-id=' + modelEntry.id + ']'); }, identifier: 'test-sorter', itemSelector: 'li', @@ -101,7 +101,13 @@ const MY_SORTER_CONFIG: UmbSorterConfig = {...} export class MyElement extends UmbElementMixin(LitElement) { - #sorter = new UmbSorterController(this, MY_SORTER_CONFIG); + #sorter = new UmbSorterController(this, {...MY_SORTER_CONFIG, + onChange: ({ model }) => { + const oldValue = this.awesomeModel; + this.awesomeModel = model; + this.requestUpdate('awesomeModel', oldValue); + }, + }); constructor() { this.#sorter.setModel(awesomeModel); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts index d01ba962c5..4c2864dd6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/stories/test-sorter-controller.element.ts @@ -12,8 +12,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-sort-entry-id') === model.id; }, querySelectModelToElement: (container: HTMLElement, modelEntry: SortEntryType) => { - // TODO: This selector does not make sense: - return container.querySelector('data-sort-entry-id=[' + modelEntry.id + ']'); + return container.querySelector('[data-sort-entry-id=' + modelEntry.id + ']'); }, identifier: 'test-sorter', itemSelector: 'li', @@ -41,6 +40,9 @@ export default class UmbTestSorterControllerElement extends UmbLitElement { @state() private vertical = true; + @state() + private _items: Array = [...model]; + constructor() { super(); this.sorter = new UmbSorterController(this, { @@ -48,6 +50,11 @@ export default class UmbTestSorterControllerElement extends UmbLitElement { resolveVerticalDirection: () => { this.vertical ? true : false; }, + onChange: ({ model }) => { + const oldValue = this._items; + this._items = model; + this.requestUpdate('_items', oldValue); + }, }); this.sorter.setModel(model); } @@ -62,7 +69,7 @@ export default class UmbTestSorterControllerElement extends UmbLitElement { Horizontal/Vertical
    - ${model.map( + ${this._items.map( (entry) => html`
  • ${entry.value} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts index c5f2fd9dcd..fbfee1c86f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts @@ -18,7 +18,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-umb-property-id') === model.id; }, querySelectModelToElement: (container: HTMLElement, modelEntry: DocumentTypePropertyTypeResponseModel) => { - return container.querySelector('data-umb-property-id=[' + modelEntry.id + ']'); + return container.querySelector('[data-umb-property-id=' + modelEntry.id + ']'); }, identifier: 'content-type-property-sorter', itemSelector: '[data-umb-property-id]', @@ -45,6 +45,19 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle performItemRemove: (args) => { return this._propertyStructureHelper.removeProperty(args.item.id!); }, + performItemMove: (args) => { + this._propertyStructureHelper.removeProperty(args.item.id!); + let sortOrder = 0; + if (this._propertyStructure.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._propertyStructure[0].sortOrder ?? 0) - 1; + } else { + sortOrder = + (this._propertyStructure[Math.min(args.newIndex, this._propertyStructure.length - 1)].sortOrder ?? 0) + 1; + } + } + return this._propertyStructureHelper.insertProperty(args.item, sortOrder); + }, }); private _containerId: string | undefined; @@ -134,6 +147,7 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle if (isSorting) { this.#propertySorter.setModel(this._propertyStructure); } else { + // TODO: Make a more proper way to disable sorting: this.#propertySorter.setModel([]); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts index 99c81ac4b4..5ab781f63d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts @@ -28,6 +28,7 @@ export class UmbDocumentTypeWorkspaceViewEditTabElement extends UmbLitElement { config: UmbSorterConfig = { ...SORTER_CONFIG, + // TODO: Missing handlers to work properly: performItemMove and performItemRemove performItemInsert: async (args) => { if (!this._groups) return false; const oldIndex = this._groups.findIndex((group) => group.id! === args.item.id); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index 0b5156443f..c6a496ad70 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -38,6 +38,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple config: UmbSorterConfig = { ...SORTER_CONFIG, + // TODO: Missing handlers to work properly: performItemMove and performItemRemove performItemInsert: async (args) => { if (!this._tabs) return false; const oldIndex = this._tabs.findIndex((tab) => tab.id! === args.item.id); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts index 1e4c45984b..84dd842b41 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-properties.element.ts @@ -18,7 +18,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-umb-property-id') === model.id; }, querySelectModelToElement: (container: HTMLElement, modelEntry: MediaTypePropertyTypeResponseModel) => { - return container.querySelector('data-umb-property-id=[' + modelEntry.id + ']'); + return container.querySelector('[data-umb-property-id=' + modelEntry.id + ']'); }, identifier: 'content-type-property-sorter', itemSelector: '[data-umb-property-id]', @@ -45,6 +45,19 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen performItemRemove: (args) => { return this._propertyStructureHelper.removeProperty(args.item.id!); }, + performItemMove: (args) => { + this._propertyStructureHelper.removeProperty(args.item.id!); + let sortOrder = 0; + if (this._propertyStructure.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._propertyStructure[0].sortOrder ?? 0) - 1; + } else { + sortOrder = + (this._propertyStructure[Math.min(args.newIndex, this._propertyStructure.length - 1)].sortOrder ?? 0) + 1; + } + } + return this._propertyStructureHelper.insertProperty(args.item, sortOrder); + }, }); private _containerId: string | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts index fd56eccc7e..8fd560c1fc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit-tab.element.ts @@ -28,6 +28,7 @@ export class UmbMediaTypeWorkspaceViewEditTabElement extends UmbLitElement { config: UmbSorterConfig = { ...SORTER_CONFIG, + // TODO: Missing handlers to work properly: performItemMove and performItemRemove performItemInsert: async (args) => { if (!this._groups) return false; const oldIndex = this._groups.findIndex((group) => group.id! === args.item.id); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts index 13534b91be..86b41a2c33 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/workspace/views/design/media-type-workspace-view-edit.element.ts @@ -38,6 +38,7 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen config: UmbSorterConfig = { ...SORTER_CONFIG, + // TODO: Missing handlers for these: performItemRemove, performItemMove performItemInsert: async (args) => { if (!this._tabs) return false; const oldIndex = this._tabs.findIndex((tab) => tab.id! === args.item.id); diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/views/rich-text-editor/stylesheet-workspace-view-rich-text-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/views/rich-text-editor/stylesheet-workspace-view-rich-text-editor.element.ts index 005410cbcb..64908209ae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/views/rich-text-editor/stylesheet-workspace-view-rich-text-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/workspace/views/rich-text-editor/stylesheet-workspace-view-rich-text-editor.element.ts @@ -27,7 +27,7 @@ const SORTER_CONFIG: UmbSorterConfig = { return element.getAttribute('data-umb-rule-name') === model.name; }, querySelectModelToElement: (container: HTMLElement, modelEntry: RichTextRuleModel) => { - return container.querySelector('data-umb-rule-name[' + modelEntry.name + ']'); + return container.querySelector('[data-umb-rule-name' + modelEntry.name + ']'); }, identifier: 'stylesheet-rules-sorter', itemSelector: 'umb-stylesheet-rich-text-editor-rule', @@ -44,6 +44,8 @@ export class UmbStylesheetWorkspaceViewRichTextEditorElement extends UmbLitEleme #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, + // TODO: Implement correctly, this code below was not correct: + /* performItemInsert: ({ item, newIndex }) => { return this.#context?.findNewSortOrder(item, newIndex) ?? false; }, @@ -51,6 +53,13 @@ export class UmbStylesheetWorkspaceViewRichTextEditorElement extends UmbLitEleme //defined so the default does not run return true; }, + */ + // End of todo comment. + onChange: ({ model }) => { + const oldValue = this._rules; + this._rules = model; + this.requestUpdate('_rules', oldValue); + }, }); constructor() { From c4e7c44427059787b7aee0e5de53cede2d3d1ae4 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:43:44 +0100 Subject: [PATCH 23/47] changes to make build work on the server --- .../core/components/input-slider/input-slider.element.ts | 2 +- src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts index 0e4883c957..473e4d8498 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts @@ -28,7 +28,7 @@ export class UmbInputSliderElement extends FormControlMixin(UmbLitElement) { #onChange(e: UUISliderEvent) { e.stopPropagation(); - super.value = e.target.value; + this.value = e.target.value; this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts index 90b526bc17..fe13f65f3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts @@ -1,7 +1,5 @@ -import packageJson from '../../../../package.json'; - export const umbMeta = { name: 'Bellissima', - clientName: packageJson.name, - clientVersion: packageJson.version, + clientName: 'Umbraco.CMS.Backoffice', + clientVersion: '#CLIENTVERSION#', // will be replaced by the build script }; From 1fd0a51cdded1366997f8eed75caee12767e9478 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:56:17 +0100 Subject: [PATCH 24/47] remove the 'meta' module as it does not work as intended on the server --- src/Umbraco.Web.UI.Client/package.json | 1 - .../core/components/input-tiny-mce/input-tiny-mce.element.ts | 3 --- src/Umbraco.Web.UI.Client/src/packages/core/index.ts | 1 - src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts | 5 ----- src/Umbraco.Web.UI.Client/tsconfig.json | 1 - src/Umbraco.Web.UI.Client/web-test-runner.config.mjs | 1 - 6 files changed, 12 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 1f79b1c58d..f3074ff40a 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -33,7 +33,6 @@ "./localization": "./dist-cms/packages/core/localization/index.js", "./macro": "./dist-cms/packages/core/macro/index.js", "./menu": "./dist-cms/packages/core/menu/index.js", - "./meta": "./dist-cms/packages/core/meta/index.js", "./modal": "./dist-cms/packages/core/modal/index.js", "./notification": "./dist-cms/packages/core/notification/index.js", "./picker-input": "./dist-cms/packages/core/picker-input/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index a429861425..7ef6f439f7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -2,7 +2,6 @@ import { defaultFallbackConfig } from './input-tiny-mce.defaults.js'; import { pastePreProcessHandler } from './input-tiny-mce.handlers.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; -import { umbMeta } from '@umbraco-cms/backoffice/meta'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { type Editor, type RawEditorOptions, renderEditor } from '@umbraco-cms/backoffice/external/tinymce'; import { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; @@ -185,8 +184,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { this._tinyConfig = { autoresize_bottom_margin: 10, body_class: 'umb-rte', - //see https://www.tiny.cloud/docs/tinymce/6/editor-important-options/#cache_suffix - cache_suffix: `?umb__rnd=${umbMeta.clientVersion}`, contextMenu: false, inline_boundaries_selector: 'a[href],code,.mce-annotation,.umb-embed-holder,.umb-macro-holder', menubar: false, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts index fa1cd94e7f..e0e9907a0f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts @@ -29,7 +29,6 @@ export * from './extension-registry/index.js'; export * from './id/index.js'; export * from './macro/index.js'; export * from './menu/index.js'; -export * from './meta/index.js'; export * from './modal/index.js'; export * from './notification/index.js'; export * from './picker-input/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts deleted file mode 100644 index fe13f65f3e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/meta/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const umbMeta = { - name: 'Bellissima', - clientName: 'Umbraco.CMS.Backoffice', - clientVersion: '#CLIENTVERSION#', // will be replaced by the build script -}; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index f5dadc571b..93e0cfd9d6 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -73,7 +73,6 @@ "@umbraco-cms/backoffice/localization": ["src/packages/core/localization"], "@umbraco-cms/backoffice/macro": ["src/packages/core/macro"], "@umbraco-cms/backoffice/menu": ["src/packages/core/menu"], - "@umbraco-cms/backoffice/meta": ["src/packages/core/meta"], "@umbraco-cms/backoffice/modal": ["src/packages/core/modal"], "@umbraco-cms/backoffice/notification": ["src/packages/core/notification"], "@umbraco-cms/backoffice/picker-input": ["src/packages/core/picker-input"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 23d5ce5960..0b6264800e 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -73,7 +73,6 @@ export default { '@umbraco-cms/backoffice/localization': './src/packages/core/localization/index.ts', '@umbraco-cms/backoffice/macro': './src/packages/core/macro/index.ts', '@umbraco-cms/backoffice/menu': './src/packages/core/menu/index.ts', - '@umbraco-cms/backoffice/meta': './src/packages/core/meta/index.ts', '@umbraco-cms/backoffice/modal': './src/packages/core/modal/index.ts', '@umbraco-cms/backoffice/notification': './src/packages/core/notification/index.ts', '@umbraco-cms/backoffice/picker-input': './src/packages/core/picker-input/index.ts', From 8ea448f3e94494614c0da245a8e93ad7c1176e1b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 2 Jan 2024 16:26:20 +0000 Subject: [PATCH 25/47] MNTP UI amends --- .../property-editor-ui-tree-picker.element.ts | 5 +- .../input-tree/input-tree.element.ts | 84 ++++++++++++------- .../input-document/input-document.element.ts | 30 ++++--- .../input-media/input-media.element.ts | 19 ++++- 4 files changed, 95 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index a505a8dd77..0b437d1ccc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,8 +1,9 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import type { StartNode } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbInputTreeElement } from '@umbraco-cms/backoffice/tree'; import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts index 93c78f79d6..f58127bf21 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts @@ -2,6 +2,8 @@ import { css, html, customElement, property } from '@umbraco-cms/backoffice/exte import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; +import { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; +//import { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; @@ -64,7 +66,20 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { selectedIds: Array = []; #onChange(event: CustomEvent) { - this.value = (event.target as UmbInputDocumentElement).selectedIds.join(','); + switch (this._type) { + case 'content': + this.value = (event.target as UmbInputDocumentElement).selectedIds.join(','); + break; + case 'media': + this.value = (event.target as UmbInputMediaElement).selectedIds.join(','); + break; + // case 'member': + // this.value = (event.target as UmbInputMemberElement).selectedIds.join(','); + // break; + default: + break; + } + this.dispatchEvent(new UmbChangeEvent()); } @@ -75,40 +90,51 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { render() { switch (this._type) { case 'content': - return html``; + return this.#renderContentPicker(); case 'media': - return html``; + return this.#renderMediaPicker(); case 'member': - return html` - `; + return this.#renderMemberPicker(); default: - return html`Type could not be found`; + return html`

    Type could not be found.

    `; } } + #renderContentPicker() { + return html``; + } + + #renderMediaPicker() { + return html``; + } + + #renderMemberPicker() { + return html``; + } + static styles = [ css` p { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 504d487406..f07b21eb93 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -90,31 +90,41 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } + protected _openPicker() { + this.#pickerContext.openPicker({ + hideTreeRoot: true, + }); + } + protected getFormElement() { return undefined; } render() { return html` - ${this._items - ? html` ${repeat( - this._items, - (item) => item.id, - (item) => this._renderItem(item), - )} - ` - : ''} + ${this.#renderItems()} ${this.#renderAddButton()} `; } + #renderItems() { + if (!this._items) return; + // TODO: Add sorting. [LK] + return html`${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + `; + } + #renderAddButton() { if (this.max > 0 && this.selectedIds.length >= this.max) return; return html` this.#pickerContext.openPicker()} + @click=${this._openPicker} label=${this.localize.term('general_choose')}>`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts index 2b20086885..bea829a72e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts @@ -90,18 +90,33 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } + protected _openPicker() { + this.#pickerContext.openPicker({ + hideTreeRoot: true, + }); + } + protected getFormElement() { return undefined; } render() { - return html` ${this._items?.map((item) => this.#renderItem(item))} ${this.#renderButton()} `; + return html` ${this.#renderItems()} ${this.#renderButton()} `; + } + + #renderItems() { + // TODO: Add sorting. [LK] + return html` ${this._items?.map((item) => this.#renderItem(item))} `; } #renderButton() { if (this._items && this.max && this._items.length >= this.max) return; return html` - this.#pickerContext.openPicker()} label=${this.localize.term('general_choose')}> + ${this.localize.term('general_choose')} From 35bbc472a69c45636f57bb8f67e4eca3a2df4505 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 4 Jan 2024 15:30:47 +0000 Subject: [PATCH 26/47] Input Document: Moved `constructor` code to `connectedCallback`, as attribute properties for configuration values, such as min/max, haven't been set at the point of execution. --- .../components/input-document/input-document.element.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index f07b21eb93..1342efd1a8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -73,6 +73,10 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { constructor() { super(); + } + + connectedCallback() { + super.connectedCallback(); this.addValidator( 'rangeUnderflow', From 6c76b0292b9381c50ac2b6ad524ef32512d02e3d Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 4 Jan 2024 15:33:55 +0000 Subject: [PATCH 27/47] Tree Picker: Ensure min/max are numeric There is a scenario where the `config.getValueByAlias('maxNumber')` would return as a `string`. This would cause issues further down when they are strongly-typed compared, e.g. `max === 1` would be false. I experienced this issue with the Tree Picker, for setting the `multiple` property value. --- .../tree-picker/property-editor-ui-tree-picker.element.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 0b437d1ccc..6a86f69070 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -45,8 +45,10 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen this.startNodeId = startNode.id; } - this.min = config?.getValueByAlias('minNumber') || 0; - this.max = config?.getValueByAlias('maxNumber') || 0; + // TODO: The value from `config.getValueByAlias('maxNumber')` could be a `string`, can't be cast as a `number`. [LK] + // This causes issues when the `max` value is compared against other numbers, e.g. `max === 1` would be false. + this.min = Number(config?.getValueByAlias('minNumber')) || 0; + this.max = Number(config?.getValueByAlias('maxNumber')) || 0; this.filter = config?.getValueByAlias('filter'); this.showOpenButton = config?.getValueByAlias('showOpenButton'); From 46d5ae3711d6372c1359a2fa5c2dcf848c78337c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 8 Jan 2024 14:22:15 +0000 Subject: [PATCH 28/47] Added TODO note about the management API as it is expecting the MNTP value to be as UDIs not GUIDs. --- .../uis/tree-picker/property-editor-ui-tree-picker.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 6a86f69070..3f848b6bf8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -57,6 +57,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen #onChange(e: CustomEvent) { this.value = (e.target as UmbInputTreeElement).value as string; + // TODO: Unable to save MNTP value, as the management API is expecting UDI, not GUIDs. [LK] this.dispatchEvent(new CustomEvent('property-value-change')); } From 44e8cdaa7f9fa0611a47a0c6b5aa06df737890cd Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 8 Jan 2024 17:00:08 +0000 Subject: [PATCH 29/47] input-document: adds properties for `startNodeId`, `filter`, `showOpenButton` and `ignoreUserStartNodes`. Wired up the `showOpenButton` and `isTrashed` tag. Added TODO notes for the rest. --- .../input-document/input-document.element.ts | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 1342efd1a8..186f6cd874 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -60,6 +60,18 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { this.#pickerContext.setSelection(ids); } + @property({ type: String }) + startNodeId?: string; + + @property({ type: String }) + filter?: string; + + @property({ type: Boolean }) + showOpenButton?: boolean; + + @property({ type: Boolean }) + ignoreUserStartNodes?: boolean; + @property() public set value(idsString: string) { // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. @@ -95,20 +107,24 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { } protected _openPicker() { + // TODO: Configure the content picker, with `startNodeId`, `filter` and `ignoreUserStartNodes` [LK] + console.log("_openPicker", [this.startNodeId, this.filter, this.ignoreUserStartNodes]); this.#pickerContext.openPicker({ hideTreeRoot: true, }); } + protected _openItem(item: DocumentItemResponseModel) { + // TODO: Implement the Content editing infinity editor. [LK] + console.log('TODO: _openItem', item); + } + protected getFormElement() { return undefined; } render() { - return html` - ${this.#renderItems()} - ${this.#renderAddButton()} - `; + return html` ${this.#renderItems()} ${this.#renderAddButton()} `; } #renderItems() { @@ -136,8 +152,9 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { if (!item.id) return; return html` - + ${this._renderIsTrashed(item)} + ${this._renderOpenButton(item)} this.#pickerContext.requestRemoveItem(item.id!)} label="Remove document ${item.name}" @@ -148,6 +165,18 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { `; } + private _renderIsTrashed(item: DocumentItemResponseModel) { + if (!item.isTrashed) return; + return html`Trashed`; + } + + private _renderOpenButton(item: DocumentItemResponseModel) { + if (!this.showOpenButton) return; + return html` this._openItem(item)} label="Open document ${item.name}" + >${this.localize.term('general_open')}`; + } + static styles = [ css` #add-button { From 5a10f889af3ef7efcf860529c032fbb1249ea12a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 13:35:40 +0000 Subject: [PATCH 30/47] Uncommented Member Picker --- .../core/tree/components/input-tree/input-tree.element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts index f58127bf21..f70a269eea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts @@ -3,7 +3,7 @@ import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document'; import { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; -//import { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; +import { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; @@ -73,9 +73,9 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { case 'media': this.value = (event.target as UmbInputMediaElement).selectedIds.join(','); break; - // case 'member': - // this.value = (event.target as UmbInputMemberElement).selectedIds.join(','); - // break; + case 'member': + this.value = (event.target as UmbInputMemberElement).selectedIds.join(','); + break; default: break; } From 58a5da22d6aec1d26e4030a458958138670b71fd Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 13:36:27 +0000 Subject: [PATCH 31/47] Added `UmbPropertyValueChangeEvent` and removed TODOs. --- .../tree-picker/property-editor-ui-tree-picker.element.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts index 3f848b6bf8..43c5f393ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/property-editor-ui-tree-picker.element.ts @@ -1,8 +1,7 @@ import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { StartNode } from '@umbraco-cms/backoffice/components'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { type UmbPropertyEditorConfigCollection, UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbInputTreeElement } from '@umbraco-cms/backoffice/tree'; import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; @@ -45,8 +44,6 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen this.startNodeId = startNode.id; } - // TODO: The value from `config.getValueByAlias('maxNumber')` could be a `string`, can't be cast as a `number`. [LK] - // This causes issues when the `max` value is compared against other numbers, e.g. `max === 1` would be false. this.min = Number(config?.getValueByAlias('minNumber')) || 0; this.max = Number(config?.getValueByAlias('maxNumber')) || 0; @@ -57,8 +54,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen #onChange(e: CustomEvent) { this.value = (e.target as UmbInputTreeElement).value as string; - // TODO: Unable to save MNTP value, as the management API is expecting UDI, not GUIDs. [LK] - this.dispatchEvent(new CustomEvent('property-value-change')); + this.dispatchEvent(new UmbPropertyValueChangeEvent()); } render() { From 876cef09f310c549eb828b9e0980a7b2377734f4 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 14:10:54 +0000 Subject: [PATCH 32/47] TreePickerSource: Removed the Media and Member pickers as the old backoffice did not have these options to configure a root for Media or Members. --- .../input-tree-picker-source.element.ts | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts index 90f59172a5..cc4f054661 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts @@ -1,8 +1,7 @@ import { UmbInputDocumentPickerRootElement } from '@umbraco-cms/backoffice/document'; -import { html, customElement, property, css, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, css, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; //import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export type UmbTreePickerSource = { @@ -82,8 +81,7 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem this.nodeId = (event.target).nodeId; break; case 'media': - this.nodeId = (event.target).selectedIds.join(''); - break; + case 'member': default: break; } @@ -103,11 +101,9 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem case 'content': return this.#renderTypeContent(); case 'media': - return this.#renderTypeMedia(); case 'member': - return this.#renderTypeMember(); default: - return 'No type found'; + return nothing; } } @@ -117,18 +113,6 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem .nodeId=${this.nodeId}>`; } - #renderTypeMedia() { - const nodeId = this.nodeId ? [this.nodeId] : []; - //TODO => MediaTypes - return html``; - } - - #renderTypeMember() { - const nodeId = this.nodeId ? [this.nodeId] : []; - //TODO => Members - return html``; - } - static styles = [ css` :host { From 61416d9ef9785687ff5f9f726c8f645254a3feca Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 14:11:24 +0000 Subject: [PATCH 33/47] TreePickerSource: Fixed change event bubbling --- .../input-tree-picker-source.element.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts index cc4f054661..4cb56e5dec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tree-picker-source/input-tree-picker-source.element.ts @@ -2,7 +2,7 @@ import { UmbInputDocumentPickerRootElement } from '@umbraco-cms/backoffice/docum import { html, customElement, property, css, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -//import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export type UmbTreePickerSource = { type?: UmbTreePickerSourceType; @@ -64,18 +64,16 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem ]; #onTypeChange(event: UUISelectEvent) { - //console.log('onTypeChange'); + event.stopPropagation(); this.type = event.target.value as UmbTreePickerSource['type']; this.nodeId = ''; - // TODO: Appears that the event gets bubbled up. Will need to review. [LK] - //this.dispatchEvent(new UmbChangeEvent()); + this.dispatchEvent(new UmbChangeEvent()); } #onIdChange(event: CustomEvent) { - //console.log('onIdChange', event.target); switch (this.type) { case 'content': this.nodeId = (event.target).nodeId; From a991ac8d8016165a87aa356aa15a7dadd5941504 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 15:30:37 +0000 Subject: [PATCH 34/47] MediaPicker: wired up attributes for `filter`, `ignoreUserStartNodes` and `showOpenButton`. Added method for showing if trashed. --- .../input-media/input-media.element.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts index bea829a72e..94530be00a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/components/input-media/input-media.element.ts @@ -60,6 +60,15 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { this.#pickerContext.setSelection(ids); } + @property({ type: String }) + filter?: string; + + @property({ type: Boolean }) + showOpenButton?: boolean; + + @property({ type: Boolean }) + ignoreUserStartNodes?: boolean; + @property() public set value(idsString: string) { // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. @@ -73,6 +82,10 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { constructor() { super(); + } + + connectedCallback() { + super.connectedCallback(); this.addValidator( 'rangeUnderflow', @@ -91,11 +104,18 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { } protected _openPicker() { + // TODO: Configure the media picker, with `filter` and `ignoreUserStartNodes` [LK] + console.log('_openPicker', [this.filter, this.ignoreUserStartNodes]); this.#pickerContext.openPicker({ hideTreeRoot: true, }); } + protected _openItem(item: MediaItemResponseModel) { + // TODO: Implement the Content editing infinity editor. [LK] + console.log('TODO: _openItem', item); + } + protected getFormElement() { return undefined; } @@ -105,6 +125,7 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { } #renderItems() { + if (!this._items) return; // TODO: Add sorting. [LK] return html` ${this._items?.map((item) => this.#renderItem(item))} `; } @@ -124,12 +145,14 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { } #renderItem(item: MediaItemResponseModel) { + // TODO: `file-ext` value has been hardcoded here. Find out if API model has value for it. [LK] + // TODO: How to handle the `showOpenButton` option? [LK] return html` - + ${this._renderIsTrashed(item)} @@ -142,6 +165,11 @@ export class UmbInputMediaElement extends FormControlMixin(UmbLitElement) { `; } + private _renderIsTrashed(item: MediaItemResponseModel) { + if (!item.isTrashed) return; + return html`Trashed`; + } + static styles = [ css` :host { From 2798ac3b2d2bdb38026933f1ad69b1aaae3961ac Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 15:32:28 +0000 Subject: [PATCH 35/47] TreePickerSourceType: fixed bug with re-initializing the value The `UmbPropertyValueChangeEvent` had to be triggered. --- ...r-ui-tree-picker-source-type-picker.element.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts index 16b11ffa40..90176aa18f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts @@ -7,12 +7,16 @@ import { customElement, html, property, state } from '@umbraco-cms/backoffice/ex import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; /** * @element umb-property-editor-ui-tree-picker-source-type-picker */ @customElement('umb-property-editor-ui-tree-picker-source-type-picker') -export class UmbPropertyEditorUITreePickerSourceTypePickerElement extends UmbLitElement implements UmbPropertyEditorUiElement { +export class UmbPropertyEditorUITreePickerSourceTypePickerElement + extends UmbLitElement + implements UmbPropertyEditorUiElement +{ #datasetContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE; @property({ type: Array }) @@ -42,7 +46,7 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement extends UmbLit // If we had a sourceType before, we can see this as a change and not the initial value, // so let's reset the value, so we don't carry over content-types to the new source type. if (this.#initialized && this.sourceType !== startNode.type) { - this.value = []; + this.#setValue([]); } this.sourceType = startNode.type; @@ -65,13 +69,16 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement extends UmbLit this.value = (event.target).selectedIds; break; case 'member': - this.value = (event.target).selectedIds; + this.#setValue((event.target).selectedIds); break; default: break; } + } - this.dispatchEvent(new CustomEvent('property-value-change')); + #setValue(value: string[]) { + this.value = value; + this.dispatchEvent(new UmbPropertyValueChangeEvent()); } render() { From 9689be39417fe865cced3c7b49d06c1496bd5bb7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 15:38:48 +0000 Subject: [PATCH 36/47] Corrected `UmbInputMemberTypeElement` class name --- ...erty-editor-ui-tree-picker-source-type-picker.element.ts | 4 ++-- .../input-member-type/input-member-type.element.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts index 90176aa18f..6e2dcfebc0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts @@ -1,6 +1,6 @@ import { UmbInputDocumentTypeElement } from '@umbraco-cms/backoffice/document-type'; import { UmbInputMediaTypeElement } from '@umbraco-cms/backoffice/media-type'; -import { UmbMemberTypeInputElement } from '@umbraco-cms/backoffice/member-type'; +import { UmbInputMemberTypeElement } from '@umbraco-cms/backoffice/member-type'; import type { UmbTreePickerSource } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; @@ -69,7 +69,7 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement this.value = (event.target).selectedIds; break; case 'member': - this.#setValue((event.target).selectedIds); + this.#setValue((event.target).selectedIds); break; default: break; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts index 35cb32f71a..94e6483d14 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts @@ -6,7 +6,7 @@ import type { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/backen import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; @customElement('umb-input-member-type') -export class UmbMemberTypeInputElement extends FormControlMixin(UmbLitElement) { +export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { /** * This is a minimum amount of selected items in this input. * @type {number} @@ -162,10 +162,10 @@ export class UmbMemberTypeInputElement extends FormControlMixin(UmbLitElement) { ]; } -export default UmbMemberTypeInputElement; +export default UmbInputMemberTypeElement; declare global { interface HTMLElementTagNameMap { - 'umb-input-member-type': UmbMemberTypeInputElement; + 'umb-input-member-type': UmbInputMemberTypeElement; } } From d9433abf24e61c57276eb7e34819bc9d5d1c86c1 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 15:40:01 +0000 Subject: [PATCH 37/47] Removed setting the `startNodeId` attribute from the Media Picker, as the MNTP implementation doesn't configure it. --- .../core/tree/components/input-tree/input-tree.element.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts index f70a269eea..234a127b12 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/components/input-tree/input-tree.element.ts @@ -115,7 +115,6 @@ export class UmbInputTreeElement extends FormControlMixin(UmbLitElement) { #renderMediaPicker() { return html` Date: Thu, 11 Jan 2024 15:53:04 +0000 Subject: [PATCH 38/47] Aligned the code between the DocumentType, MediaType and MemberType pickers --- .../input-document-type.element.ts | 40 ++++++++++++---- .../input-media-type.element.ts | 48 +++++++++++++++---- .../input-member-type.element.ts | 18 +++---- 3 files changed, 77 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index ad9de9b02a..262cfff0f0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -1,5 +1,5 @@ import { UmbDocumentTypePickerContext } from './input-document-type.context.js'; -import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property, state, ifDefined, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { DocumentTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -95,6 +95,10 @@ export class UmbInputDocumentTypeElement extends FormControlMixin(UmbLitElement) .observeRouteBuilder((routeBuilder) => { this._editDocumentTypePath = routeBuilder({}); }); + } + + connectedCallback() { + super.connectedCallback(); this.addValidator( 'rangeUnderflow', @@ -123,20 +127,40 @@ export class UmbInputDocumentTypeElement extends FormControlMixin(UmbLitElement) pickableFilter: (x) => x.isElement, }); } else { - this.#pickerContext.openPicker({ hideTreeRoot: true }); + this.#pickerContext.openPicker({ + hideTreeRoot: true, + }); } } render() { - return html` ${this._items?.map((item) => this._renderItem(item))} - ${this.#renderAddButton()}`; + return html` ${this.#renderItems()} ${this.#renderAddButton()} `; + } + + #renderItems() { + if (!this._items) return nothing; + if (this.max === 1 && this.selectedIds.length === 1) return nothing; + ${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + `; } #renderAddButton() { - if (this.max === 1 && this.selectedIds.length === 1) return nothing; - return html` - Add - `; + if (this.max > 0 && this.selectedIds.length >= this.max) return nothing; + return html` + ${this.localize.term('general_choose')} + `; } private _renderItem(item: DocumentTypeItemResponseModel) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts index 787e5be130..aff4f78246 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts @@ -1,5 +1,5 @@ import { UmbMediaTypePickerContext } from './input-media-type.context.js'; -import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { MediaTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -73,6 +73,10 @@ export class UmbInputMediaTypeElement extends FormControlMixin(UmbLitElement) { constructor() { super(); + } + + connectedCallback() { + super.connectedCallback(); this.addValidator( 'rangeUnderflow', @@ -94,30 +98,54 @@ export class UmbInputMediaTypeElement extends FormControlMixin(UmbLitElement) { return undefined; } + #openPicker() { + this.#pickerContext.openPicker({ + hideTreeRoot: true, + }); + } + render() { - console.log('ITEMS', this._items); + return html` ${this.#renderItems()} ${this.#renderAddButton()} `; + } + + #renderItems() { + if (!this._items) return; return html` - ${this._items?.map((item) => this._renderItem(item))} - this.#pickerContext.openPicker()} label="open" - >Add${repeat( + this._items, + (item) => item.id, + (item) => this._renderItem(item), + )} + `; + } + + #renderAddButton() { + if (this.max > 0 && this.selectedIds.length >= this.max) return; + return html` + ${this.localize.term('general_choose')} `; } private _renderItem(item: MediaTypeItemResponseModel) { if (!item.id) return; - - //TODO: Using uui-ref-node as we don't have a uui-ref-media-type yet. return html` - + this.#pickerContext.requestRemoveItem(item.id!)} label="Remove Media Type ${item.name}" - >Remove${this.localize.term('general_remove')} - + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts index 94e6483d14..0ce146c11d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts @@ -94,26 +94,22 @@ export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } - protected _openPicker() { + protected getFormElement() { + return undefined; + } + + #openPicker() { this.#pickerContext.openPicker({ hideTreeRoot: true, }); } - protected getFormElement() { - return undefined; - } - render() { - return html` - ${this.#renderItems()} - ${this.#renderAddButton()} - `; + return html` ${this.#renderItems()} ${this.#renderAddButton()} `; } #renderItems() { if (!this._items) return; - // TODO: Add sorting. [LK] return html` ${repeat( @@ -131,7 +127,7 @@ export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { ${this.localize.term('general_choose')} From 90b2fa947d0575ac734efd1c183e8db660472f03 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 16:20:21 +0000 Subject: [PATCH 39/47] TreePickerSourceType: fixed bug (again) after bad merge --- ...erty-editor-ui-tree-picker-source-type-picker.element.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts index 6e2dcfebc0..af31d985d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-type-picker/property-editor-ui-tree-picker-source-type-picker.element.ts @@ -63,10 +63,10 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement #onChange(event: CustomEvent) { switch (this.sourceType) { case 'content': - this.value = (event.target).selectedIds; + this.#setValue((event.target).selectedIds); break; case 'media': - this.value = (event.target).selectedIds; + this.#setValue((event.target).selectedIds); break; case 'member': this.#setValue((event.target).selectedIds); @@ -94,7 +94,7 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement case 'member': return this.#renderTypeMember(); default: - return 'No source type found'; + return html`

    No source type found

    `; } } From f8138a118e144ad8c0ad189feb327141681b9b1e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 11 Jan 2024 16:25:25 +0000 Subject: [PATCH 40/47] TreePickerSource: Changed custom event to be `UmbPropertyValueChangeEvent` --- .../property-editor-ui-tree-picker-source-picker.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-picker/property-editor-ui-tree-picker-source-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-picker/property-editor-ui-tree-picker-source-picker.element.ts index 67ddcc214b..8f7bb83ce7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-picker/property-editor-ui-tree-picker-source-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tree-picker/config/source-picker/property-editor-ui-tree-picker-source-picker.element.ts @@ -1,7 +1,7 @@ import { type UmbTreePickerSource, UmbInputTreePickerSourceElement } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { type UmbPropertyEditorConfigCollection, UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -25,7 +25,7 @@ export class UmbPropertyEditorUITreePickerSourcePickerElement extends UmbLitElem dynamicRoot: target.dynamicRoot, }; - this.dispatchEvent(new CustomEvent('property-value-change')); + this.dispatchEvent(new UmbPropertyValueChangeEvent()); } render() { From 0053577d7d6220c46520354ed7762d198f7966cb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 15 Jan 2024 12:08:19 +0000 Subject: [PATCH 41/47] Corrected bad rebase code --- .../input-document-type.element.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index 262cfff0f0..435310ca50 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -1,5 +1,14 @@ import { UmbDocumentTypePickerContext } from './input-document-type.context.js'; -import { css, html, customElement, property, state, ifDefined, repeat, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + html, + customElement, + property, + state, + ifDefined, + repeat, + nothing, +} from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { DocumentTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -139,7 +148,7 @@ export class UmbInputDocumentTypeElement extends FormControlMixin(UmbLitElement) #renderItems() { if (!this._items) return nothing; - if (this.max === 1 && this.selectedIds.length === 1) return nothing; + return html` ${repeat( this._items, From 0a38673a215764b2a8049ba69744a51bb462ec15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 12:35:22 +0100 Subject: [PATCH 42/47] corrected to new field names --- .../property-type-based-property.element.ts | 8 ++++---- .../ref-data-type/ref-data-type.element.ts | 4 ++-- .../data-type-detail.server.data-source.ts | 20 +++++++++---------- .../detail/data-type-detail.store.ts | 4 +--- .../src/packages/core/data-type/types.ts | 4 ++-- .../workspace/data-type-workspace.context.ts | 8 ++++---- .../workspace-view-data-type-info.element.ts | 4 ++-- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts index 7d8a2e2b3d..8260f8f636 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/property-type-based-property/property-type-based-property.element.ts @@ -39,12 +39,12 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement { await this._dataTypeDetailRepository.byUnique(dataTypeUnique), (dataType) => { this._dataTypeData = dataType?.values; - this._propertyEditorUiAlias = dataType?.propertyEditorUiAlias || undefined; + this._propertyEditorUiAlias = dataType?.editorUiAlias || undefined; // If there is no UI, we will look up the Property editor model to find the default UI alias: - if (!this._propertyEditorUiAlias && dataType?.propertyEditorAlias) { - //use 'dataType.propertyEditorAlias' to look up the extension in the registry: + if (!this._propertyEditorUiAlias && dataType?.editorAlias) { + //use 'dataType.editorAlias' to look up the extension in the registry: this.observe( - umbExtensionsRegistry.getByTypeAndAlias('propertyEditorSchema', dataType.propertyEditorAlias), + umbExtensionsRegistry.getByTypeAndAlias('propertyEditorSchema', dataType.editorAlias), (extension) => { if (!extension) return; this._propertyEditorUiAlias = extension?.meta.defaultPropertyEditorUiAlias; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/ref-data-type/ref-data-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/ref-data-type/ref-data-type.element.ts index e54290fea0..4af4380ec5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/ref-data-type/ref-data-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/ref-data-type/ref-data-type.element.ts @@ -28,8 +28,8 @@ export class UmbRefDataTypeElement extends UmbElementMixin(UUIRefNodeElement) { (dataType) => { if (dataType) { this.name = dataType.name ?? ''; - this.propertyEditorUiAlias = dataType.propertyEditorUiAlias ?? ''; - this.propertyEditorSchemaAlias = dataType.propertyEditorAlias ?? ''; + this.propertyEditorUiAlias = dataType.editorUiAlias ?? ''; + this.propertyEditorSchemaAlias = dataType.editorAlias ?? ''; } }, 'dataType', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/repository/detail/data-type-detail.server.data-source.ts index 948cb77c1b..f4bb5545ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -39,8 +39,8 @@ export class UmbDataTypeServerDataSource implements UmbDetailDataSource, }; @@ -85,15 +85,15 @@ export class UmbDataTypeServerDataSource implements UmbDetailDataSource - items.filter((item) => item.propertyEditorUiAlias === propertyEditorUiAlias), - ); + return this._data.asObservablePart((items) => items.filter((item) => item.editorUiAlias === propertyEditorUiAlias)); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/types.ts index 2fd8260458..452b900470 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/types.ts @@ -3,8 +3,8 @@ export interface UmbDataTypeDetailModel { unique: string; parentUnique: string | null; name: string; - propertyEditorAlias: string | undefined; - propertyEditorUiAlias: string | null; + editorAlias: string | undefined; + editorUiAlias: string | null; values: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts index 95068965ce..71498bfea6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.context.ts @@ -32,8 +32,8 @@ export class UmbDataTypeWorkspaceContext readonly name = this.#data.asObservablePart((data) => data?.name); readonly unique = this.#data.asObservablePart((data) => data?.unique); - readonly propertyEditorUiAlias = this.#data.asObservablePart((data) => data?.propertyEditorUiAlias); - readonly propertyEditorSchemaAlias = this.#data.asObservablePart((data) => data?.propertyEditorAlias); + readonly propertyEditorUiAlias = this.#data.asObservablePart((data) => data?.editorUiAlias); + readonly propertyEditorSchemaAlias = this.#data.asObservablePart((data) => data?.editorAlias); #properties = new UmbArrayState([], (x) => x.alias); readonly properties = this.#properties.asObservable(); @@ -231,10 +231,10 @@ export class UmbDataTypeWorkspaceContext } setPropertyEditorSchemaAlias(alias?: string) { - this.#data.update({ propertyEditorAlias: alias }); + this.#data.update({ editorAlias: alias }); } setPropertyEditorUiAlias(alias?: string) { - this.#data.update({ propertyEditorUiAlias: alias }); + this.#data.update({ editorUiAlias: alias }); } async propertyValueByAlias(propertyAlias: string) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/views/info/workspace-view-data-type-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/views/info/workspace-view-data-type-info.element.ts index e315847fa5..d7c14bc646 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/views/info/workspace-view-data-type-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/views/info/workspace-view-data-type-info.element.ts @@ -41,11 +41,11 @@ export class UmbWorkspaceViewDataTypeInfoElement extends UmbLitElement implement
    ${this._dataType?.unique}
    -
    ${this._dataType?.propertyEditorAlias}
    +
    ${this._dataType?.editorAlias}
    -
    ${this._dataType?.propertyEditorUiAlias}
    +
    ${this._dataType?.editorUiAlias}
    `; From a3381b3669949493677e02b4d1b2b00d7cbbbbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 12:35:52 +0100 Subject: [PATCH 43/47] datatype workspace modal --- .../data-type-flow-input.element.ts | 11 +++-------- .../data-type-picker-flow-modal.element.ts | 4 ++-- .../workspace/data-type-workspace.modal-token.ts | 14 ++++++++++++++ .../src/packages/core/data-type/workspace/index.ts | 1 + .../core/modal/token/workspace-modal.token.ts | 7 ++----- 5 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/data-type-flow-input/data-type-flow-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/data-type-flow-input/data-type-flow-input.element.ts index 181c11f8de..e3429f218f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/data-type-flow-input/data-type-flow-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/components/data-type-flow-input/data-type-flow-input.element.ts @@ -1,11 +1,8 @@ +import { UMB_DATATYPE_WORKSPACE_MODAL } from '../../index.js'; import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { - UmbModalRouteRegistrationController, - UMB_DATA_TYPE_PICKER_FLOW_MODAL, - UMB_WORKSPACE_MODAL, -} from '@umbraco-cms/backoffice/modal'; +import { UmbModalRouteRegistrationController, UMB_DATA_TYPE_PICKER_FLOW_MODAL } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; // Note: Does only support picking a single data type. But this could be developed later into this same component. To follow other picker input components. @@ -49,9 +46,7 @@ export class UmbInputDataTypeElement extends FormControlMixin(UmbLitElement) { constructor() { super(); - this.#editDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL).onSetup(() => { - return { data: { entityType: 'data-type', preset: {} } }; - }); + this.#editDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_DATATYPE_WORKSPACE_MODAL); new UmbModalRouteRegistrationController(this, UMB_DATA_TYPE_PICKER_FLOW_MODAL) .onSetup(() => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts index d20f934b85..6c30d005bb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts @@ -4,7 +4,6 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UMB_DATA_TYPE_PICKER_FLOW_DATA_TYPE_PICKER_MODAL, - UMB_WORKSPACE_MODAL, UmbDataTypePickerFlowModalData, UmbDataTypePickerFlowModalValue, UmbModalBaseElement, @@ -13,6 +12,7 @@ import { } from '@umbraco-cms/backoffice/modal'; import { ManifestPropertyEditorUi, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbEntityTreeItemModel } from '@umbraco-cms/backoffice/tree'; +import { UMB_DATATYPE_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/data-type'; interface GroupedItems { [key: string]: Array; @@ -73,7 +73,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< this.requestUpdate('_dataTypePickerModalRouteBuilder'); }); - this._createDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + this._createDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_DATATYPE_WORKSPACE_MODAL) .addAdditionalPath(':uiAlias') .onSetup((params) => { return { data: { entityType: 'data-type', preset: { editorUiAlias: params.uiAlias } } }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts new file mode 100644 index 0000000000..05f1fdc5e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts @@ -0,0 +1,14 @@ +import { CreateDataTypeRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbModalToken, UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; + +export const UMB_DATATYPE_WORKSPACE_MODAL = new UmbModalToken< + UmbWorkspaceData, + UmbWorkspaceValue +>('Umb.Modal.Workspace', { + modal: { + type: 'sidebar', + size: 'large', + }, + data: { entityType: 'data-type', preset: {} }, + // Recast the type, so the entityType data prop is not required: +}) as UmbModalToken, 'entityType'>, UmbWorkspaceValue>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/index.ts index edea71c7a5..cd8d2359be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/index.ts @@ -1 +1,2 @@ export * from './data-type-workspace.context-token.js'; +export * from './data-type-workspace.modal-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts index 3f64bfaa1f..93b568b232 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/workspace-modal.token.ts @@ -1,10 +1,7 @@ -import { CreateDataTypeRequestModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; - -// TODO: Change model: -export interface UmbWorkspaceData { +export interface UmbWorkspaceData { entityType: string; - preset: Partial; + preset: Partial; } // TODO: It would be good with a WorkspaceValueBaseType, to avoid the hardcoded type for unique here: From fb94fb0427037b1e1c0785ab16611f9ca2890813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 12 Jan 2024 12:38:55 +0100 Subject: [PATCH 44/47] corrected misused type --- .../data-type/workspace/data-type-workspace.modal-token.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts index 05f1fdc5e9..e4af72f9ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/data-type/workspace/data-type-workspace.modal-token.ts @@ -1,8 +1,8 @@ -import { CreateDataTypeRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbDataTypeDetailModel } from '../types.js'; import { UmbModalToken, UmbWorkspaceData, UmbWorkspaceValue } from '@umbraco-cms/backoffice/modal'; export const UMB_DATATYPE_WORKSPACE_MODAL = new UmbModalToken< - UmbWorkspaceData, + UmbWorkspaceData, UmbWorkspaceValue >('Umb.Modal.Workspace', { modal: { @@ -11,4 +11,4 @@ export const UMB_DATATYPE_WORKSPACE_MODAL = new UmbModalToken< }, data: { entityType: 'data-type', preset: {} }, // Recast the type, so the entityType data prop is not required: -}) as UmbModalToken, 'entityType'>, UmbWorkspaceValue>; +}) as UmbModalToken, 'entityType'>, UmbWorkspaceValue>; From 9e47fdfc3150878c7162591db81ca65890c52296 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 15 Jan 2024 12:18:10 +0000 Subject: [PATCH 45/47] MediaType picker: sets icons --- .../input-media-type/input-media-type.element.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts index aff4f78246..4b977a296d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/components/input-media-type/input-media-type.element.ts @@ -115,7 +115,7 @@ export class UmbInputMediaTypeElement extends FormControlMixin(UmbLitElement) { >${repeat( this._items, (item) => item.id, - (item) => this._renderItem(item), + (item) => this.#renderItem(item), )}
    `; @@ -134,10 +134,11 @@ export class UmbInputMediaTypeElement extends FormControlMixin(UmbLitElement) { `; } - private _renderItem(item: MediaTypeItemResponseModel) { + #renderItem(item: MediaTypeItemResponseModel) { if (!item.id) return; return html` + ${this.#renderIcon(item)} this.#pickerContext.requestRemoveItem(item.id!)} @@ -149,6 +150,11 @@ export class UmbInputMediaTypeElement extends FormControlMixin(UmbLitElement) { `; } + #renderIcon(item: MediaTypeItemResponseModel) { + if (!item.icon) return; + return html``; + } + static styles = [ css` #add-button { From e667d8845d991c482fed0fea6175277eb48e1dbb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 15 Jan 2024 13:31:59 +0000 Subject: [PATCH 46/47] DocumentType picker: sets icons --- .../input-document-type/input-document-type.element.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts index 435310ca50..d9f31b2ebe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/components/input-document-type/input-document-type.element.ts @@ -153,7 +153,7 @@ export class UmbInputDocumentTypeElement extends FormControlMixin(UmbLitElement) >${repeat( this._items, (item) => item.id, - (item) => this._renderItem(item), + (item) => this.#renderItem(item), )}
    `; @@ -172,10 +172,11 @@ export class UmbInputDocumentTypeElement extends FormControlMixin(UmbLitElement) `; } - private _renderItem(item: DocumentTypeItemResponseModel) { + #renderItem(item: DocumentTypeItemResponseModel) { if (!item.id) return; return html` + ${this.#renderIcon(item)} `; + } + static styles = [ css` #add-button { From 877cd3de14880627465db828fa89735398d9e4cc Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 15 Jan 2024 13:34:59 +0000 Subject: [PATCH 47/47] MemberType picker: sets icons --- .../input-member-type/input-member-type.element.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts index 0ce146c11d..6ac9e16ad7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-types/components/input-member-type/input-member-type.element.ts @@ -115,7 +115,7 @@ export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { >${repeat( this._items, (item) => item.id, - (item) => this._renderItem(item), + (item) => this.#renderItem(item), )} `; @@ -134,10 +134,11 @@ export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { `; } - private _renderItem(item: MemberTypeItemResponseModel) { + #renderItem(item: MemberTypeItemResponseModel) { if (!item.id) return; return html` + ${this.#renderIcon(item)} this.#pickerContext.requestRemoveItem(item.id!)} @@ -149,6 +150,11 @@ export class UmbInputMemberTypeElement extends FormControlMixin(UmbLitElement) { `; } + #renderIcon(item: MemberTypeItemResponseModel) { + if (!item.icon) return; + return html``; + } + static styles = [ css` #add-button {