From c41b6b492d9f4cd6187d85ba00a6c1b2b41b13bf Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Mar 2024 18:51:21 +0000 Subject: [PATCH 1/2] Collection View columns configuration Updates to the UI, to align with the layout configuration. --- ...ction-view-column-configuration.element.ts | 150 +++++++++--------- 1 file changed, 72 insertions(+), 78 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/collection-view/config/column-configuration/property-editor-ui-collection-view-column-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/collection-view/config/column-configuration/property-editor-ui-collection-view-column-configuration.element.ts index ac99056914..4445927e3e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/collection-view/config/column-configuration/property-editor-ui-collection-view-column-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/collection-view/config/column-configuration/property-editor-ui-collection-view-column-configuration.element.ts @@ -1,7 +1,8 @@ import type { UmbCollectionColumnConfiguration } from '../../../../../collection/types.js'; -import { html, customElement, property, repeat, css, state } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, repeat, css, state, nothing, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbInputContentTypePropertyElement } from '@umbraco-cms/backoffice/components'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; @@ -15,6 +16,9 @@ export class UmbPropertyEditorUICollectionViewColumnConfigurationElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + // TODO: [LK] Add sorting. + @property({ type: Array }) value?: Array = []; @@ -24,7 +28,7 @@ export class UmbPropertyEditorUICollectionViewColumnConfigurationElement @state() private _field?: UmbInputContentTypePropertyElement['selectedProperty']; - #onChange(e: CustomEvent) { + #onAdd(e: CustomEvent) { const element = e.target as UmbInputContentTypePropertyElement; if (!element.selectedProperty) return; @@ -49,6 +53,15 @@ export class UmbPropertyEditorUICollectionViewColumnConfigurationElement this.dispatchEvent(new UmbPropertyValueChangeEvent()); } + #onChangeLabel(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) { + this.value = this.value?.map( + (config): UmbCollectionColumnConfiguration => + config.alias === configuration.alias ? { ...config, header: e.target.value as string } : config, + ); + + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + } + #onRemove(unique: string) { const newValue: Array = []; @@ -60,105 +73,86 @@ export class UmbPropertyEditorUICollectionViewColumnConfigurationElement this.dispatchEvent(new UmbPropertyValueChangeEvent()); } - #onHeaderChange(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) { - this.value = this.value?.map( - (config): UmbCollectionColumnConfiguration => - config.alias === configuration.alias ? { ...config, header: e.target.value as string } : config, - ); - - this.dispatchEvent(new UmbPropertyValueChangeEvent()); - } - - #onTemplateChange(e: UUIInputEvent, configuration: UmbCollectionColumnConfiguration) { - this.value = this.value?.map( - (config): UmbCollectionColumnConfiguration => - config.alias === configuration.alias ? { ...config, nameTemplate: e.target.value as string } : config, - ); - - this.dispatchEvent(new UmbPropertyValueChangeEvent()); - } - render() { + if (!this.value) return nothing; return html` - ${this.#renderTable()} - +
+ ${repeat( + this.value, + (column) => column.alias, + (column) => this.#renderField(column), + )} +
+ @change=${this.#onAdd}> `; } - #renderTable() { - if (!this.value?.length) return; + #renderField(column: UmbCollectionColumnConfiguration) { return html` - - - - Alias - Header - Template - - - ${repeat( - this.value, - (configuration) => configuration.alias, - (configuration) => html` - - - ${configuration.isSystem === 1 - ? this.#renderSystemFieldRow(configuration) - : this.#renderCustomFieldRow(configuration)} - - this.#onRemove(configuration.alias)}> - - - `, - )} - - `; - } +
+ - #renderSystemFieldRow(configuration: UmbCollectionColumnConfiguration) { - return html` - ${configuration.alias} (system field) - ${configuration.header} - - `; - } - - #renderCustomFieldRow(configuration: UmbCollectionColumnConfiguration) { - return html` - ${configuration.alias} - this.#onHeaderChange(e, configuration)}> - - + label="label" + placeholder="Enter a label..." + .value=${column.header ?? ''} + @change=${(e: UUIInputEvent) => this.#onChangeLabel(e, column)}> + +
+ ${column.alias} +
+ this.#onTemplateChange(e, configuration)}> -
+ placeholder="Enter a name template..." + .value=${column.nameTemplate ?? ''}> + +
+ this.#onRemove(column.alias)}> +
+
`; } static styles = [ + UmbTextStyles, css` - :host { + #layout-wrapper { display: flex; flex-direction: column; - gap: var(--uui-size-space-1); + gap: 1px; + margin-bottom: var(--uui-size-1); } - uui-input { - width: 100%; + .layout-item { + background-color: var(--uui-color-surface-alt); + display: flex; + align-items: center; + gap: var(--uui-size-6); + padding: var(--uui-size-3) var(--uui-size-6); + } + + .layout-item > uui-icon { + flex: 0 0 var(--uui-size-6); + } + + .layout-item > uui-input, + .layout-item > .alias { + flex: 1; + } + + .layout-item > .actions { + flex: 0 0 auto; + display: flex; + justify-content: flex-end; } `, ]; From fc26b0e8acef26356ea7b46fb86b2f7ec3690692 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 20 Mar 2024 18:53:29 +0000 Subject: [PATCH 2/2] `umb-input-content-type-property` refinements Updated to make the Content Type picker submit on first/single selection for a smoother flow. Exports MemberType modal token. --- .../input-content-type-property.element.ts | 110 +++++++++--------- .../src/packages/members/member-type/index.ts | 2 +- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts index 8c870db936..5ea836aa88 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-content-type-property/input-content-type-property.element.ts @@ -1,18 +1,14 @@ -import { UmbDocumentTypePickerContext } from '../../../documents/document-types/components/input-document-type/input-document-type.context.js'; -import { UmbMediaTypePickerContext } from '../../../media/media-types/components/input-media-type/input-media-type.context.js'; -import { UmbMemberTypePickerContext } from '../../../members/member-type/components/input-member-type/input-member-type.context.js'; import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type'; +import { UmbChangeEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbMediaTypeDetailRepository } from '@umbraco-cms/backoffice/media-type'; -import { UmbMemberTypeDetailRepository } from '@umbraco-cms/backoffice/member-type'; -import { UMB_ITEM_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_DOCUMENT_TYPE_PICKER_MODAL, UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type'; +import { UMB_MEMBER_TYPE_PICKER_MODAL, UmbMemberTypeDetailRepository } from '@umbraco-cms/backoffice/member-type'; +import { UMB_ITEM_PICKER_MODAL, UMB_MEDIA_TYPE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; import type { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository'; -import type { UmbItemPickerModel } from '@umbraco-cms/backoffice/modal'; -import type { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import type { UmbItemPickerModel, UmbModalToken, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; export type UmbContentTypePropertyValue = { label: string; @@ -22,7 +18,7 @@ export type UmbContentTypePropertyValue = { type UmbInputContentTypePropertyConfigurationItem = { item: UmbItemPickerModel; - pickerContext(): UmbPickerInputContext; + pickerModal(): UmbModalToken; pickableFilter?(item: any): boolean; repository(): UmbDetailRepositoryBase; systemProperties?: Array; @@ -43,22 +39,19 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE label: this.localize.term('content_documentType'), description: this.localize.term('defaultdialogs_selectContentType'), value: 'documentTypes', + icon: 'icon-document', }, - pickerContext: () => new UmbDocumentTypePickerContext(this), + pickerModal: () => UMB_DOCUMENT_TYPE_PICKER_MODAL, pickableFilter: (docType) => !docType.isElement, repository: () => new UmbDocumentTypeDetailRepository(this), systemProperties: [ - { - label: this.localize.term('content_documentType'), - description: 'contentTypeAlias', - value: 'contentTypeAlias', - }, - { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, - { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner' }, - { label: this.localize.term('content_isPublished'), description: 'published', value: 'published' }, - { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder' }, - { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, - { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater' }, + { label: this.localize.term('content_documentType'), description: 'contentTypeAlias', value: 'contentTypeAlias', icon: 'icon-settings' }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate', icon: 'icon-settings' }, + { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner', icon: 'icon-settings' }, + { label: this.localize.term('content_isPublished'), description: 'published', value: 'published', icon: 'icon-settings' }, + { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder', icon: 'icon-settings' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate', icon: 'icon-settings' }, + { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater', icon: 'icon-settings' }, ], }, elementTypes: { @@ -66,8 +59,9 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE label: this.localize.term('create_elementType'), description: this.localize.term('content_nestedContentSelectElementTypeModalTitle'), value: 'elementTypes', + icon: 'icon-plugin', }, - pickerContext: () => new UmbDocumentTypePickerContext(this), + pickerModal: () => UMB_DOCUMENT_TYPE_PICKER_MODAL, pickableFilter: (docType) => docType.isElement, repository: () => new UmbDocumentTypeDetailRepository(this), systemProperties: [ @@ -83,20 +77,17 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE label: this.localize.term('content_mediatype'), description: this.localize.term('defaultdialogs_selectMediaType'), value: 'mediaTypes', + icon: 'icon-picture', }, - pickerContext: () => new UmbMediaTypePickerContext(this), + pickerModal: () => UMB_MEDIA_TYPE_PICKER_MODAL, repository: () => new UmbMediaTypeDetailRepository(this), systemProperties: [ - { - label: this.localize.term('content_documentType'), - description: 'contentTypeAlias', - value: 'contentTypeAlias', - }, - { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, - { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner' }, - { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder' }, - { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, - { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater' }, + { label: this.localize.term('content_documentType'), description: 'contentTypeAlias', value: 'contentTypeAlias', icon: 'icon-settings' }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate', icon: 'icon-settings' }, + { label: this.localize.term('content_createBy'), description: 'owner', value: 'owner', icon: 'icon-settings' }, + { label: this.localize.term('general_sort'), description: 'sortOrder', value: 'sortOrder', icon: 'icon-settings' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate', icon: 'icon-settings' }, + { label: this.localize.term('content_updatedBy'), description: 'updater', value: 'updater', icon: 'icon-settings' }, ], }, memberTypes: { @@ -104,19 +95,16 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE label: this.localize.term('content_membertype'), description: this.localize.term('defaultdialogs_selectMemberType'), value: 'memberTypes', + icon: 'icon-user', }, - pickerContext: () => new UmbMemberTypePickerContext(this), + pickerModal: () => UMB_MEMBER_TYPE_PICKER_MODAL, repository: () => new UmbMemberTypeDetailRepository(this), systemProperties: [ - { - label: this.localize.term('content_documentType'), - description: 'contentTypeAlias', - value: 'contentTypeAlias', - }, - { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate' }, - { label: this.localize.term('general_email'), description: 'email', value: 'email' }, - { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate' }, - { label: this.localize.term('general_username'), description: 'username', value: 'username' }, + { label: this.localize.term('content_documentType'), description: 'contentTypeAlias', value: 'contentTypeAlias', icon: 'icon-settings' }, + { label: this.localize.term('content_createDate'), description: 'createDate', value: 'createDate', icon: 'icon-settings' }, + { label: this.localize.term('general_email'), description: 'email', value: 'email', icon: 'icon-settings' }, + { label: this.localize.term('content_updateDate'), description: 'updateDate', value: 'updateDate', icon: 'icon-settings' }, + { label: this.localize.term('general_username'), description: 'username', value: 'username', icon: 'icon-settings' }, ], }, }; @@ -195,28 +183,33 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE const config = this.#configuration[configKey]; if (!config) return; - const pickerContext = config.pickerContext(); + const pickerModal = config.pickerModal(); - pickerContext.max = 1; - - await pickerContext.openPicker({ - hideTreeRoot: true, - multiple: false, - pickableFilter: config.pickableFilter, + const pickerContext = this.#modalManager?.open(this, pickerModal, { + data: { + hideTreeRoot: true, + multiple: false, + pickableFilter: config.pickableFilter, + }, }); - const selectedItems = pickerContext.getSelection(); + // NOTE: We listen for the selection change event to submit the picker. + // This is to enforce a single selection and progress to the next modal. + pickerContext?.addEventListener(UmbSelectionChangeEvent.TYPE, pickerContext.submit); + + const pickerValue = await pickerContext?.onSubmit(); + + const selectedItems = pickerValue?.selection ?? []; if (selectedItems.length === 0) return; const repository = config.repository(); - const { data } = await repository.requestByUnique(selectedItems[0]); - + const { data } = await repository.requestByUnique(selectedItems[0] ?? ''); if (!data) return; - this.#openPropertyPicker(data, config.systemProperties); + this.#openPropertyPicker(data, config); } - async #openPropertyPicker(contentType?: UmbContentTypeModel, systemProperties?: Array) { + async #openPropertyPicker(contentType: UmbContentTypeModel, config: UmbInputContentTypePropertyConfigurationItem) { if (!contentType) return; if (!this.#modalManager) return; @@ -225,9 +218,10 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE label: property.name, value: property.alias, description: property.alias, + icon: config.item.icon, })) ?? []; - const items = [...(systemProperties ?? []), ...properties]; + const items = [...(config.systemProperties ?? []), ...properties]; const modalContext = this.#modalManager.open(this, UMB_ITEM_PICKER_MODAL, { data: { @@ -243,7 +237,7 @@ export class UmbInputContentTypePropertyElement extends FormControlMixin(UmbLitE this.selectedProperty = { label: modalValue.label, alias: modalValue.value, - isSystem: systemProperties?.some((property) => property.value === modalValue.value) ?? false, + isSystem: config.systemProperties?.some((property) => property.value === modalValue.value) ?? false, }; this.dispatchEvent(new UmbChangeEvent()); diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/index.ts index b67effb41a..7a1a270d73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/index.ts @@ -1,10 +1,10 @@ import './components/index.js'; export * from './workspace/index.js'; - export * from './components/index.js'; export * from './repository/index.js'; export * from './entity.js'; export * from './tree/index.js'; +export * from './modal/member-type-picker-modal.token.js'; export type { UmbMemberTypeDetailModel } from './types.js';