diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts index 2d23f998dd7a..67873bffb961 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts @@ -92,6 +92,7 @@ export abstract class UmbContentTypeWorkspaceContextBase< let { data } = await request; if (data) { + data = await this._processIncomingData(data); data = await this._scaffoldProcessData(data); if (this.modalContext) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts index aee796bc65e3..a0e1ffddadec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts @@ -11,7 +11,11 @@ export interface UmbElementDetailModel { export interface UmbElementValueModel extends UmbPropertyValueData { culture: string | null; editorAlias: string; - entityType: string; + /** + * @deprecated, we do not use entityType on values anymore. To be removed in Umbraco v.18. + * Just remove the property. + **/ + entityType?: string; segment: string | null; } // eslint-disable-next-line @typescript-eslint/no-empty-object-type diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts index 0b5d379f5170..5f341330df7e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts @@ -32,7 +32,6 @@ describe('UmbValidationPropertyPathTranslationController', () => { value: 'value1', culture: null, segment: null, - entityType: 'document-property-value', }, ], variants: [], diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index 162da85e149d..3a3d2cba5d89 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -392,7 +392,14 @@ export abstract class UmbContentDetailWorkspaceContextBase< this.#segments.setValue(data?.items ?? []); } - protected override async _scaffoldProcessData(data: DetailModelType): Promise { + /** + * @deprecated Call `_processIncomingData` instead. `_scaffoldProcessData` will be removed in v.18. + */ + protected override _scaffoldProcessData(data: DetailModelType): Promise { + return this._processIncomingData(data); + } + + protected override async _processIncomingData(data: DetailModelType): Promise { const contentTypeUnique: string | undefined = (data as any)[this.#contentTypePropertyName].unique; if (!contentTypeUnique) { throw new Error(`Could not find content type unique on property '${this.#contentTypePropertyName}'`); @@ -400,10 +407,6 @@ export abstract class UmbContentDetailWorkspaceContextBase< // Load the content type structure, usually this comes from the data, but in this case we are making the data, and we need this to be able to complete the data. [NL] await this.structure.loadType(contentTypeUnique); - /** - * TODO: Should we also set Preset Values when loading Content, because maybe content contains uncreated Cultures or Segments. - */ - // Set culture and segment for all values: const cultures = this.#languages.getValue().map((x) => x.unique); @@ -448,12 +451,17 @@ export abstract class UmbContentDetailWorkspaceContextBase< controller.setSegments(segments); } - const presetValues = await controller.create(valueDefinitions, { + controller.setValues(data.values); + + const processedValues = await controller.create(valueDefinitions, { entityType: this.getEntityType(), entityUnique: data.unique, entityTypeUnique: contentTypeUnique, }); + /* + const presetValues = ... + // Don't just set the values, as we could have some already populated from a blueprint. // If we have a value from both a blueprint and a preset, use the latter as priority. const dataValues = [...data.values]; @@ -469,8 +477,9 @@ export abstract class UmbContentDetailWorkspaceContextBase< } data.values = dataValues; + */ - return data; + return { ...data, values: processedValues }; } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts index e73b840640c4..ddbdd19103c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts @@ -11,12 +11,15 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -const EMPTY_CALL_ARGS = Object.freeze({}); - export class UmbPropertyValuePresetBuilderController< - ReturnType = UmbPropertyValueData | UmbPropertyValueDataPotentiallyWithEditorAlias, + ReturnType extends UmbPropertyValueData = UmbPropertyValueData | UmbPropertyValueDataPotentiallyWithEditorAlias, > extends UmbControllerBase { #baseCreateArgs?: UmbPropertyValuePresetApiCallArgsEntityBase; + protected _existingValues?: Array; + + setValues(values: Array): void { + this._existingValues = values; + } /** * Clones the property data. @@ -26,7 +29,7 @@ export class UmbPropertyValuePresetBuilderController< */ async create( propertyTypes: Array, - // TODO: Remove Option argument and Partial<> in v.17.0 [NL] + // TODO: Remove that the argument is Optional and as well remove Partial<> in v.17.0 [NL] createArgs?: Partial, ): Promise> { // @@ -104,7 +107,10 @@ export class UmbPropertyValuePresetBuilderController< apis: Array, propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel, ): Promise> { - const property = await this._generatePropertyValue(apis, propertyType, EMPTY_CALL_ARGS); + const args: Partial = { + value: this._existingValues?.find((x) => x.alias === propertyType.alias)?.value, + }; + const property = await this._generatePropertyValue(apis, propertyType, args); return property ? [property] : []; } @@ -113,22 +119,26 @@ export class UmbPropertyValuePresetBuilderController< propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel, incomingCallArgs: Partial, ): Promise { - let value: unknown = undefined; - - const callArgs: UmbPropertyValuePresetApiCallArgs = { - ...this.#baseCreateArgs!, - alias: propertyType.alias, - propertyEditorUiAlias: propertyType.propertyEditorUiAlias, - propertyEditorSchemaAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel).propertyEditorSchemaAlias, - ...incomingCallArgs, - }; - // Important to use a inline for loop, to secure that each entry is processed(asynchronously) in order - for (const api of apis) { - if (!api.processValue) { - throw new Error(`'processValue()' method is not defined in the api: ${api.constructor.name}`); - } + let value: unknown = incomingCallArgs.value; - value = await api.processValue(value, propertyType.config, propertyType.typeArgs, callArgs); + // Only process value if it is `undefined`, if a property has a value we do not want to process it. [NL] + if (value === undefined) { + const callArgs: UmbPropertyValuePresetApiCallArgs = { + ...this.#baseCreateArgs!, + alias: propertyType.alias, + propertyEditorUiAlias: propertyType.propertyEditorUiAlias, + propertyEditorSchemaAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel) + .propertyEditorSchemaAlias, + ...incomingCallArgs, + }; + // Important to use a inline for loop, to secure that each entry is processed(asynchronously) in order + for (const api of apis) { + if (!api.processValue) { + throw new Error(`'processValue()' method is not defined in the api: ${api.constructor.name}`); + } + + value = await api.processValue(value, propertyType.config, propertyType.typeArgs, callArgs); + } } if (value === undefined) { @@ -140,7 +150,7 @@ export class UmbPropertyValuePresetBuilderController< editorAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel).propertyEditorSchemaAlias, alias: propertyType.alias, value, - } satisfies UmbPropertyValueDataPotentiallyWithEditorAlias as ReturnType; + } satisfies UmbPropertyValueDataPotentiallyWithEditorAlias as UmbPropertyValueDataPotentiallyWithEditorAlias as ReturnType; } else { return { alias: propertyType.alias, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts index dda4302a4cf7..178658ca9425 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts @@ -75,6 +75,42 @@ describe('UmbPropertyValuePresetVariantBuilderController', () => { expect(result[1]?.culture).to.be.equal('cultureB'); }); + it('uses the preset value when creating a culture variant values', async () => { + const ctrlHost = new UmbTestControllerHostElement(); + const ctrl = new UmbPropertyValuePresetVariantBuilderController(ctrlHost); + ctrl.setCultures(['cultureA', 'cultureB']); + ctrl.setValues([ + { + alias: 'test', + value: 'blueprint value for cultureB', + culture: 'cultureB', + segment: null, + editorAlias: 'test-editor-schema', + }, + ]); + + const propertyTypes: Array = [ + { + alias: 'test', + propertyEditorUiAlias: 'test-editor-ui', + propertyEditorSchemaAlias: 'test-editor-schema', + config: [], + typeArgs: { variesByCulture: true }, + }, + ]; + + const result = await ctrl.create(propertyTypes, { + entityType: 'test', + entityUnique: 'some-unique', + }); + + expect(result.length).to.be.equal(2); + expect(result[0]?.value).to.be.equal('value for culture cultureA'); + expect(result[0]?.culture).to.be.equal('cultureA'); + expect(result[1]?.value).to.be.equal('blueprint value for cultureB'); + expect(result[1]?.culture).to.be.equal('cultureB'); + }); + it('creates culture variant values when no cultures available should fail', async () => { const ctrlHost = new UmbTestControllerHostElement(); const ctrl = new UmbPropertyValuePresetVariantBuilderController(ctrlHost); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.ts index 80370c44ece3..ebe8f677d34b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.ts @@ -4,6 +4,7 @@ import type { UmbPropertyTypePresetModel, UmbPropertyTypePresetWithSchemaAliasModel, UmbPropertyValuePreset, + UmbPropertyValuePresetApiCallArgs, } from './types.js'; import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; @@ -37,9 +38,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV for (const culture of this.#cultures) { for (const segment of this.#segments) { - const value = await this._generatePropertyValue(apis, propertyType, { - variantId: new UmbVariantId(culture, segment), - }); + const value = await this._generatePropertyValue( + apis, + propertyType, + this.#makeArgsFor(propertyType.alias, culture, segment), + ); if (value) { value.culture = culture; value.segment = segment; @@ -53,9 +56,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV } for (const culture of this.#cultures) { - const value = await this._generatePropertyValue(apis, propertyType, { - variantId: new UmbVariantId(culture), - }); + const value = await this._generatePropertyValue( + apis, + propertyType, + this.#makeArgsFor(propertyType.alias, culture, null), + ); if (value) { value.culture = culture; value.segment = null; @@ -64,9 +69,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV } } else if (propertyType.typeArgs.variesBySegment) { for (const segment of this.#segments) { - const value = await this._generatePropertyValue(apis, propertyType, { - variantId: new UmbVariantId(null, segment), - }); + const value = await this._generatePropertyValue( + apis, + propertyType, + this.#makeArgsFor(propertyType.alias, null, segment), + ); if (value) { // Be aware this maybe should have been the default culture? value.culture = null; @@ -75,7 +82,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV } } } else { - const value = await this._generatePropertyValue(apis, propertyType, {}); + const value = await this._generatePropertyValue( + apis, + propertyType, + this.#makeArgsFor(propertyType.alias, null, null), + ); if (value) { // Be aware this maybe should have been the default culture? value.culture = null; @@ -85,4 +96,13 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV } return values; } + + #makeArgsFor(alias: string, culture: null | string, segment: null | string) { + const variantId = new UmbVariantId(culture, segment); + const args: Partial = { + variantId, + value: this._existingValues?.find((x) => x.alias === alias && variantId.compare(x))?.value, + }; + return args; + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/types.ts index c749e53612f0..28872a6995f2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/types.ts @@ -45,6 +45,7 @@ export interface UmbPropertyValuePresetApiCallArgs extends UmbPropertyValuePrese propertyEditorUiAlias: string; propertyEditorSchemaAlias?: string; variantId?: UmbVariantId; + value?: unknown; } export interface UmbPropertyTypePresetWithSchemaAliasModel extends UmbPropertyTypePresetModel { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index 4b021f1abde6..8325a12bacd7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -253,8 +253,10 @@ export abstract class UmbEntityDetailWorkspaceContextBase< } } } else if (data) { - this._data.setPersisted(data); - this._data.setCurrent(data); + const processedData = await this._processIncomingData(data); + + this._data.setPersisted(processedData); + this._data.setCurrent(processedData); this.observe(asObservable?.(), (entity) => this.#onDetailStoreChange(entity), 'umbEntityDetailTypeStoreObserver'); } @@ -309,6 +311,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase< let { data } = await request; if (data) { + data = await this._processIncomingData(data); data = await this._scaffoldProcessData(data); if (this.modalContext) { @@ -327,9 +330,17 @@ export abstract class UmbEntityDetailWorkspaceContextBase< return data; } + /** + * @deprecated Override `_processIncomingData` instead. `_scaffoldProcessData` will be removed in v.18. + * @param {DetailModelType} data - The data to process. + * @returns {Promise} The processed data. + */ protected async _scaffoldProcessData(data: DetailModelType): Promise { return data; } + protected async _processIncomingData(data: DetailModelType): Promise { + return data; + } async submit() { await this.#init;