* observation as promise util

* all success observer

* next step todos

* await everything loaded

* contentTypeLoaded observable

* tidying up

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove comment

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Niels Lyngsø
2025-08-13 08:16:59 +02:00
committed by GitHub
parent aa269e317b
commit cee441da49
5 changed files with 73 additions and 5 deletions

View File

@@ -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';

View File

@@ -0,0 +1,40 @@
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
/**
* @function observationAsPromise
* @param {Observable<unknown>} observable - an Array of Observables to use for this combined observation.
* @param {Promise<condition>} 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<unknown>} - 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<T>(
observable: Observable<T>,
condition: (value: T) => Promise<boolean | undefined>,
): Promise<T> {
return new Promise<T>((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();
}
});
}

View File

@@ -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 };
}

View File

@@ -151,14 +151,14 @@ export class UmbRepositoryDetailsManager<DetailType extends { unique: string }>
*/
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.
}

View File

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