wip add entity-create-option-action extension point

This commit is contained in:
Mads Rasmussen
2024-10-28 14:56:14 +01:00
committed by Niels Lyngsø
parent 59790431c4
commit 42fe0c3819
12 changed files with 337 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
import type { UmbEntityCreateOptionAction } from '../entity-create-option-action.interface.js';
import type { UmbEntityCreateOptionActionElement } from '../entity-create-option-action-element.interface.js';
import type { ManifestEntityCreateOptionAction } from '../entity-create-option-action.extension.js';
import type { MetaEntityCreateOptionActionDefaultKind } from './types.js';
import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
import { html, nothing, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UUIMenuItemEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
const elementName = 'umb-entity-create-option-action';
@customElement(elementName)
export class UmbEntityCreateOptionActionDefaultElement<
MetaType extends MetaEntityCreateOptionActionDefaultKind = MetaEntityCreateOptionActionDefaultKind,
ApiType extends UmbEntityCreateOptionAction<MetaType> = UmbEntityCreateOptionAction<MetaType>,
>
extends UmbLitElement
implements UmbEntityCreateOptionActionElement
{
#api?: ApiType;
// TODO: Do these need to be properties? [NL]
@property({ type: String })
entityType?: string | null;
// TODO: Do these need to be properties? [NL]
@property({ type: String })
public unique?: string | null;
@property({ attribute: false })
public manifest?: ManifestEntityCreateOptionAction<MetaType>;
public set api(api: ApiType | undefined) {
this.#api = api;
// TODO: Fix so when we use a HREF it does not refresh the page?
this.#api?.getHref?.().then((href) => {
this._href = href;
// TODO: Do we need to update the component here? [NL]
});
}
@state()
_href?: string;
override async focus() {
await this.updateComplete;
this.shadowRoot?.querySelector('uui-menu-item')?.focus();
}
async #onClickLabel(event: UUIMenuItemEvent) {
if (!this._href) {
event.stopPropagation();
await this.#api?.execute();
}
this.dispatchEvent(new UmbActionExecutedEvent());
}
// TODO: we need to stop the regular click event from bubbling up to the table so it doesn't select the row.
// This should probably be handled in the UUI Menu item component. so we don't dispatch a label-click event and click event at the same time.
#onClick(event: PointerEvent) {
event.stopPropagation();
}
override render() {
const label = this.manifest?.meta.label ? this.localize.string(this.manifest.meta.label) : this.manifest?.name;
return html`
<uui-menu-item
label=${ifDefined(this.manifest?.meta.additionalOptions ? label + '...' : label)}
href=${ifDefined(this._href)}
@click-label=${this.#onClickLabel}
@click=${this.#onClick}>
${this.manifest?.meta.icon
? html`<umb-icon slot="icon" name="${this.manifest?.meta.icon}"></umb-icon>`
: nothing}
</uui-menu-item>
`;
}
}
export { UmbEntityCreateOptionActionDefaultElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbEntityCreateOptionActionDefaultElement;
}
}

View File

@@ -0,0 +1 @@
export * from './types.js';

View File

@@ -0,0 +1,20 @@
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
{
type: 'kind',
alias: 'Umb.Kind.EntityCreateOptionAction.Default',
matchKind: 'default',
matchType: 'entityCreateOptionAction',
manifest: {
type: 'entityCreateOptionAction',
kind: 'default',
weight: 1000,
element: () => import('./entity-create-option-action.element.js'),
meta: {
icon: '',
label: 'Default Entity Create Option Action',
},
},
},
];

View File

@@ -0,0 +1,44 @@
import type {
ManifestEntityCreateOptionAction,
MetaEntityCreateOptionAction,
} from '../entity-create-option-action.extension.js';
export interface ManifestEntityCreateOptionActionDefaultKind
extends ManifestEntityCreateOptionAction<MetaEntityCreateOptionActionDefaultKind> {
type: 'entityCreateOptionAction';
kind: 'default';
}
export interface MetaEntityCreateOptionActionDefaultKind extends MetaEntityCreateOptionAction {
/**
* An icon to represent the action to be performed
* @examples [
* "icon-box",
* "icon-grid"
* ]
*/
icon: string;
/**
* The friendly name of the action to perform
* @examples [
* "Create",
* "Create Content Template"
* ]
*/
label: string;
/**
* The action requires additional input from the user.
* A dialog will prompt the user for more information or to make a choice.
* @type {boolean}
* @memberof MetaEntityCreateOptionActionDefaultKind
*/
additionalOptions?: boolean;
}
declare global {
interface UmbExtensionManifestMap {
umbDefaultEntityCreateOptionActionKind: ManifestEntityCreateOptionActionDefaultKind;
}
}

View File

@@ -0,0 +1,27 @@
import type { UmbEntityCreateOptionActionArgs } from './types.js';
import type { UmbEntityCreateOptionAction } from './entity-create-option-action.interface.js';
import { UmbActionBase } from '@umbraco-cms/backoffice/action';
export abstract class UmbEntityCreateOptionActionBase<ArgsMetaType>
extends UmbActionBase<UmbEntityCreateOptionActionArgs<ArgsMetaType>>
implements UmbEntityCreateOptionAction<ArgsMetaType>
{
/**
* By specifying the href, the action will act as a link.
* The `execute` method will not be called.
* @abstract
* @returns {string | undefined}
*/
public getHref(): Promise<string | undefined> {
return Promise.resolve(undefined);
}
/**
* By specifying the `execute` method, the action will act as a button.
* @abstract
* @returns {Promise<void>}
*/
public execute(): Promise<void> {
return Promise.resolve();
}
}

View File

@@ -0,0 +1,4 @@
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface UmbEntityCreateOptionActionElement extends UmbControllerHostElement {}

View File

@@ -0,0 +1,108 @@
import type { UmbEntityCreateOptionActionArgs } from './types.js';
import type {
ManifestEntityCreateOptionAction,
MetaEntityCreateOptionAction,
} from './entity-create-option-action.extension.js';
import { UmbEntityContext } from '@umbraco-cms/backoffice/entity';
import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbApiConstructorArgumentsMethodType } from '@umbraco-cms/backoffice/extension-api';
const elementName = 'umb-entity-create-option-action-list';
@customElement(elementName)
export class UmbEntityCreateOptionActionListElement extends UmbLitElement {
@property({ type: String, attribute: 'entity-type' })
public get entityType(): string | undefined {
return this._props.entityType;
}
public set entityType(value: string | undefined) {
if (value === undefined || value === this._props.entityType) return;
this._props.entityType = value;
this.#generateApiArgs();
this.requestUpdate('_props');
// Update filter:
//const oldValue = this._filter;
this._filter = (extension: ManifestEntityCreateOptionAction<MetaEntityCreateOptionAction>) =>
extension.forEntityTypes.includes(value);
//this.requestUpdate('_filter', oldValue);
}
@state()
_filter?: (extension: ManifestEntityCreateOptionAction<MetaEntityCreateOptionAction>) => boolean;
@property({ type: String })
public get unique(): string | null | undefined {
return this._props.unique;
}
public set unique(value: string | null | undefined) {
if (value === this._props.unique) return;
this._props.unique = value;
this.#generateApiArgs();
this.requestUpdate('_props');
}
@state()
_props: Partial<UmbEntityCreateOptionActionArgs<unknown>> = {};
@state()
_apiArgs?: UmbApiConstructorArgumentsMethodType<
ManifestEntityCreateOptionAction<MetaEntityCreateOptionAction>,
[UmbEntityCreateOptionActionArgs<MetaEntityCreateOptionAction>]
>;
#entityContext = new UmbEntityContext(this);
#generateApiArgs() {
if (!this._props.entityType || this._props.unique === undefined) return;
this.#entityContext.setEntityType(this._props.entityType);
this.#entityContext.setUnique(this._props.unique);
this.#hasRenderedOnce = false;
this._apiArgs = (manifest: ManifestEntityCreateOptionAction<MetaEntityCreateOptionAction>) => {
return [{ entityType: this._props.entityType!, unique: this._props.unique!, meta: manifest.meta }];
};
}
#hasRenderedOnce?: boolean;
override render() {
return this._filter
? html`
<umb-extension-with-api-slot
type="entityAction"
.filter=${this._filter}
.elementProps=${this._props}
.apiArgs=${this._apiArgs}
.renderMethod=${(ext: any, i: number) => {
if (!this.#hasRenderedOnce && i === 0) {
// TODO: Replace this block:
ext.component?.updateComplete.then(async () => {
const menuitem = ext.component?.shadowRoot?.querySelector('uui-menu-item');
menuitem?.updateComplete.then(async () => {
menuitem?.shadowRoot?.querySelector('#label-button')?.focus?.();
});
});
// end of block, with this, when this PR is part of UI Lib: https://github.com/umbraco/Umbraco.UI/pull/789
// ext.component?.focus();
this.#hasRenderedOnce = true;
}
return ext.component;
}}></umb-extension-with-api-slot>
`
: '';
}
static override styles = [
css`
:host {
--uui-menu-item-flat-structure: 1;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbEntityCreateOptionActionListElement;
}
}

View File

@@ -0,0 +1,13 @@
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestEntityCreateOptionAction<
MetaType extends MetaEntityCreateOptionAction = MetaEntityCreateOptionAction,
> extends ManifestElementAndApi,
ManifestWithDynamicConditions<UmbExtensionConditionConfig> {
type: 'entityCreateOptionAction';
forEntityTypes: Array<string>;
meta: MetaType;
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface MetaEntityCreateOptionAction {}

View File

@@ -0,0 +1,17 @@
import type { UmbEntityCreateOptionActionArgs } from './types.js';
import type { UmbAction } from '@umbraco-cms/backoffice/action';
export interface UmbEntityCreateOptionAction<ArgsMetaType>
extends UmbAction<UmbEntityCreateOptionActionArgs<ArgsMetaType>> {
/**
* The href location, the action will act as a link.
* @returns {Promise<string | undefined>}
*/
getHref(): Promise<string | undefined>;
/**
* The `execute` method, the action will act as a button.
* @returns {Promise<void>}
*/
execute(): Promise<void>;
}

View File

@@ -0,0 +1,7 @@
export * from './default/index.js';
export * from './entity-create-option-action-base.js';
export * from './entity-create-option-action-list.element.js';
export * from './entity-create-option-action.extension.js';
export * from './entity-create-option-action.interface.js';
export * from './types.js';
export type * from './entity-create-option-action-element.interface.js';

View File

@@ -0,0 +1,4 @@
import { manifests as defaultEntityActionManifests } from './default/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [...defaultEntityActionManifests];

View File

@@ -0,0 +1,5 @@
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
export interface UmbEntityCreateOptionActionArgs<MetaArgsType> extends UmbEntityModel {
meta: MetaArgsType;
}