From bd49774d28036f96f02ac9d1a14e86cc9fdd4d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 29 Jul 2024 14:56:08 +0200 Subject: [PATCH 1/6] example --- .../examples/property-editor/README.md | 7 ++++++ .../examples/property-editor/index.ts | 25 +++++++++++++++++++ .../property-editor/property-editor.ts | 20 +++++++++++++++ .../property-editor-config.element.ts | 5 ++-- 4 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/examples/property-editor/README.md create mode 100644 src/Umbraco.Web.UI.Client/examples/property-editor/index.ts create mode 100644 src/Umbraco.Web.UI.Client/examples/property-editor/property-editor.ts diff --git a/src/Umbraco.Web.UI.Client/examples/property-editor/README.md b/src/Umbraco.Web.UI.Client/examples/property-editor/README.md new file mode 100644 index 0000000000..e0894b30b7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/property-editor/README.md @@ -0,0 +1,7 @@ +# Property Dataset Dashboard Example + +This example is a work in progress example of how to write a property editor. + +This example covers a few points: + +- Using an existing Property Editor Schema diff --git a/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts b/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts new file mode 100644 index 0000000000..4d7c9c26fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts @@ -0,0 +1,25 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'propertyEditorUi', + alias: 'example.propertyEditorUi.propertyEditor', + name: 'Example Property Editor UI', + element: () => import('./property-editor.js'), + meta: { + label: 'Example Editor', + propertyEditorSchemaAlias: 'Umbraco.ListView', + icon: 'icon-code', + group: 'common', + settings: { + properties: [ + { + alias: 'customText', + label: 'Custom text', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', + }, + ], + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/property-editor/property-editor.ts b/src/Umbraco.Web.UI.Client/examples/property-editor/property-editor.ts new file mode 100644 index 0000000000..029d1f9b4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/property-editor/property-editor.ts @@ -0,0 +1,20 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +@customElement('example-property-editor') +export class ExamplePropertyEditor extends UmbElementMixin(LitElement) { + override render() { + return html`

Property Editor Example

`; + } + + static override styles = [UmbTextStyles]; +} + +export default ExamplePropertyEditor; + +declare global { + interface HTMLElementTagNameMap { + 'example-property-editor': ExamplePropertyEditor; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts index 58953b5163..1282735778 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts @@ -12,7 +12,7 @@ import { UmbDataPathPropertyValueFilter } from '@umbraco-cms/backoffice/validati */ @customElement('umb-property-editor-config') export class UmbPropertyEditorConfigElement extends UmbLitElement { - // TODO: Make this element generic, so its not bound to DATA-TYPEs. This will require moving some functionality of Data-Type-Context to this. and this might need to self provide a variant Context for its inner property editor UIs. + // TODO: Make this element generic, so its not bound to DATA-TYPEs. This will require moving some functionality of Data-Type-Context to this. and this might need to self provide a variant Context for its inner property editor UIs. [NL] #workspaceContext?: typeof UMB_DATA_TYPE_WORKSPACE_CONTEXT.TYPE; @state() @@ -53,7 +53,8 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement { property-editor-ui-alias=${property.propertyEditorUiAlias} .config=${property.config}>`, ) - : html`
No configuration
`; + : // TODO: Localize this [NL] + html`
No configuration
`; } static override styles = [UmbTextStyles]; From 76be31eb3f1452602627f61d00c0b407dbc44e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 29 Jul 2024 16:11:30 +0200 Subject: [PATCH 2/6] set default data --- src/Umbraco.Web.UI.Client/examples/property-editor/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts b/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts index 4d7c9c26fa..389e110866 100644 --- a/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/property-editor/index.ts @@ -19,6 +19,12 @@ export const manifests: Array = [ propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', }, ], + defaultData: [ + { + alias: 'customText', + value: 'Default value', + }, + ], }, }, }, From 7bcc429137a60ff6e6115389ffa7cce1e784119d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Mon, 29 Jul 2024 16:11:50 +0200 Subject: [PATCH 3/6] refactor merge data flow --- .../workspace/data-type-workspace.context.ts | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index 4442f73ec6..59b38280ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -72,8 +72,6 @@ export class UmbDataTypeWorkspaceContext #settingsDefaultData?: Array; - #propertyEditorUISettingsSchemaAlias?: string; - #propertyEditorUiIcon = new UmbStringState(null); readonly propertyEditorUiIcon = this.#propertyEditorUiIcon.asObservable(); @@ -82,6 +80,8 @@ export class UmbDataTypeWorkspaceContext constructor(host: UmbControllerHost) { super(host, 'Umb.Workspace.DataType'); + + this.#observePropertyEditorSchemaAlias(); this.#observePropertyEditorUIAlias(); this.routes.setRoutes([ @@ -121,7 +121,7 @@ export class UmbDataTypeWorkspaceContext this.#propertyEditorUISettingsDefaultData = []; this.#settingsDefaultData = undefined; - this._mergeConfigProperties(); + this.#mergeConfigProperties(); } // Hold the last set property editor ui alias, so we know when it changes, so we can reset values. [NL] @@ -131,30 +131,13 @@ export class UmbDataTypeWorkspaceContext this.observe( this.propertyEditorUiAlias, async (propertyEditorUiAlias) => { - const previousPropertyEditorUIAlias = this.#lastPropertyEditorUIAlias; - this.#lastPropertyEditorUIAlias = propertyEditorUiAlias; + this.#propertyEditorUISettingsProperties = []; + this.#propertyEditorUISettingsDefaultData = []; + // we only want to react on the change if the alias is set or null. When it is undefined something is still loading if (propertyEditorUiAlias === undefined) return; - // if the property editor ui alias is not set, we use the default alias from the schema - if (propertyEditorUiAlias === null) { - await this.#observePropertyEditorSchemaAlias(); - if (this.#propertyEditorSchemaConfigDefaultUIAlias !== null) { - this.setPropertyEditorUiAlias(this.#propertyEditorSchemaConfigDefaultUIAlias); - } - } else { - await this.#setPropertyEditorUIConfig(propertyEditorUiAlias); - this.setPropertyEditorSchemaAlias(this.#propertyEditorUISettingsSchemaAlias!); - await this.#observePropertyEditorSchemaAlias(); - } - - if ( - this.getIsNew() || - (previousPropertyEditorUIAlias && previousPropertyEditorUIAlias !== propertyEditorUiAlias) - ) { - this.#transferConfigDefaultData(); - } - this._mergeConfigProperties(); + this.#observePropertyEditorUIConfig(propertyEditorUiAlias); }, 'editorUiAlias', ); @@ -164,13 +147,19 @@ export class UmbDataTypeWorkspaceContext return this.observe( this.propertyEditorSchemaAlias, (propertyEditorSchemaAlias) => { - this.#setPropertyEditorSchemaConfig(propertyEditorSchemaAlias); + this.#propertyEditorSchemaSettingsProperties = []; + this.#propertyEditorSchemaSettingsDefaultData = []; + this.#observePropertyEditorSchemaConfig(propertyEditorSchemaAlias); }, 'schemaAlias', - ).asPromise(); + ); } - #setPropertyEditorSchemaConfig(propertyEditorSchemaAlias?: string) { + #observePropertyEditorSchemaConfig(propertyEditorSchemaAlias?: string) { + if (!propertyEditorSchemaAlias) { + this.removeUmbControllerByAlias('schema'); + return; + } this.observe( propertyEditorSchemaAlias ? umbExtensionsRegistry.byTypeAndAlias('propertyEditorSchema', propertyEditorSchemaAlias) @@ -183,36 +172,56 @@ export class UmbDataTypeWorkspaceContext })); this.#propertyEditorSchemaSettingsDefaultData = manifest?.meta.settings?.defaultData || []; this.#propertyEditorSchemaConfigDefaultUIAlias = manifest?.meta.defaultPropertyEditorUiAlias || null; + if (this.#propertyEditorSchemaConfigDefaultUIAlias && this.getPropertyEditorUiAlias() === null) { + // Fallback to the default property editor ui for this property editor schema. + this.setPropertyEditorUiAlias(this.#propertyEditorSchemaConfigDefaultUIAlias); + } + this.#mergeConfigProperties(); }, 'schema', ); } - #setPropertyEditorUIConfig(propertyEditorUIAlias: string) { - return this.observe( + #observePropertyEditorUIConfig(propertyEditorUIAlias: string | null) { + if (!propertyEditorUIAlias) { + this.removeUmbControllerByAlias('editorUi'); + return; + } + this.observe( umbExtensionsRegistry.byTypeAndAlias('propertyEditorUi', propertyEditorUIAlias), (manifest) => { this.#propertyEditorUiIcon.setValue(manifest?.meta.icon || null); this.#propertyEditorUiName.setValue(manifest?.name || null); - this.#propertyEditorUISettingsSchemaAlias = manifest?.meta.propertyEditorSchemaAlias; // Maps properties to have a weight, so they can be sorted, notice UI properties have a +1000 weight compared to schema properties. this.#propertyEditorUISettingsProperties = (manifest?.meta.settings?.properties ?? []).map((x, i) => ({ ...x, weight: x.weight ?? 1000 + i, })); this.#propertyEditorUISettingsDefaultData = manifest?.meta.settings?.defaultData || []; + this.setPropertyEditorSchemaAlias(manifest?.meta.propertyEditorSchemaAlias); + this.#mergeConfigProperties(); }, 'editorUi', - ).asPromise(); + ); } - private _mergeConfigProperties() { + #mergeConfigProperties() { if (this.#propertyEditorSchemaSettingsProperties && this.#propertyEditorUISettingsProperties) { // Reset the value to this array, and then afterwards append: this.#properties.setValue(this.#propertyEditorSchemaSettingsProperties); // Append the UI settings properties to the schema properties, so they can override the schema properties: this.#properties.append(this.#propertyEditorUISettingsProperties); + + // If new or if the alias was changed then set default values. + const previousPropertyEditorUIAlias = this.#lastPropertyEditorUIAlias; + this.#lastPropertyEditorUIAlias = this.getPropertyEditorUiAlias(); + if ( + this.getIsNew() || + (previousPropertyEditorUIAlias && previousPropertyEditorUIAlias !== this.#lastPropertyEditorUIAlias) + ) { + this.#transferConfigDefaultData(); + } } } @@ -301,9 +310,15 @@ export class UmbDataTypeWorkspaceContext this.#currentData.update({ name }); } + getPropertyEditorSchemaAlias() { + return this.#currentData.getValue()?.editorAlias; + } setPropertyEditorSchemaAlias(alias?: string) { this.#currentData.update({ editorAlias: alias }); } + getPropertyEditorUiAlias() { + return this.#currentData.getValue()?.editorUiAlias; + } setPropertyEditorUiAlias(alias?: string) { this.#currentData.update({ editorUiAlias: alias }); } From 6f1e48d72e666aba0ddda04eddfcfc7ce0a1e1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 30 Jul 2024 08:52:18 +0200 Subject: [PATCH 4/6] setting default data --- .../packages/data-type/workspace/data-type-workspace.context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index 59b38280ef..6a26a5acf9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -213,7 +213,7 @@ export class UmbDataTypeWorkspaceContext // Append the UI settings properties to the schema properties, so they can override the schema properties: this.#properties.append(this.#propertyEditorUISettingsProperties); - // If new or if the alias was changed then set default values. + // If new or if the alias was changed then set default values. This 'complexity' to prevent setting default data when initialized [NL] const previousPropertyEditorUIAlias = this.#lastPropertyEditorUIAlias; this.#lastPropertyEditorUIAlias = this.getPropertyEditorUiAlias(); if ( From cfcca5bd812578f6bc1fdfe99933736e6baace9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 30 Jul 2024 09:32:31 +0200 Subject: [PATCH 5/6] comments --- .../workspace/data-type-workspace.context.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index 6a26a5acf9..90d1dffc5a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -30,6 +30,24 @@ import { } from '@umbraco-cms/backoffice/entity-action'; type EntityType = UmbDataTypeDetailModel; + +/** + * @class uUmbDataTypeWorkspaceContext + * @description - Context for handling data type workspace + * There is two overall code flows to be aware about: + * + * propertyEditorUiAlias is observed + * loads propertyEditorUi manifest + * then the propertyEditorSchemaAlias is set to what the UI is configured for. + * + * propertyEditorSchemaAlias is observed + * loads the propertyEditorSchema manifest + * if no UI is defined then the propertyEditorSchema manifest default ui is set for the propertyEditorUiAlias. + * + * This supports two cases: + * - when editing an existing data type that only has a schema alias set, then it gets the UI set. + * - a new property editor ui is picked for a data-type, uses the data-type configuration to set the schema, if such is configured for the Property Editor UI. (The user picks the UI via the UI, the schema comes from the UI that the user picked, we store both on the data-type) + */ export class UmbDataTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext @@ -137,7 +155,7 @@ export class UmbDataTypeWorkspaceContext // we only want to react on the change if the alias is set or null. When it is undefined something is still loading if (propertyEditorUiAlias === undefined) return; - this.#observePropertyEditorUIConfig(propertyEditorUiAlias); + this.#observePropertyEditorUIManifest(propertyEditorUiAlias); }, 'editorUiAlias', ); @@ -149,13 +167,13 @@ export class UmbDataTypeWorkspaceContext (propertyEditorSchemaAlias) => { this.#propertyEditorSchemaSettingsProperties = []; this.#propertyEditorSchemaSettingsDefaultData = []; - this.#observePropertyEditorSchemaConfig(propertyEditorSchemaAlias); + this.#observePropertyEditorSchemaManifest(propertyEditorSchemaAlias); }, 'schemaAlias', ); } - #observePropertyEditorSchemaConfig(propertyEditorSchemaAlias?: string) { + #observePropertyEditorSchemaManifest(propertyEditorSchemaAlias?: string) { if (!propertyEditorSchemaAlias) { this.removeUmbControllerByAlias('schema'); return; @@ -182,7 +200,7 @@ export class UmbDataTypeWorkspaceContext ); } - #observePropertyEditorUIConfig(propertyEditorUIAlias: string | null) { + #observePropertyEditorUIManifest(propertyEditorUIAlias: string | null) { if (!propertyEditorUIAlias) { this.removeUmbControllerByAlias('editorUi'); return; From b2815fdfd174c3c4127a4047a880acafe3686a07 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Tue, 30 Jul 2024 09:52:37 +0100 Subject: [PATCH 6/6] Adds localization for "No configuration" --- src/Umbraco.Web.UI.Client/src/assets/lang/en.ts | 1 + .../property-editor-config/property-editor-config.element.ts | 5 +++-- .../data-type/workspace/data-type-workspace.context.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 0aca405fba..c6a84438d9 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -695,6 +695,7 @@ export default { hasReferencesDeleteConsequence: 'Deleting %0% will delete the properties and their data from the following items', acceptDeleteConsequence: 'I understand this action will delete the properties and data based on this Data Type', + noConfiguration: 'There is no configuration for this property editor.', }, errorHandling: { errorButDataWasSaved: diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts index 1282735778..46a9136d23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/components/property-editor-config/property-editor-config.element.ts @@ -53,8 +53,9 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement { property-editor-ui-alias=${property.propertyEditorUiAlias} .config=${property.config}>`, ) - : // TODO: Localize this [NL] - html`
No configuration
`; + : html`There is no configuration for this property editor.`; } static override styles = [UmbTextStyles]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index 90d1dffc5a..49aa80140f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -32,7 +32,7 @@ import { type EntityType = UmbDataTypeDetailModel; /** - * @class uUmbDataTypeWorkspaceContext + * @class UmbDataTypeWorkspaceContext * @description - Context for handling data type workspace * There is two overall code flows to be aware about: *