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] 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);