From eeca7ee4859fd6222922f44ab648e910afc845ae Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 18 Jan 2024 17:07:37 +0000 Subject: [PATCH 01/33] [WIP] Dynamic Root --- src/Umbraco.Web.UI.Client/package.json | 1 + .../src/apps/backoffice/backoffice.element.ts | 1 + .../input-document-picker-root.element.ts | 93 ++++++++++++------- .../src/packages/dynamic-root/index.ts | 2 + .../src/packages/dynamic-root/manifests.ts | 7 ++ ...ynamic-root-origin-picker-modal.element.ts | 47 ++++++++++ .../src/packages/dynamic-root/modals/index.ts | 9 ++ .../packages/dynamic-root/modals/manifests.ts | 14 +++ .../packages/dynamic-root/repository/index.ts | 1 + .../dynamic-root/repository/manifests.ts | 3 + .../packages/dynamic-root/umbraco-package.ts | 9 ++ src/Umbraco.Web.UI.Client/tsconfig.json | 1 + .../web-test-runner.config.mjs | 1 + 13 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index a756f55069..1038f071b5 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -62,6 +62,7 @@ "./member-type": "./dist-cms/packages/members/member-types/index.js", "./package": "./dist-cms/packages/packages/package/index.js", "./data-type": "./dist-cms/packages/core/data-type/index.js", + "./dynamic-root": "./dist-cms/packages/dynamic-root/index.js", "./language": "./dist-cms/packages/settings/languages/index.js", "./logviewer": "./dist-cms/packages/settings/logviewer/index.js", "./relation-type": "./dist-cms/packages/relations/relation-types/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts index 84243dafc2..997f5a79fa 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/backoffice.element.ts @@ -30,6 +30,7 @@ const CORE_PACKAGES = [ import('../../packages/log-viewer/umbraco-package.js'), import('../../packages/health-check/umbraco-package.js'), import('../../packages/static-file/umbraco-package.js'), + import('../../packages/dynamic-root/umbraco-package.js'), ]; @customElement('umb-backoffice') diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 68b5ab60b3..f4f8b4de73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -1,8 +1,14 @@ import { UmbDocumentPickerContext } from '../input-document/input-document.context.js'; +import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL } from '@umbraco-cms/backoffice/dynamic-root'; import { 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 { DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + UMB_MODAL_MANAGER_CONTEXT_TOKEN, + UmbModalContext, + UmbModalManagerContext, +} from '@umbraco-cms/backoffice/modal'; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -24,16 +30,17 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #documentPickerContext = new UmbDocumentPickerContext(this); - // TODO: DynamicRoot - once feature implemented, wire up context and picker UI. [LK] - #dynamicRootPickerContext = { - openPicker: () => { - throw new Error('DynamicRoot picker has not been implemented yet.'); - }, - }; + private _modalContext?: UmbModalManagerContext; + + #openModal?: UmbModalContext; constructor() { super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this._modalContext = instance; + }); + this.#documentPickerContext.max = 1; this.observe(this.#documentPickerContext.selection, (selection) => (super.value = selection.join(','))); @@ -44,55 +51,79 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl return undefined; } + #openDocumentPicker() { + this.#documentPickerContext.openPicker({ + hideTreeRoot: true, + }); + } + + #openDynamicRootPicker() { + this.#openModal = this._modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, { + data: { testing: 123 }, + }); + this.#openModal?.onSubmit().then((data) => { + console.log('openDynamicRootPicker.onSubmit', data); + }); + } + render() { - return html` - ${this._items - ? html` ${repeat( - this._items, - (item) => item.id, - (item) => this._renderItem(item), - )} - ` - : ''} - ${this.#renderButtons()} - `; + return html`${this.#renderButtons()} ${this.#renderItems()}`; } #renderButtons() { if (this.nodeId) return; - - //TODO: Dynamic paths - return html` + return html` this.#documentPickerContext.openPicker()} + @click=${this.#openDocumentPicker} label=${this.localize.term('contentPicker_defineRootNode')}> this.#dynamicRootPickerContext.openPicker()} + @click=${this.#openDynamicRootPicker} label=${this.localize.term('contentPicker_defineDynamicRoot')}> `; } - private _renderItem(item: DocumentItemResponseModel) { + #renderItems() { + if (!this._items) return; + return html` + ${repeat( + this._items, + (item) => item.id, + (item) => this.#renderItem(item), + )} + `; + } + + #renderItem(item: DocumentItemResponseModel) { if (!item.id) return; return html` - + ${this.#renderIcon(item)} + ${this.#renderIsTrashed(item)} - this.#documentPickerContext.openPicker()} label="Edit document ${item.name}" - >Edit + + ${this.localize.term('general_edit')} + this.#documentPickerContext.requestRemoveItem(item.id!)} - label="Remove document ${item.name}" - >Remove + label="Remove document ${item.name}"> + ${this.localize.term('general_remove')} + `; } + + #renderIcon(item: DocumentItemResponseModel) { + if (!item.icon) return; + return html``; + } + + #renderIsTrashed(item: DocumentItemResponseModel) { + if (!item.isTrashed) return; + return html`Trashed`; + } } export default UmbInputDocumentPickerRootElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/index.ts new file mode 100644 index 0000000000..736a54915b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/index.ts @@ -0,0 +1,2 @@ +export * from './modals/index.js'; +export * from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/manifests.ts new file mode 100644 index 0000000000..4322a89557 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/manifests.ts @@ -0,0 +1,7 @@ +import { manifests as modalManifests } from './modals/manifests.js'; +import { manifests as repositoryManifests } from './repository/manifests.js'; + +export const manifests = [ + ...modalManifests, + ...repositoryManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts new file mode 100644 index 0000000000..e2caf5f367 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts @@ -0,0 +1,47 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { + UMB_MODAL_MANAGER_CONTEXT_TOKEN, + UmbModalManagerContext, + UmbModalBaseElement, +} from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-dynamic-root-origin-picker-modal') +export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseElement { + private _modalContext?: UmbModalManagerContext; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this._modalContext = instance; + }); + } + + #close() { + this.modalContext?.reject(); + } + + render() { + return html` + +
+

DynamicRoot Origin Picker Modal

+ +
+
+ Close +
+
+ `; + } + + static styles = [UmbTextStyles, css``]; +} + +export default UmbDynamicRootOriginPickerModalModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dynamic-root-origin-picker-modal': UmbDynamicRootOriginPickerModalModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts new file mode 100644 index 0000000000..5cdcc2eda3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts @@ -0,0 +1,9 @@ +import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS } from './manifests.js'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL = new UmbModalToken(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, { + modal: { + type: 'sidebar', + size: 'small', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts new file mode 100644 index 0000000000..c915e81db3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts @@ -0,0 +1,14 @@ +import { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.OriginPicker'; + +const modals: Array = [ + { + type: 'modal', + alias: UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, + name: 'Choose an origin', + js: () => import('./dynamic-root-origin-picker-modal.element.js'), + }, +]; + +export const manifests = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts new file mode 100644 index 0000000000..ff6d8716e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts @@ -0,0 +1 @@ +export { UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts new file mode 100644 index 0000000000..a140a9a729 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/manifests.ts @@ -0,0 +1,3 @@ +export const UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS = 'Umb.Repository.DynamicRoot'; + +export const manifests = []; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts new file mode 100644 index 0000000000..46eff51dfd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/umbraco-package.ts @@ -0,0 +1,9 @@ +export const name = 'Umbraco.Core.DynamicRoot'; +export const extensions = [ + { + name: 'Dynamic Root Bundle', + alias: 'Umb.Bundle.DynamicRoot', + type: 'bundle', + js: () => import('./manifests.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index de24c48990..c4c88e5d83 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -92,6 +92,7 @@ "@umbraco-cms/backoffice/temporary-file": ["src/packages/core/temporary-file"], "@umbraco-cms/backoffice/data-type": ["./src/packages/core/data-type/index.ts"], + "@umbraco-cms/backoffice/dynamic-root": ["./src/packages/dynamic-root/index.ts"], "@umbraco-cms/backoffice/language": ["./src/packages/settings/languages/index.ts"], "@umbraco-cms/backoffice/logviewer": ["src/packages/log-viewer/index.ts"], "@umbraco-cms/backoffice/relation-types": ["src/packages/relations/relation-types/index.ts"], 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 7a0d981ce5..25e576de50 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -107,6 +107,7 @@ export default { '@umbraco-cms/backoffice/package': './src/packages/packages/package/index.ts', '@umbraco-cms/backoffice/data-type': './src/packages/core/data-type/index.ts', + '@umbraco-cms/backoffice/dynamic-root': './src/packages/dynamic-root/index.ts', '@umbraco-cms/backoffice/language': './src/packages/settings/languages/index.ts', '@umbraco-cms/backoffice/logviewer': './src/packages/settings/logviewer/index.ts', '@umbraco-cms/backoffice/relation-type': './src/packages/relations/relation-types/index.ts', From ac0c8024ac46878e8d4c2258f7b076d68764e4d9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 22 Jan 2024 11:49:34 +0000 Subject: [PATCH 02/33] DynamicRoot: Added localization labels --- .../src/assets/lang/da-dk.ts | 32 +++++++++++++++++ .../src/assets/lang/en-us.ts | 35 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index d3b00b4443..c6f399e298 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -1156,6 +1156,38 @@ export default { pickedTrashedItem: 'Du har valgt et dokument som er slettet eller lagt i papirkurven', pickedTrashedItems: 'Du har valgt dokumenter som er slettede eller lagt i papirkurven', }, + dynamicRoot: { + configurationTitle: 'Dynamisk udgangspunkts forespørgsel', + pickDynamicRootOriginTitle: 'Vælg begyndelsen', + pickDynamicRootOriginDesc: 'Beskriv begyndelsen for dynamisk udgangspunkts forespørgselen', + originRootTitle: 'Roden', + originRootDesc: 'Rod noden for denne kilde', + originParentTitle: 'Overliggende', + originParentDesc: 'Den overliggende node af kilden i denne redigerings session', + originCurrentTitle: 'Nuværende', + originCurrentDesc: 'Kilde noden for denne redigerings session', + originSiteTitle: 'Siden', + originSiteDesc: 'Nærmeste node med et domæne', + originByKeyTitle: 'Specifik Node', + originByKeyDesc: 'Vælg en specifik Node', + pickDynamicRootQueryStepTitle: 'Tilføj skridt til forespørgsel', + pickDynamicRootQueryStepDesc: 'Specificer næste skridt i din dynamisk udgangspunkts forespørgsel', + queryStepNearestAncestorOrSelfTitle: 'Nærmeste forældre eller selv', + queryStepNearestAncestorOrSelfDesc: 'Forespørg the nærmeste forældre eller selv der passer på en af de givne typer', + queryStepFurthestAncestorOrSelfTitle: 'Fjerneste forældre eller selv', + queryStepFurthestAncestorOrSelfDesc: 'Forespørg fjerneste forældre eller selv der passer på en af de givne typer', + queryStepNearestDescendantOrSelfTitle: 'Nærmeste barn eller selv', + queryStepNearestDescendantOrSelfDesc: 'Forespørg nærmeste barn eller selv der passer på en af de givne typer', + queryStepFurthestDescendantOrSelfTitle: 'Fjerneste barn eller selv', + queryStepFurthestDescendantOrSelfDesc: 'Forespørg fjerneste barn eller selv der passer på en af de givne typer', + queryStepCustomTitle: 'Brugerdefineret', + queryStepCustomDesc: 'Forespørg med et skræddersyet forespørgsels skridt', + addQueryStep: 'Tilføj skridt', + queryStepTypes: 'der passer med typerne: ', + noValidStartNodeTitle: 'Intet passende indhold', + noValidStartNodeDesc: + 'Konfigurationen af dette felt passer ikke med noget indhold. Opret det manglende indhold eller kontakt din adminnistrator for at tilpasse Dynamisk Udgangspunkts Forespørgselen for dette felt.', + }, mediaPicker: { deletedItem: 'Slettet medie', pickedTrashedItem: 'Du har valgt et medie som er slettet eller lagt i papirkurven', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 6dce152d56..fcedf7094b 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1153,6 +1153,41 @@ export default { pickedTrashedItem: 'You have picked a content item currently deleted or in the recycle bin', pickedTrashedItems: 'You have picked content items currently deleted or in the recycle bin', }, + dynamicRoot: { + configurationTitle: 'Dynamic Root Query', + pickDynamicRootOriginTitle: 'Pick origin', + pickDynamicRootOriginDesc: 'Define the origin for your Dynamic Root Query', + originRootTitle: 'Root', + originRootDesc: 'Root node of this editing session', + originParentTitle: 'Parent', + originParentDesc: 'The parent node of the source in this editing session', + originCurrentTitle: 'Current', + originCurrentDesc: 'The content node that is source for this editing session', + originSiteTitle: 'Site', + originSiteDesc: 'Find nearest node with a hostname', + originByKeyTitle: 'Specific Node', + originByKeyDesc: 'Pick a specific Node as the origin for this query', + pickDynamicRootQueryStepTitle: 'Append step to query', + pickDynamicRootQueryStepDesc: 'Define the next step of your Dynamic Root Query', + queryStepNearestAncestorOrSelfTitle: 'Nearest Ancestor Or Self', + queryStepNearestAncestorOrSelfDesc: 'Query the nearest ancestor or self that fits with one of the configured types', + queryStepFurthestAncestorOrSelfTitle: 'Furthest Ancestor Or Self', + queryStepFurthestAncestorOrSelfDesc: + 'Query the Furthest ancestor or self that fits with one of the configured types', + queryStepNearestDescendantOrSelfTitle: 'Nearest Descendant Or Self', + queryStepNearestDescendantOrSelfDesc: + 'Query the nearest descendant or self that fits with one of the configured types', + queryStepFurthestDescendantOrSelfTitle: 'Furthest Descendant Or Self', + queryStepFurthestDescendantOrSelfDesc: + 'Query the Furthest descendant or self that fits with one of the configured types', + queryStepCustomTitle: 'Custom', + queryStepCustomDesc: 'Query the using a custom Query Step', + addQueryStep: 'Add query step', + queryStepTypes: 'That matches types: ', + noValidStartNodeTitle: 'No matching content', + noValidStartNodeDesc: + 'The configuration of this property does not match any content. Create the missing content or contact your administrator to adjust the Dynamic Root settings for this property.', + }, mediaPicker: { deletedItem: 'Deleted item', pickedTrashedItem: 'You have picked a media item currently deleted or in the recycle bin', From d6fd17cf24d497ce5a053d6ba665fd236c9904e0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 22 Jan 2024 15:56:56 +0000 Subject: [PATCH 03/33] [WIP] Dynamic Root Origin Picker modal --- .../input-document-picker-root.element.ts | 5 + ...ynamic-root-origin-picker-modal.element.ts | 114 +++++++++++++++--- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index f4f8b4de73..758985f8dc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -72,6 +72,11 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #renderButtons() { if (this.nodeId) return; + + // TODO: Have one button "Specify root node", which opens the Dynamic Root Picker. [LK] + + // TODO: If the old root node ID value is set, then pre-populate the "Specific Node" option. [LK] + return html` { - this._modalContext = instance; + + this.#documentPickerContext.max = 1; + + this.observe(this.#documentPickerContext.selectedItems, (selectedItems) => this.#selectedDocument(selectedItems)); + } + + #choose(alias: string) { + this.#submit({ + originAlias: alias, }); } @@ -21,21 +25,101 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle this.modalContext?.reject(); } + #documentPickerContext = new UmbDocumentPickerContext(this); + + #openDocumentPicker() { + this.#documentPickerContext.openPicker({ + hideTreeRoot: true, + }); + } + + #selectedDocument(selectedItems: Array) { + if (selectedItems.length !== 1) return; + this.#submit({ + originAlias: 'ByKey', + originKey: selectedItems[0].id, + }); + } + + #submit(value: UmbTreePickerDynamicRoot) { + this.modalContext?.setValue(value); + this.modalContext?.submit(); + } + + #originButtons = [ + { + alias: 'Root', + title: this.localize.term('dynamicRoot_originRootTitle'), + description: this.localize.term('dynamicRoot_originRootDesc'), + action: () => this.#choose('Root'), + }, + { + alias: 'Parent', + title: this.localize.term('dynamicRoot_originParentTitle'), + description: this.localize.term('dynamicRoot_originParentDesc'), + action: () => this.#choose('Parent'), + }, + { + alias: 'Current', + title: this.localize.term('dynamicRoot_originCurrentTitle'), + description: this.localize.term('dynamicRoot_originCurrentDesc'), + action: () => this.#choose('Current'), + }, + { + alias: 'Site', + title: this.localize.term('dynamicRoot_originSiteTitle'), + description: this.localize.term('dynamicRoot_originSiteDesc'), + action: () => this.#choose('Site'), + }, + { + alias: 'ByKey', + title: this.localize.term('dynamicRoot_originByKeyTitle'), + description: this.localize.term('dynamicRoot_originByKeyDesc'), + action: () => this.#openDocumentPicker(), + }, + ]; + render() { return html` - +
-

DynamicRoot Origin Picker Modal

- + + ${map( + this.#originButtons, + (btn) => html` + +

${btn.title}

+

${btn.description}

+
+ `, + )} +
- Close +
`; } - static styles = [UmbTextStyles, css``]; + static styles = [ + UmbTextStyles, + css` + uui-box > uui-button { + display: block; + --uui-button-content-align: flex-start; + } + + uui-box > uui-button:not(:last-of-type) { + margin-bottom: var(--uui-size-space-5); + } + + h3, + p { + text-align: left; + } + `, + ]; } export default UmbDynamicRootOriginPickerModalModalElement; From 75068814c7497571906905e295e3de4565c4dfed Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 10:01:20 +0000 Subject: [PATCH 04/33] [WIP] Dynamic Root repository + server data API --- .../repository/dynamic-root.repository.ts | 19 +++++++++++++++++++ .../repository/dynamic-root.server.data.ts | 17 +++++++++++++++++ .../packages/dynamic-root/repository/index.ts | 1 + 3 files changed, 37 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts new file mode 100644 index 0000000000..eef5691f85 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts @@ -0,0 +1,19 @@ +import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; + +export class UmbDynamicRootRepository extends UmbBaseController { + #dataSource: UmbDynamicRootServerDataSource; + + constructor(host: UmbControllerHostElement) { + super(host); + + this.#dataSource = new UmbDynamicRootServerDataSource(host); + } + + // TODO: Implement `postDynamicRootQuery` [LK] + + async getQuerySteps() { + return this.#dataSource.getQuerySteps(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts new file mode 100644 index 0000000000..fce089255e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts @@ -0,0 +1,17 @@ +import { DynamicRootResource } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +export class UmbDynamicRootServerDataSource { + #host: UmbControllerHost; + + constructor(host: UmbControllerHost) { + this.#host = host; + } + + // TODO: Implement `postDynamicRootQuery` [LK] + + async getQuerySteps() { + return await tryExecuteAndNotify(this.#host, DynamicRootResource.getDynamicRootSteps()); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts index ff6d8716e5..f60228e806 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/index.ts @@ -1 +1,2 @@ export { UMB_DYNAMIC_ROOT_REPOSITORY_ALIAS } from './manifests.js'; +export { UmbDynamicRootRepository } from './dynamic-root.repository.js'; From b82039eebfe07d586fc3fac554dd6b218fca1bce Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 10:01:42 +0000 Subject: [PATCH 05/33] [WIP] Dynamic Root Query Step Picker modal --- ...ic-root-query-step-picker-modal.element.ts | 145 ++++++++++++++++++ .../src/packages/dynamic-root/modals/index.ts | 15 +- .../packages/dynamic-root/modals/manifests.ts | 7 + 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts new file mode 100644 index 0000000000..9b8a2abf96 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -0,0 +1,145 @@ +import { UmbDocumentTypePickerContext } from '../../documents/document-types/components/input-document-type/input-document-type.context.js'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, map } from '@umbraco-cms/backoffice/external/lit'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { type UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; +import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root'; + +@customElement('umb-dynamic-root-query-step-picker-modal') +export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBaseElement { + #dynamicRootRepository: UmbDynamicRootRepository; + + constructor() { + super(); + + this.#dynamicRootRepository = new UmbDynamicRootRepository(this); + } + + // TODO: LK to read up on this: https://lit.dev/docs/components/lifecycle/ [LK] + protected firstUpdated(): void { + this.#getDynamicRootQuerySteps(); + } + + async #getDynamicRootQuerySteps() { + + const { data } = await this.#dynamicRootRepository.getQuerySteps(); + console.log('steps', data); + } + + #close() { + this.modalContext?.reject(); + } + + #documentTypePickerContext = new UmbDocumentTypePickerContext(this); + + #openCustom() { + this.#submit({ + alias: 'custom', + anyOfDocTypeKeys: [], + }); + } + + #openDocumentTypePicker(alias: string) { + this.#documentTypePickerContext + .openPicker({ + hideTreeRoot: true, + }) + .then(() => { + const selectedItems = this.#documentTypePickerContext.getSelection(); + this.#submit({ + alias: alias, + anyOfDocTypeKeys: selectedItems, + }); + }); + } + + #submit(value: UmbTreePickerDynamicRootQueryStep) { + this.modalContext?.setValue(value); + this.modalContext?.submit(); + } + + // TODO: This needs to be replaced with a lookup from the manifests, e.g. new extension type `dynamicRoot` [LK] + #queryStepButtons = [ + { + alias: 'NearestAncestorOrSelf', + title: this.localize.term('dynamicRoot_queryStepNearestAncestorOrSelfTitle'), + description: this.localize.term('dynamicRoot_queryStepNearestAncestorOrSelfDesc'), + action: () => this.#openDocumentTypePicker('NearestAncestorOrSelf'), + }, + { + alias: 'FurthestAncestorOrSelf', + title: this.localize.term('dynamicRoot_queryStepFurthestAncestorOrSelfTitle'), + description: this.localize.term('dynamicRoot_queryStepFurthestAncestorOrSelfDesc'), + action: () => this.#openDocumentTypePicker('FurthestAncestorOrSelf'), + }, + { + alias: 'NearestDescendantOrSelf', + title: this.localize.term('dynamicRoot_queryStepNearestDescendantOrSelfTitle'), + description: this.localize.term('dynamicRoot_queryStepNearestDescendantOrSelfDesc'), + action: () => this.#openDocumentTypePicker('NearestDescendantOrSelf'), + }, + { + alias: 'FurthestDescendantOrSelf', + title: this.localize.term('dynamicRoot_queryStepFurthestDescendantOrSelfTitle'), + description: this.localize.term('dynamicRoot_queryStepFurthestDescendantOrSelfDesc'), + action: () => this.#openDocumentTypePicker('FurthestDescendantOrSelf'), + }, + // TODO: Remove `custom` once the above are implemented. [LK] + { + alias: 'custom', + title: this.localize.term('dynamicRoot_queryStepCustomTitle'), + description: this.localize.term('dynamicRoot_queryStepCustomDesc'), + action: () => this.#openCustom(), + }, + ]; + + render() { + return html` + +
+ + ${map( + this.#queryStepButtons, + (btn) => html` + +

${btn.title}

+

${btn.description}

+
+ `, + )} +
+
+
+ +
+
+ `; + } + + static styles = [ + UmbTextStyles, + css` + uui-box > uui-button { + display: block; + --uui-button-content-align: flex-start; + } + + uui-box > uui-button:not(:last-of-type) { + margin-bottom: var(--uui-size-space-5); + } + + h3, + p { + text-align: left; + } + `, + ]; +} + +export default UmbDynamicRootQueryStepPickerModalModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-dynamic-root-query-step-picker-modal': UmbDynamicRootQueryStepPickerModalModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts index 5cdcc2eda3..1a7a4d6fdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/index.ts @@ -1,4 +1,7 @@ -import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS } from './manifests.js'; +import { + UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, + UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS, +} from './manifests.js'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL = new UmbModalToken(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS, { @@ -7,3 +10,13 @@ export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL = new UmbModalToken(UMB_DYNAMI size: 'small', }, }); + +export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL = new UmbModalToken( + UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS, + { + modal: { + type: 'sidebar', + size: 'small', + }, + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts index c915e81db3..5d27d63d23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts @@ -1,6 +1,7 @@ import { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.OriginPicker'; +export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.QueryStepPicker'; const modals: Array = [ { @@ -9,6 +10,12 @@ const modals: Array = [ name: 'Choose an origin', js: () => import('./dynamic-root-origin-picker-modal.element.js'), }, + { + type: 'modal', + alias: UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS, + name: 'Append step to query', + js: () => import('./dynamic-root-query-step-picker-modal.element.js'), + }, ]; export const manifests = [...modals]; From f01a0416b7043917361cffbd1104ed9158a810dc Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 10:03:44 +0000 Subject: [PATCH 06/33] [WIP] Document Root picker UI amends Work to consolidate the Document picker and Dynamic Root picker logic. --- .../src/assets/lang/en-us.ts | 2 +- .../input-tree-picker-source.element.ts | 68 +++-- ...or-ui-tree-picker-source-picker.element.ts | 5 +- .../input-tree/input-tree.element.ts | 2 +- .../input-document-picker-root.element.ts | 253 +++++++++++------- ...ic-root-query-step-picker-modal.element.ts | 1 - 6 files changed, 208 insertions(+), 123 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index fcedf7094b..eb8b1f3dc2 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -1148,7 +1148,7 @@ export default { }, contentPicker: { allowedItemTypes: 'You can only select items of type(s): %0%', - defineDynamicRoot: 'Specify a Dynamic Root', + defineDynamicRoot: 'Specify root node', defineRootNode: 'Pick root node', pickedTrashedItem: 'You have picked a content item currently deleted or in the recycle bin', pickedTrashedItems: 'You have picked content items currently deleted or in the recycle bin', 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 4d8a8ac9ff..3e33c26423 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 @@ -6,21 +6,22 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; export type UmbTreePickerSource = { - type?: UmbTreePickerSourceType; - id?: string | null; - dynamicRoot?: UmbTreePickerDynamicRoot | null; + type: UmbTreePickerSourceType; + id?: string; + dynamicRoot?: UmbTreePickerDynamicRoot; }; export type UmbTreePickerSourceType = 'content' | 'member' | 'media'; export type UmbTreePickerDynamicRoot = { originAlias: string; - querySteps?: Array | null; + originKey?: string; + querySteps?: Array; }; export type UmbTreePickerDynamicRootQueryStep = { alias: string; - anyOfDocTypeKeys: Array; + anyOfDocTypeKeys?: Array; }; @customElement('umb-input-tree-picker-source') @@ -29,33 +30,33 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem return undefined; } - private _type: UmbTreePickerSource['type'] = 'content'; + #type: UmbTreePickerSourceType = 'content'; @property() - public set type(value: UmbTreePickerSource['type']) { + public set type(value: UmbTreePickerSourceType) { if (value === undefined) { - value = this._type; + value = this.#type; } - const oldValue = this._type; + const oldValue = this.#type; this._options = this._options.map((option) => option.value === value ? { ...option, selected: true } : { ...option, selected: false }, ); - this._type = value; + this.#type = value; this.requestUpdate('type', oldValue); } - public get type(): UmbTreePickerSource['type'] { - return this._type; + public get type(): UmbTreePickerSourceType { + return this.#type; } @property({ attribute: 'node-id' }) - nodeId?: string | null; + nodeId?: string; @property({ attribute: false }) - dynamicRoot?: UmbTreePickerDynamicRoot | null; + dynamicRoot?: UmbTreePickerDynamicRoot | undefined; @state() _options: Array
`; } - #renderIcon(item: DocumentItemResponseModel) { - if (!item.icon) return; - return html``; + #renderAddQueryStepButton() { + return html` `; } - #renderIsTrashed(item: DocumentItemResponseModel) { - if (!item.isTrashed) return; - return html`Trashed`; - } + static styles = [ + css` + .add-button { + width: 100%; + } + `, + ]; } export default UmbInputDocumentPickerRootElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index 9b8a2abf96..58eee202e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -21,7 +21,6 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase } async #getDynamicRootQuerySteps() { - const { data } = await this.#dynamicRootRepository.getQuerySteps(); console.log('steps', data); } From 1e62003909fa8e7744052267f1b4763b23677625 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 16:02:42 +0000 Subject: [PATCH 07/33] Dynamic Root: Adds sorting to Query Steps --- .../input-document-picker-root.element.ts | 72 ++++++++++++++----- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 9f50a832db..35889cb17c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -2,16 +2,23 @@ import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, } from '@umbraco-cms/backoffice/dynamic-root'; -import { html, customElement, property, ifDefined, map, css } from '@umbraco-cms/backoffice/external/lit'; +import { html, css, customElement, property, ifDefined, map } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import { - UMB_MODAL_MANAGER_CONTEXT, - UmbModalContext, - UmbModalManagerContext, -} from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT, UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element, model) => { + return element.getAttribute('data-idx') === model; + }, + querySelectModelToElement: () => null, + identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', + itemSelector: 'uui-ref-node', + containerSelector: '#query-steps', +}; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -34,6 +41,23 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl }); } + connectedCallback(): void { + super.connectedCallback(); + + this.#updateQuerySteps(this.data?.querySteps); + } + + #sorter = new UmbSorterController(this, { + ...SORTER_CONFIG, + onChange: ({ model }) => { + if (this.data && this.data.querySteps) { + const steps = [...this.data.querySteps]; + const querySteps = model.map((index) => steps[parseInt(index)]); + this.#updateQuerySteps(querySteps); + } + }, + }); + #openDynamicRootOriginPicker() { this.#openModal = this._modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {}); this.#openModal?.onSubmit().then((data) => { @@ -46,15 +70,19 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl this.#openModal = this._modalContext?.open(UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, {}); this.#openModal?.onSubmit().then((step) => { if (this.data) { - const oldValue = this.data; const querySteps = [...(this.data.querySteps ?? []), step]; - this.data = { ...this.data, ...{ querySteps } }; - this.requestUpdate('data', oldValue); - this.dispatchEvent(new UmbChangeEvent()); + this.#updateQuerySteps(querySteps); } }); } + #updateQuerySteps(querySteps?: Array) { + if (!this.data) return; + this.#sorter.setModel(querySteps?.map((_, index) => index.toString()) ?? []); + this.data = { ...this.data, ...{ querySteps } }; + this.dispatchEvent(new UmbChangeEvent()); + } + // NOTE: Taken from: https://github.com/umbraco/Umbraco-CMS/blob/release-13.0.0/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js#L128-L141 [LK] #getIconForDynamicRootOrigin(alias?: string) { switch (alias) { @@ -109,21 +137,16 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl if (this.data?.querySteps) { const index = this.data.querySteps.indexOf(item); if (index !== -1) { - const oldValue = this.data; - const querySteps = [...this.data.querySteps]; querySteps.splice(index, 1); - - this.data = { ...this.data, ...{ querySteps } }; - - this.requestUpdate('data', oldValue); - this.dispatchEvent(new UmbChangeEvent()); + this.#updateQuerySteps(querySteps); } } } #clearDynamicRootQuery() { this.data = undefined; + this.dispatchEvent(new UmbChangeEvent()); } render() { @@ -148,6 +171,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl @@ -167,14 +191,20 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #renderQuerySteps() { if (!this.data?.querySteps) return; - return html`${map(this.data.querySteps, (item) => this.#renderQueryStep(item))}`; + return html` + + ${map(this.data.querySteps, (item, index) => this.#renderQueryStep(item, index))} + + `; } - #renderQueryStep(item: UmbTreePickerDynamicRootQueryStep) { + #renderQueryStep(item: UmbTreePickerDynamicRootQueryStep, index: number) { if (!item.alias) return; return html` @@ -200,6 +230,10 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl .add-button { width: 100%; } + + uui-ref-node[drag-placeholder] { + opacity: 0.2; + } `, ]; } From 3de7759c9344bdb1c07f6c97900cc5fb2dadd470 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 16:58:32 +0000 Subject: [PATCH 08/33] Added backwards compatibility logic for the old content picker values --- .../input-tree-picker-source.element.ts | 13 ++++++++++++- .../input-document-picker-root.element.ts | 4 +++- 2 files changed, 15 insertions(+), 2 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 3e33c26423..fcc69f7944 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 @@ -65,6 +65,15 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem { value: 'member', name: 'Members' }, ]; + connectedCallback(): void { + super.connectedCallback(); + + // NOTE: Slight hack to workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] + if (this.nodeId && !this.dynamicRoot) { + this.dynamicRoot = { originAlias: 'ByKey', originKey: this.nodeId, querySteps: [] }; + } + } + #onContentTypeChange(event: UUISelectEvent) { event.stopPropagation(); @@ -83,11 +92,13 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem // NOTE: Slight hack to workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] if (this.dynamicRoot?.originAlias === 'ByKey') { - if (!this.dynamicRoot?.querySteps || this.dynamicRoot?.querySteps?.length === 0) { + if (!this.dynamicRoot.querySteps || this.dynamicRoot.querySteps?.length === 0) { this.nodeId = this.dynamicRoot.originKey; } else { this.nodeId = undefined; } + } else if (this.nodeId) { + this.nodeId = undefined; } break; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 35889cb17c..012b4e88d8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -54,6 +54,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl const steps = [...this.data.querySteps]; const querySteps = model.map((index) => steps[parseInt(index)]); this.#updateQuerySteps(querySteps); + this.dispatchEvent(new UmbChangeEvent()); } }, }); @@ -72,6 +73,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl if (this.data) { const querySteps = [...(this.data.querySteps ?? []), step]; this.#updateQuerySteps(querySteps); + this.dispatchEvent(new UmbChangeEvent()); } }); } @@ -80,7 +82,6 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl if (!this.data) return; this.#sorter.setModel(querySteps?.map((_, index) => index.toString()) ?? []); this.data = { ...this.data, ...{ querySteps } }; - this.dispatchEvent(new UmbChangeEvent()); } // NOTE: Taken from: https://github.com/umbraco/Umbraco-CMS/blob/release-13.0.0/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js#L128-L141 [LK] @@ -140,6 +141,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl const querySteps = [...this.data.querySteps]; querySteps.splice(index, 1); this.#updateQuerySteps(querySteps); + this.dispatchEvent(new UmbChangeEvent()); } } } From 791bcc212fc76f9ce8f4ae104faf2b291e12a460 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2024 17:48:12 +0000 Subject: [PATCH 09/33] [WIP] Started implementing the `postDynamicRootQuery` API --- .../property-editor-ui-tree-picker.element.ts | 54 ++++++++++++++++++- .../repository/dynamic-root.repository.ts | 5 +- .../repository/dynamic-root.server.data.ts | 23 +++++++- 3 files changed, 77 insertions(+), 5 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 0b6d9b8133..bdd43b08b6 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,10 +1,14 @@ 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 UmbPropertyEditorConfigCollection, UmbPropertyValueChangeEvent } 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'; +import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root'; /** * @element umb-property-editor-ui-tree-picker @@ -36,12 +40,17 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @state() ignoreUserStartNodes?: boolean; + #dynamicRoot?: UmbTreePickerSource['dynamicRoot'] | undefined; + + #dynamicRootRepository: UmbDynamicRootRepository; + @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { const startNode: UmbTreePickerSource | undefined = config?.getValueByAlias('startNode'); if (startNode) { this.type = startNode.type; this.startNodeId = startNode.id; + this.#dynamicRoot = startNode.dynamicRoot; } this.min = Number(config?.getValueByAlias('minNumber')) || 0; @@ -52,6 +61,47 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen this.ignoreUserStartNodes = config?.getValueByAlias('ignoreUserStartNodes'); } + constructor() { + super(); + + this.#dynamicRootRepository = new UmbDynamicRootRepository(this); + } + + connectedCallback() { + super.connectedCallback(); + + // TODO: Implement `startNode.dynamicRoot` [LK] + if (this.#dynamicRoot) { + console.log('dynamicRoot', this.#dynamicRoot); + + this.#dynamicRootRepository + .postDynamicRootQuery({ + context: { + id: 'b327ffa1-749a-4278-aa63-c020dba6b932', + parentId: 'b327ffa1-749a-4278-aa63-c020dba6b932', + culture: null, + segment: null, + }, + query: { + origin: { + alias: this.#dynamicRoot.originAlias, + key: this.#dynamicRoot.originKey, + }, + steps: this.#dynamicRoot.querySteps!.map((step) => { + return { + alias: step.alias!, + documentTypeIds: step.anyOfDocTypeKeys!, + }; + }), + }, + }) + .then((result) => { + // TODO: Implement the result from `postDynamicRootQuery`. [LK] + console.log('postDynamicRootQuery', result); + }); + } + } + #onChange(e: CustomEvent) { this.value = (e.target as UmbInputTreeElement).value as string; this.dispatchEvent(new UmbPropertyValueChangeEvent()); @@ -60,7 +110,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen render() { return html` Date: Thu, 25 Jan 2024 12:22:01 +0000 Subject: [PATCH 10/33] Dynamic Root: Introduced new extension types - `ManifestDynamicRootOrigin` - `ManifestDynamicRootQueryStep` --- .../models/dynamic-root.model.ts | 25 ++++ .../core/extension-registry/models/index.ts | 4 + .../packages/dynamic-root/modals/manifests.ts | 122 +++++++++++++++++- 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/dynamic-root.model.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/dynamic-root.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/dynamic-root.model.ts new file mode 100644 index 0000000000..a9280cb7a5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/dynamic-root.model.ts @@ -0,0 +1,25 @@ +import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestDynamicRootOrigin extends ManifestBase { + type: 'dynamicRootOrigin'; + meta: MetaDynamicRootOrigin; +} + +export interface ManifestDynamicRootQueryStep extends ManifestBase { + type: 'dynamicRootQueryStep'; + meta: MetaDynamicRootQueryStep; +} + +export interface MetaDynamicRootOrigin { + originAlias: string; + label?: string; + description?: string; + icon?: string; +} + +export interface MetaDynamicRootQueryStep { + queryStepAlias: string; + label?: string; + description?: string; + icon?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index c5378f48cd..405d41ee4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -2,6 +2,7 @@ import type { ManifestCollection } from './collection.models.js'; import type { ManifestCollectionView } from './collection-view.model.js'; import type { ManifestDashboard } from './dashboard.model.js'; import type { ManifestDashboardCollection } from './dashboard-collection.model.js'; +import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep } from './dynamic-root.model.js'; import type { ManifestEntityAction } from './entity-action.model.js'; import type { ManifestEntityBulkAction } from './entity-bulk-action.model.js'; import type { ManifestExternalLoginProvider } from './external-login-provider.model.js'; @@ -45,6 +46,7 @@ export type * from './collection-action.model.js'; export type * from './collection-view.model.js'; export type * from './dashboard-collection.model.js'; export type * from './dashboard.model.js'; +export type * from './dynamic-root.model.js'; export type * from './entity-action.model.js'; export type * from './entity-bulk-action.model.js'; export type * from './external-login-provider.model.js'; @@ -84,6 +86,8 @@ export type ManifestTypes = | ManifestCollectionAction | ManifestDashboard | ManifestDashboardCollection + | ManifestDynamicRootOrigin + | ManifestDynamicRootQueryStep | ManifestEntityAction | ManifestEntityBulkAction | ManifestEntryPoint diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts index 5d27d63d23..97a63c351d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/manifests.ts @@ -1,4 +1,8 @@ -import { ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; +import type { + ManifestDynamicRootOrigin, + ManifestDynamicRootQueryStep, + ManifestModal, +} from '@umbraco-cms/backoffice/extension-registry'; export const UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.OriginPicker'; export const UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL_ALIAS = 'Umb.Modal.DynamicRoot.QueryStepPicker'; @@ -18,4 +22,118 @@ const modals: Array = [ }, ]; -export const manifests = [...modals]; +const origins: Array = [ + { + type: 'dynamicRootOrigin', + alias: 'Umb.DynamicRootOrigin.Root', + name: 'Dynamic Root Origin: Root', + meta: { + originAlias: 'Root', + label: 'Root', + description: 'Root node of this editing session.', + icon: 'icon-home', + }, + weight: 100, + }, + { + type: 'dynamicRootOrigin', + alias: 'Umb.DynamicRootOrigin.Parent', + name: 'Dynamic Root Origin: Parent', + meta: { + originAlias: 'Parent', + label: 'Parent', + description: 'The parent node of the source in this editing session.', + icon: 'icon-page-up', + }, + weight: 90, + }, + { + type: 'dynamicRootOrigin', + alias: 'Umb.DynamicRootOrigin.Current', + name: 'Dynamic Root Origin: Current', + meta: { + originAlias: 'Current', + label: 'Current', + description: 'The content node that is source for this editing session.', + icon: 'icon-document', + }, + weight: 80, + }, + { + type: 'dynamicRootOrigin', + alias: 'Umb.DynamicRootOrigin.Site', + name: 'Dynamic Root Origin: Site', + meta: { + originAlias: 'Site', + label: 'Site', + description: 'Find nearest node with a hostname.', + icon: 'icon-home', + }, + weight: 70, + }, + { + type: 'dynamicRootOrigin', + alias: 'Umb.DynamicRootOrigin.ByKey', + name: 'Dynamic Root Origin: By Key', + meta: { + originAlias: 'ByKey', + label: 'Specific Node', + description: 'Pick a specific Node as the origin for this query.', + icon: 'icon-wand', + }, + weight: 60, + }, +]; + +const querySteps: Array = [ + { + type: 'dynamicRootQueryStep', + alias: 'Umb.DynamicRootQueryStep.NearestAncestorOrSelf', + name: 'Dynamic Root Query Step: Nearest Ancestor Or Self', + meta: { + queryStepAlias: 'NearestAncestorOrSelf', + label: 'Nearest Ancestor Or Self', + description: 'Query the nearest ancestor or self that fits with one of the configured types.', + icon: 'icon-arrow-up', + }, + weight: 100, + }, + { + type: 'dynamicRootQueryStep', + alias: 'Umb.DynamicRootQueryStep.FurthestAncestorOrSelf', + name: 'Dynamic Root Query Step: Furthest Ancestor Or Self', + meta: { + queryStepAlias: 'FurthestAncestorOrSelf', + label: 'Furthest Ancestor Or Self', + description: 'Query the furthest ancestor or self that fits with one of the configured types.', + icon: 'icon-arrow-up', + }, + weight: 90, + }, + { + type: 'dynamicRootQueryStep', + alias: 'Umb.DynamicRootQueryStep.NearestDescendantOrSelf', + name: 'Dynamic Root Query Step: Nearest Descendant Or Self', + meta: { + queryStepAlias: 'NearestDescendantOrSelf', + label: 'Nearest Descendant Or Self', + description: 'Query the nearest descendant or self that fits with one of the configured types.', + icon: 'icon-arrow-down', + }, + weight: 80, + }, + { + type: 'dynamicRootQueryStep', + alias: 'Umb.DynamicRootQueryStep.FurthestDescendantOrSelf', + name: 'Dynamic Root Query Step: Furthest Descendant Or Self', + meta: { + queryStepAlias: 'FurthestDescendantOrSelf', + label: 'Furthest Descendant Or Self', + description: 'Query the furthest descendant or self that fits with one of the configured types.', + icon: 'icon-arrow-down', + }, + weight: 70, + }, +]; + +export const manifests = [...modals, ...origins, ...querySteps]; From 02376cd9908061ea5f980a0b62692da579a0e1d2 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jan 2024 12:23:47 +0000 Subject: [PATCH 11/33] Wired up the Dynamic Root extension types in the UI We don't need to call `dynamicRootRepository.getQuerySteps()` API, as the manifest should contain all the custom Dynamic Root query steps. --- ...ynamic-root-origin-picker-modal.element.ts | 105 +++++++----------- ...ic-root-query-step-picker-modal.element.ts | 82 ++++---------- .../repository/dynamic-root.repository.ts | 2 +- .../repository/dynamic-root.server.data.ts | 2 +- 4 files changed, 65 insertions(+), 126 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts index d9e95681b1..283a5cc813 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts @@ -1,44 +1,59 @@ import { UmbDocumentPickerContext } from '../../documents/documents/components/input-document/input-document.context.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, map } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, map, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { type UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components'; -import { type DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components'; +import { type ManifestDynamicRootOrigin, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-dynamic-root-origin-picker-modal') export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseElement { + @state() + private _origins: Array = []; + + #documentPickerContext = new UmbDocumentPickerContext(this); + constructor() { super(); this.#documentPickerContext.max = 1; - this.observe(this.#documentPickerContext.selectedItems, (selectedItems) => this.#selectedDocument(selectedItems)); + this.observe( + umbExtensionsRegistry.extensionsOfType('dynamicRootOrigin'), + (origins: Array) => { + this._origins = origins; + }, + ); } - #choose(alias: string) { - this.#submit({ - originAlias: alias, - }); + #choose(item: ManifestDynamicRootOrigin) { + switch (item.meta.originAlias) { + // NOTE: Edge-case. Currently this is the only one that uses a document picker, + // but other custom origins may want other configuration options. [LK:2024-01-25] + case 'ByKey': + this.#openDocumentPicker(item.meta.originAlias); + break; + default: + this.#submit({ originAlias: item.meta.originAlias }); + break; + } } #close() { this.modalContext?.reject(); } - #documentPickerContext = new UmbDocumentPickerContext(this); - - #openDocumentPicker() { - this.#documentPickerContext.openPicker({ - hideTreeRoot: true, - }); - } - - #selectedDocument(selectedItems: Array) { - if (selectedItems.length !== 1) return; - this.#submit({ - originAlias: 'ByKey', - originKey: selectedItems[0].id, - }); + #openDocumentPicker(originAlias: string) { + this.#documentPickerContext + .openPicker({ + hideTreeRoot: true, + }) + .then(() => { + const selectedItems = this.#documentPickerContext.getSelection(); + if (selectedItems.length !== 1) return; + this.#submit({ + originAlias, + originKey: selectedItems[0], + }); + }); } #submit(value: UmbTreePickerDynamicRoot) { @@ -46,50 +61,17 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle this.modalContext?.submit(); } - #originButtons = [ - { - alias: 'Root', - title: this.localize.term('dynamicRoot_originRootTitle'), - description: this.localize.term('dynamicRoot_originRootDesc'), - action: () => this.#choose('Root'), - }, - { - alias: 'Parent', - title: this.localize.term('dynamicRoot_originParentTitle'), - description: this.localize.term('dynamicRoot_originParentDesc'), - action: () => this.#choose('Parent'), - }, - { - alias: 'Current', - title: this.localize.term('dynamicRoot_originCurrentTitle'), - description: this.localize.term('dynamicRoot_originCurrentDesc'), - action: () => this.#choose('Current'), - }, - { - alias: 'Site', - title: this.localize.term('dynamicRoot_originSiteTitle'), - description: this.localize.term('dynamicRoot_originSiteDesc'), - action: () => this.#choose('Site'), - }, - { - alias: 'ByKey', - title: this.localize.term('dynamicRoot_originByKeyTitle'), - description: this.localize.term('dynamicRoot_originByKeyDesc'), - action: () => this.#openDocumentPicker(), - }, - ]; - render() { return html`
${map( - this.#originButtons, - (btn) => html` - -

${btn.title}

-

${btn.description}

+ this._origins, + (item) => html` + this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}"> +

${item.meta.label}

+

${item.meta.description}

`, )} @@ -103,7 +85,6 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle } static styles = [ - UmbTextStyles, css` uui-box > uui-button { display: block; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index 58eee202e3..75407f1cda 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -1,43 +1,36 @@ import { UmbDocumentTypePickerContext } from '../../documents/document-types/components/input-document-type/input-document-type.context.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, map } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, map, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { type UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root'; +import type { UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; +import { type ManifestDynamicRootQueryStep, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-dynamic-root-query-step-picker-modal') export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBaseElement { - #dynamicRootRepository: UmbDynamicRootRepository; + @state() + private _querySteps: Array = []; + + #documentTypePickerContext = new UmbDocumentTypePickerContext(this); constructor() { super(); - this.#dynamicRootRepository = new UmbDynamicRootRepository(this); + this.observe( + umbExtensionsRegistry.extensionsOfType('dynamicRootQueryStep'), + (querySteps: Array) => { + this._querySteps = querySteps; + }, + ); } - // TODO: LK to read up on this: https://lit.dev/docs/components/lifecycle/ [LK] - protected firstUpdated(): void { - this.#getDynamicRootQuerySteps(); - } - - async #getDynamicRootQuerySteps() { - const { data } = await this.#dynamicRootRepository.getQuerySteps(); - console.log('steps', data); + #choose(item: ManifestDynamicRootQueryStep) { + this.#openDocumentTypePicker(item.meta.queryStepAlias); } #close() { this.modalContext?.reject(); } - #documentTypePickerContext = new UmbDocumentTypePickerContext(this); - - #openCustom() { - this.#submit({ - alias: 'custom', - anyOfDocTypeKeys: [], - }); - } - #openDocumentTypePicker(alias: string) { this.#documentTypePickerContext .openPicker({ @@ -57,52 +50,17 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase this.modalContext?.submit(); } - // TODO: This needs to be replaced with a lookup from the manifests, e.g. new extension type `dynamicRoot` [LK] - #queryStepButtons = [ - { - alias: 'NearestAncestorOrSelf', - title: this.localize.term('dynamicRoot_queryStepNearestAncestorOrSelfTitle'), - description: this.localize.term('dynamicRoot_queryStepNearestAncestorOrSelfDesc'), - action: () => this.#openDocumentTypePicker('NearestAncestorOrSelf'), - }, - { - alias: 'FurthestAncestorOrSelf', - title: this.localize.term('dynamicRoot_queryStepFurthestAncestorOrSelfTitle'), - description: this.localize.term('dynamicRoot_queryStepFurthestAncestorOrSelfDesc'), - action: () => this.#openDocumentTypePicker('FurthestAncestorOrSelf'), - }, - { - alias: 'NearestDescendantOrSelf', - title: this.localize.term('dynamicRoot_queryStepNearestDescendantOrSelfTitle'), - description: this.localize.term('dynamicRoot_queryStepNearestDescendantOrSelfDesc'), - action: () => this.#openDocumentTypePicker('NearestDescendantOrSelf'), - }, - { - alias: 'FurthestDescendantOrSelf', - title: this.localize.term('dynamicRoot_queryStepFurthestDescendantOrSelfTitle'), - description: this.localize.term('dynamicRoot_queryStepFurthestDescendantOrSelfDesc'), - action: () => this.#openDocumentTypePicker('FurthestDescendantOrSelf'), - }, - // TODO: Remove `custom` once the above are implemented. [LK] - { - alias: 'custom', - title: this.localize.term('dynamicRoot_queryStepCustomTitle'), - description: this.localize.term('dynamicRoot_queryStepCustomDesc'), - action: () => this.#openCustom(), - }, - ]; - render() { return html`
${map( - this.#queryStepButtons, - (btn) => html` - -

${btn.title}

-

${btn.description}

+ this._querySteps, + (item) => html` + this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}"> +

${item.meta.label}

+

${item.meta.description}

`, )} diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts index c02cb44978..62c6c6c0ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts @@ -1,5 +1,5 @@ import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js'; -import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts index dac4688cbe..bf8e66ea11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts @@ -1,4 +1,4 @@ -import { DynamicRootRequestModel, DynamicRootResource } from '@umbraco-cms/backoffice/backend-api'; +import { type DynamicRootRequestModel, DynamicRootResource } from '@umbraco-cms/backoffice/backend-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; From 9f97582c3d961a216c6e1ed984e97bb983098078 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jan 2024 12:24:08 +0000 Subject: [PATCH 12/33] Dynamic Root related housekeeping --- .../input-tree-picker-source.element.ts | 4 ++-- .../input-document-picker-root.element.ts | 5 +++-- .../input-document/input-document.element.ts | 12 ++++++++---- 3 files changed, 13 insertions(+), 8 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 fcc69f7944..41d50ad036 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 @@ -68,7 +68,7 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem connectedCallback(): void { super.connectedCallback(); - // NOTE: Slight hack to workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] + // HACK: Workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] if (this.nodeId && !this.dynamicRoot) { this.dynamicRoot = { originAlias: 'ByKey', originKey: this.nodeId, querySteps: [] }; } @@ -90,7 +90,7 @@ export class UmbInputTreePickerSourceElement extends FormControlMixin(UmbLitElem case 'content': this.dynamicRoot = (event.target as UmbInputDocumentPickerRootElement).data; - // NOTE: Slight hack to workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] + // HACK: Workaround consolidating the old content-picker and dynamic-root. [LK:2024-01-24] if (this.dynamicRoot?.originAlias === 'ByKey') { if (!this.dynamicRoot.querySteps || this.dynamicRoot.querySteps?.length === 0) { this.nodeId = this.dynamicRoot.originKey; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 012b4e88d8..9569dedff0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -5,8 +5,9 @@ import { import { html, css, customElement, property, ifDefined, map } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import { UMB_MODAL_MANAGER_CONTEXT, UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; +import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; 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 c2877f68ed..08192b0d86 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 @@ -118,6 +118,13 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { this._editDocumentPath = routeBuilder({}); }); + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + connectedCallback(): void { + super.connectedCallback(); + this.addValidator( 'rangeUnderflow', () => this.minMessage, @@ -129,9 +136,6 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { () => this.maxMessage, () => !!this.max && this.#pickerContext.getSelection().length > this.max, ); - - this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); - this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); } protected getFormElement() { @@ -147,7 +151,7 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) { #openPicker() { // TODO: Configure the content picker, with `startNodeId` and `ignoreUserStartNodes` [LK] - console.log('_openPicker', [this.startNodeId, this.ignoreUserStartNodes]); + console.log('#openPicker', [this.startNodeId, this.ignoreUserStartNodes]); this.#pickerContext.openPicker({ hideTreeRoot: true, pickableFilter: this.#pickableFilter, From fdd5522103cbdd4850d6efbde6e605c15fe08518 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jan 2024 13:04:07 +0000 Subject: [PATCH 13/33] Loads `` on-demand --- ...property-editor-ui-tree-picker-source-type-picker.element.ts | 2 ++ 1 file changed, 2 insertions(+) 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 3730878b08..7c9e19b87e 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 @@ -9,6 +9,8 @@ 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'; +import '../../../../../../documents/document-types/components/input-document-type/input-document-type.element.js'; + /** * @element umb-property-editor-ui-tree-picker-source-type-picker */ From 1f900a89afcf926aa3987074ba4f1c00afc4ecba Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 25 Jan 2024 15:29:14 +0000 Subject: [PATCH 14/33] Refactored Dynamic Root repository code Wired up the call in the MNTP editor. --- ...-tree-picker-source-type-picker.element.ts | 4 +- .../property-editor-ui-tree-picker.element.ts | 55 ++++++++----------- .../repository/dynamic-root.repository.ts | 34 ++++++++++-- 3 files changed, 55 insertions(+), 38 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 7c9e19b87e..9d24671e0a 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 @@ -52,8 +52,10 @@ export class UmbPropertyEditorUITreePickerSourceTypePickerElement this.observe( await this.#datasetContext.propertyValueByAlias('startNode'), (value) => { + if (!value) return; + const startNode = value as UmbTreePickerSource; - if (startNode.type) { + if (startNode?.type) { // 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) { 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 bdd43b08b6..b6eaf34933 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,14 +1,13 @@ 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 UmbPropertyEditorConfigCollection, - UmbPropertyValueChangeEvent, -} from '@umbraco-cms/backoffice/property-editor'; +import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root'; +import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +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'; -import { UmbDynamicRootRepository } from '@umbraco-cms/backoffice/dynamic-root'; /** * @element umb-property-editor-ui-tree-picker @@ -44,6 +43,8 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen #dynamicRootRepository: UmbDynamicRootRepository; + #workspaceContext?: typeof UMB_WORKSPACE_CONTEXT.TYPE; + @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { const startNode: UmbTreePickerSource | undefined = config?.getValueByAlias('startNode'); @@ -65,40 +66,28 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen super(); this.#dynamicRootRepository = new UmbDynamicRootRepository(this); + + this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext: typeof UMB_WORKSPACE_CONTEXT.TYPE) => { + this.#workspaceContext = workspaceContext; + }); } connectedCallback() { super.connectedCallback(); - // TODO: Implement `startNode.dynamicRoot` [LK] - if (this.#dynamicRoot) { - console.log('dynamicRoot', this.#dynamicRoot); + this.#setStartNodeId(); + } - this.#dynamicRootRepository - .postDynamicRootQuery({ - context: { - id: 'b327ffa1-749a-4278-aa63-c020dba6b932', - parentId: 'b327ffa1-749a-4278-aa63-c020dba6b932', - culture: null, - segment: null, - }, - query: { - origin: { - alias: this.#dynamicRoot.originAlias, - key: this.#dynamicRoot.originKey, - }, - steps: this.#dynamicRoot.querySteps!.map((step) => { - return { - alias: step.alias!, - documentTypeIds: step.anyOfDocTypeKeys!, - }; - }), - }, - }) - .then((result) => { - // TODO: Implement the result from `postDynamicRootQuery`. [LK] - console.log('postDynamicRootQuery', result); - }); + #setStartNodeId() { + if (this.startNodeId) return; + + const entityId = this.#workspaceContext?.getEntityId(); + if (entityId && this.#dynamicRoot) { + this.#dynamicRootRepository.postDynamicRootQuery(this.#dynamicRoot, entityId).then((result) => { + if (result) { + this.startNodeId = result[0]; + } + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts index 62c6c6c0ac..66b401dc41 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts @@ -1,7 +1,10 @@ import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; -import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { DynamicRootRequestModel, DynamicRootResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components'; + +const GUID_EMPTY: string = '00000000-0000-0000-0000-000000000000'; export class UmbDynamicRootRepository extends UmbBaseController { #dataSource: UmbDynamicRootServerDataSource; @@ -12,8 +15,31 @@ export class UmbDynamicRootRepository extends UmbBaseController { this.#dataSource = new UmbDynamicRootServerDataSource(host); } - async postDynamicRootQuery(args: DynamicRootRequestModel) { - return this.#dataSource.postDynamicRootQuery(args); + + + async postDynamicRootQuery(query: UmbTreePickerDynamicRoot, currentId: string, parentId?: string) { + const model: DynamicRootRequestModel = { + context: { + id: currentId, + parentId: parentId ?? GUID_EMPTY, + }, + query: { + origin: { + alias: query.originAlias, + key: query.originKey, + }, + steps: query.querySteps!.map((step) => { + return { + alias: step.alias!, + documentTypeIds: step.anyOfDocTypeKeys!, + }; + }), + }, + }; + + const result = (await this.#dataSource.postDynamicRootQuery(model)) as DynamicRootResponseModel; + + return result.roots; } async getQuerySteps() { From 68faccae3fe1416a41043403289de79b9ff0a70c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 29 Jan 2024 15:26:05 +0000 Subject: [PATCH 15/33] Document Picker Root: refactorings --- .../input-document-picker-root.element.ts | 46 +++++++++---------- ...ynamic-root-origin-picker-modal.element.ts | 2 +- ...ic-root-query-step-picker-modal.element.ts | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 9569dedff0..a41541b39b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -9,17 +9,7 @@ import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; - -const SORTER_CONFIG: UmbSorterConfig = { - compareElementToModel: (element, model) => { - return element.getAttribute('data-idx') === model; - }, - querySelectModelToElement: () => null, - identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', - itemSelector: 'uui-ref-node', - containerSelector: '#query-steps', -}; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -30,7 +20,9 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl @property({ attribute: false }) data?: UmbTreePickerDynamicRoot | undefined; - private _modalContext?: UmbModalManagerContext; + #dynamicRootOrigin?: { label: string; icon: string; description?: string }; + + #modalContext?: UmbModalManagerContext; #openModal?: UmbModalContext; @@ -38,48 +30,56 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl super(); this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this._modalContext = instance; + this.#modalContext = instance; }); } connectedCallback(): void { super.connectedCallback(); - this.#updateQuerySteps(this.data?.querySteps); + this.#updateDynamicRootQuerySteps(this.data?.querySteps); } #sorter = new UmbSorterController(this, { - ...SORTER_CONFIG, + compareElementToModel: (element: HTMLElement, model: string) => { + return element.getAttribute('data-idx') === model; + }, + querySelectModelToElement: () => null, + identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', + itemSelector: 'uui-ref-node', + containerSelector: '#query-steps', onChange: ({ model }) => { if (this.data && this.data.querySteps) { const steps = [...this.data.querySteps]; const querySteps = model.map((index) => steps[parseInt(index)]); - this.#updateQuerySteps(querySteps); + this.#updateDynamicRootQuerySteps(querySteps); this.dispatchEvent(new UmbChangeEvent()); } }, }); #openDynamicRootOriginPicker() { - this.#openModal = this._modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {}); - this.#openModal?.onSubmit().then((data) => { - this.data = { ...this.data, ...data }; + this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {}); + this.#openModal?.onSubmit().then((data: UmbTreePickerDynamicRoot) => { + const existingData = { ...this.data }; + existingData.originKey = undefined; + this.data = { ...existingData, ...data }; this.dispatchEvent(new UmbChangeEvent()); }); } #openDynamicRootQueryStepPicker() { - this.#openModal = this._modalContext?.open(UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, {}); + this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, {}); this.#openModal?.onSubmit().then((step) => { if (this.data) { const querySteps = [...(this.data.querySteps ?? []), step]; - this.#updateQuerySteps(querySteps); + this.#updateDynamicRootQuerySteps(querySteps); this.dispatchEvent(new UmbChangeEvent()); } }); } - #updateQuerySteps(querySteps?: Array) { + #updateDynamicRootQuerySteps(querySteps?: Array) { if (!this.data) return; this.#sorter.setModel(querySteps?.map((_, index) => index.toString()) ?? []); this.data = { ...this.data, ...{ querySteps } }; @@ -141,7 +141,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl if (index !== -1) { const querySteps = [...this.data.querySteps]; querySteps.splice(index, 1); - this.#updateQuerySteps(querySteps); + this.#updateDynamicRootQuerySteps(querySteps); this.dispatchEvent(new UmbChangeEvent()); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts index 283a5cc813..b8d6c90bec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts @@ -17,7 +17,7 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle this.#documentPickerContext.max = 1; this.observe( - umbExtensionsRegistry.extensionsOfType('dynamicRootOrigin'), + umbExtensionsRegistry.byType('dynamicRootOrigin'), (origins: Array) => { this._origins = origins; }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index 75407f1cda..ba1058fbb2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -16,7 +16,7 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase super(); this.observe( - umbExtensionsRegistry.extensionsOfType('dynamicRootQueryStep'), + umbExtensionsRegistry.byType('dynamicRootQueryStep'), (querySteps: Array) => { this._querySteps = querySteps; }, From 945cb829de5f6a88e1f1c4c7d241599ec2114603 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 29 Jan 2024 15:27:03 +0000 Subject: [PATCH 16/33] Document Picker Root: Wired up UI with manifests from the extensions registry. Removing hardcoded labels and icons. --- .../input-document-picker-root.element.ts | 111 ++++++++---------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index a41541b39b..f08ba534d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -2,7 +2,7 @@ import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, } from '@umbraco-cms/backoffice/dynamic-root'; -import { html, css, customElement, property, ifDefined, map } from '@umbraco-cms/backoffice/external/lit'; +import { html, css, customElement, property, ifDefined, map, state } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; @@ -10,6 +10,11 @@ import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backo import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { + type ManifestDynamicRootOrigin, + type ManifestDynamicRootQueryStep, + umbExtensionsRegistry, +} from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -17,6 +22,12 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl return undefined; } + @state() + private _origins: Array = []; + + @state() + private _querySteps: Array = []; + @property({ attribute: false }) data?: UmbTreePickerDynamicRoot | undefined; @@ -32,11 +43,23 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { this.#modalContext = instance; }); + + this.observe(umbExtensionsRegistry.byType('dynamicRootOrigin'), (origins: Array) => { + this._origins = origins; + }); + + this.observe( + umbExtensionsRegistry.byType('dynamicRootQueryStep'), + (querySteps: Array) => { + this._querySteps = querySteps; + }, + ); } connectedCallback(): void { super.connectedCallback(); + this.#updateDynamicRootOrigin(this.data); this.#updateDynamicRootQuerySteps(this.data?.querySteps); } @@ -64,6 +87,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl const existingData = { ...this.data }; existingData.originKey = undefined; this.data = { ...existingData, ...data }; + this.#updateDynamicRootOrigin(this.data); this.dispatchEvent(new UmbChangeEvent()); }); } @@ -79,60 +103,32 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl }); } + #updateDynamicRootOrigin(data?: UmbTreePickerDynamicRoot) { + if (!data) return; + const origin = this._origins.find((item) => item.meta.originAlias === data.originAlias)?.meta; + this.#dynamicRootOrigin = { + label: origin?.label ?? data.originAlias, + icon: origin?.icon ?? 'icon-wand', + description: data.originKey, + }; + } + #updateDynamicRootQuerySteps(querySteps?: Array) { if (!this.data) return; this.#sorter.setModel(querySteps?.map((_, index) => index.toString()) ?? []); this.data = { ...this.data, ...{ querySteps } }; } - // NOTE: Taken from: https://github.com/umbraco/Umbraco-CMS/blob/release-13.0.0/src/Umbraco.Web.UI.Client/src/views/prevalueeditors/treesource.controller.js#L128-L141 [LK] - #getIconForDynamicRootOrigin(alias?: string) { - switch (alias) { - case 'Parent': - return 'icon-page-up'; - case 'Current': - return 'icon-document'; - case 'ByKey': - return 'icon-wand'; - case 'Root': - case 'Site': - default: - return 'icon-home'; - } - } - - #getNameForDynamicRootOrigin(alias?: string) { - return this.localize.term(`dynamicRoot_origin${alias}Title`); - } - - #getIconForDynamicRootQueryStep(alias?: string) { - switch (alias) { - case 'NearestAncestorOrSelf': - case 'FurthestAncestorOrSelf': - return 'icon-arrow-up'; - case 'NearestDescendantOrSelf': - case 'FurthestDescendantOrSelf': - return 'icon-arrow-down'; - default: - return 'icon-lab'; - } - } - - #getNameForDynamicRootQueryStep(alias?: string) { - switch (alias) { - case 'NearestAncestorOrSelf': - case 'FurthestAncestorOrSelf': - case 'NearestDescendantOrSelf': - case 'FurthestDescendantOrSelf': - return this.localize.term(`dynamicRoot_queryStep${alias}Title`); - default: - return alias; - } - } - - #getDescriptionForDynamicRootQueryStep(item: UmbTreePickerDynamicRootQueryStep) { + #getQueryStepMeta(item: UmbTreePickerDynamicRootQueryStep): { label: string; icon: string; description?: string } { + const step = this._querySteps.find((step) => step.meta.queryStepAlias === item.alias)?.meta; const docTypes = item.anyOfDocTypeKeys?.join(', '); - return docTypes ? this.localize.term('dynamicRoot_queryStepTypes') + docTypes : undefined; + const description = docTypes ? this.localize.term('dynamicRoot_queryStepTypes') + docTypes : undefined; + + return { + label: step?.label ?? item.alias, + icon: step?.icon ?? 'icon-lab', + description, + }; } #removeDynamicRootQueryStep(item: UmbTreePickerDynamicRootQueryStep) { @@ -153,7 +149,6 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl } render() { - // TODO: If the old root node ID value is set, then pre-populate the "Specific Node" option. [LK] return html`${this.#renderButton()} ${this.#renderOrigin()}`; } @@ -169,15 +164,15 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl } #renderOrigin() { - if (!this.data) return; + if (!this.#dynamicRootOrigin) return; return html` - + name=${this.#dynamicRootOrigin.label} + detail=${ifDefined(this.#dynamicRootOrigin.description)}> + - + + this.#removeDynamicRootQueryStep(item)} From e477c8c675b75bba3feb9d9d3e6f887c93dbf74c Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:23:33 +0100 Subject: [PATCH 17/33] Block type create labels --- .../property-editor-ui-block-list.element.ts | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 2bcc827d31..3398809967 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -67,9 +67,13 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement this.#context.setSettings(buildUpValue.settingsData); } + @state() + private _createButtonLabel = this.localize.term('content_createEmpty'); + @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; + const validationLimit = config.getValueByAlias('validationLimit'); this._limitMin = validationLimit?.min; @@ -78,6 +82,13 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement const blocks = config.getValueByAlias>('blocks') ?? []; this.#context.setBlockTypes(blocks); + const customCreateButtonLabel = config.getValueByAlias('createLabel'); + if (customCreateButtonLabel) { + this._createButtonLabel = customCreateButtonLabel; + } else if (blocks.length === 1) { + this._createButtonLabel = `${this.localize.term('general_add')} ${blocks[0].label}`; + } + const useInlineEditingAsDefault = config.getValueByAlias('useInlineEditingAsDefault'); this.#context.setInlineEditingMode(useInlineEditingAsDefault); //config.useSingleBlockMode @@ -97,10 +108,13 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement private _blocks?: Array; @state() - _layouts: Array = []; + private _layouts: Array = []; @state() - _catalogueRouteBuilder?: UmbModalRouteBuilder; + private _catalogueRouteBuilder?: UmbModalRouteBuilder; + + @state() + private _directRoute?: string; #context = new UmbBlockListManagerContext(this); @@ -151,6 +165,11 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement } render() { + if (this._blocks?.length === 1) { + const elementKey = this._blocks[0].contentElementTypeKey; + this._directRoute = + this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey; + } return html` ${repeat( this._layouts, (x) => x.contentUdi, @@ -164,10 +183,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement - ${this.localize.term('content_createEmpty')} - + label=${this._createButtonLabel} + href=${this._directRoute ?? this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) ?? ''}> Date: Tue, 30 Jan 2024 15:22:41 +0100 Subject: [PATCH 18/33] blockgrid editor setup --- .../property-editor-ui-block-grid.element.ts | 213 ++++++++++++++---- .../block-catalogue-modal.element.ts | 3 +- .../block-catalogue-modal.token.ts | 4 +- 3 files changed, 174 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index 3c2afc0908..8b8c318156 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -1,11 +1,19 @@ import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { + UMB_BLOCK_CATALOGUE_MODAL, + type UmbBlockLayoutBaseModel, + type UmbBlockTypeBaseModel, + type UmbBlockTypeGroup, +} from '@umbraco-cms/backoffice/block'; +import { type UmbModalRouteBuilder, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; +import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models'; /** * @element umb-property-editor-ui-block-grid @@ -15,62 +23,161 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement @property() value = ''; + @state() + private _limitMin?: number; + @state() + private _limitMax?: number; + + @state() + private _blocks?: Array; + + @state() + private _blockGroups?: Array; + + @state() + private _layouts: Array = []; + + @state() + private _catalogueRouteBuilder?: UmbModalRouteBuilder; + + @state() + private _directRoute?: string; + + @state() + private _createButtonLabel = this.localize.term('content_createEmpty'); + @property({ attribute: false }) - public config?: UmbPropertyEditorConfigCollection; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + if (!config) return; - @state() - private _routes: UmbRoute[] = []; + const validationLimit = config.getValueByAlias('validationLimit'); - @state() - private _routerPath: string | undefined; + this._limitMin = validationLimit?.min; + this._limitMax = validationLimit?.max; - @state() - private _activePath: string | undefined; + this._blocks = config.getValueByAlias>('blocks') ?? []; + this._blockGroups = config.getValueByAlias>('blockGroups') ?? []; - @state() - private _variantId?: UmbVariantId; + const customCreateButtonLabel = config.getValueByAlias('createLabel'); + if (customCreateButtonLabel) { + this._createButtonLabel = customCreateButtonLabel; + } else if (this._blocks.length === 1) { + this._createButtonLabel = `${this.localize.term('general_add')} ${this._blocks[0].label}`; + } + + //const useInlineEditingAsDefault = config.getValueByAlias('useInlineEditingAsDefault'); + + //this.#context.setInlineEditingMode(useInlineEditingAsDefault); + //config.useSingleBlockMode + //config.useLiveEditing + //config.useInlineEditingAsDefault + this.style.maxWidth = config.getValueByAlias('maxPropertyWidth') ?? ''; + + //this.#context.setEditorConfiguration(config); + } constructor() { super(); - this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { - this.observe(context?.variantId, (propertyVariantId) => { - this._variantId = propertyVariantId; - this.setupRoutes(); - }); + /* + // TODO: Prevent initial notification from these observes: + this.observe(this.#context.layouts, (layouts) => { + this._value = { ...this._value, layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS]: layouts } }; + // Notify that the value has changed. + //console.log('layout changed', this._value); + // TODO: idea: consider inserting an await here, so other changes could appear first? Maybe some mechanism to only fire change event onces? + this._layouts = layouts; + this.#sorter.setModel(layouts); + this.dispatchEvent(new UmbChangeEvent()); }); + this.observe(this.#context.contents, (contents) => { + this._value = { ...this._value, contentData: contents }; + // Notify that the value has changed. + //console.log('content changed', this._value); + this.dispatchEvent(new UmbChangeEvent()); + }); + this.observe(this.#context.settings, (settings) => { + this._value = { ...this._value, settingsData: settings }; + // Notify that the value has changed. + //console.log('settings changed', this._value); + this.dispatchEvent(new UmbChangeEvent()); + }); + this.observe(this.#context.blockTypes, (blockTypes) => { + this._blocks = blockTypes; + }); + */ + + new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL) + .addAdditionalPath(':view/:index') + .onSetup((routingInfo) => { + const index = routingInfo.index ? parseInt(routingInfo.index) : -1; + return { + data: { + blocks: this._blocks ?? [], + blockGroups: this._blockGroups ?? [], + openClipboard: routingInfo.view === 'clipboard', + blockOriginData: { index: index }, + }, + }; + }) + .observeRouteBuilder((routeBuilder) => { + this._catalogueRouteBuilder = routeBuilder; + }); } - setupRoutes() { - this._routes = []; - if (this._variantId !== undefined) { - this._routes = [ - { - path: 'modal-1', - component: () => { - return import('./property-editor-ui-block-grid-inner-test.element.js'); - }, - setup: (component) => { - if (component instanceof HTMLElement) { - (component as any).name = 'block-grid-1'; - } - }, - }, - { - path: 'modal-2', - component: () => { - return import('./property-editor-ui-block-grid-inner-test.element.js'); - }, - setup: (component) => { - if (component instanceof HTMLElement) { - (component as any).name = 'block-grid-2'; - } - }, - }, - ]; + render() { + if (this._blocks?.length === 1) { + const elementKey = this._blocks[0].contentElementTypeKey; + this._directRoute = + this._catalogueRouteBuilder?.({ view: 'create', index: -1 }) + 'modal/umb-modal-workspace/create/' + elementKey; } + return html` + + + + + `; } + /* +setupRoutes() { + this._routes = []; + if (this._variantId !== undefined) { + this._routes = [ + { + path: 'modal-1', + component: () => { + return import('./property-editor-ui-block-grid-inner-test.element.js'); + }, + setup: (component) => { + if (component instanceof HTMLElement) { + (component as any).name = 'block-grid-1'; + } + }, + }, + { + path: 'modal-2', + component: () => { + return import('./property-editor-ui-block-grid-inner-test.element.js'); + }, + setup: (component) => { + if (component instanceof HTMLElement) { + (component as any).name = 'block-grid-2'; + } + }, + }, + ]; + } +} +*/ + /* render() { return this._variantId ? html`
@@ -101,8 +208,28 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement
` : 'loading...'; } + */ - static styles = [UmbTextStyles]; + static styles = [ + UmbTextStyles, + css` + :host { + display: grid; + gap: 1px; + } + > div { + display: flex; + flex-direction: column; + align-items: stretch; + } + + uui-button-group { + padding-top: 1px; + display: grid; + grid-template-columns: 1fr auto; + } + `, + ]; } export default UmbPropertyEditorUIBlockGridElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 9bba4b38ee..a427ab584b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -2,6 +2,7 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js'; import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue, + UmbBlockTypeGroup, UmbBlockTypeWithGroupKey, } from '@umbraco-cms/backoffice/block'; import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; @@ -21,7 +22,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< private _blocks: Array = []; @state() - private _blockGroups: Array<{ key: string; name: string }> = []; + private _blockGroups: Array = []; @state() _openClipboard?: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.token.ts index 6872272e0e..7a68521e47 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.token.ts @@ -1,9 +1,9 @@ -import type { UmbBlockTypeBaseModel, UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block'; +import type { UmbBlockTypeBaseModel, UmbBlockTypeGroup, UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export interface UmbBlockCatalogueModalData { blocks: Array; - blockGroups?: Array<{ name: string; key: string }>; + blockGroups?: Array; openClipboard?: boolean; blockOriginData: UmbBlockWorkspaceData['originData']; } From 793e024e68e4abe05bd71cbb95ba4589049c67d3 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 30 Jan 2024 14:46:59 +0000 Subject: [PATCH 19/33] MNTP + Dynamic Root: TODO note about future Parent ID work --- .../tree-picker/property-editor-ui-tree-picker.element.ts | 2 ++ .../dynamic-root/repository/dynamic-root.repository.ts | 6 ++---- 2 files changed, 4 insertions(+), 4 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 b6eaf34933..35293f8789 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 @@ -82,6 +82,8 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen if (this.startNodeId) return; const entityId = this.#workspaceContext?.getEntityId(); + // TODO: Awaiting the workspace context to have a parent entity ID value. [LK] + // e.g. const parentEntityId = this.#workspaceContext?.getParentEntityId(); if (entityId && this.#dynamicRoot) { this.#dynamicRootRepository.postDynamicRootQuery(this.#dynamicRoot, entityId).then((result) => { if (result) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts index 66b401dc41..5986cc8a1e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts @@ -15,12 +15,10 @@ export class UmbDynamicRootRepository extends UmbBaseController { this.#dataSource = new UmbDynamicRootServerDataSource(host); } - - - async postDynamicRootQuery(query: UmbTreePickerDynamicRoot, currentId: string, parentId?: string) { + async postDynamicRootQuery(query: UmbTreePickerDynamicRoot, entityId: string, parentId?: string) { const model: DynamicRootRequestModel = { context: { - id: currentId, + id: entityId, parentId: parentId ?? GUID_EMPTY, }, query: { From b4c2ea7b193a186be998308a90d826493e8ddfc1 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 30 Jan 2024 15:20:31 +0000 Subject: [PATCH 20/33] Dynamic Root: Clear the backing field --- .../input-document-picker-root.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index f08ba534d6..88bdb37eb1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -145,6 +145,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #clearDynamicRootQuery() { this.data = undefined; + this.#dynamicRootOrigin = undefined; this.dispatchEvent(new UmbChangeEvent()); } From 5c311b1a512c0303aa63a599905506f2789fb704 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:21:42 +0100 Subject: [PATCH 21/33] catalogue show block info --- .../block-catalogue-modal.element.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index a427ab584b..e12ac2b161 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -1,4 +1,8 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js'; +import { + DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, + type UmbDocumentTypeItemModel, +} from '@umbraco-cms/backoffice/document-type'; import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue, @@ -12,12 +16,19 @@ import { UmbModalBaseElement, UmbModalRouteRegistrationController, } from '@umbraco-cms/backoffice/modal'; +import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; @customElement('umb-block-catalogue-modal') export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue > { + #itemManager = new UmbRepositoryItemsManager( + this, + DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, + (x) => x.unique, + ); + @state() private _blocks: Array = []; @@ -49,6 +60,19 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< this._workspacePath = routeBuilder({}); }); }); + + this.observe(this.#itemManager.items, (items) => { + this._blocks = items.map((item) => { + const blockGroup = this._blocks.find((block) => block.contentElementTypeKey === item.unique)?.groupKey; + const block: UmbBlockTypeWithGroupKey = { + contentElementTypeKey: item.unique, + label: item.name, + icon: item.icon ?? undefined, + groupKey: blockGroup, + }; + return block; + }); + }); } connectedCallback() { @@ -58,14 +82,9 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< this._openClipboard = this.data.openClipboard ?? false; this._blocks = this.data.blocks ?? []; this._blockGroups = this.data.blockGroups ?? []; - } - /* - #onClickBlock(contentElementTypeKey: string) { - this.modalContext?.updateValue({ key: contentElementTypeKey }); - this.modalContext?.submit(); + this.#itemManager.setUniques(this._blocks.map((x) => x.contentElementTypeKey)); } - */ render() { return html` From d198786af59799214979b3b393dbe96920aa2559 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 31 Jan 2024 17:00:16 +0000 Subject: [PATCH 22/33] Amends based on @iOvergaard's review --- ...itor-ui-tree-picker-source-type-picker.element.ts | 2 -- .../property-editor-ui-tree-picker.element.ts | 12 +++++------- 2 files changed, 5 insertions(+), 9 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 9d24671e0a..c11144dbd7 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 @@ -9,8 +9,6 @@ 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'; -import '../../../../../../documents/document-types/components/input-document-type/input-document-type.element.js'; - /** * @element umb-property-editor-ui-tree-picker-source-type-picker */ 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 35293f8789..15a9a7a6ec 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 @@ -19,7 +19,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen value = ''; @state() - type?: UmbTreePickerSource['type']; + type: UmbTreePickerSource['type'] = 'content'; @state() startNodeId?: string | null; @@ -39,9 +39,9 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen @state() ignoreUserStartNodes?: boolean; - #dynamicRoot?: UmbTreePickerSource['dynamicRoot'] | undefined; + #dynamicRoot?: UmbTreePickerSource['dynamicRoot']; - #dynamicRootRepository: UmbDynamicRootRepository; + #dynamicRootRepository = new UmbDynamicRootRepository(this); #workspaceContext?: typeof UMB_WORKSPACE_CONTEXT.TYPE; @@ -65,9 +65,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen constructor() { super(); - this.#dynamicRootRepository = new UmbDynamicRootRepository(this); - - this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext: typeof UMB_WORKSPACE_CONTEXT.TYPE) => { + this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => { this.#workspaceContext = workspaceContext; }); } @@ -101,7 +99,7 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen render() { return html` Date: Mon, 5 Feb 2024 11:04:39 +0000 Subject: [PATCH 23/33] DynamicRoot: Restricted the doctype picker to non-element-types --- .../modals/dynamic-root-query-step-picker-modal.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index ba1058fbb2..2ded733531 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -35,6 +35,7 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase this.#documentTypePickerContext .openPicker({ hideTreeRoot: true, + pickableFilter: (x) => x.isElement === false, }) .then(() => { const selectedItems = this.#documentTypePickerContext.getSelection(); From d1449d47fc5501fb2dbeb448cca2817e3f10a2d9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 5 Feb 2024 11:27:15 +0000 Subject: [PATCH 24/33] Added `unique` to `UmbTreePickerDynamicRootQueryStep` as the sorter needs to have a unique value to identify the item. This would be persisted to the database, which isn't ideal, as it is unused elsewhere. --- .../input-tree-picker-source.element.ts | 1 + .../input-document-picker-root.element.ts | 118 +++++++++++------- ...ynamic-root-origin-picker-modal.element.ts | 14 +-- ...ic-root-query-step-picker-modal.element.ts | 7 +- 4 files changed, 87 insertions(+), 53 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 41d50ad036..c7337d6cac 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 @@ -20,6 +20,7 @@ export type UmbTreePickerDynamicRoot = { }; export type UmbTreePickerDynamicRootQueryStep = { + unique: string; alias: string; anyOfDocTypeKeys?: Array; }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index 88bdb37eb1..f80ee6ac0e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -1,20 +1,22 @@ +import { html, css, customElement, property, ifDefined, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbId } from '@umbraco-cms/backoffice/id'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, UMB_DYNAMIC_ROOT_QUERY_STEP_PICKER_MODAL, } from '@umbraco-cms/backoffice/dynamic-root'; -import { html, css, customElement, property, ifDefined, map, state } from '@umbraco-cms/backoffice/external/lit'; -import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; -import { - type ManifestDynamicRootOrigin, - type ManifestDynamicRootQueryStep, - umbExtensionsRegistry, +import type { + ManifestDynamicRootOrigin, + ManifestDynamicRootQueryStep, } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; +import { query } from '@umbraco-cms/backoffice/router'; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -23,10 +25,10 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl } @state() - private _origins: Array = []; + private _originManifests: Array = []; @state() - private _querySteps: Array = []; + private _queryStepManifests: Array = []; @property({ attribute: false }) data?: UmbTreePickerDynamicRoot | undefined; @@ -44,14 +46,17 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl this.#modalContext = instance; }); - this.observe(umbExtensionsRegistry.byType('dynamicRootOrigin'), (origins: Array) => { - this._origins = origins; - }); + this.observe( + umbExtensionsRegistry.byType('dynamicRootOrigin'), + (originManifests: Array) => { + this._originManifests = originManifests; + }, + ); this.observe( umbExtensionsRegistry.byType('dynamicRootQueryStep'), - (querySteps: Array) => { - this._querySteps = querySteps; + (queryStepManifests: Array) => { + this._queryStepManifests = queryStepManifests; }, ); } @@ -63,23 +68,29 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl this.#updateDynamicRootQuerySteps(this.data?.querySteps); } - #sorter = new UmbSorterController(this, { - compareElementToModel: (element: HTMLElement, model: string) => { - return element.getAttribute('data-idx') === model; - }, - querySelectModelToElement: () => null, - identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', - itemSelector: 'uui-ref-node', - containerSelector: '#query-steps', - onChange: ({ model }) => { - if (this.data && this.data.querySteps) { - const steps = [...this.data.querySteps]; - const querySteps = model.map((index) => steps[parseInt(index)]); - this.#updateDynamicRootQuerySteps(querySteps); - this.dispatchEvent(new UmbChangeEvent()); - } - }, - }); + #sorter?: UmbSorterController; + + #initSorter() { + this.#sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry.unique; + }, + identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', + itemSelector: 'uui-ref-node', + containerSelector: '#query-steps', + onChange: ({ model }) => { + if (this.data && this.data.querySteps) { + //const steps = [...this.data.querySteps]; + const querySteps = model; //.map((index) => steps[parseInt(index)]); + this.#updateDynamicRootQuerySteps(querySteps); + this.dispatchEvent(new UmbChangeEvent()); + } + }, + }); + } #openDynamicRootOriginPicker() { this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {}); @@ -105,7 +116,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #updateDynamicRootOrigin(data?: UmbTreePickerDynamicRoot) { if (!data) return; - const origin = this._origins.find((item) => item.meta.originAlias === data.originAlias)?.meta; + const origin = this._originManifests.find((item) => item.meta.originAlias === data.originAlias)?.meta; this.#dynamicRootOrigin = { label: origin?.label ?? data.originAlias, icon: origin?.icon ?? 'icon-wand', @@ -115,16 +126,33 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #updateDynamicRootQuerySteps(querySteps?: Array) { if (!this.data) return; - this.#sorter.setModel(querySteps?.map((_, index) => index.toString()) ?? []); + + if (!this.#sorter) { + // NOTE: The sorter controller can only be initialized when the `#query-steps` element is available. [LK] + this.#initSorter(); + } + + if (querySteps) { + // NOTE: Ensure that the `unique` ID is populated for each query step. [LK] + querySteps = querySteps.map((item) => (item.unique ? item : { ...item, unique: UmbId.new() })); + } + + this.#sorter?.setModel(querySteps ?? []); this.data = { ...this.data, ...{ querySteps } }; } - #getQueryStepMeta(item: UmbTreePickerDynamicRootQueryStep): { label: string; icon: string; description?: string } { - const step = this._querySteps.find((step) => step.meta.queryStepAlias === item.alias)?.meta; + #getQueryStepMeta(item: UmbTreePickerDynamicRootQueryStep): { + unique: string; + label: string; + icon: string; + description?: string; + } { + const step = this._queryStepManifests.find((step) => step.meta.queryStepAlias === item.alias)?.meta; const docTypes = item.anyOfDocTypeKeys?.join(', '); const description = docTypes ? this.localize.term('dynamicRoot_queryStepTypes') + docTypes : undefined; return { + unique: item.unique, label: step?.label ?? item.alias, icon: step?.icon ?? 'icon-lab', description, @@ -192,16 +220,20 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl if (!this.data?.querySteps) return; return html` - ${map(this.data.querySteps, (item, index) => this.#renderQueryStep(item, index))} + ${repeat( + this.data.querySteps, + (item) => item.unique, + (item) => this.#renderQueryStep(item), + )} `; } - #renderQueryStep(item: UmbTreePickerDynamicRootQueryStep, index: number) { + #renderQueryStep(item: UmbTreePickerDynamicRootQueryStep) { if (!item.alias) return; const step = this.#getQueryStepMeta(item); return html` - + ) => { - this._origins = origins; - }, - ); + this.observe(umbExtensionsRegistry.byType('dynamicRootOrigin'), (origins: Array) => { + this._origins = origins; + }); } #choose(item: ManifestDynamicRootOrigin) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index 2ded733531..b7d7dcd759 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -1,9 +1,11 @@ import { UmbDocumentTypePickerContext } from '../../documents/document-types/components/input-document-type/input-document-type.context.js'; +import { UmbId } from '@umbraco-cms/backoffice/id'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, map, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import { type ManifestDynamicRootQueryStep, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestDynamicRootQueryStep } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-dynamic-root-query-step-picker-modal') export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBaseElement { @@ -40,6 +42,7 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase .then(() => { const selectedItems = this.#documentTypePickerContext.getSelection(); this.#submit({ + unique: UmbId.new(), alias: alias, anyOfDocTypeKeys: selectedItems, }); From 4ec22ed11807dfd5ef47226604fa4225a6336871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 5 Feb 2024 13:26:50 +0100 Subject: [PATCH 25/33] make modal registration unique towards with property alias --- .../property-editor-ui-block-grid.element.ts | 109 +++--------------- .../property-editor-ui-block-list.element.ts | 16 ++- 2 files changed, 29 insertions(+), 96 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index 8b8c318156..7c9b52e093 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -1,9 +1,6 @@ -import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; -import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestPropertyEditorUi, UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { @@ -14,12 +11,15 @@ import { } from '@umbraco-cms/backoffice/block'; import { type UmbModalRouteBuilder, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models'; +import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; /** * @element umb-property-editor-ui-block-grid */ @customElement('umb-property-editor-ui-block-grid') export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implements UmbPropertyEditorUiElement { + #catalogueModal: UmbModalRouteRegistrationController; + @property() value = ''; @@ -79,35 +79,18 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement constructor() { super(); - /* - // TODO: Prevent initial notification from these observes: - this.observe(this.#context.layouts, (layouts) => { - this._value = { ...this._value, layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS]: layouts } }; - // Notify that the value has changed. - //console.log('layout changed', this._value); - // TODO: idea: consider inserting an await here, so other changes could appear first? Maybe some mechanism to only fire change event onces? - this._layouts = layouts; - this.#sorter.setModel(layouts); - this.dispatchEvent(new UmbChangeEvent()); + this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => { + this.observe( + propertyContext?.alias, + (alias) => { + this.#catalogueModal.setUniquePathValue('propertyAlias', alias); + }, + 'observePropertyAlias', + ); }); - this.observe(this.#context.contents, (contents) => { - this._value = { ...this._value, contentData: contents }; - // Notify that the value has changed. - //console.log('content changed', this._value); - this.dispatchEvent(new UmbChangeEvent()); - }); - this.observe(this.#context.settings, (settings) => { - this._value = { ...this._value, settingsData: settings }; - // Notify that the value has changed. - //console.log('settings changed', this._value); - this.dispatchEvent(new UmbChangeEvent()); - }); - this.observe(this.#context.blockTypes, (blockTypes) => { - this._blocks = blockTypes; - }); - */ - new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL) + this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL) + .addUniquePaths(['propertyAlias']) .addAdditionalPath(':view/:index') .onSetup((routingInfo) => { const index = routingInfo.index ? parseInt(routingInfo.index) : -1; @@ -146,70 +129,6 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement `; } - /* -setupRoutes() { - this._routes = []; - if (this._variantId !== undefined) { - this._routes = [ - { - path: 'modal-1', - component: () => { - return import('./property-editor-ui-block-grid-inner-test.element.js'); - }, - setup: (component) => { - if (component instanceof HTMLElement) { - (component as any).name = 'block-grid-1'; - } - }, - }, - { - path: 'modal-2', - component: () => { - return import('./property-editor-ui-block-grid-inner-test.element.js'); - }, - setup: (component) => { - if (component instanceof HTMLElement) { - (component as any).name = 'block-grid-2'; - } - }, - }, - ]; - } -} -*/ - /* - render() { - return this._variantId - ? html`
- umb-property-editor-ui-block-grid, inner routing test: - - - - - - - { - this._routerPath = event.target.absoluteRouterPath; - }} - @change=${(event: UmbRouterSlotChangeEvent) => { - this._activePath = event.target.localActiveViewPath; - }}> - -
` - : 'loading...'; - } - */ - static styles = [ UmbTextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 669f8f75e2..f8671c248d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -15,6 +15,7 @@ import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/mod import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; +import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {} @@ -45,6 +46,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement }, }); + #catalogueModal: UmbModalRouteRegistrationController; + private _value: UmbBlockListValueModel = { layout: {}, contentData: [], @@ -121,6 +124,16 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement constructor() { super(); + this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => { + this.observe( + propertyContext?.alias, + (alias) => { + this.#catalogueModal.setUniquePathValue('propertyAlias', alias); + }, + 'observePropertyAlias', + ); + }); + // TODO: Prevent initial notification from these observes: this.observe(this.#context.layouts, (layouts) => { this._value = { ...this._value, layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS]: layouts } }; @@ -147,7 +160,8 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement this._blocks = blockTypes; }); - new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL) + this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL) + .addUniquePaths(['propertyAlias']) .addAdditionalPath(':view/:index') .onSetup((routingInfo) => { const index = routingInfo.index ? parseInt(routingInfo.index) : -1; From 55c2e5a2bbaa00344d6b1d642c88974ca7fbd154 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 5 Feb 2024 13:34:16 +0100 Subject: [PATCH 26/33] localizations --- .../property-editor-ui-block-grid.element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index 7c9b52e093..f4d2b2e5ac 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -44,7 +44,7 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement private _directRoute?: string; @state() - private _createButtonLabel = this.localize.term('content_createEmpty'); + private _createButtonLabel = this.localize.term('blockEditor_addBlock'); @property({ attribute: false }) public set config(config: UmbPropertyEditorConfigCollection | undefined) { @@ -62,7 +62,7 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement if (customCreateButtonLabel) { this._createButtonLabel = customCreateButtonLabel; } else if (this._blocks.length === 1) { - this._createButtonLabel = `${this.localize.term('general_add')} ${this._blocks[0].label}`; + this._createButtonLabel = this.localize.term('blockEditor_addThis', [this._blocks[0].label]); } //const useInlineEditingAsDefault = config.getValueByAlias('useInlineEditingAsDefault'); From 0c4599f87854be807bf39cc70abe81826aac5f18 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:31:56 +0100 Subject: [PATCH 27/33] size --- .../block-grid-editor/property-editor-ui-block-grid.element.ts | 2 +- .../modals/block-catalogue/block-catalogue-modal.element.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts index f4d2b2e5ac..fccea2bcf0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts @@ -1,6 +1,6 @@ import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { ManifestPropertyEditorUi, UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index e12ac2b161..8cc4b3f4d6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -117,7 +117,7 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< return html` ${mappedGroupsAndBlocks.map( (group) => html` - ${group.name ? html`

${group.name}

` : nothing} + ${group.name ? html`

${group.name}

` : nothing}
${repeat( group.blocks, From 76825064f61cfe47111067ebe32cecd766c26038 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:13:21 +0100 Subject: [PATCH 28/33] render and grouping --- .../block-catalogue-modal.element.ts | 64 ++++++++----------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 8cc4b3f4d6..6c65ce57e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -1,8 +1,5 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js'; -import { - DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, - type UmbDocumentTypeItemModel, -} from '@umbraco-cms/backoffice/document-type'; +import { DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type'; import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue, @@ -10,7 +7,6 @@ import type { UmbBlockTypeWithGroupKey, } from '@umbraco-cms/backoffice/block'; import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { groupBy } from '@umbraco-cms/backoffice/external/lodash'; import { UMB_MODAL_CONTEXT, UmbModalBaseElement, @@ -23,17 +19,14 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue > { - #itemManager = new UmbRepositoryItemsManager( + #itemManager = new UmbRepositoryItemsManager( this, DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, - (x) => x.unique, + (x) => x.contentElementTypeKey, ); @state() - private _blocks: Array = []; - - @state() - private _blockGroups: Array = []; + private _groupedBlocks: Array<{ name?: string; blocks: Array }> = []; @state() _openClipboard?: boolean; @@ -60,19 +53,6 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< this._workspacePath = routeBuilder({}); }); }); - - this.observe(this.#itemManager.items, (items) => { - this._blocks = items.map((item) => { - const blockGroup = this._blocks.find((block) => block.contentElementTypeKey === item.unique)?.groupKey; - const block: UmbBlockTypeWithGroupKey = { - contentElementTypeKey: item.unique, - label: item.name, - icon: item.icon ?? undefined, - groupKey: blockGroup, - }; - return block; - }); - }); } connectedCallback() { @@ -80,10 +60,17 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< if (!this.data) return; this._openClipboard = this.data.openClipboard ?? false; - this._blocks = this.data.blocks ?? []; - this._blockGroups = this.data.blockGroups ?? []; - this.#itemManager.setUniques(this._blocks.map((x) => x.contentElementTypeKey)); + const blocks: Array = this.data.blocks ?? []; + const blockGroups: Array = this.data.blockGroups ?? []; + + const noGroupBlocks = blocks.filter((block) => !blockGroups.find((group) => group.key === block.groupKey)); + const grouped = blockGroups.map((group) => ({ + name: group.name ?? '', + blocks: blocks.filter((block) => block.groupKey === group.key), + })); + + this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped]; } render() { @@ -107,15 +94,8 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< } #renderCreateEmpty() { - const blockArrays = groupBy(this._blocks, 'groupKey'); - - const mappedGroupsAndBlocks = Object.entries(blockArrays).map(([key, value]) => { - const group = this._blockGroups.find((group) => group.key === key); - return { name: group?.name, blocks: value }; - }); - return html` - ${mappedGroupsAndBlocks.map( + ${this._groupedBlocks.map( (group) => html` ${group.name ? html`

${group.name}

` : nothing}
@@ -141,12 +121,18 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< #renderViews() { return html` - (this._openClipboard = false)}> - Create Empty + (this._openClipboard = false)}> + Create Empty - (this._openClipboard = true)}> - Clipboard + (this._openClipboard = true)}> + Clipboard From 2d97e1ab6bcbc573100a8be5f8409adbf4dff0f8 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:15:28 +0100 Subject: [PATCH 29/33] remove leftovers --- .../block-catalogue/block-catalogue-modal.element.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts index 6c65ce57e5..1beec67023 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/modals/block-catalogue/block-catalogue-modal.element.ts @@ -1,5 +1,4 @@ import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js'; -import { DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS } from '@umbraco-cms/backoffice/document-type'; import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue, @@ -12,19 +11,12 @@ import { UmbModalBaseElement, UmbModalRouteRegistrationController, } from '@umbraco-cms/backoffice/modal'; -import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; @customElement('umb-block-catalogue-modal') export class UmbBlockCatalogueModalElement extends UmbModalBaseElement< UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue > { - #itemManager = new UmbRepositoryItemsManager( - this, - DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, - (x) => x.contentElementTypeKey, - ); - @state() private _groupedBlocks: Array<{ name?: string; blocks: Array }> = []; From 1e6736d19263175ea2d874bba7cdf93dae03e7f7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Feb 2024 10:55:16 +0000 Subject: [PATCH 30/33] Added mock API data/handler --- .../src/mocks/browser-handlers.ts | 2 ++ .../src/mocks/data/data-type/data-type.data.ts | 9 +++++++++ .../src/mocks/handlers/dynamic-root.handlers.ts | 15 +++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/mocks/handlers/dynamic-root.handlers.ts diff --git a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts index cde1bb1640..0cc2f7eb41 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/browser-handlers.ts @@ -36,6 +36,7 @@ import { handlers as partialViewHandlers } from './handlers/partial-view/index.j import { handlers as tagHandlers } from './handlers/tag-handlers.js'; import { handlers as configHandlers } from './handlers/config.handlers.js'; import { handlers as scriptHandlers } from './handlers/script/index.js'; +import { handlers as dynamicRootHandlers } from './handlers/dynamic-root.handlers.js'; const handlers = [ serverHandlers.serverInformationHandler, @@ -46,6 +47,7 @@ const handlers = [ ...dictionaryHandlers, ...documentHandlers, ...documentTypeHandlers, + ...dynamicRootHandlers, ...examineManagementHandlers, ...healthCheckHandlers, ...installHandlers, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index a9976a2bda..c7bb12cbcf 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -227,6 +227,15 @@ export const data: Array = [ value: { type: 'content', id: null, + dynamicRoot: { + originAlias: 'Root', + querySteps: [ + { + alias: 'FurthestAncestorOrSelf', + anyOfDocTypeKeys: ['all-property-editors-document-type-id'], + }, + ], + }, }, }, { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/dynamic-root.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dynamic-root.handlers.ts new file mode 100644 index 0000000000..c7c0072f06 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/dynamic-root.handlers.ts @@ -0,0 +1,15 @@ +import { umbDocumentMockDb } from '../data/document/document.db.js'; +import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +const { rest } = window.MockServiceWorker; + +export const handlers = [ + rest.post(umbracoPath('/dynamic-root/query'), async (req, res, ctx) => { + const response = umbDocumentMockDb.tree + .getRoot() + .items.map((item) => item.id) + .slice(0, 1); + return res(ctx.status(200), ctx.json(response)); + }), +]; From 51ac1b78b96494cfdc4312a57ce7053161deec50 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Feb 2024 10:57:19 +0000 Subject: [PATCH 31/33] DynamicRoot: Amended repository/data-source pattern Following @iOvergaard's advice. --- .../repository/dynamic-root.repository.ts | 10 +++------ .../repository/dynamic-root.server.data.ts | 22 +++++-------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts index 5986cc8a1e..4134a427f1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.repository.ts @@ -1,7 +1,7 @@ import { UmbDynamicRootServerDataSource } from './dynamic-root.server.data.js'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import type { DynamicRootRequestModel, DynamicRootResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { DynamicRootRequestModel } from '@umbraco-cms/backoffice/backend-api'; import type { UmbTreePickerDynamicRoot } from '@umbraco-cms/backoffice/components'; const GUID_EMPTY: string = '00000000-0000-0000-0000-000000000000'; @@ -35,12 +35,8 @@ export class UmbDynamicRootRepository extends UmbBaseController { }, }; - const result = (await this.#dataSource.postDynamicRootQuery(model)) as DynamicRootResponseModel; + const result = await this.#dataSource.postDynamicRootQuery(model); - return result.roots; - } - - async getQuerySteps() { - return this.#dataSource.getQuerySteps(); + return result?.roots; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts index bf8e66ea11..f5bc47fc30 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/repository/dynamic-root.server.data.ts @@ -1,6 +1,7 @@ -import { type DynamicRootRequestModel, DynamicRootResource } from '@umbraco-cms/backoffice/backend-api'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { DynamicRootResource } from '@umbraco-cms/backoffice/backend-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import type { DynamicRootRequestModel, DynamicRootResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbDynamicRootServerDataSource { #host: UmbControllerHost; @@ -9,7 +10,7 @@ export class UmbDynamicRootServerDataSource { this.#host = host; } - async postDynamicRootQuery(args: DynamicRootRequestModel) { + async postDynamicRootQuery(args: DynamicRootRequestModel): Promise { if (!args.context) throw new Error('Dynamic Root context is missing'); if (!args.query) throw new Error('Dynamic Root query is missing'); @@ -18,19 +19,8 @@ export class UmbDynamicRootServerDataSource { query: args.query, }; - const { data, error } = await tryExecuteAndNotify( - this.#host, - DynamicRootResource.postDynamicRootQuery({ requestBody }), - ); + const { data } = await tryExecuteAndNotify(this.#host, DynamicRootResource.postDynamicRootQuery({ requestBody })); - if (!error) { - return data; - } - - return { error }; - } - - async getQuerySteps() { - return await tryExecuteAndNotify(this.#host, DynamicRootResource.getDynamicRootSteps()); + return data; } } From 962ac5280f9ac99b9d74144f44e8aa34a27f714e Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Feb 2024 10:58:09 +0000 Subject: [PATCH 32/33] DynamicRoot: Refactored to async/await pattern --- .../property-editor-ui-tree-picker.element.ts | 11 ++++---- ...ynamic-root-origin-picker-modal.element.ts | 25 +++++++++-------- ...ic-root-query-step-picker-modal.element.ts | 27 +++++++++---------- 3 files changed, 30 insertions(+), 33 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 15a9a7a6ec..ebfe63e0c8 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 @@ -76,18 +76,17 @@ export class UmbPropertyEditorUITreePickerElement extends UmbLitElement implemen this.#setStartNodeId(); } - #setStartNodeId() { + async #setStartNodeId() { if (this.startNodeId) return; const entityId = this.#workspaceContext?.getEntityId(); // TODO: Awaiting the workspace context to have a parent entity ID value. [LK] // e.g. const parentEntityId = this.#workspaceContext?.getParentEntityId(); if (entityId && this.#dynamicRoot) { - this.#dynamicRootRepository.postDynamicRootQuery(this.#dynamicRoot, entityId).then((result) => { - if (result) { - this.startNodeId = result[0]; - } - }); + const result = await this.#dynamicRootRepository.postDynamicRootQuery(this.#dynamicRoot, entityId); + if (result && result.length > 0) { + this.startNodeId = result[0]; + } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts index cd58d68968..c98490a93f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-origin-picker-modal.element.ts @@ -39,19 +39,18 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle this.modalContext?.reject(); } - #openDocumentPicker(originAlias: string) { - this.#documentPickerContext - .openPicker({ - hideTreeRoot: true, - }) - .then(() => { - const selectedItems = this.#documentPickerContext.getSelection(); - if (selectedItems.length !== 1) return; - this.#submit({ - originAlias, - originKey: selectedItems[0], - }); - }); + async #openDocumentPicker(originAlias: string) { + await this.#documentPickerContext.openPicker({ + hideTreeRoot: true, + }); + + const selectedItems = this.#documentPickerContext.getSelection(); + if (selectedItems.length !== 1) return; + + this.#submit({ + originAlias, + originKey: selectedItems[0], + }); } #submit(value: UmbTreePickerDynamicRoot) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts index b7d7dcd759..1a1eeb259b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dynamic-root/modals/dynamic-root-query-step-picker-modal.element.ts @@ -33,20 +33,19 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase this.modalContext?.reject(); } - #openDocumentTypePicker(alias: string) { - this.#documentTypePickerContext - .openPicker({ - hideTreeRoot: true, - pickableFilter: (x) => x.isElement === false, - }) - .then(() => { - const selectedItems = this.#documentTypePickerContext.getSelection(); - this.#submit({ - unique: UmbId.new(), - alias: alias, - anyOfDocTypeKeys: selectedItems, - }); - }); + async #openDocumentTypePicker(alias: string) { + await this.#documentTypePickerContext.openPicker({ + hideTreeRoot: true, + pickableFilter: (x) => x.isElement === false, + }); + + const selectedItems = this.#documentTypePickerContext.getSelection(); + + this.#submit({ + unique: UmbId.new(), + alias: alias, + anyOfDocTypeKeys: selectedItems, + }); } #submit(value: UmbTreePickerDynamicRootQueryStep) { From 4c9a45d2f31df31eef329436bc485170ef792cff Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 6 Feb 2024 10:59:38 +0000 Subject: [PATCH 33/33] DocumentPickerRoot: Tidy-up + refactor Initial markup to ensure the Sorter can hook on to. --- .../input-document-picker-root.element.ts | 108 ++++++++---------- 1 file changed, 50 insertions(+), 58 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts index f80ee6ac0e..3109d65a1d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-picker-root/input-document-picker-root.element.ts @@ -14,9 +14,8 @@ import type { ManifestDynamicRootOrigin, ManifestDynamicRootQueryStep, } from '@umbraco-cms/backoffice/extension-registry'; -import type { UmbModalContext, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalContext } from '@umbraco-cms/backoffice/modal'; import type { UmbTreePickerDynamicRoot, UmbTreePickerDynamicRootQueryStep } from '@umbraco-cms/backoffice/components'; -import { query } from '@umbraco-cms/backoffice/router'; @customElement('umb-input-document-picker-root') export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitElement) { @@ -35,7 +34,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #dynamicRootOrigin?: { label: string; icon: string; description?: string }; - #modalContext?: UmbModalManagerContext; + #modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; #openModal?: UmbModalContext; @@ -68,29 +67,24 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl this.#updateDynamicRootQuerySteps(this.data?.querySteps); } - #sorter?: UmbSorterController; - - #initSorter() { - this.#sorter = new UmbSorterController(this, { - getUniqueOfElement: (element) => { - return element.id; - }, - getUniqueOfModel: (modelEntry) => { - return modelEntry.unique; - }, - identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', - itemSelector: 'uui-ref-node', - containerSelector: '#query-steps', - onChange: ({ model }) => { - if (this.data && this.data.querySteps) { - //const steps = [...this.data.querySteps]; - const querySteps = model; //.map((index) => steps[parseInt(index)]); - this.#updateDynamicRootQuerySteps(querySteps); - this.dispatchEvent(new UmbChangeEvent()); - } - }, - }); - } + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry.unique; + }, + identifier: 'Umb.SorterIdentifier.InputDocumentPickerRoot', + itemSelector: 'uui-ref-node', + containerSelector: '#query-steps', + onChange: ({ model }) => { + if (this.data && this.data.querySteps) { + const querySteps = model; + this.#updateDynamicRootQuerySteps(querySteps); + this.dispatchEvent(new UmbChangeEvent()); + } + }, + }); #openDynamicRootOriginPicker() { this.#openModal = this.#modalContext?.open(UMB_DYNAMIC_ROOT_ORIGIN_PICKER_MODAL, {}); @@ -127,11 +121,6 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl #updateDynamicRootQuerySteps(querySteps?: Array) { if (!this.data) return; - if (!this.#sorter) { - // NOTE: The sorter controller can only be initialized when the `#query-steps` element is available. [LK] - this.#initSorter(); - } - if (querySteps) { // NOTE: Ensure that the `unique` ID is populated for each query step. [LK] querySteps = querySteps.map((item) => (item.unique ? item : { ...item, unique: UmbId.new() })); @@ -178,10 +167,15 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl } render() { - return html`${this.#renderButton()} ${this.#renderOrigin()}`; + return html` + ${this.#renderAddOriginButton()} + ${this.#renderOrigin()} + ${this.#renderQuerySteps()} + ${this.#renderAddQueryStepButton()} ${this.#renderClearButton()} + `; } - #renderButton() { + #renderAddOriginButton() { if (this.data?.originAlias) return; return html` - - - - - - - - - ${this.#renderQuerySteps()} ${this.#renderAddQueryStepButton()} + + + + + + + `; + } + #renderClearButton() { + if (!this.#dynamicRootOrigin) return; + return html` ${this.localize.term('buttons_clearSelection')} `; } #renderQuerySteps() { if (!this.data?.querySteps) return; - return html` - - ${repeat( - this.data.querySteps, - (item) => item.unique, - (item) => this.#renderQueryStep(item), - )} - - `; + return repeat( + this.data.querySteps, + (item) => item.unique, + (item) => this.#renderQueryStep(item), + ); } #renderQueryStep(item: UmbTreePickerDynamicRootQueryStep) { @@ -245,6 +236,7 @@ export class UmbInputDocumentPickerRootElement extends FormControlMixin(UmbLitEl } #renderAddQueryStepButton() { + if (!this.#dynamicRootOrigin) return; return html`