@umbraco-cms/backoffice 17.1.0-rc → 17.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -75,6 +75,7 @@ export declare abstract class UmbContentDetailWorkspaceContextBase<DetailModelTy
75
75
  * @internal
76
76
  */
77
77
  readonly languages: Observable<UmbLanguageDetailModel[]>;
78
+ getLanguages(): Array<UmbLanguageDetailModel>;
78
79
  protected readonly _segments: UmbArrayState<UmbSegmentModel, unknown>;
79
80
  readonly variantOptions: Observable<VariantOptionModelType[]>;
80
81
  protected _variantOptionsFilter: (variantOption: VariantOptionModelType) => boolean;
@@ -235,10 +236,5 @@ export declare abstract class UmbContentDetailWorkspaceContextBase<DetailModelTy
235
236
  resetState(): void;
236
237
  abstract getContentTypeUnique(): string | undefined;
237
238
  abstract createPropertyDatasetContext(host: UmbControllerHost, variantId: UmbVariantId): UmbContentPropertyDatasetContext<DetailModelType, ContentTypeDetailModelType, VariantModelType>;
238
- /**
239
- * Override reload to process incoming data through the value migration pipeline.
240
- * This ensures property values are properly transformed when variation settings change.
241
- */
242
- reload(): Promise<void>;
243
239
  destroy(): void;
244
240
  }
@@ -3,6 +3,7 @@ import { UmbContentWorkspaceDataManager } from '../manager/index.js';
3
3
  import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js';
4
4
  import { UmbContentDetailValidationPathTranslator } from './content-detail-validation-path-translator.js';
5
5
  import { UmbContentValidationToHintsManager } from './content-validation-to-hints.manager.js';
6
+ import { UmbContentDetailWorkspaceTypeTransformController } from './content-detail-workspace-type-transform.controller.js';
6
7
  import { appendToFrozenArray, mergeObservables, observeMultiple, UmbArrayState, } from '../../../../libs/observable-api/index.js';
7
8
  import { firstValueFrom, map } from '../../../../external/rxjs/index.js';
8
9
  import { umbOpenModal } from '../../../core/modal/index.js';
@@ -43,6 +44,9 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
43
44
  // TODO: Optimize this so it uses either a App Language Context? [NL]
44
45
  #languageRepository;
45
46
  #languages;
47
+ getLanguages() {
48
+ return this.#languages.getValue();
49
+ }
46
50
  #variantValidationContexts;
47
51
  getVariantValidationContext(variantId) {
48
52
  return this.#variantValidationContexts.find((x) => x.getVariantId()?.compare(variantId));
@@ -54,11 +58,6 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
54
58
  #validationRepository;
55
59
  #saveModalToken;
56
60
  #contentTypePropertyName;
57
- /**
58
- * Cache of property variation settings to detect changes when structure is updated.
59
- * Used to trigger value migration when properties change from invariant to variant or vice versa.
60
- */
61
- #propertyVariationCache;
62
61
  constructor(host, args) {
63
62
  super(host, args);
64
63
  this.IS_CONTENT_WORKSPACE_CONTEXT = true;
@@ -93,11 +92,6 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
93
92
  this._variantOptionsFilter = (variantOption) => true;
94
93
  this.#variantValidationContexts = [];
95
94
  this.#serverValidation = new UmbServerModelValidatorContext(this);
96
- /**
97
- * Cache of property variation settings to detect changes when structure is updated.
98
- * Used to trigger value migration when properties change from invariant to variant or vice versa.
99
- */
100
- this.#propertyVariationCache = new Map();
101
95
  this.finishPropertyValueChange = () => {
102
96
  this._data.finishPropertyValueChange();
103
97
  };
@@ -120,6 +114,7 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
120
114
  this.varies = this.structure.ownerContentTypeObservablePart((x) => x ? x.variesByCulture || x.variesBySegment : undefined);
121
115
  this.collection = new UmbContentCollectionManager(this, this.structure, args.collectionAlias);
122
116
  new UmbContentValidationToHintsManager(this, this.structure, this.validationContext, this.view.hints);
117
+ new UmbContentDetailWorkspaceTypeTransformController(this);
123
118
  this.variantOptions = mergeObservables([this.variesByCulture, this.variesBySegment, this.variants, this.languages, this._segments.asObservable()], ([variesByCulture, variesBySegment, variants, languages, segments]) => {
124
119
  if ((variesByCulture || variesBySegment) === undefined) {
125
120
  return [];
@@ -235,11 +230,6 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
235
230
  this.observe(this.structure.contentTypeDataTypeUniques, (dataTypeUniques) => {
236
231
  this.#dataTypeItemManager.setUniques(dataTypeUniques);
237
232
  }, null);
238
- // Observe property variation changes to trigger value migration when properties change
239
- // from invariant to variant (or vice versa) via Infinite Editing
240
- this.observe(this.structure.contentTypeProperties, (properties) => {
241
- this.#handlePropertyVariationChanges(properties);
242
- }, null);
243
233
  this.loadLanguages();
244
234
  }
245
235
  async loadLanguages() {
@@ -804,61 +794,6 @@ export class UmbContentDetailWorkspaceContextBase extends UmbEntityDetailWorkspa
804
794
  this.propertyViewGuard.fallbackToPermitted();
805
795
  this.propertyWriteGuard.fallbackToPermitted();
806
796
  }
807
- /**
808
- * Handles property variation changes when the document type is updated via Infinite Editing.
809
- * When a property's variation setting changes (e.g., from shared/invariant to variant or vice versa),
810
- * this method reloads the document to get properly migrated values from the server.
811
- */
812
- #handlePropertyVariationChanges(properties) {
813
- // Skip if no current data or if this is initial load
814
- const currentData = this._data.getCurrent();
815
- if (!currentData || this.#propertyVariationCache.size === 0) {
816
- // Initial load - just populate the cache
817
- for (const property of properties) {
818
- this.#propertyVariationCache.set(property.alias, {
819
- variesByCulture: property.variesByCulture,
820
- variesBySegment: property.variesBySegment,
821
- });
822
- }
823
- return;
824
- }
825
- // Check for variation setting changes
826
- let hasChanges = false;
827
- for (const property of properties) {
828
- const cached = this.#propertyVariationCache.get(property.alias);
829
- if (cached) {
830
- if (cached.variesByCulture !== property.variesByCulture ||
831
- cached.variesBySegment !== property.variesBySegment) {
832
- hasChanges = true;
833
- }
834
- }
835
- // Update cache
836
- this.#propertyVariationCache.set(property.alias, {
837
- variesByCulture: property.variesByCulture,
838
- variesBySegment: property.variesBySegment,
839
- });
840
- }
841
- // If variation settings changed, reload to get properly migrated values
842
- if (hasChanges) {
843
- this.reload();
844
- }
845
- }
846
- /**
847
- * Override reload to process incoming data through the value migration pipeline.
848
- * This ensures property values are properly transformed when variation settings change.
849
- */
850
- async reload() {
851
- const unique = this.getUnique();
852
- if (!unique)
853
- throw new Error('Unique is not set');
854
- const { data } = await this._detailRepository.requestByUnique(unique);
855
- if (data) {
856
- // Process the data through _processIncomingData to handle value migration
857
- const processedData = await this._processIncomingData(data);
858
- this._data.setPersisted(processedData);
859
- this._data.setCurrent(processedData);
860
- }
861
- }
862
797
  destroy() {
863
798
  this.structure.destroy();
864
799
  this.#languageRepository.destroy();
@@ -0,0 +1,12 @@
1
+ import type { UmbContentDetailModel } from '../index.js';
2
+ import type { UmbContentDetailWorkspaceContextBase } from './content-detail-workspace-base.js';
3
+ import { UmbControllerBase } from '../../../../libs/class-api/index.js';
4
+ import type { UmbEntityVariantModel } from '../../../core/variant/index.js';
5
+ /**
6
+ * @class UmbContentDetailWorkspaceTypeTransformController
7
+ * @description - Controller to handle content detail workspace type transformations, such as property variation changes.
8
+ */
9
+ export declare class UmbContentDetailWorkspaceTypeTransformController<DetailModelType extends UmbContentDetailModel<UmbEntityVariantModel>> extends UmbControllerBase {
10
+ #private;
11
+ constructor(host: UmbContentDetailWorkspaceContextBase<DetailModelType, any, any, any>);
12
+ }
@@ -0,0 +1,72 @@
1
+ import { UmbControllerBase } from '../../../../libs/class-api/index.js';
2
+ /**
3
+ * @class UmbContentDetailWorkspaceTypeTransformController
4
+ * @description - Controller to handle content detail workspace type transformations, such as property variation changes.
5
+ */
6
+ export class UmbContentDetailWorkspaceTypeTransformController extends UmbControllerBase {
7
+ #workspace;
8
+ // Current property types, currently used to detect variation changes:
9
+ #propertyTypes;
10
+ constructor(host) {
11
+ super(host);
12
+ this.#workspace = host;
13
+ // Observe property variation changes to trigger value migration when properties change
14
+ // from invariant to variant (or vice versa) via Infinite Editing
15
+ this.observe(host.structure.contentTypeProperties, (propertyTypes) => {
16
+ this.#handlePropertyTypeVariationChanges(this.#propertyTypes, propertyTypes);
17
+ this.#propertyTypes = propertyTypes;
18
+ }, null);
19
+ }
20
+ async #handlePropertyTypeVariationChanges(oldPropertyTypes, newPropertyTypes) {
21
+ if (!oldPropertyTypes || !newPropertyTypes) {
22
+ return;
23
+ }
24
+ // Skip if no current data or if this is initial load
25
+ const currentData = this.#workspace.getData();
26
+ if (!currentData) {
27
+ return;
28
+ }
29
+ const defaultLanguage = this.#getDefaultLanguage();
30
+ // To spare a bit of energy we keep awareness of whether this brings any changes:
31
+ let hasChanges = false;
32
+ const values = currentData.values.map((v) => {
33
+ const transformation = this.#transformValueForVariationChange(v, oldPropertyTypes, newPropertyTypes, defaultLanguage);
34
+ if (transformation.changed) {
35
+ hasChanges = true;
36
+ }
37
+ return transformation.value;
38
+ });
39
+ if (hasChanges) {
40
+ this.#workspace.setData({ ...currentData, values });
41
+ }
42
+ }
43
+ #getDefaultLanguage() {
44
+ const languages = this.#workspace.getLanguages();
45
+ const defaultLanguage = languages.find((lang) => lang.isDefault)?.unique;
46
+ if (!defaultLanguage) {
47
+ throw new Error('Default language not found');
48
+ }
49
+ return defaultLanguage;
50
+ }
51
+ #transformValueForVariationChange(value, oldPropertyTypes, newPropertyTypes, defaultLanguage) {
52
+ const oldType = oldPropertyTypes.find((p) => p.alias === value.alias);
53
+ const newType = newPropertyTypes.find((p) => p.alias === value.alias);
54
+ // If we cant find both, we do not dare changing anything. Notice a composition may not have been loaded yet.
55
+ if (!oldType || !newType) {
56
+ return { value, changed: false };
57
+ }
58
+ // If variation hasn't changed, return unchanged
59
+ if (oldType.variesByCulture === newType.variesByCulture) {
60
+ return { value, changed: false };
61
+ }
62
+ // Variation has changed, migrate the value
63
+ if (newType.variesByCulture) {
64
+ // If it now varies by culture, set to default language
65
+ return { value: { ...value, culture: defaultLanguage }, changed: true };
66
+ }
67
+ else {
68
+ // If it no longer varies by culture, set to invariant
69
+ return { value: { ...value, culture: null }, changed: true };
70
+ }
71
+ }
72
+ }
@@ -72,42 +72,14 @@ export class UmbPropertyValuePresetVariantBuilderController extends UmbPropertyV
72
72
  };
73
73
  return args;
74
74
  }
75
- /**
76
- * Finds an existing value for a property, with fallback logic for variation setting changes.
77
- * When a property changes from invariant to variant (or vice versa), the existing values
78
- * may have a different culture/segment than what we're looking for. This method handles
79
- * value migration by trying fallback lookups.
80
- */
81
75
  #findExistingValue(alias, variantId) {
82
- if (!this._existingValues)
76
+ if (!this._existingValues) {
83
77
  return undefined;
84
- // First, try exact match (same alias + same culture/segment)
78
+ }
85
79
  const exactMatch = this._existingValues.find((x) => x.alias === alias && variantId.compare(x));
86
80
  if (exactMatch) {
87
81
  return exactMatch.value;
88
82
  }
89
- // No exact match - try fallback for variation setting changes
90
- // Get all values for this property alias
91
- const valuesForAlias = this._existingValues.filter((x) => x.alias === alias);
92
- if (valuesForAlias.length === 0) {
93
- return undefined;
94
- }
95
- // Fallback 1: If looking for a culture-specific value, try to use the invariant value
96
- // This handles: invariant → variant migration
97
- if (variantId.culture !== null) {
98
- const invariantValue = valuesForAlias.find((x) => x.culture === null && x.segment === variantId.segment);
99
- if (invariantValue) {
100
- return invariantValue.value;
101
- }
102
- }
103
- // Fallback 2: If looking for an invariant value, try to use the first culture-specific value
104
- // This handles: variant → invariant migration
105
- if (variantId.culture === null) {
106
- const anyVariantValue = valuesForAlias.find((x) => x.culture !== null && x.segment === variantId.segment);
107
- if (anyVariantValue) {
108
- return anyVariantValue.value;
109
- }
110
- }
111
83
  return undefined;
112
84
  }
113
85
  }
@@ -62,6 +62,11 @@ export declare abstract class UmbEntityDetailWorkspaceContextBase<DetailModelTyp
62
62
  * @returns { DetailModelType | undefined } The entity context
63
63
  */
64
64
  getData(): DetailModelType | undefined;
65
+ /**
66
+ * Get the current data
67
+ * @param {DetailModelType | undefined} data - New data of this workspace.
68
+ */
69
+ setData(data: DetailModelType | undefined): void;
65
70
  /**
66
71
  * Get the persisted data
67
72
  * @returns { DetailModelType | undefined } The persisted data
@@ -145,6 +145,13 @@ export class UmbEntityDetailWorkspaceContextBase extends UmbSubmittableWorkspace
145
145
  getData() {
146
146
  return this._data.getCurrent();
147
147
  }
148
+ /**
149
+ * Get the current data
150
+ * @param {DetailModelType | undefined} data - New data of this workspace.
151
+ */
152
+ setData(data) {
153
+ this._data.setCurrent(data);
154
+ }
148
155
  /**
149
156
  * Get the persisted data
150
157
  * @returns { DetailModelType | undefined } The persisted data
@@ -231,7 +238,7 @@ export class UmbEntityDetailWorkspaceContextBase extends UmbSubmittableWorkspace
231
238
  }
232
239
  }
233
240
  else if (data) {
234
- const processedData = await this._scaffoldProcessData(data);
241
+ const processedData = await this._processIncomingData(data);
235
242
  this._data.setPersisted(processedData);
236
243
  this._data.setCurrent(processedData);
237
244
  this.observe(asObservable?.(), (entity) => this.#onDetailStoreChange(entity), 'umbEntityDetailTypeStoreObserver');
@@ -249,8 +256,9 @@ export class UmbEntityDetailWorkspaceContextBase extends UmbSubmittableWorkspace
249
256
  throw new Error('Unique is not set');
250
257
  const { data } = await this._detailRepository.requestByUnique(unique);
251
258
  if (data) {
252
- this._data.setPersisted(data);
253
- this._data.setCurrent(data);
259
+ const processedData = await this._processIncomingData(data);
260
+ this._data.setPersisted(processedData);
261
+ this._data.setCurrent(processedData);
254
262
  }
255
263
  }
256
264
  /**