Feature: Content Type Workspace Context Base (#17542)
* Create content-type-workspace-context-base.ts * make detail model with entityType * allow repository alias * export base class * fix type check * add method to get unpersisted changes * remove duplicate code * remove duplicate code * remove duplicate code * wip porting code to the base class * improve extendability * clean up * clean up * move logic to base * allow to preset the scaffold * pass preset * add public tag * clean up * simplify the number of places we store the entity type * add js docs * rename private method to clear * remove debugger * use flag instead of a data state * set persisted data after create + update * Update entity-detail-workspace-base.ts * add js docs * add protected tag * call super * make linter happy * add comment * type casting * no need create observables for unique and entityType it is already handled * add null check --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com>
This commit is contained in:
@@ -17,6 +17,8 @@ import {
|
||||
} from '@umbraco-cms/backoffice/observable-api';
|
||||
import { incrementString } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { umbExtensionsRegistry, type ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
type UmbPropertyTypeId = UmbPropertyTypeModel['id'];
|
||||
|
||||
@@ -35,7 +37,16 @@ export class UmbContentTypeStructureManager<
|
||||
> extends UmbControllerBase {
|
||||
#init!: Promise<unknown>;
|
||||
|
||||
#repository: UmbDetailRepository<T>;
|
||||
#repository?: UmbDetailRepository<T>;
|
||||
#initRepositoryResolver?: () => void;
|
||||
|
||||
#initRepository = new Promise<void>((resolve) => {
|
||||
if (this.#repository) {
|
||||
resolve();
|
||||
} else {
|
||||
this.#initRepositoryResolver = resolve;
|
||||
}
|
||||
});
|
||||
|
||||
#ownerContentTypeUnique?: string;
|
||||
#contentTypeObservers = new Array<UmbController>();
|
||||
@@ -84,9 +95,15 @@ export class UmbContentTypeStructureManager<
|
||||
return this.#containers.asObservablePart((x) => x.find((y) => y.id === id));
|
||||
}
|
||||
|
||||
constructor(host: UmbControllerHost, typeRepository: UmbDetailRepository<T>) {
|
||||
constructor(host: UmbControllerHost, typeRepository: UmbDetailRepository<T> | string) {
|
||||
super(host);
|
||||
this.#repository = typeRepository;
|
||||
|
||||
if (typeof typeRepository === 'string') {
|
||||
this.#observeRepository(typeRepository);
|
||||
} else {
|
||||
this.#repository = typeRepository;
|
||||
this.#initRepositoryResolver?.();
|
||||
}
|
||||
|
||||
// Observe owner content type compositions, as we only allow one level of compositions at this moment. [NL]
|
||||
// But, we could support more, we would just need to flatMap all compositions and make sure the entries are unique and then base the observation on that. [NL]
|
||||
@@ -107,7 +124,7 @@ export class UmbContentTypeStructureManager<
|
||||
public async loadType(unique?: string) {
|
||||
//if (!unique) return;
|
||||
//if (this.#ownerContentTypeUnique === unique) return;
|
||||
this._reset();
|
||||
this.#clear();
|
||||
|
||||
this.#ownerContentTypeUnique = unique;
|
||||
|
||||
@@ -117,10 +134,11 @@ export class UmbContentTypeStructureManager<
|
||||
return promise;
|
||||
}
|
||||
|
||||
public async createScaffold() {
|
||||
this._reset();
|
||||
public async createScaffold(preset?: Partial<T>) {
|
||||
await this.#initRepository;
|
||||
this.#clear();
|
||||
|
||||
const { data } = await this.#repository.createScaffold();
|
||||
const { data } = await this.#repository!.createScaffold(preset);
|
||||
if (!data) return {};
|
||||
|
||||
this.#ownerContentTypeUnique = data.unique;
|
||||
@@ -135,10 +153,11 @@ export class UmbContentTypeStructureManager<
|
||||
* @returns {Promise} - A promise that will be resolved when the content type is saved.
|
||||
*/
|
||||
public async save() {
|
||||
await this.#initRepository;
|
||||
const contentType = this.getOwnerContentType();
|
||||
if (!contentType || !contentType.unique) throw new Error('Could not find the Content Type to save');
|
||||
|
||||
const { error, data } = await this.#repository.save(contentType);
|
||||
const { error, data } = await this.#repository!.save(contentType);
|
||||
if (error || !data) {
|
||||
throw error?.message ?? 'Repository did not return data after save.';
|
||||
}
|
||||
@@ -155,12 +174,13 @@ export class UmbContentTypeStructureManager<
|
||||
* @returns {Promise} - a promise that is resolved when the content type has been created.
|
||||
*/
|
||||
public async create(parentUnique: string | null) {
|
||||
await this.#initRepository;
|
||||
const contentType = this.getOwnerContentType();
|
||||
if (!contentType || !contentType.unique) {
|
||||
throw new Error('Could not find the Content Type to create');
|
||||
}
|
||||
|
||||
const { data } = await this.#repository.create(contentType, parentUnique);
|
||||
const { data } = await this.#repository!.create(contentType, parentUnique);
|
||||
if (!data) return Promise.reject();
|
||||
|
||||
// Update state with latest version:
|
||||
@@ -200,9 +220,10 @@ export class UmbContentTypeStructureManager<
|
||||
|
||||
async #loadType(unique?: string) {
|
||||
if (!unique) return {};
|
||||
await this.#initRepository;
|
||||
|
||||
// Lets initiate the content type:
|
||||
const { data, asObservable } = await this.#repository.requestByUnique(unique);
|
||||
const { data, asObservable } = await this.#repository!.requestByUnique(unique);
|
||||
if (!data) return {};
|
||||
|
||||
await this.#observeContentType(data);
|
||||
@@ -211,12 +232,13 @@ export class UmbContentTypeStructureManager<
|
||||
|
||||
async #observeContentType(data: T) {
|
||||
if (!data.unique) return;
|
||||
await this.#initRepository;
|
||||
|
||||
// Notice we do not store the content type in the store here, cause it will happen shortly after when the observations gets its first initial callback. [NL]
|
||||
|
||||
const ctrl = this.observe(
|
||||
// Then lets start observation of the content type:
|
||||
await this.#repository.byUnique(data.unique),
|
||||
await this.#repository!.byUnique(data.unique),
|
||||
(docType) => {
|
||||
if (docType) {
|
||||
this.#contentTypes.appendOne(docType);
|
||||
@@ -725,13 +747,29 @@ export class UmbContentTypeStructureManager<
|
||||
);
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
#observeRepository(repositoryAlias: string) {
|
||||
if (!repositoryAlias) throw new Error('Content Type structure manager must have a repository alias.');
|
||||
|
||||
new UmbExtensionApiInitializer<ManifestRepository<UmbDetailRepository<T>>>(
|
||||
this,
|
||||
umbExtensionsRegistry,
|
||||
repositoryAlias,
|
||||
[this._host],
|
||||
(permitted, ctrl) => {
|
||||
this.#repository = permitted ? ctrl.api : undefined;
|
||||
this.#initRepositoryResolver?.();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#clear() {
|
||||
this.#contentTypes.setValue([]);
|
||||
this.#contentTypeObservers.forEach((observer) => observer.destroy());
|
||||
this.#contentTypeObservers = [];
|
||||
this.#contentTypes.setValue([]);
|
||||
this.#containers.setValue([]);
|
||||
}
|
||||
|
||||
public override destroy() {
|
||||
this.#contentTypes.destroy();
|
||||
this.#containers.destroy();
|
||||
|
||||
@@ -12,7 +12,13 @@ export interface UmbPropertyTypeContainerModel {
|
||||
type: UmbPropertyContainerTypes;
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated
|
||||
* This model is deprecated and will be removed in version 17. Please use the UmbContentTypeDetailModel instead.
|
||||
* @export
|
||||
* @interface UmbContentTypeModel
|
||||
*/
|
||||
export interface UmbContentTypeModel {
|
||||
unique: string;
|
||||
name: string;
|
||||
@@ -30,6 +36,10 @@ export interface UmbContentTypeModel {
|
||||
collection: UmbReferenceByUnique | null;
|
||||
}
|
||||
|
||||
export interface UmbContentTypeDetailModel extends UmbContentTypeModel {
|
||||
entityType: string;
|
||||
}
|
||||
|
||||
export interface UmbPropertyTypeScaffoldModel extends Omit<UmbPropertyTypeModel, 'dataType'> {
|
||||
dataType?: UmbPropertyTypeModel['dataType'];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import {
|
||||
UmbEntityDetailWorkspaceContextBase,
|
||||
type UmbEntityDetailWorkspaceContextArgs,
|
||||
type UmbEntityDetailWorkspaceContextCreateArgs,
|
||||
type UmbRoutableWorkspaceContext,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbContentTypeWorkspaceContext } from './content-type-workspace-context.interface.js';
|
||||
import type { UmbContentTypeCompositionModel, UmbContentTypeDetailModel, UmbContentTypeSortModel } from '../types.js';
|
||||
import { UmbValidationContext } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbContentTypeStructureManager } from '../structure/index.js';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
import { jsonStringComparison, type Observable } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface UmbContentTypeWorkspaceContextArgs extends UmbEntityDetailWorkspaceContextArgs {}
|
||||
|
||||
export abstract class UmbContentTypeWorkspaceContextBase<
|
||||
DetailModelType extends UmbContentTypeDetailModel = UmbContentTypeDetailModel,
|
||||
DetailRepositoryType extends UmbDetailRepository<DetailModelType> = UmbDetailRepository<DetailModelType>,
|
||||
>
|
||||
extends UmbEntityDetailWorkspaceContextBase<DetailModelType, DetailRepositoryType>
|
||||
implements UmbContentTypeWorkspaceContext<DetailModelType>, UmbRoutableWorkspaceContext
|
||||
{
|
||||
public readonly IS_CONTENT_TYPE_WORKSPACE_CONTEXT = true;
|
||||
|
||||
public readonly name: Observable<string | undefined>;
|
||||
public readonly alias: Observable<string | undefined>;
|
||||
public readonly description: Observable<string | undefined>;
|
||||
public readonly icon: Observable<string | undefined>;
|
||||
|
||||
public readonly allowedAtRoot: Observable<boolean | undefined>;
|
||||
public readonly variesByCulture: Observable<boolean | undefined>;
|
||||
public readonly variesBySegment: Observable<boolean | undefined>;
|
||||
public readonly isElement: Observable<boolean | undefined>;
|
||||
public readonly allowedContentTypes: Observable<Array<UmbContentTypeSortModel> | undefined>;
|
||||
public readonly compositions: Observable<Array<UmbContentTypeCompositionModel> | undefined>;
|
||||
public readonly collection: Observable<UmbReferenceByUnique | null | undefined>;
|
||||
|
||||
public readonly structure: UmbContentTypeStructureManager<DetailModelType>;
|
||||
|
||||
constructor(host: UmbControllerHost, args: UmbContentTypeWorkspaceContextArgs) {
|
||||
super(host, args);
|
||||
|
||||
this.structure = new UmbContentTypeStructureManager<DetailModelType>(this, args.detailRepositoryAlias);
|
||||
|
||||
this.addValidationContext(new UmbValidationContext(this));
|
||||
|
||||
this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name);
|
||||
this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias);
|
||||
this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description);
|
||||
this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon);
|
||||
this.allowedAtRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAtRoot);
|
||||
this.variesByCulture = this.structure.ownerContentTypeObservablePart((data) => data?.variesByCulture);
|
||||
this.variesBySegment = this.structure.ownerContentTypeObservablePart((data) => data?.variesBySegment);
|
||||
this.isElement = this.structure.ownerContentTypeObservablePart((data) => data?.isElement);
|
||||
this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes);
|
||||
this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions);
|
||||
this.collection = this.structure.ownerContentTypeObservablePart((data) => data?.collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new scaffold
|
||||
* @param { UmbEntityDetailWorkspaceContextCreateArgs<DetailModelType> } args The arguments for creating a new scaffold
|
||||
* @returns { Promise<DetailModelType | undefined> } The new scaffold
|
||||
*/
|
||||
public override async createScaffold(
|
||||
args: UmbEntityDetailWorkspaceContextCreateArgs<DetailModelType>,
|
||||
): Promise<DetailModelType | undefined> {
|
||||
this.resetState();
|
||||
this.setParent(args.parent);
|
||||
|
||||
const request = this.structure.createScaffold(args.preset);
|
||||
this._getDataPromise = request;
|
||||
let { data } = await request;
|
||||
if (!data) return undefined;
|
||||
|
||||
this.setUnique(data.unique);
|
||||
|
||||
if (this.modalContext) {
|
||||
data = { ...data, ...this.modalContext.data.preset };
|
||||
}
|
||||
|
||||
this.setIsNew(true);
|
||||
this._data.setPersisted(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the data for the workspace
|
||||
* @param { string } unique The unique identifier of the data to load
|
||||
* @returns { Promise<DetailModelType> } The loaded data
|
||||
*/
|
||||
override async load(unique: string) {
|
||||
this.resetState();
|
||||
this.setUnique(unique);
|
||||
this._getDataPromise = this.structure.loadType(unique);
|
||||
const response = await this._getDataPromise;
|
||||
const data = response.data;
|
||||
|
||||
if (data) {
|
||||
this._data.setPersisted(data);
|
||||
this.setIsNew(false);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Content Type
|
||||
* @param { DetailModelType } currentData The current data
|
||||
* @param { UmbEntityModel } parent The parent entity
|
||||
* @memberof UmbContentTypeWorkspaceContextBase
|
||||
*/
|
||||
override async _create(currentData: DetailModelType, parent: UmbEntityModel) {
|
||||
try {
|
||||
await this.structure.create(parent?.unique);
|
||||
|
||||
this._data.setPersisted(this.structure.getOwnerContentType());
|
||||
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content type for the workspace
|
||||
* @memberof UmbContentTypeWorkspaceContextBase
|
||||
*/
|
||||
override async _update() {
|
||||
try {
|
||||
await this.structure.save();
|
||||
|
||||
this._data.setPersisted(this.structure.getOwnerContentType());
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.getUnique()!,
|
||||
entityType: this.getEntityType(),
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the content type
|
||||
* @returns { string | undefined } The name of the content type
|
||||
*/
|
||||
public getName(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the content type
|
||||
* @param { string } name The name of the content type
|
||||
*/
|
||||
public setName(name: string) {
|
||||
this.structure.updateOwnerContentType({ name } as Partial<DetailModelType>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alias of the content type
|
||||
* @returns { string | undefined } The alias of the content type
|
||||
*/
|
||||
public getAlias(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alias of the content type
|
||||
* @param { string } alias The alias of the content type
|
||||
*/
|
||||
public setAlias(alias: string) {
|
||||
this.structure.updateOwnerContentType({ alias } as Partial<DetailModelType>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the description of the content type
|
||||
* @returns { string | undefined } The description of the content type
|
||||
*/
|
||||
public getDescription(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of the content type
|
||||
* @param { string } description The description of the content type
|
||||
*/
|
||||
public setDescription(description: string) {
|
||||
this.structure.updateOwnerContentType({ description } as Partial<DetailModelType>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compositions of the content type
|
||||
* @returns { string | undefined } The icon of the content type
|
||||
*/
|
||||
public getCompositions(): Array<UmbContentTypeCompositionModel> | undefined {
|
||||
return this.structure.getOwnerContentType()?.compositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compositions of the content type
|
||||
* @param { string } compositions The compositions of the content type
|
||||
* @returns { void }
|
||||
*
|
||||
*/
|
||||
public setCompositions(compositions: Array<UmbContentTypeCompositionModel>) {
|
||||
this.structure.updateOwnerContentType({ compositions } as Partial<DetailModelType>);
|
||||
}
|
||||
|
||||
// TODO: manage setting icon color alias?
|
||||
public setIcon(icon: string) {
|
||||
this.structure.updateOwnerContentType({ icon } as Partial<DetailModelType>);
|
||||
}
|
||||
|
||||
public override getData() {
|
||||
return this.structure.getOwnerContentType();
|
||||
}
|
||||
|
||||
protected override _getHasUnpersistedChanges(): boolean {
|
||||
const currentData = this.structure.getOwnerContentType();
|
||||
const persistedData = this._data.getPersisted();
|
||||
return jsonStringComparison(persistedData, currentData) === false;
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
this.structure.destroy();
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export type * from './content-type-workspace-context.interface.js';
|
||||
export * from './content-type-workspace.context-token.js';
|
||||
export * from './views/design/content-type-design-editor-property.context-token.js';
|
||||
export * from './content-type-workspace-context-base.js';
|
||||
|
||||
@@ -78,8 +78,6 @@ export abstract class UmbContentDetailWorkspaceContextBase<
|
||||
|
||||
/* Content Data */
|
||||
protected override readonly _data = new UmbContentWorkspaceDataManager<DetailModelType, VariantModelType>(this);
|
||||
public override readonly entityType = this._data.createObservablePartOfCurrent((data) => data?.entityType);
|
||||
public override readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique);
|
||||
public readonly values = this._data.createObservablePartOfCurrent((data) => data?.values);
|
||||
public readonly variants = this._data.createObservablePartOfCurrent((data) => data?.variants ?? []);
|
||||
|
||||
|
||||
@@ -43,18 +43,19 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
protected readonly _data = new UmbEntityWorkspaceDataManager<DetailModelType>(this);
|
||||
|
||||
public readonly data = this._data.current;
|
||||
public readonly entityType = this._data.createObservablePartOfCurrent((data) => data?.entityType);
|
||||
public readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique);
|
||||
|
||||
protected _getDataPromise?: Promise<any>;
|
||||
protected _detailRepository?: DetailRepositoryType;
|
||||
|
||||
#entityContext = new UmbEntityContext(this);
|
||||
#entityType: string;
|
||||
public readonly entityType = this.#entityContext.entityType;
|
||||
public readonly unique = this.#entityContext.unique;
|
||||
|
||||
#parent = new UmbObjectState<{ entityType: string; unique: UmbEntityUnique } | undefined>(undefined);
|
||||
readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined));
|
||||
readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined));
|
||||
public readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined));
|
||||
public readonly parentEntityType = this.#parent.asObservablePart((parent) =>
|
||||
parent ? parent.entityType : undefined,
|
||||
);
|
||||
|
||||
#initResolver?: () => void;
|
||||
#initialized = false;
|
||||
@@ -67,10 +68,9 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
}
|
||||
});
|
||||
|
||||
constructor(host: UmbControllerHost, args: UmbEntityWorkspaceContextArgs) {
|
||||
constructor(host: UmbControllerHost, args: UmbEntityDetailWorkspaceContextArgs) {
|
||||
super(host, args.workspaceAlias);
|
||||
this.#entityType = args.entityType;
|
||||
this.#entityContext.setEntityType(this.#entityType);
|
||||
this.#entityContext.setEntityType(args.entityType);
|
||||
window.addEventListener('willchangestate', this.#onWillNavigate);
|
||||
this.#observeRepository(args.detailRepositoryAlias);
|
||||
}
|
||||
@@ -80,7 +80,9 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
* @returns { string } The entity type
|
||||
*/
|
||||
getEntityType(): string {
|
||||
return this.#entityType;
|
||||
const entityType = this.#entityContext.getEntityType();
|
||||
if (!entityType) throw new Error('Entity type is not set');
|
||||
return entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,7 +98,11 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
* @returns { string | undefined } The unique identifier
|
||||
*/
|
||||
getUnique(): UmbEntityUnique | undefined {
|
||||
return this._data.getCurrent()?.unique;
|
||||
return this.getData()?.unique;
|
||||
}
|
||||
|
||||
setUnique(unique: string) {
|
||||
this.#entityContext.setUnique(unique);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +113,10 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
return this.#parent.getValue();
|
||||
}
|
||||
|
||||
setParent(parent: UmbEntityModel) {
|
||||
this.#parent.setValue(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent unique
|
||||
* @returns { string | undefined } The parent unique identifier
|
||||
@@ -120,7 +130,6 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
}
|
||||
|
||||
async load(unique: string) {
|
||||
this.#entityContext.setEntityType(this.#entityType);
|
||||
this.#entityContext.setUnique(unique);
|
||||
await this.#init;
|
||||
this.resetState();
|
||||
@@ -156,21 +165,22 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
* @param {Partial<DetailModelType>} args.preset The preset data.
|
||||
* @returns { Promise<any> | undefined } The data of the scaffold.
|
||||
*/
|
||||
async createScaffold(args: CreateArgsType) {
|
||||
public async createScaffold(args: CreateArgsType) {
|
||||
await this.#init;
|
||||
this.resetState();
|
||||
this.#parent.setValue(args.parent);
|
||||
this.setParent(args.parent);
|
||||
|
||||
const request = this._detailRepository!.createScaffold(args.preset);
|
||||
this._getDataPromise = request;
|
||||
let { data } = await request;
|
||||
if (!data) return undefined;
|
||||
|
||||
this.#entityContext.setEntityType(this.#entityType);
|
||||
this.#entityContext.setUnique(data.unique);
|
||||
|
||||
if (this.modalContext) {
|
||||
data = { ...data, ...this.modalContext.data.preset };
|
||||
}
|
||||
|
||||
this.setIsNew(true);
|
||||
this._data.setPersisted(data);
|
||||
this._data.setCurrent(data);
|
||||
@@ -180,7 +190,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
|
||||
async submit() {
|
||||
await this.#init;
|
||||
const currentData = this._data.getCurrent();
|
||||
const currentData = this.getData();
|
||||
|
||||
if (!currentData) {
|
||||
throw new Error('Data is not set');
|
||||
@@ -191,9 +201,12 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
}
|
||||
|
||||
if (this.getIsNew()) {
|
||||
await this.#create(currentData);
|
||||
const parent = this.#parent.getValue();
|
||||
if (parent?.unique === undefined) throw new Error('Parent unique is missing');
|
||||
if (!parent.entityType) throw new Error('Parent entity type is missing');
|
||||
await this._create(currentData, parent);
|
||||
} else {
|
||||
await this.#update(currentData);
|
||||
await this._update(currentData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,12 +230,9 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
return !newUrl.includes(this.routes.getActiveLocalPath());
|
||||
}
|
||||
|
||||
async #create(currentData: DetailModelType) {
|
||||
protected async _create(currentData: DetailModelType, parent: UmbEntityModel) {
|
||||
if (!this._detailRepository) throw new Error('Detail repository is not set');
|
||||
|
||||
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.';
|
||||
@@ -240,7 +250,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
this.setIsNew(false);
|
||||
}
|
||||
|
||||
async #update(currentData: DetailModelType) {
|
||||
protected async _update(currentData: DetailModelType) {
|
||||
const { error, data } = await this._detailRepository!.save(currentData);
|
||||
if (error || !data) {
|
||||
throw error?.message ?? 'Repository did not return data after create.';
|
||||
@@ -258,16 +268,26 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
|
||||
#allowNavigateAway = false;
|
||||
|
||||
#onWillNavigate = async (e: CustomEvent) => {
|
||||
const newUrl = e.detail.url;
|
||||
|
||||
if (this.#allowNavigateAway) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO: temp removal of discard changes in workspace modals.
|
||||
The modal closes before the discard changes dialog is resolved.*/
|
||||
if (newUrl.includes('/modal/umb-modal-workspace/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._checkWillNavigateAway(newUrl) && this._data.getHasUnpersistedChanges()) {
|
||||
if (this._checkWillNavigateAway(newUrl) && this._getHasUnpersistedChanges()) {
|
||||
/* Since ours modals are async while events are synchronous, we need to prevent the default behavior of the event, even if the modal hasn’t been resolved yet.
|
||||
Once the modal is resolved (the user accepted to discard the changes and navigate away from the route), we will push a new history state.
|
||||
This push will make the "willchangestate" event happen again and due to this somewhat "backward" behavior,
|
||||
we set an "allowNavigateAway"-flag to prevent the "discard-changes" functionality from running in a loop.*/
|
||||
e.preventDefault();
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const modal = modalManager.open(this, UMB_DISCARD_CHANGES_MODAL);
|
||||
@@ -275,8 +295,7 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
try {
|
||||
// navigate to the new url when discarding changes
|
||||
await modal.onSubmit();
|
||||
// Reset the current data so we don't end in a endless loop of asking to discard changes.
|
||||
this._data.resetCurrent();
|
||||
this.#allowNavigateAway = true;
|
||||
history.pushState({}, '', e.detail.url);
|
||||
return true;
|
||||
} catch {
|
||||
@@ -287,9 +306,18 @@ export abstract class UmbEntityDetailWorkspaceContextBase<
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if there are unpersisted changes.
|
||||
* @returns { boolean } true if there are unpersisted changes.
|
||||
*/
|
||||
protected _getHasUnpersistedChanges(): boolean {
|
||||
return this._data.getHasUnpersistedChanges();
|
||||
}
|
||||
|
||||
override resetState() {
|
||||
super.resetState();
|
||||
this._data.clear();
|
||||
this.#allowNavigateAway = false;
|
||||
}
|
||||
|
||||
#checkIfInitialized() {
|
||||
|
||||
@@ -7,99 +7,42 @@ import {
|
||||
} from '../../paths.js';
|
||||
import type { UmbDocumentTypeDetailModel } from '../../types.js';
|
||||
import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js';
|
||||
import { UmbDocumentTypeDetailRepository } from '../../repository/detail/document-type-detail.repository.js';
|
||||
import { UmbDocumentTypeWorkspaceEditorElement } from './document-type-workspace-editor.element.js';
|
||||
import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbContentTypeWorkspaceContextBase } from '@umbraco-cms/backoffice/content-type';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import {
|
||||
UmbSubmittableWorkspaceContextBase,
|
||||
UmbWorkspaceIsNewRedirectController,
|
||||
UmbWorkspaceIsNewRedirectControllerAlias,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbTemplateDetailRepository } from '@umbraco-cms/backoffice/template';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import type {
|
||||
UmbContentTypeCompositionModel,
|
||||
UmbContentTypeSortModel,
|
||||
UmbContentTypeWorkspaceContext,
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbContentTypeSortModel, UmbContentTypeWorkspaceContext } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
import type { UmbRoutableWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbPathPatternTypeAsEncodedParamsType } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbValidationContext } from '@umbraco-cms/backoffice/validation';
|
||||
import { UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS } from './constants.js';
|
||||
import { UMB_DOCUMENT_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
import { UmbTemplateDetailRepository } from '@umbraco-cms/backoffice/template';
|
||||
|
||||
type EntityType = UmbDocumentTypeDetailModel;
|
||||
type DetailModelType = UmbDocumentTypeDetailModel;
|
||||
export class UmbDocumentTypeWorkspaceContext
|
||||
extends UmbSubmittableWorkspaceContextBase<EntityType>
|
||||
implements UmbContentTypeWorkspaceContext<EntityType>, UmbRoutableWorkspaceContext
|
||||
extends UmbContentTypeWorkspaceContextBase<DetailModelType>
|
||||
implements UmbContentTypeWorkspaceContext<DetailModelType>, UmbRoutableWorkspaceContext
|
||||
{
|
||||
readonly IS_CONTENT_TYPE_WORKSPACE_CONTEXT = true;
|
||||
//
|
||||
readonly repository = new UmbDocumentTypeDetailRepository(this);
|
||||
// Data/Draft is located in structure manager
|
||||
|
||||
#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));
|
||||
|
||||
#persistedData = new UmbObjectState<EntityType | undefined>(undefined);
|
||||
|
||||
// General for content types:
|
||||
//readonly data;
|
||||
readonly unique;
|
||||
readonly entityType;
|
||||
readonly name;
|
||||
getName(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.name;
|
||||
}
|
||||
readonly alias;
|
||||
readonly description;
|
||||
readonly icon;
|
||||
|
||||
readonly allowedAtRoot;
|
||||
readonly variesByCulture;
|
||||
readonly variesBySegment;
|
||||
readonly isElement;
|
||||
readonly allowedContentTypes;
|
||||
readonly compositions;
|
||||
readonly collection;
|
||||
|
||||
// Document type specific:
|
||||
readonly allowedTemplateIds;
|
||||
readonly defaultTemplate;
|
||||
readonly cleanup;
|
||||
|
||||
readonly structure = new UmbContentTypeStructureManager<EntityType>(this, this.repository);
|
||||
|
||||
createTemplateMode: boolean = false;
|
||||
|
||||
#templateRepository = new UmbTemplateDetailRepository(this);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, 'Umb.Workspace.DocumentType');
|
||||
|
||||
this.addValidationContext(new UmbValidationContext(this));
|
||||
|
||||
// General for content types:
|
||||
//this.data = this.structure.ownerContentType;
|
||||
|
||||
this.unique = this.structure.ownerContentTypeObservablePart((data) => data?.unique);
|
||||
this.entityType = this.structure.ownerContentTypeObservablePart((data) => data?.entityType);
|
||||
|
||||
this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name);
|
||||
this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias);
|
||||
this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description);
|
||||
this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon);
|
||||
this.allowedAtRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAtRoot);
|
||||
this.variesByCulture = this.structure.ownerContentTypeObservablePart((data) => data?.variesByCulture);
|
||||
this.variesBySegment = this.structure.ownerContentTypeObservablePart((data) => data?.variesBySegment);
|
||||
this.isElement = this.structure.ownerContentTypeObservablePart((data) => data?.isElement);
|
||||
this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes);
|
||||
this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions);
|
||||
this.collection = this.structure.ownerContentTypeObservablePart((data) => data?.collection);
|
||||
super(host, {
|
||||
workspaceAlias: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS,
|
||||
entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE,
|
||||
detailRepositoryAlias: UMB_DOCUMENT_TYPE_DETAIL_REPOSITORY_ALIAS,
|
||||
});
|
||||
|
||||
// Document type specific:
|
||||
this.allowedTemplateIds = this.structure.ownerContentTypeObservablePart((data) => data?.allowedTemplates);
|
||||
@@ -117,10 +60,12 @@ export class UmbDocumentTypeWorkspaceContext
|
||||
const parentEntityType = params.parentEntityType;
|
||||
const parentUnique = params.parentUnique === 'null' ? null : params.parentUnique;
|
||||
const presetAlias = params.presetAlias === 'null' ? null : (params.presetAlias ?? null);
|
||||
|
||||
if (parentUnique === undefined) {
|
||||
throw new Error('ParentUnique url parameter is required to create a document type');
|
||||
}
|
||||
await this.create({ entityType: parentEntityType, unique: parentUnique }, presetAlias);
|
||||
|
||||
await this.#onScaffoldSetup({ entityType: parentEntityType, unique: parentUnique }, presetAlias);
|
||||
|
||||
new UmbWorkspaceIsNewRedirectController(
|
||||
this,
|
||||
@@ -141,40 +86,6 @@ export class UmbDocumentTypeWorkspaceContext
|
||||
]);
|
||||
}
|
||||
|
||||
protected override resetState(): void {
|
||||
super.resetState();
|
||||
this.#persistedData.setValue(undefined);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.structure.getOwnerContentType();
|
||||
}
|
||||
|
||||
getUnique() {
|
||||
return this.getData()?.unique;
|
||||
}
|
||||
|
||||
getEntityType() {
|
||||
return UMB_DOCUMENT_TYPE_ENTITY_TYPE;
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this.structure.updateOwnerContentType({ name });
|
||||
}
|
||||
|
||||
setAlias(alias: string) {
|
||||
this.structure.updateOwnerContentType({ alias });
|
||||
}
|
||||
|
||||
setDescription(description: string) {
|
||||
this.structure.updateOwnerContentType({ description });
|
||||
}
|
||||
|
||||
// TODO: manage setting icon color alias?
|
||||
setIcon(icon: string) {
|
||||
this.structure.updateOwnerContentType({ icon });
|
||||
}
|
||||
|
||||
setAllowedAtRoot(allowedAtRoot: boolean) {
|
||||
this.structure.updateOwnerContentType({ allowedAtRoot });
|
||||
}
|
||||
@@ -199,10 +110,6 @@ export class UmbDocumentTypeWorkspaceContext
|
||||
this.structure.updateOwnerContentType({ cleanup });
|
||||
}
|
||||
|
||||
setCompositions(compositions: Array<UmbContentTypeCompositionModel>) {
|
||||
this.structure.updateOwnerContentType({ compositions });
|
||||
}
|
||||
|
||||
setCollection(collection: UmbReferenceByUnique) {
|
||||
this.structure.updateOwnerContentType({ collection });
|
||||
}
|
||||
@@ -220,115 +127,69 @@ export class UmbDocumentTypeWorkspaceContext
|
||||
this.structure.updateOwnerContentType({ defaultTemplate });
|
||||
}
|
||||
|
||||
async create(parent: { entityType: string; unique: string | null }, presetAlias: string | null) {
|
||||
this.resetState();
|
||||
this.#parent.setValue(parent);
|
||||
const { data } = await this.structure.createScaffold();
|
||||
if (!data) return undefined;
|
||||
async #onScaffoldSetup(parent: UmbEntityModel, presetAlias: string | null) {
|
||||
let preset: Partial<DetailModelType> | undefined = undefined;
|
||||
|
||||
switch (presetAlias) {
|
||||
case UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_TEMPLATE satisfies UmbCreateDocumentTypeWorkspacePresetType: {
|
||||
this.setIcon('icon-document-html');
|
||||
preset = {
|
||||
icon: 'icon-document-html',
|
||||
};
|
||||
this.createTemplateMode = true;
|
||||
break;
|
||||
}
|
||||
case UMB_CREATE_DOCUMENT_TYPE_WORKSPACE_PRESET_ELEMENT satisfies UmbCreateDocumentTypeWorkspacePresetType: {
|
||||
this.setIcon('icon-plugin');
|
||||
this.setIsElement(true);
|
||||
preset = {
|
||||
icon: 'icon-plugin',
|
||||
isElement: true,
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setIsNew(true);
|
||||
|
||||
this.#persistedData.setValue(this.structure.getOwnerContentType());
|
||||
|
||||
return data;
|
||||
this.createScaffold({ parent, preset });
|
||||
}
|
||||
|
||||
async load(unique: string) {
|
||||
this.resetState();
|
||||
const { data, asObservable } = await this.structure.loadType(unique);
|
||||
|
||||
if (data) {
|
||||
this.setIsNew(false);
|
||||
this.#persistedData.update(data);
|
||||
override async _create(currentData: DetailModelType, parent: UmbEntityModel) {
|
||||
// TODO: move this responsibility to the template package
|
||||
if (this.createTemplateMode) {
|
||||
await this.#createAndAssignTemplate();
|
||||
}
|
||||
|
||||
if (asObservable) {
|
||||
this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbDocumentTypeStoreObserver');
|
||||
try {
|
||||
super._create(currentData, parent);
|
||||
this.createTemplateMode = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
#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/document-type-root');
|
||||
}
|
||||
// TODO: move this responsibility to the template package
|
||||
async #createAndAssignTemplate() {
|
||||
const { data: templateScaffold } = await this.#templateRepository.createScaffold({
|
||||
name: this.getName(),
|
||||
alias: this.getAlias(),
|
||||
});
|
||||
|
||||
if (!templateScaffold) throw new Error('Could not create template scaffold');
|
||||
const { data: template } = await this.#templateRepository.create(templateScaffold, null);
|
||||
if (!template) throw new Error('Could not create template');
|
||||
|
||||
const templateEntity = { id: template.unique };
|
||||
const allowedTemplates = this.getAllowedTemplateIds() ?? [];
|
||||
this.setAllowedTemplateIds([templateEntity, ...allowedTemplates]);
|
||||
this.setDefaultTemplate(templateEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or creates the document type, based on wether its a new one or existing.
|
||||
* @deprecated Use the createScaffold method instead. Will be removed in 17.
|
||||
* @param {UmbEntityModel} parent
|
||||
* @memberof UmbMediaTypeWorkspaceContext
|
||||
*/
|
||||
async submit() {
|
||||
const data = this.getData();
|
||||
if (data === undefined) {
|
||||
throw new Error('Cannot save, no data');
|
||||
}
|
||||
|
||||
if (this.getIsNew()) {
|
||||
const parent = this.#parent.getValue();
|
||||
if (!parent) throw new Error('Parent is not set');
|
||||
|
||||
if (this.createTemplateMode) {
|
||||
const repo = new UmbTemplateDetailRepository(this);
|
||||
const { data: templateScaffold } = await repo.createScaffold();
|
||||
if (!templateScaffold) throw new Error('Could not create template scaffold');
|
||||
|
||||
templateScaffold.name = data.name;
|
||||
templateScaffold.alias = data.alias;
|
||||
|
||||
const { data: template } = await repo.create(templateScaffold, null);
|
||||
if (!template) throw new Error('Could not create template');
|
||||
|
||||
const templateEntity = { id: template.unique };
|
||||
const allowedTemplates = this.getAllowedTemplateIds() ?? [];
|
||||
this.setAllowedTemplateIds([templateEntity, ...allowedTemplates]);
|
||||
this.setDefaultTemplate(templateEntity);
|
||||
}
|
||||
|
||||
await this.structure.create(parent.unique);
|
||||
|
||||
// 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);
|
||||
this.createTemplateMode = false;
|
||||
} else {
|
||||
await this.structure.save();
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.getUnique()!,
|
||||
entityType: this.getEntityType(),
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
this.#persistedData.destroy();
|
||||
this.structure.destroy();
|
||||
this.repository.destroy();
|
||||
super.destroy();
|
||||
async create(parent: UmbEntityModel, presetAlias: string | null) {
|
||||
this.#onScaffoldSetup(parent, presetAlias);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export class UmbDocumentWorkspaceViewInfoLinksElement extends UmbLitElement {
|
||||
this.observe(
|
||||
observeMultiple([context.isNew, context.unique, context.variantOptions]),
|
||||
([isNew, unique, variantOptions]) => {
|
||||
if (!unique) return;
|
||||
this._isNew = isNew === true;
|
||||
this._unique = unique;
|
||||
this._variantOptions = variantOptions;
|
||||
|
||||
@@ -1,90 +1,39 @@
|
||||
import { UmbMediaTypeDetailRepository } from '../repository/detail/media-type-detail.repository.js';
|
||||
import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../entity.js';
|
||||
import type { UmbMediaTypeDetailModel } from '../types.js';
|
||||
import { UmbMediaTypeWorkspaceEditorElement } from './media-type-workspace-editor.element.js';
|
||||
import {
|
||||
UmbSubmittableWorkspaceContextBase,
|
||||
type UmbRoutableWorkspaceContext,
|
||||
UmbWorkspaceIsNewRedirectController,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type {
|
||||
UmbContentTypeCompositionModel,
|
||||
UmbContentTypeSortModel,
|
||||
UmbContentTypeWorkspaceContext,
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbContentTypeWorkspaceContextBase } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbContentTypeSortModel, UmbContentTypeWorkspaceContext } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UMB_MEDIA_TYPE_WORKSPACE_ALIAS } from './constants.js';
|
||||
import { UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
type EntityType = UmbMediaTypeDetailModel;
|
||||
type DetailModelType = UmbMediaTypeDetailModel;
|
||||
export class UmbMediaTypeWorkspaceContext
|
||||
extends UmbSubmittableWorkspaceContextBase<EntityType>
|
||||
implements UmbContentTypeWorkspaceContext<EntityType>, UmbRoutableWorkspaceContext
|
||||
extends UmbContentTypeWorkspaceContextBase<DetailModelType>
|
||||
implements UmbContentTypeWorkspaceContext<DetailModelType>, UmbRoutableWorkspaceContext
|
||||
{
|
||||
readonly IS_CONTENT_TYPE_WORKSPACE_CONTEXT = true;
|
||||
//
|
||||
public readonly repository: UmbMediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this);
|
||||
// Draft is located in structure manager
|
||||
|
||||
#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));
|
||||
|
||||
#persistedData = new UmbObjectState<EntityType | undefined>(undefined);
|
||||
|
||||
// General for content types:
|
||||
readonly data;
|
||||
readonly unique;
|
||||
readonly entityType;
|
||||
readonly name;
|
||||
getName(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.name;
|
||||
}
|
||||
readonly alias;
|
||||
readonly description;
|
||||
readonly icon;
|
||||
|
||||
readonly allowedAtRoot;
|
||||
readonly variesByCulture;
|
||||
readonly variesBySegment;
|
||||
readonly allowedContentTypes;
|
||||
readonly compositions;
|
||||
readonly collection;
|
||||
|
||||
readonly structure = new UmbContentTypeStructureManager<EntityType>(this, this.repository);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, 'Umb.Workspace.MediaType');
|
||||
|
||||
// General for content types:
|
||||
this.data = this.structure.ownerContentType;
|
||||
this.unique = this.structure.ownerContentTypeObservablePart((data) => data?.unique);
|
||||
this.entityType = this.structure.ownerContentTypeObservablePart((data) => data?.entityType);
|
||||
this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name);
|
||||
this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias);
|
||||
this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description);
|
||||
this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon);
|
||||
this.allowedAtRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAtRoot);
|
||||
this.variesByCulture = this.structure.ownerContentTypeObservablePart((data) => data?.variesByCulture);
|
||||
this.variesBySegment = this.structure.ownerContentTypeObservablePart((data) => data?.variesBySegment);
|
||||
this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes);
|
||||
this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions);
|
||||
this.collection = this.structure.ownerContentTypeObservablePart((data) => data?.collection);
|
||||
super(host, {
|
||||
workspaceAlias: UMB_MEDIA_TYPE_WORKSPACE_ALIAS,
|
||||
entityType: UMB_MEDIA_TYPE_ENTITY_TYPE,
|
||||
detailRepositoryAlias: UMB_MEDIA_TYPE_DETAIL_REPOSITORY_ALIAS,
|
||||
});
|
||||
|
||||
this.routes.setRoutes([
|
||||
{
|
||||
path: 'create/parent/:entityType/:parentUnique',
|
||||
path: 'create/parent/:parentEntityType/:parentUnique',
|
||||
component: UmbMediaTypeWorkspaceEditorElement,
|
||||
setup: async (_component, info) => {
|
||||
const parentEntityType = info.match.params.entityType;
|
||||
const parentEntityType = info.match.params.parentEntityType;
|
||||
const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique;
|
||||
await this.create({ entityType: parentEntityType, unique: parentUnique });
|
||||
const parent: UmbEntityModel = { entityType: parentEntityType, unique: parentUnique };
|
||||
await this.createScaffold({ parent });
|
||||
|
||||
new UmbWorkspaceIsNewRedirectController(
|
||||
this,
|
||||
@@ -104,40 +53,6 @@ export class UmbMediaTypeWorkspaceContext
|
||||
]);
|
||||
}
|
||||
|
||||
protected override resetState(): void {
|
||||
super.resetState();
|
||||
this.#persistedData.setValue(undefined);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.structure.getOwnerContentType();
|
||||
}
|
||||
|
||||
getUnique() {
|
||||
return this.getData()?.unique;
|
||||
}
|
||||
|
||||
getEntityType() {
|
||||
return UMB_MEDIA_TYPE_ENTITY_TYPE;
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this.structure.updateOwnerContentType({ name });
|
||||
}
|
||||
|
||||
setAlias(alias: string) {
|
||||
this.structure.updateOwnerContentType({ alias });
|
||||
}
|
||||
|
||||
setDescription(description: string) {
|
||||
this.structure.updateOwnerContentType({ description });
|
||||
}
|
||||
|
||||
// TODO: manage setting icon color alias?
|
||||
setIcon(icon: string) {
|
||||
this.structure.updateOwnerContentType({ icon });
|
||||
}
|
||||
|
||||
setAllowedAtRoot(allowedAtRoot: boolean) {
|
||||
this.structure.updateOwnerContentType({ allowedAtRoot });
|
||||
}
|
||||
@@ -158,86 +73,17 @@ export class UmbMediaTypeWorkspaceContext
|
||||
this.structure.updateOwnerContentType({ allowedContentTypes });
|
||||
}
|
||||
|
||||
setCompositions(compositions: Array<UmbContentTypeCompositionModel>) {
|
||||
this.structure.updateOwnerContentType({ compositions });
|
||||
}
|
||||
|
||||
setCollection(collection: UmbReferenceByUnique) {
|
||||
this.structure.updateOwnerContentType({ collection });
|
||||
}
|
||||
|
||||
async create(parent: { entityType: string; unique: string | null }) {
|
||||
this.resetState();
|
||||
this.#parent.setValue(parent);
|
||||
const { data } = await this.structure.createScaffold();
|
||||
if (!data) return undefined;
|
||||
|
||||
this.setIsNew(true);
|
||||
this.#persistedData.setValue(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async load(unique: string) {
|
||||
this.resetState();
|
||||
const { data, asObservable } = await this.structure.loadType(unique);
|
||||
|
||||
if (data) {
|
||||
this.setIsNew(false);
|
||||
this.#persistedData.update(data);
|
||||
}
|
||||
|
||||
if (asObservable) {
|
||||
this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbMediaTypeStoreObserver');
|
||||
}
|
||||
}
|
||||
|
||||
#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/media-type-root');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or creates the media type, based on wether its a new one or existing.
|
||||
* @deprecated Use the createScaffold method instead. Will be removed in 17.
|
||||
* @param {UmbEntityModel} parent
|
||||
* @memberof UmbMediaTypeWorkspaceContext
|
||||
*/
|
||||
async submit() {
|
||||
const data = this.getData();
|
||||
if (!data) {
|
||||
throw new Error('Something went wrong, there is no data for media type you want to save...');
|
||||
}
|
||||
|
||||
if (this.getIsNew()) {
|
||||
const parent = this.#parent.getValue();
|
||||
if (!parent) throw new Error('Parent is not set');
|
||||
|
||||
await this.structure.create(parent.unique);
|
||||
|
||||
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 {
|
||||
await this.structure.save();
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.getUnique()!,
|
||||
entityType: this.getEntityType(),
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
this.#persistedData.destroy();
|
||||
this.structure.destroy();
|
||||
this.repository.destroy();
|
||||
super.destroy();
|
||||
async create(parent: UmbEntityModel) {
|
||||
this.createScaffold({ parent });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +1,40 @@
|
||||
import { UmbMemberTypeDetailRepository } from '../repository/detail/index.js';
|
||||
import { UMB_MEMBER_TYPE_DETAIL_REPOSITORY_ALIAS } from '../repository/detail/index.js';
|
||||
import type { UmbMemberTypeDetailModel } from '../types.js';
|
||||
import { UMB_MEMBER_TYPE_ENTITY_TYPE } from '../index.js';
|
||||
import { UmbMemberTypeWorkspaceEditorElement } from './member-type-workspace-editor.element.js';
|
||||
import {
|
||||
UmbSubmittableWorkspaceContextBase,
|
||||
type UmbRoutableWorkspaceContext,
|
||||
UmbWorkspaceIsNewRedirectController,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import {
|
||||
type UmbContentTypeCompositionModel,
|
||||
UmbContentTypeStructureManager,
|
||||
type UmbContentTypeWorkspaceContext,
|
||||
UmbContentTypeWorkspaceContextBase,
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UMB_MEMBER_TYPE_WORKSPACE_ALIAS } from './manifests.js';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
type EntityType = UmbMemberTypeDetailModel;
|
||||
type EntityDetailModel = UmbMemberTypeDetailModel;
|
||||
export class UmbMemberTypeWorkspaceContext
|
||||
extends UmbSubmittableWorkspaceContextBase<EntityType>
|
||||
implements UmbContentTypeWorkspaceContext<EntityType>, UmbRoutableWorkspaceContext
|
||||
extends UmbContentTypeWorkspaceContextBase<EntityDetailModel>
|
||||
implements UmbContentTypeWorkspaceContext<EntityDetailModel>, UmbRoutableWorkspaceContext
|
||||
{
|
||||
readonly IS_CONTENT_TYPE_WORKSPACE_CONTEXT = true;
|
||||
|
||||
public readonly repository = new UmbMemberTypeDetailRepository(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));
|
||||
|
||||
#persistedData = new UmbObjectState<EntityType | undefined>(undefined);
|
||||
|
||||
// General for content types:
|
||||
readonly data;
|
||||
readonly unique;
|
||||
readonly name;
|
||||
getName(): string | undefined {
|
||||
return this.structure.getOwnerContentType()?.name;
|
||||
}
|
||||
readonly alias;
|
||||
readonly description;
|
||||
readonly icon;
|
||||
|
||||
readonly allowedAtRoot;
|
||||
readonly variesByCulture;
|
||||
readonly variesBySegment;
|
||||
readonly isElement;
|
||||
readonly allowedContentTypes;
|
||||
readonly compositions;
|
||||
|
||||
readonly structure = new UmbContentTypeStructureManager<EntityType>(this, this.repository);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, 'Umb.Workspace.MemberType');
|
||||
|
||||
// General for content types:
|
||||
this.data = this.structure.ownerContentType;
|
||||
|
||||
this.unique = this.structure.ownerContentTypeObservablePart((data) => data?.unique);
|
||||
this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name);
|
||||
this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias);
|
||||
this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description);
|
||||
this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon);
|
||||
this.allowedAtRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAtRoot);
|
||||
this.variesByCulture = this.structure.ownerContentTypeObservablePart((data) => data?.variesByCulture);
|
||||
this.variesBySegment = this.structure.ownerContentTypeObservablePart((data) => data?.variesBySegment);
|
||||
this.isElement = this.structure.ownerContentTypeObservablePart((data) => data?.isElement);
|
||||
this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes);
|
||||
this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions);
|
||||
super(host, {
|
||||
workspaceAlias: UMB_MEMBER_TYPE_WORKSPACE_ALIAS,
|
||||
entityType: UMB_MEMBER_TYPE_ENTITY_TYPE,
|
||||
detailRepositoryAlias: UMB_MEMBER_TYPE_DETAIL_REPOSITORY_ALIAS,
|
||||
});
|
||||
|
||||
this.routes.setRoutes([
|
||||
{
|
||||
path: 'create/parent/:entityType/:parentUnique',
|
||||
path: 'create/parent/:parentEntityType/:parentUnique',
|
||||
component: UmbMemberTypeWorkspaceEditorElement,
|
||||
setup: async (_component, info) => {
|
||||
const parentEntityType = info.match.params.entityType;
|
||||
const parentEntityType = info.match.params.parentEntityType;
|
||||
const parentUnique = info.match.params.parentUnique === 'null' ? null : info.match.params.parentUnique;
|
||||
await this.create({ entityType: parentEntityType, unique: parentUnique });
|
||||
const parent: UmbEntityModel = { entityType: parentEntityType, unique: parentUnique };
|
||||
await this.createScaffold({ parent });
|
||||
|
||||
new UmbWorkspaceIsNewRedirectController(
|
||||
this,
|
||||
@@ -100,120 +54,27 @@ export class UmbMemberTypeWorkspaceContext
|
||||
]);
|
||||
}
|
||||
|
||||
set<PropertyName extends keyof EntityType>(propertyName: PropertyName, value: EntityType[PropertyName]) {
|
||||
/**
|
||||
* @deprecated Use the individual set methods instead. Will be removed in 17.
|
||||
* @template PropertyName
|
||||
* @param {PropertyName} propertyName
|
||||
* @param {EntityDetailModel[PropertyName]} value
|
||||
* @memberof UmbMemberTypeWorkspaceContext
|
||||
*/
|
||||
set<PropertyName extends keyof EntityDetailModel>(
|
||||
propertyName: PropertyName,
|
||||
value: EntityDetailModel[PropertyName],
|
||||
) {
|
||||
this.structure.updateOwnerContentType({ [propertyName]: value });
|
||||
}
|
||||
|
||||
protected override resetState(): void {
|
||||
super.resetState();
|
||||
this.#persistedData.setValue(undefined);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.structure.getOwnerContentType();
|
||||
}
|
||||
|
||||
getUnique() {
|
||||
return this.getData()?.unique;
|
||||
}
|
||||
|
||||
getEntityType() {
|
||||
return UMB_MEMBER_TYPE_ENTITY_TYPE;
|
||||
}
|
||||
|
||||
setName(name: string) {
|
||||
this.structure.updateOwnerContentType({ name });
|
||||
}
|
||||
|
||||
setAlias(alias: string) {
|
||||
this.structure.updateOwnerContentType({ alias });
|
||||
}
|
||||
|
||||
setDescription(description: string) {
|
||||
this.structure.updateOwnerContentType({ description });
|
||||
}
|
||||
|
||||
// TODO: manage setting icon color alias?
|
||||
setIcon(icon: string) {
|
||||
this.structure.updateOwnerContentType({ icon });
|
||||
}
|
||||
|
||||
setCompositions(compositions: Array<UmbContentTypeCompositionModel>) {
|
||||
this.structure.updateOwnerContentType({ compositions });
|
||||
}
|
||||
|
||||
async create(parent: { entityType: string; unique: string | null }) {
|
||||
this.resetState();
|
||||
this.#parent.setValue(parent);
|
||||
const { data } = await this.structure.createScaffold();
|
||||
if (!data) return undefined;
|
||||
|
||||
this.setIsNew(true);
|
||||
this.#persistedData.setValue(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
async load(unique: string) {
|
||||
this.resetState();
|
||||
const { data, asObservable } = await this.structure.loadType(unique);
|
||||
|
||||
if (data) {
|
||||
this.setIsNew(false);
|
||||
this.#persistedData.update(data);
|
||||
}
|
||||
|
||||
if (asObservable) {
|
||||
this.observe(asObservable(), (entity) => this.#onStoreChange(entity), 'umbMemberTypeStoreObserver');
|
||||
}
|
||||
}
|
||||
|
||||
#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/member-type-root');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or creates the member type, based on wether its a new one or existing.
|
||||
* @deprecated Use the createScaffold method instead. Will be removed in 17.
|
||||
* @param {UmbEntityModel} parent
|
||||
* @memberof UmbMemberTypeWorkspaceContext
|
||||
*/
|
||||
async submit() {
|
||||
const data = this.getData();
|
||||
if (!data) {
|
||||
throw new Error('Something went wrong, there is no data for media type you want to save...');
|
||||
}
|
||||
|
||||
if (this.getIsNew()) {
|
||||
const parent = this.#parent.getValue();
|
||||
if (!parent) throw new Error('Parent is not set');
|
||||
|
||||
await this.structure.create(parent.unique);
|
||||
|
||||
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 {
|
||||
await this.structure.save();
|
||||
|
||||
const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
|
||||
const event = new UmbRequestReloadStructureForEntityEvent({
|
||||
unique: this.getUnique()!,
|
||||
entityType: this.getEntityType(),
|
||||
});
|
||||
|
||||
actionEventContext.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
this.#persistedData.destroy();
|
||||
this.structure.destroy();
|
||||
this.repository.destroy();
|
||||
super.destroy();
|
||||
async create(parent: UmbEntityModel) {
|
||||
this.createScaffold({ parent });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user