add submittable workspace data manager

This commit is contained in:
Mads Rasmussen
2024-09-24 11:48:37 +02:00
parent 79c9b73f6f
commit 43c66bbd26
4 changed files with 276 additions and 1 deletions

View File

@@ -1,10 +1,12 @@
export * from './components/index.js';
export * from './contexts/index.js';
export * from './controllers/index.js';
export * from './data-manager/index.js';
export * from './modals/index.js';
export * from './paths.js';
export * from './submittable/index.js';
export * from './workspace-property-dataset/index.js';
export * from './workspace.element.js';
export * from './paths.js';
export type * from './conditions/index.js';
export type * from './types.js';

View File

@@ -0,0 +1,2 @@
export * from './submittable-workspace-data-manager.js';
export * from './submittable-workspace-context-base.js';

View File

@@ -0,0 +1,164 @@
import { UmbWorkspaceRouteManager } from '../controllers/workspace-route-manager.controller.js';
import { UMB_WORKSPACE_CONTEXT } from '../contexts/tokens/workspace.context-token.js';
import type { UmbSubmittableWorkspaceContext } from '../contexts/tokens/submittable-workspace-context.interface.js';
import { UmbSubmittableWorkspaceDataManager } from './submittable-workspace-data-manager.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbValidationController } from '@umbraco-cms/backoffice/validation';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType extends UmbEntityModel>
extends UmbContextBase<UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>>
implements UmbSubmittableWorkspaceContext
{
protected readonly _data = new UmbSubmittableWorkspaceDataManager<WorkspaceDataModelType>(this);
public readonly workspaceAlias: string;
// TODO: We could make a base type for workspace modal data, and use this here: As well as a base for the result, to make sure we always include the unique (instead of the object type)
public readonly modalContext?: UmbModalContext<{ preset: object }>;
//public readonly validation = new UmbValidationContext(this);
#validationContexts: Array<UmbValidationController> = [];
/**
* Appends a validation context to the workspace.
* @param context
*/
addValidationContext(context: UmbValidationController) {
this.#validationContexts.push(context);
}
#submitPromise: Promise<void> | undefined;
#submitResolve: (() => void) | undefined;
#submitReject: (() => void) | undefined;
abstract readonly unique: Observable<string | null | undefined>;
#isNew = new UmbBooleanState(undefined);
isNew = this.#isNew.asObservable();
readonly routes = new UmbWorkspaceRouteManager(this);
/*
Concept notes: [NL]
Considerations are, if we bring a dirty state (observable) we need to maintain it all the time.
This might be too heavy process, so we might want to consider just having a get dirty state method.
*/
//#isDirty = new UmbBooleanState(undefined);
//isDirty = this.#isNew.asObservable();
constructor(host: UmbControllerHost, workspaceAlias: string) {
super(host, UMB_WORKSPACE_CONTEXT.toString());
this.workspaceAlias = workspaceAlias;
// TODO: Consider if we can move this consumption to #resolveSubmit, just as a getContext, but it depends if others use the modalContext prop.. [NL]
this.consumeContext(UMB_MODAL_CONTEXT, (context) => {
(this.modalContext as UmbModalContext) = context;
});
}
protected resetState() {
//this.validation.reset();
this.#validationContexts.forEach((context) => context.reset());
this.#isNew.setValue(undefined);
}
getIsNew() {
return this.#isNew.getValue();
}
protected setIsNew(isNew: boolean) {
this.#isNew.setValue(isNew);
}
/**
* If a Workspace has multiple validation contexts, then this method can be overwritten to return the correct one.
* @returns Promise that resolves to void when the validation is complete.
*/
async validate(): Promise<Array<void>> {
//return this.validation.validate();
return Promise.all(this.#validationContexts.map((context) => context.validate()));
}
async requestSubmit(): Promise<void> {
return this.validateAndSubmit(
() => this.submit(),
() => this.invalidSubmit(),
);
}
protected async validateAndSubmit(onValid: () => Promise<void>, onInvalid: () => Promise<void>): Promise<void> {
if (this.#submitPromise) {
return this.#submitPromise;
}
this.#submitPromise = new Promise<void>((resolve, reject) => {
this.#submitResolve = resolve;
this.#submitReject = reject;
});
this.validate().then(
async () => {
onValid().then(this.#completeSubmit, this.#rejectSubmit);
},
async () => {
onInvalid().then(this.#resolveSubmit, this.#rejectSubmit);
},
);
return this.#submitPromise;
}
#rejectSubmit = () => {
if (this.#submitPromise) {
// TODO: Capture the validation contexts messages on open, and then reset to them in this case. [NL]
this.#submitReject?.();
this.#submitPromise = undefined;
this.#submitResolve = undefined;
this.#submitReject = undefined;
}
};
#resolveSubmit = () => {
// Resolve the submit promise:
this.#submitResolve?.();
this.#submitPromise = undefined;
this.#submitResolve = undefined;
this.#submitReject = undefined;
// If we do not want to close a modal when saving something with errors, then move this part down to #completeSubmit method. [NL]
if (this.modalContext) {
this.modalContext?.setValue(this.getData());
this.modalContext?.submit();
}
};
#completeSubmit = () => {
this.#resolveSubmit();
// Calling reset on the validation context here. [NL]
// TODO: Capture the validation messages on open, and then reset to that.
//this.validation.reset();
};
//abstract getIsDirty(): Promise<boolean>;
abstract getUnique(): string | undefined;
abstract getEntityType(): string;
abstract getData(): WorkspaceDataModelType | undefined;
protected abstract submit(): Promise<void>;
protected invalidSubmit(): Promise<void> {
return Promise.reject();
}
}
/*
* @deprecated Use UmbSubmittableWorkspaceContextBase instead — Will be removed before RC.
* Rename `save` to `submit` and return a promise that resolves to true when save is complete.
* TODO: Delete before RC.
*/
export abstract class UmbEditableWorkspaceContextBase<
WorkspaceDataModelType,
> extends UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType> {}

View File

@@ -0,0 +1,107 @@
import type { UmbWorkspaceDataManager } from '../data-manager/workspace-data-manager.interface.js';
import { jsonStringComparison, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbSubmittableWorkspaceDataManager<ModelType extends UmbEntityModel>
extends UmbControllerBase
implements UmbWorkspaceDataManager<ModelType>
{
#persisted = new UmbObjectState<ModelType | undefined>(undefined);
#current = new UmbObjectState<ModelType | undefined>(undefined);
public readonly current = this.#current.asObservable();
constructor(host: UmbControllerHost) {
super(host);
}
/**
* Gets persisted data
* @returns {(ModelType | undefined)}
* @memberof UmbSubmittableWorkspaceDataManager
*/
getPersistedData() {
return this.#persisted.getValue();
}
/**
* Sets the persisted data
* @param {(ModelType | undefined)} data
* @memberof UmbSubmittableWorkspaceDataManager
*/
setPersistedData(data: ModelType | undefined) {
this.#persisted.setValue(data);
}
/**
* Updates the persisted data
* @param {Partial<ModelType>} partialData
* @memberof UmbSubmittableWorkspaceDataManager
*/
updatePersistedData(partialData: Partial<ModelType>) {
this.#persisted.update(partialData);
}
/**
* Gets the current data
* @returns {(ModelType | undefined)}
* @memberof UmbSubmittableWorkspaceDataManager
*/
getCurrentData() {
return this.#current.getValue();
}
/**
* Sets the current data
* @param {(ModelType | undefined)} data
* @memberof UmbSubmittableWorkspaceDataManager
*/
setCurrentData(data: ModelType | undefined) {
this.#current.setValue(data);
}
/**
* Updates the current data
* @param {Partial<ModelType>} partialData
* @memberof UmbSubmittableWorkspaceDataManager
*/
updateCurrentData(partialData: Partial<ModelType>) {
this.#current.update(partialData);
}
/**
* Checks if there are unpersisted changes
* @returns {*}
* @memberof UmbSubmittableWorkspaceDataManager
*/
hasUnpersistedChanges() {
const persisted = this.#persisted.getValue();
const current = this.#current.getValue();
return jsonStringComparison(persisted, current) === false;
}
/**
* Resets the current data to the persisted data
* @memberof UmbSubmittableWorkspaceDataManager
*/
resetCurrentData() {
this.#current.setValue(this.#persisted.getValue());
}
/**
* Clears the data
* @memberof UmbSubmittableWorkspaceDataManager
*/
clearData() {
this.#persisted.setValue(undefined);
this.#current.setValue(undefined);
}
override destroy() {
this.#persisted.destroy();
this.#current.destroy();
super.destroy();
}
}