diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index a306ed2b7d..579432df67 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -459,13 +459,30 @@ export abstract class UmbBlockEntryContext< abstract _gotContentType(contentType: UmbContentTypeModel | undefined): void; - #observeVariantId() { + async #observeVariantId() { if (!this._manager) return; + await this.#contentStructurePromise; + if (!this.#contentStructure) { + throw new Error('No contentStructure found'); + } // observe blockType: this.observe( - this._manager.variantId, - (variantId) => { + observeMultiple([ + this._manager.variantId, + this.#contentStructure?.ownerContentTypeObservablePart((x) => x?.variesByCulture), + this.#contentStructure?.ownerContentTypeObservablePart((x) => x?.variesBySegment), + ]), + ([variantId, variesByCulture, variesBySegment]) => { + if (!variantId || variesByCulture === undefined || variesBySegment === undefined) return; + if (!variesBySegment && !variesByCulture) { + variantId = UmbVariantId.CreateInvariant(); + } else if (!variesBySegment) { + variantId = variantId.toSegmentInvariant(); + } else if (!variesByCulture) { + variantId = variantId.toCultureInvariant(); + } + this.#variantId.setValue(variantId); this.#gotVariantId(); }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index dbefee104d..eca5936da4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -7,7 +7,7 @@ import { UmbWorkspaceIsNewRedirectController, type ManifestWorkspace, } from '@umbraco-cms/backoffice/workspace'; -import { UmbClassState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbClassState, UmbObjectState, UmbStringState, observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; @@ -18,7 +18,7 @@ import { type UmbBlockWorkspaceData, } from '@umbraco-cms/backoffice/block'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; -import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; export type UmbBlockWorkspaceElementManagerNames = 'content' | 'settings'; export class UmbBlockWorkspaceContext @@ -75,18 +75,37 @@ export class UmbBlockWorkspaceContext { - this.#blockManager = context; + this.#retrieveBlockManager = this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => { + this.#blockManager = manager; + this.observe( - context.liveEditingMode, + manager.liveEditingMode, (liveEditingMode) => { this.#liveEditingMode = liveEditingMode; }, 'observeLiveEditingMode', ); - this.observe(context.variantId, (variantId) => { - this.#variantId.setValue(variantId); - }); + + this.observe( + observeMultiple([ + manager.variantId, + this.content.structure.variesByCulture, + this.content.structure.variesBySegment, + ]), + ([variantId, variesByCulture, variesBySegment]) => { + if (!variantId || variesByCulture === undefined || variesBySegment === undefined) return; + if (!variesBySegment && !variesByCulture) { + variantId = UmbVariantId.CreateInvariant(); + } else if (!variesBySegment) { + variantId = variantId.toSegmentInvariant(); + } else if (!variesByCulture) { + variantId = variantId.toCultureInvariant(); + } + + this.#variantId.setValue(variantId); + }, + 'observeBlockType', + ); }).asPromise(); this.#retrieveBlockEntries = this.consumeContext(UMB_BLOCK_ENTRIES_CONTEXT, (context) => { @@ -96,23 +115,27 @@ export class UmbBlockWorkspaceContext { // TODO: Ideally we move this into the Block Manager [NL] To avoid binding the Block Manager to a Property... // If the current property is readonly all inner block content should also be readonly. - this.observe(context.isReadOnly, (isReadOnly) => { - const unique = 'UMB_PROPERTY_CONTEXT'; - const variantId = this.#variantId.getValue(); - if (variantId === undefined) return; + this.observe( + context.isReadOnly, + (isReadOnly) => { + const unique = 'UMB_PROPERTY_CONTEXT'; + const variantId = this.#variantId.getValue(); + if (variantId === undefined) return; - if (isReadOnly) { - const state = { - unique, - variantId, - message: '', - }; + if (isReadOnly) { + const state = { + unique, + variantId, + message: '', + }; - this.readOnlyState?.addState(state); - } else { - this.readOnlyState?.removeState(unique); - } - }); + this.readOnlyState?.addState(state); + } else { + this.readOnlyState?.removeState(unique); + } + }, + 'observeIsReadOnly', + ); }); this.observe(this.variantId, (variantId) => { @@ -189,11 +212,9 @@ export class UmbBlockWorkspaceContext { - if (layoutData) { - this.#blockManager?.setOneLayout(layoutData, this.#modalContext?.data as UmbBlockWorkspaceData); - } - }); - this.observe(this.content.data, (contentData) => { - if (contentData) { - this.#blockManager?.setOneContent(contentData); - } - }); - this.observe(this.settings.data, (settingsData) => { - if (settingsData) { - this.#blockManager?.setOneSettings(settingsData); - } - }); + this.observe( + this.layout, + (layoutData) => { + if (layoutData) { + this.#blockManager?.setOneLayout(layoutData, this.#modalContext?.data as UmbBlockWorkspaceData); + } + }, + 'observeThisLayout', + ); + this.observe( + this.content.data, + (contentData) => { + if (contentData) { + this.#blockManager?.setOneContent(contentData); + } + }, + 'observeThisContent', + ); + this.observe( + this.settings.data, + (settingsData) => { + if (settingsData) { + this.#blockManager?.setOneSettings(settingsData); + } + }, + 'observeThisSettings', + ); } getData() { 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 a82c42f1b7..55c98e819d 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 @@ -76,6 +76,9 @@ export class UmbContentTypeStructureManager< readonly contentTypeUniques = this.#contentTypes.asObservablePart((x) => x.map((y) => y.unique)); readonly contentTypeAliases = this.#contentTypes.asObservablePart((x) => x.map((y) => y.alias)); + readonly variesByCulture = createObservablePart(this.ownerContentType, (x) => x?.variesByCulture); + readonly variesBySegment = createObservablePart(this.ownerContentType, (x) => x?.variesBySegment); + #containers: UmbArrayState = new UmbArrayState( [], (x) => x.id, @@ -91,7 +94,7 @@ export class UmbContentTypeStructureManager< // Observe owner content type compositions, as we only allow one level of compositions at this moment. [NL] // But, we could support more, we would just need to flatMap all compositions and make sure the entries are unique and then base the observation on that. [NL] this.observe(this.ownerContentTypeCompositions, (ownerContentTypeCompositions) => { - this._loadContentTypeCompositions(ownerContentTypeCompositions); + this.#loadContentTypeCompositions(ownerContentTypeCompositions); }); this.observe(this.#contentTypeContainers, (contentTypeContainers) => { this.#containers.setValue(contentTypeContainers); @@ -109,7 +112,7 @@ export class UmbContentTypeStructureManager< this.#ownerContentTypeUnique = unique; - const promise = this._loadType(unique); + const promise = this.#loadType(unique); this.#init = promise; await this.#init; return promise; @@ -130,7 +133,7 @@ export class UmbContentTypeStructureManager< /** * Save the owner content type. Notice this is for a Content Type that is already stored on the server. - * @returns boolean + * @returns {Promise} - A promise that will be resolved when the content type is saved. */ public async save() { const contentType = this.getOwnerContentType(); @@ -149,8 +152,8 @@ export class UmbContentTypeStructureManager< /** * Create the owner content type. Notice this is for a Content Type that is NOT already stored on the server. - * @param parentUnique - * @returns boolean + * @param {string | null} parentUnique - The unique of the parent content type + * @returns {Promise} - a promise that is resolved when the content type has been created. */ public async create(parentUnique: string | null) { const contentType = this.getOwnerContentType(); @@ -165,10 +168,10 @@ export class UmbContentTypeStructureManager< this.#contentTypes.updateOne(contentType.unique, data); // Start observe the new content type in the store, as we did not do that when it was a scaffold/local-version. - this._observeContentType(data); + this.#observeContentType(data); } - private async _loadContentTypeCompositions(ownerContentTypeCompositions: T['compositions'] | undefined) { + async #loadContentTypeCompositions(ownerContentTypeCompositions: T['compositions'] | undefined) { if (!ownerContentTypeCompositions) { // Owner content type was undefined, so we can not load compositions. But at this point we neither offload existing compositions, this is most likely not a case that needs to be handled. return; @@ -186,28 +189,28 @@ export class UmbContentTypeStructureManager< } }); ownerContentTypeCompositions.forEach((composition) => { - this._ensureType(composition.contentType.unique); + this.#ensureType(composition.contentType.unique); }); } - private async _ensureType(unique?: string) { + async #ensureType(unique?: string) { if (!unique) return; if (this.#contentTypes.getValue().find((x) => x.unique === unique)) return; - await this._loadType(unique); + await this.#loadType(unique); } - private async _loadType(unique?: string) { + async #loadType(unique?: string) { if (!unique) return {}; // Lets initiate the content type: const { data, asObservable } = await this.#repository.requestByUnique(unique); if (!data) return {}; - await this._observeContentType(data); + await this.#observeContentType(data); return { data, asObservable }; } - private async _observeContentType(data: T) { + async #observeContentType(data: T) { if (!data.unique) return; // Notice we do not store the content type in the store here, cause it will happen shortly after when the observations gets its first initial callback. [NL] @@ -611,7 +614,6 @@ export class UmbContentTypeStructureManager< return undefined; } - hasPropertyStructuresOf(containerId: string | null) { return this.#contentTypes.asObservablePart((docTypes) => { return ( @@ -659,7 +661,7 @@ export class UmbContentTypeStructureManager< ownerContainersOf(containerType: UmbPropertyContainerTypes, parentId: string | null) { return this.ownerContentTypeObservablePart( (x) => - x.containers?.filter( + x?.containers?.filter( (x) => (parentId ? x.parent?.id === parentId : x.parent === null) && x.type === containerType, ) ?? [], ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index fd17fcf3eb..79f1beb698 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -83,6 +83,13 @@ export class UmbVariantId { return { culture: this.culture, segment: this.segment }; } + public toSegmentInvariant(): UmbVariantId { + return Object.freeze(new UmbVariantId(this.culture, null)); + } + public toCultureInvariant(): UmbVariantId { + return Object.freeze(new UmbVariantId(null, this.culture)); + } + // TODO: needs localization option: // TODO: Consider if this should be handled else where, it does not seem like the responsibility of this class, since it contains wordings: public toDifferencesString(