diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts index 03ffd9d6e601..3b3452ab110e 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts @@ -7,6 +7,7 @@ export * from './default-memoization.function.js'; export * from './filter-frozen-array.function.js'; export * from './json-string-comparison.function.js'; export * from './merge-observables.function.js'; +export * from './observation-as-promise.function.js'; export * from './observe-multiple.function.js'; export * from './partial-update-frozen-array.function.js'; export * from './push-at-to-unique-array.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/observation-as-promise.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/observation-as-promise.function.ts new file mode 100644 index 000000000000..f0ae5b6efb7c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/observation-as-promise.function.ts @@ -0,0 +1,40 @@ +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; + +/** + * @function observationAsPromise + * @param {Observable} observable - an Array of Observables to use for this combined observation. + * @param {Promise} condition - a method which should return true or false, if rejected or returning undefined the observation will result in a rejected Promise. + * @description - Observes an Observable and returns a Promise that resolves when the condition returns true. If the condition returns undefined or rejects, the Promise will reject with the current value. + * @returns {Promise} - Returns a Promise which resolves when the condition returns true or rejects when the condition returns undefined or is rejecting it self. + */ +export function observationAsPromise( + observable: Observable, + condition: (value: T) => Promise, +): Promise { + return new Promise((resolve, reject) => { + let initialCallback = true; + let wantedToClose = false; + const subscription = observable.subscribe(async (value) => { + const shouldClose = await condition(value).catch(() => { + if (initialCallback) { + wantedToClose = true; + } else { + subscription.unsubscribe(); + } + reject(value); + }); + if (shouldClose === true) { + if (initialCallback) { + wantedToClose = true; + } else { + subscription.unsubscribe(); + } + resolve(value); + } + }); + initialCallback = false; + if (wantedToClose) { + subscription.unsubscribe(); + } + }); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts index eed0caeedb68..d7708d7d87c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/structure/content-type-structure-manager.class.ts @@ -19,12 +19,15 @@ import { appendToFrozenArray, filterFrozenArray, createObservablePart, + observationAsPromise, + mergeObservables, } from '@umbraco-cms/backoffice/observable-api'; import { incrementString } from '@umbraco-cms/backoffice/utils'; 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 { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbError } from '@umbraco-cms/backoffice/resources'; type UmbPropertyTypeUnique = UmbPropertyTypeModel['unique']; @@ -112,6 +115,13 @@ 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 contentTypeLoaded = mergeObservables( + [this.contentTypeCompositions, this.contentTypeUniques], + ([comps, uniques]) => { + return comps.every((x) => uniques.includes(x.contentType.unique)); + }, + ); + readonly variesByCulture = createObservablePart(this.ownerContentType, (x) => x?.variesByCulture); readonly variesBySegment = createObservablePart(this.ownerContentType, (x) => x?.variesBySegment); @@ -191,9 +201,26 @@ export class UmbContentTypeStructureManager< ); } this.#repoManager!.setUniques([unique]); - const result = await this.observe(this.#repoManager!.entryByUnique(unique)).asPromise(); + const observable = this.#repoManager!.entryByUnique(unique); + const result = await this.observe(observable).asPromise(); + if (!result) { + this.#initRejection?.(`Content Type structure manager could not load: ${unique}`); + return { + error: new UmbError(`Content Type structure manager could not load: ${unique}`), + asObservable: () => observable, + }; + } + + // Awaits that everything is loaded: + await observationAsPromise(this.contentTypeLoaded, async (loaded) => { + return loaded === true; + }).catch(() => { + const msg = `Content Type structure manager could not load: ${unique}. Not all Content Types loaded successfully.`; + this.#initRejection?.(msg); + return Promise.reject(new UmbError(msg)); + }); + this.#initResolver?.(result); - await this.#init; return { data: result, asObservable: () => this.ownerContentType }; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts index b2d170c902f0..95ad757ff5d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/repository-details.manager.ts @@ -151,14 +151,14 @@ export class UmbRepositoryDetailsManager */ addEntry(data: DetailType): void { const unique = data.unique; + this.#entries.appendOne(data); + this.#uniques.appendOne(unique); this.#statuses.appendOne({ state: { type: 'success', }, unique, }); - this.#entries.appendOne(data); - this.#uniques.appendOne(unique); // Notice in this case we do not have a observable from the repo, but it should maybe be fine that we just listen for ACTION EVENTS. } 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 d66f888b4283..c92309e57553 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 @@ -415,7 +415,7 @@ export class UmbDocumentWorkspaceContext this.readOnlyGuard?.addRule({ unique: identifier, message, - /* This guard is a bit backwards. The rule is permitted to be read-only. + /* This guard is a bit backwards. The rule is permitted to be read-only. If the user does not have permission, we set it to true = permitted to be read-only. */ permitted: true, });