invariant ID based on Element Type

This commit is contained in:
Niels Lyngsø
2024-09-26 21:12:26 +02:00
parent ee2e692fea
commit b360b7935e
4 changed files with 117 additions and 58 deletions

View File

@@ -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();
},

View File

@@ -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<LayoutDataType extends UmbBlockLayoutBaseModel = UmbBlockLayoutBaseModel>
@@ -75,18 +75,37 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
context.onSubmit().catch(this.#modalRejected);
}).asPromise();
this.#retrieveBlockManager = this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (context) => {
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<LayoutDataType extends UmbBlockLayoutBaseM
this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => {
// 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<LayoutDataType extends UmbBlockLayoutBaseM
await this.#retrieveModalContext;
if (!this.#blockEntries) {
throw new Error('Block Entries not found');
return;
}
if (!this.#modalContext) {
throw new Error('Modal Context not found');
return;
}
// TODO: Missing some way to append more layout data... this could be part of modal data, (or context api?)
@@ -303,21 +324,33 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
* in the backoffice UI.
*/
establishLiveSync() {
this.observe(this.layout, (layoutData) => {
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() {

View File

@@ -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<UmbPropertyTypeContainerModel> = new UmbArrayState<UmbPropertyTypeContainerModel>(
[],
(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,
) ?? [],
);

View File

@@ -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(