Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c65e4ac
term example
nielslyngsoe Sep 23, 2025
43853ab
better localization options
nielslyngsoe Sep 23, 2025
2145719
localize range
nielslyngsoe Sep 23, 2025
be34327
ensure range value handling
nielslyngsoe Sep 23, 2025
4bb07cf
Merge branch 'main' into v16/feature/improve-slider-value-handling
nielslyngsoe Sep 23, 2025
b5ca194
extract lox high from value setting
nielslyngsoe Sep 23, 2025
c58b636
further improvements
nielslyngsoe Sep 23, 2025
162d750
stop requiring entity-type for values
nielslyngsoe Sep 23, 2025
b3d61e4
setup for parsing blueprints as values to the value preset manager
nielslyngsoe Sep 23, 2025
34d3cfc
write test for blueprint values in value preset controller
nielslyngsoe Sep 23, 2025
7917657
deprecate scaffold method in order to use a new more generic name
nielslyngsoe Sep 23, 2025
9e9bfad
Merge branch 'v16/feature/improve-slider-value-handling' into v16/fea…
nielslyngsoe Sep 23, 2025
cbd6c80
Avoid manipulating the incoming data
nielslyngsoe Sep 23, 2025
9662105
Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
nielslyngsoe Sep 23, 2025
19e4261
Merge branch 'main' into v16/feature/improve-slider-value-handling
nielslyngsoe Sep 23, 2025
8a1c1ab
use max here
nielslyngsoe Sep 23, 2025
1040455
Merge branch 'v16/feature/improve-slider-value-handling' into v16/fea…
nielslyngsoe Sep 23, 2025
17ce0fe
Merge branch 'main' into v16/feature/run-value-presets-on-load
nielslyngsoe Oct 2, 2025
ff460a4
fix import
nielslyngsoe Oct 6, 2025
ac1496f
Merge branch 'main' into v16/feature/run-value-presets-on-load
iOvergaard Oct 6, 2025
4889ad6
Update src/Umbraco.Web.UI.Client/src/packages/content/content/workspa…
nielslyngsoe Oct 6, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('UmbValidationPropertyPathTranslationController', () => {
value: 'value1',
culture: null,
segment: null,
entityType: 'document-property-value',
},
],
variants: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,85 +392,94 @@
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}'`);
}
// 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);

if (this.structure.variesBySegment) {
// TODO: v.17 Engage please note we have not implemented support for segments yet. [NL]
console.warn('Segments are not yet implemented for preset');
}
// TODO: Add Segments for Presets:
const segments: Array<string> | undefined = this.structure.variesBySegment ? [] : undefined;

const repo = new UmbDataTypeDetailRepository(this);

const propertyTypes = await this.structure.getContentTypeProperties();
const valueDefinitions = await Promise.all(
propertyTypes.map(async (property) => {
// TODO: Implement caching for data-type requests. [NL]
const dataType = (await repo.requestByUnique(property.dataType.unique)).data;
// This means if its not loaded this will never resolve and the error below will never happen.
if (!dataType) {
throw new Error(`DataType of "${property.dataType.unique}" not found.`);
}
if (!dataType.editorUiAlias) {
throw new Error(`DataType of "${property.dataType.unique}" did not have a editorUiAlias.`);
}

return {
alias: property.alias,
propertyEditorUiAlias: dataType.editorUiAlias,
propertyEditorSchemaAlias: dataType.editorAlias,
config: dataType.values,
typeArgs: {
variesByCulture: property.variesByCulture,
variesBySegment: property.variesBySegment,
} as UmbPropertyTypePresetModelTypeModel,
} as UmbPropertyTypePresetModel;
}),
);

const controller = new UmbPropertyValuePresetVariantBuilderController(this);
controller.setCultures(cultures);
if (segments) {
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];
for (let index = 0; index < presetValues.length; index++) {
const presetValue = presetValues[index];
const variantId = UmbVariantId.Create(presetValue);
const matchingDataValueIndex = dataValues.findIndex((v) => v.alias === presetValue.alias && variantId.compare(v));
if (matchingDataValueIndex > -1) {
dataValues[matchingDataValueIndex] = presetValue;
} else {
dataValues.push(presetValue);
}
}

data.values = dataValues;
*/

return data;
return { ...data, values: processedValues };

Check notice on line 482 in src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ No longer an issue: Complex Method

_scaffoldProcessData is no longer above the threshold for cyclomatic complexity. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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>> {
//
Expand Down Expand Up @@ -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] : [];
}

Expand All @@ -113,22 +119,26 @@ export class UmbPropertyValuePresetBuilderController<
propertyType: UmbPropertyTypePresetModel | UmbPropertyTypePresetWithSchemaAliasModel,
incomingCallArgs: Partial<UmbPropertyValuePresetApiCallArgs>,
): Promise<ReturnType | undefined> {
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) {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from '@open-wc/testing';

Check notice on line 1 in src/Umbraco.Web.UI.Client/src/packages/core/property/property-value-preset/property-value-preset-variant-builder.controller.test.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

✅ Getting better: Overall Code Complexity

The mean cyclomatic complexity decreases from 5.11 to 5.10, threshold = 4. This file has many conditional statements (e.g. if, for, while) across its implementation, leading to lower code health. Avoid adding more conditionals.
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
Expand Down Expand Up @@ -75,6 +75,42 @@
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
UmbPropertyTypePresetModel,
UmbPropertyTypePresetWithSchemaAliasModel,
UmbPropertyValuePreset,
UmbPropertyValuePresetApiCallArgs,
} from './types.js';
import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content';

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface UmbPropertyValuePresetApiCallArgs extends UmbPropertyValuePrese
propertyEditorUiAlias: string;
propertyEditorSchemaAlias?: string;
variantId?: UmbVariantId;
value?: unknown;
}

export interface UmbPropertyTypePresetWithSchemaAliasModel extends UmbPropertyTypePresetModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
Loading