diff --git a/src/Umbraco.Web.UI.Client/eslint.config.js b/src/Umbraco.Web.UI.Client/eslint.config.js index e0f4913ab9cf..450a95e71e61 100644 --- a/src/Umbraco.Web.UI.Client/eslint.config.js +++ b/src/Umbraco.Web.UI.Client/eslint.config.js @@ -80,6 +80,7 @@ export default [ '@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-import-type-side-effects': 'warn', + '@typescript-eslint/no-deprecated': 'warn', }, }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts index a2b0cd669f4c..068eaed33cd7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content-type/workspace/content-type-workspace-context-base.ts @@ -82,7 +82,7 @@ export abstract class UmbContentTypeWorkspaceContextBase< ): Promise { this.resetState(); this.loading.addState({ unique: LOADING_STATE_UNIQUE, message: `Creating ${this.getEntityType()} scaffold` }); - this.setParent(args.parent); + this._internal_setCreateUnderParent(args.parent); const request = this.structure.createScaffold(args.preset); this._getDataPromise = request; diff --git a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts index 3c257fbb1c22..1f2fa3151d8a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/content/content/workspace/content-detail-workspace-base.ts @@ -754,7 +754,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< // We ask the server first to get a concatenated set of validation messages. So we see both front-end and back-end validation messages [NL] if (this.getIsNew()) { - const parent = this.getParent(); + const parent = this._internal_getCreateUnderParent(); if (!parent) throw new Error('Parent is not set'); await this.#serverValidation.askServerForValidation( saveData, @@ -885,7 +885,7 @@ export abstract class UmbContentDetailWorkspaceContextBase< async #create(variantIds: Array, saveData: DetailModelType) { if (!this._detailRepository) throw new Error('Detail repository is not set'); - const parent = this.getParent(); + const parent = this._internal_getCreateUnderParent(); if (!parent) throw new Error('Parent is not set'); const { data, error } = await this._detailRepository.create(saveData, parent.unique); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts index 9967dead3446..ddea42cdfb3d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts @@ -25,7 +25,7 @@ import { } from '@umbraco-cms/backoffice/entity-action'; import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; -import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; +import { UMB_ENTITY_CONTEXT, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; @@ -83,6 +83,7 @@ export class UmbDefaultCollectionContext< }); #actionEventContext: UmbActionEventContext | undefined; + #parentEntityContext = new UmbParentEntityContext(this); constructor(host: UmbControllerHost, defaultViewAlias: string, defaultFilter: Partial = {}) { super(host, UMB_COLLECTION_CONTEXT); @@ -92,6 +93,23 @@ export class UmbDefaultCollectionContext< this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange); this.#listenToEntityEvents(); + + // The parent entity context is used to get the parent entity for the collection items + // All items in the collection are children of the current entity context + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + const currentEntityUnique = context?.getUnique(); + const currentEntityType = context?.getEntityType(); + + const parent: UmbEntityModel | undefined = + currentEntityUnique && currentEntityType + ? { + unique: currentEntityUnique, + entityType: currentEntityType, + } + : undefined; + + this.#parentEntityContext?.setParent(parent); + }); } setupView(viewElement: UmbControllerHost) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts index 3ed4ee95ed20..f80435864942 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/constants.ts @@ -1 +1,2 @@ export * from './contexts/ancestors/constants.js'; +export * from './contexts/parent/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts new file mode 100644 index 000000000000..31caa05f93ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/constants.ts @@ -0,0 +1 @@ +export { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts new file mode 100644 index 000000000000..d499a54ed561 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/index.ts @@ -0,0 +1 @@ +export { UmbParentEntityContext } from './parent.entity-context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts new file mode 100644 index 000000000000..8a28553f5a8c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context-token.ts @@ -0,0 +1,4 @@ +import type { UmbParentEntityContext } from './parent.entity-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_PARENT_ENTITY_CONTEXT = new UmbContextToken('UmbParentEntityContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts new file mode 100644 index 000000000000..efc1fed494fb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/contexts/parent/parent.entity-context.ts @@ -0,0 +1,38 @@ +import type { UmbEntityModel } from '../../types.js'; +import { UMB_PARENT_ENTITY_CONTEXT } from './parent.entity-context-token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; + +/** + * A entity context for the parent + * @class UmbParentEntityContext + * @augments {UmbContextBase} + * @implements {UmbParentEntityContext} + */ +export class UmbParentEntityContext extends UmbContextBase { + #parent = new UmbObjectState(undefined); + parent = this.#parent.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_PARENT_ENTITY_CONTEXT); + } + + /** + * Gets the parent state + * @returns {UmbEntityModel | undefined} - The parent state + * @memberof UmbParentEntityContext + */ + getParent(): UmbEntityModel | undefined { + return this.#parent.getValue(); + } + + /** + * Sets the parent state + * @param {UmbEntityModel | undefined} parent - The parent state + * @memberof UmbParentEntityContext + */ + setParent(parent: UmbEntityModel | undefined): void { + this.#parent.setValue(parent); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts index d064b7c97abb..046856fa0ef4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts @@ -2,4 +2,6 @@ export { UMB_ENTITY_CONTEXT } from './entity.context-token.js'; export { UmbEntityContext } from './entity.context.js'; export * from './constants.js'; export * from './contexts/ancestors/index.js'; +export * from './contexts/parent/index.js'; + export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts index 46a6c42b4b86..c7d6c36f9e18 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts @@ -1,5 +1,7 @@ export * from './components/index.js'; export * from './menu-tree-structure-workspace-context-base.js'; +export * from './menu-structure-workspace-context.context-token.js'; +export * from './menu-variant-structure-workspace-context.context-token.js'; export * from './menu-variant-tree-structure-workspace-context-base.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts new file mode 100644 index 000000000000..e04fca4af2fe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure-workspace-context.context-token.ts @@ -0,0 +1,7 @@ +import type { UmbMenuStructureWorkspaceContext } from './menu-structure-workspace-context.interface.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT = new UmbContextToken( + 'UmbWorkspaceContext', + 'UmbMenuStructure', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts index d461bff39130..5ebd0dc2ae90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-tree-structure-workspace-context-base.ts @@ -1,32 +1,41 @@ import type { UmbStructureItemModel } from './types.js'; +import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from './menu-structure-workspace-context.context-token.js'; import type { UmbTreeRepository, UmbTreeItemModel, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; -import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; interface UmbMenuTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; } +// TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContextBase { - #workspaceContext?: typeof UMB_SUBMITTABLE_WORKSPACE_CONTEXT.TYPE; + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuTreeStructureWorkspaceContextBaseArgs; #structure = new UmbArrayState([], (x) => x.unique); public readonly structure = this.#structure.asObservable(); #parent = new UmbObjectState(undefined); + /** + * @deprecated Will be removed in v.18: Use UMB_PARENT_ENTITY_CONTEXT instead. + */ public readonly parent = this.#parent.asObservable(); + #parentContext = new UmbParentEntityContext(this); + #ancestorContext = new UmbAncestorsEntityContext(this); + constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) { - // TODO: set up context token - super(host, 'UmbMenuStructureWorkspaceContext'); + super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT); + // 'UmbMenuStructureWorkspaceContext' is Obsolete, will be removed in v.18 + this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; - // TODO: set up context token that supports parentEntityType, parentUnique, entityType. - this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (instance) => { + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe(this.#workspaceContext?.unique, (value) => { if (!value) return; @@ -59,14 +68,16 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex const isNew = this.#workspaceContext?.getIsNew(); const entityTypeObservable = isNew - ? (this.#workspaceContext as any)?.parentEntityType - : (this.#workspaceContext as any).entityType; + ? this.#workspaceContext?._internal_createUnderParentEntityType + : this.#workspaceContext?.entityType; const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; if (!entityType) throw new Error('Entity type is not available'); // If the entity type is different from the root entity type, then we can request the ancestors. if (entityType !== root?.entityType) { - const uniqueObservable = isNew ? (this.#workspaceContext as any)?.parentUnique : this.#workspaceContext?.unique; + const uniqueObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityUnique + : this.#workspaceContext?.unique; const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; if (!unique) throw new Error('Unique is not available'); @@ -83,11 +94,49 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex }); structureItems.push(...ancestorItems); + + this.#structure.setValue(structureItems); + this.#setParentData(structureItems); + this.#setAncestorData(data); } } + } + + #setParentData(structureItems: Array) { + /* If the item is not new, the current item is the last item in the array. + We filter out the current item unique to handle any case where it could show up */ + const parent = structureItems.filter((item) => item.unique !== this.#workspaceContext?.getUnique()).pop(); - const parent = structureItems[structureItems.length - 2]; + // TODO: remove this when the parent gets removed from the structure interface this.#parent.setValue(parent); - this.#structure.setValue(structureItems); + + const parentEntity = parent + ? { + unique: parent.unique, + entityType: parent.entityType, + } + : undefined; + + this.#parentContext.setParent(parentEntity); + } + + /* Notice: ancestors are based on the server "data" ancestors and are not based on the full Menu (UI) structure. + This will mean that any item placed in the data root will not have any ancestors. But will have a parent based on the UI structure. + */ + #setAncestorData(ancestors: Array) { + const ancestorEntities = ancestors + .map((treeItem) => { + const entity: UmbEntityModel = { + unique: treeItem.unique, + entityType: treeItem.entityType, + }; + + return entity; + }) + /* If the item is not new, the current item is the last item in the array. + We filter out the current item unique to handle any case where it could show up */ + .filter((item) => item.unique !== this.#workspaceContext?.getUnique()); + + this.#ancestorContext.setAncestors(ancestorEntities); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts new file mode 100644 index 000000000000..8cd8e1c75005 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.context-token.ts @@ -0,0 +1,10 @@ +import type { UmbMenuVariantStructureWorkspaceContext } from './menu-variant-structure-workspace-context.interface.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT = + new UmbContextToken( + 'UmbWorkspaceContext', + 'UmbMenuStructure', + (context): context is UmbMenuVariantStructureWorkspaceContext => + 'IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT' in context, + ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts new file mode 100644 index 000000000000..ade079e3ccf8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-structure-workspace-context.interface.ts @@ -0,0 +1,7 @@ +import type { UmbVariantStructureItemModel } from './types.js'; +import type { UmbContext } from '@umbraco-cms/backoffice/class-api'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; + +export interface UmbMenuVariantStructureWorkspaceContext extends UmbContext { + structure: Observable; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts index 8268a9accc1f..1e483db90d52 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts @@ -1,36 +1,44 @@ import type { UmbVariantStructureItemModel } from './types.js'; -import type { UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; +import { UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT } from './menu-variant-structure-workspace-context.context-token.js'; +import type { UmbTreeItemModel, UmbTreeRepository, UmbTreeRootModel } from '@umbraco-cms/backoffice/tree'; import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; -import { UMB_VARIANT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbAncestorsEntityContext } from '@umbraco-cms/backoffice/entity'; +import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; } +// TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends UmbContextBase { - // TODO: add correct interface - #workspaceContext?: typeof UMB_VARIANT_WORKSPACE_CONTEXT.TYPE; + // + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs; #structure = new UmbArrayState([], (x) => x.unique); public readonly structure = this.#structure.asObservable(); #parent = new UmbObjectState(undefined); + /** + * @deprecated Will be removed in v.18: Use UMB_PARENT_ENTITY_CONTEXT instead. + */ public readonly parent = this.#parent.asObservable(); + #parentContext = new UmbParentEntityContext(this); #ancestorContext = new UmbAncestorsEntityContext(this); + public readonly IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT = true; + constructor(host: UmbControllerHost, args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs) { - // TODO: set up context token - super(host, 'UmbMenuStructureWorkspaceContext'); + super(host, UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT); + // 'UmbMenuStructureWorkspaceContext' is Obsolete, will be removed in v.18 + this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; - // TODO: Implement a Context Token that supports parentUnique, parentEntityType, entityType - this.consumeContext(UMB_VARIANT_WORKSPACE_CONTEXT, (instance) => { + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe( this.#workspaceContext?.unique, @@ -45,10 +53,12 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um async #requestStructure() { const isNew = this.#workspaceContext?.getIsNew(); - const uniqueObservable = isNew ? (this.#workspaceContext as any)?.parentUnique : this.#workspaceContext?.unique; + const uniqueObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityType + : this.#workspaceContext?.unique; const entityTypeObservable = isNew - ? (this.#workspaceContext as any)?.parentEntityType - : (this.#workspaceContext as any)?.entityType; + ? this.#workspaceContext?._internal_createUnderParentEntityUnique + : this.#workspaceContext?.entityType; let structureItems: Array = []; @@ -58,7 +68,7 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; if (!entityType) throw new Error('Entity type is not available'); - // TODO: add correct tree variant item model + // TODO: introduce variant tree item model const treeRepository = await createExtensionApiByAlias>( this, this.#args.treeRepositoryAlias, @@ -79,7 +89,7 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um const { data } = await treeRepository.requestTreeItemAncestors({ treeItem: { unique, entityType } }); if (data) { - const ancestorItems = data.map((treeItem) => { + const treeItemAncestors = data.map((treeItem) => { return { unique: treeItem.unique, entityType: treeItem.entityType, @@ -93,20 +103,49 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um }; }); - const ancestorEntities = data.map((treeItem) => { - return { + structureItems.push(...treeItemAncestors); + + this.#structure.setValue(structureItems); + this.#setParentData(structureItems); + this.#setAncestorData(data); + } + } + + #setParentData(structureItems: Array) { + /* If the item is not new, the current item is the last item in the array. + We filter out the current item unique to handle any case where it could show up */ + const parent = structureItems.filter((item) => item.unique !== this.#workspaceContext?.getUnique()).pop(); + + // TODO: remove this when the parent gets removed from the structure interface + this.#parent.setValue(parent); + + const parentEntity = parent + ? { + unique: parent.unique, + entityType: parent.entityType, + } + : undefined; + + this.#parentContext.setParent(parentEntity); + } + + /* Notice: ancestors are based on the server "data" ancestors and are not based on the full Menu (UI) structure. + This will mean that any item placed in the data root will not have any ancestors. But will have a parent based on the UI structure. + */ + #setAncestorData(ancestors: Array) { + const ancestorEntities = ancestors + .map((treeItem) => { + const entity: UmbEntityModel = { unique: treeItem.unique, entityType: treeItem.entityType, }; - }); - - this.#ancestorContext.setAncestors(ancestorEntities); - structureItems.push(...ancestorItems); + return entity; + }) + /* If the item is not new, the current item is the last item in the array. + We filter out the current item unique to handle any case where it could show up */ + .filter((item) => item.unique !== this.#workspaceContext?.getUnique()); - const parent = structureItems[structureItems.length - 2]; - this.#parent.setValue(parent); - this.#structure.setValue(structureItems); - } + this.#ancestorContext.setAncestors(ancestorEntities); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts index d54c73fa52eb..4fbe32d81504 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/section-sidebar-menu-with-entity-actions.element.ts @@ -1,8 +1,9 @@ import { UmbSectionSidebarMenuElement } from '../section-sidebar-menu/section-sidebar-menu.element.js'; import type { ManifestSectionSidebarAppMenuWithEntityActionsKind } from '../section-sidebar-menu/types.js'; -import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, type PropertyValues, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbParentEntityContext } from '@umbraco-cms/backoffice/entity'; const manifestWithEntityActions: UmbExtensionManifestKind = { type: 'kind', @@ -18,13 +19,28 @@ umbExtensionsRegistry.register(manifestWithEntityActions); @customElement('umb-section-sidebar-menu-with-entity-actions') export class UmbSectionSidebarMenuWithEntityActionsElement extends UmbSectionSidebarMenuElement { + @state() + _unique = null; + + @state() + _entityType?: string | null; + + #parentContext = new UmbParentEntityContext(this); + + protected override updated(_changedProperties: PropertyValues): void { + if (_changedProperties.has('manifest')) { + const entityType = this.manifest?.meta.entityType; + this.#parentContext.setParent(entityType ? { unique: this._unique, entityType } : undefined); + } + } + override renderHeader() { return html`