From df1738aec7bd2c27ab6e912dbb591cbb856caae2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 19 Mar 2025 20:32:48 +0100 Subject: [PATCH 1/7] add read only state --- .../structure/content-type-structure-manager.class.ts | 2 ++ .../src/packages/core/property/index.ts | 6 ++++-- .../core/property/property-read-only-state.manager.ts | 10 ++++++++++ .../src/packages/core/variant/index.ts | 2 ++ .../variant-property-read-only-state.manager.ts | 9 +++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/property/property-read-only-state.manager.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts index e3512137cb01..9e78d815f775 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts @@ -20,6 +20,7 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; import { + UmbVariantPropertyReadOnlyStateManager, UmbVariantPropertyReadStateManager, UmbVariantPropertyWriteStateManager, } from '@umbraco-cms/backoffice/variant'; @@ -104,6 +105,7 @@ export class UmbContentTypeStructureManager< public readonly propertyReadState = new UmbVariantPropertyReadStateManager(this); public readonly propertyWriteState = new UmbVariantPropertyWriteStateManager(this); + public readonly propertyReadOnlyState = new UmbVariantPropertyReadOnlyStateManager(this); #containers: UmbArrayState = new UmbArrayState( [], diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts index ed281fb9774f..6ee121b492ea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts @@ -1,10 +1,12 @@ export * from './components/index.js'; export * from './conditions/index.js'; export * from './property-dataset/index.js'; +export * from './property-read-only-state.manager.js'; +export * from './property-read-state.manager.js'; +export * from './property-read-state.manager.js'; export * from './property-value-cloner/property-value-clone.controller.js'; export * from './property-value-preset/index.js'; -export * from './property-read-state.manager.js'; export * from './property-write-state.manager.js'; -export * from './property-read-state.manager.js'; export * from './property-write-state.manager.js'; + export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-read-only-state.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-read-only-state.manager.ts new file mode 100644 index 000000000000..bdd967107733 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-read-only-state.manager.ts @@ -0,0 +1,10 @@ +import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; +import { UmbReadOnlyStateManager, type UmbState } from '@umbraco-cms/backoffice/utils'; + +export interface UmbPropertyReadOnlyState extends UmbState { + propertyType: UmbReferenceByUnique; +} + +export class UmbPropertyReadOnlyStateManager< + ReadOnlyStateType extends UmbPropertyReadOnlyState = UmbPropertyReadOnlyState, +> extends UmbReadOnlyStateManager {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts index 831be38b4a56..ccfce4897814 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts @@ -1,5 +1,7 @@ export * from './variant-id.class.js'; export * from './variant-object-compare.function.js'; +export * from './variant-property-read-only-state.manager.js'; export * from './variant-property-read-state.manager.js'; export * from './variant-property-write-state.manager.js'; + export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts new file mode 100644 index 000000000000..cb6eeed1f070 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts @@ -0,0 +1,9 @@ +import type { UmbReferenceByVariantId } from './types.js'; +import { UmbPropertyReadOnlyStateManager, type UmbPropertyReadOnlyState } from '@umbraco-cms/backoffice/property'; +import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; + +export interface UmbVariantPropertyReadOnlyState extends UmbPropertyReadOnlyState { + propertyType: UmbReferenceByUnique & UmbReferenceByVariantId; +} + +export class UmbVariantPropertyReadOnlyStateManager extends UmbPropertyReadOnlyStateManager {} From 31eede6176284fb4da4450b734509175372ca221 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 19 Mar 2025 20:52:10 +0100 Subject: [PATCH 2/7] handle read only property state in properties element --- .../edit/content-editor-properties.element.ts | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/views/edit/content-editor-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/views/edit/content-editor-properties.element.ts index c8deaa05f809..516a8092b494 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/views/edit/content-editor-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/views/edit/content-editor-properties.element.ts @@ -41,6 +41,9 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement @state() _propertyWriteStates: Array = []; + @state() + _propertyReadOnlyStates: Array = []; + constructor() { super(); @@ -61,6 +64,12 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement (states) => (this._propertyWriteStates = states), 'umbObservePropertyWriteStates', ); + + this.observe( + workspaceContext.structure.propertyReadOnlyState.states, + (states) => (this._propertyReadOnlyStates = states), + 'umbObservePropertyWriteStates', + ); }); this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (datasetContext) => { @@ -103,9 +112,20 @@ export class UmbContentWorkspaceViewEditPropertiesElement extends UmbLitElement #isWritablePropertyType(property: UmbPropertyTypeModel) { const propertyVariantId = this.#getPropertyVariantId(property); - return this._propertyWriteStates.some( + + // Check if the property is writable + const isWriteAllowed = this._propertyWriteStates.some( (state) => state.propertyType.unique === property.unique && state.propertyType.variantId.equal(propertyVariantId), ); + + // Check if the property is read only + const isReadOnly = this._propertyReadOnlyStates.some( + (state) => state.propertyType.unique === property.unique && state.propertyType.variantId.equal(propertyVariantId), + ); + + // If the property has a read only state it will override the write state + // and the property will always be read only + return isWriteAllowed && !isReadOnly; } #getPropertyVariantId(property: UmbPropertyTypeModel) { From 1c57c3263246f3ce3d1bfe086b1833b9bbbf17c0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 20 Mar 2025 20:33:17 +0100 Subject: [PATCH 3/7] prevent editing shared props on non default --- .../document-property-dataset.context.ts | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts index 5b34ca61e1cd..3b7954e5f611 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts @@ -1,9 +1,46 @@ -import type { UmbDocumentDetailModel, UmbDocumentVariantModel } from '../types.js'; +import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentWorkspaceContext } from '../types.js'; import { UmbContentPropertyDatasetContext } from '@umbraco-cms/backoffice/content'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { UmbVariantId, type UmbVariantPropertyReadOnlyState } from '@umbraco-cms/backoffice/variant'; export class UmbDocumentPropertyDatasetContext extends UmbContentPropertyDatasetContext< UmbDocumentDetailModel, UmbDocumentTypeDetailModel, UmbDocumentVariantModel -> {} +> { + constructor(host: UmbControllerHost, dataOwner: UmbDocumentWorkspaceContext, variantId?: UmbVariantId) { + super(host, dataOwner, variantId); + + this.observe( + observeMultiple([this._dataOwner.structure.contentTypeProperties, this._dataOwner.variantOptions]), + ([properties, variantOptions]) => { + if (properties.length === 0) return; + if (variantOptions.length === 0) return; + + const currentVariantOption = variantOptions.find((option) => option.culture === variantId?.culture); + const isDefaultLanguage = currentVariantOption?.language.isDefault; + + properties.forEach((property) => { + const unique = 'UMB_PREVENT_SHARED_PROPERTY_EDITING_FROM_NON_DEFAULT_' + property.unique; + + this._dataOwner.structure.propertyReadOnlyState.removeState(unique); + + if (!property.variesByCulture && !isDefaultLanguage) { + const state: UmbVariantPropertyReadOnlyState = { + unique, + message: 'Shared properties can only be edited in the default language', + propertyType: { + unique: property.unique, + variantId: new UmbVariantId(), + }, + }; + + this._dataOwner.structure.propertyReadOnlyState.addState(state); + } + }); + }, + ); + } +} From 7b6492650c4a1d0ea410614a19695d519786b9c5 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 20 Mar 2025 20:46:41 +0100 Subject: [PATCH 4/7] enforce configuration --- .../document-property-dataset.context.ts | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts index 3b7954e5f611..8f8839d928b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts @@ -1,29 +1,46 @@ import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentWorkspaceContext } from '../types.js'; +import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../global-contexts/index.js'; import { UmbContentPropertyDatasetContext } from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UmbVariantId, type UmbVariantPropertyReadOnlyState } from '@umbraco-cms/backoffice/variant'; +import type { DocumentConfigurationResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; export class UmbDocumentPropertyDatasetContext extends UmbContentPropertyDatasetContext< UmbDocumentDetailModel, UmbDocumentTypeDetailModel, UmbDocumentVariantModel > { + #dataSetVariantId?: UmbVariantId; + #documentConfiguration?: DocumentConfigurationResponseModel; + constructor(host: UmbControllerHost, dataOwner: UmbDocumentWorkspaceContext, variantId?: UmbVariantId) { super(host, dataOwner, variantId); + this.consumeContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT, async (context) => { + this.#documentConfiguration = (await context?.getDocumentConfiguration()) ?? undefined; + + if (this.#documentConfiguration?.allowEditInvariantFromNonDefault !== true) { + this.#preventEditInvariantFromNonDefault(); + } + }); + } + + #preventEditInvariantFromNonDefault() { this.observe( observeMultiple([this._dataOwner.structure.contentTypeProperties, this._dataOwner.variantOptions]), ([properties, variantOptions]) => { if (properties.length === 0) return; if (variantOptions.length === 0) return; - const currentVariantOption = variantOptions.find((option) => option.culture === variantId?.culture); + const currentVariantOption = variantOptions.find( + (option) => option.culture === this.#dataSetVariantId?.culture, + ); const isDefaultLanguage = currentVariantOption?.language.isDefault; properties.forEach((property) => { - const unique = 'UMB_PREVENT_SHARED_PROPERTY_EDITING_FROM_NON_DEFAULT_' + property.unique; + const unique = 'UMB_PREVENT_EDIT_INVARIANT_FROM_NON_DEFAULT_' + property.unique; this._dataOwner.structure.propertyReadOnlyState.removeState(unique); From 870bdb86fce2b3f312445eda2a46a5399e0c8265 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 20 Mar 2025 20:55:08 +0100 Subject: [PATCH 5/7] clean up --- .../structure/content-type-structure-manager.class.ts | 1 + .../documents/documents/workspace/document-workspace.context.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts index 9e78d815f775..361d4385e6cb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts @@ -820,6 +820,7 @@ export class UmbContentTypeStructureManager< this.#containers.destroy(); this.propertyReadState.destroy(); this.propertyWriteState.destroy(); + this.propertyReadOnlyState.destroy(); super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 79e057fe5c25..adc3231e1fb1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -184,6 +184,7 @@ export class UmbDocumentWorkspaceContext this.#isTrashedContext.setIsTrashed(false); this.structure.propertyReadState.clear(); this.structure.propertyWriteState.clear(); + this.structure.propertyReadOnlyState.clear(); } override async load(unique: string) { From 9b117c1275eb477e696228c8cd10ebab81b1883d Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 21 Mar 2025 13:12:22 +0100 Subject: [PATCH 6/7] set variant id --- .../document-property-dataset.context.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts index 8f8839d928b3..0da536938ca9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/document-property-dataset.context.ts @@ -18,6 +18,8 @@ export class UmbDocumentPropertyDatasetContext extends UmbContentPropertyDataset constructor(host: UmbControllerHost, dataOwner: UmbDocumentWorkspaceContext, variantId?: UmbVariantId) { super(host, dataOwner, variantId); + this.#dataSetVariantId = variantId; + this.consumeContext(UMB_DOCUMENT_CONFIGURATION_CONTEXT, async (context) => { this.#documentConfiguration = (await context?.getDocumentConfiguration()) ?? undefined; From 8ec98a182a2fba98c42d75f604e54be605708a0a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 24 Mar 2025 10:32:37 +0100 Subject: [PATCH 7/7] move to property module --- src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts | 1 + .../variant-property-read-only-state.manager.ts | 4 ++-- src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Umbraco.Web.UI.Client/src/packages/core/{variant => property}/variant-property-read-only-state.manager.ts (75%) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts index 7a13dc614157..795963351f5d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/index.ts @@ -6,6 +6,7 @@ export * from './property-value-cloner/property-value-clone.controller.js'; export * from './property-value-preset/index.js'; export * from './property-view-state.manager.js'; export * from './property-write-state.manager.js'; +export * from './variant-property-read-only-state.manager.js'; export * from './variant-property-view-state.manager.js'; export * from './variant-property-write-state.manager.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/variant-property-read-only-state.manager.ts similarity index 75% rename from src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/property/variant-property-read-only-state.manager.ts index cb6eeed1f070..e92b1a498840 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-property-read-only-state.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/variant-property-read-only-state.manager.ts @@ -1,6 +1,6 @@ -import type { UmbReferenceByVariantId } from './types.js'; -import { UmbPropertyReadOnlyStateManager, type UmbPropertyReadOnlyState } from '@umbraco-cms/backoffice/property'; +import { UmbPropertyReadOnlyStateManager, type UmbPropertyReadOnlyState } from './property-read-only-state.manager.js'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; +import type { UmbReferenceByVariantId } from '@umbraco-cms/backoffice/variant'; export interface UmbVariantPropertyReadOnlyState extends UmbPropertyReadOnlyState { propertyType: UmbReferenceByUnique & UmbReferenceByVariantId; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts index 0b5e86997dca..d190db617d65 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/index.ts @@ -1,5 +1,4 @@ export * from './variant-id.class.js'; export * from './variant-object-compare.function.js'; -export * from './variant-property-read-only-state.manager.js'; export type * from './types.js';