Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Use the right property editor UI alias for config options #2148

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/property-editor/README.md
Original file line number Diff line number Diff line change
@@ -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
31 changes: 31 additions & 0 deletions examples/property-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';

export const manifests: Array<ManifestPropertyEditorUi> = [
{
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',
},
],
defaultData: [
{
alias: 'customText',
value: 'Default value',
},
],
},
},
},
];
20 changes: 20 additions & 0 deletions examples/property-editor/property-editor.ts
Original file line number Diff line number Diff line change
@@ -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` <h1 class="uui-h2">Property Editor Example</h1> `;
}

static override styles = [UmbTextStyles];
}

export default ExamplePropertyEditor;

declare global {
interface HTMLElementTagNameMap {
'example-property-editor': ExamplePropertyEditor;
}
}
1 change: 1 addition & 0 deletions src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,7 @@ export default {
hasReferencesDeleteConsequence:
'Deleting <strong>%0%</strong> 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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -53,7 +53,9 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement {
property-editor-ui-alias=${property.propertyEditorUiAlias}
.config=${property.config}></umb-property>`,
)
: html`<div>No configuration</div>`;
: html`<umb-localize key="editdatatype_noConfiguration"
>There is no configuration for this property editor.</umb-localize
>`;
}

static override styles = [UmbTextStyles];
Expand Down
97 changes: 65 additions & 32 deletions src/packages/data-type/workspace/data-type-workspace.context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ import {
} from '@umbraco-cms/backoffice/entity-action';

type EntityType = UmbDataTypeDetailModel;

/**
* @class UmbDataTypeWorkspaceContext
* @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<EntityType>
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext
Expand Down Expand Up @@ -72,8 +90,6 @@ export class UmbDataTypeWorkspaceContext

#settingsDefaultData?: Array<PropertyEditorSettingsDefaultData>;

#propertyEditorUISettingsSchemaAlias?: string;

#propertyEditorUiIcon = new UmbStringState<string | null>(null);
readonly propertyEditorUiIcon = this.#propertyEditorUiIcon.asObservable();

Expand All @@ -82,6 +98,8 @@ export class UmbDataTypeWorkspaceContext

constructor(host: UmbControllerHost) {
super(host, 'Umb.Workspace.DataType');

this.#observePropertyEditorSchemaAlias();
this.#observePropertyEditorUIAlias();

this.routes.setRoutes([
Expand Down Expand Up @@ -121,7 +139,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]
Expand All @@ -131,30 +149,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.#observePropertyEditorUIManifest(propertyEditorUiAlias);
},
'editorUiAlias',
);
Expand All @@ -164,13 +165,19 @@ export class UmbDataTypeWorkspaceContext
return this.observe(
this.propertyEditorSchemaAlias,
(propertyEditorSchemaAlias) => {
this.#setPropertyEditorSchemaConfig(propertyEditorSchemaAlias);
this.#propertyEditorSchemaSettingsProperties = [];
this.#propertyEditorSchemaSettingsDefaultData = [];
this.#observePropertyEditorSchemaManifest(propertyEditorSchemaAlias);
},
'schemaAlias',
).asPromise();
);
}

#setPropertyEditorSchemaConfig(propertyEditorSchemaAlias?: string) {
#observePropertyEditorSchemaManifest(propertyEditorSchemaAlias?: string) {
if (!propertyEditorSchemaAlias) {
this.removeUmbControllerByAlias('schema');
return;
}
this.observe(
propertyEditorSchemaAlias
? umbExtensionsRegistry.byTypeAndAlias('propertyEditorSchema', propertyEditorSchemaAlias)
Expand All @@ -183,36 +190,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(
#observePropertyEditorUIManifest(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. This 'complexity' to prevent setting default data when initialized [NL]
const previousPropertyEditorUIAlias = this.#lastPropertyEditorUIAlias;
this.#lastPropertyEditorUIAlias = this.getPropertyEditorUiAlias();
if (
this.getIsNew() ||
(previousPropertyEditorUIAlias && previousPropertyEditorUIAlias !== this.#lastPropertyEditorUIAlias)
) {
this.#transferConfigDefaultData();
}
}
}

Expand Down Expand Up @@ -301,9 +328,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;
}
nielslyngsoe marked this conversation as resolved.
Show resolved Hide resolved
setPropertyEditorUiAlias(alias?: string) {
this.#currentData.update({ editorUiAlias: alias });
}
Expand Down
Loading