Merge pull request #374 from umbraco/feature/simpler-usage-of-manifest-types

This commit is contained in:
Niels Lyngsø
2023-01-05 13:58:19 +01:00
committed by GitHub
13 changed files with 60 additions and 128 deletions

View File

@@ -3,8 +3,7 @@ import { css, html, nothing } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { UmbModalService } from '../../../../../../core/modal';
import { UmbWorkspaceDataTypeContext } from '../../workspace-data-type.context';
import { UmbDataTypeStoreItemType } from '../../../data-type.store';
import type { DataTypeDetails, ManifestPropertyEditorUI } from '@umbraco-cms/models';
import type { DataTypeDetails } from '@umbraco-cms/models';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import '../../../../../shared/property-editors/shared/property-editor-config/property-editor-config.element';
@@ -79,12 +78,12 @@ export class UmbWorkspaceViewDataTypeEditElement extends UmbLitElement {
if (!propertyEditorUIAlias) return;
this.observe(
umbExtensionsRegistry.getByAlias<ManifestPropertyEditorUI>(propertyEditorUIAlias),
umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', propertyEditorUIAlias),
(propertyEditorUI) => {
this._propertyEditorUIName = propertyEditorUI?.meta.label ?? propertyEditorUI?.name ?? '';
this._propertyEditorUIAlias = propertyEditorUI?.alias ?? '';
this._propertyEditorUIIcon = propertyEditorUI?.meta?.icon ?? '';
this._propertyEditorModelAlias = propertyEditorUI?.meta?.propertyEditorModel ?? '';
this._propertyEditorUIIcon = propertyEditorUI?.meta.icon ?? '';
this._propertyEditorModelAlias = propertyEditorUI?.meta.propertyEditorModel ?? '';
this._workspaceContext?.update({ propertyEditorModelAlias: this._propertyEditorModelAlias });
}

View File

@@ -2,13 +2,13 @@ import { html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { isManifestElementNameType } from '@umbraco-cms/extensions-api';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import type { ManifestTypes } from '@umbraco-cms/models';
import type { ManifestBase } from '@umbraco-cms/models';
import { UmbLitElement } from '@umbraco-cms/element';
@customElement('umb-extension-root-workspace')
export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
@state()
private _extensions?: Array<ManifestTypes> = undefined;
private _extensions?: Array<ManifestBase> = undefined;
connectedCallback(): void {
super.connectedCallback();

View File

@@ -135,14 +135,12 @@ export class UmbEntityPropertyElement extends UmbLitElement {
private _observePropertyEditorUI() {
this.propertyEditorUIObserver?.destroy();
this.propertyEditorUIObserver = new UmbObserverController(this, umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => {
if (manifest?.type === 'propertyEditorUI') {
this._gotData(manifest);
}
this.propertyEditorUIObserver = this.observe(umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', this.propertyEditorUIAlias), (manifest) => {
this._gotEditor(manifest);
});
}
private _gotData(propertyEditorUIManifest?: ManifestPropertyEditorUI) {
private _gotEditor(propertyEditorUIManifest?: ManifestPropertyEditorUI | null) {
if (!propertyEditorUIManifest) {
// TODO: if dataTypeKey didn't exist in store, we should do some nice UI.
return;

View File

@@ -2,7 +2,7 @@ import { nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { map } from 'rxjs';
import { repeat } from 'lit/directives/repeat.js';
import { ManifestBase, ManifestTypes, umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import { createExtensionElement } from '@umbraco-cms/extensions-api';
import { isManifestElementableType } from 'src/core/extensions-api/is-manifest-elementable-type.function';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -40,7 +40,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
umbExtensionsRegistry
?.extensionsOfType(this.type)
.pipe(map((extensions) => extensions.filter(this.filter))),
async (extensions: ManifestTypes[]) => {
async (extensions) => {
const oldLength = this._extensions.length;
this._extensions = this._extensions.filter(current => extensions.find(incoming => incoming.alias === current.alias));
@@ -48,7 +48,7 @@ export class UmbExtensionSlotElement extends UmbLitElement {
this.requestUpdate('_extensions');
}
extensions.forEach(async (extension: ManifestTypes) => {
extensions.forEach(async (extension) => {
const hasExt = this._extensions.find(x => x.alias === extension.alias);
if(!hasExt) {

View File

@@ -58,27 +58,23 @@ export class UmbPropertyEditorConfigElement extends UmbLitElement {
private _observePropertyEditorUIConfig() {
if (!this._propertyEditorUIAlias) return;
this.observe(umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => {
if (manifest?.type === 'propertyEditorUI') {
this._observePropertyEditorModelConfig(manifest.meta.propertyEditorModel);
this._propertyEditorUIConfigProperties = manifest?.meta.config?.properties || [];
this._propertyEditorUIConfigDefaultData = manifest?.meta.config?.defaultData || [];
this._mergeConfigProperties();
this._mergeConfigDefaultData();
}
this.observe(umbExtensionsRegistry.getByTypeAndAlias('propertyEditorUI', this.propertyEditorUIAlias), (manifest) => {
this._observePropertyEditorModelConfig(manifest?.meta.propertyEditorModel);
this._propertyEditorUIConfigProperties = manifest?.meta.config?.properties || [];
this._propertyEditorUIConfigDefaultData = manifest?.meta.config?.defaultData || [];
this._mergeConfigProperties();
this._mergeConfigDefaultData();
});
}
private _observePropertyEditorModelConfig(propertyEditorModelAlias?: string) {
if (!propertyEditorModelAlias) return;
this.observe(umbExtensionsRegistry.getByAlias(propertyEditorModelAlias), (manifest) => {
if (manifest?.type === 'propertyEditorModel') {
this._propertyEditorModelConfigProperties = manifest?.meta.config?.properties || [];
this._propertyEditorModelConfigDefaultData = manifest?.meta.config?.defaultData || [];
this._mergeConfigProperties();
this._mergeConfigDefaultData();
}
this.observe(umbExtensionsRegistry.getByTypeAndAlias('propertyEditorModel', propertyEditorModelAlias), (manifest) => {
this._propertyEditorModelConfigProperties = manifest?.meta.config?.properties || [];
this._propertyEditorModelConfigDefaultData = manifest?.meta.config?.defaultData || [];
this._mergeConfigProperties();
this._mergeConfigDefaultData();
});
}

View File

@@ -1,9 +1,9 @@
import type { ManifestElementType } from '../models';
import type { ManifestElement } from '../models';
import { hasDefaultExport } from './has-default-export.function';
import { isManifestElementNameType } from './is-manifest-element-name-type.function';
import { loadExtension } from './load-extension.function';
export async function createExtensionElement(manifest: ManifestElementType): Promise<HTMLElement | undefined> {
export async function createExtensionElement(manifest: ManifestElement): Promise<HTMLElement | undefined> {
//TODO: Write tests for these extension options:
const js = await loadExtension(manifest);

View File

@@ -1,7 +1,7 @@
import type { ManifestElementType, ManifestElementWithElementName } from '../models';
import type { ManifestElement, ManifestElementWithElementName } from '../models';
export function isManifestElementNameType(manifest: unknown): manifest is ManifestElementWithElementName {
return (
typeof manifest === 'object' && manifest !== null && (manifest as ManifestElementType).elementName !== undefined
typeof manifest === 'object' && manifest !== null && (manifest as ManifestElement).elementName !== undefined
);
}

View File

@@ -1,8 +1,8 @@
import type { ManifestElementType, ManifestTypes } from "../extensions-registry/models";
import type { ManifestElement, ManifestBase } from "../extensions-registry/models";
import { isManifestElementNameType } from "./is-manifest-element-name-type.function";
import { isManifestJSType } from "./is-manifest-js-type.function";
import { isManifestLoaderType } from "./is-manifest-loader-type.function";
export function isManifestElementableType(manifest: ManifestTypes): manifest is ManifestElementType {
export function isManifestElementableType(manifest: ManifestBase): manifest is ManifestElement {
return isManifestElementNameType(manifest) || isManifestLoaderType(manifest) || isManifestJSType(manifest);
}

View File

@@ -1,6 +1,6 @@
import type { ManifestTypes } from "../extensions-registry/models";
import type { ManifestBase } from "../extensions-registry/models";
import { ManifestJSType } from "./load-extension.function";
export function isManifestJSType(manifest: ManifestTypes): manifest is ManifestJSType {
export function isManifestJSType(manifest: ManifestBase): manifest is ManifestJSType {
return (manifest as ManifestJSType).js !== undefined;
}

View File

@@ -1,6 +1,6 @@
import type { ManifestTypes } from "../extensions-registry/models";
import type { ManifestBase } from "../extensions-registry/models";
import { ManifestLoaderType } from "./load-extension.function";
export function isManifestLoaderType(manifest: ManifestTypes): manifest is ManifestLoaderType {
export function isManifestLoaderType(manifest: ManifestBase): manifest is ManifestLoaderType {
return typeof (manifest as ManifestLoaderType).loader === 'function';
}

View File

@@ -1,11 +1,11 @@
import type { ManifestTypes } from '../models';
import type { ManifestElement } from '../models';
import { isManifestJSType } from './is-manifest-js-type.function';
import { isManifestLoaderType } from './is-manifest-loader-type.function';
export type ManifestLoaderType = ManifestTypes & { loader: () => Promise<object | HTMLElement> };
export type ManifestJSType = ManifestTypes & { js: string };
export type ManifestLoaderType = ManifestElement & { loader: () => Promise<object | HTMLElement> };
export type ManifestJSType = ManifestElement & { js: string };
export async function loadExtension(manifest: ManifestTypes): Promise<object | HTMLElement | null> {
export async function loadExtension(manifest: ManifestElement): Promise<object | HTMLElement | null> {
try {
if (isManifestLoaderType(manifest)) {
return manifest.loader();

View File

@@ -1,30 +1,16 @@
import { BehaviorSubject, map, Observable } from 'rxjs';
import type {
ManifestTypes,
ManifestDashboard,
ManifestWorkspaceView,
ManifestEntrypoint,
ManifestPropertyAction,
ManifestPropertyEditorUI,
ManifestPropertyEditorModel,
ManifestSection,
ManifestSectionView,
ManifestTree,
ManifestTreeItemAction,
ManifestWorkspace,
ManifestWorkspaceAction,
ManifestCustom,
ManifestPackageView,
ManifestExternalLoginProvider,
ManifestHeaderApp,
ManifestCollectionView,
ManifestCollectionBulkAction,
ManifestTypeMap,
ManifestBase
} from '../../models';
import { hasDefaultExport } from '../has-default-export.function';
import { loadExtension } from '../load-extension.function';
type SpecificManifestTypeOrManifestBase<T extends keyof ManifestTypeMap | string> = T extends keyof ManifestTypeMap ? ManifestTypeMap[T] : ManifestBase;
export class UmbExtensionRegistry {
private _extensions = new BehaviorSubject<Array<ManifestTypes>>([]);
private _extensions = new BehaviorSubject<Array<ManifestBase>>([]);
public readonly extensions = this._extensions.asObservable();
register(manifest: ManifestTypes & { loader?: () => Promise<object | HTMLElement> }): void {
@@ -70,43 +56,27 @@ export class UmbExtensionRegistry {
}
getByAlias<T = ManifestTypes>(alias: string): Observable<T | null> {
getByAlias(alias: string) {
// TODO: make pipes prettier/simpler/reuseable
return this.extensions.pipe(map((dataTypes) => dataTypes.find((extension) => extension.alias === alias) || null)) as Observable<T | null>;
return this.extensions.pipe(map((dataTypes) => dataTypes.find((extension) => extension.alias === alias) || null));
}
getByTypeAndAlias<Key extends keyof ManifestTypeMap>(type: Key, alias: string) {
return this.extensionsOfType(type).pipe(map((extensions) => extensions.find((extension) => extension.alias === alias) || null));
}
// TODO: implement unregister of extension
// Typings concept, need to put all core types to get a good array return type for the provided type...
extensionsOfType(type: 'headerApp'): Observable<Array<ManifestHeaderApp>>;
extensionsOfType(type: 'section'): Observable<Array<ManifestSection>>;
extensionsOfType(type: 'sectionView'): Observable<Array<ManifestSectionView>>;
extensionsOfType<T = ManifestTree>(type: 'tree'): Observable<Array<T>>;
extensionsOfType(type: 'workspace'): Observable<Array<ManifestWorkspace>>;
extensionsOfType(type: 'treeItemAction'): Observable<Array<ManifestTreeItemAction>>;
extensionsOfType(type: 'dashboard'): Observable<Array<ManifestDashboard>>;
extensionsOfType(type: 'dashboardCollection'): Observable<Array<ManifestDashboard>>;
extensionsOfType(type: 'workspaceView'): Observable<Array<ManifestWorkspaceView>>;
extensionsOfType(type: 'workspaceAction'): Observable<Array<ManifestWorkspaceAction>>;
extensionsOfType(type: 'propertyEditorUI'): Observable<Array<ManifestPropertyEditorUI>>;
extensionsOfType(type: 'propertyEditorModel'): Observable<Array<ManifestPropertyEditorModel>>;
extensionsOfType(type: 'propertyAction'): Observable<Array<ManifestPropertyAction>>;
extensionsOfType(type: 'packageView'): Observable<Array<ManifestPackageView>>;
extensionsOfType(type: 'entrypoint'): Observable<Array<ManifestEntrypoint>>;
extensionsOfType(type: 'custom'): Observable<Array<ManifestCustom>>;
extensionsOfType(type: 'externalLoginProvider'): Observable<Array<ManifestExternalLoginProvider>>;
extensionsOfType(type: 'collectionView'): Observable<Array<ManifestCollectionView>>;
extensionsOfType(type: 'collectionBulkAction'): Observable<Array<ManifestCollectionBulkAction>>;
extensionsOfType<T extends ManifestTypes>(type: string): Observable<Array<T>>;
extensionsOfType(type: string): Observable<Array<ManifestTypes>> {
extensionsOfType<Key extends keyof ManifestTypeMap | string, T = SpecificManifestTypeOrManifestBase<Key>>(type: Key) {
return this.extensions.pipe(
map((exts) => exts.filter((ext) => ext.type === type).sort((a, b) => (b.weight || 0) - (a.weight || 0)))
);
) as Observable<Array<T>>;
}
extensionsOfTypes<ExtensionType = ManifestTypes>(types: string[]): Observable<Array<ExtensionType>> {
extensionsOfTypes<ExtensionType = ManifestBase>(types: string[]): Observable<Array<ExtensionType>> {
return this.extensions.pipe(
map((exts) => exts.filter((ext) => (types.indexOf(ext.type) !== -1)).sort((a, b) => (b.weight || 0) - (a.weight || 0)))
) as Observable<Array<ExtensionType>>;
}
}
}

View File

@@ -37,6 +37,7 @@ export * from './collection-bulk-action.models';
export * from './collection-view.models';
export type ManifestTypes =
| ManifestCustom
| ManifestHeaderApp
| ManifestSection
| ManifestSectionView
@@ -55,48 +56,16 @@ export type ManifestTypes =
| ManifestPackageView
| ManifestExternalLoginProvider
| ManifestEntrypoint
| ManifestCustom
| ManifestCollectionBulkAction
| ManifestCollectionView;
export type ManifestStandardTypes =
| 'headerApp'
| 'section'
| 'sectionView'
| 'tree'
| 'workspace'
| 'workspaceAction'
| 'workspaceView'
| 'workspaceViewCollection'
| 'treeItemAction'
| 'propertyEditorUI'
| 'propertyEditorModel'
| 'dashboard'
| 'dashboardCollection'
| 'userDashboard'
| 'propertyAction'
| 'packageView'
| 'entrypoint'
| 'externalLoginProvider'
| 'collectionBulkAction'
| 'collectionView';
export type ManifestStandardTypes = ManifestTypes['type'];
export type ManifestTypeMap = {
[Manifest in ManifestTypes as Manifest['type']]: Manifest;
};
export type ManifestElementType =
| ManifestSection
| ManifestSectionView
| ManifestTree
| ManifestTreeItemAction
| ManifestWorkspace
| ManifestWorkspaceView
| ManifestPropertyAction
| ManifestPropertyEditorUI
| ManifestDashboard
| ManifestUserDashboard
| ManifestWorkspaceAction
| ManifestPackageView
| ManifestExternalLoginProvider
| ManifestCollectionBulkAction
| ManifestCollectionView;
export interface ManifestBase {
type: string;