From cd41c72ee5c863bc804d8851e59e01cce83cacb7 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 7 Aug 2024 16:47:22 +0100 Subject: [PATCH] Refactored data-type picker flow modal Refactored the grouping code, to sort items alphabetically and `fromCamelCase` the group labels. Added grouping when filtering data-types. Added missing localization keys. --- ...ker-flow-data-type-picker-modal.element.ts | 77 +++-- .../data-type-picker-flow-modal.element.ts | 285 ++++++++++-------- 2 files changed, 188 insertions(+), 174 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts index 09b740d180..aaf4b0dfdf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts @@ -3,9 +3,9 @@ import type { UmbDataTypePickerFlowDataTypePickerModalData, UmbDataTypePickerFlowDataTypePickerModalValue, } from './data-type-picker-flow-data-type-picker-modal.token.js'; -import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbDataTypeItemModel } from '@umbraco-cms/backoffice/data-type'; @customElement('umb-data-type-picker-flow-data-type-picker-modal') @@ -25,10 +25,10 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas this._propertyEditorUiAlias = this.data.propertyEditorUiAlias; - this._observeDataTypesOf(this._propertyEditorUiAlias); + this.#observeDataTypesOf(this._propertyEditorUiAlias); } - private async _observeDataTypesOf(propertyEditorUiAlias: string) { + async #observeDataTypesOf(propertyEditorUiAlias: string) { if (!this.data) return; const dataTypeCollectionRepository = new UmbDataTypeCollectionRepository(this); @@ -40,64 +40,65 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas }); this.observe(collection.asObservable(), (dataTypes) => { - this._dataTypes = dataTypes; + this._dataTypes = dataTypes.sort((a, b) => a.name.localeCompare(b.name)); }); } - private _handleClick(dataType: UmbDataTypeItemModel) { + #handleClick(dataType: UmbDataTypeItemModel) { if (dataType.unique) { this.value = { dataTypeId: dataType.unique }; this.modalContext?.submit(); } } - private _handleCreate() { + #handleCreate() { this.value = { createNewWithPropertyEditorUiAlias: this._propertyEditorUiAlias }; this.modalContext?.submit(); } - private _close() { + #close() { this.modalContext?.reject(); } override render() { return html` - - ${this._renderDataTypes()} ${this._renderCreate()} + + ${this.#renderDataTypes()} ${this.#renderCreate()}
- +
`; } - private _renderDataTypes() { - return this._dataTypes && this._dataTypes.length > 0 - ? html`` - : ''; - } - private _renderCreate() { + #renderDataTypes() { + if (!this._dataTypes?.length) return; return html` - +
    + ${repeat( + this._dataTypes, + (dataType) => dataType.unique, + (dataType) => html` +
  • + this.#handleClick(dataType)}> +
    + + ${dataType.name} +
    +
    +
  • + `, + )} +
+ `; + } + + #renderCreate() { + return html` +
- Create new + Create new
`; @@ -174,12 +175,6 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas margin: auto; } - #category-name { - text-align: center; - display: block; - text-transform: capitalize; - font-size: 1.2rem; - } #create-button { max-width: 100px; --uui-button-padding-left-factor: 0; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts index 99ec2f2155..94255693c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts @@ -6,23 +6,20 @@ import type { UmbDataTypePickerFlowModalData, UmbDataTypePickerFlowModalValue, } from './data-type-picker-flow-modal.token.js'; -import { css, html, repeat, customElement, state, when, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; +import { UmbPaginationManager, debounce, fromCamelCase } from '@umbraco-cms/backoffice/utils'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content-type'; +import { UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/property-type'; import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbDataTypeItemModel } from '@umbraco-cms/backoffice/data-type'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; -import { umbFocus } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content-type'; -import { UMB_PROPERTY_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/property-type'; -import { UmbPaginationManager, debounce } from '@umbraco-cms/backoffice/utils'; -interface GroupedItems { - [key: string]: Array; -} @customElement('umb-data-type-picker-flow-modal') export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< UmbDataTypePickerFlowModalData, @@ -35,10 +32,10 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< } @state() - private _groupedDataTypes?: GroupedItems; + private _groupedDataTypes?: Array<{ key: string; items: Array }> = []; @state() - private _groupedPropertyEditorUIs: GroupedItems = {}; + private _groupedPropertyEditorUIs: Array<{ key: string; items: Array }> = []; @state() private _currentPage = 1; @@ -48,13 +45,18 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< pagination = new UmbPaginationManager(); - private _createDataTypeModal!: UmbModalRouteRegistrationController; - #collectionRepository; - #dataTypes: Array = []; - #propertyEditorUIs: Array = []; + + #createDataTypeModal!: UmbModalRouteRegistrationController; + #currentFilterQuery = ''; + #dataTypes: Array = []; + + #groupLookup: Record = {}; + + #propertyEditorUIs: Array = []; + constructor() { super(); @@ -62,10 +64,10 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< this.#init(); } - private _createDataType(propertyEditorUiAlias: string) { + #createDataType(propertyEditorUiAlias: string) { // TODO: Could be nice with a more pretty way to prepend to the URL: // Open create modal: - this._createDataTypeModal.open( + this.#createDataTypeModal.open( { uiAlias: propertyEditorUiAlias }, `create/parent/${UMB_DATA_TYPE_ENTITY_TYPE}/null`, ); @@ -78,10 +80,13 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< this.#initPromise = Promise.all([ this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => { // Only include Property Editor UIs which has Property Editor Schema Alias - this.#propertyEditorUIs = propertyEditorUIs.filter( - (propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias, - ); - this._performFiltering(); + this.#propertyEditorUIs = propertyEditorUIs + .filter((propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias) + .sort((a, b) => a.meta.label.localeCompare(b.meta.label)); + + this.#groupLookup = Object.fromEntries(propertyEditorUIs.map((ui) => [ui.alias, ui.meta.group])); + + this.#performFiltering(); }).asPromise(), ]); @@ -97,10 +102,10 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< }) .onSubmit((submitData) => { if (submitData?.dataTypeId) { - this._select(submitData.dataTypeId); + this.#select(submitData.dataTypeId); this._submitModal(); } else if (submitData?.createNewWithPropertyEditorUiAlias) { - this._createDataType(submitData.createNewWithPropertyEditorUiAlias); + this.#createDataType(submitData.createNewWithPropertyEditorUiAlias); } }) .observeRouteBuilder((routeBuilder) => { @@ -108,7 +113,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< this.requestUpdate('_dataTypePickerModalRouteBuilder'); }); - this._createDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_DATATYPE_WORKSPACE_MODAL) + this.#createDataTypeModal = new UmbModalRouteRegistrationController(this, UMB_DATATYPE_WORKSPACE_MODAL) .addAdditionalPath(':uiAlias') .onSetup(async (params) => { const contentContextConsumer = this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, () => { @@ -133,7 +138,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< }; }) .onSubmit((value) => { - this._select(value?.unique); + this.#select(value?.unique); this._submitModal(); }); } @@ -156,14 +161,14 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< } } - private _handleDataTypeClick(dataType: UmbDataTypeItemModel) { + #handleDataTypeClick(dataType: UmbDataTypeItemModel) { if (dataType.unique) { - this._select(dataType.unique); + this.#select(dataType.unique); this._submitModal(); } } - private _select(unique: string | undefined) { + #select(unique: string | undefined) { this.value = { selection: unique ? [unique] : [] }; } @@ -184,98 +189,102 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< async #handleFiltering() { await this.#getDataTypes(); - this._performFiltering(); + this.#performFiltering(); } - private _performFiltering() { + #performFiltering() { if (this.#currentFilterQuery) { - const filteredDataTypes = this.#dataTypes.filter((dataType) => - dataType.name?.toLowerCase().includes(this.#currentFilterQuery), + const filteredDataTypes = this.#dataTypes + .filter((dataType) => dataType.name?.toLowerCase().includes(this.#currentFilterQuery)) + .sort((a, b) => a.name.localeCompare(b.name)); + + // TODO: groupBy is not known by TS yet + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const grouped = Object.groupBy(filteredDataTypes, (dataType: UmbDataTypeItemModel) => + fromCamelCase(this.#groupLookup[dataType.propertyEditorUiAlias] ?? 'Uncategorized'), ); - /* TODO: data type items doesn't have a group property. We will need a reference to the Property Editor UI to get the group. - this is a temp solution to group them as uncategorized. The same result as with the lodash groupBy. - */ - this._groupedDataTypes = { - undefined: filteredDataTypes, - }; + this._groupedDataTypes = Object.keys(grouped) + .sort() + .map((key) => ({ key, items: grouped[key] })); } else { - this._groupedDataTypes = undefined; + this._groupedDataTypes = []; } const filteredUIs = !this.#currentFilterQuery ? this.#propertyEditorUIs - : this.#propertyEditorUIs.filter((propertyEditorUI) => { - return ( + : this.#propertyEditorUIs.filter( + (propertyEditorUI) => propertyEditorUI.name.toLowerCase().includes(this.#currentFilterQuery) || - propertyEditorUI.alias.toLowerCase().includes(this.#currentFilterQuery) - ); - }); + propertyEditorUI.alias.toLowerCase().includes(this.#currentFilterQuery), + ); // TODO: groupBy is not known by TS yet // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - this._groupedPropertyEditorUIs = Object.groupBy( - filteredUIs, - (propertyEditorUI: ManifestPropertyEditorUi) => propertyEditorUI.meta.group, + const grouped = Object.groupBy(filteredUIs, (propertyEditorUi: ManifestPropertyEditorUi) => + fromCamelCase(propertyEditorUi.meta.group ?? 'Uncategorized'), ); + + this._groupedPropertyEditorUIs = Object.keys(grouped) + .sort() + .map((key) => ({ key, items: grouped[key] })); } override render() { return html` - - ${this._renderFilter()} ${this._renderGrid()} + + ${this.#renderFilter()} ${this.#renderGrid()}
- +
`; } - private _renderGrid() { - return this.#currentFilterQuery ? this._renderFilteredList() : this._renderUIs(); + #renderGrid() { + return this.#currentFilterQuery ? this.#renderFilteredList() : this.#renderUIs(); } - private _renderFilter() { + #renderFilter() { return html` `; } - private _renderFilteredList() { + #renderFilteredList() { if (!this._groupedDataTypes) return nothing; - const dataTypesEntries = Object.entries(this._groupedDataTypes); - if (!this._groupedPropertyEditorUIs) return nothing; - const editorUIEntries = Object.entries(this._groupedPropertyEditorUIs); - - if (dataTypesEntries.length === 0 && editorUIEntries.length === 0) { - return html`Nothing matches your search, try another search term.`; + if (this._groupedDataTypes.length === 0 && this._groupedPropertyEditorUIs.length === 0) { + return html`

Nothing matches your search, try another search term.

`; } return html` ${when( - dataTypesEntries.length > 0, - () => - html`
- Available configurations -
- ${this._renderDataTypes()}${this.#renderLoadMore()}`, + this._groupedDataTypes.length > 0, + () => html` +
+ Available configurations +
+ ${this.#renderDataTypes()} ${this.#renderLoadMore()} + `, )} ${when( - editorUIEntries.length > 0, - () => - html`
- Create a new configuration -
- ${this._renderUIs(true)}`, + this._groupedPropertyEditorUIs.length > 0, + () => html` +
+ Create a new configuration +
+ ${this.#renderUIs(true)} + `, )} `; } @@ -285,83 +294,93 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< return html``; } - private _renderDataTypes() { + #renderDataTypes() { if (!this._groupedDataTypes) return nothing; - const entries = Object.entries(this._groupedDataTypes); - // TODO: Fix so we can have Data Types grouped. (or choose not to group them) - return entries.map( - ([key, value]) => - html`
${key === 'undefined' ? 'Uncategorized' : key}
- ${this._renderGroupDataTypes(value)}`, + return this._groupedDataTypes.map( + (group) => html` +
${group.key}
+ ${this.#renderGroupDataTypes(group.items)} + `, ); } - private _renderUIs(createAsNewOnPick?: boolean) { + #renderUIs(createAsNewOnPick?: boolean) { if (!this._groupedPropertyEditorUIs) return nothing; - const entries = Object.entries(this._groupedPropertyEditorUIs); - - return entries.map( - ([key, value]) => - html`
${key === 'undefined' ? 'Uncategorized' : key}
- ${this._renderGroupUIs(value, createAsNewOnPick)}`, + return this._groupedPropertyEditorUIs.map( + (group) => html` +
${group.key}
+ ${this.#renderGroupUIs(group.items, createAsNewOnPick)} + `, ); } - private _renderGroupUIs(uis: Array, createAsNewOnPick?: boolean) { - return html`
    - ${this._dataTypePickerModalRouteBuilder - ? repeat( - uis, - (propertyEditorUI) => propertyEditorUI.alias, - (propertyEditorUI) => { - return html`
  • ${this._renderDataTypeButton(propertyEditorUI, createAsNewOnPick)}
  • `; - }, - ) - : ''} -
`; + #renderGroupUIs(uis: Array, createAsNewOnPick?: boolean) { + return html` +
    + ${this._dataTypePickerModalRouteBuilder + ? repeat( + uis, + (propertyEditorUI) => propertyEditorUI.alias, + (propertyEditorUI) => { + return html`
  • ${this.#renderDataTypeButton(propertyEditorUI, createAsNewOnPick)}
  • `; + }, + ) + : ''} +
+ `; } - private _renderDataTypeButton(propertyEditorUI: ManifestPropertyEditorUi, createAsNewOnPick?: boolean) { + #renderDataTypeButton(propertyEditorUI: ManifestPropertyEditorUi, createAsNewOnPick?: boolean) { if (createAsNewOnPick) { - return html` this._createDataType(propertyEditorUI.alias)}> - ${this._renderItemContent(propertyEditorUI)} - `; + return html` + this.#createDataType(propertyEditorUI.alias)}> + ${this.#renderItemContent(propertyEditorUI)} + + `; } else { - return html` - ${this._renderItemContent(propertyEditorUI)} - `; + return html` + + ${this.#renderItemContent(propertyEditorUI)} + + `; } } - private _renderItemContent(propertyEditorUI: ManifestPropertyEditorUi) { - return html`
- - ${propertyEditorUI.meta.label || propertyEditorUI.name} -
`; + + #renderItemContent(propertyEditorUI: ManifestPropertyEditorUi) { + return html` +
+ + ${propertyEditorUI.meta.label || propertyEditorUI.name} +
+ `; } - private _renderGroupDataTypes(dataTypes: Array) { - return html`
    - ${repeat( - dataTypes, - (dataType) => dataType.unique, - (dataType) => - html`
  • - -
    - - ${dataType.name} -
    -
    -
  • `, - )} -
`; + #renderGroupDataTypes(dataTypes: Array) { + return html` +
    + ${repeat( + dataTypes, + (dataType) => dataType.unique, + (dataType) => html` +
  • + this.#handleDataTypeClick(dataType)}> +
    + + ${dataType.name} +
    +
    +
  • + `, + )} +
+ `; } static override styles = [ @@ -426,6 +445,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< height: 100%; width: 100%; } + #item-grid .item .icon { font-size: 2em; margin: auto; @@ -436,7 +456,6 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< } .choice-type-headline { - text-transform: capitalize; border-bottom: 1px solid var(--uui-color-divider); } `,