format
This commit is contained in:
@@ -3,9 +3,7 @@ import { css, html, customElement, state } from '@umbraco-cms/backoffice/externa
|
||||
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/section';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { ManifestSection, UmbSectionElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import {
|
||||
UmbExtensionManifestInitializer, createExtensionElement
|
||||
} from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbExtensionManifestInitializer, createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-backoffice-main')
|
||||
@@ -37,7 +35,7 @@ export class UmbBackofficeMainElement extends UmbLitElement {
|
||||
this._sections = sections;
|
||||
this._createRoutes();
|
||||
},
|
||||
'observeAllowedSections'
|
||||
'observeAllowedSections',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,21 +45,23 @@ export class UmbBackofficeMainElement extends UmbLitElement {
|
||||
const oldValue = this._routes;
|
||||
|
||||
// TODO: Refactor this for re-use across the app where the routes are re-generated at any time.
|
||||
this._routes = this._sections.filter(x => x.manifest).map((section) => {
|
||||
const existingRoute = this._routes.find((r) => r.alias === section.alias);
|
||||
if (existingRoute) {
|
||||
return existingRoute;
|
||||
} else {
|
||||
return {
|
||||
alias: section.alias,
|
||||
path: this._routePrefix + (section.manifest as ManifestSection).meta.pathname,
|
||||
component: () => createExtensionElement(section.manifest!, 'umb-section-default'),
|
||||
setup: (component) => {
|
||||
(component as UmbSectionElement).manifest = section.manifest as ManifestSection;
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
this._routes = this._sections
|
||||
.filter((x) => x.manifest)
|
||||
.map((section) => {
|
||||
const existingRoute = this._routes.find((r) => r.alias === section.alias);
|
||||
if (existingRoute) {
|
||||
return existingRoute;
|
||||
} else {
|
||||
return {
|
||||
alias: section.alias,
|
||||
path: this._routePrefix + (section.manifest as ManifestSection).meta.pathname,
|
||||
component: () => createExtensionElement(section.manifest!, 'umb-section-default'),
|
||||
setup: (component) => {
|
||||
(component as UmbSectionElement).manifest = section.manifest as ManifestSection;
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (this._sections.length > 0) {
|
||||
this._routes.push({
|
||||
@@ -79,7 +79,7 @@ export class UmbBackofficeMainElement extends UmbLitElement {
|
||||
const section = this._sections.find((s) => this._routePrefix + (s.manifest as any).meta.pathname === currentPath);
|
||||
if (!section) return;
|
||||
await section.asPromise();
|
||||
if(section.manifest) {
|
||||
if (section.manifest) {
|
||||
this._backofficeContext?.setActiveSectionAlias(section.alias);
|
||||
this._provideSectionContext(section.manifest);
|
||||
}
|
||||
@@ -105,7 +105,9 @@ export class UmbBackofficeMainElement extends UmbLitElement {
|
||||
:host {
|
||||
background-color: var(--uui-color-background);
|
||||
display: block;
|
||||
height: calc(100% - 60px); // 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive.
|
||||
height: calc(
|
||||
100% - 60px
|
||||
); // 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive.
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -169,7 +169,7 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
|
||||
};
|
||||
|
||||
const { error } = await tryExecute(
|
||||
InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails })
|
||||
InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails }),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -199,7 +199,7 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
|
||||
this._installerContext.nextStep();
|
||||
|
||||
const { error } = await tryExecute(
|
||||
InstallResource.postInstallSetup({ requestBody: this._installerContext.getData() })
|
||||
InstallResource.postInstallSetup({ requestBody: this._installerContext.getData() }),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -243,7 +243,7 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
result.push(
|
||||
this._renderDatabaseName(this.databaseFormData.name ?? this._selectedDatabase.defaultDatabaseName ?? 'umbraco')
|
||||
this._renderDatabaseName(this.databaseFormData.name ?? this._selectedDatabase.defaultDatabaseName ?? 'umbraco'),
|
||||
);
|
||||
|
||||
if (this._selectedDatabase.requiresCredentials) {
|
||||
@@ -271,19 +271,20 @@ export class UmbInstallerDatabaseElement extends UmbLitElement {
|
||||
</uui-form-layout-item>
|
||||
`;
|
||||
|
||||
private _renderDatabaseName = (value: string) => html` <uui-form-layout-item>
|
||||
<uui-label for="database-name" slot="label" required>Database Name</uui-label>
|
||||
<uui-input
|
||||
type="text"
|
||||
.value=${value}
|
||||
id="database-name"
|
||||
name="name"
|
||||
label="Database name"
|
||||
@input=${this._handleChange}
|
||||
placeholder="umbraco"
|
||||
required
|
||||
required-message="Database name is required"></uui-input>
|
||||
</uui-form-layout-item>`;
|
||||
private _renderDatabaseName = (value: string) =>
|
||||
html` <uui-form-layout-item>
|
||||
<uui-label for="database-name" slot="label" required>Database Name</uui-label>
|
||||
<uui-input
|
||||
type="text"
|
||||
.value=${value}
|
||||
id="database-name"
|
||||
name="name"
|
||||
label="Database name"
|
||||
@input=${this._handleChange}
|
||||
placeholder="umbraco"
|
||||
required
|
||||
required-message="Database name is required"></uui-input>
|
||||
</uui-form-layout-item>`;
|
||||
|
||||
private _renderCredentials = () => html`
|
||||
<h2 class="uui-h4">Credentials</h2>
|
||||
|
||||
@@ -60,8 +60,11 @@ export class UmbInstallerLayoutElement extends LitElement {
|
||||
max-width: 1200px;
|
||||
height: 100%;
|
||||
max-height: 900px;
|
||||
box-shadow: 0px 1.1px 3.7px rgba(0, 0, 0, 0.091), 0px 3.1px 10.1px rgba(0, 0, 0, 0.13),
|
||||
0px 7.5px 24.4px rgba(0, 0, 0, 0.169), 0px 25px 81px rgba(0, 0, 0, 0.26);
|
||||
box-shadow:
|
||||
0px 1.1px 3.7px rgba(0, 0, 0, 0.091),
|
||||
0px 3.1px 10.1px rgba(0, 0, 0, 0.13),
|
||||
0px 7.5px 24.4px rgba(0, 0, 0, 0.169),
|
||||
0px 25px 81px rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
|
||||
#grid {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { UmbInstallerContext } from '../installer.context.js';
|
||||
import { html, type TemplateResult } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export const installerContextProvider = (story: () => Node | string | TemplateResult, installerContext = new UmbInstallerContext()) => html`
|
||||
export const installerContextProvider = (
|
||||
story: () => Node | string | TemplateResult,
|
||||
installerContext = new UmbInstallerContext(),
|
||||
) => html`
|
||||
<umb-context-provider
|
||||
style="display: block;margin: 2rem 25%;padding: 1rem;border: 1px solid #ddd;"
|
||||
key="umbInstallerContext"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PathMatch } from "./model.js";
|
||||
import { PathMatch } from './model.js';
|
||||
|
||||
export const CATCH_ALL_WILDCARD: string = "**";
|
||||
export const TRAVERSE_FLAG: string = "\\.\\.\\/";
|
||||
export const CATCH_ALL_WILDCARD: string = '**';
|
||||
export const TRAVERSE_FLAG: string = '\\.\\.\\/';
|
||||
export const PARAM_IDENTIFIER: RegExp = /:([^\\/]+)/g;
|
||||
export const ROUTER_SLOT_TAG_NAME: string = "router-slot";
|
||||
export const ROUTER_SLOT_TAG_NAME: string = 'router-slot';
|
||||
export const GLOBAL_ROUTER_EVENTS_TARGET = window;
|
||||
export const HISTORY_PATCH_NATIVE_KEY: string = `native`;
|
||||
export const DEFAULT_PATH_MATCH: PathMatch = "prefix";
|
||||
export const DEFAULT_PATH_MATCH: PathMatch = 'prefix';
|
||||
|
||||
@@ -5,38 +5,48 @@ export interface IRouterSlot<D = any, P = any> extends HTMLElement {
|
||||
readonly params: Params | null;
|
||||
readonly match: IRouteMatch<D> | null;
|
||||
routes: IRoute<D>[];
|
||||
add: ((routes: IRoute<D>[], navigate?: boolean) => void);
|
||||
clear: (() => void);
|
||||
render: (() => Promise<void>);
|
||||
constructAbsolutePath: ((path: PathFragment) => string);
|
||||
add: (routes: IRoute<D>[], navigate?: boolean) => void;
|
||||
clear: () => void;
|
||||
render: () => Promise<void>;
|
||||
constructAbsolutePath: (path: PathFragment) => string;
|
||||
parent: IRouterSlot<P> | null | undefined;
|
||||
queryParentRouterSlot: (() => IRouterSlot<P> | null);
|
||||
queryParentRouterSlot: () => IRouterSlot<P> | null;
|
||||
}
|
||||
|
||||
export type IRoutingInfo<D = any, P = any> = {
|
||||
slot: IRouterSlot<D, P>,
|
||||
match: IRouteMatch<D>
|
||||
slot: IRouterSlot<D, P>;
|
||||
match: IRouteMatch<D>;
|
||||
};
|
||||
|
||||
export type CustomResolver<D = any, P = any> = ((info: IRoutingInfo<D>) => boolean | void | Promise<boolean> | Promise<void>);
|
||||
export type Guard<D = any, P = any> = ((info: IRoutingInfo<D, P>) => boolean | Promise<boolean>);
|
||||
export type Cancel = (() => boolean);
|
||||
export type CustomResolver<D = any, P = any> = (
|
||||
info: IRoutingInfo<D>,
|
||||
) => boolean | void | Promise<boolean> | Promise<void>;
|
||||
export type Guard<D = any, P = any> = (info: IRoutingInfo<D, P>) => boolean | Promise<boolean>;
|
||||
export type Cancel = () => boolean;
|
||||
|
||||
export type PageComponent = HTMLElement | undefined;
|
||||
export type ModuleResolver = Promise<{default: any; /*PageComponent*/}>;
|
||||
export type Class<T extends PageComponent = PageComponent> = {new (...args: any[]): T;};
|
||||
export type Component = Class | ModuleResolver | PageComponent | (() => Class) | (() => PromiseLike<Class>) | (() => PageComponent) | (() => PromiseLike<PageComponent>) | (() => ModuleResolver) | (() => PromiseLike<ModuleResolver>);
|
||||
export type Setup<D = any> = ((component: PageComponent, info: IRoutingInfo<D>) => void);
|
||||
export type ModuleResolver = Promise<{ default: any /*PageComponent*/ }>;
|
||||
export type Class<T extends PageComponent = PageComponent> = { new (...args: any[]): T };
|
||||
export type Component =
|
||||
| Class
|
||||
| ModuleResolver
|
||||
| PageComponent
|
||||
| (() => Class)
|
||||
| (() => PromiseLike<Class>)
|
||||
| (() => PageComponent)
|
||||
| (() => PromiseLike<PageComponent>)
|
||||
| (() => ModuleResolver)
|
||||
| (() => PromiseLike<ModuleResolver>);
|
||||
export type Setup<D = any> = (component: PageComponent, info: IRoutingInfo<D>) => void;
|
||||
|
||||
export type RouterTree<D = any, P = any> = {slot: IRouterSlot<D, P>} & {child?: RouterTree} | null | undefined;
|
||||
export type PathMatch = "prefix" | "suffix" | "full" | "fuzzy";
|
||||
export type RouterTree<D = any, P = any> = ({ slot: IRouterSlot<D, P> } & { child?: RouterTree }) | null | undefined;
|
||||
export type PathMatch = 'prefix' | 'suffix' | 'full' | 'fuzzy';
|
||||
|
||||
/**
|
||||
* The base route interface.
|
||||
* D = the data type of the data
|
||||
*/
|
||||
export interface IRouteBase<D = any> {
|
||||
|
||||
// The path for the route fragment
|
||||
path: PathFragment;
|
||||
|
||||
@@ -58,7 +68,6 @@ export interface IRouteBase<D = any> {
|
||||
* Route type used for redirection.
|
||||
*/
|
||||
export interface IRedirectRoute<D = any> extends IRouteBase<D> {
|
||||
|
||||
// The paths the route should redirect to. Can either be relative or absolute.
|
||||
redirectTo: string;
|
||||
|
||||
@@ -70,7 +79,6 @@ export interface IRedirectRoute<D = any> extends IRouteBase<D> {
|
||||
* Route type used to resolve and stamp components.
|
||||
*/
|
||||
export interface IComponentRoute<D = any> extends IRouteBase<D> {
|
||||
|
||||
// The component loader (should return a module with a default export)
|
||||
component: Component | PromiseLike<Component>;
|
||||
|
||||
@@ -82,7 +90,6 @@ export interface IComponentRoute<D = any> extends IRouteBase<D> {
|
||||
* Route type used to take control of how the route should resolve.
|
||||
*/
|
||||
export interface IResolverRoute<D = any> extends IRouteBase<D> {
|
||||
|
||||
// A custom resolver that handles the route change
|
||||
resolve: CustomResolver;
|
||||
}
|
||||
@@ -90,13 +97,13 @@ export interface IResolverRoute<D = any> extends IRouteBase<D> {
|
||||
export type IRoute<D = any> = IRedirectRoute<D> | IComponentRoute<D> | IResolverRoute<D>;
|
||||
export type PathFragment = string;
|
||||
export type IPathFragments = {
|
||||
consumed: PathFragment,
|
||||
rest: PathFragment
|
||||
}
|
||||
consumed: PathFragment;
|
||||
rest: PathFragment;
|
||||
};
|
||||
|
||||
export interface IRouteMatch<D = any> {
|
||||
route: IRoute<D>;
|
||||
params: Params,
|
||||
params: Params;
|
||||
fragments: IPathFragments;
|
||||
match: RegExpMatchArray;
|
||||
}
|
||||
@@ -104,57 +111,56 @@ export interface IRouteMatch<D = any> {
|
||||
export type PushStateEvent = CustomEvent<null>;
|
||||
export type ReplaceStateEvent = CustomEvent<null>;
|
||||
export type ChangeStateEvent = CustomEvent<null>;
|
||||
export type WillChangeStateEvent = CustomEvent<{ url?: string | null, eventName: GlobalRouterEvent}>;
|
||||
export type WillChangeStateEvent = CustomEvent<{ url?: string | null; eventName: GlobalRouterEvent }>;
|
||||
export type NavigationStartEvent<D = any> = CustomEvent<IRoutingInfo<D>>;
|
||||
export type NavigationSuccessEvent<D = any> = CustomEvent<IRoutingInfo<D>>;
|
||||
export type NavigationCancelEvent<D = any> = CustomEvent<IRoutingInfo<D>>;
|
||||
export type NavigationErrorEvent<D = any> = CustomEvent<IRoutingInfo<D>>;
|
||||
export type NavigationEndEvent<D = any> = CustomEvent<IRoutingInfo<D>>;
|
||||
|
||||
export type Params = {[key: string]: string};
|
||||
export type Query = {[key: string]: string};
|
||||
export type Params = { [key: string]: string };
|
||||
export type Query = { [key: string]: string };
|
||||
|
||||
export type EventListenerSubscription = (() => void);
|
||||
export type EventListenerSubscription = () => void;
|
||||
|
||||
/**
|
||||
* RouterSlot related events.
|
||||
*/
|
||||
export type RouterSlotEvent = "changestate";
|
||||
export type RouterSlotEvent = 'changestate';
|
||||
|
||||
/**
|
||||
* History related events.
|
||||
*/
|
||||
export type GlobalRouterEvent =
|
||||
|
||||
// An event triggered when a new state is added to the history.
|
||||
"pushstate"
|
||||
// An event triggered when a new state is added to the history.
|
||||
| 'pushstate'
|
||||
|
||||
// An event triggered when the current state is replaced in the history.
|
||||
| "replacestate"
|
||||
| 'replacestate'
|
||||
|
||||
// An event triggered when a state in the history is popped from the history.
|
||||
| "popstate"
|
||||
| 'popstate'
|
||||
|
||||
// An event triggered when the state changes (eg. pop, push and replace)
|
||||
| "changestate"
|
||||
| 'changestate'
|
||||
|
||||
// A cancellable event triggered before the history state changes.
|
||||
| "willchangestate"
|
||||
| 'willchangestate'
|
||||
|
||||
// An event triggered when navigation starts.
|
||||
| "navigationstart"
|
||||
| 'navigationstart'
|
||||
|
||||
// An event triggered when navigation is canceled. This is due to a route guard returning false during navigation.
|
||||
| "navigationcancel"
|
||||
| 'navigationcancel'
|
||||
|
||||
// An event triggered when navigation fails due to an unexpected error.
|
||||
| "navigationerror"
|
||||
| 'navigationerror'
|
||||
|
||||
// An event triggered when navigation successfully completes.
|
||||
| "navigationsuccess"
|
||||
| 'navigationsuccess'
|
||||
|
||||
// An event triggered when navigation ends.
|
||||
| "navigationend";
|
||||
| 'navigationend';
|
||||
|
||||
export interface ISlashOptions {
|
||||
start: boolean;
|
||||
@@ -164,15 +170,15 @@ export interface ISlashOptions {
|
||||
/* Extend the global event handlers map with the router related events */
|
||||
declare global {
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"pushstate": PushStateEvent,
|
||||
"replacestate": ReplaceStateEvent,
|
||||
"popstate": PopStateEvent,
|
||||
"changestate": ChangeStateEvent,
|
||||
"navigationstart": NavigationStartEvent,
|
||||
"navigationend": NavigationEndEvent,
|
||||
"navigationsuccess": NavigationSuccessEvent,
|
||||
"navigationcancel": NavigationCancelEvent,
|
||||
"navigationerror": NavigationErrorEvent,
|
||||
"willchangestate": WillChangeStateEvent
|
||||
pushstate: PushStateEvent;
|
||||
replacestate: ReplaceStateEvent;
|
||||
popstate: PopStateEvent;
|
||||
changestate: ChangeStateEvent;
|
||||
navigationstart: NavigationStartEvent;
|
||||
navigationend: NavigationEndEvent;
|
||||
navigationsuccess: NavigationSuccessEvent;
|
||||
navigationcancel: NavigationCancelEvent;
|
||||
navigationerror: NavigationErrorEvent;
|
||||
willchangestate: WillChangeStateEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET, ROUTER_SLOT_TAG_NAME } from "./config.js";
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET, ROUTER_SLOT_TAG_NAME } from './config.js';
|
||||
import {
|
||||
Cancel,
|
||||
EventListenerSubscription,
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Params,
|
||||
PathFragment,
|
||||
RouterSlotEvent,
|
||||
} from "./model.js";
|
||||
} from './model.js';
|
||||
import {
|
||||
addListener,
|
||||
constructAbsolutePath,
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
removeListeners,
|
||||
resolvePageComponent,
|
||||
shouldNavigate,
|
||||
} from "./util.js";
|
||||
} from './util.js';
|
||||
|
||||
const template = document.createElement("template");
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `<slot></slot>`;
|
||||
|
||||
// Patches the history object and ensures the correct events.
|
||||
@@ -44,10 +44,7 @@ ensureAnchorHistory();
|
||||
* @slot - Default content.
|
||||
* @event changestate - Dispatched when the router slot state changes.
|
||||
*/
|
||||
export class RouterSlot<D = any, P = any>
|
||||
extends HTMLElement
|
||||
implements IRouterSlot<D, P>
|
||||
{
|
||||
export class RouterSlot<D = any, P = any> extends HTMLElement implements IRouterSlot<D, P> {
|
||||
/**
|
||||
* Listeners on the router.
|
||||
*/
|
||||
@@ -131,7 +128,7 @@ export class RouterSlot<D = any, P = any>
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addEventListener("router-slot:capture-parent", (e: any) => {
|
||||
this.addEventListener('router-slot:capture-parent', (e: any) => {
|
||||
e.stopPropagation();
|
||||
e.detail.parent = this;
|
||||
});
|
||||
@@ -139,7 +136,7 @@ export class RouterSlot<D = any, P = any>
|
||||
this.render = this.render.bind(this);
|
||||
|
||||
// Attach the template
|
||||
const shadow = this.attachShadow({ mode: "open" });
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
shadow.appendChild(template.content.cloneNode(true));
|
||||
}
|
||||
|
||||
@@ -149,7 +146,7 @@ export class RouterSlot<D = any, P = any>
|
||||
connectedCallback() {
|
||||
// Do not query a parent if the parent has been set from the outside.
|
||||
if (!this._lockParent) {
|
||||
const captureParentEvent = new CustomEvent("router-slot:capture-parent", {
|
||||
const captureParentEvent = new CustomEvent('router-slot:capture-parent', {
|
||||
composed: true,
|
||||
bubbles: true,
|
||||
detail: { parent: null },
|
||||
@@ -237,9 +234,7 @@ export class RouterSlot<D = any, P = any>
|
||||
// Either choose the parent fragment or the current path if no parent exists.
|
||||
// The root router slot will always use the entire path.
|
||||
const pathFragment =
|
||||
this.parent != null && this.parent.fragments != null
|
||||
? this.parent.fragments.rest
|
||||
: pathWithoutBasePath();
|
||||
this.parent != null && this.parent.fragments != null ? this.parent.fragments.rest : pathWithoutBasePath();
|
||||
|
||||
// Route to the path
|
||||
await this.renderPath(pathFragment);
|
||||
@@ -253,17 +248,9 @@ export class RouterSlot<D = any, P = any>
|
||||
this.listeners.push(
|
||||
this.parent != null
|
||||
? // Attach child router listeners
|
||||
addListener<Event, RouterSlotEvent>(
|
||||
this.parent,
|
||||
"changestate",
|
||||
this.render
|
||||
)
|
||||
addListener<Event, RouterSlotEvent>(this.parent, 'changestate', this.render)
|
||||
: // Add global listeners.
|
||||
addListener<Event, GlobalRouterEvent>(
|
||||
GLOBAL_ROUTER_EVENTS_TARGET,
|
||||
"changestate",
|
||||
this.render
|
||||
)
|
||||
addListener<Event, GlobalRouterEvent>(GLOBAL_ROUTER_EVENTS_TARGET, 'changestate', this.render),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -328,12 +315,14 @@ export class RouterSlot<D = any, P = any>
|
||||
// while we are about to navigate we have to cancel.
|
||||
let navigationInvalidated = false;
|
||||
const cancelNavigation = () => (navigationInvalidated = true);
|
||||
const removeChangeListener: EventListenerSubscription = addListener<
|
||||
Event,
|
||||
GlobalRouterEvent
|
||||
>(GLOBAL_ROUTER_EVENTS_TARGET, "changestate", cancelNavigation, {
|
||||
once: true,
|
||||
});
|
||||
const removeChangeListener: EventListenerSubscription = addListener<Event, GlobalRouterEvent>(
|
||||
GLOBAL_ROUTER_EVENTS_TARGET,
|
||||
'changestate',
|
||||
cancelNavigation,
|
||||
{
|
||||
once: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Cleans up the routing by removing listeners and restoring the match from before
|
||||
const cleanup = () => {
|
||||
@@ -343,13 +332,13 @@ export class RouterSlot<D = any, P = any>
|
||||
// Cleans up and dispatches a global event that a navigation was cancelled.
|
||||
const cancel: Cancel = () => {
|
||||
cleanup();
|
||||
dispatchGlobalRouterEvent("navigationcancel", info);
|
||||
dispatchGlobalRouterEvent("navigationend", info);
|
||||
dispatchGlobalRouterEvent('navigationcancel', info);
|
||||
dispatchGlobalRouterEvent('navigationend', info);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Dispatch globally that a navigation has started
|
||||
dispatchGlobalRouterEvent("navigationstart", info);
|
||||
dispatchGlobalRouterEvent('navigationstart', info);
|
||||
|
||||
// Check whether the guards allow us to go to the new route.
|
||||
if (route.guards != null) {
|
||||
@@ -395,7 +384,7 @@ export class RouterSlot<D = any, P = any>
|
||||
// We do this to ensure that we can find the match in the connectedCallback of the page.
|
||||
this._routeMatch = match;
|
||||
|
||||
if(page) {
|
||||
if (page) {
|
||||
// Append the new page
|
||||
this.appendChild(page);
|
||||
}
|
||||
@@ -413,14 +402,14 @@ export class RouterSlot<D = any, P = any>
|
||||
|
||||
// Dispatch globally that a navigation has ended.
|
||||
if (navigate) {
|
||||
dispatchGlobalRouterEvent("navigationsuccess", info);
|
||||
dispatchGlobalRouterEvent("navigationend", info);
|
||||
dispatchGlobalRouterEvent('navigationsuccess', info);
|
||||
dispatchGlobalRouterEvent('navigationend', info);
|
||||
}
|
||||
|
||||
return navigate;
|
||||
} catch (e) {
|
||||
dispatchGlobalRouterEvent("navigationerror", info);
|
||||
dispatchGlobalRouterEvent("navigationend", info);
|
||||
dispatchGlobalRouterEvent('navigationerror', info);
|
||||
dispatchGlobalRouterEvent('navigationend', info);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -430,6 +419,6 @@ window.customElements.define(ROUTER_SLOT_TAG_NAME, RouterSlot);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"router-slot": RouterSlot;
|
||||
'router-slot': RouterSlot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
* Hook up a click listener to the window that, for all anchor tags
|
||||
* that has a relative HREF, uses the history API instead.
|
||||
*/
|
||||
export function ensureAnchorHistory () {
|
||||
window.addEventListener("click", (e: MouseEvent) => {
|
||||
|
||||
export function ensureAnchorHistory() {
|
||||
window.addEventListener('click', (e: MouseEvent) => {
|
||||
// Find the target by using the composed path to get the element through the shadow boundaries.
|
||||
const $anchor = ("composedPath" in e as any) ? e.composedPath().find($elem => $elem instanceof HTMLAnchorElement) : e.target;
|
||||
const $anchor = (('composedPath' in e) as any)
|
||||
? e.composedPath().find(($elem) => $elem instanceof HTMLAnchorElement)
|
||||
: e.target;
|
||||
|
||||
// Abort if the event is not about the anchor tag
|
||||
if ($anchor == null || !($anchor instanceof HTMLAnchorElement)) {
|
||||
@@ -20,9 +21,11 @@ export function ensureAnchorHistory () {
|
||||
// - The HREF is relative to the origin of the current location.
|
||||
// - The target is targeting the current frame.
|
||||
// - The anchor doesn't have the attribute [data-router-slot]="disabled"
|
||||
if (!href.startsWith(location.origin) ||
|
||||
($anchor.target !== "" && $anchor.target !== "_self") ||
|
||||
$anchor.dataset["routerSlot"] === "disabled") {
|
||||
if (
|
||||
!href.startsWith(location.origin) ||
|
||||
($anchor.target !== '' && $anchor.target !== '_self') ||
|
||||
$anchor.dataset['routerSlot'] === 'disabled'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,6 +36,6 @@ export function ensureAnchorHistory () {
|
||||
e.preventDefault();
|
||||
|
||||
// Change the history!
|
||||
history.pushState(null, "", path);
|
||||
history.pushState(null, '', path);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET } from "../config";
|
||||
import { EventListenerSubscription, GlobalRouterEvent, IRoute, IRoutingInfo } from "../model";
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET } from '../config';
|
||||
import { EventListenerSubscription, GlobalRouterEvent, IRoute, IRoutingInfo } from '../model';
|
||||
|
||||
/**
|
||||
* Dispatches a did change route event.
|
||||
* @param $elem
|
||||
* @param {IRoute} detail
|
||||
*/
|
||||
export function dispatchRouteChangeEvent<D = any> ($elem: HTMLElement, detail: IRoutingInfo<D>) {
|
||||
$elem.dispatchEvent(new CustomEvent("changestate", {detail}));
|
||||
export function dispatchRouteChangeEvent<D = any>($elem: HTMLElement, detail: IRoutingInfo<D>) {
|
||||
$elem.dispatchEvent(new CustomEvent('changestate', { detail }));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -15,8 +15,8 @@ export function dispatchRouteChangeEvent<D = any> ($elem: HTMLElement, detail: I
|
||||
* @param name
|
||||
* @param detail
|
||||
*/
|
||||
export function dispatchGlobalRouterEvent<D = any> (name: GlobalRouterEvent, detail?: IRoutingInfo<D>) {
|
||||
GLOBAL_ROUTER_EVENTS_TARGET.dispatchEvent(new CustomEvent(name, {detail}));
|
||||
export function dispatchGlobalRouterEvent<D = any>(name: GlobalRouterEvent, detail?: IRoutingInfo<D>) {
|
||||
GLOBAL_ROUTER_EVENTS_TARGET.dispatchEvent(new CustomEvent(name, { detail }));
|
||||
// if ("debugRouterSlot" in window) {
|
||||
// console.log(`%c [router-slot]: ${name}`, `color: #286ee0`, detail);
|
||||
// }
|
||||
@@ -29,21 +29,22 @@ export function dispatchGlobalRouterEvent<D = any> (name: GlobalRouterEvent, det
|
||||
* @param listener
|
||||
* @param options
|
||||
*/
|
||||
export function addListener<T extends Event, eventType extends string> ($elem: EventTarget,
|
||||
type: eventType[] | eventType,
|
||||
listener: ((e: T) => void),
|
||||
options?: boolean | AddEventListenerOptions): EventListenerSubscription {
|
||||
export function addListener<T extends Event, eventType extends string>(
|
||||
$elem: EventTarget,
|
||||
type: eventType[] | eventType,
|
||||
listener: (e: T) => void,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): EventListenerSubscription {
|
||||
const types = Array.isArray(type) ? type : [type];
|
||||
types.forEach(t => $elem.addEventListener(t, listener as EventListenerOrEventListenerObject, options));
|
||||
return () => types.forEach(
|
||||
t => $elem.removeEventListener(t, listener as EventListenerOrEventListenerObject, options));
|
||||
types.forEach((t) => $elem.addEventListener(t, listener as EventListenerOrEventListenerObject, options));
|
||||
return () =>
|
||||
types.forEach((t) => $elem.removeEventListener(t, listener as EventListenerOrEventListenerObject, options));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the event listeners in the array.
|
||||
* @param listeners
|
||||
*/
|
||||
export function removeListeners (listeners: EventListenerSubscription[]) {
|
||||
listeners.forEach(unsub => unsub());
|
||||
export function removeListeners(listeners: EventListenerSubscription[]) {
|
||||
listeners.forEach((unsub) => unsub());
|
||||
}
|
||||
|
||||
@@ -1,47 +1,44 @@
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET, HISTORY_PATCH_NATIVE_KEY } from "../config";
|
||||
import { GlobalRouterEvent } from "../model";
|
||||
import { dispatchGlobalRouterEvent } from "./events";
|
||||
import { GLOBAL_ROUTER_EVENTS_TARGET, HISTORY_PATCH_NATIVE_KEY } from '../config';
|
||||
import { GlobalRouterEvent } from '../model';
|
||||
import { dispatchGlobalRouterEvent } from './events';
|
||||
|
||||
// Mapping a history functions to the events they are going to dispatch.
|
||||
export const historyPatches: [string, GlobalRouterEvent[]][] = [
|
||||
["pushState", ["pushstate", "changestate"]],
|
||||
["replaceState", ["replacestate", "changestate"]],
|
||||
["forward", ["pushstate", "changestate"]],
|
||||
["go", ["pushstate", "changestate"]],
|
||||
['pushState', ['pushstate', 'changestate']],
|
||||
['replaceState', ['replacestate', 'changestate']],
|
||||
['forward', ['pushstate', 'changestate']],
|
||||
['go', ['pushstate', 'changestate']],
|
||||
|
||||
// We need to handle the popstate a little differently when it comes to the change state event.
|
||||
["back", ["popstate"]],
|
||||
// We need to handle the popstate a little differently when it comes to the change state event.
|
||||
['back', ['popstate']],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Patches the history object by ensuring correct events are dispatches when the history changes.
|
||||
*/
|
||||
export function ensureHistoryEvents() {
|
||||
for (const [name, events] of historyPatches) {
|
||||
for (const event of events) {
|
||||
attachCallback(history, name, event);
|
||||
}
|
||||
}
|
||||
for (const [name, events] of historyPatches) {
|
||||
for (const event of events) {
|
||||
attachCallback(history, name, event);
|
||||
}
|
||||
}
|
||||
|
||||
// The popstate is the only event natively dispatched when using the hardware buttons.
|
||||
// Therefore we need to handle this case a little different. To ensure the changestate event
|
||||
// is fired also when the hardware back button is used, we make sure to listen for the popstate
|
||||
// event and dispatch a change state event right after. The reason for the setTimeout is because we
|
||||
// want the popstate event to bubble up before the changestate event is dispatched.
|
||||
window.addEventListener("popstate", (e: PopStateEvent) => {
|
||||
// The popstate is the only event natively dispatched when using the hardware buttons.
|
||||
// Therefore we need to handle this case a little different. To ensure the changestate event
|
||||
// is fired also when the hardware back button is used, we make sure to listen for the popstate
|
||||
// event and dispatch a change state event right after. The reason for the setTimeout is because we
|
||||
// want the popstate event to bubble up before the changestate event is dispatched.
|
||||
window.addEventListener('popstate', (e: PopStateEvent) => {
|
||||
// Check if the state should be allowed to change
|
||||
if (shouldCancelChangeState({ eventName: 'popstate' })) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the state should be allowed to change
|
||||
if (shouldCancelChangeState({eventName: "popstate"})) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispatch the global router event to change the routes after the popstate has bubbled up
|
||||
setTimeout(() => dispatchGlobalRouterEvent("changestate"), 0);
|
||||
}
|
||||
);
|
||||
// Dispatch the global router event to change the routes after the popstate has bubbled up
|
||||
setTimeout(() => dispatchGlobalRouterEvent('changestate'), 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,20 +49,19 @@ export function ensureHistoryEvents() {
|
||||
* @param eventName
|
||||
*/
|
||||
export function attachCallback(obj: any, functionName: string, eventName: GlobalRouterEvent) {
|
||||
const func = obj[functionName];
|
||||
saveNativeFunction(obj, functionName, func);
|
||||
obj[functionName] = (...args: any[]) => {
|
||||
const func = obj[functionName];
|
||||
saveNativeFunction(obj, functionName, func);
|
||||
obj[functionName] = (...args: any[]) => {
|
||||
// If its push/replace state we want to send the url to the should cancel change state event
|
||||
const url = args.length > 2 ? args[2] : null;
|
||||
|
||||
// If its push/replace state we want to send the url to the should cancel change state event
|
||||
const url = args.length > 2 ? args[2] : null;
|
||||
// Check if the state should be allowed to change
|
||||
if (shouldCancelChangeState({ url, eventName })) return;
|
||||
|
||||
// Check if the state should be allowed to change
|
||||
if (shouldCancelChangeState({url, eventName})) return;
|
||||
|
||||
// Navigate
|
||||
func.apply(obj, args);
|
||||
dispatchGlobalRouterEvent(eventName)
|
||||
};
|
||||
// Navigate
|
||||
func.apply(obj, args);
|
||||
dispatchGlobalRouterEvent(eventName);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,37 +70,38 @@ export function attachCallback(obj: any, functionName: string, eventName: Global
|
||||
* @param name
|
||||
* @param func
|
||||
*/
|
||||
export function saveNativeFunction(obj: any, name: string, func: (() => void)) {
|
||||
export function saveNativeFunction(obj: any, name: string, func: () => void) {
|
||||
// Ensure that the native object exists.
|
||||
if (obj[HISTORY_PATCH_NATIVE_KEY] == null) {
|
||||
obj[HISTORY_PATCH_NATIVE_KEY] = {};
|
||||
}
|
||||
|
||||
// Ensure that the native object exists.
|
||||
if (obj[HISTORY_PATCH_NATIVE_KEY] == null) {
|
||||
obj[HISTORY_PATCH_NATIVE_KEY] = {};
|
||||
}
|
||||
|
||||
// Save the native function.
|
||||
obj[HISTORY_PATCH_NATIVE_KEY][`${name}`] = func.bind(obj);
|
||||
// Save the native function.
|
||||
obj[HISTORY_PATCH_NATIVE_KEY][`${name}`] = func.bind(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches and event and returns whether the state change should be cancelled.
|
||||
* The state will be considered as cancelled if the "willChangeState" event was cancelled.
|
||||
*/
|
||||
function shouldCancelChangeState(data: { url?: string | null, eventName: GlobalRouterEvent }): boolean {
|
||||
return !GLOBAL_ROUTER_EVENTS_TARGET.dispatchEvent(new CustomEvent("willchangestate", {
|
||||
cancelable: true,
|
||||
detail: data
|
||||
}));
|
||||
function shouldCancelChangeState(data: { url?: string | null; eventName: GlobalRouterEvent }): boolean {
|
||||
return !GLOBAL_ROUTER_EVENTS_TARGET.dispatchEvent(
|
||||
new CustomEvent('willchangestate', {
|
||||
cancelable: true,
|
||||
detail: data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Expose the native history functions.
|
||||
declare global {
|
||||
interface History {
|
||||
"native": {
|
||||
"back": ((distance?: any) => void);
|
||||
"forward": ((distance?: any) => void);
|
||||
"go": ((delta?: any) => void);
|
||||
"pushState": ((data: any, title?: string, url?: string | null) => void);
|
||||
"replaceState": ((data: any, title?: string, url?: string | null) => void);
|
||||
}
|
||||
}
|
||||
}
|
||||
interface History {
|
||||
native: {
|
||||
back: (distance?: any) => void;
|
||||
forward: (distance?: any) => void;
|
||||
go: (delta?: any) => void;
|
||||
pushState: (data: any, title?: string, url?: string | null) => void;
|
||||
replaceState: (data: any, title?: string, url?: string | null) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
import { CATCH_ALL_WILDCARD, DEFAULT_PATH_MATCH, PARAM_IDENTIFIER, TRAVERSE_FLAG } from "../config";
|
||||
import { IComponentRoute, IRedirectRoute, IResolverRoute, IRoute, IRouteMatch, IRouterSlot, ModuleResolver, PageComponent, Params, PathFragment, RouterTree, IRoutingInfo } from "../model";
|
||||
import { constructPathWithBasePath, path as getPath, queryString, stripSlash } from "./url";
|
||||
import { CATCH_ALL_WILDCARD, DEFAULT_PATH_MATCH, PARAM_IDENTIFIER, TRAVERSE_FLAG } from '../config';
|
||||
import {
|
||||
IComponentRoute,
|
||||
IRedirectRoute,
|
||||
IResolverRoute,
|
||||
IRoute,
|
||||
IRouteMatch,
|
||||
IRouterSlot,
|
||||
ModuleResolver,
|
||||
PageComponent,
|
||||
Params,
|
||||
PathFragment,
|
||||
RouterTree,
|
||||
IRoutingInfo,
|
||||
} from '../model';
|
||||
import { constructPathWithBasePath, path as getPath, queryString, stripSlash } from './url';
|
||||
|
||||
/**
|
||||
* Determines whether the path is active.
|
||||
@@ -8,8 +21,8 @@ import { constructPathWithBasePath, path as getPath, queryString, stripSlash } f
|
||||
* @param path
|
||||
* @param fullPath
|
||||
*/
|
||||
export function isPathActive (path: string | PathFragment, fullPath: string = getPath()): boolean {
|
||||
return new RegExp(`^${stripSlash(path)}(\/|$)`, "gm").test(stripSlash(fullPath));
|
||||
export function isPathActive(path: string | PathFragment, fullPath: string = getPath()): boolean {
|
||||
return new RegExp(`^${stripSlash(path)}(\/|$)`, 'gm').test(stripSlash(fullPath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,16 +30,17 @@ export function isPathActive (path: string | PathFragment, fullPath: string = ge
|
||||
* @param route
|
||||
* @param path
|
||||
*/
|
||||
export function matchRoute<D = any> (route: IRoute<D>, path: string | PathFragment): IRouteMatch<D> | null {
|
||||
|
||||
export function matchRoute<D = any>(route: IRoute<D>, path: string | PathFragment): IRouteMatch<D> | null {
|
||||
// We start by preparing the route path by replacing the param names with a regex that matches everything
|
||||
// until either the end of the path or the next "/". While replacing the param placeholders we make sure
|
||||
// to store the names of the param placeholders.
|
||||
const paramNames: string[] = [];
|
||||
const routePath = stripSlash(route.path.replace(PARAM_IDENTIFIER, (substring: string, ...args: string[]) => {
|
||||
paramNames.push(args[0]);
|
||||
return `([^\/]+)`;
|
||||
}));
|
||||
const routePath = stripSlash(
|
||||
route.path.replace(PARAM_IDENTIFIER, (substring: string, ...args: string[]) => {
|
||||
paramNames.push(args[0]);
|
||||
return `([^\/]+)`;
|
||||
}),
|
||||
);
|
||||
|
||||
// Construct the regex to match with the path or fragment
|
||||
// If path is wildcard:
|
||||
@@ -45,19 +59,26 @@ export function matchRoute<D = any> (route: IRoute<D>, path: string | PathFragme
|
||||
// - We start the match with .*? to allow anything to be in front of what we are trying to match.
|
||||
// - We end the match with .*? to allow anything to be after what we are trying to match.
|
||||
// All matches starts with ^ to make sure the match is done from the beginning of the path.
|
||||
const regex = route.path === CATCH_ALL_WILDCARD || (route.path.length === 0 && route.pathMatch != "full" ) ? /^/ : (() => {
|
||||
switch (route.pathMatch || DEFAULT_PATH_MATCH) {
|
||||
case "full": return new RegExp(`^${routePath}\/?$`);
|
||||
case "suffix": return new RegExp(`^.*?${routePath}\/?$`);
|
||||
case "fuzzy": return new RegExp(`^.*?${routePath}.*?$`);
|
||||
case "prefix": default: return new RegExp(`^[\/]?${routePath}(?:\/|$)`);
|
||||
}
|
||||
})();
|
||||
const regex =
|
||||
route.path === CATCH_ALL_WILDCARD || (route.path.length === 0 && route.pathMatch != 'full')
|
||||
? /^/
|
||||
: (() => {
|
||||
switch (route.pathMatch || DEFAULT_PATH_MATCH) {
|
||||
case 'full':
|
||||
return new RegExp(`^${routePath}\/?$`);
|
||||
case 'suffix':
|
||||
return new RegExp(`^.*?${routePath}\/?$`);
|
||||
case 'fuzzy':
|
||||
return new RegExp(`^.*?${routePath}.*?$`);
|
||||
case 'prefix':
|
||||
default:
|
||||
return new RegExp(`^[\/]?${routePath}(?:\/|$)`);
|
||||
}
|
||||
})();
|
||||
|
||||
// Check if there's a match
|
||||
const match = path.match(regex);
|
||||
if (match != null) {
|
||||
|
||||
// Match the param names with the matches. The matches starts from index 1 which is the
|
||||
// reason why we add 1. match[0] is the entire string.
|
||||
const params = paramNames.reduce((acc: Params, name: string, i: number) => {
|
||||
@@ -75,12 +96,11 @@ export function matchRoute<D = any> (route: IRoute<D>, path: string | PathFragme
|
||||
params,
|
||||
fragments: {
|
||||
consumed,
|
||||
rest
|
||||
}
|
||||
rest,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -89,7 +109,7 @@ export function matchRoute<D = any> (route: IRoute<D>, path: string | PathFragme
|
||||
* @param routes
|
||||
* @param path
|
||||
*/
|
||||
export function matchRoutes<D = any> (routes: IRoute<D>[], path: string | PathFragment): IRouteMatch<D> | null {
|
||||
export function matchRoutes<D = any>(routes: IRoute<D>[], path: string | PathFragment): IRouteMatch<D> | null {
|
||||
for (const route of routes) {
|
||||
const match = matchRoute(route, path);
|
||||
if (match != null) {
|
||||
@@ -106,15 +126,13 @@ export function matchRoutes<D = any> (routes: IRoute<D>[], path: string | PathFr
|
||||
* @param route
|
||||
* @param info
|
||||
*/
|
||||
export async function resolvePageComponent (route: IComponentRoute, info: IRoutingInfo): Promise<PageComponent> {
|
||||
|
||||
export async function resolvePageComponent(route: IComponentRoute, info: IRoutingInfo): Promise<PageComponent> {
|
||||
// Figure out if the component were given as an import or class.
|
||||
let cmp = route.component;
|
||||
if (cmp instanceof Function) {
|
||||
try {
|
||||
cmp = (cmp as Function)();
|
||||
} catch (err) {
|
||||
|
||||
// The invocation most likely failed because the function is a class.
|
||||
// If it failed due to the "new" keyword not being used, the error will be of type "TypeError".
|
||||
// This is the most reliable way to check whether the provided function is a class or a function.
|
||||
@@ -147,24 +165,23 @@ export async function resolvePageComponent (route: IComponentRoute, info: IRouti
|
||||
* Determines if a route is a redirect route.
|
||||
* @param route
|
||||
*/
|
||||
export function isRedirectRoute (route: IRoute): route is IRedirectRoute {
|
||||
return "redirectTo" in route;
|
||||
export function isRedirectRoute(route: IRoute): route is IRedirectRoute {
|
||||
return 'redirectTo' in route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a route is a resolver route.
|
||||
* @param route
|
||||
*/
|
||||
export function isResolverRoute (route: IRoute): route is IResolverRoute {
|
||||
return "resolve" in route;
|
||||
export function isResolverRoute(route: IRoute): route is IResolverRoute {
|
||||
return 'resolve' in route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the router tree up to the root route.
|
||||
* @param slot
|
||||
*/
|
||||
export function traverseRouterTree (slot: IRouterSlot): {tree: RouterTree, depth: number} {
|
||||
|
||||
export function traverseRouterTree(slot: IRouterSlot): { tree: RouterTree; depth: number } {
|
||||
// Find the nodes from the route up to the root route
|
||||
let routes: IRouterSlot[] = [slot];
|
||||
while (slot.parent != null) {
|
||||
@@ -174,12 +191,12 @@ export function traverseRouterTree (slot: IRouterSlot): {tree: RouterTree, depth
|
||||
|
||||
// Create the tree
|
||||
const tree: RouterTree = routes.reduce((acc: RouterTree, slot: IRouterSlot) => {
|
||||
return {slot, child: acc};
|
||||
return { slot, child: acc };
|
||||
}, undefined);
|
||||
|
||||
const depth = routes.length;
|
||||
|
||||
return {tree, depth};
|
||||
return { tree, depth };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +204,7 @@ export function traverseRouterTree (slot: IRouterSlot): {tree: RouterTree, depth
|
||||
* @param tree
|
||||
* @param depth
|
||||
*/
|
||||
export function getFragments (tree: RouterTree, depth: number): PathFragment[] {
|
||||
export function getFragments(tree: RouterTree, depth: number): PathFragment[] {
|
||||
let child = tree;
|
||||
const fragments: PathFragment[] = [];
|
||||
|
||||
@@ -209,27 +226,27 @@ export function getFragments (tree: RouterTree, depth: number): PathFragment[] {
|
||||
* @param slot
|
||||
* @param path
|
||||
*/
|
||||
export function constructAbsolutePath<D = any, P = any> (slot: IRouterSlot<D, P>,
|
||||
path: string | PathFragment = ""): string {
|
||||
|
||||
export function constructAbsolutePath<D = any, P = any>(
|
||||
slot: IRouterSlot<D, P>,
|
||||
path: string | PathFragment = '',
|
||||
): string {
|
||||
// Grab the router tree
|
||||
const {tree, depth} = traverseRouterTree(slot);
|
||||
const { tree, depth } = traverseRouterTree(slot);
|
||||
|
||||
// If the path starts with "/" we treat it as an absolute path
|
||||
// and therefore don't continue because it is already absolute.
|
||||
if (!path.startsWith("/")) {
|
||||
if (!path.startsWith('/')) {
|
||||
let traverseDepth = 0;
|
||||
|
||||
// If the path starts with "./" we can remove that part
|
||||
// because we know the path is relative to its route.
|
||||
if (path.startsWith("./")) {
|
||||
if (path.startsWith('./')) {
|
||||
path = path.slice(2);
|
||||
}
|
||||
|
||||
// Match with the traverse flag.
|
||||
const match = path.match(new RegExp(TRAVERSE_FLAG, "g"));
|
||||
const match = path.match(new RegExp(TRAVERSE_FLAG, 'g'));
|
||||
if (match != null) {
|
||||
|
||||
// If the path matched with the traverse flag we know that we have to construct
|
||||
// a route until a certain depth. The traverse depth is the amount of "../" in the path
|
||||
// and the depth is the part of the path we a slicing away.
|
||||
@@ -243,12 +260,12 @@ export function constructAbsolutePath<D = any, P = any> (slot: IRouterSlot<D, P>
|
||||
// Grab the fragments and construct the new path, taking the traverse depth into account.
|
||||
// Always subtract at least 1 because we the path is relative to its parent.
|
||||
// Filter away the empty fragments from the path.
|
||||
const fragments = getFragments(tree, depth - 1 - traverseDepth).filter(fragment => fragment.length > 0);
|
||||
path = `${fragments.join("/")}${fragments.length > 0 ? "/" : ""}${path}`;
|
||||
const fragments = getFragments(tree, depth - 1 - traverseDepth).filter((fragment) => fragment.length > 0);
|
||||
path = `${fragments.join('/')}${fragments.length > 0 ? '/' : ''}${path}`;
|
||||
}
|
||||
|
||||
// Add the base path in front of the path. If the path is already absolute, the path wont get the base path added.
|
||||
return constructPathWithBasePath(path, {end: false});
|
||||
return constructPathWithBasePath(path, { end: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,8 +273,12 @@ export function constructAbsolutePath<D = any, P = any> (slot: IRouterSlot<D, P>
|
||||
* @param slot
|
||||
* @param route
|
||||
*/
|
||||
export function handleRedirect (slot: IRouterSlot, route: IRedirectRoute) {
|
||||
history.replaceState(history.state, "", `${constructAbsolutePath(slot, route.redirectTo)}${route.preserveQuery ? queryString() : ""}`);
|
||||
export function handleRedirect(slot: IRouterSlot, route: IRedirectRoute) {
|
||||
history.replaceState(
|
||||
history.state,
|
||||
'',
|
||||
`${constructAbsolutePath(slot, route.redirectTo)}${route.preserveQuery ? queryString() : ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,20 +286,19 @@ export function handleRedirect (slot: IRouterSlot, route: IRedirectRoute) {
|
||||
* @param currentMatch
|
||||
* @param newMatch
|
||||
*/
|
||||
export function shouldNavigate<D> (currentMatch: IRouteMatch<D> | null, newMatch: IRouteMatch<D>) {
|
||||
|
||||
export function shouldNavigate<D>(currentMatch: IRouteMatch<D> | null, newMatch: IRouteMatch<D>) {
|
||||
// If the current match is not defined we should always route.
|
||||
if (currentMatch == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract information about the matches
|
||||
const {route: currentRoute, fragments: currentFragments} = currentMatch;
|
||||
const {route: newRoute, fragments: newFragments} = newMatch;
|
||||
const { route: currentRoute, fragments: currentFragments } = currentMatch;
|
||||
const { route: newRoute, fragments: newFragments } = newMatch;
|
||||
|
||||
const isSameRoute = currentRoute == newRoute;
|
||||
const isSameFragments = currentFragments.consumed == newFragments.consumed;
|
||||
|
||||
// Only navigate if the URL consumption is new or if the two routes are no longer the same.
|
||||
return !isSameFragments || !isSameRoute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ROUTER_SLOT_TAG_NAME } from "../config";
|
||||
import { IRouterSlot } from "../model";
|
||||
import { ROUTER_SLOT_TAG_NAME } from '../config';
|
||||
import { IRouterSlot } from '../model';
|
||||
|
||||
/**
|
||||
* Queries the parent router.
|
||||
* @param $elem
|
||||
*/
|
||||
export function queryParentRouterSlot<D = any> ($elem: Element): IRouterSlot<D> | null {
|
||||
export function queryParentRouterSlot<D = any>($elem: Element): IRouterSlot<D> | null {
|
||||
return queryParentRoots<IRouterSlot<D>>($elem, ROUTER_SLOT_TAG_NAME);
|
||||
}
|
||||
|
||||
@@ -17,14 +17,12 @@ export function queryParentRouterSlot<D = any> ($elem: Element): IRouterSlot<D>
|
||||
* @param minRoots
|
||||
* @param roots
|
||||
*/
|
||||
export function queryParentRoots<T> ($elem: Element, query: string, minRoots: number = 0, roots: number = 0): T | null {
|
||||
|
||||
export function queryParentRoots<T>($elem: Element, query: string, minRoots: number = 0, roots: number = 0): T | null {
|
||||
// Grab the rood node and query it
|
||||
const $root = (<any>$elem).getRootNode();
|
||||
|
||||
// If we are at the right level or above we can query!
|
||||
if (roots >= minRoots) {
|
||||
|
||||
// See if there's a match
|
||||
const match = $root.querySelector(query);
|
||||
if (match != null && match != $elem) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ISlashOptions, Params, Query } from "../model";
|
||||
import { ISlashOptions, Params, Query } from '../model';
|
||||
|
||||
const $anchor = document.createElement("a");
|
||||
const $anchor = document.createElement('a');
|
||||
|
||||
/**
|
||||
* The current path of the location.
|
||||
* As default slashes are included at the start and end.
|
||||
* @param options
|
||||
*/
|
||||
export function path (options: Partial<ISlashOptions> = {}): string {
|
||||
export function path(options: Partial<ISlashOptions> = {}): string {
|
||||
return slashify(window.location.pathname, options);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export function path (options: Partial<ISlashOptions> = {}): string {
|
||||
* Returns the path without the base path.
|
||||
* @param options
|
||||
*/
|
||||
export function pathWithoutBasePath (options: Partial<ISlashOptions> = {}): string {
|
||||
export function pathWithoutBasePath(options: Partial<ISlashOptions> = {}): string {
|
||||
return slashify(stripStart(path(), basePath()), options);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ export function pathWithoutBasePath (options: Partial<ISlashOptions> = {}): stri
|
||||
* To make this method more performant we could cache the anchor element.
|
||||
* As default it will return the base path with slashes in front and at the end.
|
||||
*/
|
||||
export function basePath (options: Partial<ISlashOptions> = {}): string {
|
||||
return constructPathWithBasePath(".", options);
|
||||
export function basePath(options: Partial<ISlashOptions> = {}): string {
|
||||
return constructPathWithBasePath('.', options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ export function basePath (options: Partial<ISlashOptions> = {}): string {
|
||||
* @param path
|
||||
* @param options
|
||||
*/
|
||||
export function constructPathWithBasePath (path: string, options: Partial<ISlashOptions> = {}) {
|
||||
export function constructPathWithBasePath(path: string, options: Partial<ISlashOptions> = {}) {
|
||||
$anchor.href = path;
|
||||
return slashify($anchor.pathname, options);
|
||||
}
|
||||
@@ -54,14 +54,14 @@ export function constructPathWithBasePath (path: string, options: Partial<ISlash
|
||||
* @param path
|
||||
* @param part
|
||||
*/
|
||||
export function stripStart (path: string, part: string) {
|
||||
return path.replace(new RegExp(`^${part}`), "");
|
||||
export function stripStart(path: string, part: string) {
|
||||
return path.replace(new RegExp(`^${part}`), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query string.
|
||||
*/
|
||||
export function queryString (): string {
|
||||
export function queryString(): string {
|
||||
return window.location.search;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function queryString (): string {
|
||||
* Returns the params for the current path.
|
||||
* @returns Params
|
||||
*/
|
||||
export function query (): Query {
|
||||
export function query(): Query {
|
||||
return toQuery(queryString().substr(1));
|
||||
}
|
||||
|
||||
@@ -77,16 +77,16 @@ export function query (): Query {
|
||||
* Strips the slash from the start and end of a path.
|
||||
* @param path
|
||||
*/
|
||||
export function stripSlash (path: string): string {
|
||||
return slashify(path, {start: false, end: false});
|
||||
export function stripSlash(path: string): string {
|
||||
return slashify(path, { start: false, end: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the path starts and ends with a slash
|
||||
* @param path
|
||||
*/
|
||||
export function ensureSlash (path: string): string {
|
||||
return slashify(path, {start: true, end: true});
|
||||
export function ensureSlash(path: string): string {
|
||||
return slashify(path, { start: true, end: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +95,9 @@ export function ensureSlash (path: string): string {
|
||||
* @param start
|
||||
* @param end
|
||||
*/
|
||||
export 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,31 +105,33 @@ export function slashify (path: string, {start = true, end = true}: Partial<ISla
|
||||
* @param {string} queryString (example: ("test=123&hejsa=LOL&wuhuu"))
|
||||
* @returns {Query}
|
||||
*/
|
||||
export function toQuery (queryString: string): Query {
|
||||
|
||||
export function toQuery(queryString: string): Query {
|
||||
// If the query does not contain anything, return an empty object.
|
||||
if (queryString.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Grab the atoms (["test=123", "hejsa=LOL", "wuhuu"])
|
||||
const atoms = queryString.split("&");
|
||||
const atoms = queryString.split('&');
|
||||
|
||||
// Split by the values ([["test", "123"], ["hejsa", "LOL"], ["wuhuu"]])
|
||||
const arrayMap = atoms.map(atom => atom.split("="));
|
||||
const arrayMap = atoms.map((atom) => atom.split('='));
|
||||
|
||||
// Assign the values to an object ({ test: "123", hejsa: "LOL", wuhuu: "" })
|
||||
return Object.assign({}, ...arrayMap.map(arr => ({
|
||||
[decodeURIComponent(arr[0])]: (arr.length > 1 ? decodeURIComponent(arr[1]) : "")
|
||||
})));
|
||||
return Object.assign(
|
||||
{},
|
||||
...arrayMap.map((arr) => ({
|
||||
[decodeURIComponent(arr[0])]: arr.length > 1 ? decodeURIComponent(arr[1]) : '',
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a query object into a string query.
|
||||
* @param query
|
||||
*/
|
||||
export function toQueryString (query: Query): string {
|
||||
export function toQueryString(query: Query): string {
|
||||
return Object.entries(query)
|
||||
.map(([key, value]) => `${key}${value != "" ? `=${encodeURIComponent(value)}` : ""}`)
|
||||
.join("&");
|
||||
.map(([key, value]) => `${key}${value != '' ? `=${encodeURIComponent(value)}` : ''}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
@@ -3,19 +3,22 @@ import { UmbContextConsumer } from './context-consumer.js';
|
||||
import { UmbContextCallback } from './context-request.event.js';
|
||||
import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
|
||||
export class UmbContextConsumerController<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType
|
||||
> extends UmbContextConsumer<BaseType, ResultType> implements UmbController {
|
||||
export class UmbContextConsumerController<BaseType = unknown, ResultType extends BaseType = BaseType>
|
||||
extends UmbContextConsumer<BaseType, ResultType>
|
||||
implements UmbController
|
||||
{
|
||||
#controllerAlias = Symbol();
|
||||
#host: UmbControllerHost;
|
||||
|
||||
public get controllerAlias() {
|
||||
return this.#controllerAlias;
|
||||
}
|
||||
|
||||
constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken<BaseType, ResultType>, callback: UmbContextCallback<ResultType>) {
|
||||
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
) {
|
||||
super(host.getHostElement(), contextAlias, callback);
|
||||
this.#host = host;
|
||||
host.addController(this);
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('UmbContextProviderController', () => {
|
||||
expect(_instance?.prop).to.eq('value from provider');
|
||||
done();
|
||||
localConsumer.hostDisconnected();
|
||||
}
|
||||
},
|
||||
);
|
||||
localConsumer.hostConnected();
|
||||
});
|
||||
|
||||
@@ -3,18 +3,25 @@ import { UmbContextProvider } from './context-provider.js';
|
||||
import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbContextProviderController<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType
|
||||
> extends UmbContextProvider<BaseType, ResultType> implements UmbController {
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType,
|
||||
>
|
||||
extends UmbContextProvider<BaseType, ResultType>
|
||||
implements UmbController
|
||||
{
|
||||
#host: UmbControllerHost;
|
||||
#controllerAlias:string;
|
||||
#controllerAlias: string;
|
||||
|
||||
public get controllerAlias() {
|
||||
return this.#controllerAlias;
|
||||
}
|
||||
|
||||
constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken<BaseType, ResultType>, instance: InstanceType) {
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
instance: InstanceType,
|
||||
) {
|
||||
super(host.getHostElement(), contextAlias, instance);
|
||||
this.#host = host;
|
||||
// Makes the controllerAlias unique for this instance, this enables multiple Contexts to be provided under the same name. (This only makes sense cause of Context Token Discriminators)
|
||||
@@ -30,7 +37,9 @@ export class UmbContextProviderController<
|
||||
// This just an additional awareness feature to make devs Aware, the alternative would be adding it anyway, but that would destroy existing controller of this alias.
|
||||
// Back out, this instance is already provided, by another controller.
|
||||
throw new Error(
|
||||
`Context API: The context of '${this.controllerAlias}' and instance '${(instance as any).constructor?.name ?? 'unnamed'}' is already provided by another Context Provider Controller.`
|
||||
`Context API: The context of '${this.controllerAlias}' and instance '${
|
||||
(instance as any).constructor?.name ?? 'unnamed'
|
||||
}' is already provided by another Context Provider Controller.`,
|
||||
);
|
||||
} else {
|
||||
host.addController(this);
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('UmbContextProvider', () => {
|
||||
element = await fixture(
|
||||
html` <umb-context-provider key="test-context" .value=${contextValue}>
|
||||
<umb-test-context></umb-test-context>
|
||||
</umb-context-provider>`
|
||||
</umb-context-provider>`,
|
||||
);
|
||||
consumer = element.getElementsByTagName('umb-test-context')[0] as unknown as UmbTestContextElement;
|
||||
});
|
||||
|
||||
@@ -22,16 +22,19 @@ export declare class UmbElement extends UmbControllerHostElement {
|
||||
observe<T>(
|
||||
source: Observable<T> | { asObservable: () => Observable<T> },
|
||||
callback: ObserverCallback<T>,
|
||||
unique?: string
|
||||
unique?: string,
|
||||
): UmbObserverController<T>;
|
||||
provideContext<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType
|
||||
>(alias: string | UmbContextToken<BaseType, ResultType>, instance: InstanceType): UmbContextProviderController<BaseType, ResultType, InstanceType>;
|
||||
InstanceType extends ResultType = ResultType,
|
||||
>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
instance: InstanceType,
|
||||
): UmbContextProviderController<BaseType, ResultType, InstanceType>;
|
||||
consumeContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType>;
|
||||
/**
|
||||
* Use the UmbLocalizeController to localize your element.
|
||||
@@ -65,9 +68,11 @@ export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T)
|
||||
provideContext<
|
||||
BaseType = unknown,
|
||||
ResultType extends BaseType = BaseType,
|
||||
InstanceType extends ResultType = ResultType
|
||||
>
|
||||
(alias: string | UmbContextToken<BaseType, ResultType>, instance: InstanceType): UmbContextProviderController<BaseType, ResultType, InstanceType> {
|
||||
InstanceType extends ResultType = ResultType,
|
||||
>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
instance: InstanceType,
|
||||
): UmbContextProviderController<BaseType, ResultType, InstanceType> {
|
||||
return new UmbContextProviderController(this, alias, instance);
|
||||
}
|
||||
|
||||
@@ -80,7 +85,7 @@ export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T)
|
||||
*/
|
||||
consumeContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType> {
|
||||
return new UmbContextConsumerController(this, alias, callback);
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ import { UmbConditionConfigBase } from '../types/index.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export type UmbConditionControllerArguments<
|
||||
ConditionConfigType extends UmbConditionConfigBase = UmbConditionConfigBase
|
||||
ConditionConfigType extends UmbConditionConfigBase = UmbConditionConfigBase,
|
||||
> = { host: UmbControllerHost; config: ConditionConfigType; onChange: () => void };
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('UmbExtensionElementController', () => {
|
||||
extensionController.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ describe('UmbExtensionElementController', () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
'umb-test-fallback-element'
|
||||
'umb-test-fallback-element',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -143,7 +143,7 @@ describe('UmbExtensionElementController', () => {
|
||||
done();
|
||||
extensionController.destroy(); // need to destroy the controller.
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,21 +8,21 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
* When the extension is permitted to be used, the manifest is available for the consumer.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const controller = new UmbExtensionManifestController(host, extensionRegistry, alias, (permitted, ctrl) => { console.log("Extension is permitted and this is the manifest: ", ctrl.manifest) }));
|
||||
* ```
|
||||
* ```ts
|
||||
* const controller = new UmbExtensionManifestController(host, extensionRegistry, alias, (permitted, ctrl) => { console.log("Extension is permitted and this is the manifest: ", ctrl.manifest) }));
|
||||
* ```
|
||||
* @export
|
||||
* @class UmbExtensionManifestController
|
||||
*/
|
||||
export class UmbExtensionManifestInitializer<
|
||||
ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions,
|
||||
ControllerType extends UmbBaseExtensionInitializer<ManifestType, any> = any
|
||||
ControllerType extends UmbBaseExtensionInitializer<ManifestType, any> = any,
|
||||
> extends UmbBaseExtensionInitializer<ManifestType, ControllerType> {
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
extensionRegistry: UmbExtensionRegistry<ManifestCondition>,
|
||||
alias: string,
|
||||
onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void
|
||||
onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void,
|
||||
) {
|
||||
super(host, extensionRegistry, 'extManifest_', alias, onPermissionChanged);
|
||||
this._init();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { ManifestTypeMap, SpecificManifestTypeOrManifestBase } from '../types/map.types.js';
|
||||
import { type PermittedControllerType, UmbBaseExtensionsInitializer } from './base-extensions-initializer.controller.js';
|
||||
import { UmbExtensionApiInitializer } from './extension-api-initializer.controller.js';
|
||||
import {
|
||||
type UmbExtensionRegistry,
|
||||
ManifestApi,
|
||||
ManifestBase,
|
||||
} from '@umbraco-cms/backoffice/extension-api';
|
||||
type PermittedControllerType,
|
||||
UmbBaseExtensionsInitializer,
|
||||
} from './base-extensions-initializer.controller.js';
|
||||
import { UmbExtensionApiInitializer } from './extension-api-initializer.controller.js';
|
||||
import { type UmbExtensionRegistry, ManifestApi, ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,7 @@ export class UmbExtensionsApiInitializer<
|
||||
ManifestType extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, ManifestTypeName>,
|
||||
ManifestTypeAsApi extends ManifestApi = ManifestType extends ManifestApi ? ManifestType : never,
|
||||
ControllerType extends UmbExtensionApiInitializer<ManifestTypeAsApi> = UmbExtensionApiInitializer<ManifestTypeAsApi>,
|
||||
MyPermittedControllerType extends ControllerType = PermittedControllerType<ControllerType>
|
||||
MyPermittedControllerType extends ControllerType = PermittedControllerType<ControllerType>,
|
||||
> extends UmbBaseExtensionsInitializer<
|
||||
ManifestTypes,
|
||||
ManifestTypeName,
|
||||
@@ -59,7 +58,7 @@ export class UmbExtensionsApiInitializer<
|
||||
type: ManifestTypeName | Array<ManifestTypeName>,
|
||||
constructorArguments: Array<unknown> | undefined,
|
||||
filter?: undefined | null | ((manifest: ManifestTypeAsApi) => boolean),
|
||||
onChange?: (permittedManifests: Array<MyPermittedControllerType>) => void
|
||||
onChange?: (permittedManifests: Array<MyPermittedControllerType>) => void,
|
||||
) {
|
||||
super(host, extensionRegistry, type, filter, onChange);
|
||||
this.#extensionRegistry = extensionRegistry;
|
||||
@@ -73,7 +72,7 @@ export class UmbExtensionsApiInitializer<
|
||||
this.#extensionRegistry,
|
||||
manifest.alias,
|
||||
this.#constructorArgs,
|
||||
this._extensionChanged
|
||||
this._extensionChanged,
|
||||
) as ControllerType;
|
||||
|
||||
return extController;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { ManifestTypeMap, SpecificManifestTypeOrManifestBase } from '../types/map.types.js';
|
||||
import { UmbExtensionManifestInitializer } from './extension-manifest-initializer.controller.js';
|
||||
import { type PermittedControllerType, UmbBaseExtensionsInitializer } from './base-extensions-initializer.controller.js';
|
||||
import {
|
||||
type ManifestBase,
|
||||
type UmbExtensionRegistry,
|
||||
} from '@umbraco-cms/backoffice/extension-api';
|
||||
type PermittedControllerType,
|
||||
UmbBaseExtensionsInitializer,
|
||||
} from './base-extensions-initializer.controller.js';
|
||||
import { type ManifestBase, type UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
/**
|
||||
@@ -14,7 +14,7 @@ export class UmbExtensionsManifestInitializer<
|
||||
ManifestTypeName extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
ManifestType extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, ManifestTypeName>,
|
||||
ControllerType extends UmbExtensionManifestInitializer<ManifestType> = UmbExtensionManifestInitializer<ManifestType>,
|
||||
MyPermittedControllerType extends ControllerType = PermittedControllerType<ControllerType>
|
||||
MyPermittedControllerType extends ControllerType = PermittedControllerType<ControllerType>,
|
||||
> extends UmbBaseExtensionsInitializer<
|
||||
ManifestTypes,
|
||||
ManifestTypeName,
|
||||
@@ -30,7 +30,7 @@ export class UmbExtensionsManifestInitializer<
|
||||
extensionRegistry: UmbExtensionRegistry<ManifestTypes>,
|
||||
type: ManifestTypeName | Array<ManifestTypeName>,
|
||||
filter: null | ((manifest: ManifestType) => boolean),
|
||||
onChange: (permittedManifests: Array<MyPermittedControllerType>) => void
|
||||
onChange: (permittedManifests: Array<MyPermittedControllerType>) => void,
|
||||
) {
|
||||
super(host, extensionRegistry, type, filter, onChange);
|
||||
this.#extensionRegistry = extensionRegistry;
|
||||
@@ -42,7 +42,7 @@ export class UmbExtensionsManifestInitializer<
|
||||
this,
|
||||
this.#extensionRegistry,
|
||||
manifest.alias,
|
||||
this._extensionChanged
|
||||
this._extensionChanged,
|
||||
) as ControllerType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
import { UmbApi } from "../models/api.interface.js";
|
||||
import { ManifestApi, ManifestElementAndApi } from "../types/base.types.js";
|
||||
import { loadManifestApi } from "./load-manifest-api.function.js";
|
||||
import { UmbApi } from '../models/api.interface.js';
|
||||
import { ManifestApi, ManifestElementAndApi } from '../types/base.types.js';
|
||||
import { loadManifestApi } from './load-manifest-api.function.js';
|
||||
|
||||
export async function createExtensionApi<ApiType extends UmbApi = UmbApi>(manifest: ManifestApi<ApiType> | ManifestElementAndApi<any, ApiType>, constructorArguments: Array<unknown> = []): Promise<ApiType | undefined> {
|
||||
|
||||
if(manifest.api) {
|
||||
export async function createExtensionApi<ApiType extends UmbApi = UmbApi>(
|
||||
manifest: ManifestApi<ApiType> | ManifestElementAndApi<any, ApiType>,
|
||||
constructorArguments: Array<unknown> = [],
|
||||
): Promise<ApiType | undefined> {
|
||||
if (manifest.api) {
|
||||
const apiConstructor = await loadManifestApi<ApiType>(manifest.api);
|
||||
if(apiConstructor) {
|
||||
if (apiConstructor) {
|
||||
return new apiConstructor(...constructorArguments);
|
||||
} else {
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed instantiate a API class via the extension manifest property 'api', using either a 'api' or 'default' export`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(manifest.js) {
|
||||
if (manifest.js) {
|
||||
const apiConstructor2 = await loadManifestApi<ApiType>(manifest.js);
|
||||
if(apiConstructor2) {
|
||||
if (apiConstructor2) {
|
||||
return new apiConstructor2(...constructorArguments);
|
||||
} else {
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed instantiate a API class via the extension manifest property 'js', using either a 'api' or 'default' export`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed creating an api class instance, missing a JavaScript file via the 'api' or 'js' property.`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -3,9 +3,6 @@ import { ManifestApi } from '../types/index.js';
|
||||
import { UmbApi } from '../models/api.interface.js';
|
||||
import { createExtensionApi } from './create-extension-api.function.js';
|
||||
|
||||
|
||||
|
||||
|
||||
class UmbExtensionApiTrueTestClass implements UmbApi {
|
||||
isValidClassInstance() {
|
||||
return true;
|
||||
@@ -20,33 +17,25 @@ class UmbExtensionApiFalseTestClass implements UmbApi {
|
||||
destroy() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const jsModuleWithDefaultExport = {
|
||||
default: UmbExtensionApiTrueTestClass
|
||||
default: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
const jsModuleWithApiExport = {
|
||||
api: UmbExtensionApiTrueTestClass
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
const jsModuleWithDefaultAndApiExport = {
|
||||
default: UmbExtensionApiFalseTestClass,
|
||||
api: UmbExtensionApiTrueTestClass
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
|
||||
|
||||
describe('Extension-Api: Create Extension Api', () => {
|
||||
|
||||
|
||||
|
||||
it('Returns `undefined` when manifest does not have any correct properties', async () => {
|
||||
|
||||
const manifest: ManifestApi = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name'
|
||||
name: 'pretty name',
|
||||
};
|
||||
|
||||
const api = await createExtensionApi(manifest, []);
|
||||
@@ -54,70 +43,65 @@ describe('Extension-Api: Create Extension Api', () => {
|
||||
});
|
||||
|
||||
it('Handles when `api` property contains a class constructor', async () => {
|
||||
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
api: UmbExtensionApiTrueTestClass
|
||||
api: UmbExtensionApiTrueTestClass,
|
||||
};
|
||||
|
||||
const api = await createExtensionApi(manifest, []);
|
||||
expect(api).to.not.be.undefined;
|
||||
if(api) {
|
||||
if (api) {
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a default export', async () => {
|
||||
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultExport)
|
||||
js: () => Promise.resolve(jsModuleWithDefaultExport),
|
||||
};
|
||||
|
||||
const api = await createExtensionApi(manifest, []);
|
||||
expect(api).to.not.be.undefined;
|
||||
if(api) {
|
||||
if (api) {
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a api export', async () => {
|
||||
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithApiExport)
|
||||
js: () => Promise.resolve(jsModuleWithApiExport),
|
||||
};
|
||||
|
||||
const api = await createExtensionApi(manifest, []);
|
||||
expect(api).to.not.be.undefined;
|
||||
if(api) {
|
||||
if (api) {
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Prioritizes api export from loader property', async () => {
|
||||
|
||||
const manifest: ManifestApi<UmbExtensionApiTrueTestClass> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.createManifestApi',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultAndApiExport)
|
||||
js: () => Promise.resolve(jsModuleWithDefaultAndApiExport),
|
||||
};
|
||||
|
||||
const api = await createExtensionApi(manifest, []);
|
||||
expect(api).to.not.be.undefined;
|
||||
if(api) {
|
||||
if (api) {
|
||||
expect(api.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
//TODO: Test apiJs
|
||||
//TODO: Test js
|
||||
|
||||
});
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
import { ManifestElement, ManifestElementAndApi } from "../types/base.types.js";
|
||||
import { loadManifestElement } from "./load-manifest-element.function.js";
|
||||
import { ManifestElement, ManifestElementAndApi } from '../types/base.types.js';
|
||||
import { loadManifestElement } from './load-manifest-element.function.js';
|
||||
|
||||
export async function createExtensionElement<ElementType extends HTMLElement>(manifest: ManifestElement<ElementType> | ManifestElementAndApi<ElementType>, fallbackElement?: string): Promise<ElementType | undefined> {
|
||||
|
||||
if(manifest.element) {
|
||||
export async function createExtensionElement<ElementType extends HTMLElement>(
|
||||
manifest: ManifestElement<ElementType> | ManifestElementAndApi<ElementType>,
|
||||
fallbackElement?: string,
|
||||
): Promise<ElementType | undefined> {
|
||||
if (manifest.element) {
|
||||
const elementConstructor = await loadManifestElement<ElementType>(manifest.element);
|
||||
if(elementConstructor) {
|
||||
if (elementConstructor) {
|
||||
return new elementConstructor();
|
||||
} else {
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property 'element', using either a 'element' or 'default' export`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(manifest.js) {
|
||||
if (manifest.js) {
|
||||
const elementConstructor2 = await loadManifestElement<ElementType>(manifest.js);
|
||||
if(elementConstructor2) {
|
||||
if (elementConstructor2) {
|
||||
return new elementConstructor2();
|
||||
} else {
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property 'js', using either a 'element' or 'default' export`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(manifest.elementName) {
|
||||
if (manifest.elementName) {
|
||||
return document.createElement(manifest.elementName) as ElementType;
|
||||
}
|
||||
|
||||
if(fallbackElement) {
|
||||
if (fallbackElement) {
|
||||
return document.createElement(fallbackElement) as ElementType;
|
||||
}
|
||||
|
||||
console.error(
|
||||
`-- Extension of alias "${manifest.alias}" did not succeed creating an element, missing a JavaScript file via the 'element' or 'js' property or a Element Name in 'elementName' in the manifest.`,
|
||||
manifest
|
||||
manifest,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { ManifestElement, ManifestElementAndApi } from '../types/index.js';
|
||||
import { createExtensionElement } from './create-extension-element.function.js';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
|
||||
|
||||
@customElement('umb-extension-api-true-test-element')
|
||||
class UmbExtensionApiTrueTestElement extends HTMLElement {
|
||||
isValidClassInstance() {
|
||||
@@ -19,32 +17,25 @@ class UmbExtensionApiFalseTestElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const jsModuleWithDefaultExport = {
|
||||
default: UmbExtensionApiTrueTestElement
|
||||
default: UmbExtensionApiTrueTestElement,
|
||||
};
|
||||
|
||||
const jsModuleWithElementExport = {
|
||||
element: UmbExtensionApiTrueTestElement
|
||||
element: UmbExtensionApiTrueTestElement,
|
||||
};
|
||||
|
||||
const jsModuleWithDefaultAndElementExport = {
|
||||
default: UmbExtensionApiFalseTestElement,
|
||||
element: UmbExtensionApiTrueTestElement
|
||||
element: UmbExtensionApiTrueTestElement,
|
||||
};
|
||||
|
||||
|
||||
|
||||
describe('Extension-Api: Create Extension Element', () => {
|
||||
|
||||
|
||||
it('Returns `undefined` when manifest does not have any correct properties', async () => {
|
||||
|
||||
const manifest: ManifestElement = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name'
|
||||
name: 'pretty name',
|
||||
};
|
||||
|
||||
const api = await createExtensionElement(manifest);
|
||||
@@ -52,102 +43,94 @@ describe('Extension-Api: Create Extension Element', () => {
|
||||
});
|
||||
|
||||
it('Returns fallback element instance when manifest does not provide element', async () => {
|
||||
|
||||
const manifest: ManifestElement<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name'
|
||||
name: 'pretty name',
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest, 'umb-extension-api-true-test-element');
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('Still returns fallback element instance when manifest does not provide element and manifest has api', async () => {
|
||||
|
||||
const manifest: ManifestElementAndApi<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name',
|
||||
api: class TestApi {}
|
||||
api: class TestApi {},
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest, 'umb-extension-api-true-test-element');
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles when `api` property contains a class constructor', async () => {
|
||||
|
||||
const manifest: ManifestElement<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name',
|
||||
elementName: 'umb-extension-api-true-test-element'
|
||||
elementName: 'umb-extension-api-true-test-element',
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest);
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a default export', async () => {
|
||||
|
||||
const manifest: ManifestElement<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultExport)
|
||||
js: () => Promise.resolve(jsModuleWithDefaultExport),
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest);
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles when `loader` has a element export', async () => {
|
||||
|
||||
const manifest: ManifestElement<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithElementExport)
|
||||
js: () => Promise.resolve(jsModuleWithElementExport),
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest);
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
it('Prioritizes api export from loader property', async () => {
|
||||
|
||||
const manifest: ManifestElement<UmbExtensionApiTrueTestElement> = {
|
||||
type: 'my-test-type',
|
||||
alias: 'Umb.Test.CreateManifestElement',
|
||||
name: 'pretty name',
|
||||
js: () => Promise.resolve(jsModuleWithDefaultAndElementExport)
|
||||
js: () => Promise.resolve(jsModuleWithDefaultAndElementExport),
|
||||
};
|
||||
|
||||
const element = await createExtensionElement(manifest);
|
||||
expect(element).to.not.be.undefined;
|
||||
if(element) {
|
||||
if (element) {
|
||||
expect(element.isValidClassInstance()).to.be.true;
|
||||
}
|
||||
});
|
||||
|
||||
//TODO: Test elementJs
|
||||
//TODO: Test js
|
||||
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export * from './create-extension-api.function.js';
|
||||
export * from './create-extension-element.function.js';
|
||||
export * from './has-init-export.function.js';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface UmbApi {
|
||||
destroy?(): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './entry-point.interface.js'
|
||||
export * from './api.interface.js'
|
||||
export * from './entry-point.interface.js';
|
||||
export * from './api.interface.js';
|
||||
|
||||
@@ -2,4 +2,4 @@ export * from './has-api-export.function.js';
|
||||
export * from './has-default-export.function.js';
|
||||
export * from './has-element-export.function.js';
|
||||
export * from './is-manifest-base-type.function.js';
|
||||
export * from './is-manifest-element-name-type.function.js';
|
||||
export * from './is-manifest-element-name-type.function.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ManifestBase } from "../types/manifest-base.interface.js";
|
||||
import type { ManifestBase } from '../types/manifest-base.interface.js';
|
||||
|
||||
export function isManifestBaseType(x: unknown): x is ManifestBase {
|
||||
return typeof x === 'object' && x !== null && 'alias' in x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ManifestBase } from "./manifest-base.interface.js";
|
||||
import type { ManifestBase } from './manifest-base.interface.js';
|
||||
|
||||
export interface UmbConditionConfigBase<AliasType extends string = string> {
|
||||
alias: AliasType;
|
||||
@@ -12,7 +12,7 @@ export type ConditionTypeMap<ConditionTypes extends UmbConditionConfigBase> = {
|
||||
|
||||
export type SpecificConditionTypeOrUmbConditionConfigBase<
|
||||
ConditionTypes extends UmbConditionConfigBase,
|
||||
T extends keyof ConditionTypeMap<ConditionTypes> | string
|
||||
T extends keyof ConditionTypeMap<ConditionTypes> | string,
|
||||
> = T extends keyof ConditionTypeMap<ConditionTypes> ? ConditionTypeMap<ConditionTypes>[T] : UmbConditionConfigBase;
|
||||
|
||||
export interface ManifestWithDynamicConditions<ConditionTypes extends UmbConditionConfigBase = UmbConditionConfigBase>
|
||||
@@ -25,4 +25,4 @@ export interface ManifestWithDynamicConditions<ConditionTypes extends UmbConditi
|
||||
* Define one or more extension aliases that this extension should overwrite.
|
||||
*/
|
||||
overwrites?: string | Array<string>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ export * from './manifest-bundle.interface.js';
|
||||
export * from './manifest-condition.interface.js';
|
||||
export * from './manifest-entrypoint.interface.js';
|
||||
export * from './manifest-kind.interface.js';
|
||||
export * from './utils.js';
|
||||
export * from './utils.js';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export interface ManifestBase {
|
||||
/**
|
||||
* The type of extension such as dashboard etc...
|
||||
@@ -26,4 +25,4 @@ export interface ManifestBase {
|
||||
* Extensions such as dashboards are ordered by weight with lower numbers being first in the list
|
||||
*/
|
||||
weight?: number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ManifestPlainJs } from "./base.types.js";
|
||||
import type { ManifestBase } from "./manifest-base.interface.js";
|
||||
import type { ManifestPlainJs } from './base.types.js';
|
||||
import type { ManifestBase } from './manifest-base.interface.js';
|
||||
|
||||
/**
|
||||
* This type of extension takes a JS module and registers all exported manifests from the pointed JS file.
|
||||
@@ -8,4 +8,3 @@ export interface ManifestBundle<UmbManifestTypes extends ManifestBase = Manifest
|
||||
extends ManifestPlainJs<{ [key: string]: Array<UmbManifestTypes> }> {
|
||||
type: 'bundle';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbExtensionCondition } from "../condition/index.js";
|
||||
import type { ManifestApi } from "./base.types.js";
|
||||
import type { UmbExtensionCondition } from '../condition/index.js';
|
||||
import type { ManifestApi } from './base.types.js';
|
||||
|
||||
/**
|
||||
* This type of extension takes a JS module and registers all exported manifests from the pointed JS file.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbEntryPointModule } from "../models/index.js";
|
||||
import type { ManifestPlainJs } from "./base.types.js";
|
||||
import type { UmbEntryPointModule } from '../models/index.js';
|
||||
import type { ManifestPlainJs } from './base.types.js';
|
||||
|
||||
/**
|
||||
* This type of extension gives full control and will simply load the specified JS file
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ManifestBase } from "./manifest-base.interface.js";
|
||||
import type { ManifestBase } from './manifest-base.interface.js';
|
||||
|
||||
export type ManifestTypeMap<ManifestTypes extends ManifestBase> = {
|
||||
[Manifest in ManifestTypes as Manifest['type']]: Manifest;
|
||||
@@ -8,5 +8,5 @@ export type ManifestTypeMap<ManifestTypes extends ManifestBase> = {
|
||||
|
||||
export type SpecificManifestTypeOrManifestBase<
|
||||
ManifestTypes extends ManifestBase,
|
||||
T extends keyof ManifestTypeMap<ManifestTypes> | string
|
||||
T extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
> = T extends keyof ManifestTypeMap<ManifestTypes> ? ManifestTypeMap<ManifestTypes>[T] : ManifestBase;
|
||||
|
||||
@@ -6,14 +6,12 @@ import { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
* @description - State ensures the data is unique, not updating any Observes unless there is an actual change of the value using `===`.
|
||||
*/
|
||||
export class UmbBasicState<T> {
|
||||
|
||||
protected _subject:BehaviorSubject<T>;
|
||||
protected _subject: BehaviorSubject<T>;
|
||||
|
||||
constructor(initialData: T) {
|
||||
this._subject = new BehaviorSubject(initialData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @method asObservable
|
||||
* @return {Observable} Observable that the State casts to.
|
||||
@@ -22,7 +20,7 @@ export class UmbBasicState<T> {
|
||||
* const myState = new UmbArrayState('Hello world');
|
||||
*
|
||||
* this.observe(myState, (latestStateValue) => console.log("Value is: ", latestStateValue));
|
||||
*/
|
||||
*/
|
||||
public asObservable(): ReturnType<BehaviorSubject<T>['asObservable']> {
|
||||
return this._subject.asObservable();
|
||||
}
|
||||
@@ -33,10 +31,10 @@ export class UmbBasicState<T> {
|
||||
* @example <caption>Example retrieve the current data of a state</caption>
|
||||
* const myState = new UmbArrayState('Hello world');
|
||||
* console.log("Value is: ", myState.getValue());
|
||||
*/
|
||||
*/
|
||||
public get value(): BehaviorSubject<T>['value'] {
|
||||
return this._subject.value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @method getValue
|
||||
@@ -45,7 +43,7 @@ export class UmbBasicState<T> {
|
||||
* @example <caption>Example retrieve the current data of a state</caption>
|
||||
* const myState = new UmbArrayState('Hello world');
|
||||
* console.log("Value is: ", myState.value);
|
||||
*/
|
||||
*/
|
||||
public getValue(): ReturnType<BehaviorSubject<T>['getValue']> {
|
||||
return this._subject.getValue();
|
||||
}
|
||||
@@ -53,7 +51,7 @@ export class UmbBasicState<T> {
|
||||
/**
|
||||
* @method destroy
|
||||
* @description - Destroys this state and completes all observations made to it.
|
||||
*/
|
||||
*/
|
||||
public destroy(): void {
|
||||
this._subject.complete();
|
||||
}
|
||||
@@ -67,7 +65,7 @@ export class UmbBasicState<T> {
|
||||
* // myState.value is equal 'Good morning'.
|
||||
* myState.next('Goodnight')
|
||||
* // myState.value is equal 'Goodnight'.
|
||||
*/
|
||||
*/
|
||||
next(newData: T): void {
|
||||
if (newData !== this._subject.getValue()) {
|
||||
this._subject.next(newData);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbBasicState } from "./basic-state.js";
|
||||
import { UmbBasicState } from './basic-state.js';
|
||||
|
||||
export interface UmbClassStateData {
|
||||
equal(otherClass: this | undefined): boolean;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
export function partialUpdateFrozenArray<T>(
|
||||
data: T[],
|
||||
partialEntry: Partial<T>,
|
||||
findMethod: (entry: T) => boolean
|
||||
findMethod: (entry: T) => boolean,
|
||||
): T[] {
|
||||
const unFrozenDataSet = [...data];
|
||||
const indexToReplace = unFrozenDataSet.findIndex((x) => findMethod(x));
|
||||
|
||||
@@ -695,7 +695,6 @@ class UmbDocumentData extends UmbEntityData<DocumentResponseModel> {
|
||||
}
|
||||
|
||||
publish(id: string, data: PublishDocumentRequestModel) {
|
||||
|
||||
// Update detail data:
|
||||
const foundIndex = this.data.findIndex((item) => item.id === id);
|
||||
if (foundIndex !== -1) {
|
||||
@@ -719,7 +718,6 @@ class UmbDocumentData extends UmbEntityData<DocumentResponseModel> {
|
||||
}
|
||||
|
||||
unpublish(id: string, data: PublishDocumentRequestModel) {
|
||||
|
||||
// Update detail data:
|
||||
const foundIndex = this.data.findIndex((item) => item.id === id);
|
||||
if (foundIndex !== -1) {
|
||||
|
||||
@@ -375,4 +375,4 @@ class UmbPartialViewsData extends UmbEntityData<PartialViewResponseModel> {
|
||||
}
|
||||
|
||||
export const umbPartialViewSnippetsData = new UmbPartialViewSnippetsData();
|
||||
export const umbPartialViewsData = new UmbPartialViewsData();
|
||||
export const umbPartialViewsData = new UmbPartialViewsData();
|
||||
|
||||
@@ -104,7 +104,7 @@ export const arrayFilter = (filterBy: Array<string>, value?: Array<string>): boo
|
||||
}
|
||||
|
||||
return filterBy.some((filterValue: string) => value?.includes(filterValue));
|
||||
}
|
||||
};
|
||||
|
||||
export const stringFilter = (filterBy: Array<string>, value?: string): boolean => {
|
||||
// if a filter is not set, return all items
|
||||
|
||||
@@ -7,7 +7,7 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<ServertimeOffset>({ offset: -120 })
|
||||
ctx.json<ServertimeOffset>({ offset: -120 }),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PagedIndexResponseModel>(PagedIndexers)
|
||||
ctx.json<PagedIndexResponseModel>(PagedIndexers),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -51,7 +51,7 @@ export const handlers = [
|
||||
ctx.json<PagedSearcherResponseModel>({
|
||||
total: 0,
|
||||
items: [{ name: 'ExternalSearcher' }, { name: 'InternalSearcher' }, { name: 'InternalMemberSearcher' }],
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -69,7 +69,7 @@ export const handlers = [
|
||||
ctx.json<PagedSearchResultResponseModel>({
|
||||
total: 0,
|
||||
items: searchResultMockData,
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return res(ctx.status(404));
|
||||
|
||||
@@ -22,7 +22,7 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PagedHealthCheckGroupResponseModel>({ total: 9999, items: healthGroupsWithoutResult })
|
||||
ctx.json<PagedHealthCheckGroupResponseModel>({ total: 9999, items: healthGroupsWithoutResult }),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -74,7 +74,7 @@ export const handlers = [
|
||||
// Respond with a 200 status code
|
||||
ctx.delay(1000),
|
||||
ctx.status(200),
|
||||
ctx.json<HealthCheckResultResponseModel>(result)
|
||||
ctx.json<HealthCheckResultResponseModel>(result),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -74,7 +74,7 @@ export const handlers = [
|
||||
requiresConnectionTest: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -88,13 +88,13 @@ export const handlers = [
|
||||
type: 'connection',
|
||||
status: 400,
|
||||
detail: 'Database connection failed',
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(201)
|
||||
ctx.status(201),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -113,12 +113,12 @@ export const handlers = [
|
||||
errors: {
|
||||
name: ['Database name is invalid'],
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(201)
|
||||
ctx.status(201),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -55,7 +55,7 @@ export const handlers = [
|
||||
errors: {
|
||||
isoCode: ['Language with same iso code already exists'],
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -75,7 +75,7 @@ export const manifestDevelopmentHandler = rest.get(umbracoPath('/package/manifes
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -83,6 +83,6 @@ export const manifestEmptyHandler = rest.get(umbracoPath('/package/manifest'), (
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<PackageManifestResponse>([])
|
||||
ctx.json<PackageManifestResponse>([]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json({})
|
||||
ctx.json({}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -26,7 +26,7 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<OutOfDateStatusResponseModel>({})
|
||||
ctx.json<OutOfDateStatusResponseModel>({}),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -30,7 +30,7 @@ export const handlers = [
|
||||
packageName: 'Package with a view',
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -47,7 +47,7 @@ export const handlers = [
|
||||
ctx.json<PagedPackageDefinitionResponseModel>({
|
||||
total: packageArray.length,
|
||||
items: packageArray,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ export const handlers = [
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<ProfilingStatusResponseModel>({ enabled: true })
|
||||
ctx.json<ProfilingStatusResponseModel>({ enabled: true }),
|
||||
);
|
||||
}),
|
||||
|
||||
rest.put(umbracoPath('/profiling/status'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
ctx.status(200),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -8,8 +8,8 @@ export const handlers = [
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200),
|
||||
ctx.json<string>(
|
||||
'Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot.'
|
||||
)
|
||||
'Database cache is ok. ContentStore contains 1 item and has 1 generation and 0 snapshot. MediaStore contains 5 items and has 1 generation and 0 snapshot.',
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -18,7 +18,7 @@ export const handlers = [
|
||||
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
ctx.status(200),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -27,14 +27,14 @@ export const handlers = [
|
||||
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
ctx.status(200),
|
||||
);
|
||||
}),
|
||||
|
||||
rest.post(umbracoPath('/published-cache/collect'), (_req, res, ctx) => {
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
ctx.status(200),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -12,7 +12,7 @@ export const serverRunningHandler = rest.get(umbracoPath('/server/status'), (_re
|
||||
ctx.status(200),
|
||||
ctx.json<ServerStatusResponseModel>({
|
||||
serverStatus: RuntimeLevelModel.RUN,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ export const serverMustInstallHandler = rest.get(umbracoPath('/server/status'),
|
||||
ctx.status(200),
|
||||
ctx.json<ServerStatusResponseModel>({
|
||||
serverStatus: RuntimeLevelModel.INSTALL,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ export const serverMustUpgradeHandler = rest.get(umbracoPath('/server/status'),
|
||||
ctx.status(200),
|
||||
ctx.json<ServerStatusResponseModel>({
|
||||
serverStatus: RuntimeLevelModel.UPGRADE,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -42,6 +42,6 @@ export const serverVersionHandler = rest.get(umbracoPath('/server/version'), (_r
|
||||
ctx.status(200),
|
||||
ctx.json<VersionResponseModel>({
|
||||
version: '13.0.0',
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ export const handlers = [
|
||||
ctx.status(200),
|
||||
ctx.json<TelemetryResponseModel>({
|
||||
telemetryLevel,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -31,7 +31,7 @@ export const handlers = [
|
||||
{ telemetryLevel: TelemetryLevelModel.BASIC },
|
||||
{ telemetryLevel: TelemetryLevelModel.DETAILED },
|
||||
],
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -41,7 +41,7 @@ export const handlers = [
|
||||
telemetryLevel = newLevel;
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(200)
|
||||
ctx.status(200),
|
||||
);
|
||||
} else {
|
||||
return res(ctx.status(400));
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handlers = [
|
||||
oldVersion: '13.0.0',
|
||||
newVersion: '13.1.0',
|
||||
reportUrl: 'https://our.umbraco.com/download/releases/1000',
|
||||
})
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -23,7 +23,7 @@ export const handlers = [
|
||||
|
||||
return res(
|
||||
// Respond with a 200 status code
|
||||
ctx.status(201)
|
||||
ctx.status(201),
|
||||
);
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, CSSResultGroup, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
UmbNotificationHandler,
|
||||
@@ -38,7 +38,7 @@ export class UmbBackofficeNotificationContainerElement extends UmbLitElement {
|
||||
? repeat(
|
||||
this._notifications,
|
||||
(notification: UmbNotificationHandler) => notification.key,
|
||||
(notification) => html`${notification.element}`
|
||||
(notification) => html`${notification.element}`,
|
||||
)
|
||||
: ''}
|
||||
</uui-toast-notification-container>
|
||||
|
||||
@@ -13,9 +13,10 @@ export default meta;
|
||||
type Story = StoryObj<UmbBodyLayoutElement>;
|
||||
|
||||
export const Overview: Story = {
|
||||
render: () => html` <umb-body-layout>
|
||||
<div slot="header"><uui-button color="" look="placeholder">Header slot</uui-button></div>
|
||||
<uui-button color="" look="placeholder">Main slot</uui-button>
|
||||
<div slot="footer-info"><uui-button color="" look="placeholder">Footer slot</uui-button></div>
|
||||
</umb-body-layout>`,
|
||||
render: () =>
|
||||
html` <umb-body-layout>
|
||||
<div slot="header"><uui-button color="" look="placeholder">Header slot</uui-button></div>
|
||||
<uui-button color="" look="placeholder">Main slot</uui-button>
|
||||
<div slot="footer-info"><uui-button color="" look="placeholder">Footer slot</uui-button></div>
|
||||
</umb-body-layout>`,
|
||||
};
|
||||
|
||||
@@ -8,8 +8,9 @@ export default {
|
||||
id: 'umb-button-with-dropdown',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbButtonWithDropdownElement> = () => html` <umb-button-with-dropdown>
|
||||
Open me
|
||||
<div slot="dropdown" style="background: pink; height: 300px">I am a dropdown</div>
|
||||
</umb-button-with-dropdown>`;
|
||||
export const AAAOverview: Story<UmbButtonWithDropdownElement> = () =>
|
||||
html` <umb-button-with-dropdown>
|
||||
Open me
|
||||
<div slot="dropdown" style="background: pink; height: 300px">I am a dropdown</div>
|
||||
</umb-button-with-dropdown>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('umb-empty-state')
|
||||
|
||||
@@ -6,9 +6,10 @@ import type { UmbEmptyStateElement } from './empty-state.element.js';
|
||||
const meta: Meta<UmbEmptyStateElement> = {
|
||||
title: 'Components/Empty State',
|
||||
component: 'umb-empty-state',
|
||||
render: (args) => html` <umb-empty-state .position=${args.position} .size=${args.size}
|
||||
>There are no items to be found</umb-empty-state
|
||||
>`,
|
||||
render: (args) =>
|
||||
html` <umb-empty-state .position=${args.position} .size=${args.size}
|
||||
>There are no items to be found</umb-empty-state
|
||||
>`,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbSectionSidebarContext, UMB_SECTION_SIDEBAR_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/section';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
@@ -48,7 +48,7 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
|
||||
(actions) => {
|
||||
this._hasActions = actions.length > 0;
|
||||
},
|
||||
'umbEntityActionsObserver'
|
||||
'umbEntityActionsObserver',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
#props?: Record<string, unknown> = {};
|
||||
|
||||
@property({ type: String, attribute: 'default-element' })
|
||||
public defaultElement?:string;
|
||||
public defaultElement?: string;
|
||||
|
||||
@property()
|
||||
public renderMethod?: (extension: UmbExtensionElementInitializer) => TemplateResult | HTMLElement | null | undefined;
|
||||
@@ -114,7 +114,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
(extensionControllers) => {
|
||||
this._permittedExts = extensionControllers;
|
||||
},
|
||||
this.defaultElement
|
||||
this.defaultElement,
|
||||
);
|
||||
this.#extensionsController.properties = this.#props;
|
||||
}
|
||||
@@ -124,7 +124,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
|
||||
return repeat(
|
||||
this._permittedExts,
|
||||
(ext) => ext.alias,
|
||||
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component)
|
||||
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('UmbExtensionSlotElement', () => {
|
||||
element = await fixture(
|
||||
html`<umb-extension-slot
|
||||
type="dashboard"
|
||||
.filter=${(x: ManifestDashboard) => x.alias === 'unit-test-ext-slot-element-manifest'}></umb-extension-slot>`
|
||||
.filter=${(x: ManifestDashboard) => x.alias === 'unit-test-ext-slot-element-manifest'}></umb-extension-slot>`,
|
||||
);
|
||||
|
||||
await sleep(0);
|
||||
@@ -90,14 +90,14 @@ describe('UmbExtensionSlotElement', () => {
|
||||
type="dashboard"
|
||||
.filter=${(x: ManifestDashboard) => x.alias === 'unit-test-ext-slot-element-manifest'}
|
||||
.renderMethod=${(controller: UmbExtensionElementInitializer) => html`<bla>${controller.component}</bla>`}>
|
||||
</umb-extension-slot>`
|
||||
</umb-extension-slot>`,
|
||||
);
|
||||
|
||||
await sleep(0);
|
||||
|
||||
expect(element.shadowRoot!.firstElementChild?.nodeName).to.be.equal('BLA');
|
||||
expect(element.shadowRoot!.firstElementChild?.firstElementChild).to.be.instanceOf(
|
||||
UmbTestExtensionSlotManifestElement
|
||||
UmbTestExtensionSlotManifestElement,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('UmbExtensionSlotElement', () => {
|
||||
type="dashboard"
|
||||
.filter=${(x: ManifestDashboard) => x.alias === 'unit-test-ext-slot-element-manifest'}
|
||||
.props=${{ testProp: 'fooBar' }}>
|
||||
</umb-extension-slot>`
|
||||
</umb-extension-slot>`,
|
||||
);
|
||||
|
||||
await sleep(0);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
/**
|
||||
* @element umb-footer-layout
|
||||
|
||||
@@ -10,10 +10,11 @@ export default {
|
||||
id: 'umb-footer-layout',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbFooterLayoutElement> = () => html` <umb-body-layout>
|
||||
<div slot="footer-info">
|
||||
<uui-button color="" look="placeholder">Footer slot</uui-button
|
||||
><uui-button color="" look="placeholder">Actions slot</uui-button>
|
||||
</div>
|
||||
</umb-body-layout>`;
|
||||
export const AAAOverview: Story<UmbFooterLayoutElement> = () =>
|
||||
html` <umb-body-layout>
|
||||
<div slot="footer-info">
|
||||
<uui-button color="" look="placeholder">Footer slot</uui-button
|
||||
><uui-button color="" look="placeholder">Actions slot</uui-button>
|
||||
</div>
|
||||
</umb-body-layout>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, CSSResultGroup, html, LitElement, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
ManifestHeaderAppButtonKind,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-history-item')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-history-list')
|
||||
|
||||
@@ -13,25 +13,25 @@ export default {
|
||||
id: 'umb-history-list',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbHistoryListElement> = () => html` <umb-history-list>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
</umb-history-list>`;
|
||||
export const AAAOverview: Story<UmbHistoryListElement> = () =>
|
||||
html` <umb-history-list>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>
|
||||
</umb-history-list>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
|
||||
export const Node: Story<UmbHistoryItemElement> = () => html`<umb-history-item
|
||||
name="Name attribute"
|
||||
detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>`;
|
||||
export const Node: Story<UmbHistoryItemElement> = () =>
|
||||
html`<umb-history-item name="Name attribute" detail="Detail attribute">
|
||||
Default slot
|
||||
<uui-button slot="actions" label="action">Action slot</uui-button>
|
||||
</umb-history-item>`;
|
||||
|
||||
@@ -112,12 +112,12 @@ export class UmbInputMultiUrlElement extends FormControlMixin(UmbLitElement) {
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
() => !!this.min && this.urls.length < this.min
|
||||
() => !!this.min && this.urls.length < this.min,
|
||||
);
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this.urls.length > this.max
|
||||
() => !!this.max && this.urls.length > this.max,
|
||||
);
|
||||
|
||||
this.myModalRegistration = new UmbModalRouteRegistrationController(this, UMB_LINK_PICKER_MODAL)
|
||||
|
||||
@@ -102,7 +102,11 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
|
||||
const manifests = (await firstValueFrom(observable)) as ManifestTinyMcePlugin[];
|
||||
|
||||
for (const manifest of manifests) {
|
||||
const plugin = manifest.js ? await loadManifestApi(manifest.js) : manifest.api ? await loadManifestApi(manifest.api) : undefined;
|
||||
const plugin = manifest.js
|
||||
? await loadManifestApi(manifest.js)
|
||||
: manifest.api
|
||||
? await loadManifestApi(manifest.api)
|
||||
: undefined;
|
||||
if (plugin) {
|
||||
this.#plugins.push(plugin);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
@@ -189,12 +189,13 @@ export class UmbTableElement extends LitElement {
|
||||
return html` <uui-table-head-cell style="--uui-table-cell-padding: 0">
|
||||
${when(
|
||||
this.config.allowSelection,
|
||||
() => html` <uui-checkbox
|
||||
label="Select All"
|
||||
style="padding: var(--uui-size-4) var(--uui-size-5);"
|
||||
@change="${this._handleAllRowsCheckboxChange}"
|
||||
?checked="${this.selection.length === this.items.length}">
|
||||
</uui-checkbox>`
|
||||
() =>
|
||||
html` <uui-checkbox
|
||||
label="Select All"
|
||||
style="padding: var(--uui-size-4) var(--uui-size-5);"
|
||||
@change="${this._handleAllRowsCheckboxChange}"
|
||||
?checked="${this.selection.length === this.items.length}">
|
||||
</uui-checkbox>`,
|
||||
)}
|
||||
</uui-table-head-cell>`;
|
||||
}
|
||||
@@ -217,12 +218,13 @@ export class UmbTableElement extends LitElement {
|
||||
${when(!this.config.hideIcon, () => html`<uui-icon name=${ifDefined(item.icon)}></uui-icon>`)}
|
||||
${when(
|
||||
this.config.allowSelection,
|
||||
() => html` <uui-checkbox
|
||||
label="Select Row"
|
||||
@click=${(e: PointerEvent) => e.stopPropagation()}
|
||||
@change=${(event: Event) => this._handleRowCheckboxChange(event, item)}
|
||||
?checked="${this._isSelected(item.id)}">
|
||||
</uui-checkbox>`
|
||||
() =>
|
||||
html` <uui-checkbox
|
||||
label="Select Row"
|
||||
@click=${(e: PointerEvent) => e.stopPropagation()}
|
||||
@change=${(event: Event) => this._handleRowCheckboxChange(event, item)}
|
||||
?checked="${this._isSelected(item.id)}">
|
||||
</uui-checkbox>`,
|
||||
)}
|
||||
</uui-table-cell>`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, LitElement, nothing, repeat, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export interface TooltipMenuItem {
|
||||
@@ -48,7 +48,7 @@ export class UmbTooltipMenuElement extends LitElement {
|
||||
return repeat(
|
||||
this.items,
|
||||
(item) => item.label,
|
||||
(item) => this._renderItem(item)
|
||||
(item) => this._renderItem(item),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { DocumentVariantResponseModel, ContentStateModel } from '@umbraco-cms/ba
|
||||
|
||||
@customElement('umb-variant-selector')
|
||||
export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
|
||||
@state()
|
||||
_variants: Array<DocumentVariantResponseModel> = [];
|
||||
|
||||
@@ -21,7 +20,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
@state()
|
||||
_activeVariants: Array<ActiveVariant> = [];
|
||||
|
||||
@property({attribute: false})
|
||||
@property({ attribute: false })
|
||||
public get _activeVariantsCultures(): string[] {
|
||||
return this._activeVariants.map((el) => el.culture ?? '') ?? [];
|
||||
}
|
||||
@@ -70,7 +69,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
this._variants = variants;
|
||||
}
|
||||
},
|
||||
'_observeVariants'
|
||||
'_observeVariants',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +86,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
this._activeVariants = activeVariants;
|
||||
}
|
||||
},
|
||||
'_observeActiveVariants'
|
||||
'_observeActiveVariants',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +104,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
(name) => {
|
||||
this._name = name;
|
||||
},
|
||||
'_name'
|
||||
'_name',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +122,11 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
if (event instanceof UUIInputEvent) {
|
||||
const target = event.composedPath()[0] as UUIInputElement;
|
||||
|
||||
if (typeof target?.value === 'string' && this.#variantContext && isNameablePropertySetContext(this.#variantContext)) {
|
||||
if (
|
||||
typeof target?.value === 'string' &&
|
||||
this.#variantContext &&
|
||||
isNameablePropertySetContext(this.#variantContext)
|
||||
) {
|
||||
this.#variantContext.setName(target.value);
|
||||
}
|
||||
}
|
||||
@@ -196,33 +199,32 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
<uui-scroll-container>
|
||||
<ul>
|
||||
${this._variants.map(
|
||||
(variant) =>
|
||||
html`
|
||||
<li class="${this._isVariantActive(variant.culture!) ? 'selected' : ''}">
|
||||
<button
|
||||
class="variant-selector-switch-button
|
||||
(variant) => html`
|
||||
<li class="${this._isVariantActive(variant.culture!) ? 'selected' : ''}">
|
||||
<button
|
||||
class="variant-selector-switch-button
|
||||
${this._isNotPublishedMode(variant.culture!, variant.state!) ? 'add-mode' : ''}"
|
||||
@click=${() => this._switchVariant(variant)}>
|
||||
${this._isNotPublishedMode(variant.culture!, variant.state!)
|
||||
? html`<uui-icon class="add-icon" name="icon-add"></uui-icon>`
|
||||
: nothing}
|
||||
<div>
|
||||
${variant.name} <i>(${variant.culture})</i> ${variant.segment}
|
||||
<div class="variant-selector-state">${variant.state}</div>
|
||||
</div>
|
||||
</button>
|
||||
@click=${() => this._switchVariant(variant)}>
|
||||
${this._isNotPublishedMode(variant.culture!, variant.state!)
|
||||
? html`<uui-icon class="add-icon" name="icon-add"></uui-icon>`
|
||||
: nothing}
|
||||
<div>
|
||||
${variant.name} <i>(${variant.culture})</i> ${variant.segment}
|
||||
<div class="variant-selector-state">${variant.state}</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
${this._isVariantActive(variant.culture!)
|
||||
? nothing
|
||||
: html`
|
||||
<uui-button
|
||||
class="variant-selector-split-view"
|
||||
@click=${() => this._openSplitView(variant)}>
|
||||
Split view
|
||||
</uui-button>
|
||||
`}
|
||||
</li>
|
||||
`
|
||||
${this._isVariantActive(variant.culture!)
|
||||
? nothing
|
||||
: html`
|
||||
<uui-button
|
||||
class="variant-selector-split-view"
|
||||
@click=${() => this._openSplitView(variant)}>
|
||||
Split view
|
||||
</uui-button>
|
||||
`}
|
||||
</li>
|
||||
`,
|
||||
)}
|
||||
</ul>
|
||||
</uui-scroll-container>
|
||||
@@ -307,7 +309,12 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--uui-color-divider-standalone);
|
||||
font-family: Lato, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
font-family:
|
||||
Lato,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.variant-selector-switch-button:hover {
|
||||
|
||||
@@ -11,6 +11,5 @@ export default meta;
|
||||
type Story = StoryObj<UmbVariantSelectorElement>;
|
||||
|
||||
export const Overview: Story = {
|
||||
args: {
|
||||
},
|
||||
args: {},
|
||||
};
|
||||
|
||||
@@ -9,5 +9,5 @@ export const UMB_DATA_TYPE_CREATE_OPTIONS_MODAL = new UmbModalToken<UmbDataTypeC
|
||||
{
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export class UmbDataTypeItemServerDataSource implements UmbItemDataSource<DataTy
|
||||
this.#host,
|
||||
DataTypeResource.getDataTypeItem({
|
||||
id: ids,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry';
|
||||
*/
|
||||
@customElement('umb-data-type-workspace-editor')
|
||||
export class UmbDataTypeWorkspaceEditorElement extends UmbLitElement {
|
||||
|
||||
@property({attribute: false})
|
||||
@property({ attribute: false })
|
||||
manifest?: ManifestWorkspace;
|
||||
|
||||
@state()
|
||||
@@ -43,7 +42,7 @@ export class UmbDataTypeWorkspaceEditorElement extends UmbLitElement {
|
||||
}
|
||||
this.removeControllerByAlias('_observeIsNew');
|
||||
},
|
||||
'_observeIsNew'
|
||||
'_observeIsNew',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
@@ -60,7 +60,7 @@ export class UmbDebugElement extends UmbLitElement {
|
||||
// Massage the data into a simplier array of objects
|
||||
// From a function in the context-api '
|
||||
this.contextData = contextData(contexts);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ export class UmbDebugElement extends UmbLitElement {
|
||||
<ul>
|
||||
${this._renderInstance(contextData.data)}
|
||||
</ul>
|
||||
</li>`
|
||||
</li>`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -132,16 +132,14 @@ export class UmbDebugElement extends UmbLitElement {
|
||||
return instanceTemplates.push(html`<li>Callable Function</li>`);
|
||||
} else if (instance.type === 'object') {
|
||||
if (instance.methods?.length) {
|
||||
instanceTemplates.push(
|
||||
html`
|
||||
<li>
|
||||
<strong>Methods</strong>
|
||||
<ul>
|
||||
${instance.methods?.map((methodName) => html`<li>${methodName}</li>`)}
|
||||
</ul>
|
||||
</li>
|
||||
`
|
||||
);
|
||||
instanceTemplates.push(html`
|
||||
<li>
|
||||
<strong>Methods</strong>
|
||||
<ul>
|
||||
${instance.methods?.map((methodName) => html`<li>${methodName}</li>`)}
|
||||
</ul>
|
||||
</li>
|
||||
`);
|
||||
}
|
||||
|
||||
const props: TemplateResult[] = [];
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { UmbFolderRepository } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
export class UmbFolderUpdateEntityAction<
|
||||
T extends UmbFolderRepository = UmbFolderRepository
|
||||
T extends UmbFolderRepository = UmbFolderRepository,
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UmbEntityActionBase } from '../../entity-action.js';
|
||||
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbSortChildrenOfEntityAction<
|
||||
T extends { sortChildrenOf(): Promise<void> }
|
||||
T extends { sortChildrenOf(): Promise<void> },
|
||||
> extends UmbEntityActionBase<T> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
|
||||
@@ -6,7 +6,9 @@ import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbr
|
||||
* An action to perform on multiple entities
|
||||
* For example for content you may wish to move one or more documents in bulk
|
||||
*/
|
||||
export interface ManifestEntityBulkAction extends ManifestElementAndApi<HTMLElement, UmbEntityBulkActionBase>, ManifestWithDynamicConditions<ConditionTypes> {
|
||||
export interface ManifestEntityBulkAction
|
||||
extends ManifestElementAndApi<HTMLElement, UmbEntityBulkActionBase>,
|
||||
ManifestWithDynamicConditions<ConditionTypes> {
|
||||
type: 'entityBulkAction';
|
||||
meta: MetaEntityBulkAction;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
|
||||
|
||||
export interface ManifestLocalization extends ManifestPlainJs<{default: UmbLocalizationDictionary}> {
|
||||
export interface ManifestLocalization extends ManifestPlainJs<{ default: UmbLocalizationDictionary }> {
|
||||
type: 'localization';
|
||||
meta: MetaLocalization;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { UmbModalExtensionElement } from '../interfaces/modal-extension-element.interface.js';
|
||||
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestModal extends ManifestElement<UmbModalExtensionElement<any, any> | UmbModalExtensionElement<any, undefined>> {
|
||||
export interface ManifestModal
|
||||
extends ManifestElement<UmbModalExtensionElement<any, any> | UmbModalExtensionElement<any, undefined>> {
|
||||
type: 'modal';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { ConditionTypes } from '../conditions/types.js';
|
||||
import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
export interface ManifestPropertyAction extends ManifestElement<HTMLElement>, ManifestWithDynamicConditions<ConditionTypes> {
|
||||
export interface ManifestPropertyAction
|
||||
extends ManifestElement<HTMLElement>,
|
||||
ManifestWithDynamicConditions<ConditionTypes> {
|
||||
type: 'propertyAction';
|
||||
meta: MetaPropertyAction;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { ConditionTypes } from '../conditions/types.js';
|
||||
import type { UmbApi, ManifestApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
// TODO: Consider adding a ClassType for this manifest. (Currently we cannot know the scope of a repository, therefor we are going with ExtensionApi for now.)
|
||||
export interface ManifestRepository<ApiType extends UmbApi = UmbApi> extends ManifestApi<ApiType>, ManifestWithDynamicConditions<ConditionTypes> {
|
||||
export interface ManifestRepository<ApiType extends UmbApi = UmbApi>
|
||||
extends ManifestApi<ApiType>,
|
||||
ManifestWithDynamicConditions<ConditionTypes> {
|
||||
type: 'repository';
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { ConditionTypes } from '../conditions/types.js';
|
||||
import type { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type {
|
||||
ManifestElementAndApi,
|
||||
ManifestWithDynamicConditions,
|
||||
} from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
|
||||
|
||||
export interface ManifestWorkspaceAction extends ManifestElementAndApi<HTMLElement, UmbWorkspaceAction>, ManifestWithDynamicConditions<ConditionTypes> {
|
||||
export interface ManifestWorkspaceAction
|
||||
extends ManifestElementAndApi<HTMLElement, UmbWorkspaceAction>,
|
||||
ManifestWithDynamicConditions<ConditionTypes> {
|
||||
type: 'workspaceAction';
|
||||
meta: MetaWorkspaceAction;
|
||||
}
|
||||
|
||||
@@ -16,23 +16,21 @@ export class UmbLocalizeDateElement extends UmbLitElement {
|
||||
@property()
|
||||
date!: string | Date;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Formatting options
|
||||
* @attr
|
||||
* @example options={ dateStyle: 'full', timeStyle: 'long', timeZone: 'Australia/Sydney' }
|
||||
*/
|
||||
@property()
|
||||
@property()
|
||||
options?: Intl.DateTimeFormatOptions;
|
||||
|
||||
|
||||
@state()
|
||||
protected get text(): string {
|
||||
return this.localize.date(this.date, this.options);
|
||||
return this.localize.date(this.date, this.options);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return this.date
|
||||
? html`${unsafeHTML(this.text)}`
|
||||
: html`<slot></slot>`
|
||||
return this.date ? html`${unsafeHTML(this.text)}` : html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -16,23 +16,21 @@ export class UmbLocalizeNumberElement extends UmbLitElement {
|
||||
@property()
|
||||
number!: number | string;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Formatting options
|
||||
* @attr
|
||||
* @example options={ style: 'currency', currency: 'EUR' }
|
||||
*/
|
||||
@property()
|
||||
@property()
|
||||
options?: Intl.NumberFormatOptions;
|
||||
|
||||
|
||||
@state()
|
||||
protected get text(): string {
|
||||
return this.localize.number(this.number, this.options);
|
||||
return this.localize.number(this.number, this.options);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return this.number
|
||||
? html`${unsafeHTML(this.text)}`
|
||||
: html`<slot></slot>`
|
||||
return this.number ? html`${unsafeHTML(this.text)}` : html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -16,15 +16,15 @@ export class UmbLocalizeRelativeTimeElement extends UmbLitElement {
|
||||
@property()
|
||||
time!: number;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Formatting options
|
||||
* @attr
|
||||
* @example options={ dateStyle: 'full', timeStyle: 'long', timeZone: 'Australia/Sydney' }
|
||||
*/
|
||||
@property()
|
||||
@property()
|
||||
options?: Intl.RelativeTimeFormatOptions;
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Unit
|
||||
* @attr
|
||||
* @example unit='seconds'
|
||||
@@ -34,13 +34,11 @@ export class UmbLocalizeRelativeTimeElement extends UmbLitElement {
|
||||
|
||||
@state()
|
||||
protected get text(): string {
|
||||
return this.localize.relativeTime(this.time, this.unit, this.options);
|
||||
return this.localize.relativeTime(this.time, this.unit, this.options);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return this.time
|
||||
? html`${unsafeHTML(this.text)}`
|
||||
: html`<slot></slot>`
|
||||
return this.time ? html`${unsafeHTML(this.text)}` : html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -9,7 +9,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
@customElement('umb-localize')
|
||||
export class UmbLocalizeElement extends UmbLitElement {
|
||||
/**
|
||||
* The key to localize. The key is case sensitive.
|
||||
* The key to localize. The key is case sensitive.
|
||||
* @attr
|
||||
* @example key="general_ok"
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ export class UmbLocalizeElement extends UmbLitElement {
|
||||
*/
|
||||
@property()
|
||||
debug = false;
|
||||
|
||||
|
||||
@state()
|
||||
protected get text(): string {
|
||||
const localizedValue = this.localize.term(this.key, ...(this.args ?? []));
|
||||
@@ -53,8 +53,8 @@ export class UmbLocalizeElement extends UmbLitElement {
|
||||
return this.text.trim()
|
||||
? html`${unsafeHTML(this.text)}`
|
||||
: this.debug
|
||||
? html`<span style="color:red">${this.key}</span>`
|
||||
: html`<slot></slot>`;
|
||||
? html`<span style="color:red">${this.key}</span>`
|
||||
: html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
|
||||
@@ -64,7 +64,7 @@ export class UmbLocalizationRegistry {
|
||||
}
|
||||
|
||||
// If extension contains a js file, load it and add the default dictionary to the inner dictionary.
|
||||
if(extension.js) {
|
||||
if (extension.js) {
|
||||
const loadedExtension = await loadManifestPlainJs(extension.js);
|
||||
|
||||
if (loadedExtension && hasDefaultExport<UmbLocalizationDictionary>(loadedExtension)) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-confirm-modal')
|
||||
export class UmbConfirmModalElement extends UmbLitElement {
|
||||
|
||||
@property({ attribute: false })
|
||||
modalContext?: UmbModalContext<UmbConfirmModalData, UmbConfirmModalValue>;
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ import { UmbIconPickerModalData, UmbIconPickerModalValue, UmbModalBaseElement }
|
||||
// TODO: to prevent element extension we need to move the Picker logic into a separate class we can reuse across all pickers
|
||||
@customElement('umb-icon-picker-modal')
|
||||
export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPickerModalData, UmbIconPickerModalValue> {
|
||||
|
||||
|
||||
private _iconList = icons.filter((icon) => !icon.legacy);
|
||||
|
||||
@state()
|
||||
@@ -19,26 +17,26 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
|
||||
@state()
|
||||
private _colorList = [
|
||||
{alias:'text', varName:'--uui-color-text'},
|
||||
{alias:'yellow', varName:'--uui-palette-sunglow'},
|
||||
{alias:'pink', varName:'--uui-palette-spanish-pink'},
|
||||
{alias:'dark', varName:'--uui-palette-gunmetal'},
|
||||
{alias:'darkblue', varName:'--uui-palette-space-cadet'},
|
||||
{alias:'blue', varName:'--uui-palette-violet-blue'},
|
||||
{alias:'red', varName:'--uui-palette-maroon-flush'},
|
||||
{alias:'green', varName:'--uui-palette-jungle-green'},
|
||||
{alias:'brown', varName:'--uui-palette-chamoisee'},
|
||||
{ alias: 'text', varName: '--uui-color-text' },
|
||||
{ alias: 'yellow', varName: '--uui-palette-sunglow' },
|
||||
{ alias: 'pink', varName: '--uui-palette-spanish-pink' },
|
||||
{ alias: 'dark', varName: '--uui-palette-gunmetal' },
|
||||
{ alias: 'darkblue', varName: '--uui-palette-space-cadet' },
|
||||
{ alias: 'blue', varName: '--uui-palette-violet-blue' },
|
||||
{ alias: 'red', varName: '--uui-palette-maroon-flush' },
|
||||
{ alias: 'green', varName: '--uui-palette-jungle-green' },
|
||||
{ alias: 'brown', varName: '--uui-palette-chamoisee' },
|
||||
];
|
||||
|
||||
@state()
|
||||
_modalValue?:UmbIconPickerModalValue;
|
||||
_modalValue?: UmbIconPickerModalValue;
|
||||
|
||||
@state()
|
||||
_currentColorVarName = '--uui-color-text';
|
||||
|
||||
#changeIcon(e: { target: HTMLInputElement; type: any; key: unknown }) {
|
||||
if (e.type == 'click' || (e.type == 'keyup' && e.key == 'Enter')) {
|
||||
this.modalContext?.updateValue({icon: e.target.id});
|
||||
this.modalContext?.updateValue({ icon: e.target.id });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,18 +58,23 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
}
|
||||
|
||||
#onColorChange(e: UUIColorSwatchesEvent) {
|
||||
this.modalContext?.updateValue({icon: e.target.value});
|
||||
this.modalContext?.updateValue({ icon: e.target.value });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._iconListFiltered = this._iconList;
|
||||
|
||||
if(this.modalContext) {
|
||||
this.observe(this.modalContext?.value, (newValue) => {
|
||||
this._modalValue = newValue;
|
||||
this._currentColorVarName = this._colorList.find(x => x.alias === newValue?.color)?.alias ?? this._colorList[0].varName;
|
||||
}, '_observeModalContextValue');
|
||||
if (this.modalContext) {
|
||||
this.observe(
|
||||
this.modalContext?.value,
|
||||
(newValue) => {
|
||||
this._modalValue = newValue;
|
||||
this._currentColorVarName =
|
||||
this._colorList.find((x) => x.alias === newValue?.color)?.alias ?? this._colorList[0].varName;
|
||||
},
|
||||
'_observeModalContextValue',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +91,15 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
${
|
||||
// TODO: Missing translation for the color aliases.
|
||||
this._colorList.map(
|
||||
(color) => html`
|
||||
<uui-color-swatch label="${color.alias}" title="${color.alias}" value=${color.alias} style="--uui-swatch-color: var(${color.varName})"></uui-color-swatch>
|
||||
`,
|
||||
)}
|
||||
(color) => html`
|
||||
<uui-color-swatch
|
||||
label="${color.alias}"
|
||||
title="${color.alias}"
|
||||
value=${color.alias}
|
||||
style="--uui-swatch-color: var(${color.varName})"></uui-color-swatch>
|
||||
`,
|
||||
)
|
||||
}
|
||||
</uui-color-swatches>
|
||||
<hr />
|
||||
<uui-scroll-container id="icon-selection">${this.renderIconSelection()}</uui-scroll-container>
|
||||
@@ -116,7 +124,8 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
}
|
||||
|
||||
renderIconSelection() {
|
||||
return repeat(this._iconListFiltered,
|
||||
return repeat(
|
||||
this._iconListFiltered,
|
||||
(icon) => icon.name,
|
||||
(icon) => html`
|
||||
<uui-icon
|
||||
@@ -130,7 +139,7 @@ export class UmbIconPickerModalElement extends UmbModalBaseElement<UmbIconPicker
|
||||
@click="${this.#changeIcon}"
|
||||
@keyup="${this.#changeIcon}">
|
||||
</uui-icon>
|
||||
`
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ export default {
|
||||
id: 'umb-icon-picker-modal',
|
||||
} as Meta;
|
||||
|
||||
const data: UmbIconPickerModalData = {
|
||||
};
|
||||
const data: UmbIconPickerModalData = {};
|
||||
|
||||
const value: UmbIconPickerModalValue = {
|
||||
color: undefined,
|
||||
|
||||
@@ -38,7 +38,8 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement<
|
||||
this.observe(
|
||||
umbExtensionsRegistry.extensionsOfType('section'),
|
||||
(sections: Array<ManifestSection>) => (this._sections = sections),
|
||||
), 'umbSectionsObserver';
|
||||
),
|
||||
'umbSectionsObserver';
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -104,7 +104,7 @@ export class UmbModalElement extends UmbLitElement {
|
||||
|
||||
async #createInnerElement(manifest: ManifestModal) {
|
||||
// TODO: add inner fallback element if no extension element is found
|
||||
const innerElement = (await createExtensionElement(manifest));
|
||||
const innerElement = await createExtensionElement(manifest);
|
||||
|
||||
if (innerElement) {
|
||||
innerElement.data = this.#modalContext!.data;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user