diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/types.ts new file mode 100644 index 0000000000..450cb3273d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/types.ts @@ -0,0 +1,2 @@ +export type * from './workspace-action/types.js'; +export type * from './workspace-action-menu-item/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts index 8d73b1d2d2..f1994d1e8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/default/workspace-action-menu-item.element.ts @@ -1,4 +1,4 @@ -import type { UmbWorkspaceActionMenuItem } from '../index.js'; +import type { UmbWorkspaceActionMenuItem } from '../types.js'; import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/index.ts index 5be3d13fc0..3db891eb25 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/index.ts @@ -1,3 +1 @@ export * from './workspace-action-menu-item-base.controller.js'; -export type * from './types.js'; -export type * from './workspace-action-menu-item.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/types.ts index 7f3c638c81..13091c3311 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action-menu-item/types.ts @@ -1,3 +1,4 @@ +export type * from './workspace-action-menu-item.interface.js'; export interface UmbWorkspaceActionMenuItemArgs { meta: MetaArgsType; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/index.ts index 22e8c2d675..313328f9e7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/index.ts @@ -1 +1,2 @@ export * from './submit.action.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts index 20bd6d3e05..1cd6a6fefc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts @@ -1,30 +1,40 @@ +import type { MetaWorkspaceAction } from '../../../../types.js'; import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '../../../../contexts/tokens/index.js'; import type { UmbSubmittableWorkspaceContext } from '../../../../contexts/tokens/index.js'; -import type { UmbWorkspaceActionArgs } from '../../types.js'; import { UmbWorkspaceActionBase } from '../../workspace-action-base.controller.js'; +import type { UmbSubmitWorkspaceActionArgs } from './types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase { - #workspaceContext?: UmbSubmittableWorkspaceContext; +export class UmbSubmitWorkspaceAction< + ArgsMetaType extends MetaWorkspaceAction = MetaWorkspaceAction, + WorkspaceContextType extends UmbSubmittableWorkspaceContext = UmbSubmittableWorkspaceContext, +> extends UmbWorkspaceActionBase { + protected _retrieveWorkspaceContext: Promise; + protected _workspaceContext?: WorkspaceContextType; - constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) { + constructor(host: UmbControllerHost, args: UmbSubmitWorkspaceActionArgs) { super(host, args); - // TODO: Could we make change label depending on the state? - this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (context) => { - this.#workspaceContext = context; - this.#observeUnique(); - }); + // TODO: Could we make change label depending on the state? [NL] + this._retrieveWorkspaceContext = this.consumeContext( + args.workspaceContextToken ?? UMB_SUBMITTABLE_WORKSPACE_CONTEXT, + (context) => { + this._workspaceContext = context as WorkspaceContextType; + this.#observeUnique(); + this._gotWorkspaceContext(); + }, + ).asPromise(); } #observeUnique() { this.observe( - this.#workspaceContext?.unique, + this._workspaceContext?.unique, (unique) => { // We can't save if we don't have a unique if (unique === undefined) { this.disable(); } else { + // Dangerous, cause this could enable despite a class extension decided to disable it?. [NL] this.enable(); } }, @@ -32,9 +42,13 @@ export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase extends UmbWorkspaceActionArgs { + workspaceContextToken?: string | UmbContextToken; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/default.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/default.action.kind.ts deleted file mode 100644 index 8b21321659..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/default.action.kind.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; - -export const manifest: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.WorkspaceAction.Default', - matchKind: 'default', - matchType: 'workspaceAction', - manifest: { - type: 'workspaceAction', - kind: 'default', - weight: 1000, - element: () => import('./workspace-action.element.js'), - meta: { - icon: '', - label: '(Missing label in manifest)', - }, - }, -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/manifests.ts index 8d8ea584a1..8b30dea13f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/manifests.ts @@ -1,4 +1,19 @@ -import { manifest as defaultKindManifest } from './default.action.kind.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -export const manifests: Array = [defaultKindManifest]; +export const manifest: UmbExtensionManifestKind = { + type: 'kind', + alias: 'Umb.Kind.WorkspaceAction.Default', + matchKind: 'default', + matchType: 'workspaceAction', + manifest: { + type: 'workspaceAction', + kind: 'default', + weight: 1000, + element: () => import('./workspace-action-default-kind.element.js'), + meta: { + label: '(Missing label in manifest)', + }, + }, +}; + +export const manifests: Array = [manifest]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts similarity index 72% rename from src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts index 546a7f74db..166a57e22f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.element.ts @@ -1,11 +1,11 @@ -import type { UmbWorkspaceAction } from '../workspace-action.interface.js'; import type { ManifestWorkspaceAction, ManifestWorkspaceActionMenuItem, MetaWorkspaceActionDefaultKind, + UmbWorkspaceActionDefaultKind, } from '../../../types.js'; import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; -import { html, customElement, property, state, ifDefined, when } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, property, state, when } from '@umbraco-cms/backoffice/external/lit'; import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -19,7 +19,7 @@ import '../../workspace-action-menu/index.js'; @customElement('umb-workspace-action') export class UmbWorkspaceActionElement< MetaType extends MetaWorkspaceActionDefaultKind = MetaWorkspaceActionDefaultKind, - ApiType extends UmbWorkspaceAction = UmbWorkspaceAction, + ApiType extends UmbWorkspaceActionDefaultKind = UmbWorkspaceActionDefaultKind, > extends UmbLitElement { #manifest?: ManifestWorkspaceAction; #api?: ApiType; @@ -29,21 +29,14 @@ export class UmbWorkspaceActionElement< ManifestWorkspaceActionMenuItem >; - @state() - private _buttonState?: UUIButtonState; - - @state() - _href?: string; - - @state() - _isDisabled = false; - @property({ type: Object, attribute: false }) public set manifest(value: ManifestWorkspaceAction | undefined) { if (!value) return; const oldValue = this.#manifest; - this.#manifest = value; - if (oldValue !== this.#manifest) { + if (oldValue !== value) { + this.#manifest = value; + this._href = value?.meta.href; + this._additionalOptions = value?.meta.additionalOptions; this.#createAliases(); this.requestUpdate('manifest', oldValue); } @@ -56,10 +49,12 @@ export class UmbWorkspaceActionElement< 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] + this._href = href ?? this.manifest?.meta.href; + }); + + this.#api?.hasAdditionalOptions?.().then((additionalOptions) => { + this._additionalOptions = additionalOptions ?? this.manifest?.meta.additionalOptions; }); this.#observeIsDisabled(); @@ -68,6 +63,18 @@ export class UmbWorkspaceActionElement< return this.#api; } + @state() + private _buttonState?: UUIButtonState; + + @state() + private _additionalOptions?: boolean; + + @state() + private _href?: string; + + @state() + _isDisabled = false; + @state() private _items: Array> = []; @@ -92,21 +99,28 @@ export class UmbWorkspaceActionElement< this.#observeExtensions(Array.from(aliases)); } - private async _onClick(event: MouseEvent) { + async #onClick(event: MouseEvent) { if (this._href) { event.stopPropagation(); } + // If its a link or has additional options, then we do not want to display state on the button. [NL] + if (!this._href) { + if (!this._additionalOptions) { + this._buttonState = 'waiting'; + } - this._buttonState = 'waiting'; - - try { - if (!this.#api) throw new Error('No api defined'); - await this.#api.execute(); - this._buttonState = 'success'; - } catch { - this._buttonState = 'failed'; + try { + if (!this.#api) throw new Error('No api defined'); + await this.#api.execute(); + if (!this._additionalOptions) { + this._buttonState = 'success'; + } + } catch { + if (!this._additionalOptions) { + this._buttonState = 'failed'; + } + } } - this.dispatchEvent(new UmbActionExecutedEvent()); } @@ -144,18 +158,19 @@ export class UmbWorkspaceActionElement< } #renderButton() { + const label = this.#manifest?.meta.label + ? this.localize.string(this.#manifest.meta.label) + : (this.#manifest?.name ?? ''); return html` + .state=${this._buttonState} + @click=${this.#onClick}> `; } @@ -163,8 +178,8 @@ export class UmbWorkspaceActionElement< return html` + color="${this.#manifest?.meta.color ?? 'default'}" + look="${this.#manifest?.meta.look ?? 'default'}"> `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.interface.ts new file mode 100644 index 0000000000..3a0222688c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/default/workspace-action-default-kind.interface.ts @@ -0,0 +1,9 @@ +import type { UmbWorkspaceAction } from '../types.js'; + +export interface UmbWorkspaceActionDefaultKind extends UmbWorkspaceAction { + /** + * The action has additional options. + * @returns {undefined | Promise} + */ + hasAdditionalOptions?(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/index.ts index 9a6b6a0812..e2e16c2370 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/index.ts @@ -1,4 +1,2 @@ export * from './common/index.js'; export * from './workspace-action-base.controller.js'; -export type * from './types.js'; -export type * from './workspace-action.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/types.ts index e4af37630c..446b03e15f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/types.ts @@ -1,3 +1,6 @@ +export type * from './workspace-action.interface.js'; +export type * from './default/workspace-action-default-kind.interface.js'; + export interface UmbWorkspaceActionArgs { meta: MetaArgsType; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/workspace-action.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/workspace-action.interface.ts index d91bde5607..5863a277fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/workspace-action.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/workspace-action.interface.ts @@ -9,7 +9,7 @@ export interface UmbWorkspaceAction extends UmbAction} */ - getHref(): Promise; + getHref?(): Promise; /** * The `execute` method, the action will act as a button. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/extensions/workspace-action.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/extensions/workspace-action.model.ts index 2319caf3ff..85e29794c1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/extensions/workspace-action.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/extensions/workspace-action.model.ts @@ -1,10 +1,12 @@ +import type { UmbWorkspaceAction, UmbWorkspaceActionDefaultKind } from '../types.js'; import type { UUIInterfaceColor, UUIInterfaceLook } from '@umbraco-cms/backoffice/external/uui'; import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -export interface ManifestWorkspaceAction - extends ManifestElementAndApi>, +export interface ManifestWorkspaceAction< + MetaType extends MetaWorkspaceAction = MetaWorkspaceAction, + ApiType extends UmbWorkspaceAction = UmbWorkspaceAction, +> extends ManifestElementAndApi, ManifestWithDynamicConditions { type: 'workspaceAction'; meta: MetaType; @@ -13,7 +15,8 @@ export interface ManifestWorkspaceAction { +export interface ManifestWorkspaceActionDefaultKind + extends ManifestWorkspaceAction { type: 'workspaceAction'; kind: 'default'; } @@ -22,6 +25,8 @@ export interface MetaWorkspaceActionDefaultKind extends MetaWorkspaceAction { label?: string; look?: UUIInterfaceLook; color?: UUIInterfaceColor; + href?: string; + additionalOptions?: boolean; } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts index cc9d02132f..83618af082 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts @@ -1,5 +1,6 @@ import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +export type * from './components/types.js'; export type * from './conditions/types.js'; export type * from './data-manager/types.js'; export type * from './extensions/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts index 23e6e3ab33..142e5393a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/publishing/publish/workspace-action/save-and-publish.action.ts @@ -4,11 +4,12 @@ import { UMB_USER_PERMISSION_DOCUMENT_UPDATE, } from '../../../user-permissions/constants.js'; import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from '../../workspace-context/constants.js'; -import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../../constants.js'; +import { UmbWorkspaceActionBase, type UmbWorkspaceActionArgs } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceActionBase { - constructor(host: UmbControllerHost, args: any) { + constructor(host: UmbControllerHost, args: UmbWorkspaceActionArgs) { super(host, args); /* The action is disabled by default because the onChange callback @@ -31,6 +32,12 @@ export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceAction }); } + async hasAdditionalOptions() { + const workspaceContext = await this.getContext(UMB_DOCUMENT_WORKSPACE_CONTEXT); + const variantOptions = await this.observe(workspaceContext.variantOptions).asPromise(); + return variantOptions?.length > 1; + } + override async execute() { const workspaceContext = await this.getContext(UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT); return workspaceContext.saveAndPublish(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts index 8ef0251705..ee14d3ff05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save.action.ts @@ -3,27 +3,40 @@ import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../document-workspace.context-to import type UmbDocumentWorkspaceContext from '../document-workspace.context.js'; import type { UmbVariantState } from '@umbraco-cms/backoffice/utils'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { + UmbSubmitWorkspaceAction, + type MetaWorkspaceAction, + type UmbSubmitWorkspaceActionArgs, + type UmbWorkspaceActionDefaultKind, +} from '@umbraco-cms/backoffice/workspace'; -export class UmbDocumentSaveWorkspaceAction extends UmbSubmitWorkspaceAction { - #documentWorkspaceContext?: UmbDocumentWorkspaceContext; +export class UmbDocumentSaveWorkspaceAction + extends UmbSubmitWorkspaceAction + implements UmbWorkspaceActionDefaultKind +{ #variants: Array = []; #readOnlyStates: Array = []; - constructor(host: UmbControllerHost, args: any) { - super(host, args); + constructor(host: UmbControllerHost, args: UmbSubmitWorkspaceActionArgs) { + super(host, { workspaceContextToken: UMB_DOCUMENT_WORKSPACE_CONTEXT, ...args }); + } - this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { - this.#documentWorkspaceContext = context; - this.#observeVariants(); - this.#observeReadOnlyStates(); - }); + async hasAdditionalOptions() { + await this._retrieveWorkspaceContext; + const variantOptions = await this.observe(this._workspaceContext!.variantOptions).asPromise(); + return variantOptions?.length > 1; + } + + override _gotWorkspaceContext() { + super._gotWorkspaceContext(); + this.#observeVariants(); + this.#observeReadOnlyStates(); } #observeVariants() { this.observe( - this.#documentWorkspaceContext?.variants, + this._workspaceContext?.variants, (variants) => { this.#variants = variants ?? []; this.#check(); @@ -34,7 +47,7 @@ export class UmbDocumentSaveWorkspaceAction extends UmbSubmitWorkspaceAction { #observeReadOnlyStates() { this.observe( - this.#documentWorkspaceContext?.readOnlyState.states, + this._workspaceContext?.readOnlyState.states, (readOnlyStates) => { this.#readOnlyStates = readOnlyStates ?? []; this.#check();