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 <175728472+Copilot@users.noreply.github.com> * use max here --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -11,7 +11,11 @@ export interface UmbElementDetailModel {
|
||||
export interface UmbElementValueModel<ValueType = unknown> extends UmbPropertyValueData<ValueType> {
|
||||
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
|
||||
|
||||
@@ -32,7 +32,6 @@ describe('UmbValidationPropertyPathTranslationController', () => {
|
||||
value: 'value1',
|
||||
culture: null,
|
||||
segment: null,
|
||||
entityType: 'document-property-value',
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
|
||||
@@ -392,7 +392,14 @@ export abstract class UmbContentDetailWorkspaceContextBase<
|
||||
this.#segments.setValue(data?.items ?? []);
|
||||
}
|
||||
|
||||
protected override async _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
|
||||
/**
|
||||
* @deprecated Call `_processIncomingData` instead. `_scaffoldProcessData` will be removed in v.18.
|
||||
*/
|
||||
protected override _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
|
||||
return this._processIncomingData(data);
|
||||
}
|
||||
|
||||
protected override async _processIncomingData(data: DetailModelType): Promise<DetailModelType> {
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<ReturnType>;
|
||||
|
||||
setValues(values: Array<ReturnType>): void {
|
||||
this._existingValues = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the property data.
|
||||
@@ -26,7 +29,7 @@ export class UmbPropertyValuePresetBuilderController<
|
||||
*/
|
||||
async create<GivenPropertyTypesType extends UmbPropertyTypePresetModel>(
|
||||
propertyTypes: Array<GivenPropertyTypesType>,
|
||||
// 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<UmbPropertyValuePresetApiCallArgsEntityBase>,
|
||||
): Promise<Array<ReturnType>> {
|
||||
//
|
||||
@@ -104,7 +107,10 @@ export class UmbPropertyValuePresetBuilderController<
|
||||
apis: Array<UmbPropertyValuePreset>,
|
||||
propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel,
|
||||
): Promise<Array<ReturnType>> {
|
||||
const property = await this._generatePropertyValue(apis, propertyType, EMPTY_CALL_ARGS);
|
||||
const args: Partial<UmbPropertyValuePresetApiCallArgs> = {
|
||||
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<UmbPropertyValuePresetApiCallArgs>,
|
||||
): Promise<ReturnType | undefined> {
|
||||
let value: unknown = undefined;
|
||||
let value: unknown = incomingCallArgs.value;
|
||||
|
||||
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}`);
|
||||
// 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);
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -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<UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel> = [
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -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<UmbPropertyValuePresetApiCallArgs> = {
|
||||
variantId,
|
||||
value: this._existingValues?.find((x) => x.alias === alias && variantId.compare(x))?.value,
|
||||
};
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export interface UmbPropertyValuePresetApiCallArgs extends UmbPropertyValuePrese
|
||||
propertyEditorUiAlias: string;
|
||||
propertyEditorSchemaAlias?: string;
|
||||
variantId?: UmbVariantId;
|
||||
value?: unknown;
|
||||
}
|
||||
|
||||
export interface UmbPropertyTypePresetWithSchemaAliasModel extends UmbPropertyTypePresetModel {
|
||||
|
||||
@@ -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<DetailModelType>} The processed data.
|
||||
*/
|
||||
protected async _scaffoldProcessData(data: DetailModelType): Promise<DetailModelType> {
|
||||
return data;
|
||||
}
|
||||
protected async _processIncomingData(data: DetailModelType): Promise<DetailModelType> {
|
||||
return data;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.#init;
|
||||
|
||||
Reference in New Issue
Block a user