Skip to content

Commit

Permalink
Merge pull request #2509 from umbraco/v15/hotfix/create-options-exten…
Browse files Browse the repository at this point in the history
…sion-point

Hotfix: Entity Create Option Action Extension Point
  • Loading branch information
nielslyngsoe authored Nov 4, 2024
2 parents a3d7906 + 2613f3e commit 404ee93
Show file tree
Hide file tree
Showing 50 changed files with 2,378 additions and 1,819 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"./document": "./dist-cms/packages/documents/documents/index.js",
"./entity-action": "./dist-cms/packages/core/entity-action/index.js",
"./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js",
"./entity-create-option-action": "./dist-cms/packages/core/entity-create-option-action/index.js",
"./entity": "./dist-cms/packages/core/entity/index.js",
"./event": "./dist-cms/packages/core/event/index.js",
"./extension-registry": "./dist-cms/packages/core/extension-registry/index.js",
Expand Down
3,160 changes: 1,625 additions & 1,535 deletions src/assets/lang/ar.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/json-schema/all-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import '@umbraco-cms/backoffice/document-type';
import '@umbraco-cms/backoffice/document';
import '@umbraco-cms/backoffice/entity-action';
import '@umbraco-cms/backoffice/entity-bulk-action';
import '@umbraco-cms/backoffice/entity-create-option-action';
import '@umbraco-cms/backoffice/entity';
import '@umbraco-cms/backoffice/event';
import '@umbraco-cms/backoffice/extension-registry';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';

type ManifestType = ManifestEntityCreateOptionAction;

const elementName = 'umb-collection-create-action-button';
@customElement(elementName)
export class UmbCollectionCreateActionButtonElement extends UmbLitElement {
@state()
private _popoverOpen = false;

@state()
private _multipleOptions = false;

@state()
private _apiControllers: Array<UmbExtensionApiInitializer<ManifestType>> = [];

@state()
_hrefList: Array<string | undefined> = [];

#createLabel = this.localize.term('general_create');
#entityContext?: typeof UMB_ENTITY_CONTEXT.TYPE;

#onPopoverToggle(event: PointerEvent) {
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._popoverOpen = event.newState === 'open';
}

async #onClick(event: Event, controller: UmbExtensionApiInitializer<ManifestType>, href?: string) {
event.stopPropagation();

// skip if href is defined
if (href) {
return;
}

if (!controller.api) throw new Error('No API found');
await controller.api.execute();
}

constructor() {
super();

this.consumeContext(UMB_ENTITY_CONTEXT, (context) => {
this.#entityContext = context;
this.#initApi();
});
}

#initApi() {
if (!this.#entityContext) return;

const entityType = this.#entityContext.getEntityType();
if (!entityType) throw new Error('No entityType found');

const unique = this.#entityContext.getUnique();
if (unique === undefined) throw new Error('No unique found');

new UmbExtensionsApiInitializer(
this,
umbExtensionsRegistry,
'entityCreateOptionAction',
(manifest: ManifestType) => {
return [{ entityType, unique, meta: manifest.meta }];
},
(manifest: ManifestType) => manifest.forEntityTypes.includes(entityType),
async (controllers) => {
this._apiControllers = controllers as unknown as Array<UmbExtensionApiInitializer<ManifestType>>;
this._multipleOptions = controllers.length > 1;
const hrefPromises = this._apiControllers.map((controller) => controller.api?.getHref());
this._hrefList = await Promise.all(hrefPromises);
},
);
}

#getTarget(href?: string) {
if (href && href.startsWith('http')) {
return '_blank';
}

return '_self';
}

override render() {
return this._multipleOptions ? this.#renderMultiOptionAction() : this.#renderSingleOptionAction();
}

#renderSingleOptionAction() {
return html` <uui-button
label=${this.#createLabel}
color="default"
look="outline"
@click=${(event: Event) => this.#onClick(event, this._apiControllers[0])}></uui-button>`;
}

#renderMultiOptionAction() {
return html`
<uui-button
popovertarget="collection-action-menu-popover"
label=${this.#createLabel}
color="default"
look="outline">
${this.#createLabel}
<uui-symbol-expand .open=${this._popoverOpen}></uui-symbol-expand>
</uui-button>
${this.#renderDropdown()}
`;
}

#renderDropdown() {
return html`
<uui-popover-container
id="collection-action-menu-popover"
placement="bottom-start"
@toggle=${this.#onPopoverToggle}>
<umb-popover-layout>
<uui-scroll-container>
${this._apiControllers.map((controller, index) => this.#renderMenuItem(controller, index))}
</uui-scroll-container>
</umb-popover-layout>
</uui-popover-container>
`;
}

#renderMenuItem(controller: UmbExtensionApiInitializer<ManifestType>, index: number) {
const manifest = controller.manifest;
if (!manifest) throw new Error('No manifest found');

const label = manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name;
const href = this._hrefList[index];

return html`
<uui-menu-item
label=${label}
@click=${(event: Event) => this.#onClick(event, controller, href)}
href=${ifDefined(href)}
target=${this.#getTarget(href)}>
<umb-icon slot="icon" .name=${manifest.meta.icon}></umb-icon>
</uui-menu-item>
`;
}
}

export { UmbCollectionCreateActionButtonElement as element };

declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbCollectionCreateActionButtonElement;
}
}
19 changes: 19 additions & 0 deletions src/packages/core/collection/action/create/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';

export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
{
type: 'kind',
alias: 'Umb.Kind.CollectionAction.Create',
matchKind: 'create',
matchType: 'collectionAction',
manifest: {
type: 'collectionAction',
kind: 'create',
element: () => import('./collection-create-action.element.js'),
weight: 1200,
meta: {
label: '#actions_create',
},
},
},
];
12 changes: 12 additions & 0 deletions src/packages/core/collection/action/create/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ManifestCollectionAction } from '../../extensions/index.js';

export interface ManifestCollectionActionCreateKind extends ManifestCollectionAction {
type: 'collectionAction';
kind: 'create';
}

declare global {
interface UmbExtensionManifestMap {
umbCollectionActionCreateKind: ManifestCollectionActionCreateKind;
}
}
4 changes: 4 additions & 0 deletions src/packages/core/collection/action/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { manifests as createManifests } from './create/manifests.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';

export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [...createManifests];
2 changes: 2 additions & 0 deletions src/packages/core/collection/manifests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { UmbExtensionManifestKind } from '../extension-registry/registry.js';
import { manifests as actionManifests } from './action/manifests.js';
import { manifests as conditionManifests } from './conditions/manifests.js';
import { manifests as workspaceViewManifests } from './workspace-view/manifests.js';

export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [
...actionManifests,
...workspaceViewManifests,
...conditionManifests,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';

export const manifest: UmbExtensionManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityAction.Create',
matchKind: 'create',
matchType: 'entityAction',
manifest: {
...UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST.manifest,
type: 'entityAction',
kind: 'create',
api: () => import('./create.action.js'),
weight: 1200,
forEntityTypes: [],
meta: {
icon: 'icon-add',
label: '#actions_create',
additionalOptions: true,
},
},
};
59 changes: 59 additions & 0 deletions src/packages/core/entity-action/common/create/create.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { UmbEntityActionBase } from '../../entity-action-base.js';
import type { UmbEntityActionArgs } from '../../types.js';
import type { MetaEntityActionCreateKind } from './types.js';
import { UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL } from './modal/index.js';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { createExtensionApi, UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action';

export class UmbCreateEntityAction extends UmbEntityActionBase<MetaEntityActionCreateKind> {
#hasSingleOption = true;
#singleActionOptionManifest?: ManifestEntityCreateOptionAction;

constructor(host: UmbControllerHost, args: UmbEntityActionArgs<MetaEntityActionCreateKind>) {
super(host, args);

new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'entityCreateOptionAction',
(ext) => ext.forEntityTypes.includes(this.args.entityType),
async (actionOptions) => {
this.#hasSingleOption = actionOptions.length === 1;
this.#singleActionOptionManifest = this.#hasSingleOption
? (actionOptions[0].manifest as unknown as ManifestEntityCreateOptionAction)
: undefined;
},
'umbEntityActionsObserver',
);
}

override async execute() {
if (this.#hasSingleOption) {
if (!this.#singleActionOptionManifest) throw new Error('No first action manifest found');

const api = await createExtensionApi(this, this.#singleActionOptionManifest, [
{ unique: this.args.unique, entityType: this.args.entityType, meta: this.#singleActionOptionManifest.meta },
]);

if (!api) throw new Error(`Could not create api for ${this.#singleActionOptionManifest.alias}`);

await api.execute();
return;
}

const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL, {
data: {
unique: this.args.unique,
entityType: this.args.entityType,
},
});

await modalContext.onSubmit();
}
}

export { UmbCreateEntityAction as api };
1 change: 1 addition & 0 deletions src/packages/core/entity-action/common/create/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UmbCreateEntityAction } from './create.action.js';
4 changes: 4 additions & 0 deletions src/packages/core/entity-action/common/create/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { manifest as createKindManifest } from './create.action.kind.js';
import { manifests as modalManifests } from './modal/manifests.js';

export const manifests = [createKindManifest, ...modalManifests];
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS = 'Umb.Modal.Entity.CreateOptionActionList';
Loading

0 comments on commit 404ee93

Please sign in to comment.