Saveable workspace interface + token (#19220)

Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
Niels Lyngsø
2025-05-07 10:20:05 +02:00
committed by GitHub
parent 744ff61fb0
commit 6de255f667
13 changed files with 126 additions and 44 deletions

View File

@@ -13,6 +13,7 @@ import {
UmbWorkspaceSplitViewManager,
type UmbEntityDetailWorkspaceContextArgs,
type UmbEntityDetailWorkspaceContextCreateArgs,
type UmbSaveableWorkspaceContext,
} from '@umbraco-cms/backoffice/workspace';
import {
UmbContentTypeStructureManager,
@@ -97,7 +98,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
UmbEntityDetailWorkspaceContextCreateArgs<DetailModelType> = UmbEntityDetailWorkspaceContextCreateArgs<DetailModelType>,
>
extends UmbEntityDetailWorkspaceContextBase<DetailModelType, DetailRepositoryType, CreateArgsType>
implements UmbContentWorkspaceContext<DetailModelType, ContentTypeDetailModelType, VariantModelType>
implements
UmbContentWorkspaceContext<DetailModelType, ContentTypeDetailModelType, VariantModelType>,
UmbSaveableWorkspaceContext
{
public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const;
@@ -778,6 +781,14 @@ export abstract class UmbContentDetailWorkspaceContextBase<
return this._handleSubmit();
}
/**
* Request a save of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be saved.
* @returns {Promise<void>} a promise which resolves once it has been completed.
*/
public requestSave() {
return this._handleSave();
}
/**
* Get the data to save
* @param {Array<UmbVariantId>} variantIds - The variant ids to save
@@ -789,6 +800,10 @@ export abstract class UmbContentDetailWorkspaceContextBase<
}
protected async _handleSubmit() {
await this._handleSave();
this._closeModal();
}
protected async _handleSave() {
const data = this.getData();
if (!data) {
throw new Error('Data is missing');
@@ -818,8 +833,8 @@ export abstract class UmbContentDetailWorkspaceContextBase<
variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? [];
} else {
/* If there are multiple variants but no modal token is set
we will save the variants that would have been preselected in the modal.
/* If there are multiple variants but no modal token is set
we will save the variants that would have been preselected in the modal.
These are based on the variants that have been edited */
variantIds = selected.map((x) => UmbVariantId.FromString(x));
}
@@ -829,18 +844,13 @@ export abstract class UmbContentDetailWorkspaceContextBase<
await this.runMandatoryValidationForSaveData(saveData, variantIds);
if (this.#validateOnSubmit) {
await this.askServerToValidate(saveData, variantIds);
return this.validateAndSubmit(
async () => {
return this.performCreateOrUpdate(variantIds, saveData);
},
async (reason?: any) => {
if (this.#ignoreValidationResultOnSubmit) {
return this.performCreateOrUpdate(variantIds, saveData);
} else {
return this.invalidSubmit(reason);
}
},
const valid = await this._validateAndLog().then(
() => true,
() => false,
);
if (valid || this.#ignoreValidationResultOnSubmit) {
return this.performCreateOrUpdate(variantIds, saveData);
}
} else {
await this.performCreateOrUpdate(variantIds, saveData);
}
@@ -915,8 +925,6 @@ export abstract class UmbContentDetailWorkspaceContextBase<
});
eventContext.dispatchEvent(event);
this.setIsNew(false);
this._closeModal();
}
async #update(variantIds: Array<UmbVariantId>, saveData: DetailModelType) {
@@ -966,8 +974,6 @@ export abstract class UmbContentDetailWorkspaceContextBase<
});
eventContext.dispatchEvent(updatedEvent);
this._closeModal();
}
override resetState() {

View File

@@ -1 +1,2 @@
export * from './save/index.js';
export * from './submit/index.js';

View File

@@ -0,0 +1,2 @@
export * from './save.action.js';
export type * from './types.js';

View File

@@ -0,0 +1,52 @@
import type { MetaWorkspaceAction } from '../../../../types.js';
import { UMB_SAVEABLE_WORKSPACE_CONTEXT } from '../../../../contexts/tokens/index.js';
import type { UmbSaveableWorkspaceContext } from '../../../../contexts/tokens/index.js';
import { UmbWorkspaceActionBase } from '../../workspace-action-base.controller.js';
import type { UmbSaveWorkspaceActionArgs } from './types.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbSaveWorkspaceAction<
ArgsMetaType extends MetaWorkspaceAction = MetaWorkspaceAction,
WorkspaceContextType extends UmbSaveableWorkspaceContext = UmbSaveableWorkspaceContext,
> extends UmbWorkspaceActionBase<ArgsMetaType> {
protected _retrieveWorkspaceContext: Promise<unknown>;
protected _workspaceContext?: WorkspaceContextType;
constructor(host: UmbControllerHost, args: UmbSaveWorkspaceActionArgs<ArgsMetaType, WorkspaceContextType>) {
super(host, args);
this._retrieveWorkspaceContext = this.consumeContext(
args.workspaceContextToken ?? UMB_SAVEABLE_WORKSPACE_CONTEXT,
(context) => {
this._workspaceContext = context as WorkspaceContextType | undefined;
this.#observeUnique();
this._gotWorkspaceContext();
},
).asPromise();
}
#observeUnique() {
this.observe(
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();
}
},
'saveWorkspaceActionUniqueObserver',
);
}
protected _gotWorkspaceContext() {
// Override in subclass
}
override async execute() {
await this._retrieveWorkspaceContext;
return await this._workspaceContext?.requestSave();
}
}

View File

@@ -0,0 +1,8 @@
import type { UmbSaveableWorkspaceContext, UmbWorkspaceContext } from '../../../../contexts/index.js';
import type { UmbWorkspaceActionArgs } from '../../types.js';
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export interface UmbSaveWorkspaceActionArgs<MetaArgsType, WorkspaceContextType extends UmbSaveableWorkspaceContext>
extends UmbWorkspaceActionArgs<MetaArgsType> {
workspaceContextToken?: string | UmbContextToken<UmbWorkspaceContext, WorkspaceContextType>;
}

View File

@@ -51,8 +51,3 @@ export class UmbSubmitWorkspaceAction<
return await this._workspaceContext!.requestSubmit();
}
}
/*
* @deprecated Use UmbSubmitWorkspaceAction instead
*/
export { UmbSubmitWorkspaceAction as UmbSaveWorkspaceAction };

View File

@@ -2,11 +2,13 @@ export * from './entity-workspace.context-token.js';
export * from './publishable-workspace.context-token.js';
export * from './routable-workspace.context-token.js';
export * from './submittable-workspace.context-token.js';
export * from './saveable-workspace.context-token.js';
export * from './variant-workspace.context-token.js';
export type * from './entity-workspace-context.interface.js';
export type * from './invariant-dataset-workspace-context.interface.js';
export type * from './publishable-workspace-context.interface.js';
export type * from './routable-workspace-context.interface.js';
export type * from './submittable-workspace-context.interface.js';
export type * from './saveable-workspace-context.interface.js';
export type * from './variant-dataset-workspace-context.interface.js';
export type * from '../../workspace-context.interface.js';

View File

@@ -0,0 +1,5 @@
import type { UmbSubmittableWorkspaceContext } from './submittable-workspace-context.interface.js';
export interface UmbSaveableWorkspaceContext extends UmbSubmittableWorkspaceContext {
requestSave(): Promise<void>;
}

View File

@@ -0,0 +1,9 @@
import type { UmbWorkspaceContext } from '../../types.js';
import type { UmbSaveableWorkspaceContext } from './saveable-workspace-context.interface.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_SAVEABLE_WORKSPACE_CONTEXT = new UmbContextToken<UmbWorkspaceContext, UmbSaveableWorkspaceContext>(
'UmbWorkspaceContext',
undefined,
(context): context is UmbSaveableWorkspaceContext => 'requestSave' in context,
);

View File

@@ -84,6 +84,17 @@ export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
);
}
protected async _validateAndLog(): Promise<void> {
await this.validate().catch(async () => {
// TODO: Implement developer-mode logging here. [NL]
console.warn(
'Validation failed because of these validation messages still begin present: ',
this.#validationContexts.flatMap((x) => x.messages.getMessages()),
);
return Promise.reject();
});
}
public async validateAndSubmit(
onValid: () => Promise<void>,
onInvalid: (reason?: any) => Promise<void>,
@@ -95,16 +106,11 @@ export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
this.#submitResolve = resolve;
this.#submitReject = reject;
});
this.validate().then(
this._validateAndLog().then(
async () => {
onValid().then(this.#completeSubmit, this.#rejectSubmit);
},
async (error) => {
// TODO: Implement developer-mode logging here. [NL]
console.warn(
'Validation failed because of these validation messages still begin present: ',
this.#validationContexts.flatMap((x) => x.messages.getMessages()),
);
onInvalid(error).then(this.#resolveSubmit, this.#rejectSubmit);
},
);

View File

@@ -3,17 +3,20 @@ import type UmbDocumentWorkspaceContext from '../document-workspace.context.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import {
UmbSubmitWorkspaceAction,
UmbSaveWorkspaceAction,
type MetaWorkspaceAction,
type UmbSubmitWorkspaceActionArgs,
type UmbSaveWorkspaceActionArgs,
type UmbWorkspaceActionDefaultKind,
} from '@umbraco-cms/backoffice/workspace';
export class UmbDocumentSaveWorkspaceAction
extends UmbSubmitWorkspaceAction<MetaWorkspaceAction, UmbDocumentWorkspaceContext>
extends UmbSaveWorkspaceAction<MetaWorkspaceAction, UmbDocumentWorkspaceContext>
implements UmbWorkspaceActionDefaultKind<MetaWorkspaceAction>
{
constructor(host: UmbControllerHost, args: UmbSubmitWorkspaceActionArgs<MetaWorkspaceAction>) {
constructor(
host: UmbControllerHost,
args: UmbSaveWorkspaceActionArgs<MetaWorkspaceAction, UmbDocumentWorkspaceContext>,
) {
super(host, { workspaceContextToken: UMB_DOCUMENT_WORKSPACE_CONTEXT, ...args });
}

View File

@@ -1,12 +1,9 @@
import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js';
import type { UmbDocumentWorkspaceContext } from './document-workspace.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbSubmittableWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken<
UmbSubmittableWorkspaceContext,
UmbDocumentWorkspaceContext
>(
export const UMB_DOCUMENT_WORKSPACE_CONTEXT = new UmbContextToken<UmbWorkspaceContext, UmbDocumentWorkspaceContext>(
'UmbWorkspaceContext',
undefined,
(context): context is UmbDocumentWorkspaceContext => context.getEntityType?.() === UMB_DOCUMENT_ENTITY_TYPE,

View File

@@ -341,17 +341,13 @@ export class UmbDocumentWorkspaceContext
this._data.updateCurrent({ template: { unique: templateUnique } });
}
/**
* Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted.
* @returns {Promise<void>} a promise which resolves once it has been completed.
*/
public override requestSubmit() {
protected override async _handleSave() {
const elementStyle = (this.getHostElement() as HTMLElement).style;
elementStyle.setProperty('--uui-color-invalid', 'var(--uui-color-warning)');
elementStyle.setProperty('--uui-color-invalid-emphasis', 'var(--uui-color-warning-emphasis)');
elementStyle.setProperty('--uui-color-invalid-standalone', 'var(--uui-color-warning-standalone)');
elementStyle.setProperty('--uui-color-invalid-contrast', 'var(--uui-color-warning-contrast)');
return this._handleSubmit();
await super._handleSave();
}
public async saveAndPreview(): Promise<void> {