Merge branch 'main' into v14/chore/publish-cache-package-and-bundle
This commit is contained in:
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UmbModalToken } from './modal-token.js';
|
||||
|
||||
export interface UmbPropertyEditorUIPickerModalData {
|
||||
/** @deprecated This property will be removed in Umbraco 15. */
|
||||
submitLabel?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?? [] },
|
||||
};
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -11,7 +11,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
label: 'Document Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.ContentPicker',
|
||||
icon: 'icon-document',
|
||||
group: 'common',
|
||||
group: 'pickers',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Markdown Editor',
|
||||
propertyEditorSchemaAlias: 'Umbraco.MarkdownEditor',
|
||||
icon: 'icon-code',
|
||||
group: 'pickers',
|
||||
group: 'richContent',
|
||||
settings: {
|
||||
properties: [
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ const manifest: ManifestPropertyEditorUi = {
|
||||
meta: {
|
||||
label: 'Image Cropper',
|
||||
icon: 'icon-crop',
|
||||
group: 'pickers',
|
||||
group: 'media',
|
||||
propertyEditorSchemaAlias: 'Umbraco.ImageCropper',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Media Picker',
|
||||
propertyEditorSchemaAlias: 'Umbraco.MediaPicker3',
|
||||
icon: 'icon-picture',
|
||||
group: 'pickers',
|
||||
group: 'media',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const manifest: ManifestPropertyEditorUi = {
|
||||
label: 'Upload Field',
|
||||
propertyEditorSchemaAlias: 'Umbraco.UploadField',
|
||||
icon: 'icon-download-alt',
|
||||
group: 'common',
|
||||
group: 'media',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
label: 'Dropdown',
|
||||
propertyEditorSchemaAlias: 'Umbraco.DropDown.Flexible',
|
||||
icon: 'icon-list',
|
||||
group: 'pickers',
|
||||
group: 'lists',
|
||||
},
|
||||
},
|
||||
schemaManifest,
|
||||
|
||||
@@ -10,7 +10,7 @@ export const manifests: Array<ManifestTypes> = [
|
||||
meta: {
|
||||
label: 'Label',
|
||||
icon: 'icon-readonly',
|
||||
group: 'pickers',
|
||||
group: 'common',
|
||||
propertyEditorSchemaAlias: 'Umbraco.Label',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user