fix loader helpers

This commit is contained in:
Niels Lyngsø
2023-11-28 22:04:03 +01:00
committed by Jacob Overgaard
parent 2655a555ea
commit 8250575422
8 changed files with 145 additions and 118 deletions

View File

@@ -1,31 +1,44 @@
import type { UmbApi } from "../models/api.interface.js";
import type { ApiLoaderExports, ApiLoaderProperty, ClassConstructor, ElementAndApiLoaderProperty, ElementLoaderExports } from "../types/utils.js";
import type { UmbApi } from '../models/api.interface.js';
import type {
ApiLoaderExports,
ApiLoaderProperty,
ClassConstructor,
ElementAndApiLoaderProperty,
ElementLoaderExports,
} from '../types/utils.js';
export async function loadManifestApi<ApiType extends UmbApi>(property: ApiLoaderProperty<ApiType> | ElementAndApiLoaderProperty<any, ApiType>): Promise<ClassConstructor<ApiType> | undefined> {
const propType = typeof property
if(propType === 'function') {
if((property as ClassConstructor).prototype) {
export async function loadManifestApi<ApiType extends UmbApi>(
property: ApiLoaderProperty<ApiType> | ElementAndApiLoaderProperty<any, ApiType>,
): Promise<ClassConstructor<ApiType> | undefined> {
const propType = typeof property;
if (propType === 'function') {
if ((property as ClassConstructor).prototype) {
// Class Constructor
return property as ClassConstructor<ApiType>;
} else {
// Promise function
const result = await (property as (Exclude<Exclude<ApiLoaderProperty<ApiType>, string>, ClassConstructor<ApiType>>))();
if(typeof result === 'object' && result != null) {
const exportValue = 'api' in result ? result.api : undefined || 'default' in result ? (result as Exclude<(typeof result), ElementLoaderExports>).default : undefined;
if(exportValue && typeof exportValue === 'function') {
const result = await (
property as Exclude<Exclude<ApiLoaderProperty<ApiType>, string>, ClassConstructor<ApiType>>
)();
if (typeof result === 'object' && result != null) {
const exportValue =
('api' in result ? result.api : undefined) ||
('default' in result ? (result as Exclude<typeof result, ElementLoaderExports>).default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}
}
} else if(propType === 'string') {
} else if (propType === 'string') {
// Import string
const result = await (import(/* @vite-ignore */ property as string) as unknown as ApiLoaderExports<ApiType>);
if(typeof result === 'object' && result != null) {
const exportValue = 'api' in result ? result.api : undefined || 'default' in result ? result.default : undefined;
if(exportValue && typeof exportValue === 'function') {
if (result && typeof result === 'object') {
const exportValue =
('api' in result ? result.api : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}
}
return undefined;
}
}

View File

@@ -1,28 +1,40 @@
import type { ApiLoaderExports, ClassConstructor, ElementAndApiLoaderProperty, ElementLoaderExports, ElementLoaderProperty } from "../types/utils.js";
import type {
ApiLoaderExports,
ClassConstructor,
ElementAndApiLoaderProperty,
ElementLoaderExports,
ElementLoaderProperty,
} from '../types/utils.js';
export async function loadManifestElement<ElementType extends HTMLElement>(property: ElementLoaderProperty<ElementType> | ElementAndApiLoaderProperty<ElementType>): Promise<ClassConstructor<ElementType> | undefined> {
const propType = typeof property
if(propType === 'function') {
if((property as ClassConstructor).prototype) {
export async function loadManifestElement<ElementType extends HTMLElement>(
property: ElementLoaderProperty<ElementType> | ElementAndApiLoaderProperty<ElementType>,
): Promise<ClassConstructor<ElementType> | undefined> {
const propType = typeof property;
if (propType === 'function') {
if ((property as ClassConstructor).prototype) {
// Class Constructor
return property as ClassConstructor<ElementType>;
} else {
// Promise function
const result = await (property as (Exclude<Exclude<typeof property, string>, ClassConstructor<ElementType>>))();
if(typeof result === 'object' && result !== null) {
const exportValue = 'element' in result ? result.element : undefined || 'default' in result ? (result as Exclude<(typeof result), ApiLoaderExports>).default : undefined;
if(exportValue && typeof exportValue === 'function') {
const result = await (property as Exclude<Exclude<typeof property, string>, ClassConstructor<ElementType>>)();
if (typeof result === 'object' && result !== null) {
const exportValue =
('element' in result ? result.element : undefined) ||
('default' in result ? (result as Exclude<typeof result, ApiLoaderExports>).default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}
}
} else if(propType === 'string') {
} else if (propType === 'string') {
// Import string
const result = await (import(/* @vite-ignore */ property as string) as unknown as ElementLoaderExports<ElementType>);
if(typeof result === 'object' && result != null) {
const exportValue = 'element' in result ? result.element : undefined || 'default' in result ? result.default : undefined;
if(exportValue && typeof exportValue === 'function') {
const result = await (import(
/* @vite-ignore */ property as string
) as unknown as ElementLoaderExports<ElementType>);
if (result && typeof result === 'object') {
const exportValue =
('element' in result ? result.element : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'function') {
return exportValue;
}
}

View File

@@ -1,18 +1,29 @@
import type { JsLoaderProperty } from "../types/utils.js";
import type { CssLoaderExports, CssLoaderProperty } from '../types/utils.js';
export async function loadManifestPlainCss<CssType = string>(property: JsLoaderProperty<CssType>): Promise<CssType | undefined> {
export async function loadManifestPlainCss<CssType extends string>(
property: CssLoaderProperty<CssType>,
): Promise<CssType | undefined> {
const propType = typeof property;
if(propType === 'function') {
const result = await (property as (Exclude<(typeof property), string>))();
if(result != null) {
return result;
if (propType === 'function') {
// Promise function
const result = await (property as Exclude<typeof property, string>)();
if (typeof result === 'object' && result != null) {
const exportValue =
('css' in result ? result.css : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'string') {
return exportValue as CssType;
}
}
} else if(propType === 'string') {
} else if (propType === 'string') {
// Import string
const result = await (import(/* @vite-ignore */ property as string) as unknown as CssType);
if(result != null) {
return result;
const result = await (import(/* @vite-ignore */ property as string) as unknown as CssLoaderExports<CssType>);
if (typeof result === 'object' && result != null) {
const exportValue =
('css' in result ? result.css : undefined) || ('default' in result ? result.default : undefined);
if (exportValue && typeof exportValue === 'string') {
return exportValue;
}
}
}
return undefined;
}
}

View File

@@ -1,18 +1,21 @@
import type { JsLoaderProperty } from "../types/utils.js";
import type { JsLoaderProperty } from '../types/utils.js';
export async function loadManifestPlainJs<JsType extends object>(property: JsLoaderProperty<JsType>): Promise<JsType | undefined> {
export async function loadManifestPlainJs<JsType extends object>(
property: JsLoaderProperty<JsType>,
): Promise<JsType | undefined> {
const propType = typeof property;
if(propType === 'function') {
const result = await (property as (Exclude<(typeof property), string>))();
if(typeof result === 'object' && result != null) {
if (propType === 'function') {
// Promise function
const result = await (property as Exclude<typeof property, string>)();
if (typeof result === 'object' && result != null) {
return result;
}
} else if(propType === 'string') {
} else if (propType === 'string') {
// Import string
const result = await (import(/* @vite-ignore */ property as string) as unknown as JsType);
if(typeof result === 'object' && result != null) {
if (typeof result === 'object' && result != null) {
return result;
}
}
return undefined;
}
}

View File

@@ -26,12 +26,12 @@ export interface ManifestElementWithElementName extends ManifestElement {
elementName: string;
}
export interface ManifestPlainCss<CssType = unknown> extends ManifestBase {
export interface ManifestPlainCss extends ManifestBase {
/**
* The file location of the stylesheet file to load
* @TJS-type string
*/
css?: CssLoaderProperty<CssType>;
css?: CssLoaderProperty;
}
export interface ManifestPlainJs<JsType> extends ManifestBase {

View File

@@ -1,5 +1,4 @@
import type { UmbApi } from "../models/index.js";
import type { UmbApi } from '../models/index.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type HTMLElementConstructor<T = HTMLElement> = new (...args: any[]) => T;
@@ -7,80 +6,60 @@ export type HTMLElementConstructor<T = HTMLElement> = new (...args: any[]) => T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ClassConstructor<T = object> = new (...args: any[]) => T;
// Module Export Types:
export type ElementLoaderExports<
ElementType extends HTMLElement = HTMLElement
> = ({default: ClassConstructor<ElementType>} | {element: ClassConstructor<ElementType>})// | Omit<Omit<object, 'element'>, 'default'>
export type CssLoaderExports<CssType extends string = string> = { default: CssType } | { css: CssType };
export type ApiLoaderExports<
ApiType extends UmbApi = UmbApi
> = ({default: ClassConstructor<ApiType>} | {api: ClassConstructor<ApiType>})//| Omit<Omit<object, 'api'>, 'default'>
export type ElementLoaderExports<ElementType extends HTMLElement = HTMLElement> =
| { default: ClassConstructor<ElementType> }
| { element: ClassConstructor<ElementType> }; // | Omit<Omit<object, 'element'>, 'default'>
export type ApiLoaderExports<ApiType extends UmbApi = UmbApi> =
| { default: ClassConstructor<ApiType> }
| { api: ClassConstructor<ApiType> }; //| Omit<Omit<object, 'api'>, 'default'>
export type ElementAndApiLoaderExports<
ElementType extends HTMLElement = HTMLElement,
ApiType extends UmbApi = UmbApi
> = ({api: ClassConstructor<ApiType>} | {element: ClassConstructor<ElementType>} | {api: ClassConstructor<ApiType>, element: ClassConstructor<ElementType>})// | Omit<Omit<Omit<object, 'element'>, 'api'>, 'default'>
ApiType extends UmbApi = UmbApi,
> =
| { api: ClassConstructor<ApiType> }
| { element: ClassConstructor<ElementType> }
| { api: ClassConstructor<ApiType>; element: ClassConstructor<ElementType> }; // | Omit<Omit<Omit<object, 'element'>, 'api'>, 'default'>
// Promise Types:
export type CssLoaderPromise<
CssType = unknown
> = (() => Promise<CssType>)
export type CssLoaderPromise<CssType extends string = string> = () => Promise<CssLoaderExports<CssType>>;
export type JsLoaderPromise<
JsType
> = (() => Promise<JsType>)
export type JsLoaderPromise<JsExportType> = () => Promise<JsExportType>;
export type ElementLoaderPromise<
ElementType extends HTMLElement = HTMLElement
> = (() => Promise<ElementLoaderExports<ElementType>>)
export type ElementLoaderPromise<ElementType extends HTMLElement = HTMLElement> = () => Promise<
ElementLoaderExports<ElementType>
>;
export type ApiLoaderPromise<
ApiType extends UmbApi = UmbApi
> = (() => Promise<ApiLoaderExports<ApiType>>)
export type ApiLoaderPromise<ApiType extends UmbApi = UmbApi> = () => Promise<ApiLoaderExports<ApiType>>;
export type ElementAndApiLoaderPromise<
ElementType extends HTMLElement = HTMLElement,
ApiType extends UmbApi = UmbApi
> = (() => Promise<ElementAndApiLoaderExports<ElementType, ApiType>>)
ApiType extends UmbApi = UmbApi,
> = () => Promise<ElementAndApiLoaderExports<ElementType, ApiType>>;
// Property Types:
export type CssLoaderProperty<CssType = string> = (
string
|
CssLoaderPromise<CssType>
);
export type JsLoaderProperty<JsType> = (
string
|
JsLoaderPromise<JsType>
);
export type ElementLoaderProperty<ElementType extends HTMLElement = HTMLElement> = (
string
|
ElementLoaderPromise<ElementType>
|
ClassConstructor<ElementType>
);
export type ApiLoaderProperty<ApiType extends UmbApi = UmbApi> = (
string
|
ApiLoaderPromise<ApiType>
|
ClassConstructor<ApiType>
);
export type ElementAndApiLoaderProperty<ElementType extends HTMLElement = HTMLElement, ApiType extends UmbApi = UmbApi> = (
string
|
ElementAndApiLoaderPromise<ElementType, ApiType>
|
ElementLoaderPromise<ElementType>
|
ApiLoaderPromise<ApiType>
);
export type CssLoaderProperty<CssType extends string = string> = string | CssLoaderPromise<CssType>;
export type JsLoaderProperty<JsExportType> = string | JsLoaderPromise<JsExportType>;
export type ElementLoaderProperty<ElementType extends HTMLElement = HTMLElement> =
| string
| ElementLoaderPromise<ElementType>
| ClassConstructor<ElementType>;
export type ApiLoaderProperty<ApiType extends UmbApi = UmbApi> =
| string
| ApiLoaderPromise<ApiType>
| ClassConstructor<ApiType>;
export type ElementAndApiLoaderProperty<
ElementType extends HTMLElement = HTMLElement,
ApiType extends UmbApi = UmbApi,
> =
| string
| ElementAndApiLoaderPromise<ElementType, ApiType>
| ElementLoaderPromise<ElementType>
| ApiLoaderPromise<ApiType>;

View File

@@ -2,6 +2,6 @@ import type { ManifestPlainCss } from '@umbraco-cms/backoffice/extension-api';
/**
* Theme manifest for styling the backoffice of Umbraco such as dark, high contrast etc
*/
export interface ManifestTheme extends ManifestPlainCss<string> {
export interface ManifestTheme extends ManifestPlainCss {
type: 'theme';
}

View File

@@ -42,14 +42,16 @@ export class UmbThemeContext extends UmbBaseController {
if (themes.length > 0 && themes[0].css) {
const activeTheme = themes[0];
if (typeof activeTheme.css === 'function') {
const styleEl = (this.#styleElement = document.createElement('style'));
styleEl.setAttribute('type', 'text/css');
document.head.appendChild(styleEl);
this.#styleElement = document.createElement('style') as HTMLStyleElement;
// We store the current style element so we can check if it has been replaced by another theme in between.
const currentStyleEl = this.#styleElement;
currentStyleEl.setAttribute('type', 'text/css');
const result = await loadManifestPlainCss(activeTheme.css);
// Checking that this is still our styleElement, it has not been replaced with another theme in between.
if (result && styleEl === this.#styleElement) {
styleEl.appendChild(document.createTextNode(result));
if (result && currentStyleEl === this.#styleElement) {
currentStyleEl.appendChild(document.createTextNode(result));
document.head.appendChild(currentStyleEl);
}
} else if (typeof activeTheme.css === 'string') {
this.#styleElement = document.createElement('link');
@@ -58,16 +60,23 @@ export class UmbThemeContext extends UmbBaseController {
document.head.appendChild(this.#styleElement);
}
} else {
console.log('remove style element', this.#styleElement);
// We could not load a theme for this alias, so we remove the theme.
localStorage.removeItem(LOCAL_STORAGE_KEY);
this.#styleElement?.childNodes.forEach((node) => node.remove());
this.#styleElement?.setAttribute('href', '');
this.#styleElement = null;
}
},
);
} else {
// Super clean, we got a falsy value, so we remove the theme.
localStorage.removeItem(LOCAL_STORAGE_KEY);
this.#styleElement?.remove();
this.#styleElement?.childNodes.forEach((node) => node.remove());
this.#styleElement?.setAttribute('href', '');
this.#styleElement = null;
}
}
}