Merge branch 'main' into v14/chore/publish-cache-package-and-bundle

This commit is contained in:
Mads Rasmussen
2024-08-13 11:41:29 +02:00
committed by GitHub
15 changed files with 271 additions and 273 deletions

View File

@@ -8,8 +8,8 @@ export const manifest: ManifestPropertyEditorUi = {
meta: {
label: 'Code Editor',
propertyEditorSchemaAlias: 'Umbraco.Plain.String',
icon: 'icon-code',
group: 'common',
icon: 'icon-brackets',
group: 'richContent',
settings: {
properties: [
{

View File

@@ -1,6 +1,7 @@
import { UmbModalToken } from './modal-token.js';
export interface UmbPropertyEditorUIPickerModalData {
/** @deprecated This property will be removed in Umbraco 15. */
submitLabel?: string;
}

View File

@@ -52,9 +52,7 @@ export class UmbInputDataTypeElement extends UUIFormControlMixin(UmbLitElement,
new UmbModalRouteRegistrationController(this, UMB_DATA_TYPE_PICKER_FLOW_MODAL)
.onSetup(() => {
return {
data: {
submitLabel: 'Submit',
},
data: {},
value: { selection: this._ids ?? [] },
};
})

View File

@@ -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`
<umb-body-layout headline="Select a configuration">
<uui-box> ${this._renderDataTypes()} ${this._renderCreate()}</uui-box>
<umb-body-layout headline=${this.localize.term('defaultdialogs_selectEditorConfiguration')}>
<uui-box>${this.#renderDataTypes()} ${this.#renderCreate()}</uui-box>
<div slot="actions">
<uui-button label=${this.localize.term('general_close')} @click=${this._close}></uui-button>
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
</div>
</umb-body-layout>
`;
}
private _renderDataTypes() {
return this._dataTypes && this._dataTypes.length > 0
? html`<ul id="item-grid">
${repeat(
this._dataTypes!,
(dataType) => dataType.unique,
(dataType) =>
dataType.unique
? html` <li class="item">
<uui-button label="dataType.name" type="button" @click="${() => this._handleClick(dataType)}">
<div class="item-content">
<umb-icon name=${dataType.icon ?? 'icon-circle-dotted'} class="icon"></umb-icon>
${dataType.name}
</div>
</uui-button>
</li>`
: '',
)}
</ul>`
: '';
}
private _renderCreate() {
#renderDataTypes() {
if (!this._dataTypes?.length) return;
return html`
<uui-button id="create-button" type="button" look="placeholder" @click="${this._handleCreate}">
<ul id="item-grid">
${repeat(
this._dataTypes,
(dataType) => dataType.unique,
(dataType) => html`
<li class="item">
<uui-button label=${dataType.name} @click=${() => this.#handleClick(dataType)}>
<div class="item-content">
<umb-icon name=${dataType.icon ?? 'icon-circle-dotted'} class="icon"></umb-icon>
${dataType.name}
</div>
</uui-button>
</li>
`,
)}
</ul>
`;
}
#renderCreate() {
return html`
<uui-button id="create-button" look="placeholder" @click=${this.#handleCreate}>
<div class="content">
<uui-icon name="icon-add" class="icon"></uui-icon>
Create new
<umb-localize key="contentTypeEditor_availableEditors">Create new</umb-localize>
</div>
</uui-button>
`;
@@ -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;

View File

@@ -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<T> {
[key: string]: Array<T>;
}
@customElement('umb-data-type-picker-flow-modal')
export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
UmbDataTypePickerFlowModalData,
@@ -32,17 +29,13 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
public override set data(value: UmbDataTypePickerFlowModalData) {
super.data = value;
this._submitLabel = this.data?.submitLabel ?? this._submitLabel;
}
@state()
private _groupedDataTypes?: GroupedItems<UmbDataTypeItemModel>;
private _groupedDataTypes?: Array<{ key: string; items: Array<UmbDataTypeItemModel> }> = [];
@state()
private _groupedPropertyEditorUIs: GroupedItems<ManifestPropertyEditorUi> = {};
@state()
private _submitLabel = 'Select';
private _groupedPropertyEditorUIs: Array<{ key: string; items: Array<ManifestPropertyEditorUi> }> = [];
@state()
private _currentPage = 1;
@@ -52,13 +45,18 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
pagination = new UmbPaginationManager();
private _createDataTypeModal!: UmbModalRouteRegistrationController;
#collectionRepository;
#dataTypes: Array<UmbDataTypeItemModel> = [];
#propertyEditorUIs: Array<ManifestPropertyEditorUi> = [];
#createDataTypeModal!: UmbModalRouteRegistrationController;
#currentFilterQuery = '';
#dataTypes: Array<UmbDataTypeItemModel> = [];
#groupLookup: Record<string, string> = {};
#propertyEditorUIs: Array<ManifestPropertyEditorUi> = [];
constructor() {
super();
@@ -66,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`,
);
@@ -82,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(),
]);
@@ -101,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) => {
@@ -112,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, () => {
@@ -137,7 +138,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
};
})
.onSubmit((value) => {
this._select(value?.unique);
this.#select(value?.unique);
this._submitModal();
});
}
@@ -160,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] : [] };
}
@@ -188,98 +189,100 @@ 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));
// 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((a, b) => a.localeCompare(b))
.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((a, b) => a.localeCompare(b))
.map((key) => ({ key, items: grouped[key] }));
}
override render() {
return html`
<umb-body-layout headline="Select editor" class="uui-text">
<uui-box> ${this._renderFilter()} ${this._renderGrid()} </uui-box>
<umb-body-layout headline=${this.localize.term('defaultdialogs_selectEditor')} class="uui-text">
<uui-box> ${this.#renderFilter()} ${this.#renderGrid()} </uui-box>
<div slot="actions">
<uui-button label="Close" @click=${this._rejectModal}></uui-button>
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
</div>
</umb-body-layout>
`;
}
private _renderGrid() {
return this.#currentFilterQuery ? this._renderFilteredList() : this._renderUIs();
#renderGrid() {
return this.#currentFilterQuery ? this.#renderFilteredList() : this.#renderUIs();
}
private _renderFilter() {
#renderFilter() {
return html` <uui-input
type="search"
id="filter"
@input="${this.#onFilterInput}"
placeholder="Type to filter..."
label="Type to filter icons"
@input=${this.#onFilterInput}
placeholder=${this.localize.term('placeholders_filter')}
label=${this.localize.term('placeholders_filter')}
${umbFocus()}>
<uui-icon name="search" slot="prepend" id="filter-icon"></uui-icon>
</uui-input>`;
}
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`<p>Nothing matches your search, try another search term.</p>`;
}
return html`
${when(
dataTypesEntries.length > 0,
() =>
html` <h5 class="choice-type-headline">
<umb-localize key="contentTypeEditor_searchResultSettings">Available configurations</umb-localize>
</h5>
${this._renderDataTypes()}${this.#renderLoadMore()}`,
this._groupedDataTypes.length > 0,
() => html`
<h5 class="choice-type-headline">
<umb-localize key="contentTypeEditor_searchResultSettings">Available configurations</umb-localize>
</h5>
${this.#renderDataTypes()} ${this.#renderLoadMore()}
`,
)}
${when(
editorUIEntries.length > 0,
() =>
html` <h5 class="choice-type-headline">
<umb-localize key="contentTypeEditor_searchResultEditors">Create a new configuration</umb-localize>
</h5>
${this._renderUIs(true)}`,
this._groupedPropertyEditorUIs.length > 0,
() => html`
<h5 class="choice-type-headline">
<umb-localize key="contentTypeEditor_searchResultEditors">Create a new configuration</umb-localize>
</h5>
${this.#renderUIs(true)}
`,
)}
`;
}
@@ -289,83 +292,93 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
return html`<uui-button @click=${this.#onLoadMore} look="secondary" label="Load more"></uui-button>`;
}
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` <h5 class="category-name">${key === 'undefined' ? 'Uncategorized' : key}</h5>
${this._renderGroupDataTypes(value)}`,
return this._groupedDataTypes.map(
(group) => html`
<h5 class="category-name">${group.key}</h5>
${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` <h5 class="category-name">${key === 'undefined' ? 'Uncategorized' : key}</h5>
${this._renderGroupUIs(value, createAsNewOnPick)}`,
return this._groupedPropertyEditorUIs.map(
(group) => html`
<h5 class="category-name">${group.key}</h5>
${this.#renderGroupUIs(group.items, createAsNewOnPick)}
`,
);
}
private _renderGroupUIs(uis: Array<ManifestPropertyEditorUi>, createAsNewOnPick?: boolean) {
return html` <ul id="item-grid">
${this._dataTypePickerModalRouteBuilder
? repeat(
uis,
(propertyEditorUI) => propertyEditorUI.alias,
(propertyEditorUI) => {
return html` <li class="item">${this._renderDataTypeButton(propertyEditorUI, createAsNewOnPick)}</li>`;
},
)
: ''}
</ul>`;
#renderGroupUIs(uis: Array<ManifestPropertyEditorUi>, createAsNewOnPick?: boolean) {
return html`
<ul id="item-grid">
${this._dataTypePickerModalRouteBuilder
? repeat(
uis,
(propertyEditorUI) => propertyEditorUI.alias,
(propertyEditorUI) => {
return html`<li class="item">${this.#renderDataTypeButton(propertyEditorUI, createAsNewOnPick)}</li>`;
},
)
: ''}
</ul>
`;
}
private _renderDataTypeButton(propertyEditorUI: ManifestPropertyEditorUi, createAsNewOnPick?: boolean) {
#renderDataTypeButton(propertyEditorUI: ManifestPropertyEditorUi, createAsNewOnPick?: boolean) {
if (createAsNewOnPick) {
return html` <uui-button
label="${propertyEditorUI.meta.label || propertyEditorUI.name}"
@click=${() => this._createDataType(propertyEditorUI.alias)}>
${this._renderItemContent(propertyEditorUI)}
</uui-button>`;
return html`
<uui-button
label=${propertyEditorUI.meta.label || propertyEditorUI.name}
@click=${() => this.#createDataType(propertyEditorUI.alias)}>
${this.#renderItemContent(propertyEditorUI)}
</uui-button>
`;
} else {
return html` <uui-button
label="${propertyEditorUI.meta.label || propertyEditorUI.name}"
href=${this._dataTypePickerModalRouteBuilder!({ uiAlias: propertyEditorUI.alias })}>
${this._renderItemContent(propertyEditorUI)}
</uui-button>`;
return html`
<uui-button
label=${propertyEditorUI.meta.label || propertyEditorUI.name}
href=${this._dataTypePickerModalRouteBuilder!({ uiAlias: propertyEditorUI.alias })}>
${this.#renderItemContent(propertyEditorUI)}
</uui-button>
`;
}
}
private _renderItemContent(propertyEditorUI: ManifestPropertyEditorUi) {
return html`<div class="item-content">
<umb-icon name="${propertyEditorUI.meta.icon}" class="icon"></umb-icon>
${propertyEditorUI.meta.label || propertyEditorUI.name}
</div>`;
#renderItemContent(propertyEditorUI: ManifestPropertyEditorUi) {
return html`
<div class="item-content">
<umb-icon name=${propertyEditorUI.meta.icon} class="icon"></umb-icon>
${propertyEditorUI.meta.label || propertyEditorUI.name}
</div>
`;
}
private _renderGroupDataTypes(dataTypes: Array<UmbDataTypeItemModel>) {
return html` <ul id="item-grid">
${repeat(
dataTypes,
(dataType) => dataType.unique,
(dataType) =>
html` <li class="item" ?selected=${this.value.selection.includes(dataType.unique)}>
<uui-button .label=${dataType.name} type="button" @click="${() => this._handleDataTypeClick(dataType)}">
<div class="item-content">
<umb-icon name=${dataType.icon ?? 'icon-circle-dotted'} class="icon"></umb-icon>
${dataType.name}
</div>
</uui-button>
</li>`,
)}
</ul>`;
#renderGroupDataTypes(dataTypes: Array<UmbDataTypeItemModel>) {
return html`
<ul id="item-grid">
${repeat(
dataTypes,
(dataType) => dataType.unique,
(dataType) => html`
<li class="item" ?selected=${this.value.selection.includes(dataType.unique)}>
<uui-button .label=${dataType.name} type="button" @click=${() => this.#handleDataTypeClick(dataType)}>
<div class="item-content">
<umb-icon name=${dataType.icon ?? 'icon-circle-dotted'} class="icon"></umb-icon>
${dataType.name}
</div>
</uui-button>
</li>
`,
)}
</ul>
`;
}
static override styles = [
@@ -430,6 +443,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
height: 100%;
width: 100%;
}
#item-grid .item .icon {
font-size: 2em;
margin: auto;
@@ -440,7 +454,6 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
}
.choice-type-headline {
text-transform: capitalize;
border-bottom: 1px solid var(--uui-color-divider);
}
`,

View File

@@ -1,6 +1,7 @@
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbDataTypePickerFlowModalData {
/** @deprecated This property will be removed in Umbraco 15. */
submitLabel?: string;
}

View File

@@ -1,141 +1,131 @@
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import { fromCamelCase } from '@umbraco-cms/backoffice/utils';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
import type {
UmbPropertyEditorUIPickerModalData,
UmbPropertyEditorUIPickerModalValue,
} from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
interface GroupedPropertyEditorUIs {
[key: string]: Array<ManifestPropertyEditorUi>;
}
@customElement('umb-property-editor-ui-picker-modal')
export class UmbPropertyEditorUIPickerModalElement extends UmbModalBaseElement<
UmbPropertyEditorUIPickerModalData,
UmbPropertyEditorUIPickerModalValue
> {
@state()
private _groupedPropertyEditorUIs: GroupedPropertyEditorUIs = {};
private _groupedPropertyEditorUIs: Array<{ key: string; items: Array<ManifestPropertyEditorUi> }> = [];
@state()
private _propertyEditorUIs: Array<ManifestPropertyEditorUi> = [];
@state()
private _submitLabel = 'Select';
override connectedCallback(): void {
super.connectedCallback();
// TODO: We never parse on a submit label, so this seem weird as we don't enable this of other places.
//this._submitLabel = this.data?.submitLabel ?? this._submitLabel;
this.#usePropertyEditorUIs();
}
#usePropertyEditorUIs() {
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._propertyEditorUIs = propertyEditorUIs
.filter((propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias)
.sort((a, b) => a.meta.label.localeCompare(b.meta.label));
// TODO: groupBy is not known by TS yet
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
this._groupedPropertyEditorUIs = Object.groupBy(
this._propertyEditorUIs,
(propertyEditorUi: ManifestPropertyEditorUi) => propertyEditorUi.meta.group,
);
this.#groupPropertyEditorUIs(this._propertyEditorUIs);
});
}
#handleClick(propertyEditorUi: ManifestPropertyEditorUi) {
this.#select(propertyEditorUi.alias);
}
#select(alias: string) {
this.value = { selection: [alias] };
this.value = { selection: [propertyEditorUi.alias] };
this._submitModal();
}
#handleFilterInput(event: UUIInputEvent) {
let query = (event.target.value as string) || '';
query = query.toLowerCase();
const query = ((event.target.value as string) || '').toLowerCase();
const result = !query
? this._propertyEditorUIs
: this._propertyEditorUIs.filter((propertyEditorUI) => {
return (
propertyEditorUI.name.toLowerCase().includes(query) || propertyEditorUI.alias.toLowerCase().includes(query)
);
});
: this._propertyEditorUIs.filter(
(propertyEditorUI) =>
propertyEditorUI.name.toLowerCase().includes(query) || propertyEditorUI.alias.toLowerCase().includes(query),
);
// TODO: groupBy is not known by TS yet
this.#groupPropertyEditorUIs(result);
}
#groupPropertyEditorUIs(items: Array<ManifestPropertyEditorUi>) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
this._groupedPropertyEditorUIs = Object.groupBy(
result,
(propertyEditorUI: ManifestPropertyEditorUi) => propertyEditorUI.meta.group,
const grouped = Object.groupBy(items, (propertyEditorUi: ManifestPropertyEditorUi) =>
fromCamelCase(propertyEditorUi.meta.group),
);
this._groupedPropertyEditorUIs = Object.keys(grouped)
.sort((a, b) => a.localeCompare(b))
.map((key) => ({ key, items: grouped[key] }));
}
override render() {
return html`
<umb-body-layout headline=${this.localize.term('propertyEditorPicker_openPropertyEditorPicker')}>
<uui-box> ${this._renderFilter()} ${this._renderGrid()} </uui-box>
<uui-box>${this.#renderFilter()} ${this.#renderGrid()}</uui-box>
<div slot="actions">
<uui-button label="Close" @click=${this._rejectModal}></uui-button>
<uui-button
label="${this._submitLabel}"
look="primary"
color="positive"
@click=${this._submitModal}></uui-button>
<uui-button label=${this.localize.term('general_close')} @click=${this._rejectModal}></uui-button>
</div>
</umb-body-layout>
`;
}
private _renderFilter() {
return html` <uui-input
type="search"
id="filter"
@input="${this.#handleFilterInput}"
placeholder="Type to filter..."
label="Type to filter icons"
${umbFocus()}>
<uui-icon name="search" slot="prepend" id="filter-icon"></uui-icon>
</uui-input>`;
#renderFilter() {
return html`
<uui-input
type="search"
id="filter"
@input=${this.#handleFilterInput}
placeholder=${this.localize.term('placeholders_filter')}
label=${this.localize.term('placeholders_filter')}
${umbFocus()}>
<uui-icon name="search" slot="prepend" id="filter-icon"></uui-icon>
</uui-input>
`;
}
private _renderGrid() {
return html` ${Object.entries(this._groupedPropertyEditorUIs).map(
([key, value]) =>
html` <h4>${key}</h4>
${this._renderGroupItems(value)}`,
)}`;
}
private _renderGroupItems(groupItems: Array<ManifestPropertyEditorUi>) {
return html` <ul id="item-grid">
#renderGrid() {
return html`
${repeat(
groupItems,
(propertyEditorUI) => propertyEditorUI.alias,
(propertyEditorUI) =>
html` <li class="item" ?selected=${this.value.selection.includes(propertyEditorUI.alias)}>
<button type="button" @click="${() => this.#handleClick(propertyEditorUI)}">
<umb-icon name="${propertyEditorUI.meta.icon}" class="icon"></umb-icon>
${propertyEditorUI.meta.label || propertyEditorUI.name}
</button>
</li>`,
this._groupedPropertyEditorUIs,
(group) => group.key,
(group) => html`
<h4>${group.key}</h4>
${this.#renderGroupItems(group.items)}
`,
)}
</ul>`;
`;
}
#renderGroupItems(groupItems: Array<ManifestPropertyEditorUi>) {
return html`
<ul id="item-grid">
${repeat(
groupItems,
(propertyEditorUI) => propertyEditorUI.alias,
(propertyEditorUI) => html`
<li class="item" ?selected=${this.value.selection.includes(propertyEditorUI.alias)}>
<button type="button" @click=${() => this.#handleClick(propertyEditorUI)}>
<umb-icon name=${propertyEditorUI.meta.icon} class="icon"></umb-icon>
${propertyEditorUI.meta.label || propertyEditorUI.name}
</button>
</li>
`,
)}
</ul>
`;
}
static override styles = [
UmbTextStyles,
css`
#filter {
width: 100%;

View File

@@ -11,7 +11,7 @@ export const manifests: Array<ManifestTypes> = [
label: 'Document Picker',
propertyEditorSchemaAlias: 'Umbraco.ContentPicker',
icon: 'icon-document',
group: 'common',
group: 'pickers',
settings: {
properties: [
{

View File

@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
label: 'Markdown Editor',
propertyEditorSchemaAlias: 'Umbraco.MarkdownEditor',
icon: 'icon-code',
group: 'pickers',
group: 'richContent',
settings: {
properties: [
{

View File

@@ -9,7 +9,7 @@ const manifest: ManifestPropertyEditorUi = {
meta: {
label: 'Image Cropper',
icon: 'icon-crop',
group: 'pickers',
group: 'media',
propertyEditorSchemaAlias: 'Umbraco.ImageCropper',
},
};

View File

@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
label: 'Media Picker',
propertyEditorSchemaAlias: 'Umbraco.MediaPicker3',
icon: 'icon-picture',
group: 'pickers',
group: 'media',
},
};

View File

@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
label: 'Upload Field',
propertyEditorSchemaAlias: 'Umbraco.UploadField',
icon: 'icon-download-alt',
group: 'common',
group: 'media',
},
};

View File

@@ -11,7 +11,7 @@ export const manifests: Array<ManifestTypes> = [
label: 'Dropdown',
propertyEditorSchemaAlias: 'Umbraco.DropDown.Flexible',
icon: 'icon-list',
group: 'pickers',
group: 'lists',
},
},
schemaManifest,

View File

@@ -10,7 +10,7 @@ export const manifests: Array<ManifestTypes> = [
meta: {
label: 'Label',
icon: 'icon-readonly',
group: 'pickers',
group: 'common',
propertyEditorSchemaAlias: 'Umbraco.Label',
},
},

View File

@@ -13,7 +13,7 @@ const manifest: ManifestPropertyEditorUi = {
label: 'Rich Text Editor',
propertyEditorSchemaAlias: UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS,
icon: 'icon-browser-window',
group: 'richText',
group: 'richContent',
settings: {
properties: [
{