move methods to entity workspace context

This commit is contained in:
Mads Rasmussen
2024-09-24 14:34:01 +02:00
parent 1f72419383
commit 21d441d764
5 changed files with 229 additions and 200 deletions

View File

@@ -1,11 +1,27 @@
import { UmbSubmittableWorkspaceContextBase } from '../submittable/index.js';
import { UmbEntityWorkspaceDataManager } from './entity-workspace-data-manager.js';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
export interface UmbEntityWorkspaceContextArgs {
entityType: string;
workspaceAlias: string;
detailRepositoryAlias: string;
}
export abstract class UmbEntityWorkspaceContextBase<
EntityModelType extends UmbEntityModel,
DetailRepositoryType extends UmbDetailRepository<EntityModelType> = UmbDetailRepository<EntityModelType>,
> extends UmbSubmittableWorkspaceContextBase<EntityModelType> {
/**
* @description Data manager for the workspace.
@@ -14,9 +30,137 @@ export abstract class UmbEntityWorkspaceContextBase<
*/
protected readonly _data = new UmbEntityWorkspaceDataManager<EntityModelType>(this);
constructor(host: UmbControllerHost, workspaceAlias: string) {
super(host, workspaceAlias);
protected _getDataPromise?: Promise<any>;
protected _detailRepository?: DetailRepositoryType;
#entityType: string;
#parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined);
readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined));
readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined));
#initResolver?: () => void;
#initialized = false;
#init = new Promise<void>((resolve) => {
if (this.#initialized) {
resolve();
} else {
this.#initResolver = resolve;
}
});
constructor(host: UmbControllerHost, args: UmbEntityWorkspaceContextArgs) {
super(host, args.workspaceAlias);
this.#entityType = args.entityType;
window.addEventListener('willchangestate', this.#onWillNavigate);
this.#observeRepository(args.detailRepositoryAlias);
}
getEntityType() {
return this.#entityType;
}
getData() {
return this._data.getCurrentData();
}
getUnique() {
return this._data.getCurrentData()?.unique;
}
async load(unique: string) {
await this.#init;
this.resetState();
this._getDataPromise = this._detailRepository!.requestByUnique(unique);
type GetDataType = Awaited<ReturnType<UmbDetailRepository<EntityModelType>['requestByUnique']>>;
const response = (await this._getDataPromise) as GetDataType;
const data = response.data;
if (!data) return undefined;
if (data) {
this.setIsNew(false);
this._data.setPersistedData(data);
this._data.setCurrentData(data);
}
return response;
}
public isLoaded() {
return this._getDataPromise;
}
async submit() {
await this.#init;
const currentData = this._data.getCurrentData();
if (!currentData) {
throw new Error('Data is not set');
}
if (!currentData.unique) {
throw new Error('Unique is not set');
}
if (this.getIsNew()) {
const parent = this.#parent.getValue();
if (!parent) throw new Error('Parent is not set');
const { error, data } = await this._detailRepository!.create(currentData, parent.unique);
if (error || !data) {
throw error?.message ?? 'Repository did not return data after create.';
}
this._data.setPersistedData(data);
// TODO: this might not be the right place to alert the tree, but it works for now
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
});
eventContext.dispatchEvent(event);
this.setIsNew(false);
} else {
const { error, data } = await this._detailRepository!.save(currentData);
if (error || !data) {
throw error?.message ?? 'Repository did not return data after create.';
}
this._data.setPersistedData(data);
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.getUnique()!,
entityType: this.getEntityType(),
});
actionEventContext.dispatchEvent(event);
}
}
async create(parent: { entityType: string; unique: string | null }) {
await this.#init;
this.resetState();
this.#parent.setValue(parent);
const request = this._detailRepository!.createScaffold();
this._getDataPromise = request;
let { data } = await request;
if (!data) return undefined;
if (this.modalContext) {
data = { ...data, ...this.modalContext.data.preset };
}
this.setIsNew(true);
this._data.setPersistedData(data);
this._data.setCurrentData(data);
return data;
}
async delete(unique: string) {
await this.#init;
await this._detailRepository!.delete(unique);
}
/**
@@ -61,8 +205,36 @@ export abstract class UmbEntityWorkspaceContextBase<
return true;
};
override resetState() {
super.resetState();
this._data.clearData();
}
#checkIfInitialized() {
if (this._detailRepository) {
this.#initialized = true;
this.#initResolver?.();
}
}
#observeRepository(repositoryAlias: string) {
if (!repositoryAlias) throw new Error('Entity Workspace must have a repository alias.');
new UmbExtensionApiInitializer<ManifestRepository<DetailRepositoryType>>(
this,
umbExtensionsRegistry,
repositoryAlias,
[this._host],
(permitted, ctrl) => {
this._detailRepository = permitted ? ctrl.api : undefined;
this.#checkIfInitialized();
},
);
}
public override destroy(): void {
this._data.destroy();
this._detailRepository?.destroy();
window.removeEventListener('willchangestate', this.#onWillNavigate);
super.destroy();
}

View File

@@ -0,0 +1 @@
export const UMB_DATA_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DataType';

View File

@@ -1,7 +1,9 @@
import type { UmbDataTypeDetailModel, UmbDataTypePropertyModel } from '../types.js';
import { UmbDataTypeDetailRepository } from '../repository/detail/data-type-detail.repository.js';
import { UMB_DATA_TYPE_ENTITY_TYPE } from '../entity.js';
import { UMB_DATA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js';
import { UmbDataTypeWorkspaceEditorElement } from './data-type-workspace-editor.element.js';
import { UMB_DATA_TYPE_WORKSPACE_ALIAS } from './constants.js';
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import type {
UmbInvariantDatasetWorkspaceContext,
@@ -12,23 +14,13 @@ import {
UmbWorkspaceIsNewRedirectController,
UmbEntityWorkspaceContextBase,
} from '@umbraco-cms/backoffice/workspace';
import {
appendToFrozenArray,
UmbArrayState,
UmbObjectState,
UmbStringState,
} from '@umbraco-cms/backoffice/observable-api';
import { appendToFrozenArray, UmbArrayState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type {
PropertyEditorSettingsDefaultData,
PropertyEditorSettingsProperty,
} from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UmbValidationContext } from '@umbraco-cms/backoffice/validation';
type EntityType = UmbDataTypeDetailModel;
@@ -51,22 +43,9 @@ type EntityType = UmbDataTypeDetailModel;
* - a new property editor ui is picked for a data-type, uses the data-type configuration to set the schema, if such is configured for the Property Editor UI. (The user picks the UI via the UI, the schema comes from the UI that the user picked, we store both on the data-type)
*/
export class UmbDataTypeWorkspaceContext
extends UmbEntityWorkspaceContextBase<EntityType>
extends UmbEntityWorkspaceContextBase<EntityType, UmbDataTypeDetailRepository>
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext
{
//
public readonly repository: UmbDataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
#parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined);
readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined));
readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined));
#getDataPromise?: Promise<any>;
public isLoaded() {
return this.#getDataPromise;
}
readonly name = this._data.createObservablePart((data) => data?.name);
readonly unique = this._data.createObservablePart((data) => data?.unique);
readonly entityType = this._data.createObservablePart((data) => data?.entityType);
@@ -96,7 +75,11 @@ export class UmbDataTypeWorkspaceContext
readonly propertyEditorUiName = this.#propertyEditorUiName.asObservable();
constructor(host: UmbControllerHost) {
super(host, 'Umb.Workspace.DataType');
super(host, {
workspaceAlias: UMB_DATA_TYPE_WORKSPACE_ALIAS,
entityType: UMB_DATA_TYPE_ENTITY_TYPE,
detailRepositoryAlias: UMB_DATA_TYPE_DETAIL_REPOSITORY_ALIAS,
});
this.addValidationContext(new UmbValidationContext(this));
@@ -130,15 +113,28 @@ export class UmbDataTypeWorkspaceContext
]);
}
async load(unique: string) {
const { asObservable } = await super.load(unique);
if (asObservable) {
this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbDataTypeStoreObserver');
}
}
#onStoreChange(entity: EntityType | undefined) {
if (!entity) {
//TODO: This solution is alright for now. But reconsider when we introduce signal-r
history.pushState(null, '', 'section/settings/workspace/data-type-root');
}
}
override resetState() {
super.resetState();
this._data.clearData();
this.#propertyEditorSchemaSettingsProperties = [];
this.#propertyEditorUISettingsProperties = [];
this.#propertyEditorSchemaSettingsDefaultData = [];
this.#propertyEditorUISettingsDefaultData = [];
this.#settingsDefaultData = undefined;
this.#mergeConfigProperties();
}
@@ -267,60 +263,6 @@ export class UmbDataTypeWorkspaceContext
return new UmbInvariantWorkspacePropertyDatasetContext(host, this);
}
async load(unique: string) {
this.resetState();
this.#getDataPromise = this.repository.requestByUnique(unique);
type GetDataType = Awaited<ReturnType<UmbDataTypeDetailRepository['requestByUnique']>>;
const { data, asObservable } = (await this.#getDataPromise) as GetDataType;
if (!data) return undefined;
if (data) {
this.setIsNew(false);
this._data.setPersistedData(data);
this._data.setCurrentData(data);
}
if (asObservable) {
this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbDataTypeStoreObserver');
}
}
#onStoreChange(entity: EntityType | undefined) {
if (!entity) {
//TODO: This solution is alright for now. But reconsider when we introduce signal-r
history.pushState(null, '', 'section/settings/workspace/data-type-root');
}
}
async create(parent: { entityType: string; unique: string | null }) {
this.resetState();
this.#parent.setValue(parent);
const request = this.repository.createScaffold();
this.#getDataPromise = request;
let { data } = await request;
if (!data) return undefined;
if (this.modalContext) {
data = { ...data, ...this.modalContext.data.preset };
}
this.setIsNew(true);
this._data.setPersistedData(data);
this._data.setCurrentData(data);
return data;
}
getData() {
return this._data.getCurrentData();
}
getUnique() {
return this._data.getCurrentData()?.unique;
}
getEntityType() {
return UMB_DATA_TYPE_ENTITY_TYPE;
}
getName() {
return this._data.getCurrentData()?.name;
}
@@ -352,7 +294,7 @@ export class UmbDataTypeWorkspaceContext
* @description Get an Observable for the value of this property.
*/
async propertyValueByAlias<ReturnType = unknown>(propertyAlias: string) {
await this.#getDataPromise;
await this._getDataPromise;
return this._data.createObservablePart(
(data) => data?.values?.find((x) => x.alias === propertyAlias)?.value as ReturnType,
);
@@ -367,7 +309,7 @@ export class UmbDataTypeWorkspaceContext
// TODO: its not called a property in the model, but we do consider this way in our front-end
async setPropertyValue(alias: string, value: unknown) {
await this.#getDataPromise;
await this._getDataPromise;
const entry = { alias: alias, value: value };
const currentData = this._data.getCurrentData();
@@ -378,61 +320,10 @@ export class UmbDataTypeWorkspaceContext
}
}
async submit() {
const currentData = this._data.getCurrentData();
if (!currentData) {
throw new Error('Data is not set');
}
if (!currentData.unique) {
throw new Error('Unique is not set');
}
if (this.getIsNew()) {
const parent = this.#parent.getValue();
if (!parent) throw new Error('Parent is not set');
const { error, data } = await this.repository.create(currentData, parent.unique);
if (error || !data) {
throw error?.message ?? 'Repository did not return data after create.';
}
this._data.setPersistedData(data);
// TODO: this might not be the right place to alert the tree, but it works for now
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
const event = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
});
eventContext.dispatchEvent(event);
this.setIsNew(false);
} else {
const { error, data } = await this.repository.save(currentData);
if (error || !data) {
throw error?.message ?? 'Repository did not return data after create.';
}
this._data.setPersistedData(data);
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
const event = new UmbRequestReloadStructureForEntityEvent({
unique: this.getUnique()!,
entityType: this.getEntityType(),
});
actionEventContext.dispatchEvent(event);
}
}
async delete(unique: string) {
await this.repository.delete(unique);
}
public override destroy(): void {
this.#properties.destroy();
this.#propertyEditorUiIcon.destroy();
this.#propertyEditorUiName.destroy();
this.repository.destroy();
super.destroy();
}
}

View File

@@ -1,7 +1,5 @@
import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
const UMB_DATA_TYPE_WORKSPACE_ALIAS = 'Umb.Workspace.DataType';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'workspace',

View File

@@ -1,44 +1,44 @@
import type { UmbUserDetailModel, UmbUserStartNodesModel, UmbUserStateEnum } from '../../types.js';
import { UMB_USER_ENTITY_TYPE } from '../../entity.js';
import { UmbUserDetailRepository } from '../../repository/index.js';
import type { UmbUserDetailRepository } from '../../repository/index.js';
import { UMB_USER_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js';
import { UmbUserAvatarRepository } from '../../repository/avatar/index.js';
import { UmbUserConfigRepository } from '../../repository/config/index.js';
import { UMB_USER_WORKSPACE_ALIAS } from './constants.js';
import { UmbUserWorkspaceEditorElement } from './user-workspace-editor.element.js';
import type { UmbSubmittableWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import { UmbSubmittableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace';
import { UmbEntityWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
type EntityType = UmbUserDetailModel;
export class UmbUserWorkspaceContext
extends UmbSubmittableWorkspaceContextBase<EntityType>
extends UmbEntityWorkspaceContextBase<EntityType, UmbUserDetailRepository>
implements UmbSubmittableWorkspaceContext
{
public readonly detailRepository: UmbUserDetailRepository = new UmbUserDetailRepository(this);
public readonly avatarRepository: UmbUserAvatarRepository = new UmbUserAvatarRepository(this);
public readonly configRepository = new UmbUserConfigRepository(this);
#persistedData = new UmbObjectState<EntityType | undefined>(undefined);
#currentData = new UmbObjectState<EntityType | undefined>(undefined);
readonly data = this.#currentData.asObservable();
readonly state = this.#currentData.asObservablePart((x) => x?.state);
readonly unique = this.#currentData.asObservablePart((x) => x?.unique);
readonly kind = this.#currentData.asObservablePart((x) => x?.kind);
readonly userGroupUniques = this.#currentData.asObservablePart((x) => x?.userGroupUniques || []);
readonly documentStartNodeUniques = this.#currentData.asObservablePart(
(data) => data?.documentStartNodeUniques || [],
);
readonly hasDocumentRootAccess = this.#currentData.asObservablePart((data) => data?.hasDocumentRootAccess || false);
readonly mediaStartNodeUniques = this.#currentData.asObservablePart((data) => data?.mediaStartNodeUniques || []);
readonly hasMediaRootAccess = this.#currentData.asObservablePart((data) => data?.hasMediaRootAccess || false);
readonly data = this._data.current;
readonly state = this._data.createObservablePart((x) => x?.state);
readonly unique = this._data.createObservablePart((x) => x?.unique);
readonly kind = this._data.createObservablePart((x) => x?.kind);
readonly userGroupUniques = this._data.createObservablePart((x) => x?.userGroupUniques || []);
readonly documentStartNodeUniques = this._data.createObservablePart((data) => data?.documentStartNodeUniques || []);
readonly hasDocumentRootAccess = this._data.createObservablePart((data) => data?.hasDocumentRootAccess || false);
readonly mediaStartNodeUniques = this._data.createObservablePart((data) => data?.mediaStartNodeUniques || []);
readonly hasMediaRootAccess = this._data.createObservablePart((data) => data?.hasMediaRootAccess || false);
#calculatedStartNodes = new UmbObjectState<UmbUserStartNodesModel | undefined>(undefined);
readonly calculatedStartNodes = this.#calculatedStartNodes.asObservable();
constructor(host: UmbControllerHost) {
super(host, UMB_USER_WORKSPACE_ALIAS);
super(host, {
workspaceAlias: UMB_USER_WORKSPACE_ALIAS,
entityType: UMB_USER_ENTITY_TYPE,
detailRepositoryAlias: UMB_USER_DETAIL_REPOSITORY_ALIAS,
});
this.routes.setRoutes([
{
@@ -52,19 +52,17 @@ export class UmbUserWorkspaceContext
]);
}
async load(unique: string) {
const { data, asObservable } = await this.detailRepository.requestByUnique(unique);
if (data) {
this.setIsNew(false);
this.#persistedData.update(data);
this.#currentData.update(data);
}
override async load(unique: string) {
const { asObservable } = await super.load(unique);
this.observe(asObservable(), (user) => this.onUserStoreChanges(user), 'umbUserStoreObserver');
if (!this._detailRepository) {
throw new Error('Detail repository is missing');
}
// Get the calculated start nodes
const { data: calculatedStartNodes } = await this.detailRepository.requestCalculateStartNodes(unique);
const { data: calculatedStartNodes } = await this._detailRepository.requestCalculateStartNodes(unique);
this.#calculatedStartNodes.setValue(calculatedStartNodes);
}
@@ -79,43 +77,15 @@ export class UmbUserWorkspaceContext
history.pushState(null, '', 'section/user-management');
return;
}
this.#currentData.update({ state: user.state, avatarUrls: user.avatarUrls });
this._data.updateCurrentData({ state: user.state, avatarUrls: user.avatarUrls });
}
getUnique(): string | undefined {
return this.getData()?.unique;
}
getState(): UmbUserStateEnum | null | undefined {
return this.getData()?.state;
}
getEntityType(): string {
return UMB_USER_ENTITY_TYPE;
}
getData() {
return this.#currentData.getValue();
return this._data.getCurrentData()?.state;
}
updateProperty<PropertyName extends keyof EntityType>(propertyName: PropertyName, value: EntityType[PropertyName]) {
this.#currentData.update({ [propertyName]: value });
}
async submit() {
if (!this.#currentData.value) throw new Error('Data is missing');
if (!this.#currentData.value.unique) throw new Error('Unique is missing');
if (this.getIsNew()) {
const { error, data } = await this.detailRepository.create(this.#currentData.value);
if (error) throw new Error(error.message);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
} else {
const { error, data } = await this.detailRepository.save(this.#currentData.value);
if (error) throw new Error(error.message);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
}
this._data.updateCurrentData({ [propertyName]: value });
}
// TODO: implement upload progress
@@ -132,9 +102,6 @@ export class UmbUserWorkspaceContext
}
override destroy(): void {
this.#persistedData.destroy();
this.#currentData.destroy();
this.detailRepository.destroy();
this.avatarRepository.destroy();
super.destroy();
}