Files
Umbraco-CMS/src/Umbraco.Web.UI.Client/libs/router/route.context.ts
2023-03-28 13:04:11 +02:00

144 lines
4.9 KiB
TypeScript

import type { IRoutingInfo, ISlashOptions } from 'router-slot';
import { UmbRoute } from './route.interface';
import {
UmbContextConsumerController,
UmbContextProviderController,
UmbContextToken,
} from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostInterface } from '@umbraco-cms/backoffice/controller';
import { UMB_MODAL_CONTEXT_TOKEN, UmbModalRouteRegistration } from '@umbraco-cms/backoffice/modal';
const EmptyDiv = document.createElement('div');
const PARAM_IDENTIFIER = /:([^\\/]+)/g;
function stripSlash(path: string): string {
return slashify(path, { start: false, end: false });
}
function slashify(path: string, { start = true, end = true }: Partial<ISlashOptions> = {}): string {
path = start && !path.startsWith('/') ? `/${path}` : !start && path.startsWith('/') ? path.slice(1) : path;
return end && !path.endsWith('/') ? `${path}/` : !end && path.endsWith('/') ? path.slice(0, path.length - 1) : path;
}
export class UmbRouteContext {
#modalRegistrations: UmbModalRouteRegistration[] = [];
#modalContext?: typeof UMB_MODAL_CONTEXT_TOKEN.TYPE;
#contextRoutes: UmbRoute[] = [];
#routerBasePath?: string;
#activeModalPath?: string;
constructor(host: UmbControllerHostInterface, private _onGotModals: (contextRoutes: any) => void) {
new UmbContextProviderController(host, UMB_ROUTE_CONTEXT_TOKEN, this);
new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (context) => {
this.#modalContext = context;
this.#generateContextRoutes();
});
}
public registerModal(registration: UmbModalRouteRegistration) {
this.#modalRegistrations.push(registration);
this.#generateNewUrlBuilder(registration);
this.#generateContextRoutes();
return registration;
}
public unregisterModal(registrationToken: ReturnType<typeof this.registerModal>) {
const index = this.#modalRegistrations.indexOf(registrationToken);
if (index === -1) return;
this.#modalRegistrations.splice(index, 1);
this.#generateContextRoutes();
}
#getModalRoutePath(modalRegistration: UmbModalRouteRegistration) {
return `/modal/${modalRegistration.alias.toString()}/${modalRegistration.path}`;
}
#generateRoute(modalRegistration: UmbModalRouteRegistration): UmbRoute {
return {
path: this.#getModalRoutePath(modalRegistration),
component: EmptyDiv,
setup: (component: HTMLElement, info: IRoutingInfo<any, any>) => {
if (!this.#modalContext) return;
const modalHandler = modalRegistration.routeSetup(this.#modalContext, info.match.params);
if (modalHandler) {
modalHandler.onSubmit().then(
() => {
this.#removeModalPath(info);
},
() => {
this.#removeModalPath(info);
}
);
}
},
};
}
#removeModalPath(info: IRoutingInfo) {
if (window.location.href.includes(info.match.fragments.consumed)) {
window.history.pushState({}, '', window.location.href.split(info.match.fragments.consumed)[0]);
}
}
#generateContextRoutes() {
this.#contextRoutes = this.#modalRegistrations.map((modalRegistration) => {
return this.#generateRoute(modalRegistration);
});
// Add an empty route, so there is a route for the router to react on when no modals are open.
this.#contextRoutes.push({
path: '',
component: EmptyDiv,
});
// TODO: Should we await one frame, to ensure we don't call back too much?.
this._onGotModals(this.#contextRoutes);
}
public _internal_routerGotBasePath(routerBasePath: string) {
if (this.#routerBasePath === routerBasePath) return;
this.#routerBasePath = routerBasePath;
this.#generateNewUrlBuilders();
}
// Also notice each registration should now hold its handler when its active.
public _internal_modalRouterChanged(activeModalPath: string | undefined) {
if (this.#activeModalPath === activeModalPath) return;
if (this.#activeModalPath) {
// If if there is a modal using the old path.
const activeModal = this.#modalRegistrations.find(
(registration) => this.#getModalRoutePath(registration) === this.#activeModalPath
);
if (activeModal) {
this.#modalContext?.close(activeModal.key);
}
}
this.#activeModalPath = activeModalPath;
}
#generateNewUrlBuilders() {
this.#modalRegistrations.forEach(this.#generateNewUrlBuilder);
}
#generateNewUrlBuilder = (modalRegistration: UmbModalRouteRegistration) => {
if (!this.#routerBasePath) return;
const routeBasePath = this.#routerBasePath.endsWith('/') ? this.#routerBasePath : this.#routerBasePath + '/';
const localPath = `modal/${modalRegistration.alias.toString()}/${modalRegistration.path}`;
const urlBuilder = (params: { [key: string]: string | number }) => {
const localRoutePath = stripSlash(
localPath.replace(PARAM_IDENTIFIER, (substring: string, ...args: string[]) => {
return params[args[0]].toString();
})
);
return routeBasePath + localRoutePath;
};
modalRegistration._internal_setRouteBuilder(urlBuilder);
};
}
export const UMB_ROUTE_CONTEXT_TOKEN = new UmbContextToken<UmbRouteContext>('UmbRouterContext');