diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/entity-create-option-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/entity-create-option-action.element.ts new file mode 100644 index 0000000000..5c926bbe3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/entity-create-option-action.element.ts @@ -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 = UmbEntityCreateOptionAction, + > + 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; + + 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` + + ${this.manifest?.meta.icon + ? html`` + : nothing} + + `; + } +} + +export { UmbEntityCreateOptionActionDefaultElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbEntityCreateOptionActionDefaultElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/index.ts new file mode 100644 index 0000000000..d4702960d5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/index.ts @@ -0,0 +1 @@ +export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/manifests.ts new file mode 100644 index 0000000000..b1b7de24bf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/manifests.ts @@ -0,0 +1,20 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + 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', + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/types.ts new file mode 100644 index 0000000000..465664c91c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/default/types.ts @@ -0,0 +1,44 @@ +import type { + ManifestEntityCreateOptionAction, + MetaEntityCreateOptionAction, +} from '../entity-create-option-action.extension.js'; + +export interface ManifestEntityCreateOptionActionDefaultKind + extends ManifestEntityCreateOptionAction { + 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; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-base.ts new file mode 100644 index 0000000000..c8daf62492 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-base.ts @@ -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 + extends UmbActionBase> + implements UmbEntityCreateOptionAction +{ + /** + * 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 { + return Promise.resolve(undefined); + } + + /** + * By specifying the `execute` method, the action will act as a button. + * @abstract + * @returns {Promise} + */ + public execute(): Promise { + return Promise.resolve(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-element.interface.ts new file mode 100644 index 0000000000..6f2c9ab833 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-element.interface.ts @@ -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 {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-list.element.ts new file mode 100644 index 0000000000..082816e24f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action-list.element.ts @@ -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) => + extension.forEntityTypes.includes(value); + //this.requestUpdate('_filter', oldValue); + } + + @state() + _filter?: (extension: ManifestEntityCreateOptionAction) => 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> = {}; + + @state() + _apiArgs?: UmbApiConstructorArgumentsMethodType< + ManifestEntityCreateOptionAction, + [UmbEntityCreateOptionActionArgs] + >; + + #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) => { + return [{ entityType: this._props.entityType!, unique: this._props.unique!, meta: manifest.meta }]; + }; + } + + #hasRenderedOnce?: boolean; + override render() { + return this._filter + ? html` + { + 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; + }}> + ` + : ''; + } + + static override styles = [ + css` + :host { + --uui-menu-item-flat-structure: 1; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbEntityCreateOptionActionListElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.extension.ts new file mode 100644 index 0000000000..2835753767 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.extension.ts @@ -0,0 +1,13 @@ +import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestEntityCreateOptionAction< + MetaType extends MetaEntityCreateOptionAction = MetaEntityCreateOptionAction, +> extends ManifestElementAndApi, + ManifestWithDynamicConditions { + type: 'entityCreateOptionAction'; + forEntityTypes: Array; + meta: MetaType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface MetaEntityCreateOptionAction {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.interface.ts new file mode 100644 index 0000000000..0956868cb7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/entity-create-option-action.interface.ts @@ -0,0 +1,17 @@ +import type { UmbEntityCreateOptionActionArgs } from './types.js'; +import type { UmbAction } from '@umbraco-cms/backoffice/action'; + +export interface UmbEntityCreateOptionAction + extends UmbAction> { + /** + * The href location, the action will act as a link. + * @returns {Promise} + */ + getHref(): Promise; + + /** + * The `execute` method, the action will act as a button. + * @returns {Promise} + */ + execute(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/index.ts new file mode 100644 index 0000000000..c3a44c8f72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/index.ts @@ -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'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/manifests.ts new file mode 100644 index 0000000000..84f3b7f88d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/manifests.ts @@ -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 = [...defaultEntityActionManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/types.ts new file mode 100644 index 0000000000..ca6db2fc24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/create-option-action/types.ts @@ -0,0 +1,5 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbEntityCreateOptionActionArgs extends UmbEntityModel { + meta: MetaArgsType; +}