From d58a0dcccf3af5022efc1a953860f863823916cb Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Mar 2024 18:35:59 +0000 Subject: [PATCH 1/6] Feature `umb-ref-item` For use within the Item Picker modal. --- .../src/packages/core/components/index.ts | 1 + .../core/components/ref-item/index.ts | 1 + .../components/ref-item/ref-item.element.ts | 75 +++++++++++++++++++ .../item-picker/item-picker-modal.element.ts | 34 ++++++--- .../modal/token/item-picker-modal.token.ts | 3 +- 5 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/ref-item.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index a79524f077..a47b5370bc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -30,4 +30,5 @@ export * from './input-upload-field/index.js'; export * from './multiple-color-picker-input/index.js'; export * from './multiple-text-string-input/index.js'; export * from './popover-layout/index.js'; +export * from './ref-item/index.js'; export * from './table/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/index.ts new file mode 100644 index 0000000000..2ba7f18300 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/index.ts @@ -0,0 +1 @@ +export * from './ref-item.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/ref-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/ref-item.element.ts new file mode 100644 index 0000000000..04cdbaf84a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/ref-item/ref-item.element.ts @@ -0,0 +1,75 @@ +import { html, customElement, css, property, when, nothing, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UUIRefElement, UUIRefEvent, UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui'; + +@customElement('umb-ref-item') +export class UmbRefItemElement extends UmbElementMixin(UUIRefElement) { + @property({ type: String }) + name = ''; + + @property({ type: String }) + detail = ''; + + @property({ type: String }) + icon = ''; + + constructor() { + super(); + + this.selectable = true; + + this.addEventListener(UUIRefEvent.OPEN, () => this.dispatchEvent(new Event('click'))); + } + + public render() { + return html` + +
+ + `; + } + + static styles = [ + ...UUIRefElement.styles, + ...UUIRefNodeElement.styles, + css` + :host { + padding: calc(var(--uui-size-4) + 1px); + } + + #btn-item { + text-decoration: none; + color: inherit; + align-self: stretch; + line-height: normal; + + display: flex; + position: relative; + align-items: center; + cursor: pointer; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-ref-item': UmbRefItemElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts index 893a1f9797..62e3788c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts @@ -1,4 +1,12 @@ -import { css, html, customElement, repeat, nothing, when } from '@umbraco-cms/backoffice/external/lit'; +import { + css, + html, + customElement, + repeat, + nothing, + when, + ifDefined, +} from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbItemPickerModalData, UmbItemPickerModel } from '@umbraco-cms/backoffice/modal'; @@ -24,16 +32,20 @@ export class UmbItemPickerModalElement extends UmbModalBaseElement html` - ${repeat( - items, - (item) => item.value, - (item) => html` - this.#submit(item)} look="placeholder" label="${item.label}"> -

${item.label}

-

${item.description}

-
- `, - )} + + ${repeat( + items, + (item) => item.value, + (item) => html` + this.#submit(item)}> + + `, + )} +
`, () => html`

There are no items to select.

`, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/item-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/item-picker-modal.token.ts index c55a63cb8f..365c2f8088 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/item-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/item-picker-modal.token.ts @@ -6,8 +6,9 @@ export type UmbItemPickerModalData = { }; export type UmbItemPickerModel = { - label: string; description?: string; + icon?: string; + label: string; value: string; }; From 812b1dce0dfffc2c33896871ba56f3de15b5f600 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Mar 2024 18:36:54 +0000 Subject: [PATCH 2/6] Updates DynamicRoot modals to use `umb-ref-item` --- ...ynamic-root-origin-picker-modal.element.ts | 25 +++++++++++-------- ...ic-root-query-step-picker-modal.element.ts | 25 +++++++++++-------- 2 files changed, 28 insertions(+), 22 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 c641283f66..80c10bb693 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 @@ -64,19 +64,22 @@ export class UmbDynamicRootOriginPickerModalModalElement extends UmbModalBaseEle render() { return html` - +
- ${repeat( - this._origins, - (item) => item.alias, - (item) => html` - this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}"> -

${item.meta.label}

-

${item.meta.description}

-
- `, - )} + + ${repeat( + this._origins, + (item) => item.alias, + (item) => html` + this.#choose(item)}> + `, + )} +
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 9eee8c8d58..7d079983c9 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 @@ -52,19 +52,22 @@ export class UmbDynamicRootQueryStepPickerModalModalElement extends UmbModalBase render() { return html` - +
- ${repeat( - this._querySteps, - (item) => item.alias, - (item) => html` - this.#choose(item)} look="placeholder" label="${ifDefined(item.meta.label)}"> -

${item.meta.label}

-

${item.meta.description}

-
- `, - )} + + ${repeat( + this._querySteps, + (item) => item.alias, + (item) => html` + this.#choose(item)}> + `, + )} +
From 2d526911d21797d5e9a881646348c7b1fc119f68 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 21 Mar 2024 11:31:09 +0100 Subject: [PATCH 3/6] remove debug element in dashboard --- .../published-status/dashboard-published-status.element.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts index a5b796a9e8..526de9e794 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/dashboards/published-status/dashboard-published-status.element.ts @@ -111,9 +111,7 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement { } render() { - // TODO: Are we supposed to have the debug element here? return html` -

${this._publishedStatusText}

Date: Thu, 21 Mar 2024 11:10:19 +0100 Subject: [PATCH 4/6] Feature: Sorter for block grid and list config editor --- ...i-block-grid-type-configuration.element.ts | 37 +++++++++++++++++-- ...i-block-list-type-configuration.element.ts | 16 ++++++-- .../input-block-type.element.ts | 36 +++++++++++++++--- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts index edc11c5282..f8dff44840 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-type-configuration/property-editor-ui-block-grid-type-configuration.element.ts @@ -35,6 +35,7 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement extends UmbLitElement implements UmbPropertyEditorUiElement { + #moveData?: Array; #sorter = new UmbSorterController(this, { getUniqueOfElement: (element) => element.getAttribute('data-umb-group-key'), getUniqueOfModel: (modelEntry) => modelEntry.key!, @@ -128,13 +129,40 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement this.#sorter.setModel(this._groupsWithBlockTypes); } - #onChange(e: CustomEvent, groupKey?: string) { + #onDelete(e: CustomEvent, groupKey?: string) { const updatedValues = (e.target as UmbInputBlockTypeElement).value.map((value) => ({ ...value, groupKey })); const filteredValues = this.value.filter((value) => value.groupKey !== groupKey); this.value = [...filteredValues, ...updatedValues]; this.dispatchEvent(new UmbPropertyValueChangeEvent()); } + async #onChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbInputBlockTypeElement; + const value = element.value; + + if (!e.detail?.moveComplete) { + // Container change, store data of the new group... + const newGroupKey = element.getAttribute('data-umb-group-key'); + const movedItem = e.detail?.item as UmbBlockTypeWithGroupKey; + // Check if item moved back to original group... + movedItem.groupKey === newGroupKey + ? (this.#moveData = undefined) + : (this.#moveData = value.map((block) => ({ ...block, groupKey: newGroupKey }))); + } else if (e.detail?.moveComplete) { + // Move complete, get the blocks that were in an untouched group + const blocks = this.value + .filter((block) => !value.find((value) => value.contentElementTypeKey === block.contentElementTypeKey)) + .filter( + (block) => !this.#moveData?.find((value) => value.contentElementTypeKey === block.contentElementTypeKey), + ); + + this.value = this.#moveData ? [...blocks, ...value, ...this.#moveData] : [...blocks, ...value]; + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + this.#moveData = undefined; + } + } + #onCreate(e: CustomEvent, groupKey?: string) { const selectedElementType = e.detail.contentElementTypeKey; if (selectedElementType) { @@ -170,8 +198,9 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement ? html` this.#onCreate(e, undefined)} - @change=${(e: CustomEvent) => this.#onChange(e, undefined)}>` + @delete=${(e: CustomEvent) => this.#onDelete(e, undefined)}>` : ''} ${repeat( this._groupsWithBlockTypes, @@ -180,10 +209,12 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement html`
${group.key ? this.#renderGroupInput(group.key, group.name) : nothing} this.#onCreate(e, group.key)} - @change=${(e: CustomEvent) => this.#onChange(e, group.key)}> + @delete=${(e: CustomEvent) => this.#onDelete(e, group.key)}>
`, )}
`; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts index 39525dbc90..b49deaecdb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts @@ -3,7 +3,10 @@ import '../../../block-type/components/input-block-type/index.js'; import { UMB_BLOCK_LIST_TYPE } from '../../types.js'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { + UmbPropertyValueChangeEvent, + type UmbPropertyEditorConfigCollection, +} from '@umbraco-cms/backoffice/property-editor'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal'; @@ -51,14 +54,19 @@ export class UmbPropertyEditorUIBlockListBlockConfigurationElement } } + #onChange(e: CustomEvent) { + e.stopPropagation(); + this.value = (e.target as UmbInputBlockTypeElement).value; + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + } + render() { return html` { - this.value = (e.target as UmbInputBlockTypeElement).value; - }}>`; + @delete=${this.#onChange} + @change=${this.#onChange}>`; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts index 5643b99105..ad408e2314 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts @@ -1,20 +1,41 @@ -import type { UmbBlockTypeBaseModel } from '../../types.js'; +import type { UmbBlockTypeCardElement } from '../block-type-card/index.js'; +import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../../types.js'; import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal'; import '../block-type-card/index.js'; import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; import { UMB_DOCUMENT_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/document-type'; +import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; @customElement('umb-input-block-type') export class UmbInputBlockTypeElement< - BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel, + BlockType extends UmbBlockTypeWithGroupKey = UmbBlockTypeWithGroupKey, > extends UmbLitElement { + #sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => element.contentElementTypeKey, + getUniqueOfModel: (modelEntry) => modelEntry.contentElementTypeKey!, + itemSelector: 'umb-block-type-card', + identifier: 'umb-block-type-sorter', + containerSelector: '#blocks', + onChange: ({ model }) => { + this._items = model; + }, + onContainerChange: ({ model, item }) => { + this._items = model; + this.dispatchEvent(new CustomEvent('change', { detail: { item } })); + }, + onEnd: () => { + this.dispatchEvent(new CustomEvent('change', { detail: { moveComplete: true } })); + }, + }); + @property({ type: Array, attribute: false }) public set value(items) { this._items = items ?? []; + this.#sorter.setModel(this._items); } public get value() { return this._items; @@ -67,7 +88,7 @@ export class UmbInputBlockTypeElement< deleteItem(contentElementTypeKey: string) { this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey); - this.dispatchEvent(new UmbChangeEvent()); + this.dispatchEvent(new UmbDeleteEvent()); } protected getFormElement() { @@ -85,7 +106,7 @@ export class UmbInputBlockTypeElement< } render() { - return html`
+ return html`
${repeat(this.value, (block) => block.contentElementTypeKey, this.#renderItem)} ${this.#renderButton()}
`; } @@ -93,6 +114,7 @@ export class UmbInputBlockTypeElement< #renderItem = (block: BlockType) => { return html` Date: Wed, 20 Mar 2024 18:39:44 +0000 Subject: [PATCH 5/6] Adds filter input to Item Picker modal --- .../item-picker/item-picker-modal.element.ts | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts index 62e3788c6b..579ec7bec7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/item-picker/item-picker-modal.element.ts @@ -5,6 +5,7 @@ import { repeat, nothing, when, + state, ifDefined, } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; @@ -13,21 +14,49 @@ import type { UmbItemPickerModalData, UmbItemPickerModel } from '@umbraco-cms/ba @customElement('umb-item-picker-modal') export class UmbItemPickerModalElement extends UmbModalBaseElement { + @state() + private _filtered: Array = []; + #close() { this.modalContext?.reject(); } + #filter(event: { target: HTMLInputElement }) { + if (!this.data) return; + + if (event.target.value) { + const query = event.target.value.toLowerCase(); + this._filtered = this.data.items.filter( + (item) => item.label.toLowerCase().includes(query) || item.value.toLowerCase().includes(query), + ); + } else { + this._filtered = this.data.items; + } + } + #submit(item: UmbItemPickerModel) { this.modalContext?.setValue(item); this.modalContext?.submit(); } + connectedCallback() { + super.connectedCallback(); + + if (!this.data) return; + this._filtered = this.data.items; + } + render() { if (!this.data) return nothing; - const items = this.data.items; + const items = this._filtered; return html` -
+
+ +
+ +
+
${when( items.length, () => html` @@ -61,6 +90,16 @@ export class UmbItemPickerModalElement extends UmbModalBaseElement uui-input { + width: 100%; + } + uui-box > uui-button { display: block; --uui-button-content-align: flex-start; From 37e33b277f2a2dc12cf6b73766374659218e5e9c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Mar 2024 18:40:35 +0000 Subject: [PATCH 6/6] modalContext move setter to `@property` --- .../packages/core/modal/component/modal-base.element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-base.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-base.element.ts index c4a1defa21..a1aeb0725c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-base.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal-base.element.ts @@ -16,10 +16,8 @@ export abstract class UmbModalBaseElement< @property({ type: Object, attribute: false }) public manifest?: ModalManifestType; + #modalContext?: UmbModalContext | undefined; @property({ attribute: false }) - public get modalContext(): UmbModalContext | undefined { - return this.#modalContext; - } public set modalContext(context: UmbModalContext | undefined) { this.#modalContext = context; if (context) { @@ -35,7 +33,9 @@ export abstract class UmbModalBaseElement< ); } } - #modalContext?: UmbModalContext | undefined; + public get modalContext(): UmbModalContext | undefined { + return this.#modalContext; + } @property({ attribute: false }) public set data(value: ModalDataType | undefined) {