extension workspace as collection

This commit is contained in:
Mads Rasmussen
2024-02-01 20:42:50 +01:00
parent bf537bd0eb
commit 3aaecaab1f
11 changed files with 256 additions and 96 deletions

View File

@@ -114,6 +114,10 @@ export class UmbExtensionRegistry<
this._extensions.setValue([...this._extensions.getValue(), manifest as ManifestTypes]);
}
getExtensions(): Array<ManifestTypes> {
return this._extensions.getValue();
}
registerMany(manifests: Array<ManifestTypes | ManifestKind<ManifestTypes>>): void {
// we have to register extensions individually, so we ensure a manifest is valid before continuing to the next one
manifests.forEach((manifest) => this.register(manifest));

View File

@@ -0,0 +1,18 @@
import { UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS } from './repository/index.js';
import { manifests as collectionRepositoryManifests } from './repository/manifests.js';
import { manifests as collectionViewManifests } from './views/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_EXTENSION_COLLECTION_ALIAS = 'Umb.Collection.Extension';
const collectionManifest: ManifestTypes = {
type: 'collection',
kind: 'default',
alias: UMB_EXTENSION_COLLECTION_ALIAS,
name: 'Extension Collection',
meta: {
repositoryAlias: UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS,
},
};
export const manifests = [collectionManifest, ...collectionRepositoryManifests, ...collectionViewManifests];

View File

@@ -0,0 +1,21 @@
import { umbExtensionsRegistry } from '../../registry.js';
import { UmbRepositoryBase, type UmbCollectionRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbExtensionCollectionRepository extends UmbRepositoryBase implements UmbCollectionRepository {
constructor(host: UmbControllerHost) {
super(host);
}
async requestCollection(filter: any) {
const extensions = umbExtensionsRegistry.getExtensions();
const total = extensions.length;
const items = extensions.slice(filter.skip, filter.skip + filter.take);
const data = { items, total };
return { data };
}
destroy(): void {}
}
export default UmbExtensionCollectionRepository;

View File

@@ -0,0 +1,2 @@
export { UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS } from './manifests.js';
export { UmbExtensionCollectionRepository } from './extension-collection.repository.js';

View File

@@ -0,0 +1,13 @@
import { UmbExtensionCollectionRepository } from './extension-collection.repository.js';
import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS = 'Umb.Repository.ExtensionCollection';
const repository: ManifestRepository = {
type: 'repository',
alias: UMB_EXTENSION_COLLECTION_REPOSITORY_ALIAS,
name: 'Extension Collection Repository',
api: UmbExtensionCollectionRepository,
};
export const manifests = [repository];

View File

@@ -0,0 +1,50 @@
import { umbExtensionsRegistry, type ManifestTypes } from '../../index.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
@customElement('umb-extension-table-action-column-layout')
export class UmbExtensionTableActionColumnLayoutElement extends UmbLitElement {
@property({ attribute: false })
value!: ManifestTypes;
#modalContext?: UmbModalManagerContext;
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this.#modalContext = instance;
});
}
async #removeExtension() {
const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, {
data: {
headline: 'Unload extension',
confirmLabel: 'Unload',
content: html`<p>Are you sure you want to unload the extension <strong>${this.value.alias}</strong>?</p>`,
color: 'danger',
},
});
await modalContext?.onSubmit();
umbExtensionsRegistry.unregister(this.value.alias);
}
render() {
return html`
<uui-button label="Unload" color="danger" look="primary" @click=${this.#removeExtension}>
<uui-icon name="icon-trash"></uui-icon>
</uui-button>
`;
}
static styles = [UmbTextStyles];
}
declare global {
interface HTMLElementTagNameMap {
'umb-extension-table-action-column-layout': UmbExtensionTableActionColumnLayoutElement;
}
}

View File

@@ -0,0 +1,24 @@
import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection';
import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_EXTENSION_TABLE_COLLECTION_VIEW_ALIAS = 'Umb.CollectionView.Extension.Table';
const tableCollectionView: ManifestCollectionView = {
type: 'collectionView',
alias: UMB_EXTENSION_TABLE_COLLECTION_VIEW_ALIAS,
name: 'Extension Table Collection View',
js: () => import('./table/extension-table-collection-view.element.js'),
meta: {
label: 'Table',
icon: 'icon-list',
pathName: 'table',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: 'Umb.Collection.Extension',
},
],
};
export const manifests = [tableCollectionView];

View File

@@ -0,0 +1,115 @@
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import { UMB_DEFAULT_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import type { UmbTableColumn, UmbTableConfig, UmbTableItem } from '@umbraco-cms/backoffice/components';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
import '../extension-table-action-column-layout.element.js';
@customElement('umb-extension-table-collection-view')
export class UmbExtensionTableCollectionViewElement extends UmbLitElement {
@state()
private _tableConfig: UmbTableConfig = {
allowSelection: false,
};
@state()
private _tableColumns: Array<UmbTableColumn> = [
{
name: 'Type',
alias: 'extensionType',
},
{
name: 'Name',
alias: 'extensionName',
},
{
name: 'Alias',
alias: 'extensionAlias',
},
{
name: 'Weight',
alias: 'extensionWeight',
},
{
name: '',
alias: 'extensionAction',
elementName: 'umb-extension-table-action-column-layout',
},
];
@state()
private _tableItems: Array<UmbTableItem> = [];
#collectionContext?: UmbDefaultCollectionContext<ManifestBase>;
constructor() {
super();
this.consumeContext(UMB_DEFAULT_COLLECTION_CONTEXT, (instance) => {
this.#collectionContext = instance;
this.#observeCollectionItems();
});
}
#observeCollectionItems() {
if (!this.#collectionContext) return;
this.observe(this.#collectionContext.items, (items) => this.#createTableItems(items), 'umbCollectionItemsObserver');
}
#createTableItems(extensions: Array<ManifestBase>) {
this._tableItems = extensions.map((extension) => {
return {
id: extension.alias,
data: [
{
columnAlias: 'extensionType',
value: extension.type,
},
{
columnAlias: 'extensionName',
value: extension.name,
},
{
columnAlias: 'extensionAlias',
value: extension.alias,
},
{
columnAlias: 'extensionWeight',
value: extension.weight,
},
{
columnAlias: 'extensionAction',
value: extension,
},
],
};
});
}
render() {
return html`
<umb-table .config=${this._tableConfig} .columns=${this._tableColumns} .items=${this._tableItems}></umb-table>
`;
}
static styles = [
UmbTextStyles,
css`
:host {
display: flex;
flex-direction: column;
}
`,
];
}
export default UmbExtensionTableCollectionViewElement;
declare global {
interface HTMLElementTagNameMap {
'umb-extension-table-collection-view': UmbExtensionTableCollectionViewElement;
}
}

View File

@@ -1,5 +1,6 @@
import { manifests as conditionManifests } from './conditions/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
import { manifests as collectionManifests } from './collection/manifests.js';
export const manifests = [...conditionManifests, ...menuItemManifests, ...workspaceManifests];
export const manifests = [...conditionManifests, ...menuItemManifests, ...workspaceManifests, ...collectionManifests];

View File

@@ -1,107 +1,17 @@
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { map } from '@umbraco-cms/backoffice/external/rxjs';
import type { ManifestTypes} from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_EXTENSION_COLLECTION_ALIAS } from '../collection/manifests.js';
import { UMB_EXTENSION_ROOT_WORKSPACE_ALIAS } from './manifests.js';
import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
@customElement('umb-extension-root-workspace')
export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
@state()
private _extensions?: Array<ManifestTypes> = undefined;
private _modalContext?: UmbModalManagerContext;
connectedCallback(): void {
super.connectedCallback();
this._observeExtensions();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this._modalContext = instance;
});
}
private _observeExtensions() {
this.observe(
umbExtensionsRegistry.extensions.pipe(
map((exts) =>
exts.sort((a, b) => {
// If type is the same, sort by weight
if (a.type === b.type) {
return (b.weight || 0) - (a.weight || 0);
}
// Otherwise sort by type
return a.type.localeCompare(b.type);
}),
),
),
(extensions) => {
this._extensions = extensions || undefined;
},
'_observeExtensionRegistry',
);
}
async #removeExtension(extension: ManifestTypes) {
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
data: {
headline: 'Unload extension',
confirmLabel: 'Unload',
content: html`<p>Are you sure you want to unload the extension <strong>${extension.alias}</strong>?</p>`,
color: 'danger',
},
});
await modalContext?.onSubmit();
umbExtensionsRegistry.unregister(extension.alias);
}
render() {
return html`
<umb-workspace-editor headline="Extensions" alias="Umb.Workspace.ExtensionRoot" .enforceNoFooter=${true}>
<uui-box>
<uui-table>
<uui-table-head>
<uui-table-head-cell>Type</uui-table-head-cell>
<uui-table-head-cell>Name</uui-table-head-cell>
<uui-table-head-cell>Alias</uui-table-head-cell>
<uui-table-head-cell>Weight</uui-table-head-cell>
<uui-table-head-cell>Actions</uui-table-head-cell>
</uui-table-head>
${this._extensions?.map(
(extension) => html`
<uui-table-row>
<uui-table-cell>${extension.type}</uui-table-cell>
<uui-table-cell> ${extension.name} </uui-table-cell>
<uui-table-cell>${extension.alias}</uui-table-cell>
<uui-table-cell>${extension.weight ? extension.weight : ''} </uui-table-cell>
<uui-table-cell>
<uui-button
label="Unload"
color="danger"
look="primary"
@click=${() => this.#removeExtension(extension)}>
<uui-icon name="icon-trash"></uui-icon>
</uui-button>
</uui-table-cell>
</uui-table-row>
`,
)}
</uui-table>
</uui-box>
<umb-workspace-editor headline="Extensions" alias=${UMB_EXTENSION_ROOT_WORKSPACE_ALIAS} .enforceNoFooter=${true}>
<umb-collection alias=${UMB_EXTENSION_COLLECTION_ALIAS}></umb-collection>
</umb-workspace-editor>
`;
}
static styles = [
css`
uui-box {
margin: var(--uui-size-layout-1);
}
`,
];
}
export default UmbExtensionRootWorkspaceElement;

View File

@@ -4,6 +4,8 @@ import type {
ManifestWorkspaceView,
} from '@umbraco-cms/backoffice/extension-registry';
export const UMB_EXTENSION_ROOT_WORKSPACE_ALIAS = 'Umb.Workspace.ExtensionRoot';
const workspace: ManifestWorkspace = {
type: 'workspace',
alias: 'Umb.Workspace.ExtensionRoot',