diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 1f0bcc268a..3dca29a82e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -91,7 +91,7 @@ export class UmbDocumentWorkspaceContext public readonly repository = new UmbDocumentDetailRepository(this); public readonly publishingRepository = new UmbDocumentPublishingRepository(this); - #parent = new UmbObjectState<{ entityType: string; unique: string | null } | undefined>(undefined); + #parent = new UmbObjectState(undefined); readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); @@ -106,7 +106,7 @@ export class UmbDocumentWorkspaceContext #serverValidation = new UmbServerModelValidatorContext(this); #validationRepository?: UmbDocumentValidationRepository; - public readOnlyState = new UmbReadOnlyVariantStateManager(this); + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); public isLoaded() { return this.#getDataPromise; @@ -124,9 +124,6 @@ export class UmbDocumentWorkspaceContext readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); - readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); - readonly templateId = this.#data.createObservablePartOfCurrent((data) => data?.template?.unique || null); - readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); readonly variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment); @@ -137,6 +134,9 @@ export class UmbDocumentWorkspaceContext #variesByCulture?: boolean; #variesBySegment?: boolean; + readonly urls = this.#data.createObservablePartOfCurrent((data) => data?.urls || []); + readonly templateId = this.#data.createObservablePartOfCurrent((data) => data?.template?.unique || null); + readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); #dataTypeSchemaAliasMap = new Map(); @@ -518,7 +518,6 @@ export class UmbDocumentWorkspaceContext const { data, error } = await this.repository.create(saveData, parent.unique); if (!data || error) { - console.error('Error creating document', error); throw new Error('Error creating document'); } @@ -547,7 +546,6 @@ export class UmbDocumentWorkspaceContext // Save: const { data, error } = await this.repository.save(saveData); if (!data || error) { - console.error('Error saving document', error); throw new Error('Error saving document'); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/index.ts index e5bebd5e5d..d94f511fa1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/index.ts @@ -1,6 +1,8 @@ import './components/index.js'; -export * from './entity.js'; -export * from './components/index.js'; -export * from './repository/index.js'; export * from './collection/index.js'; +export * from './components/index.js'; +export * from './entity.js'; +export * from './paths.js'; +export * from './repository/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/utils.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/utils.ts new file mode 100644 index 0000000000..9cc33707b6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/utils.ts @@ -0,0 +1,16 @@ +import type { UmbMemberVariantOptionModel } from './types.js'; + +type VariantType = UmbMemberVariantOptionModel; + +export const sortVariants = (a: VariantType, b: VariantType) => { + const compareDefault = (a: VariantType, b: VariantType) => + (a.language?.isDefault ? -1 : 1) - (b.language?.isDefault ? -1 : 1); + + // Make sure mandatory variants goes on top. + const compareMandatory = (a: VariantType, b: VariantType) => + (a.language?.isMandatory ? -1 : 1) - (b.language?.isMandatory ? -1 : 1); + + const compareName = (a: VariantType, b: VariantType) => a.variant?.name.localeCompare(b.variant?.name || '') || 99; + + return compareDefault(a, b) || compareMandatory(a, b) || compareName(a, b); +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/member-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/member-workspace.context.ts index effe6818f4..d611adab9d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/member-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/workspace/member/member-workspace.context.ts @@ -6,6 +6,9 @@ import type { UmbMemberVariantOptionModel, } from '../../types.js'; import { UmbMemberPropertyDatasetContext } from '../../property-dataset-context/member-property-dataset-context.js'; +import { UMB_MEMBER_ENTITY_TYPE, UMB_MEMBER_ROOT_ENTITY_TYPE } from '../../entity.js'; +import { sortVariants } from '../../utils.js'; +import { UMB_MEMBER_MANAGEMENT_SECTION_PATH } from '../../../section/index.js'; import { UMB_MEMBER_WORKSPACE_ALIAS } from './manifests.js'; import { UmbMemberWorkspaceEditorElement } from './member-workspace-editor.element.js'; import { UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD } from './constants.js'; @@ -13,10 +16,16 @@ import { UmbMemberTypeDetailRepository, type UmbMemberTypeDetailModel } from '@u import { UmbSubmittableWorkspaceContextBase, UmbWorkspaceIsNewRedirectController, + UmbWorkspaceIsNewRedirectControllerAlias, UmbWorkspaceSplitViewManager, } from '@umbraco-cms/backoffice/workspace'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbArrayState, appendToFrozenArray, mergeObservables } from '@umbraco-cms/backoffice/observable-api'; +import { + UmbArrayState, + UmbObjectState, + appendToFrozenArray, + mergeObservables, +} from '@umbraco-cms/backoffice/observable-api'; import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language'; @@ -25,6 +34,13 @@ import type { UmbDataSourceResponse } from '@umbraco-cms/backoffice/repository'; import { UmbContentWorkspaceDataManager, type UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content'; import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; +import { map } from '@umbraco-cms/backoffice/external/rxjs'; +import { UmbEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { + UmbRequestReloadChildrenOfEntityEvent, + UmbRequestReloadStructureForEntityEvent, +} from '@umbraco-cms/backoffice/entity-action'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; type EntityModel = UmbMemberDetailModel; export class UmbMemberWorkspaceContext @@ -35,6 +51,10 @@ export class UmbMemberWorkspaceContext public readonly repository = new UmbMemberDetailRepository(this); + #parent = new UmbObjectState({ entityType: UMB_MEMBER_ROOT_ENTITY_TYPE, unique: null }); + readonly parentUnique = this.#parent.asObservablePart((parent) => (parent ? parent.unique : undefined)); + readonly parentEntityType = this.#parent.asObservablePart((parent) => (parent ? parent.entityType : undefined)); + readonly #data = new UmbContentWorkspaceDataManager(this, UMB_MEMBER_DETAIL_MODEL_VARIANT_SCAFFOLD); #getDataPromise?: Promise>; @@ -43,7 +63,7 @@ export class UmbMemberWorkspaceContext #languages = new UmbArrayState([], (x) => x.unique); public readonly languages = this.#languages.asObservable(); - readOnlyState = new UmbReadOnlyVariantStateManager(this); + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); public isLoaded() { return this.#getDataPromise; @@ -54,7 +74,8 @@ export class UmbMemberWorkspaceContext readonly createDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].createDate); readonly updateDate = this.#data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate); readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.memberType.unique); - readonly kind = this.#data.createObservablePartOfCurrent((data) => data?.kind); + + readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); readonly structure = new UmbContentTypeStructureManager(this, new UmbMemberTypeDetailRepository(this)); readonly variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture); @@ -66,7 +87,7 @@ export class UmbMemberWorkspaceContext #variesByCulture?: boolean; #variesBySegment?: boolean; - readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants ?? []); + readonly kind = this.#data.createObservablePartOfCurrent((data) => data?.kind); readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); #dataTypeSchemaAliasMap = new Map(); @@ -102,35 +123,51 @@ export class UmbMemberWorkspaceContext } return [] as Array; }, - ); + ).pipe(map((results) => results.sort(sortVariants))); + + // TODO: this should be set up for all entity workspace contexts in a base class + #entityContext = new UmbEntityContext(this); constructor(host: UmbControllerHost) { super(host, UMB_MEMBER_WORKSPACE_ALIAS); - this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique)); - - this.observe(this.variesByCulture, (varies) => { - this.#data.setVariesByCulture(varies); - this.#variesByCulture = varies; - }); - this.observe(this.variesBySegment, (varies) => { - this.#data.setVariesBySegment(varies); - this.#variesBySegment = varies; - }); - this.observe(this.varies, (varies) => (this.#varies = varies)); - - this.observe(this.structure.contentTypeDataTypeUniques, (dataTypeUniques: Array) => { - this.#dataTypeItemManager.setUniques(dataTypeUniques); - }); - - this.observe(this.#dataTypeItemManager.items, (dataTypes) => { - // Make a map of the data type unique and editorAlias: - this.#dataTypeSchemaAliasMap = new Map( - dataTypes.map((dataType) => { - return [dataType.unique, dataType.propertyEditorSchemaAlias]; - }), - ); - }); + this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique), null); + this.observe(this.varies, (varies) => (this.#varies = varies), null); + this.observe( + this.variesByCulture, + (varies) => { + this.#data.setVariesByCulture(varies); + this.#variesByCulture = varies; + }, + null, + ); + this.observe( + this.variesBySegment, + (varies) => { + this.#data.setVariesBySegment(varies); + this.#variesBySegment = varies; + }, + null, + ); + this.observe( + this.structure.contentTypeDataTypeUniques, + (dataTypeUniques: Array) => { + this.#dataTypeItemManager.setUniques(dataTypeUniques); + }, + null, + ); + this.observe( + this.#dataTypeItemManager.items, + (dataTypes) => { + // Make a map of the data type unique and editorAlias: + this.#dataTypeSchemaAliasMap = new Map( + dataTypes.map((dataType) => { + return [dataType.unique, dataType.propertyEditorSchemaAlias]; + }), + ); + }, + null, + ); this.loadLanguages(); this.routes.setRoutes([ @@ -152,6 +189,7 @@ export class UmbMemberWorkspaceContext path: 'edit/:unique', component: () => new UmbMemberWorkspaceEditorElement(), setup: (_component, info) => { + this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); const unique = info.match.params.unique; this.load(unique); }, @@ -161,8 +199,7 @@ export class UmbMemberWorkspaceContext override resetState() { super.resetState(); - this.#data.setPersisted(undefined); - this.#data.setCurrent(undefined); + this.#data.clear(); } async loadLanguages() { @@ -178,6 +215,8 @@ export class UmbMemberWorkspaceContext const { data, asObservable } = (await this.#getDataPromise) as GetDataType; if (data) { + this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); + this.#entityContext.setUnique(unique); this.setIsNew(false); this.#data.setPersisted(data); this.#data.setCurrent(data); @@ -189,7 +228,7 @@ export class UmbMemberWorkspaceContext #onMemberStoreChange(member: EntityModel | undefined) { if (!member) { //TODO: This solution is alright for now. But reconsider when we introduce signal-r - history.pushState(null, '', 'section/member-management'); + history.pushState(null, '', UMB_MEMBER_MANAGEMENT_SECTION_PATH); } } @@ -203,6 +242,8 @@ export class UmbMemberWorkspaceContext const { data } = await this.#getDataPromise; if (!data) return undefined; + this.#entityContext.setEntityType(UMB_MEMBER_ENTITY_TYPE); + this.#entityContext.setUnique(data.unique); this.setIsNew(true); this.#data.setPersisted(undefined); this.#data.setCurrent(data); @@ -214,11 +255,11 @@ export class UmbMemberWorkspaceContext } getUnique() { - return this.getData()?.unique || ''; + return this.getData()?.unique; } getEntityType() { - return 'member'; + return UMB_MEMBER_ENTITY_TYPE; } getContentTypeId() { @@ -336,35 +377,56 @@ export class UmbMemberWorkspaceContext this.#data.finishPropertyValueChange(); }; - async submit() { + async #handleSave() { const current = this.#data.getCurrent(); if (!current) throw new Error('Data is missing'); if (!current.unique) throw new Error('Unique is missing'); - let newData = undefined; - if (this.getIsNew()) { - const { data } = await this.repository.create(current); - if (!data) { - throw new Error('Could not create member.'); - } - newData = data; - this.setIsNew(false); - } else { - const { data } = await this.repository.save(current); - if (!data) { - throw new Error('Could not create member.'); - } - newData = data; - } + // Create: + const parent = this.#parent.getValue(); + if (!parent) throw new Error('Parent is not set'); - if (newData) { - this.#data.setPersisted(newData); - // TODO: Only update the variants that was chosen to be saved: - //this.#data.setCurrentData(newData); + const { data, error } = await this.repository.create(current); + if (!data || error) { + throw new Error('Could not create member.'); + } + + this.setIsNew(false); + this.#data.setPersisted(data); + // TODO: Missing variant data filtering. + this.#data.setCurrent(data); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadChildrenOfEntityEvent({ + entityType: parent.entityType, + unique: parent.unique, + }); + eventContext.dispatchEvent(event); + } else { + // Save: + const { data, error } = await this.repository.save(current); + if (!data || error) { + throw new Error('Could not update member.'); + } + this.#data.setPersisted(data); + // TODO: Missing variant data filtering. + this.#data.setCurrent(data); + + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + entityType: this.getEntityType(), + unique: this.getUnique()!, + }); + + eventContext.dispatchEvent(event); } } + async submit() { + return this.#handleSave(); + } + async delete() { const id = this.getUnique(); if (id) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/section/index.ts b/src/Umbraco.Web.UI.Client/src/packages/members/section/index.ts index 4f07201dcf..14b140114e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/section/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/section/index.ts @@ -1 +1,2 @@ export * from './constants.js'; +export * from './paths.js';