Skip to content

Commit 7819b6d

Browse files
Value Presets: also run value presets on load (#20239)
* term example * better localization options * localize range * ensure range value handling * extract lox high from value setting * further improvements * stop requiring entity-type for values * setup for parsing blueprints as values to the value preset manager * write test for blueprint values in value preset controller * deprecate scaffold method in order to use a new more generic name * Avoid manipulating the incoming data * Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts Co-authored-by: Copilot <[email protected]> * use max here --------- Co-authored-by: Copilot <[email protected]>
1 parent 11b6251 commit 7819b6d

File tree

9 files changed

+133
-42
lines changed

9 files changed

+133
-42
lines changed

src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export abstract class UmbContentTypeWorkspaceContextBase<
9292
let { data } = await request;
9393

9494
if (data) {
95+
data = await this._processIncomingData(data);
9596
data = await this._scaffoldProcessData(data);
9697

9798
if (this.modalContext) {

src/Umbraco.Web.UI.Client/src/packages/content/content/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export interface UmbElementDetailModel {
1111
export interface UmbElementValueModel<ValueType = unknown> extends UmbPropertyValueData<ValueType> {
1212
culture: string | null;
1313
editorAlias: string;
14-
entityType: string;
14+
/**
15+
* @deprecated, we do not use entityType on values anymore. To be removed in Umbraco v.18.
16+
* Just remove the property.
17+
**/
18+
entityType?: string;
1519
segment: string | null;
1620
}
1721
// eslint-disable-next-line @typescript-eslint/no-empty-object-type

src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-validation-path-translator.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('UmbValidationPropertyPathTranslationController', () => {
3232
value: 'value1',
3333
culture: null,
3434
segment: null,
35-
entityType: 'document-property-value',
3635
},
3736
],
3837
variants: [],

src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -392,18 +392,21 @@ export abstract class UmbContentDetailWorkspaceContextBase<
392392
this.#segments.setValue(data?.items ?? []);
393393
}
394394

395-
protected override async _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
395+
/**
396+
* @deprecated Call `_processIncomingData` instead. `_scaffoldProcessData` will be removed in v.18.
397+
*/
398+
protected override _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
399+
return this._processIncomingData(data);
400+
}
401+
402+
protected override async _processIncomingData(data: DetailModelType): Promise<DetailModelType> {
396403
const contentTypeUnique: string | undefined = (data as any)[this.#contentTypePropertyName].unique;
397404
if (!contentTypeUnique) {
398405
throw new Error(`Could not find content type unique on property '${this.#contentTypePropertyName}'`);
399406
}
400407
// 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]
401408
await this.structure.loadType(contentTypeUnique);
402409

403-
/**
404-
* TODO: Should we also set Preset Values when loading Content, because maybe content contains uncreated Cultures or Segments.
405-
*/
406-
407410
// Set culture and segment for all values:
408411
const cultures = this.#languages.getValue().map((x) => x.unique);
409412

@@ -448,12 +451,17 @@ export abstract class UmbContentDetailWorkspaceContextBase<
448451
controller.setSegments(segments);
449452
}
450453

451-
const presetValues = await controller.create(valueDefinitions, {
454+
controller.setValues(data.values);
455+
456+
const processedValues = await controller.create(valueDefinitions, {
452457
entityType: this.getEntityType(),
453458
entityUnique: data.unique,
454459
entityTypeUnique: contentTypeUnique,
455460
});
456461

462+
/*
463+
const presetValues = ...
464+
457465
// Don't just set the values, as we could have some already populated from a blueprint.
458466
// If we have a value from both a blueprint and a preset, use the latter as priority.
459467
const dataValues = [...data.values];
@@ -469,8 +477,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
469477
}
470478
471479
data.values = dataValues;
480+
*/
472481

473-
return data;
482+
return { ...data, values: processedValues };
474483
}
475484

476485
/**

src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-builder.controller.ts

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
1111
import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
1212
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
1313

14-
const EMPTY_CALL_ARGS = Object.freeze({});
15-
1614
export class UmbPropertyValuePresetBuilderController<
17-
ReturnType = UmbPropertyValueData | UmbPropertyValueDataPotentiallyWithEditorAlias,
15+
ReturnType extends UmbPropertyValueData = UmbPropertyValueData | UmbPropertyValueDataPotentiallyWithEditorAlias,
1816
> extends UmbControllerBase {
1917
#baseCreateArgs?: UmbPropertyValuePresetApiCallArgsEntityBase;
18+
protected _existingValues?: Array<ReturnType>;
19+
20+
setValues(values: Array<ReturnType>): void {
21+
this._existingValues = values;
22+
}
2023

2124
/**
2225
* Clones the property data.
@@ -26,7 +29,7 @@ export class UmbPropertyValuePresetBuilderController<
2629
*/
2730
async create<GivenPropertyTypesType extends UmbPropertyTypePresetModel>(
2831
propertyTypes: Array<GivenPropertyTypesType>,
29-
// TODO: Remove Option argument and Partial<> in v.17.0 [NL]
32+
// TODO: Remove that the argument is Optional and as well remove Partial<> in v.17.0 [NL]
3033
createArgs?: Partial<UmbPropertyValuePresetApiCallArgsEntityBase>,
3134
): Promise<Array<ReturnType>> {
3235
//
@@ -104,7 +107,10 @@ export class UmbPropertyValuePresetBuilderController<
104107
apis: Array<UmbPropertyValuePreset>,
105108
propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel,
106109
): Promise<Array<ReturnType>> {
107-
const property = await this._generatePropertyValue(apis, propertyType, EMPTY_CALL_ARGS);
110+
const args: Partial<UmbPropertyValuePresetApiCallArgs> = {
111+
value: this._existingValues?.find((x) => x.alias === propertyType.alias)?.value,
112+
};
113+
const property = await this._generatePropertyValue(apis, propertyType, args);
108114
return property ? [property] : [];
109115
}
110116

@@ -113,22 +119,26 @@ export class UmbPropertyValuePresetBuilderController<
113119
propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel,
114120
incomingCallArgs: Partial<UmbPropertyValuePresetApiCallArgs>,
115121
): Promise<ReturnType | undefined> {
116-
let value: unknown = undefined;
117-
118-
const callArgs: UmbPropertyValuePresetApiCallArgs = {
119-
...this.#baseCreateArgs!,
120-
alias: propertyType.alias,
121-
propertyEditorUiAlias: propertyType.propertyEditorUiAlias,
122-
propertyEditorSchemaAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel).propertyEditorSchemaAlias,
123-
...incomingCallArgs,
124-
};
125-
// Important to use a inline for loop, to secure that each entry is processed(asynchronously) in order
126-
for (const api of apis) {
127-
if (!api.processValue) {
128-
throw new Error(`'processValue()' method is not defined in the api: ${api.constructor.name}`);
129-
}
122+
let value: unknown = incomingCallArgs.value;
130123

131-
value = await api.processValue(value, propertyType.config, propertyType.typeArgs, callArgs);
124+
// Only process value if it is `undefined`, if a property has a value we do not want to process it. [NL]
125+
if (value === undefined) {
126+
const callArgs: UmbPropertyValuePresetApiCallArgs = {
127+
...this.#baseCreateArgs!,
128+
alias: propertyType.alias,
129+
propertyEditorUiAlias: propertyType.propertyEditorUiAlias,
130+
propertyEditorSchemaAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel)
131+
.propertyEditorSchemaAlias,
132+
...incomingCallArgs,
133+
};
134+
// Important to use a inline for loop, to secure that each entry is processed(asynchronously) in order
135+
for (const api of apis) {
136+
if (!api.processValue) {
137+
throw new Error(`'processValue()' method is not defined in the api: ${api.constructor.name}`);
138+
}
139+
140+
value = await api.processValue(value, propertyType.config, propertyType.typeArgs, callArgs);
141+
}
132142
}
133143

134144
if (value === undefined) {
@@ -140,7 +150,7 @@ export class UmbPropertyValuePresetBuilderController<
140150
editorAlias: (propertyType as UmbPropertyTypePresetWithSchemaAliasModel).propertyEditorSchemaAlias,
141151
alias: propertyType.alias,
142152
value,
143-
} satisfies UmbPropertyValueDataPotentiallyWithEditorAlias as ReturnType;
153+
} satisfies UmbPropertyValueDataPotentiallyWithEditorAlias as UmbPropertyValueDataPotentiallyWithEditorAlias as ReturnType;
144154
} else {
145155
return {
146156
alias: propertyType.alias,

src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,42 @@ describe('UmbPropertyValuePresetVariantBuilderController', () => {
7575
expect(result[1]?.culture).to.be.equal('cultureB');
7676
});
7777

78+
it('uses the preset value when creating a culture variant values', async () => {
79+
const ctrlHost = new UmbTestControllerHostElement();
80+
const ctrl = new UmbPropertyValuePresetVariantBuilderController(ctrlHost);
81+
ctrl.setCultures(['cultureA', 'cultureB']);
82+
ctrl.setValues([
83+
{
84+
alias: 'test',
85+
value: 'blueprint value for cultureB',
86+
culture: 'cultureB',
87+
segment: null,
88+
editorAlias: 'test-editor-schema',
89+
},
90+
]);
91+
92+
const propertyTypes: Array<UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel> = [
93+
{
94+
alias: 'test',
95+
propertyEditorUiAlias: 'test-editor-ui',
96+
propertyEditorSchemaAlias: 'test-editor-schema',
97+
config: [],
98+
typeArgs: { variesByCulture: true },
99+
},
100+
];
101+
102+
const result = await ctrl.create(propertyTypes, {
103+
entityType: 'test',
104+
entityUnique: 'some-unique',
105+
});
106+
107+
expect(result.length).to.be.equal(2);
108+
expect(result[0]?.value).to.be.equal('value for culture cultureA');
109+
expect(result[0]?.culture).to.be.equal('cultureA');
110+
expect(result[1]?.value).to.be.equal('blueprint value for cultureB');
111+
expect(result[1]?.culture).to.be.equal('cultureB');
112+
});
113+
78114
it('creates culture variant values when no cultures available should fail', async () => {
79115
const ctrlHost = new UmbTestControllerHostElement();
80116
const ctrl = new UmbPropertyValuePresetVariantBuilderController(ctrlHost);

src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
UmbPropertyTypePresetModel,
55
UmbPropertyTypePresetWithSchemaAliasModel,
66
UmbPropertyValuePreset,
7+
UmbPropertyValuePresetApiCallArgs,
78
} from './types.js';
89
import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content';
910

@@ -37,9 +38,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
3738

3839
for (const culture of this.#cultures) {
3940
for (const segment of this.#segments) {
40-
const value = await this._generatePropertyValue(apis, propertyType, {
41-
variantId: new UmbVariantId(culture, segment),
42-
});
41+
const value = await this._generatePropertyValue(
42+
apis,
43+
propertyType,
44+
this.#makeArgsFor(propertyType.alias, culture, segment),
45+
);
4346
if (value) {
4447
value.culture = culture;
4548
value.segment = segment;
@@ -53,9 +56,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
5356
}
5457

5558
for (const culture of this.#cultures) {
56-
const value = await this._generatePropertyValue(apis, propertyType, {
57-
variantId: new UmbVariantId(culture),
58-
});
59+
const value = await this._generatePropertyValue(
60+
apis,
61+
propertyType,
62+
this.#makeArgsFor(propertyType.alias, culture, null),
63+
);
5964
if (value) {
6065
value.culture = culture;
6166
value.segment = null;
@@ -64,9 +69,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
6469
}
6570
} else if (propertyType.typeArgs.variesBySegment) {
6671
for (const segment of this.#segments) {
67-
const value = await this._generatePropertyValue(apis, propertyType, {
68-
variantId: new UmbVariantId(null, segment),
69-
});
72+
const value = await this._generatePropertyValue(
73+
apis,
74+
propertyType,
75+
this.#makeArgsFor(propertyType.alias, null, segment),
76+
);
7077
if (value) {
7178
// Be aware this maybe should have been the default culture?
7279
value.culture = null;
@@ -75,7 +82,11 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
7582
}
7683
}
7784
} else {
78-
const value = await this._generatePropertyValue(apis, propertyType, {});
85+
const value = await this._generatePropertyValue(
86+
apis,
87+
propertyType,
88+
this.#makeArgsFor(propertyType.alias, null, null),
89+
);
7990
if (value) {
8091
// Be aware this maybe should have been the default culture?
8192
value.culture = null;
@@ -85,4 +96,13 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
8596
}
8697
return values;
8798
}
99+
100+
#makeArgsFor(alias: string, culture: null | string, segment: null | string) {
101+
const variantId = new UmbVariantId(culture, segment);
102+
const args: Partial<UmbPropertyValuePresetApiCallArgs> = {
103+
variantId,
104+
value: this._existingValues?.find((x) => x.alias === alias && variantId.compare(x))?.value,
105+
};
106+
return args;
107+
}
88108
}

src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface UmbPropertyValuePresetApiCallArgs extends UmbPropertyValuePrese
4545
propertyEditorUiAlias: string;
4646
propertyEditorSchemaAlias?: string;
4747
variantId?: UmbVariantId;
48+
value?: unknown;
4849
}
4950

5051
export interface UmbPropertyTypePresetWithSchemaAliasModel extends UmbPropertyTypePresetModel {

src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,10 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
253253
}
254254
}
255255
} else if (data) {
256-
this._data.setPersisted(data);
257-
this._data.setCurrent(data);
256+
const processedData = await this._processIncomingData(data);
257+
258+
this._data.setPersisted(processedData);
259+
this._data.setCurrent(processedData);
258260

259261
this.observe(asObservable?.(), (entity) => this.#onDetailStoreChange(entity), 'umbEntityDetailTypeStoreObserver');
260262
}
@@ -309,6 +311,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
309311
let { data } = await request;
310312

311313
if (data) {
314+
data = await this._processIncomingData(data);
312315
data = await this._scaffoldProcessData(data);
313316

314317
if (this.modalContext) {
@@ -327,9 +330,17 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
327330
return data;
328331
}
329332

333+
/**
334+
* @deprecated Override `_processIncomingData` instead. `_scaffoldProcessData` will be removed in v.18.
335+
* @param {DetailModelType} data - The data to process.
336+
* @returns {Promise<DetailModelType>} The processed data.
337+
*/
330338
protected async _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
331339
return data;
332340
}
341+
protected async _processIncomingData(data: DetailModelType): Promise<DetailModelType> {
342+
return data;
343+
}
333344

334345
async submit() {
335346
await this.#init;

0 commit comments

Comments
 (0)