V16/feature: get context resolves in undefined if not found (#18611)

* implement getContext rejecter + allow for resolving in undefined

* fix test

* timeout concept

* adjustments

* adapt implementation

* ability to set timeoutFrame for faster test

* update

* make sure provider only provides when connected

* make sure action apis can fail without resolving

* make sure to await the arrival of the UMB_AUTH_CONTEXT

* no need to be async

* consume to stay up to date

* one rendering cycle approach

* adjusting context consumption part 1

* implementation adjustments

* correction patch 2

* correction patch 3

* correction batch 4

* correction batch 5

* correction batch 6
This commit is contained in:
Niels Lyngsø
2025-03-14 15:06:45 +01:00
committed by GitHub
parent 9beb679dbf
commit b9eb988d29
196 changed files with 1139 additions and 1014 deletions

View File

@@ -1,11 +1,14 @@
import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context.js';
import { UmbWorkspaceActionBase, type UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context';
// The Example Incrementor Workspace Action Controller:
export class ExampleIncrementorWorkspaceAction extends UmbWorkspaceActionBase implements UmbWorkspaceAction {
// This method is executed
override async execute() {
const context = await this.getContext(EXAMPLE_COUNTER_CONTEXT);
if (!context) {
throw new Error('Could not get the counter context');
}
context.increment();
}
}

View File

@@ -29,6 +29,9 @@ export class UmbApiInterceptorController extends UmbControllerBase {
if (!isUmbNotifications(notifications)) return response;
this.getContext(UMB_NOTIFICATION_CONTEXT).then((notificationContext) => {
if (notificationContext === undefined) {
throw new Error('Notification context is not available');
}
for (const notification of notifications) {
notificationContext.peek(extractUmbNotificationColor(notification.type), {
data: { headline: notification.category, message: notification.message },

View File

@@ -7,13 +7,14 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { setStoredPath } from '@umbraco-cms/backoffice/utils';
export class UmbAppAuthController extends UmbControllerBase {
#retrievedModal: Promise<unknown>;
#authContext?: typeof UMB_AUTH_CONTEXT.TYPE;
#isFirstCheck = true;
constructor(host: UmbControllerHost) {
super(host);
this.consumeContext(UMB_AUTH_CONTEXT, (context) => {
this.#retrievedModal = this.consumeContext(UMB_AUTH_CONTEXT, (context) => {
this.#authContext = context;
// Observe the user's authorization state and start the authorization flow if the user is not authorized
@@ -24,7 +25,7 @@ export class UmbAppAuthController extends UmbControllerBase {
},
'_authState',
);
});
}).asPromise({ preventTimeout: true });
}
/**
@@ -32,6 +33,7 @@ export class UmbAppAuthController extends UmbControllerBase {
* If not, the authorization flow is started.
*/
async isAuthorized(): Promise<boolean> {
await this.#retrievedModal.catch(() => undefined);
if (!this.#authContext) {
throw new Error('[Fatal] Auth context is not available');
}
@@ -63,6 +65,7 @@ export class UmbAppAuthController extends UmbControllerBase {
* @param userLoginState
*/
async makeAuthorizationRequest(userLoginState: UmbUserLoginState = 'loggingIn'): Promise<boolean> {
await this.#retrievedModal.catch(() => undefined);
if (!this.#authContext) {
throw new Error('[Fatal] Auth context is not available');
}
@@ -111,6 +114,7 @@ export class UmbAppAuthController extends UmbControllerBase {
}
async #showLoginModal(userLoginState: UmbUserLoginState): Promise<boolean> {
await this.#retrievedModal.catch(() => undefined);
if (!this.#authContext) {
throw new Error('[Fatal] Auth context is not available');
}
@@ -118,6 +122,9 @@ export class UmbAppAuthController extends UmbControllerBase {
// Show the provider selection screen
const authModalKey = 'umbAuthModal';
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) {
throw new Error('[Fatal] Modal manager is not available');
}
const selected = await modalManager
.open(this._host, UMB_MODAL_APP_AUTH, {

View File

@@ -34,29 +34,26 @@ export class UmbBackofficeContext extends UmbContextBase<UmbBackofficeContext> {
});
});
this.#init();
}
async #init() {
const userContext = await this.getContext(UMB_CURRENT_USER_CONTEXT);
this.observe(
userContext.allowedSections,
(allowedSections) => {
if (!allowedSections) return;
// TODO: Please be aware that we re-initialize this initializer based on user permissions. I suggest we should solve this specific case should be improved by the ability to change the filter [NL]
new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'section',
(manifest) => allowedSections.includes(manifest.alias),
async (sections) => {
this.#allowedSections.setValue([...sections]);
},
'umbAllowedSectionsManifestInitializer',
);
},
'umbAllowedSectionsObserver',
);
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (userContext) => {
this.observe(
userContext.allowedSections,
(allowedSections) => {
if (!allowedSections) return;
// TODO: Please be aware that we re-initialize this initializer based on user permissions. I suggest we should solve this specific case should be improved by the ability to change the filter [NL]
new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'section',
(manifest) => allowedSections.includes(manifest.alias),
async (sections) => {
this.#allowedSections.setValue([...sections]);
},
'umbAllowedSectionsManifestInitializer',
);
},
'umbAllowedSectionsObserver',
);
});
}
async #getVersion() {

View File

@@ -47,7 +47,7 @@ export class UmbBackofficeHeaderLogoElement extends UmbLitElement {
});
}
protected override async firstUpdated() {
protected override firstUpdated() {
this.#isAdmin();
}
@@ -88,6 +88,10 @@ export class UmbBackofficeHeaderLogoElement extends UmbLitElement {
async #openSystemInformation() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) {
throw new Error('Modal manager not found');
}
modalManager
.open(this, UMB_SYSINFO_MODAL)
.onSubmit()
@@ -97,6 +101,7 @@ export class UmbBackofficeHeaderLogoElement extends UmbLitElement {
async #openNewVersion() {
if (!this._serverUpgradeCheck) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) return;
modalManager
.open(this, UMB_NEWVERSION_MODAL, {
data: {

View File

@@ -36,7 +36,7 @@ export class UmbPreviewCultureElement extends UmbLitElement {
this._culture = culture;
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
previewContext.updateIFrame({ culture: culture.unique });
previewContext?.updateIFrame({ culture: culture.unique });
}
override render() {

View File

@@ -6,7 +6,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
export class UmbPreviewExitElement extends UmbLitElement {
async #onClick() {
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
previewContext.exitPreview(0);
previewContext?.exitPreview(0);
}
override render() {

View File

@@ -6,7 +6,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
export class UmbPreviewOpenWebsiteElement extends UmbLitElement {
async #onClick() {
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
previewContext.openWebsite();
previewContext?.openWebsite();
}
override render() {

View File

@@ -24,24 +24,22 @@ export class UmbPreviewContext extends UmbContextBase<UmbPreviewContext> {
constructor(host: UmbControllerHost) {
super(host, UMB_PREVIEW_CONTEXT);
this.#init();
}
async #init() {
const appContext = await this.getContext(UMB_APP_CONTEXT);
this.#serverUrl = appContext.getServerUrl();
this.consumeContext(UMB_APP_CONTEXT, (appContext) => {
this.#serverUrl = appContext.getServerUrl();
const params = new URLSearchParams(window.location.search);
const params = new URLSearchParams(window.location.search);
this.#culture = params.get('culture');
this.#unique = params.get('id');
this.#culture = params.get('culture');
this.#unique = params.get('id');
if (!this.#unique) {
console.error('No unique ID found in query string.');
return;
}
if (!this.#unique) {
console.error('No unique ID found in query string.');
return;
}
this.#setPreviewUrl();
this.#setPreviewUrl();
});
}
#configureWebSocket() {

View File

@@ -1,5 +1,6 @@
import type {
UmbContextCallback,
UmbContextConsumerAsPromiseOptionsType,
UmbContextConsumerController,
UmbContextProviderController,
UmbContextToken,
@@ -64,5 +65,6 @@ export interface UmbClassInterface extends UmbControllerHost {
*/
getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
alias: string | UmbContextToken<BaseType, ResultType>,
): Promise<ResultType>;
options?: UmbContextConsumerAsPromiseOptionsType,
): Promise<ResultType | undefined>;
}

View File

@@ -11,6 +11,7 @@ import {
type UmbContextCallback,
UmbContextConsumerController,
UmbContextProviderController,
type UmbContextConsumerAsPromiseOptionsType,
} from '@umbraco-cms/backoffice/context-api';
import { type ObserverCallback, UmbObserverController, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
@@ -98,12 +99,19 @@ export const UmbClassMixin = <T extends ClassConstructor<EventTarget>>(superClas
async getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
contextAlias: string | UmbContextToken<BaseType, ResultType>,
): Promise<ResultType> {
options?: UmbContextConsumerAsPromiseOptionsType,
): Promise<ResultType | undefined> {
const controller = new UmbContextConsumerController(this, contextAlias);
const promise = controller.asPromise().then((result) => {
controller.destroy();
return result;
});
const promise = controller
.asPromise(options)
.then((result) => {
controller.destroy();
return result;
})
.catch(() => {
controller.destroy();
return undefined;
});
return promise;
}

View File

@@ -52,44 +52,108 @@ describe('UmbContextConsumer', () => {
});
describe('Simple implementation', () => {
let element: HTMLElement;
beforeEach(() => {
element = document.createElement('div');
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('works with UmbContextProvider', (done) => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(
element,
testContextAlias,
(_instance: UmbTestContextConsumerClass | undefined) => {
(_instance) => {
if (_instance) {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
}
},
);
localConsumer.hostConnected();
});
it('works with asPromise for UmbContextProvider', (done) => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(element, testContextAlias);
localConsumer.hostConnected();
localConsumer
.asPromise()
.then((instance) => {
expect(instance?.prop).to.eq('value from provider');
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
})
.catch(() => {
expect.fail('Promise should not reject');
});
provider.hostConnected();
});
it('gets rejected when using asPromise that does not resolve', (done) => {
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(element, testContextAlias);
localConsumer
.asPromise()
.then((instance) => {
expect.fail('Promise should reject');
})
.catch(() => {
localConsumer.hostDisconnected();
localConsumer.destroy();
done();
});
localConsumer.hostConnected();
});
it('never gets rejected when using asPromise that is set not to timeout and never will resolve', (done) => {
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(element, testContextAlias);
localConsumer.hostConnected();
const timeout = setTimeout(() => {
localConsumer.hostDisconnected();
done();
}, 200);
try {
localConsumer
.asPromise({ preventTimeout: true })
.then((instance) => {
clearTimeout(timeout);
expect.fail('Promise should not resolve');
})
.catch(() => {
clearTimeout(timeout);
expect.fail('Promise should not reject');
});
} catch (e) {
console.log('e', e);
}
});
it('works with host as a method', (done) => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(
() => element,
testContextAlias,
(_instance: UmbTestContextConsumerClass | undefined) => {
if (_instance) {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
}
},
);
@@ -97,12 +161,12 @@ describe('UmbContextConsumer', () => {
});
it('works with host method returning undefined', async () => {
const element = undefined;
const notExistingElement = undefined;
const localConsumer = new UmbContextConsumer(
() => element,
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(
() => notExistingElement,
testContextAlias,
(_instance: UmbTestContextConsumerClass | undefined) => {
(_instance) => {
if (_instance) {
expect.fail('Callback should not be called when never permitted');
}
@@ -147,6 +211,15 @@ describe('UmbContextConsumer', () => {
});
describe('Implementation with Api Alias', () => {
let element: HTMLElement;
beforeEach(() => {
element = document.createElement('div');
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('responds when api alias matches', (done) => {
const provider = new UmbContextProvider(
document.body,
@@ -155,17 +228,18 @@ describe('UmbContextConsumer', () => {
);
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(element, testContextAliasAndApiAlias, (_instance) => {
if (_instance) {
expect((_instance as UmbTestContextConsumerClass).prop).to.eq('value from provider');
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
}
});
const localConsumer = new UmbContextConsumer<UmbTestContextConsumerClass>(
element,
testContextAliasAndApiAlias,
(_instance) => {
if (_instance) {
expect(_instance.prop).to.eq('value from provider');
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
}
},
);
localConsumer.hostConnected();
});
@@ -177,9 +251,6 @@ describe('UmbContextConsumer', () => {
);
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => {
expect(false).to.be.true;
});
@@ -195,6 +266,15 @@ describe('UmbContextConsumer', () => {
});
describe('Implementation with discriminator method', () => {
let element: HTMLElement;
beforeEach(() => {
element = document.createElement('div');
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
type A = { prop: string };
function discriminator(instance: unknown): instance is A {
@@ -208,16 +288,20 @@ describe('UmbContextConsumer', () => {
}
it('discriminator determines the instance type', (done) => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
const localConsumer = new UmbContextConsumer(
document.body,
element,
new UmbContextToken(testContextAlias, undefined, discriminator),
(instance: A) => {
expect(instance.prop).to.eq('value from provider');
done();
provider.destroy();
localConsumer.destroy();
done();
},
);
localConsumer.hostConnected();
provider.hostConnected();
// This bit of code is not really a test but it serves as a TypeScript type test, making sure the given type is matches the one given from the Discriminator method.
type TestType = Exclude<typeof localConsumer.instance, undefined> extends A ? true : never;
@@ -229,17 +313,14 @@ describe('UmbContextConsumer', () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(
element,
new UmbContextToken(testContextAlias, undefined, discriminator),
(_instance) => {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
},
);
localConsumer.hostConnected();
@@ -249,9 +330,6 @@ describe('UmbContextConsumer', () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const localConsumer = new UmbContextConsumer(
element,
new UmbContextToken(testContextAlias, undefined, badDiscriminator),
@@ -263,9 +341,9 @@ describe('UmbContextConsumer', () => {
// Wait for to ensure the above request didn't succeed:
Promise.resolve().then(() => {
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
});
});
@@ -273,9 +351,6 @@ describe('UmbContextConsumer', () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const alternativeProvider = new UmbContextProvider(
element,
testContextAlias,
@@ -294,9 +369,9 @@ describe('UmbContextConsumer', () => {
// Wait for to ensure the above request didn't succeed:
Promise.resolve().then(() => {
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
});
});
@@ -304,9 +379,6 @@ describe('UmbContextConsumer', () => {
const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
provider.hostConnected();
const element = document.createElement('div');
document.body.appendChild(element);
const alternativeProvider = new UmbContextProvider(
element,
testContextAlias,
@@ -319,9 +391,9 @@ describe('UmbContextConsumer', () => {
new UmbContextToken(testContextAlias, undefined, discriminator),
(_instance) => {
expect(_instance.prop).to.eq('value from provider');
done();
localConsumer.hostDisconnected();
provider.hostDisconnected();
done();
},
);
localConsumer.passContextAliasMatches();

View File

@@ -5,17 +5,26 @@ import { UmbContextRequestEventImplementation } from './context-request.event.js
type HostElementMethod = () => Element | undefined;
export type UmbContextConsumerAsPromiseOptionsType = {
preventTimeout?: boolean;
};
/**
* @class UmbContextConsumer
* @description The context consumer class, used to consume a context from a host element.
* Notice it is not recommended to use this class directly, but rather use the `consumeContext` method from a `UmbElement` or `UmbElementMixin` or `UmbControllerBase` or `UmbClassMixin`.
* Alternatively, you can use the `UmbContextConsumerController` to consume a context from a host element. But this does require that you can implement one of the Class Mixins mentioned above.
*/
export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType = BaseType> {
protected _retrieveHost: HostElementMethod;
#raf?: number;
#skipHost?: boolean;
#stopAtContextMatch = true;
#callback?: UmbContextCallback<ResultType>;
#promise?: Promise<ResultType>;
#promise?: Promise<ResultType | undefined>;
#promiseResolver?: (instance: ResultType) => void;
#promiseRejecter?: (reason: string) => void;
#instance?: ResultType;
get instance() {
@@ -83,7 +92,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
throw new Error('Not allowed to set context api instance to undefined.');
}
if (this.#discriminator) {
// Notice if discriminator returns false, we do not want to setInstance.
// Notice if discriminator returns false, we do not want to setInstance. [NL]
if (this.#discriminator(instance)) {
this.setInstance(instance as unknown as ResultType);
return true;
@@ -102,23 +111,29 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
if (promiseResolver && instance !== undefined) {
promiseResolver(instance);
this.#promise = undefined;
this.#promiseResolver = undefined;
this.#promiseRejecter = undefined;
}
}
/**
* @public
* @memberof UmbContextConsumer
* @param {UmbContextConsumerAsPromiseOptionsType} options - Prevent the promise from timing out.
* @description Get the context as a promise.
* @returns {UmbContextConsumer} - A promise that resolves when the context is consumed.
*/
public asPromise(): Promise<ResultType> {
public asPromise(options?: UmbContextConsumerAsPromiseOptionsType): Promise<ResultType | undefined> {
return (
this.#promise ??
(this.#promise = new Promise<ResultType>((resolve) => {
(this.#promise = new Promise<ResultType | undefined>((resolve, reject) => {
if (this.#instance) {
this.#promiseResolver = undefined;
this.#promiseRejecter = undefined;
resolve(this.#instance);
} else {
this.#promiseResolver = resolve;
this.#promiseRejecter = options?.preventTimeout ? undefined : reject;
}
}))
);
@@ -137,6 +152,20 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
this.#stopAtContextMatch,
);
(this.#skipHost ? this._retrieveHost()?.parentNode : this._retrieveHost())?.dispatchEvent(event);
/*
let i: number = this.#timeoutFrames ?? 1;
while (i-- > 0 && this.#promiseRejecter) {
await new Promise((resolve) => requestAnimationFrame(resolve));
}
*/
this.#raf = requestAnimationFrame(() => {
const hostElement = this._retrieveHost();
// If we still have the rejecter, it means that the context was not found immediately, so lets reject the promise. [NL]
this.#promiseRejecter?.(
`Context could not be found. (Context Alias: ${this.#contextAlias} with API Alias: ${this.#apiAlias}). Controller is hosted on ${hostElement?.parentNode?.nodeName ?? 'Not attached node'} > ${hostElement?.nodeName}`,
);
});
}
public hostConnected(): void {
@@ -147,6 +176,10 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
}
public hostDisconnected(): void {
if (this.#raf) {
cancelAnimationFrame(this.#raf);
this.#promiseRejecter?.('Context request was cancelled, host was disconnected.');
}
// TODO: We need to use closets application element. We need this in order to have separate Backoffice running within or next to each other.
window.removeEventListener(UMB_CONTEXT_PROVIDE_EVENT_TYPE, this.#handleNewProvider);
//window.removeEventListener(umbContextUnprovidedEventType, this.#handleRemovedProvider);
@@ -161,7 +194,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
}
};
//Niels: I'm keeping this code around as it might be relevant, but I wanted to try to see if leaving this feature out for now could work for us.
//Niels: I'm keeping this code around as it might be relevant, but I wanted to try to see if leaving this feature out for now could work for us. [NL]
/*
#handleRemovedProvider = (event: Event) => {
// Does seem a bit unnecessary, we could just assume the type via type casting...
@@ -186,6 +219,7 @@ export class UmbContextConsumer<BaseType = unknown, ResultType extends BaseType
this.#callback = undefined;
this.#promise = undefined;
this.#promiseResolver = undefined;
this.#promiseRejecter = undefined;
this.#instance = undefined;
this.#discriminator = undefined;
}

View File

@@ -40,15 +40,15 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
this.#contextAlias = idSplit[0];
this.#apiAlias = idSplit[1] ?? 'default';
this.#instance = instance;
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.addEventListener(UMB_DEBUG_CONTEXT_EVENT_TYPE, this.#handleDebugContextRequest);
}
/**
* @memberof UmbContextProvider
*/
public hostConnected(): void {
this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.addEventListener(UMB_DEBUG_CONTEXT_EVENT_TYPE, this.#handleDebugContextRequest);
this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));
}
@@ -56,6 +56,8 @@ export class UmbContextProvider<BaseType = unknown, ResultType extends BaseType
* @memberof UmbContextProvider
*/
public hostDisconnected(): void {
this.#eventTarget.removeEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
this.#eventTarget.removeEventListener(UMB_DEBUG_CONTEXT_EVENT_TYPE, this.#handleDebugContextRequest);
// Out-commented for now, but kept if we like to reintroduce this:
//window.dispatchEvent(new UmbContextUnprovidedEventImplementation(this._contextAlias, this.#instance));
}

View File

@@ -3,7 +3,11 @@ import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/extension-api';
import { type UmbControllerAlias, UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
import type { UmbContextToken, UmbContextCallback } from '@umbraco-cms/backoffice/context-api';
import type {
UmbContextToken,
UmbContextCallback,
UmbContextConsumerAsPromiseOptionsType,
} from '@umbraco-cms/backoffice/context-api';
import { UmbContextConsumerController, UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
import type { ObserverCallback } from '@umbraco-cms/backoffice/observable-api';
import { UmbObserverController, simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
@@ -74,12 +78,19 @@ export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T)
async getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
contextAlias: string | UmbContextToken<BaseType, ResultType>,
): Promise<ResultType> {
options?: UmbContextConsumerAsPromiseOptionsType,
): Promise<ResultType | undefined> {
const controller = new UmbContextConsumerController(this, contextAlias);
const promise = controller.asPromise().then((result) => {
controller.destroy();
return result;
});
const promise = controller
.asPromise(options)
.then((result) => {
controller.destroy();
return result;
})
.catch(() => {
controller.destroy();
return undefined;
});
return promise;
}
}

View File

@@ -80,17 +80,22 @@ export class UmbBlockGridAreaTypeWorkspaceContext
async load(unique: string) {
this.resetState();
const context = await this.getContext(UMB_PROPERTY_CONTEXT);
this.observe(context.value, (value) => {
if (value) {
const blockTypeData = value.find((x: UmbBlockGridTypeAreaType) => x.key === unique);
if (blockTypeData) {
this.#data.setValue(blockTypeData);
return;
}
}
// Fallback to undefined:
this.#data.setValue(undefined);
this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => {
this.observe(
context.value,
(value) => {
if (value) {
const blockTypeData = value.find((x: UmbBlockGridTypeAreaType) => x.key === unique);
if (blockTypeData) {
this.#data.setValue(blockTypeData);
return;
}
}
// Fallback to undefined:
this.#data.setValue(undefined);
},
'observePropertyValue',
);
});
}
@@ -171,6 +176,9 @@ export class UmbBlockGridAreaTypeWorkspaceContext
}
const context = await this.getContext(UMB_PROPERTY_CONTEXT);
if (!context) {
throw new Error('Property context not found.');
}
// TODO: We should most likely consume already, in this way I avoid having the reset this consumption.
context.setValue(appendToFrozenArray(context.getValue() ?? [], this.#data.getValue(), (x) => x?.key));

View File

@@ -165,12 +165,18 @@ export class UmbBlockGridEntriesContext
// Idea: Maybe on setup should be async, so it can retrieve the values when needed? [NL]
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context not available');
}
const pasteTranslatorManifests = clipboardContext.getPasteTranslatorManifests(
UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS,
);
// TODO: consider moving some of this logic to the clipboard property context
const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT);
if (!propertyContext) {
throw new Error('Property context not available');
}
const config = propertyContext.getConfig() as UmbBlockGridPropertyEditorConfig;
const valueResolver = new UmbClipboardPastePropertyValueTranslatorValueResolver(this);
@@ -234,7 +240,9 @@ export class UmbBlockGridEntriesContext
}
} else if (value?.clipboard && value.clipboard.selection?.length && data) {
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context not available');
}
const propertyValues = await clipboardContext.readMultiple<UmbBlockGridValueModel>(
value.clipboard.selection,
UMB_BLOCK_GRID_PROPERTY_EDITOR_UI_ALIAS,

View File

@@ -293,6 +293,9 @@ export class UmbBlockGridEntryContext
const propertyDatasetContext = await this.getContext(UMB_PROPERTY_DATASET_CONTEXT);
const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT);
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!clipboardContext) {
throw new Error('No clipboard context found');
}
const workspaceName = propertyDatasetContext?.getName();
const propertyLabel = propertyContext?.getLabel();

View File

@@ -33,7 +33,7 @@ export class UmbBlockGridManagerContext<
return this.#inlineEditingMode.getValue();
}
#initAppUrl: Promise<void>;
#initAppUrl: Promise<unknown>;
#serverUrl?: string;
@@ -87,9 +87,9 @@ export class UmbBlockGridManagerContext<
constructor(host: UmbControllerHost) {
super(host);
this.#initAppUrl = this.getContext(UMB_APP_CONTEXT).then((appContext) => {
this.#initAppUrl = this.consumeContext(UMB_APP_CONTEXT, (appContext) => {
this.#serverUrl = appContext.getServerUrl();
});
}).asPromise({ preventTimeout: true });
}
/**
* @deprecated Use createWithPresets instead. Will be removed in v.17.

View File

@@ -292,6 +292,9 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
const propertyDatasetContext = await this.getContext(UMB_PROPERTY_DATASET_CONTEXT);
const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT);
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!propertyDatasetContext || !propertyContext || !clipboardContext) {
throw new Error('Could not get required contexts to copy.');
}
const workspaceName = propertyDatasetContext?.getName();
const propertyLabel = propertyContext?.getLabel();

View File

@@ -39,6 +39,9 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
if (!this._manager) return false;
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context not found');
}
const pasteTranslatorManifests = clipboardContext.getPasteTranslatorManifests(
UMB_BLOCK_LIST_PROPERTY_EDITOR_UI_ALIAS,
@@ -46,6 +49,9 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
// TODO: consider moving some of this logic to the clipboard property context
const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT);
if (!propertyContext) {
throw new Error('Property context not found');
}
const config = propertyContext.getConfig();
const valueResolver = new UmbClipboardPastePropertyValueTranslatorValueResolver(this);
@@ -103,7 +109,9 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
}
} else if (value?.clipboard && value.clipboard.selection?.length && data) {
const clipboardContext = await this.getContext(UMB_CLIPBOARD_PROPERTY_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context not found');
}
const propertyValues = await clipboardContext.readMultiple<UmbBlockListValueModel>(
value.clipboard.selection,
UMB_BLOCK_LIST_PROPERTY_EDITOR_UI_ALIAS,

View File

@@ -12,7 +12,7 @@ import { UUICardEvent } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-block-type-card')
export class UmbBlockTypeCardElement extends UmbLitElement {
//
#init: Promise<void>;
#init: Promise<unknown>;
#serverUrl: string = '';
readonly #itemManager = new UmbRepositoryItemsManager<UmbDocumentTypeItemModel>(
@@ -76,9 +76,9 @@ export class UmbBlockTypeCardElement extends UmbLitElement {
constructor() {
super();
this.#init = this.getContext(UMB_APP_CONTEXT).then((appContext) => {
this.#init = this.consumeContext(UMB_APP_CONTEXT, (appContext) => {
this.#serverUrl = appContext.getServerUrl();
});
}).asPromise({ preventTimeout: true });
this.observe(this.#itemManager.statuses, async (statuses) => {
const status = statuses[0];

View File

@@ -6,7 +6,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import type { ManifestBlockEditorCustomView } from '@umbraco-cms/backoffice/block-custom-view';
@customElement('umb-block-type-custom-view-guide')
@@ -82,13 +82,10 @@ export class UmbBlockTypeCustomViewGuideElement extends UmbLitElement {
};
async #viewManifest(manifest: ManifestBlockEditorCustomView) {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
modalManager.open(this, UMB_MANIFEST_VIEWER_MODAL, { data: manifest });
umbOpenModal(this, UMB_MANIFEST_VIEWER_MODAL, { data: manifest });
}
async #generateManifest() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const manifest: UmbExtensionManifest = {
type: 'blockEditorCustomView',
alias: 'Local.blockEditorCustomView.' + this.#contentTypeAlias,
@@ -97,7 +94,7 @@ export class UmbBlockTypeCustomViewGuideElement extends UmbLitElement {
forContentTypeAlias: this.#contentTypeAlias,
forBlockEditor: this.#blockEditorType,
};
modalManager.open(this, UMB_MANIFEST_VIEWER_MODAL, { data: manifest });
umbOpenModal(this, UMB_MANIFEST_VIEWER_MODAL, { data: manifest });
}
override render() {

View File

@@ -140,6 +140,9 @@ export class UmbInputBlockTypeElement<
async #onRequestDelete(item: BlockType) {
const store = await this.getContext(UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT);
if (!store) {
return;
}
const contentType = store.getItems([item.contentElementTypeKey]);
await umbConfirmModal(this, {
color: 'danger',

View File

@@ -25,6 +25,9 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
// Just for context token safety:
public readonly IS_BLOCK_TYPE_WORKSPACE_CONTEXT = true;
#gotPropertyContext: Promise<unknown>;
#propertyContext?: typeof UMB_PROPERTY_CONTEXT.TYPE;
#entityType: string;
#data = new UmbObjectState<BlockTypeData | undefined>(undefined);
readonly data = this.#data.asObservable();
@@ -44,6 +47,10 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
const manifest = args.manifest;
this.#entityType = manifest.meta?.entityType;
this.#gotPropertyContext = this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => {
this.#propertyContext = context;
}).asPromise({ preventTimeout: true });
this.routes.setRoutes([
{
// Would it make more sense to have groupKey before elementTypeKey?
@@ -84,18 +91,23 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
async load(unique: string) {
this.resetState();
const context = await this.getContext(UMB_PROPERTY_CONTEXT);
this.observe(context.value, (value) => {
if (value) {
const blockTypeData = value.find((x: UmbBlockTypeBaseModel) => x.contentElementTypeKey === unique);
if (blockTypeData) {
this.#data.setValue(blockTypeData);
return;
await this.#gotPropertyContext;
this.observe(
this.#propertyContext?.value,
(value) => {
if (value) {
const blockTypeData = value.find((x: UmbBlockTypeBaseModel) => x.contentElementTypeKey === unique);
if (blockTypeData) {
this.#data.setValue(blockTypeData);
return;
}
}
}
// Fallback to undefined:
this.#data.setValue(undefined);
});
// Fallback to undefined:
this.#data.setValue(undefined);
},
'observePropertyValue',
);
}
async create(contentElementTypeId: string, groupKey?: string | null) {
@@ -174,10 +186,16 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
throw new Error('No data to submit.');
}
const context = await this.getContext(UMB_PROPERTY_CONTEXT);
context.setValue(
appendToFrozenArray(context.getValue() ?? [], this.#data.getValue(), (x) => x?.contentElementTypeKey),
await this.#gotPropertyContext;
if (!this.#propertyContext) {
throw new Error('Property context is not available.');
}
this.#propertyContext.setValue(
appendToFrozenArray(
this.#propertyContext.getValue() ?? [],
this.#data.getValue(),
(x) => x?.contentElementTypeKey,
),
);
this.setIsNew(false);

View File

@@ -53,7 +53,7 @@ export abstract class UmbBlockEntriesContext<
this._retrieveManager = this.consumeContext(blockManagerContextToken, (blockGridManager) => {
this._manager = blockGridManager;
this._gotBlockManager();
}).asPromise();
}).asPromise({ preventTimeout: true });
}
async getManager() {

View File

@@ -363,6 +363,9 @@ export abstract class UmbBlockManagerContext<
protected async _createBlockElementData(key: string, contentTypeKey: string) {
//
const appLanguage = await this.getContext(UMB_APP_LANGUAGE_CONTEXT);
if (!appLanguage) {
throw new Error('Could not retrieve app language context.');
}
const contentStructure = this.getStructure(contentTypeKey);
if (!contentStructure) {
@@ -501,6 +504,9 @@ export abstract class UmbBlockManagerContext<
if (varyByCulture) {
// get all mandatory cultures:
const appLanguageContext = await this.getContext(UMB_APP_LANGUAGE_CONTEXT);
if (!appLanguageContext) {
throw new Error('Could not retrieve app language context.');
}
const mandatoryLanguages = await appLanguageContext.getMandatoryLanguages();
mandatoryLanguages.forEach((x) => {
// No need to insert the same expose twice:

View File

@@ -88,7 +88,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#modalContext = context;
this.#originData = context?.data.originData;
context.onSubmit().catch(this.#modalRejected);
}).asPromise();
}).asPromise({ preventTimeout: true });
this.#retrieveBlockManager = this.consumeContext(UMB_BLOCK_MANAGER_CONTEXT, (manager) => {
this.#blockManager = manager;
@@ -97,7 +97,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#retrieveBlockEntries = this.consumeContext(UMB_BLOCK_ENTRIES_CONTEXT, (context) => {
this.#blockEntries = context;
}).asPromise();
}).asPromise({ preventTimeout: true });
this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (context) => {
this.#name.setValue(context.getName());

View File

@@ -94,6 +94,9 @@ export class UmbClipboardLocalStorageManager extends UmbControllerBase {
}
const context = await this.getContext(UMB_CURRENT_USER_CONTEXT);
if (!context) {
throw new Error('Could not get current user context');
}
this.#currentUserUnique = context.getUnique();
return this.#currentUserUnique;
}

View File

@@ -12,7 +12,7 @@ import {
import { UMB_CLIPBOARD_PROPERTY_CONTEXT } from './clipboard.property-context-token.js';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_PROPERTY_CONTEXT, UmbPropertyValueCloneController } from '@umbraco-cms/backoffice/property';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor';
@@ -27,16 +27,8 @@ import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardPropertyContext> {
#init?: Promise<unknown>;
#modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
constructor(host: UmbControllerHost) {
super(host, UMB_CLIPBOARD_PROPERTY_CONTEXT);
this.#init = Promise.all([
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => {
this.#modalManagerContext = context;
}).asPromise(),
]);
}
/**
@@ -99,6 +91,9 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp
propertyEditorUiAlias: string;
}): Promise<UmbClipboardEntryDetailModel | undefined> {
const clipboardContext = await this.getContext(UMB_CLIPBOARD_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context is required');
}
const copyValueResolver = new UmbClipboardCopyPropertyValueTranslatorValueResolver(this);
const values = await copyValueResolver.resolve(args.propertyValue, args.propertyEditorUiAlias);
@@ -129,11 +124,15 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp
const pasteTranslatorManifests = this.getPasteTranslatorManifests(args.propertyEditorUiAlias);
const propertyEditorUiManifest = await this.#findPropertyEditorUiManifest(args.propertyEditorUiAlias);
const config = (await this.getContext(UMB_PROPERTY_CONTEXT)).getConfig();
const config = (await this.getContext(UMB_PROPERTY_CONTEXT))?.getConfig();
if (!config) {
throw new Error('Property context is required');
}
const valueResolver = new UmbClipboardPastePropertyValueTranslatorValueResolver(this);
const modal = this.#modalManagerContext?.open(this, UMB_CLIPBOARD_ENTRY_PICKER_MODAL, {
const result = await umbOpenModal(this, UMB_CLIPBOARD_ENTRY_PICKER_MODAL, {
data: {
asyncFilter: async (clipboardEntryDetail) => {
const hasSupportedPasteTranslator = this.hasSupportedPasteTranslator(
@@ -164,7 +163,6 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp
},
});
const result = await modal?.onSubmit();
const selection = result?.selection || [];
if (!selection.length) {
@@ -223,6 +221,9 @@ export class UmbClipboardPropertyContext extends UmbContextBase<UmbClipboardProp
}
const clipboardContext = await this.getContext(UMB_CLIPBOARD_CONTEXT);
if (!clipboardContext) {
throw new Error('Clipboard context is required');
}
const entry = await clipboardContext.read(clipboardEntryUnique);
if (!entry) {

View File

@@ -42,7 +42,7 @@ export class UmbCollectionCreateActionButtonElement extends UmbLitElement {
}
if (!controller.api) throw new Error('No API found');
await controller.api.execute();
await controller.api.execute().catch(() => {});
}
constructor() {

View File

@@ -292,6 +292,7 @@ export class UmbDefaultCollectionContext<
#onReloadChildrenRequest = async (event: UmbRequestReloadChildrenOfEntityEvent) => {
// check if the collection is in the same context as the entity from the event
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
if (!entityContext) return;
const unique = entityContext.getUnique();
const entityType = entityContext.getEntityType();

View File

@@ -104,7 +104,7 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
}
event.stopPropagation();
await this._firstActionApi?.execute();
await this._firstActionApi?.execute().catch(() => {});
}
#onActionExecuted() {

View File

@@ -33,6 +33,9 @@ export class UmbInputManifestElement extends UmbLitElement {
async #onClick() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) {
throw new Error('Modal manager not found.');
}
const modalContext = modalManager.open(this, UMB_ITEM_PICKER_MODAL, {
data: {
headline: `${this.localize.term('general_choose')}...`,

View File

@@ -51,6 +51,9 @@ export class UmbContentTypeWorkspaceEditorHeaderElement extends UmbLitElement {
private async _handleIconClick() {
const [alias, color] = this._icon?.replace('color-', '')?.split(' ') ?? [];
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) {
throw new Error('Modal manager not found.');
}
const modalContext = modalManager.open(this, UMB_ICON_PICKER_MODAL, {
value: {
icon: alias,

View File

@@ -153,6 +153,9 @@ export abstract class UmbContentTypeWorkspaceContextBase<
this._data.setPersisted(this.structure.getOwnerContentType());
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) {
throw new Error('Could not get the action event context');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
@@ -176,6 +179,9 @@ export abstract class UmbContentTypeWorkspaceContextBase<
this._data.setPersisted(this.structure.getOwnerContentType());
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Could not get the action event context');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.getUnique()!,
entityType: this.getEntityType(),

View File

@@ -18,7 +18,7 @@ import type {
UmbWorkspaceViewElement,
} from '@umbraco-cms/backoffice/workspace';
import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import { umbConfirmModal, umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
@@ -367,15 +367,13 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements
isNew: this.#workspaceContext.getIsNew()!,
};
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManagerContext.open(this, UMB_COMPOSITION_PICKER_MODAL, {
const value = await umbOpenModal(this, UMB_COMPOSITION_PICKER_MODAL, {
data: compositionConfiguration,
});
await modalContext?.onSubmit();
}).catch(() => undefined);
if (!modalContext?.value) return;
if (!value) return;
const compositionIds = modalContext.getValue().selection;
const compositionIds = value.selection;
this.#workspaceContext?.setCompositions(
compositionIds.map((unique) => ({ contentType: { unique }, compositionType: CompositionTypeModel.COMPOSITION })),

View File

@@ -39,7 +39,7 @@ import {
UmbVariantValuesValidationPathTranslator,
} from '@umbraco-cms/backoffice/validation';
import type { UmbModalToken } from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import {
UmbEntityUpdatedEvent,
@@ -578,6 +578,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
const variantsWithoutAName = saveData.variants.filter((x) => !x.name);
if (variantsWithoutAName.length > 0) {
const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT);
if (!validationContext) {
throw new Error('Validation context is missing');
}
variantsWithoutAName.forEach((variant) => {
validationContext.messages.addMessage(
'client',
@@ -657,17 +660,13 @@ export abstract class UmbContentDetailWorkspaceContextBase<
variantIds.push(UmbVariantId.Create(options[0]));
} else if (this.#saveModalToken) {
// If there are multiple variants, we will open the modal to let the user pick which variants to save.
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const result = await modalManagerContext
.open(this, this.#saveModalToken, {
data: {
options,
pickableFilter: this._saveableVariantsFilter,
},
value: { selection: selected },
})
.onSubmit()
.catch(() => undefined);
const result = await umbOpenModal(this, this.#saveModalToken, {
data: {
options,
pickableFilter: this._saveableVariantsFilter,
},
value: { selection: selected },
}).catch(() => undefined);
if (!result?.selection.length) return;
@@ -753,6 +752,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
this._data.setCurrent(newCurrentData);
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) {
throw new Error('Event context is missing');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
@@ -797,6 +799,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
const entityType = this.getEntityType();
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) {
throw new Error('Event context is missing');
}
const structureEvent = new UmbRequestReloadStructureForEntityEvent({ unique, entityType });
eventContext.dispatchEvent(structureEvent);

View File

@@ -2,7 +2,7 @@ import { UmbEntityActionBase } from '../../entity-action-base.js';
import type { UmbEntityActionArgs } from '../../types.js';
import type { MetaEntityActionCreateKind } from './types.js';
import { UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL } from './modal/constants.js';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import {
@@ -61,15 +61,12 @@ export class UmbCreateEntityAction extends UmbEntityActionBase<MetaEntityActionC
return;
}
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL, {
await umbOpenModal(this, UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL, {
data: {
unique: this.args.unique,
entityType: this.args.entityType,
},
});
await modalContext.onSubmit();
}
async #createSingleOptionApi(

View File

@@ -68,9 +68,12 @@ export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseEle
throw new Error('No API found');
}
await controller.api.execute();
this._submitModal();
controller.api
.execute()
.then(() => {
this._submitModal();
})
.catch(() => {});
}
async #onNavigate(event: Event, href: string | undefined) {
@@ -135,7 +138,7 @@ export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseEle
icon=${manifest.meta.icon}
href=${ifDefined(href)}
target=${this.#getTarget(href)}
@open=${(event: Event) => this.#onOpen(event, controller)}
@open=${async (event: Event) => await this.#onOpen(event, controller).catch(() => undefined)}
@click=${(event: Event) => this.#onNavigate(event, href)}>
</umb-ref-item>
`;

View File

@@ -60,6 +60,9 @@ export class UmbDeleteEntityAction<
async #notify() {
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Action event context not found.');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,

View File

@@ -30,6 +30,9 @@ export class UmbDuplicateEntityAction extends UmbEntityActionBase<any> {
async #reloadMenu() {
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Action event context is not available');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -49,7 +49,7 @@ export class UmbEntityActionDefaultElement<
async #onClickLabel(event: UUIMenuItemEvent) {
if (!this._href) {
event.stopPropagation();
await this.#api?.execute();
await this.#api?.execute().catch(() => {});
}
this.dispatchEvent(new UmbActionExecutedEvent());
}

View File

@@ -7,7 +7,7 @@ import {
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
import type { MetaEntityBulkActionDuplicateToKind } from '@umbraco-cms/backoffice/extension-registry';
@@ -15,9 +15,7 @@ export class UmbMediaDuplicateEntityBulkAction extends UmbEntityBulkActionBase<M
async execute() {
if (this.selection?.length === 0) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
const value = await umbOpenModal(this, UMB_TREE_PICKER_MODAL, {
data: {
foldersOnly: this.args.meta.foldersOnly,
hideTreeRoot: this.args.meta.hideTreeRoot,
@@ -25,7 +23,6 @@ export class UmbMediaDuplicateEntityBulkAction extends UmbEntityBulkActionBase<M
},
});
const value = await modalContext.onSubmit().catch(() => undefined);
if (!value?.selection?.length) return;
const destinationUnique = value.selection[0];

View File

@@ -7,7 +7,7 @@ import {
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
import type { MetaEntityBulkActionMoveToKind } from '@umbraco-cms/backoffice/extension-registry';
@@ -15,9 +15,7 @@ export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<MetaEn
async execute() {
if (this.selection?.length === 0) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
const value = await umbOpenModal(this, UMB_TREE_PICKER_MODAL, {
data: {
foldersOnly: this.args.meta.foldersOnly,
hideTreeRoot: this.args.meta.hideTreeRoot,
@@ -25,7 +23,6 @@ export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<MetaEn
},
});
const value = await modalContext.onSubmit().catch(() => undefined);
if (!value?.selection?.length) return;
const destinationUnique = value.selection[0];

View File

@@ -26,7 +26,7 @@ export class UmbEntityBulkActionDefaultElement<
async #onClick(event: PointerEvent) {
if (!this.api) return;
event.stopPropagation();
await this.api.execute();
await this.api.execute().catch(() => {});
this.dispatchEvent(new UmbActionExecutedEvent());
}

View File

@@ -4,8 +4,6 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { ManifestElementAndApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api';
// TODO: Eslint: allow abstract element class to end with "ElementBase" instead of "Element"
export abstract class UmbExtensionElementAndApiSlotElementBase<
ManifestType extends ManifestElementAndApi,
> extends UmbLitElement {

View File

@@ -1,38 +1,17 @@
import { UMB_MODAL_MANAGER_CONTEXT } from '../../context/index.js';
import { UmbOpenModalController } from '../../index.js';
import { UMB_CONFIRM_MODAL, type UmbConfirmModalData } from './confirm-modal.token.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
/** @deprecated use `UmbConfirmModalData`, will be removed in v.17 */
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface UmbConfirmModalArgs extends UmbConfirmModalData {}
export class UmbConfirmModalController extends UmbControllerBase {
async open(args: UmbConfirmModalArgs): Promise<void> {
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManagerContext.open(this, UMB_CONFIRM_MODAL, {
data: args,
});
const p = modalContext.onSubmit();
p.catch(() => {
this.destroy();
});
await p;
// This is a one time off, so we can destroy our selfs.
this.destroy();
return;
}
}
/**
*
* @param host {UmbControllerHost} - The host controller
* @param args {UmbConfirmModalArgs} - The data to pass to the modal
* @returns {UmbConfirmModalController} The modal controller instance
* @param args {UmbConfirmModalData} - The data to pass to the modal
* @returns {UmbOpenModalController} The modal controller instance
*/
export function umbConfirmModal(host: UmbControllerHost, args: UmbConfirmModalArgs) {
return new UmbConfirmModalController(host).open(args);
export function umbConfirmModal(host: UmbControllerHost, data: UmbConfirmModalData) {
return new UmbOpenModalController(host).open(UMB_CONFIRM_MODAL, { data });
}

View File

@@ -116,7 +116,7 @@ export class UmbModalContext<
async _internal_removeCurrentModal() {
const routeContext = await this.getContext(UMB_ROUTE_CONTEXT);
routeContext._internal_removeModalPath(this.#activeModalPath);
routeContext?._internal_removeModalPath(this.#activeModalPath);
}
forceResolve() {
@@ -151,8 +151,9 @@ export class UmbModalContext<
this._internal_removeCurrentModal();
return;
}
this.#submitResolver?.(this.getValue());
const resolver = this.#submitResolver;
this.#markAsResolved();
resolver?.(this.getValue());
// TODO: Could we clean up this class here? (Example destroy the value state, and other things?)
}
@@ -171,8 +172,9 @@ export class UmbModalContext<
this._internal_removeCurrentModal();
return;
}
this.#submitRejecter?.(reason);
const resolver = this.#submitRejecter;
this.#markAsResolved();
resolver?.(reason);
// TODO: Could we clean up this class here? (Example destroy the value state, and other things?)
}
@@ -182,8 +184,8 @@ export class UmbModalContext<
* @public
* @memberof UmbModalContext
*/
public onSubmit() {
return this.#submitPromise;
public async onSubmit() {
return await this.#submitPromise;
}
/**

View File

@@ -0,0 +1 @@
export * from './open-modal.controller.js';

View File

@@ -0,0 +1,45 @@
import { UMB_MODAL_MANAGER_CONTEXT, type UmbModalContextClassArgs } from '../index.js';
import type { UmbModalToken } from '../token/modal-token.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbOpenModalController extends UmbControllerBase {
async open<
ModalData extends { [key: string]: any } = { [key: string]: any },
ModalValue = unknown,
ModalAliasTypeAsToken extends UmbModalToken = UmbModalToken<ModalData, ModalValue>,
>(
modalAlias: UmbModalToken<ModalData, ModalValue> | string,
args: UmbModalContextClassArgs<ModalAliasTypeAsToken> = {},
): Promise<ModalValue> {
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManagerContext) {
this.destroy();
throw new Error('Modal manager not found.');
}
const modalContext = modalManagerContext.open(this, modalAlias, args);
return await modalContext.onSubmit().finally(() => {
this.destroy();
});
}
}
/**
*
* @param host {UmbControllerHost} - The host controller
* @param args {UmbConfirmModalArgs} - The data to pass to the modal
* @returns {UmbConfirmModalController} The modal controller instance
*/
export function umbOpenModal<
ModalData extends { [key: string]: any } = { [key: string]: any },
ModalValue = unknown,
ModalAliasTypeAsToken extends UmbModalToken = UmbModalToken<ModalData, ModalValue>,
>(
host: UmbControllerHost,
modalAlias: UmbModalToken<ModalData, ModalValue> | string,
args: UmbModalContextClassArgs<ModalAliasTypeAsToken> = {},
): Promise<ModalValue> {
return new UmbOpenModalController(host).open(modalAlias, args);
}

View File

@@ -1,6 +1,7 @@
import './component/modal.element.js';
export * from './common/index.js';
export * from './controller/index.js';
export * from './component/modal-base.element.js';
export * from './component/modal.element.js';
export * from './context/index.js';

View File

@@ -13,6 +13,9 @@ export class UmbPeekErrorNotificationElement extends UmbLitElement {
async #onClick() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
if (!modalManager) {
throw new Error('Modal manager not found.');
}
modalManager.open(this, UMB_ERROR_VIEWER_MODAL, { data: this.data?.details });

View File

@@ -8,6 +8,9 @@ import './peek-error-notification.element.js';
export class UmbPeekErrorController extends UmbControllerBase {
async open(args: UmbPeekErrorArgs): Promise<void> {
const context = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!context) {
throw new Error('Could not get notification context');
}
context.peek('danger', {
elementName: 'umb-peek-error-notification',

View File

@@ -2,7 +2,7 @@ import { UMB_PICKER_INPUT_CONTEXT } from './picker-input.context-token.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import { umbConfirmModal, umbOpenModal } from '@umbraco-cms/backoffice/modal';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal';
@@ -91,8 +91,7 @@ export class UmbPickerInputContext<
async openPicker(pickerData?: Partial<PickerModalConfigType>) {
await this.#itemManager.init;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, this.modalAlias, {
const modalValue = await umbOpenModal(this, this.modalAlias, {
data: {
multiple: this._max === 1 ? false : true,
...pickerData,
@@ -102,7 +101,6 @@ export class UmbPickerInputContext<
} as PickerModalValueType,
});
const modalValue = await modalContext?.onSubmit();
this.setSelection(modalValue.selection);
this.getHostElement().dispatchEvent(new UmbChangeEvent());
}

View File

@@ -4,6 +4,9 @@ import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
export class UmbClearPropertyAction extends UmbPropertyActionBase {
override async execute() {
const propertyContext = await this.getContext(UMB_PROPERTY_CONTEXT);
if (!propertyContext) {
return;
}
propertyContext.clearValue();
}
}

View File

@@ -34,7 +34,7 @@ export class UmbPropertyActionElement<
async #onClickLabel(event: UUIMenuItemEvent) {
if (!this._href) {
event.stopPropagation();
await this.#api?.execute();
await this.#api?.execute().catch(() => {});
}
this.dispatchEvent(new UmbActionExecutedEvent());
}

View File

@@ -57,7 +57,7 @@ export class UmbPropertyTypeWorkspaceContext<PropertyTypeData extends UmbPropert
this.#contentTypeContext = context;
})
.skipHost()
.asPromise();
.asPromise({ preventTimeout: true });
this.routes.setRoutes([
{

View File

@@ -30,6 +30,7 @@ export class UmbEmptyRecycleBinEntityAction extends UmbEntityActionBase<MetaEnti
await recycleBinRepository.requestEmpty();
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) throw new Error('Action event context is not available');
const event = new UmbRequestReloadChildrenOfEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -6,7 +6,7 @@ import type {
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { html, customElement, state, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_MODAL_MANAGER_CONTEXT, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement, umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
@@ -102,15 +102,12 @@ export class UmbRestoreFromRecycleBinModalElement extends UmbModalBaseElement<
async #onSelectCustomDestination() {
if (!this.data?.pickerModal) throw new Error('Cannot select a destination without a picker modal.');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, this.data.pickerModal, {
const { selection } = await umbOpenModal(this, this.data.pickerModal, {
data: {
multiple: false,
},
});
const { selection } = await modal.onSubmit();
if (selection.length > 0) {
const unique = selection[0];
this.setDestination(unique);

View File

@@ -1,6 +1,6 @@
import { UMB_RESTORE_FROM_RECYCLE_BIN_MODAL } from './modal/restore-from-recycle-bin-modal.token.js';
import type { MetaEntityActionRestoreFromRecycleBinKind } from './types.js';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
@@ -17,8 +17,7 @@ export class UmbRestoreFromRecycleBinEntityAction extends UmbEntityActionBase<Me
override async execute() {
if (!this.args.unique) throw new Error('Cannot restore an item without a unique identifier.');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, UMB_RESTORE_FROM_RECYCLE_BIN_MODAL, {
const { destination } = await umbOpenModal(this, UMB_RESTORE_FROM_RECYCLE_BIN_MODAL, {
data: {
unique: this.args.unique,
entityType: this.args.entityType,
@@ -28,10 +27,12 @@ export class UmbRestoreFromRecycleBinEntityAction extends UmbEntityActionBase<Me
},
});
const { destination } = await modal.onSubmit();
if (!destination) throw new Error('Cannot reload the structure without a destination.');
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Event context not found.');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -68,6 +68,7 @@ export class UmbTrashEntityAction<
async #notify() {
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) throw new Error('Action event context is missing.');
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,

View File

@@ -38,14 +38,15 @@ export abstract class UmbDetailRepositoryBase<
this.detailDataSource = new detailSource(host) as UmbDetailDataSourceType;
// TODO: ideally no preventTimeouts here.. [NL]
this.#init = Promise.all([
this.consumeContext(detailStoreContextAlias, (instance) => {
this.#detailStore = instance;
}).asPromise(),
}).asPromise({ preventTimeout: true }),
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => {
this.#notificationContext = instance;
}).asPromise(),
}).asPromise({ preventTimeout: true }),
]);
}

View File

@@ -23,7 +23,7 @@ export class UmbItemRepositoryBase<ItemType extends { unique: string }>
this._init = this.consumeContext(itemStoreContextAlias, (instance) => {
this._itemStore = instance as UmbItemStore<ItemType>;
}).asPromise();
}).asPromise({ preventTimeout: true });
}
/**

View File

@@ -107,7 +107,10 @@ export class UmbResourceController extends UmbControllerBase {
switch (error.status ?? 0) {
case 401: {
// See if we can get the UmbAuthContext and let it know the user is timed out
const authContext = await this.getContext(UMB_AUTH_CONTEXT);
const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true });
if (!authContext) {
throw new Error('Could not get the auth context');
}
authContext.timeOut();
break;
}

View File

@@ -48,7 +48,6 @@ export class UmbModalRouteRegistrationController<
{
//
#init;
#contextConsumer;
#addendum?: string;
#additionalPath?: string;
@@ -101,11 +100,10 @@ export class UmbModalRouteRegistrationController<
);
});
this.#contextConsumer = this.consumeContext(UMB_ROUTE_CONTEXT, (_routeContext) => {
this.#init = this.consumeContext(UMB_ROUTE_CONTEXT, (_routeContext) => {
this.#routeContext = _routeContext;
this.#registerModal();
});
this.#init = this.#contextConsumer.asPromise();
}).asPromise({ preventTimeout: true });
}
/**
@@ -349,7 +347,6 @@ export class UmbModalRouteRegistrationController<
public override destroy(): void {
super.destroy();
this.#contextConsumer.destroy();
this.#modalRegistrationContext = undefined;
this.#uniquePaths = undefined as any;
this.#routeContext = undefined;

View File

@@ -37,6 +37,7 @@ export abstract class UmbRenameServerFileRepositoryBase<
if (data) {
const detailStore = await this.getContext(this.#detailStoreContextAlias);
if (!detailStore) throw new Error('Detail store is missing');
/* When renaming a file the unique changed because it is based on the path/name
We need to remove the old item and append the new item */
@@ -44,6 +45,7 @@ export abstract class UmbRenameServerFileRepositoryBase<
detailStore.append(data);
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) throw new Error('Notification context is missing');
const notification = { data: { message: `Renamed` } };
notificationContext.peek('positive', notification);
}

View File

@@ -2,15 +2,14 @@ import { UMB_RENAME_SERVER_FILE_MODAL } from './modal/rename-server-file-modal.t
import type { MetaEntityActionRenameServerFileKind } from './types.js';
import { UmbServerFileRenamedEntityEvent } from './event/index.js';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
export class UmbRenameEntityAction extends UmbEntityActionBase<MetaEntityActionRenameServerFileKind> {
override async execute() {
if (!this.args.unique) throw new Error('Unique is required to rename an entity');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_RENAME_SERVER_FILE_MODAL, {
const res = await umbOpenModal(this, UMB_RENAME_SERVER_FILE_MODAL, {
data: {
unique: this.args.unique,
renameRepositoryAlias: this.args.meta.renameRepositoryAlias,
@@ -18,29 +17,25 @@ export class UmbRenameEntityAction extends UmbEntityActionBase<MetaEntityActionR
},
});
try {
const res = await modalContext.onSubmit();
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,
});
actionEventContext.dispatchEvent(event);
const event2 = new UmbServerFileRenamedEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,
newName: res.name,
newUnique: res.unique,
});
actionEventContext.dispatchEvent(event2);
} catch (error) {
// TODO: Handle error
console.log(error);
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Event context not found.');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,
});
actionEventContext.dispatchEvent(event);
const event2 = new UmbServerFileRenamedEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,
newName: res.name,
newUnique: res.unique,
});
actionEventContext.dispatchEvent(event2);
}
}

View File

@@ -84,6 +84,7 @@ export class UmbTemporaryFileManager<
maxFileSize *= 1024;
if (item.file.size > maxFileSize) {
const notification = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notification) throw new Error('Notification context is missing');
notification.peek('warning', {
data: {
headline: 'Upload',
@@ -112,6 +113,7 @@ export class UmbTemporaryFileManager<
(disallowedExtensions?.length && disallowedExtensions.includes(fileExtension))
) {
const notification = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notification) throw new Error('Notification context is missing');
notification.peek('warning', {
data: {
message: `${this.#localization.term('media_disallowedFileType')}: ${fileExtension}`,

View File

@@ -62,7 +62,7 @@ export abstract class UmbTreeRepositoryBase<
this._init = this.consumeContext(treeStoreContextAlias, (instance) => {
this._treeStore = instance;
}).asPromise();
}).asPromise({ preventTimeout: true });
}
/**

View File

@@ -52,61 +52,59 @@ export class UmbDefaultTreeElement extends UmbLitElement {
@state()
private _totalPages = 1;
#treeContext?: UmbDefaultTreeContext<UmbTreeItemModel, UmbTreeRootModel>;
#init: Promise<unknown>;
@state()
_treeContext?: UmbDefaultTreeContext<UmbTreeItemModel, UmbTreeRootModel>;
constructor() {
super();
this.#init = Promise.all([
// TODO: Notice this can be retrieve via a api property. [NL]
this.consumeContext(UMB_TREE_CONTEXT, (instance) => {
this.#treeContext = instance;
this.observe(this.#treeContext.treeRoot, (treeRoot) => (this._treeRoot = treeRoot));
this.observe(this.#treeContext.rootItems, (rootItems) => (this._rootItems = rootItems));
this.observe(this.#treeContext.pagination.currentPage, (value) => (this._currentPage = value));
this.observe(this.#treeContext.pagination.totalPages, (value) => (this._totalPages = value));
}).asPromise(),
]);
// TODO: Notice this can be retrieve via a api property. [NL]
this.consumeContext(UMB_TREE_CONTEXT, (instance) => {
this._treeContext = instance;
this.observe(this._treeContext.treeRoot, (treeRoot) => (this._treeRoot = treeRoot));
this.observe(this._treeContext.rootItems, (rootItems) => (this._rootItems = rootItems));
this.observe(this._treeContext.pagination.currentPage, (value) => (this._currentPage = value));
this.observe(this._treeContext.pagination.totalPages, (value) => (this._totalPages = value));
});
}
protected override async updated(
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
): Promise<void> {
super.updated(_changedProperties);
await this.#init;
if (this._treeContext === undefined) return;
if (_changedProperties.has('selectionConfiguration')) {
this._selectionConfiguration = this.selectionConfiguration;
this.#treeContext!.selection.setMultiple(this._selectionConfiguration.multiple ?? false);
this.#treeContext!.selection.setSelectable(this._selectionConfiguration.selectable ?? true);
this.#treeContext!.selection.setSelection(this._selectionConfiguration.selection ?? []);
this._treeContext!.selection.setMultiple(this._selectionConfiguration.multiple ?? false);
this._treeContext!.selection.setSelectable(this._selectionConfiguration.selectable ?? true);
this._treeContext!.selection.setSelection(this._selectionConfiguration.selection ?? []);
}
if (_changedProperties.has('startNode')) {
this.#treeContext!.setStartNode(this.startNode);
this._treeContext!.setStartNode(this.startNode);
}
if (_changedProperties.has('hideTreeRoot')) {
this.#treeContext!.setHideTreeRoot(this.hideTreeRoot);
this._treeContext!.setHideTreeRoot(this.hideTreeRoot);
}
if (_changedProperties.has('foldersOnly')) {
this.#treeContext!.setFoldersOnly(this.foldersOnly ?? false);
this._treeContext!.setFoldersOnly(this.foldersOnly ?? false);
}
if (_changedProperties.has('selectableFilter')) {
this.#treeContext!.selectableFilter = this.selectableFilter;
this._treeContext!.selectableFilter = this.selectableFilter;
}
if (_changedProperties.has('filter')) {
this.#treeContext!.filter = this.filter;
this._treeContext!.filter = this.filter;
}
}
getSelection() {
return this.#treeContext?.selection.getSelection();
return this._treeContext?.selection.getSelection();
}
override render() {
@@ -144,7 +142,7 @@ export class UmbDefaultTreeElement extends UmbLitElement {
#onLoadMoreClick = (event: any) => {
event.stopPropagation();
const next = (this._currentPage = this._currentPage + 1);
this.#treeContext?.pagination.setCurrentPageNumber(next);
this._treeContext?.pagination.setCurrentPageNumber(next);
};
#renderPaging() {

View File

@@ -1,7 +1,7 @@
import { UMB_DUPLICATE_TO_MODAL } from './modal/duplicate-to-modal.token.js';
import type { MetaEntityActionDuplicateToKind, UmbDuplicateToRepository } from './types.js';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
@@ -10,8 +10,7 @@ export class UmbDuplicateToEntityAction extends UmbEntityActionBase<MetaEntityAc
if (!this.args.unique) throw new Error('Unique is not available');
if (!this.args.entityType) throw new Error('Entity Type is not available');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, UMB_DUPLICATE_TO_MODAL, {
const value = await umbOpenModal(this, UMB_DUPLICATE_TO_MODAL, {
data: {
unique: this.args.unique,
entityType: this.args.entityType,
@@ -20,32 +19,28 @@ export class UmbDuplicateToEntityAction extends UmbEntityActionBase<MetaEntityAc
},
});
try {
const value = await modal.onSubmit();
const destinationUnique = value.destination.unique;
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
const destinationUnique = value.destination.unique;
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
const duplicateRepository = await createExtensionApiByAlias<UmbDuplicateToRepository>(
this,
this.args.meta.duplicateRepositoryAlias,
);
if (!duplicateRepository) throw new Error('Duplicate repository is not available');
const duplicateRepository = await createExtensionApiByAlias<UmbDuplicateToRepository>(
this,
this.args.meta.duplicateRepositoryAlias,
);
if (!duplicateRepository) throw new Error('Duplicate repository is not available');
const { error } = await duplicateRepository.requestDuplicateTo({
unique: this.args.unique,
destination: { unique: destinationUnique },
});
const { error } = await duplicateRepository.requestDuplicateTo({
unique: this.args.unique,
destination: { unique: destinationUnique },
});
if (!error) {
this.#reloadMenu();
}
} catch (error) {
console.error(error);
if (!error) {
this.#reloadMenu();
}
}
async #reloadMenu() {
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) throw new Error('Action event context is not available');
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -1,7 +1,7 @@
import type { UmbMoveRepository } from './move-repository.interface.js';
import type { MetaEntityActionMoveToKind } from './types.js';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
@@ -11,8 +11,7 @@ export class UmbMoveToEntityAction extends UmbEntityActionBase<MetaEntityActionM
if (!this.args.unique) throw new Error('Unique is not available');
if (!this.args.entityType) throw new Error('Entity Type is not available');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
const value = await umbOpenModal(this, UMB_TREE_PICKER_MODAL, {
data: {
treeAlias: this.args.meta.treeAlias,
foldersOnly: this.args.meta.foldersOnly,
@@ -20,7 +19,6 @@ export class UmbMoveToEntityAction extends UmbEntityActionBase<MetaEntityActionM
},
});
const value = await modalContext.onSubmit();
const destinationUnique = value.selection[0];
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
@@ -34,6 +32,7 @@ export class UmbMoveToEntityAction extends UmbEntityActionBase<MetaEntityActionM
async #reloadMenu() {
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) throw new Error('Action Event Context is not available');
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -11,7 +11,7 @@ export class UmbReloadTreeItemChildrenEntityAction extends UmbEntityActionBase<M
override async execute() {
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event context is not available');
eventContext.dispatchEvent(
new UmbRequestReloadChildrenOfEntityEvent({
unique: this.args.unique,

View File

@@ -1,24 +1,22 @@
import { UMB_SORT_CHILDREN_OF_MODAL } from './modal/index.js';
import type { MetaEntityActionSortChildrenOfKind } from './types.js';
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
export class UmbSortChildrenOfEntityAction extends UmbEntityActionBase<MetaEntityActionSortChildrenOfKind> {
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this._host, UMB_SORT_CHILDREN_OF_MODAL, {
await umbOpenModal(this, UMB_SORT_CHILDREN_OF_MODAL, {
data: {
unique: this.args.unique,
entityType: this.args.entityType,
sortChildrenOfRepositoryAlias: this.args.meta.sortChildrenOfRepositoryAlias,
treeRepositoryAlias: this.args.meta.treeRepositoryAlias,
},
});
await modal.onSubmit().catch(() => undefined);
}).catch(() => undefined);
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event context is not available');
eventContext.dispatchEvent(
new UmbRequestReloadChildrenOfEntityEvent({

View File

@@ -2,12 +2,11 @@ import { UMB_FOLDER_CREATE_MODAL } from '../../modal/index.js';
import type { MetaEntityActionFolderKind } from '../../types.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbCreateFolderEntityAction extends UmbEntityActionBase<MetaEntityActionFolderKind> {
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_FOLDER_CREATE_MODAL, {
await umbOpenModal(this, UMB_FOLDER_CREATE_MODAL, {
data: {
folderRepositoryAlias: this.args.meta.folderRepositoryAlias,
parent: {
@@ -17,9 +16,10 @@ export class UmbCreateFolderEntityAction extends UmbEntityActionBase<MetaEntityA
},
});
await modalContext.onSubmit();
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) {
throw new Error('Event context not found.');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: this.args.entityType,
unique: this.args.unique,

View File

@@ -1,41 +1,20 @@
import type { MetaEntityActionFolderKind, UmbFolderModel } from '../../types.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
export class UmbDeleteFolderEntityAction extends UmbEntityActionBase<MetaEntityActionFolderKind> {
// TODO: make base type for item and detail models
#folderRepository?: UmbDetailRepository<UmbFolderModel>;
#init: Promise<unknown>;
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<MetaEntityActionFolderKind>) {
super(host, args);
// TODO: We should properly look into how we can simplify the one time usage of a extension api, as its a bit of overkill to take conditions/overwrites and observation of extensions into play here: [NL]
// But since this happens when we execute an action, it does most likely not hurt any users, but it is a bit of a overkill to do this for every action: [NL]
this.#init = Promise.all([
new UmbExtensionApiInitializer(
this._host,
umbExtensionsRegistry,
this.args.meta.folderRepositoryAlias,
[this._host],
(permitted, ctrl) => {
this.#folderRepository = permitted ? (ctrl.api as UmbDetailRepository<UmbFolderModel>) : undefined;
},
).asPromise(),
]);
}
override async execute() {
if (!this.args.unique) throw new Error('Unique is not available');
await this.#init;
const folderRepository = await createExtensionApiByAlias<UmbDetailRepository<UmbFolderModel>>(
this._host,
this.args.meta.folderRepositoryAlias,
[this._host],
);
const { data: folder } = await this.#folderRepository!.requestByUnique(this.args.unique);
const { data: folder } = await folderRepository.requestByUnique(this.args.unique);
if (folder) {
// TODO: maybe we can show something about how many items are part of the folder?
@@ -46,9 +25,10 @@ export class UmbDeleteFolderEntityAction extends UmbEntityActionBase<MetaEntityA
confirmLabel: 'Delete',
});
await this.#folderRepository?.delete(this.args.unique);
await folderRepository.delete(this.args.unique);
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) throw new Error('Action event context is missing');
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -2,23 +2,23 @@ import { UMB_FOLDER_UPDATE_MODAL } from '../../modal/index.js';
import type { MetaEntityActionFolderKind } from '../../types.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UmbEntityActionBase, UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbUpdateFolderEntityAction extends UmbEntityActionBase<MetaEntityActionFolderKind> {
override async execute() {
if (!this.args.unique) throw new Error('Unique is not available');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_FOLDER_UPDATE_MODAL, {
await umbOpenModal(this, UMB_FOLDER_UPDATE_MODAL, {
data: {
folderRepositoryAlias: this.args.meta.folderRepositoryAlias,
unique: this.args.unique,
},
});
await modalContext.onSubmit();
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Event context not found.');
}
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -44,7 +44,7 @@ export class UmbServerModelValidatorContext
context.addValidator(this);
// Run translators?
}).asPromise();
}).asPromise({ preventTimeout: true });
}
async askServerForValidation(data: unknown, requestPromise: Promise<UmbDataSourceResponse<string>>): Promise<void> {

View File

@@ -34,7 +34,7 @@ export class UmbWorkspaceActionMenuItemElement<
async #onClickLabel(event: UUIMenuItemEvent) {
if (!this._href) {
event.stopPropagation();
await this.#api?.execute();
await this.#api?.execute().catch(() => {});
}
this.dispatchEvent(new UmbActionExecutedEvent());
}

View File

@@ -4,7 +4,7 @@ import type { UmbEntityDetailWorkspaceContextArgs, UmbEntityDetailWorkspaceConte
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbEntityContext, type UmbEntityModel, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_DISCARD_CHANGES_MODAL, umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import {
UmbEntityUpdatedEvent,
@@ -316,6 +316,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
this._data.setCurrent(data);
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event context not found.');
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
@@ -337,6 +338,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
const entityType = this.getEntityType();
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event context not found.');
const event = new UmbRequestReloadStructureForEntityEvent({ unique, entityType });
eventContext.dispatchEvent(event);
@@ -372,12 +374,10 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
This push will make the "willchangestate" event happen again and due to this somewhat "backward" behavior,
we set an "allowNavigateAway"-flag to prevent the "discard-changes" functionality from running in a loop.*/
e.preventDefault();
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modal = modalManager.open(this, UMB_DISCARD_CHANGES_MODAL);
try {
// navigate to the new url when discarding changes
await modal.onSubmit();
await umbOpenModal(this, UMB_DISCARD_CHANGES_MODAL);
this.#allowNavigateAway = true;
history.pushState({}, '', e.detail.url);
return true;

View File

@@ -15,11 +15,9 @@ export class UmbDataTypeCollectionRepository extends UmbRepositoryBase implement
constructor(host: UmbControllerHost) {
super(host);
this.#init = Promise.all([
this.consumeContext(UMB_DATA_TYPE_ITEM_STORE_CONTEXT, (instance) => {
this.#itemStore = instance;
}).asPromise(),
]);
this.#init = this.consumeContext(UMB_DATA_TYPE_ITEM_STORE_CONTEXT, (instance) => {
this.#itemStore = instance;
}).asPromise({ preventTimeout: true });
this.#collectionSource = new UmbDataTypeCollectionServerDataSource(host);
}

View File

@@ -51,12 +51,10 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement<Um
async #onCreateFolderClick(event: PointerEvent) {
event.stopPropagation();
try {
await this.#createFolderAction?.execute();
this._submitModal();
} catch (error) {
console.error(error);
}
await this.#createFolderAction
?.execute()
.then(() => this._submitModal())
.catch(() => undefined);
}
override render() {

View File

@@ -11,6 +11,9 @@ export class UmbDuplicateDataTypeRepository extends UmbRepositoryBase implements
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Duplicated` } };
notificationContext.peek('positive', notification);
}

View File

@@ -11,6 +11,9 @@ export class UmbMoveDataTypeRepository extends UmbRepositoryBase implements UmbM
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Moved` } };
notificationContext.peek('positive', notification);
}

View File

@@ -129,10 +129,13 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
this.removeUmbController(propContextConsumer);
}).passContextAliasMatches();
const [contentContext, propContext] = await Promise.all([
contentContextConsumer.asPromise(),
propContextConsumer.asPromise(),
contentContextConsumer.asPromise({ preventTimeout: true }),
propContextConsumer.asPromise({ preventTimeout: true }),
this.#initPromise,
]);
if (!contentContext || !propContext) {
throw new Error('Could not get content or property context');
}
const propertyEditorName = this.#propertyEditorUIs.find((ui) => ui.alias === params.uiAlias)?.name;
const dataTypeName = `${contentContext?.getName() ?? ''} - ${propContext.getName() ?? ''} - ${propertyEditorName}`;

View File

@@ -11,11 +11,9 @@ export class UmbDataTypeDetailRepository extends UmbDetailRepositoryBase<UmbData
constructor(host: UmbControllerHost) {
super(host, UmbDataTypeServerDataSource, UMB_DATA_TYPE_DETAIL_STORE_CONTEXT);
this.#init = Promise.all([
this.consumeContext(UMB_DATA_TYPE_DETAIL_STORE_CONTEXT, (instance) => {
this.#detailStore = instance;
}).asPromise(),
]);
this.#init = this.consumeContext(UMB_DATA_TYPE_DETAIL_STORE_CONTEXT, (instance) => {
this.#detailStore = instance;
}).asPromise({ preventTimeout: true });
}
async byPropertyEditorUiAlias(propertyEditorUiAlias: string) {

View File

@@ -2,7 +2,7 @@ import { UMB_DATA_TYPE_WORKSPACE_CONTEXT } from '../../data-type-workspace.conte
import { css, customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
import { UMB_PROPERTY_EDITOR_UI_PICKER_MODAL } from '@umbraco-cms/backoffice/property-editor';
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace';
import { umbBindToValidation } from '@umbraco-cms/backoffice/validation';
@@ -55,15 +55,11 @@ export class UmbDataTypeDetailsWorkspaceViewEditElement extends UmbLitElement im
}
async #openPropertyEditorUIPicker() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const value = await modalManager
.open(this, UMB_PROPERTY_EDITOR_UI_PICKER_MODAL, {
value: {
selection: this._propertyEditorUiAlias ? [this._propertyEditorUiAlias] : [],
},
})
.onSubmit()
.catch(() => undefined);
const value = await umbOpenModal(this, UMB_PROPERTY_EDITOR_UI_PICKER_MODAL, {
value: {
selection: this._propertyEditorUiAlias ? [this._propertyEditorUiAlias] : [],
},
}).catch(() => undefined);
if (value) {
this.#workspaceContext?.setPropertyEditorUiAlias(value.selection[0]);

View File

@@ -2,20 +2,17 @@ import { UmbDictionaryExportRepository } from '../../repository/index.js';
import { UMB_EXPORT_DICTIONARY_MODAL } from './export-dictionary-modal.token.js';
import { blobDownload } from '@umbraco-cms/backoffice/utils';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbExportDictionaryEntityAction extends UmbEntityActionBase<object> {
override async execute() {
if (!this.args.unique) throw new Error('Unique is not available');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_EXPORT_DICTIONARY_MODAL, { data: { unique: this.args.unique } });
const { includeChildren } = await modalContext.onSubmit();
const value = await umbOpenModal(this, UMB_EXPORT_DICTIONARY_MODAL, { data: { unique: this.args.unique } });
// Export the file
const repository = new UmbDictionaryExportRepository(this);
const { data } = await repository.requestExport(this.args.unique, includeChildren);
const { data } = await repository.requestExport(this.args.unique, value.includeChildren);
if (!data) return;

View File

@@ -1,12 +1,10 @@
import { UMB_IMPORT_DICTIONARY_MODAL } from './import-dictionary-modal.token.js';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbImportDictionaryEntityAction extends UmbEntityActionBase<object> {
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_IMPORT_DICTIONARY_MODAL, { data: { unique: this.args.unique } });
await modalContext.onSubmit();
await umbOpenModal(this, UMB_IMPORT_DICTIONARY_MODAL, { data: { unique: this.args.unique } });
}
}

View File

@@ -11,6 +11,9 @@ export class UmbMoveDictionaryRepository extends UmbRepositoryBase implements Um
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Moved` } };
notificationContext.peek('positive', notification);
}

View File

@@ -1,18 +1,11 @@
import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../../entity.js';
import { UMB_DOCUMENT_BLUEPRINT_OPTIONS_CREATE_MODAL } from './constants.js';
import type { UmbEntityActionArgs } from '@umbraco-cms/backoffice/entity-action';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbCreateDocumentBlueprintEntityAction extends UmbEntityActionBase<never> {
constructor(host: UmbControllerHost, args: UmbEntityActionArgs<never>) {
super(host, args);
}
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_DOCUMENT_BLUEPRINT_OPTIONS_CREATE_MODAL, {
const value = await umbOpenModal(this, UMB_DOCUMENT_BLUEPRINT_OPTIONS_CREATE_MODAL, {
data: {
parent: {
unique: this.args.unique,
@@ -21,11 +14,10 @@ export class UmbCreateDocumentBlueprintEntityAction extends UmbEntityActionBase<
},
});
await modalContext.onSubmit().catch(() => undefined);
const documentTypeUnique = modalContext.getValue().documentTypeUnique;
const documentTypeUnique = value.documentTypeUnique;
if (!documentTypeUnique) return;
// TODO: Lets avoid having such hardcoded URLs. [NL]
const url = `section/settings/workspace/${UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE}/create/parent/${this.args.entityType}/${this.args.unique ?? 'null'}/${documentTypeUnique}`;
history.pushState(null, '', url);
}

View File

@@ -50,12 +50,12 @@ export class UmbDocumentBlueprintOptionsCreateModalElement extends UmbModalBaseE
async #onCreateFolderClick(event: PointerEvent) {
event.stopPropagation();
try {
await this.#createFolderAction?.execute();
this._submitModal();
} catch (error) {
console.error(error);
}
this.#createFolderAction
?.execute()
.then(() => {
this._submitModal();
})
.catch(() => {});
}
#onSelected(event: UmbSelectedEvent) {

View File

@@ -11,6 +11,9 @@ export class UmbMoveDocumentBlueprintRepository extends UmbRepositoryBase implem
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Moved` } };
notificationContext.peek('positive', notification);
}

View File

@@ -1,11 +1,10 @@
import { UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL } from './modal/constants.js';
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbCreateDocumentTypeEntityAction extends UmbEntityActionBase<never> {
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL, {
await umbOpenModal(this, UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL, {
data: {
parent: {
unique: this.args.unique,
@@ -13,8 +12,6 @@ export class UmbCreateDocumentTypeEntityAction extends UmbEntityActionBase<never
},
},
});
await modalContext.onSubmit();
}
}

View File

@@ -75,7 +75,7 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement<Um
this._submitModal();
return;
} catch {
//console.error(error);
return;
}
break;

View File

@@ -11,6 +11,9 @@ export class UmbDuplicateDocumentTypeRepository extends UmbRepositoryBase implem
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Duplicated` } };
notificationContext.peek('positive', notification);
}

View File

@@ -10,6 +10,9 @@ export class UmbExportDocumentTypeRepository extends UmbRepositoryBase {
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Exported` } };
notificationContext.peek('positive', notification);
}

View File

@@ -1,17 +1,18 @@
import { UMB_DOCUMENT_TYPE_IMPORT_MODAL } from './modal/document-type-import-modal.token.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UmbEntityActionBase, UmbRequestReloadChildrenOfEntityEvent } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
export class UmbImportDocumentTypeEntityAction extends UmbEntityActionBase<object> {
override async execute() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_DOCUMENT_TYPE_IMPORT_MODAL, {
await umbOpenModal(this, UMB_DOCUMENT_TYPE_IMPORT_MODAL, {
data: { unique: this.args.unique },
});
await modalContext.onSubmit().catch(() => {});
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!actionEventContext) {
throw new Error('Action event context is not available');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({
unique: this.args.unique,
entityType: this.args.entityType,

View File

@@ -10,6 +10,9 @@ export class UmbDocumentTypeImportRepository extends UmbRepositoryBase {
if (!error) {
const notificationContext = await this.getContext(UMB_NOTIFICATION_CONTEXT);
if (!notificationContext) {
throw new Error('Notification context not found');
}
const notification = { data: { message: `Imported` } };
notificationContext.peek('positive', notification);
}

Some files were not shown because too many files have changed in this diff Show More