Skip to content

Commit

Permalink
Merge pull request #1311 from umbraco/feature/workspace-action-menu
Browse files Browse the repository at this point in the history
Feature/workspace action menu
  • Loading branch information
iOvergaard authored Feb 29, 2024
2 parents d34b893 + a685b4c commit fea8f40
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 111 deletions.
61 changes: 32 additions & 29 deletions src/packages/core/entity-action/entity-action.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,61 @@ import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';

@customElement('umb-entity-action')
export class UmbEntityActionElement extends UmbLitElement {
private _entityType?: string | null;
#entityType?: string | null;

@property({ type: String })
public get entityType() {
return this._entityType;
}
public set entityType(value: string | undefined | null) {
const oldValue = this._entityType;
this._entityType = value;
if (oldValue !== this._entityType) {
const oldValue = this.#entityType;
this.#entityType = value;
if (oldValue !== this.#entityType) {
this.#createApi();
this.requestUpdate('entityType', oldValue);
}
}
public get entityType() {
return this.#entityType;
}

#unique?: string | null;

private _unique?: string | null;
@property({ type: String })
public get unique() {
return this._unique;
}
public set unique(value: string | undefined | null) {
const oldValue = this._unique;
this._unique = value;
if (oldValue !== this._unique) {
const oldValue = this.#unique;
this.#unique = value;
if (oldValue !== this.#unique) {
this.#createApi();
this.requestUpdate('unique', oldValue);
}
}
public get unique() {
return this.#unique;
}

#manifest?: ManifestEntityAction;

private _manifest?: ManifestEntityAction;
@property({ type: Object, attribute: false })
public get manifest() {
return this._manifest;
}
public set manifest(value: ManifestEntityAction | undefined) {
if (!value) return;
const oldValue = this._manifest;
this._manifest = value;
if (oldValue !== this._manifest) {
const oldValue = this.#manifest;
this.#manifest = value;
if (oldValue !== this.#manifest) {
this.#createApi();
this.requestUpdate('manifest', oldValue);
}
}
public get manifest() {
return this.#manifest;
}

async #createApi() {
// only create the api if we have all the required properties
if (!this._manifest) return;
if (this._unique === undefined) return;
if (!this._entityType) return;
if (!this.#manifest) return;
if (this.#unique === undefined) return;
if (!this.#entityType) return;

this.#api = await createExtensionApi(this._manifest, [
this.#api = await createExtensionApi(this.#manifest, [
this,
this._manifest.meta.repositoryAlias,
this.#manifest.meta.repositoryAlias,
this.unique,
this.entityType,
]);
Expand Down Expand Up @@ -89,12 +92,12 @@ export class UmbEntityActionElement extends UmbLitElement {
render() {
return html`
<uui-menu-item
label=${ifDefined(this._manifest?.meta.label)}
label=${ifDefined(this.manifest?.meta.label)}
href=${ifDefined(this._href)}
@click-label=${this.#onClickLabel}
@click=${this.#onClick}>
${this._manifest?.meta.icon
? html`<uui-icon slot="icon" name="${this._manifest?.meta.icon}"></uui-icon>`
${this.manifest?.meta.icon
? html`<uui-icon slot="icon" name="${this.manifest?.meta.icon}"></uui-icon>`
: nothing}
</uui-menu-item>
`;
Expand Down
3 changes: 3 additions & 0 deletions src/packages/core/extension-registry/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { ManifestTreeItem } from './tree-item.model.js';
import type { ManifestUserProfileApp } from './user-profile-app.model.js';
import type { ManifestWorkspace } from './workspace.model.js';
import type { ManifestWorkspaceAction } from './workspace-action.model.js';
import type { ManifestWorkspaceActionMenuItem } from './workspace-action-menu-item.model.js';
import type { ManifestWorkspaceContext } from './workspace-context.model.js';
import type { ManifestWorkspaceFooterApp } from './workspace-footer-app.model.js';
import type { ManifestWorkspaceView } from './workspace-view.model.js';
Expand Down Expand Up @@ -75,6 +76,7 @@ export type * from './user-granular-permission.model.js';
export type * from './entity-user-permission.model.js';
export type * from './user-profile-app.model.js';
export type * from './workspace-action.model.js';
export type * from './workspace-action-menu-item.model.js';
export type * from './workspace-context.model.js';
export type * from './workspace-footer-app.model.js';
export type * from './workspace-view.model.js';
Expand Down Expand Up @@ -123,6 +125,7 @@ export type ManifestTypes =
| ManifestUserProfileApp
| ManifestWorkspace
| ManifestWorkspaceAction
| ManifestWorkspaceActionMenuItem
| ManifestWorkspaceContext
| ManifestWorkspaceFooterApp
| ManifestWorkspaceView
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { ConditionTypes } from '../conditions/types.js';
import type { MetaEntityAction } from './entity-action.model.js';
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
import type { UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace';

export interface ManifestWorkspaceActionMenuItem
extends ManifestElementAndApi<HTMLElement, UmbWorkspaceAction>,
ManifestWithDynamicConditions<ConditionTypes> {
type: 'workspaceActionMenuItem';
meta: MetaWorkspaceActionMenuItem;
}

export interface MetaWorkspaceActionMenuItem extends MetaEntityAction {
/**
* Define which workspace actions this menu item should be shown for.
* @examples [
* ['Umb.WorkspaceAction.Document.Save', 'Umb.WorkspaceAction.Document.SaveAndPublish'],
* "Umb.WorkspaceAction.Document.Save"
* ]
* @required
*/
workspaceActions: string | string[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,20 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-property-action-menu')
export class UmbPropertyActionMenuElement extends UmbLitElement {
#actionsInitializer?: UmbExtensionsElementInitializer<ManifestTypes, 'propertyAction'>;
#value: unknown;
#propertyEditorUiAlias = '';

@property({ attribute: false })
public set value(value: unknown) {
this._value = value;
this.#value = value;
if (this.#actionsInitializer) {
this.#actionsInitializer.properties = { value };
}
}
public get value(): unknown {
return this._value;
return this.#value;
}

private _value?: unknown;

#propertyEditorUiAlias = '';

@property()
set propertyEditorUiAlias(alias: string) {
this.#propertyEditorUiAlias = alias;
Expand All @@ -47,7 +45,7 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
}

@state()
private _actions: Array<UmbExtensionElementInitializer<ManifestPropertyAction, any>> = [];
private _actions: Array<UmbExtensionElementInitializer<ManifestPropertyAction, never>> = [];

render() {
return this._actions.length > 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './save/index.js';
export * from './workspace-action-base.js';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbSaveableWorkspaceContextInterface } from '../../../../workspace-context/saveable-workspace-context.interface.js';
import { UmbWorkspaceActionBase } from '../../workspace-action-base.js';
import { UmbWorkspaceActionBase } from '../workspace-action-base.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';

// TODO: add interface for repo/partial repo/save-repo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbWorkspaceContextInterface } from '../../workspace-context/index.js';
import { UMB_WORKSPACE_CONTEXT } from '../../workspace-context/index.js';
import type { UmbWorkspaceContextInterface } from '../../../workspace-context/index.js';
import { UMB_WORKSPACE_CONTEXT } from '../../../workspace-context/index.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './workspace-action-base.js';
export * from './workspace-action.element.js';
export * from './shared/index.js';
export * from './common/index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './workspace-action/index.js';
export * from './workspace-action-menu/index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './workspace-action-menu.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, property, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { ManifestTypes, ManifestWorkspaceActionMenuItem } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import type { UUIInterfaceColor, UUIInterfaceLook } from '@umbraco-cms/backoffice/external/uui';

@customElement('umb-workspace-action-menu')
export class UmbWorkspaceActionMenuElement extends UmbLitElement {
#workspaceContext?: typeof UMB_WORKSPACE_CONTEXT.TYPE;
#actionsInitializer?: UmbExtensionsElementInitializer<ManifestTypes, 'workspaceActionMenuItem'>;

/**
* The workspace actions to filter the available actions by.
* @example ['Umb.WorkspaceAction.Document.Save', 'Umb.WorkspaceAction.Document.SaveAndPublishNew']
*/
@property({ type: Array })
workspaceActions: Array<string> = [];

@property()
look: UUIInterfaceLook = 'secondary';

@property()
color: UUIInterfaceColor = 'default';

@state()
private _actions: Array<UmbExtensionElementInitializer<ManifestWorkspaceActionMenuItem, never>> = [];

@state()
_popoverOpen = false;

constructor() {
super();

this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => {
this.#workspaceContext = context;
this.#initialise();
});
}

#initialise() {
if (!this.#workspaceContext) throw new Error('No workspace context');

// If there are no workspace action aliases, then there is no need to initialize the actions.
if (!this.workspaceActions.length) return;

const unique = this.#workspaceContext.getUnique();
const entityType = this.#workspaceContext.getEntityType();

this.#actionsInitializer = new UmbExtensionsElementInitializer(
this,
umbExtensionsRegistry,
'workspaceActionMenuItem', // TODO: Stop using string for 'workspaceActionMenuItem', we need to start using Const.
(action) => {
const containsAlias = Array.isArray(action.meta.workspaceActions)
? action.meta.workspaceActions
: [action.meta.workspaceActions].some((alias) => this.workspaceActions.includes(alias));
const isValidEntityType = !action.meta.entityTypes.length || action.meta.entityTypes.includes(entityType);
return containsAlias && isValidEntityType;
},
(ctrls) => {
this._actions = ctrls;
},
'workspaceActionExtensionsInitializer',
'umb-entity-action',
);

this.#actionsInitializer.properties = { unique, entityType };
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
#onPopoverToggle(event: ToggleEvent) {
this._popoverOpen = event.newState === 'open';
}

render() {
return this._actions.length > 0
? html`
<uui-button
id="popover-trigger"
popovertarget="workspace-action-popover"
look="${this.look}"
color="${this.color}"
label=${this.localize.term('visuallyHiddenTexts_tabExpand')}
compact>
<uui-symbol-expand id="expand-symbol" .open=${this._popoverOpen}></uui-symbol-expand>
</uui-button>
<uui-popover-container
id="workspace-action-popover"
margin="5"
placement="top-end"
@toggle=${this.#onPopoverToggle}>
<umb-popover-layout>
<uui-scroll-container>
${repeat(
this._actions,
(action) => action.alias,
(action) => action.component,
)}
</uui-scroll-container>
</umb-popover-layout>
</uui-popover-container>
`
: nothing;
}

static styles: CSSResultGroup = [
UmbTextStyles,
css`
:host {
--uui-menu-item-flat-structure: 1;
}
#expand-symbol {
transform: rotate(-90deg);
}
#expand-symbol[open] {
transform: rotate(0deg);
}
#workspace-action-popover {
min-width: 200px;
}
#popover-trigger {
--uui-button-padding-top-factor: 0.5;
--uui-button-padding-bottom-factor: 0.1;
--uui-button-border-radius: 0;
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
'umb-workspace-action-menu': UmbWorkspaceActionMenuElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './workspace-action.element.js';
Loading

0 comments on commit fea8f40

Please sign in to comment.