diff --git a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js index d6e5febf2e30..39746504b55e 100644 --- a/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js +++ b/src/Umbraco.Web.UI.Client/devops/module-dependencies/index.js @@ -4,7 +4,7 @@ import { createImportMap } from '../importmap/index.js'; const ILLEGAL_CORE_IMPORTS_THRESHOLD = 5; const SELF_IMPORTS_THRESHOLD = 0; -const BIDIRECTIONAL_IMPORTS_THRESHOLD = 18; +const BIDIRECTIONAL_IMPORTS_THRESHOLD = 16; const clientProjectRoot = path.resolve(import.meta.dirname, '../../'); const modulePrefix = '@umbraco-cms/backoffice/'; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts new file mode 100644 index 000000000000..7df5c2dc2579 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/collapse-menu-item.entity-action.ts @@ -0,0 +1,11 @@ +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UMB_MENU_ITEM_CONTEXT } from '@umbraco-cms/backoffice/menu'; + +export class ExampleCollapseMenuItemEntityAction extends UmbEntityActionBase { + override async execute() { + const context = await this.getContext(UMB_MENU_ITEM_CONTEXT); + context?.expansion.collapseAll(); + } +} + +export { ExampleCollapseMenuItemEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts new file mode 100644 index 000000000000..69123c7f0c45 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/collapse-menu-item-entity-action/manifests.ts @@ -0,0 +1,15 @@ +export const manifests: Array = [ + { + type: 'entityAction', + kind: 'default', + alias: 'Umb.EntityAction.Document.MenuItem.Collapse', + name: 'Collapse Document Menu Item', + api: () => import('./collapse-menu-item.entity-action.js'), + forEntityTypes: ['document-type-root', 'media-type-root', 'member-type-root', 'data-type-root'], + weight: -10, + meta: { + label: 'Collapse Menu Item', + icon: 'icon-wand', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts new file mode 100644 index 000000000000..c911275efd3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/index.ts @@ -0,0 +1,4 @@ +import { manifests as playgroundDashboardManifests } from './playground-dashboard/manifests.js'; +import { manifests as collapseMenuItemManifests } from './collapse-menu-item-entity-action/manifests.js'; + +export const manifests: Array = [...playgroundDashboardManifests, ...collapseMenuItemManifests]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts new file mode 100644 index 000000000000..d9d47be8b013 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/manifests.ts @@ -0,0 +1,14 @@ +export const manifests: Array = [ + { + type: 'dashboard', + kind: 'default', + name: 'Example Section Sidebar Menu Playground Dashboard', + alias: 'Example.Dashboard.SectionSidebarMenuPlayground', + element: () => import('./section-sidebar-menu-playground.element.js'), + weight: 3000, + meta: { + label: 'Section Sidebar Menu Playground', + pathname: 'section-sidebar-menu-playground', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts new file mode 100644 index 000000000000..5f82b4fc45fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/section-sidebar-menu-expansion/playground-dashboard/section-sidebar-menu-playground.element.ts @@ -0,0 +1,102 @@ +import { html, customElement, LitElement, css, state, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { + UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, + UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, + type UmbSectionMenuItemExpansionEntryModel, +} from '@umbraco-cms/backoffice/menu'; + +@customElement('example-section-sidebar-menu-playground-dashboard') +export class ExampleSectionSidebarMenuPlaygroundDashboard extends UmbElementMixin(LitElement) { + #globalContext?: typeof UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT.TYPE; + #sectionContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + + @state() + private _globalExpansion: Array = []; + + @state() + private _sectionExpansion: Array = []; + + constructor() { + super(); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, (section) => { + this.#globalContext = section; + this.#observeGlobalExpansion(); + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (section) => { + this.#sectionContext = section; + this.#observeSectionExpansion(); + }); + } + + #observeGlobalExpansion() { + this.observe(this.#globalContext?.expansion.expansion, (items) => { + this._globalExpansion = items || []; + }); + } + + #observeSectionExpansion() { + this.observe(this.#sectionContext?.expansion.expansion, (items) => { + this._sectionExpansion = items || []; + }); + } + + #onCloseItem(event: PointerEvent, item: UmbSectionMenuItemExpansionEntryModel) { + event.stopPropagation(); + this.#sectionContext?.expansion.collapseItem(item); + } + + #onCollapseSection() { + this.#sectionContext?.expansion.collapseAll(); + } + + override render() { + return html` + + + + Collapse All + ${repeat( + this._sectionExpansion, + (item) => item.entityType + item.unique, + (item) => this.#renderItem(item), + )} + + + ${repeat( + this._globalExpansion, + (item) => item.entityType + item.unique, + (item) => this.#renderItem(item), + )} + + `; + } + + #renderItem(item: UmbSectionMenuItemExpansionEntryModel) { + return html` + this.#onCloseItem(event, item)}>Close + `; + } + + static override styles = [ + css` + :host { + display: block; + padding: var(--uui-size-layout-1); + } + `, + ]; +} + +export { ExampleSectionSidebarMenuPlaygroundDashboard as element }; + +declare global { + interface HTMLElementTagNameMap { + 'example-section-sidebar-menu-playground-dashboard': ExampleSectionSidebarMenuPlaygroundDashboard; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts index 0550e6c52cc3..70f1a9202b93 100644 --- a/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/apps/backoffice/components/backoffice-main.element.ts @@ -78,6 +78,7 @@ export class UmbBackofficeMainElement extends UmbLitElement { this._routes = newRoutes; } + // TODO: v17. Remove this section context when we have api's as part of the section manifest. private _provideSectionContext(sectionManifest: ManifestSection) { if (!this._sectionContext) { this._sectionContext = new UmbSectionContext(this); 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 db63689544e5..97359f3a36a9 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 @@ -924,11 +924,20 @@ export abstract class UmbContentDetailWorkspaceContextBase< if (!eventContext) { throw new Error('Event context is missing'); } - const event = new UmbRequestReloadChildrenOfEntityEvent({ + + const reloadStructureEvent = new UmbRequestReloadStructureForEntityEvent({ entityType: parent.entityType, unique: parent.unique, }); - eventContext.dispatchEvent(event); + + eventContext.dispatchEvent(reloadStructureEvent); + + const reloadChildrenEvent = new UmbRequestReloadChildrenOfEntityEvent({ + entityType: parent.entityType, + unique: parent.unique, + }); + + eventContext.dispatchEvent(reloadChildrenEvent); } async #update(variantIds: Array, saveData: DetailModelType) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts index 069e41f5dbe4..41874e9787e2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts @@ -88,6 +88,18 @@ export class UmbExtensionSlotElement extends UmbLitElement { } #props?: Record = {}; + @property({ type: Object, attribute: false }) + set events(newVal: Record void> | undefined) { + this.#events = newVal; + if (this.#extensionsController) { + this.#addEventListenersToExtensionElement(); + } + } + get events(): Record void> | undefined { + return this.#events; + } + #events?: Record void> = {}; + @property({ type: String, attribute: 'default-element' }) public defaultElement?: string; @@ -104,6 +116,7 @@ export class UmbExtensionSlotElement extends UmbLitElement { } override disconnectedCallback(): void { // _permitted is reset as the extensionsController fires a callback on destroy. + this.#removeEventListenersFromExtensionElement(); this.#attached = false; this.#extensionsController?.destroy(); this.#extensionsController = undefined; @@ -121,6 +134,7 @@ export class UmbExtensionSlotElement extends UmbLitElement { this.filter, (extensionControllers) => { this._permitted = extensionControllers; + this.#addEventListenersToExtensionElement(); }, undefined, // We can leave the alias undefined as we destroy this our selfs. this.defaultElement, @@ -144,6 +158,34 @@ export class UmbExtensionSlotElement extends UmbLitElement { return this.renderMethod ? this.renderMethod(ext, i) : ext.component; }; + #addEventListenersToExtensionElement() { + this._permitted?.forEach((initializer) => { + const component = initializer.component as HTMLElement; + if (!component) return; + + const events = this.#events; + if (!events) return; + + Object.entries(events).forEach(([eventName, handler]) => { + component.addEventListener(eventName, handler); + }); + }); + } + + #removeEventListenersFromExtensionElement() { + this._permitted?.forEach((initializer) => { + const component = initializer.component as HTMLElement; + if (!component) return; + + const events = this.#events; + if (!events) return; + + Object.entries(events).forEach(([eventName, handler]) => { + component.removeEventListener(eventName, handler); + }); + }); + } + static override styles = css` :host { display: contents; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts index c73d5980e792..77dbe4fc3e13 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/index.ts @@ -2,6 +2,8 @@ export * from './conditions/index.js'; export * from './initializers/index.js'; export * from './registry.js'; export * from './utils/index.js'; +export * from './components/index.js'; + export type * from './models/types.js'; export type * from './extensions/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts index 6e60d0bfdd56..1da233a9bd71 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/action/types.ts @@ -1,4 +1,4 @@ -import type { MetaMenuItem } from '../../../menu-item.extension.js'; +import type { MetaMenuItem } from '../menu-item.extension.js'; import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; import type { UmbAction } from '@umbraco-cms/backoffice/action'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts new file mode 100644 index 000000000000..a86e19fbb31f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/constants.ts @@ -0,0 +1 @@ +export * from './menu-item.context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts new file mode 100644 index 000000000000..84c6d2441e0c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/is-menu-item-expansion-entry.guard.ts @@ -0,0 +1,16 @@ +import type { UmbMenuItemExpansionEntryModel } from '../../types.js'; + +/** + * Checks if the provided object is a Menu Item Expansion Entry. + * @param {object } object - The object to check. + * @returns {boolean } True if the object is a Menu Item Expansion Entry, false otherwise. + */ +export function isMenuItemExpansionEntry(object: unknown): object is UmbMenuItemExpansionEntryModel { + return ( + typeof object === 'object' && + object !== null && + 'entityType' in object && + 'unique' in object && + 'menuItemAlias' in object + ); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts new file mode 100644 index 000000000000..1598232fdcbd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/expansion/menu-item-expansion.manager.ts @@ -0,0 +1,116 @@ +import type { UmbMenuItemExpansionEntryModel } from '../../menu/types.js'; +import { UMB_MENU_CONTEXT } from '../../menu/constants.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager, type UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a menu item + * @exports + * @class UmbMenuItemExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbMenuItemExpansionManager extends UmbControllerBase { + #manager = new UmbEntityExpansionManager(this); + public readonly expansion = this.#manager.expansion; + + #menuItemAlias?: string; + #menuContext?: typeof UMB_MENU_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_MENU_CONTEXT, (menuContext) => { + this.#menuContext = menuContext; + this.#observeMenuExpansion(); + }); + } + + #observeMenuExpansion() { + if (!this.#menuItemAlias) return; + this.observe(this.#menuContext?.expansion.expansion, (items) => { + const itemsForMenuItem = items?.filter((item) => item.menuItemAlias === this.#menuItemAlias) || []; + this.#manager.setExpansion(itemsForMenuItem); + }); + } + + setMenuItemAlias(alias: string | undefined): void { + this.#menuItemAlias = alias; + this.#observeMenuExpansion(); + } + + /** + * Checks if an entry is expanded + * @param {UmbMenuItemExpansionEntryModel} entry The entry to check + * @returns {Observable} True if the entry is expanded + * @memberof UmbSectionSidebarMenuSectionExpansionManager + */ + isExpanded(entry: UmbMenuItemExpansionEntryModel): Observable { + return this.#manager.isExpanded(entry); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this.#manager.setExpansion(expansion); + this.#menuContext?.expansion.setExpansion(expansion); + } + + /** + * Gets the expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this.#manager.getExpansion(); + } + + /** + * Opens a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to open + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async expandItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.expandItem(entry); + this.#menuContext?.expansion.expandItem(entry); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entries The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entries: UmbEntityExpansionModel): void { + this.#manager.expandItems(entries); + this.#menuContext?.expansion.expandItems(entries); + } + + /** + * Closes a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to close + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.collapseItem(entry); + this.#menuContext?.expansion.collapseItem(entry); + } + + /** + * Closes all menu items + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + const localEntries = this.#manager.getExpansion(); + this.#manager.collapseAll(); + this.#menuContext?.expansion.collapseItems(localEntries); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts index 6b2eaef349aa..80ecd73a78b1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/index.ts @@ -1,2 +1,3 @@ export * from './menu-item-default.element.js'; +export * from './constants.js'; export * from './action/action-menu-item.api.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts index b902cd18e289..0aeab0024fc6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/link-menu-item.element.ts @@ -1,4 +1,4 @@ -import type { UmbMenuItemElement } from '../../../menu-item-element.interface.js'; +import type { UmbMenuItemElement } from '../menu-item-element.interface.js'; import type { ManifestMenuItemLinkKind } from './types.js'; import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts index 2bc68ac163a1..f5be9cefbebc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/link/types.ts @@ -1,4 +1,4 @@ -import type { ManifestMenuItem, MetaMenuItem } from '../../../menu-item.extension.js'; +import type { ManifestMenuItem, MetaMenuItem } from '../menu-item.extension.js'; export interface ManifestMenuItemLinkKind extends ManifestMenuItem { type: 'menuItem'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts index 269133145cb4..035c5f972c4b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-default.element.ts @@ -1,5 +1,5 @@ -import type { UmbMenuItemElement } from '../../menu-item-element.interface.js'; -import type { ManifestMenuItem } from '../../menu-item.extension.js'; +import type { UmbMenuItemElement } from './menu-item-element.interface.js'; +import type { ManifestMenuItem } from './menu-item.extension.js'; import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-element.interface.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item-element.interface.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item-element.interface.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts new file mode 100644 index 000000000000..f51e5f1042ea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbDefaultMenuItemContext } from './menu-item.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_ITEM_CONTEXT = new UmbContextToken('UmbMenuItemContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts new file mode 100644 index 000000000000..d20e137b1baf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.context.ts @@ -0,0 +1,22 @@ +import type { ManifestMenuItem } from '../../types.js'; +import { UmbMenuItemExpansionManager } from './expansion/menu-item-expansion.manager.js'; +import { UMB_MENU_ITEM_CONTEXT } from './menu-item.context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultMenuItemContext extends UmbContextBase { + public readonly expansion = new UmbMenuItemExpansionManager(this); + + #manifest?: ManifestMenuItem | undefined; + public get manifest(): ManifestMenuItem | undefined { + return this.#manifest; + } + public set manifest(value: ManifestMenuItem | undefined) { + this.#manifest = value; + this.expansion.setMenuItemAlias(value?.alias); + } + + constructor(host: UmbControllerHost) { + super(host, UMB_MENU_ITEM_CONTEXT); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.extension.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-item.extension.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/menu-item.extension.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts index 1e39b8bf82b8..ed3511d79644 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu-item/types.ts @@ -1,2 +1,4 @@ export type * from './action/types.js'; export type * from './link/types.js'; +export type * from './menu-item-element.interface.js'; +export type * from './menu-item.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts new file mode 100644 index 000000000000..95d903ca23e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/constants.ts @@ -0,0 +1 @@ +export * from './menu.context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts index 242a263f7af8..be0be98f9710 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/index.ts @@ -1 +1,2 @@ export * from './menu.element.js'; +export * from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts new file mode 100644 index 000000000000..6385f345782e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbDefaultMenuContext } from './menu.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_MENU_CONTEXT = new UmbContextToken('UmbMenuContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts new file mode 100644 index 000000000000..671373df7caf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.context.ts @@ -0,0 +1,13 @@ +import { UMB_MENU_CONTEXT } from './menu.context.token.js'; +import type { UmbMenuItemExpansionEntryModel } from './types.js'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultMenuContext extends UmbContextBase { + public readonly expansion = new UmbEntityExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_MENU_CONTEXT); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts index 7e051db0e0cd..eba3a96f403a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/menu.element.ts @@ -1,7 +1,11 @@ import type { ManifestMenu } from '../../menu.extension.js'; -import type { ManifestMenuItem } from '../../menu-item.extension.js'; +import { UmbDefaultMenuItemContext } from '../menu-item/menu-item.context.js'; +import type { ManifestMenuItem } from '../menu-item/types.js'; +import { UmbDefaultMenuContext } from './menu.context.js'; +import type { UmbMenuItemExpansionEntryModel } from './types.js'; import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; import '../menu-item/menu-item-default.element.js'; @@ -10,13 +14,23 @@ export class UmbMenuElement extends UmbLitElement { @property({ attribute: false }) manifest?: ManifestMenu; + public get expansion(): UmbEntityExpansionModel { + return this.#context.expansion.getExpansion(); + } + public set expansion(value: UmbEntityExpansionModel) { + this.#context.expansion.setExpansion(value); + } + + #context = new UmbDefaultMenuContext(this); + override render() { return html` - items.meta.menus.includes(this.manifest!.alias)}> - + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts new file mode 100644 index 000000000000..dc3d827be241 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/menu/types.ts @@ -0,0 +1,5 @@ +import type { UmbEntityExpansionEntryModel } from '@umbraco-cms/backoffice/utils'; + +export interface UmbMenuItemExpansionEntryModel extends UmbEntityExpansionEntryModel { + menuItemAlias: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts index c858b14791dd..e03ad3173595 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/components/types.ts @@ -1 +1,2 @@ export type * from './menu-item/types.js'; +export type * from './menu/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts new file mode 100644 index 000000000000..fd7b96c0f839 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu/constants.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 c7d6c36f9e18..cce4aa408e06 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 @@ -3,6 +3,7 @@ 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 * from './constants.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts index a9d3417c6195..350fea31bce1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/manifests.ts @@ -1,8 +1,12 @@ -import { manifests as menuItemManifests } from './components/menu-item/manifests.js'; import { manifest as menuAliasConditionManifest } from './conditions/menu-alias.condition.js'; +import { manifests as menuItemManifests } from './components/menu-item/manifests.js'; +import { manifests as sectionSidebarMenuManifests } from './section-sidebar-menu/manifests.js'; +import { manifests as sectionSidebarMenuWithEntityActionsManifests } from './section-sidebar-menu-with-entity-actions/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ ...menuItemManifests, + ...sectionSidebarMenuManifests, + ...sectionSidebarMenuWithEntityActionsManifests, menuAliasConditionManifest, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts new file mode 100644 index 000000000000..08be58ee57e1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-structure/types.ts @@ -0,0 +1,17 @@ +import type { ManifestWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; + +export interface ManifestWorkspaceContextMenuStructureKind extends ManifestWorkspaceContext { + type: 'workspaceContext'; + kind: 'menuStructure'; + meta: MetaWorkspaceContextMenuStructureKind; +} + +export interface MetaWorkspaceContextMenuStructureKind { + menuItemAlias: string; +} + +declare global { + interface UmbExtensionManifestMap { + umbManifestWorkspaceContextMenuStructureKind: ManifestWorkspaceContextMenuStructureKind; + } +} 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 5ebd0dc2ae90..048147ec65b3 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,5 +1,6 @@ -import type { UmbStructureItemModel } from './types.js'; +import type { ManifestWorkspaceContextMenuStructureKind, UmbStructureItemModel } from './types.js'; import { UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT } from './menu-structure-workspace-context.context-token.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu/index.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'; @@ -7,6 +8,8 @@ import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/back 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'; +import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; interface UmbMenuTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; @@ -14,6 +17,8 @@ interface UmbMenuTreeStructureWorkspaceContextBaseArgs { // TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContextBase { + manifest?: ManifestWorkspaceContextMenuStructureKind; + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuTreeStructureWorkspaceContextBaseArgs; @@ -28,6 +33,8 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex #parentContext = new UmbParentEntityContext(this); #ancestorContext = new UmbAncestorsEntityContext(this); + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + #isModalContext: boolean = false; constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) { super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT); @@ -35,18 +42,45 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; + this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => { + this.#isModalContext = modalContext !== undefined; + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (instance) => { + this.#sectionSidebarMenuContext = instance; + }); + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe(this.#workspaceContext?.unique, (value) => { if (!value) return; this.#requestStructure(); }); + + this.observe(this.#workspaceContext?.isNew, (value) => { + if (value === undefined) return; + this.#requestStructure(); + }); }); } async #requestStructure() { + const isNew = this.#workspaceContext?.getIsNew(); + const uniqueObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityUnique + : this.#workspaceContext?.unique; + const entityTypeObservable = isNew + ? this.#workspaceContext?._internal_createUnderParentEntityType + : this.#workspaceContext?.entityType; + let structureItems: Array = []; + const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; + if (unique === undefined) throw new Error('Unique is not available'); + + const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; + if (!entityType) throw new Error('Entity type is not available'); + const treeRepository = await createExtensionApiByAlias>( this, this.#args.treeRepositoryAlias, @@ -65,22 +99,10 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex ]; } - const isNew = this.#workspaceContext?.getIsNew(); - - const entityTypeObservable = isNew - ? 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'); + const isRoot = entityType === root?.entityType; // 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?._internal_createUnderParentEntityUnique - : this.#workspaceContext?.unique; - const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; - if (!unique) throw new Error('Unique is not available'); - + if (!isRoot) { const { data } = await treeRepository.requestTreeItemAncestors({ treeItem: { unique, entityType } }); if (data) { @@ -93,13 +115,19 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex }; }); - structureItems.push(...ancestorItems); - - this.#structure.setValue(structureItems); - this.#setParentData(structureItems); this.#setAncestorData(data); + + structureItems.push(...ancestorItems); } } + + this.#structure.setValue(structureItems); + this.#setParentData(structureItems); + + const menuItemAlias = this.manifest?.meta?.menuItemAlias; + if (menuItemAlias && !this.#isModalContext) { + this.#expandSectionSidebarMenu(structureItems, menuItemAlias); + } } #setParentData(structureItems: Array) { @@ -139,4 +167,25 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex this.#ancestorContext.setAncestors(ancestorEntities); } + + #expandSectionSidebarMenu(structureItems: Array, menuItemAlias: string) { + const linkedEntries = linkEntityExpansionEntries(structureItems); + // Filter out the current entity as we don't want to expand it + const expandableItems = linkedEntries.filter((item) => item.unique !== this.#workspaceContext?.getUnique()); + const expandableItemsWithMenuItem = expandableItems.map((item) => { + return { + ...item, + menuItemAlias, + }; + }); + this.#sectionSidebarMenuContext?.expansion.expandItems(expandableItemsWithMenuItem); + } + + override destroy(): void { + super.destroy(); + this.#structure.destroy(); + this.#parent.destroy(); + this.#parentContext.destroy(); + this.#ancestorContext.destroy(); + } } 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 db207951b03f..3ef07138e2f3 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,5 +1,6 @@ -import type { UmbVariantStructureItemModel } from './types.js'; +import type { ManifestWorkspaceContextMenuStructureKind, UmbVariantStructureItemModel } from './types.js'; import { UMB_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT } from './menu-variant-structure-workspace-context.context-token.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu/section-context/section-sidebar-menu.section-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'; @@ -7,6 +8,8 @@ import { UmbArrayState, UmbObjectState } from '@umbraco-cms/backoffice/observabl import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { treeRepositoryAlias: string; @@ -14,7 +17,8 @@ interface UmbMenuVariantTreeStructureWorkspaceContextBaseArgs { // TODO: introduce base class for all menu structure workspaces to handle ancestors and parent export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends UmbContextBase { - // + manifest?: ManifestWorkspaceContextMenuStructureKind; + #workspaceContext?: typeof UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT.TYPE; #args: UmbMenuVariantTreeStructureWorkspaceContextBaseArgs; @@ -29,6 +33,8 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um #parentContext = new UmbParentEntityContext(this); #ancestorContext = new UmbAncestorsEntityContext(this); + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + #isModalContext: boolean = false; public readonly IS_MENU_VARIANT_STRUCTURE_WORKSPACE_CONTEXT = true; @@ -38,6 +44,14 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.provideContext('UmbMenuStructureWorkspaceContext', this); this.#args = args; + this.consumeContext(UMB_MODAL_CONTEXT, (modalContext) => { + this.#isModalContext = modalContext !== undefined; + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (instance) => { + this.#sectionSidebarMenuContext = instance; + }); + this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.observe( @@ -108,6 +122,11 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.#structure.setValue(structureItems); this.#setParentData(structureItems); this.#setAncestorData(data); + + const menuItemAlias = this.manifest?.meta?.menuItemAlias; + if (menuItemAlias && !this.#isModalContext) { + this.#expandSectionSidebarMenu(structureItems, menuItemAlias); + } } } @@ -148,4 +167,25 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um this.#ancestorContext.setAncestors(ancestorEntities); } + + #expandSectionSidebarMenu(structureItems: Array, menuItemAlias: string) { + const linkedEntries = linkEntityExpansionEntries(structureItems); + // Filter out the current entity as we don't want to expand it + const expandableItems = linkedEntries.filter((item) => item.unique !== this.#workspaceContext?.getUnique()); + const expandableItemsWithMenuItem = expandableItems.map((item) => { + return { + ...item, + menuItemAlias, + }; + }); + this.#sectionSidebarMenuContext?.expansion.expandItems(expandableItemsWithMenuItem); + } + + override destroy(): void { + super.destroy(); + this.#structure.destroy(); + this.#parent.destroy(); + this.#parentContext.destroy(); + this.#ancestorContext.destroy(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts new file mode 100644 index 000000000000..4d7249e76c81 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu-with-entity-actions/manifests.ts @@ -0,0 +1,14 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.SectionSidebarAppMenuWithEntityActions', + matchKind: 'menuWithEntityActions', + matchType: 'sectionSidebarApp', + manifest: { + type: 'sectionSidebarApp', + element: () => import('./section-sidebar-menu-with-entity-actions.element.js'), + }, + }, +]; 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 adce5916467c..f7c885fc10e1 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,22 +1,8 @@ import { UmbSectionSidebarMenuElement } from '../section-sidebar-menu/section-sidebar-menu.element.js'; import type { ManifestSectionSidebarAppMenuWithEntityActionsKind } from '../section-sidebar-menu/types.js'; 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', - alias: 'Umb.Kind.SectionSidebarAppMenuWithEntityActions', - matchKind: 'menuWithEntityActions', - matchType: 'sectionSidebarApp', - manifest: { - type: 'sectionSidebarApp', - elementName: 'umb-section-sidebar-menu-with-entity-actions', - }, -}; -umbExtensionsRegistry.register(manifestWithEntityActions); - @customElement('umb-section-sidebar-menu-with-entity-actions') export class UmbSectionSidebarMenuWithEntityActionsElement extends UmbSectionSidebarMenuElement { @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts new file mode 100644 index 000000000000..125299d13ae6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/constants.ts @@ -0,0 +1,2 @@ +export * from './global-context/constants.js'; +export * from './section-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts new file mode 100644 index 000000000000..b55969770056 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu.global-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts new file mode 100644 index 000000000000..9e58ecca5924 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/expansion/section-sidebar-menu-app-expansion.manager.ts @@ -0,0 +1,23 @@ +import type { UmbSectionMenuItemExpansionEntryModel } from '../../types.js'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a section sidebar menu. + * @exports + * @class UmbSectionSidebarMenuAppExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbSectionSidebarMenuAppExpansionManager extends UmbEntityExpansionManager { + /** + * Returns an observable of the expansion state filtered by section alias. + * @param {string} sectionAlias The alias of the section to filter by. + * @returns {Observable} An observable of the expansion state for the specified section alias. + * @memberof UmbSectionSidebarMenuAppExpansionManager + */ + expansionBySectionAlias(sectionAlias: string): Observable { + return this._expansion.asObservablePart((entries) => + entries.filter((entry) => entry.sectionAlias === sectionAlias), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts new file mode 100644 index 000000000000..1812e1d5f6cb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/manifests.ts @@ -0,0 +1,10 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'globalContext', + alias: 'Umb.GlobalContext.SectionSidebarMenu', + name: 'Section Sidebar Menu Global Context', + api: () => import('./section-sidebar-menu.global-context.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts new file mode 100644 index 000000000000..20c8eb36fa84 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.token.ts @@ -0,0 +1,6 @@ +import type { UmbSectionSidebarMenuGlobalContext } from './section-sidebar-menu.global-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT = new UmbContextToken( + 'UmbSectionSidebarMenuGlobalContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts new file mode 100644 index 000000000000..afcd007487db --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/global-context/section-sidebar-menu.global-context.ts @@ -0,0 +1,14 @@ +import { UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT } from './section-sidebar-menu.global-context.token.js'; +import { UmbSectionSidebarMenuAppExpansionManager } from './expansion/section-sidebar-menu-app-expansion.manager.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbSectionSidebarMenuGlobalContext extends UmbContextBase { + public readonly expansion = new UmbSectionSidebarMenuAppExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT); + } +} + +export { UmbSectionSidebarMenuGlobalContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts index 2d3149a8d43d..0371f2072ea1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/index.ts @@ -1 +1,2 @@ export * from './section-sidebar-menu.element.js'; +export * from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts new file mode 100644 index 000000000000..b04a4c16615f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/manifests.ts @@ -0,0 +1,18 @@ +import { manifests as sectionContextManifests } from './section-context/manifests.js'; +import { manifests as globalContextManifests } from './global-context/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.SectionSidebarAppMenu', + matchKind: 'menu', + matchType: 'sectionSidebarApp', + manifest: { + type: 'sectionSidebarApp', + element: () => import('./section-sidebar-menu.element.js'), + }, + }, + ...sectionContextManifests, + ...globalContextManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts new file mode 100644 index 000000000000..75f40d61ad85 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/constants.ts @@ -0,0 +1 @@ +export * from './section-sidebar-menu.section-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts new file mode 100644 index 000000000000..162861d5b823 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/expansion/section-sidebar-menu-section-expansion.manager.ts @@ -0,0 +1,139 @@ +import { UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT } from '../../global-context/section-sidebar-menu.global-context.token.js'; +import type { UmbSectionMenuItemExpansionEntryModel } from '../../types.js'; +import type { UmbMenuItemExpansionEntryModel } from '../../../components/menu/types.js'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UMB_SECTION_CONTEXT } from '@umbraco-cms/backoffice/section'; +import { UmbEntityExpansionManager, type UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; + +/** + * Manages the expansion state of a section sidebar menu. + * @exports + * @class UmbSectionSidebarMenuSectionExpansionManager + * @augments {UmbControllerBase} + */ +export class UmbSectionSidebarMenuSectionExpansionManager extends UmbControllerBase { + #manager = new UmbEntityExpansionManager(this); + public readonly expansion = this.#manager.expansion; + + #sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE; + #globalContext?: typeof UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT.TYPE; + #currentSectionAlias?: string; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_SECTION_CONTEXT, (sectionContext) => { + this.#sectionContext = sectionContext; + this.#observeCurrentSectionAlias(); + }); + + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, (globalContext) => { + this.#globalContext = globalContext; + this.#observeGlobalMenuExpansion(); + }); + } + + #observeCurrentSectionAlias() { + this.observe(this.#sectionContext?.alias, (alias) => { + if (!alias) return; + this.#currentSectionAlias = alias; + }); + } + + #observeGlobalMenuExpansion() { + if (!this.#globalContext || !this.#currentSectionAlias) return; + this.observe(this.#globalContext?.expansion.expansionBySectionAlias(this.#currentSectionAlias), (expansion) => { + this.#manager.setExpansion(expansion); + }); + } + + /** + * Checks if an entry is expanded + * @param {UmbMenuItemExpansionEntryModel} entry The entry to check + * @returns {Observable} True if the entry is expanded + * @memberof UmbSectionSidebarMenuSectionExpansionManager + */ + isExpanded(entry: UmbMenuItemExpansionEntryModel): Observable { + return this.#manager.isExpanded(entry); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this.#manager.setExpansion(expansion); + const entries = this.#bindEntriesToSection(expansion); + this.#globalContext?.expansion.setExpansion(entries); + } + + /** + * Gets the expansion state + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this.#manager.getExpansion(); + } + + /** + * Opens a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to open + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async expandItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.expandItem(entry); + const entries = this.#bindEntriesToSection([entry]); + this.#globalContext?.expansion.expandItem(entries[0]); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entries The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entries: UmbEntityExpansionModel): void { + this.#manager.expandItems(entries); + const entriesWithSectionAlias = this.#bindEntriesToSection(entries); + this.#globalContext?.expansion.expandItems(entriesWithSectionAlias); + } + + /** + * Closes a menu item + * @param {UmbMenuItemExpansionEntryModel} entry The item to close + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: UmbMenuItemExpansionEntryModel): Promise { + this.#manager.collapseItem(entry); + const entries = this.#bindEntriesToSection([entry]); + this.#globalContext?.expansion.collapseItem(entries[0]); + } + + /** + * Closes all menu items + * @memberof UmbSectionSidebarMenuSectionExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + // Collapse all items in the global context matching the current section + const entries = this.#bindEntriesToSection(this.#manager.getExpansion()); + this.#manager.collapseAll(); + this.#globalContext?.expansion.collapseItems(entries); + } + + #bindEntriesToSection( + expansion: UmbEntityExpansionModel, + ): UmbEntityExpansionModel { + return expansion.map((item) => ({ + ...item, + sectionAlias: this.#currentSectionAlias, + })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts new file mode 100644 index 000000000000..19c4552e6d03 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/manifests.ts @@ -0,0 +1,10 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'sectionContext', + alias: 'Umb.SectionContext.SectionSidebarMenu', + name: 'Section Sidebar Menu Section Context', + api: () => import('./section-sidebar-menu.section-context.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts new file mode 100644 index 000000000000..e7411f87297f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.token.ts @@ -0,0 +1,6 @@ +import type { UmbSectionSidebarMenuSectionContext } from './section-sidebar-menu.section-context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT = new UmbContextToken( + 'UmbSectionSidebarMenuSectionContext', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts new file mode 100644 index 000000000000..e4cfe3861f78 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-context/section-sidebar-menu.section-context.ts @@ -0,0 +1,14 @@ +import { UmbSectionSidebarMenuSectionExpansionManager } from './expansion/section-sidebar-menu-section-expansion.manager.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-sidebar-menu.section-context.token.js'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbSectionSidebarMenuSectionContext extends UmbContextBase { + public readonly expansion = new UmbSectionSidebarMenuSectionExpansionManager(this); + + constructor(host: UmbControllerHost) { + super(host, UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT); + } +} + +export { UmbSectionSidebarMenuSectionContext as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts index 51b8b8c34dea..d89fd5575e81 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/section-sidebar-menu.element.ts @@ -1,23 +1,12 @@ import type { ManifestMenu } from '../menu.extension.js'; +import { isMenuItemExpansionEntry } from '../components/menu-item/expansion/is-menu-item-expansion-entry.guard.js'; import type { ManifestSectionSidebarAppBaseMenu, ManifestSectionSidebarAppMenuKind } from './types.js'; +import { UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT } from './section-context/section-sidebar-menu.section-context.token.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; - -// TODO: Move to separate file: -const manifest: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.SectionSidebarAppMenu', - matchKind: 'menu', - matchType: 'sectionSidebarApp', - manifest: { - type: 'sectionSidebarApp', - elementName: 'umb-section-sidebar-menu', - }, -}; -umbExtensionsRegistry.register(manifest); +import { UmbExpansionEntryCollapsedEvent, UmbExpansionEntryExpandedEvent } from '@umbraco-cms/backoffice/utils'; +import { UmbExtensionSlotElement } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-section-sidebar-menu') export class UmbSectionSidebarMenuElement< @@ -34,14 +23,73 @@ export class UmbSectionSidebarMenuElement< return html`

${this.localize.string(this.manifest?.meta?.label ?? '')}

`; } + #sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE; + + #extensionSlotElement = new UmbExtensionSlotElement(); + #muteStateUpdate = false; + + constructor() { + super(); + this.#initExtensionSlotElement(); + this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (context) => { + this.#sectionSidebarMenuContext = context; + this.#observeExpansion(); + }); + } + + #initExtensionSlotElement() { + /* For better performance and UX we prevent lit from doing unnecessary rerenders we programmatically create the element, + and manually update the props when needed. */ + + this.#extensionSlotElement.type = 'menu'; + this.#extensionSlotElement.filter = (menu: ManifestMenu) => menu.alias === this.manifest?.meta?.menu; + this.#extensionSlotElement.defaultElement = 'umb-menu'; + this.#extensionSlotElement.events = { + 'expansion-entry-expanded': this.#onExpansionChange.bind(this), + 'expansion-entry-collapsed': this.#onExpansionChange.bind(this), + }; + } + + #observeExpansion() { + this.observe(this.#sectionSidebarMenuContext?.expansion.expansion, (items) => { + if (this.#muteStateUpdate) return; + + this.#extensionSlotElement.props = { + expansion: items || [], + }; + }); + } + + #onExpansionChange(e: Event) { + const event = e as UmbExpansionEntryExpandedEvent | UmbExpansionEntryCollapsedEvent; + event.stopPropagation(); + const eventEntry = event.entry; + + if (!eventEntry) { + throw new Error('Entity is required to toggle expansion.'); + } + + // Only react to the event if it is a valid Menu Item Expansion Entry + if (isMenuItemExpansionEntry(eventEntry) === false) return; + + if (event.type === UmbExpansionEntryExpandedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#sectionSidebarMenuContext?.expansion.expandItem(eventEntry); + this.#muteStateUpdate = false; + } else if (event.type === UmbExpansionEntryCollapsedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#sectionSidebarMenuContext?.expansion.collapseItem(eventEntry); + this.#muteStateUpdate = false; + } + } + override render() { - return html` - ${this.renderHeader()} - - `; + return html` ${this.renderHeader()} ${this.#extensionSlotElement}`; + } + + override disconnectedCallback(): void { + super.disconnectedCallback(); + this.#extensionSlotElement.destroy(); } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts index af00746dafbf..85a1cf64ab4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/section-sidebar-menu/types.ts @@ -1,3 +1,4 @@ +import type { UmbMenuItemExpansionEntryModel } from '../components/menu/types.js'; import type { ManifestSectionSidebarApp } from '@umbraco-cms/backoffice/section'; export interface MetaSectionSidebarAppMenuKind { @@ -24,3 +25,7 @@ export interface ManifestSectionSidebarAppMenuWithEntityActionsKind extends Mani export interface MetaSectionSidebarAppMenuWithEntityActionsKind extends MetaSectionSidebarAppMenuKind { entityType: string; } + +export interface UmbSectionMenuItemExpansionEntryModel extends UmbMenuItemExpansionEntryModel { + sectionAlias?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts index 93dda2eb92fc..028299b81adf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/types.ts @@ -2,9 +2,9 @@ import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; export type * from './components/types.js'; export type * from './conditions/types.js'; -export type * from './menu-item-element.interface.js'; -export type * from './menu-item.extension.js'; +export type * from './menu-structure/types.js'; export type * from './menu.extension.js'; +export type * from './section-sidebar-menu/types.js'; // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbStructureItemModelBase extends UmbEntityModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts index f18f3e75f681..91dc47b6bf6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/conditions/section-alias/section-alias.condition.ts @@ -1,8 +1,8 @@ -import { UmbConditionBase } from '../../../extension-registry/conditions/condition-base.controller.js'; -import { UMB_SECTION_CONTEXT } from '../../section.context.js'; import type { SectionAliasConditionConfig } from '../types.js'; +import { UMB_SECTION_CONTEXT } from '../../section.context.token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; export class UmbSectionAliasCondition extends UmbConditionBase diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts new file mode 100644 index 000000000000..26378d491464 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.context.ts @@ -0,0 +1,8 @@ +import { UmbSectionContext } from '../section.context.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDefaultSectionContext extends UmbSectionContext { + constructor(host: UmbControllerHost) { + super(host); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts index 9b3d6fc293bf..aed2293213cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/default-section.element.ts @@ -1,6 +1,7 @@ -import type { ManifestSectionRoute } from './extensions/section-route.extension.js'; -import type { UmbSectionMainViewElement } from './section-main-views/section-main-views.element.js'; -import type { ManifestSection, UmbSectionElement } from './types.js'; +import type { ManifestSectionRoute } from '../extensions/section-route.extension.js'; +import type { UmbSectionMainViewElement } from '../section-main-views/section-main-views.element.js'; +import type { ManifestSection, UmbSectionElement } from '../types.js'; +import { UmbDefaultSectionContext } from './default-section.context.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, nothing, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @@ -21,7 +22,7 @@ import { UMB_MARK_ATTRIBUTE_NAME } from '@umbraco-cms/backoffice/const'; * @description - Element hosting sections and section navigation. */ @customElement('umb-section-default') -export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectionElement { +export class UmbDefaultSectionElement extends UmbLitElement implements UmbSectionElement { private _manifest?: ManifestSection | undefined; @property({ type: Object, attribute: false }) @@ -32,7 +33,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio const oldValue = this._manifest; if (oldValue === value) return; this._manifest = value; - + this.#api.setManifest(value); this.requestUpdate('manifest', oldValue); } @@ -45,6 +46,9 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio @state() private _splitPanelPosition = '300px'; + // TODO: v17: Move this to a manifest api. It will have to wait for a major as it will be a breaking change. + #api = new UmbDefaultSectionContext(this); + constructor() { super(); @@ -111,7 +115,7 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio ...routes, { path: '**', - component: () => import('./section-main-views/section-main-views.element.js'), + component: () => import('../section-main-views/section-main-views.element.js'), setup: (element) => { (element as UmbSectionMainViewElement).sectionAlias = this.manifest?.alias; }, @@ -177,8 +181,16 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio ]; } +export { UmbDefaultSectionElement as element }; + declare global { interface HTMLElementTagNameMap { - 'umb-section-default': UmbSectionDefaultElement; + 'umb-section-default': UmbDefaultSectionElement; } } + +/** + * + * @deprecated Since 16. Use UmbDefaultSectionElement instead. UmbSectionDefaultElement will be removed in v18. + */ +export { UmbDefaultSectionElement as UmbSectionDefaultElement }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts new file mode 100644 index 000000000000..1802584f0f23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/manifests.ts @@ -0,0 +1,20 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.Section.Default', + matchKind: 'default', + matchType: 'section', + manifest: { + type: 'section', + kind: 'default', + weight: 1000, + element: () => import('./default-section.element.js'), + meta: { + label: '', + pathname: '', + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts new file mode 100644 index 000000000000..17eeb41ff005 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/default/types.ts @@ -0,0 +1,11 @@ +import type { ManifestSection } from '../../section/extensions/index.js'; + +export interface ManifestSectionDefaultKind extends ManifestSection { + kind: 'default'; +} + +declare global { + interface UmbExtensionManifestMap { + umbDefaultSectionKind: ManifestSectionDefaultKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts new file mode 100644 index 000000000000..25e0e114cc2e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section-context.extension.ts @@ -0,0 +1,17 @@ +import type { ManifestApi, ManifestWithDynamicConditions, UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestSectionContext + extends ManifestWithDynamicConditions, + ManifestApi { + type: 'sectionContext'; + meta: MetaType; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface MetaSectionContext {} + +declare global { + interface UmbExtensionManifestMap { + UmbManifestSectionContext: ManifestSectionContext; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts index 63f363013bac..b3d90397f536 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/section.extension.ts @@ -2,6 +2,7 @@ import type { UmbSectionElement } from './section-element.interface.js'; import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; export interface ManifestSection + // TODO: v17 change to extend Element and Api extends ManifestElement, ManifestWithDynamicConditions { type: 'section'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts index e4d5de7f8b8f..9e65c9399679 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts @@ -1,3 +1,4 @@ +export type * from './section-context.extension.js'; export type * from './section-element.interface.js'; export type * from './section-sidebar-app-element.interface.js'; export type * from './section-view-element.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts index 9ac642e834b7..03fa9cfd53eb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/index.ts @@ -1,11 +1,10 @@ export * from './components/index.js'; export * from './constants.js'; -export * from './section-default.element.js'; +export * from './default/default-section.element.js'; export * from './section-main/index.js'; export * from './section-picker-modal/section-picker-modal.token.js'; export * from './section-sidebar-context-menu/index.js'; -export * from '../menu/section-sidebar-menu-with-entity-actions/index.js'; -export * from '../menu/section-sidebar-menu/index.js'; export * from './section-sidebar/index.js'; export * from './section.context.js'; +export * from './section.context.token.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts index 144a2b27049c..ebae159f82fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main/section-main.element.ts @@ -1,8 +1,9 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, LitElement, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-section-main') -export class UmbSectionMainElement extends LitElement { +export class UmbSectionMainElement extends UmbLitElement { override render() { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts new file mode 100644 index 000000000000..b70b7b5ebfe4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.token.ts @@ -0,0 +1,4 @@ +import type { UmbSectionContext } from './section.context.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_SECTION_CONTEXT = new UmbContextToken('UmbSectionContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts index fbb626897fb3..510697e14607 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section.context.ts @@ -1,8 +1,10 @@ import type { ManifestSection } from './extensions/section.extension.js'; +import { UMB_SECTION_CONTEXT } from './section.context.token.js'; import { UmbStringState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; export class UmbSectionContext extends UmbContextBase { #manifestAlias = new UmbStringState(undefined); @@ -12,8 +14,11 @@ export class UmbSectionContext extends UmbContextBase { public readonly pathname = this.#manifestPathname.asObservable(); public readonly label = this.#manifestLabel.asObservable(); + #sectionContextExtensionController?: UmbExtensionsApiInitializer; + constructor(host: UmbControllerHost) { super(host, UMB_SECTION_CONTEXT); + this.#createSectionContextExtensions(); } public setManifest(manifest?: ManifestSection) { @@ -25,6 +30,18 @@ export class UmbSectionContext extends UmbContextBase { getPathname() { return this.#manifestPathname.getValue(); } -} -export const UMB_SECTION_CONTEXT = new UmbContextToken('UmbSectionContext'); + #createSectionContextExtensions() { + if (this.#sectionContextExtensionController) { + this.#sectionContextExtensionController.destroy(); + } + + this.#sectionContextExtensionController = new UmbExtensionsApiInitializer( + this, + umbExtensionsRegistry, + 'sectionContext', + [], + undefined, + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts index a2144f8c1cc0..26f6bba57deb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/tree-expansion-manager.ts @@ -1,7 +1,8 @@ import type { UmbTreeExpansionModel } from './types.js'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; -import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observable-api'; +import type { Observable } from '@umbraco-cms/backoffice/observable-api'; +import { UmbEntityExpansionManager } from '@umbraco-cms/backoffice/utils'; /** * Manages the expansion state of a tree @@ -10,8 +11,8 @@ import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observab * @augments {UmbControllerBase} */ export class UmbTreeExpansionManager extends UmbControllerBase { - #expansion = new UmbArrayState([], (x) => x.unique); - expansion = this.#expansion.asObservable(); + #manager = new UmbEntityExpansionManager(this); + expansion = this.#manager.expansion; /** * Checks if an entity is expanded @@ -22,9 +23,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @memberof UmbTreeExpansionManager */ isExpanded(entity: UmbEntityModel): Observable { - return this.#expansion.asObservablePart((entries) => - entries?.some((entry) => entry.entityType === entity.entityType && entry.unique === entity.unique), - ); + return this.#manager.isExpanded(entity); } /** @@ -34,7 +33,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {void} */ setExpansion(expansion: UmbTreeExpansionModel): void { - this.#expansion.setValue(expansion); + this.#manager.setExpansion(expansion); } /** @@ -43,7 +42,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {UmbTreeExpansionModel} The expansion state */ getExpansion(): UmbTreeExpansionModel { - return this.#expansion.getValue(); + return this.#manager.getExpansion(); } /** @@ -55,7 +54,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async expandItem(entity: UmbEntityModel): Promise { - this.#expansion.appendOne(entity); + this.#manager.expandItem(entity); } /** @@ -67,7 +66,7 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async collapseItem(entity: UmbEntityModel): Promise { - this.#expansion.filter((x) => x.entityType !== entity.entityType || x.unique !== entity.unique); + this.#manager.collapseItem(entity); } /** @@ -76,6 +75,6 @@ export class UmbTreeExpansionManager extends UmbControllerBase { * @returns {Promise} */ public async collapseAll(): Promise { - this.#expansion.setValue([]); + this.#manager.collapseAll(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts index 7d5c0500296d..0585b8ed8c1a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/expansion-manager/types.ts @@ -1,3 +1,3 @@ -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import type { UmbEntityExpansionModel } from '@umbraco-cms/backoffice/utils'; -export type UmbTreeExpansionModel = Array; +export type UmbTreeExpansionModel = UmbEntityExpansionModel; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts index c947f5199f28..daa67a87892a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/index.ts @@ -7,7 +7,7 @@ export * from './entity-actions/reload-tree-item-children/index.js'; export * from './entity-actions/sort-children-of/index.js'; export * from './folder/index.js'; export * from './tree-item/index.js'; -export * from './tree-menu-item-default/index.js'; +export * from './tree-menu-item/index.js'; export * from './tree.element.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts index e1972b17cc9a..ec8a28e85f2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/manifests.ts @@ -1,8 +1,9 @@ -import { manifests as folderManifests } from './folder/manifests.js'; import { manifests as defaultTreeItemManifests } from './tree-item/tree-item-default/manifests.js'; import { manifests as defaultTreeManifests } from './default/manifests.js'; -import { manifests as treePickerManifests } from './tree-picker-modal/manifests.js'; import { manifests as entityActionManifests } from './entity-actions/manifests.js'; +import { manifests as folderManifests } from './folder/manifests.js'; +import { manifests as treeMenuItemManifests } from './tree-menu-item/manifests.js'; +import { manifests as treePickerManifests } from './tree-picker-modal/manifests.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -10,5 +11,6 @@ export const manifests: Array = ...defaultTreeManifests, ...entityActionManifests, ...folderManifests, + ...treeMenuItemManifests, ...treePickerManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts deleted file mode 100644 index af047cc2d43d..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tree-menu-item-default.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts deleted file mode 100644 index 93ce54fa6d57..000000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/tree-menu-item-default.element.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ManifestMenuItemTreeKind } from './types.js'; -import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import type { UmbMenuItemElement } from '@umbraco-cms/backoffice/menu'; - -// TODO: Move to separate file: -const manifest: UmbExtensionManifestKind = { - type: 'kind', - alias: 'Umb.Kind.Tree', - matchKind: 'tree', - matchType: 'menuItem', - manifest: { - type: 'menuItem', - elementName: 'umb-menu-item-tree-default', - }, -}; -umbExtensionsRegistry.register(manifest); - -@customElement('umb-menu-item-tree-default') -export class UmbMenuItemTreeDefaultElement extends UmbLitElement implements UmbMenuItemElement { - @property({ type: Object }) - manifest?: ManifestMenuItemTreeKind; - - override render() { - return this.manifest - ? html` - - ` - : nothing; - } -} - -export default UmbMenuItemTreeDefaultElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-menu-item-tree-default': UmbMenuItemTreeDefaultElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts new file mode 100644 index 000000000000..c4d5f8a3f079 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/index.ts @@ -0,0 +1 @@ +export * from './tree-menu-item.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts new file mode 100644 index 000000000000..1ace3b635eb4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/manifests.ts @@ -0,0 +1,14 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.Tree', + matchKind: 'tree', + matchType: 'menuItem', + manifest: { + type: 'menuItem', + element: () => import('./tree-menu-item.element.js'), + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts new file mode 100644 index 000000000000..62624dd3d5f7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/tree-menu-item.element.ts @@ -0,0 +1,87 @@ +import type { ManifestMenuItemTreeKind } from './types.js'; +import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_MENU_ITEM_CONTEXT, type UmbMenuItemElement } from '@umbraco-cms/backoffice/menu'; +import { + UmbExpansionEntryCollapsedEvent, + UmbExpansionEntryExpandedEvent, + type UmbEntityExpansionModel, +} from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-menu-item-tree-default') +export class UmbMenuItemTreeDefaultElement extends UmbLitElement implements UmbMenuItemElement { + @property({ type: Object }) + manifest?: ManifestMenuItemTreeKind; + + @state() + private _menuItemExpansion: UmbEntityExpansionModel = []; + + #menuItemContext?: typeof UMB_MENU_ITEM_CONTEXT.TYPE; + #muteStateUpdate = false; + + constructor() { + super(); + + this.consumeContext(UMB_MENU_ITEM_CONTEXT, (context) => { + this.#menuItemContext = context; + this.#observeExpansion(); + }); + } + + #observeExpansion() { + this.observe(this.#menuItemContext?.expansion.expansion, (items) => { + if (this.#muteStateUpdate) return; + this._menuItemExpansion = items || []; + }); + } + + #onExpansionChange(event: UmbExpansionEntryExpandedEvent | UmbExpansionEntryCollapsedEvent) { + event.stopPropagation(); + const eventEntry = event.entry; + + if (!eventEntry) { + throw new Error('Entry is required to toggle expansion.'); + } + + if (!this.manifest) { + throw new Error('Manifest is required to toggle expansion.'); + } + + if (event.type === UmbExpansionEntryExpandedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#menuItemContext?.expansion.expandItem({ ...eventEntry, menuItemAlias: this.manifest.alias }); + this.#muteStateUpdate = false; + } else if (event.type === UmbExpansionEntryCollapsedEvent.TYPE) { + this.#muteStateUpdate = true; + this.#menuItemContext?.expansion.collapseItem({ ...eventEntry, menuItemAlias: this.manifest.alias }); + this.#muteStateUpdate = false; + } + } + + override render() { + return this.manifest + ? html` + + ` + : nothing; + } +} + +export default UmbMenuItemTreeDefaultElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-menu-item-tree-default': UmbMenuItemTreeDefaultElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/types.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item-default/types.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-menu-item/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts index 889ab0849b0b..a091f5e73269 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts @@ -18,6 +18,13 @@ export class UmbTreeElement extends UmbExtensionElementAndApiSlotElementBase extends UmbControllerBase { + protected _expansion = new UmbArrayState([], (x) => x.entityType + x.unique); + expansion = this._expansion.asObservable(); + + /** + * Checks if an entity is expanded + * @param {EntryModelType} entity The entity to check + * @returns {Observable} True if the entity is expanded + * @memberof UmbEntityExpansionManager + */ + isExpanded(entity: EntryModelType): Observable { + return this._expansion.asObservablePart((entries) => + entries?.some((entry) => entry.entityType === entity.entityType && entry.unique === entity.unique), + ); + } + + /** + * Sets the expansion state + * @param {UmbEntityExpansionModel | undefined} expansion The expansion state + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + setExpansion(expansion: UmbEntityExpansionModel): void { + this._expansion.setValue(expansion); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Gets the expansion state + * @memberof UmbEntityExpansionManager + * @returns {UmbEntityExpansionModel} The expansion state + */ + getExpansion(): UmbEntityExpansionModel { + return this._expansion.getValue(); + } + + /** + * Expands an entity + * @param {EntryModelType} entity The entity to open + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async expandItem(entity: EntryModelType): Promise { + this._expansion.appendOne(entity); + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryExpandedEvent(entity)); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Expands multiple entities + * @param {UmbEntityExpansionModel} entities The entities to open + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public expandItems(entities: UmbEntityExpansionModel): void { + if (!entities || entities.length === 0) return; + this._expansion.append(entities); + entities.forEach((entity) => { + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryExpandedEvent(entity)); + }); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses an entity + * @param {EntryModelType} entry The entity to open + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async collapseItem(entry: EntryModelType): Promise { + this._expansion.filter((x) => x.entityType !== entry.entityType || x.unique !== entry.unique); + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryCollapsedEvent(entry)); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses multiple entities + * @param {UmbEntityExpansionModel} entries The entities to close + * @memberof UmbEntityExpansionManager + * @returns {void} + */ + public collapseItems(entries: UmbEntityExpansionModel): void { + if (!entries || entries.length === 0) return; + this._expansion.filter( + (x) => !entries.some((entry) => entry.entityType === x.entityType && entry.unique === x.unique), + ); + entries.forEach((entry) => { + this.getHostElement()?.dispatchEvent(new UmbExpansionEntryCollapsedEvent(entry)); + }); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } + + /** + * Collapses all expanded entities + * @memberof UmbEntityExpansionManager + * @returns {Promise} + */ + public async collapseAll(): Promise { + this._expansion.setValue([]); + this.getHostElement()?.dispatchEvent(new UmbExpansionChangeEvent()); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts new file mode 100644 index 000000000000..bfc0374014f8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-change.event.ts @@ -0,0 +1,8 @@ +export class UmbExpansionChangeEvent extends Event { + public static readonly TYPE = 'expansion-change'; + + public constructor() { + // mimics the native change event + super(UmbExpansionChangeEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts new file mode 100644 index 000000000000..b946982ab964 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-collapsed.event.ts @@ -0,0 +1,12 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export class UmbExpansionEntryCollapsedEvent extends Event { + public static readonly TYPE = 'expansion-entry-collapsed'; + entry: EntryModelType; + + public constructor(entry: EntryModelType) { + // mimics the native change event + super(UmbExpansionEntryCollapsedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.entry = entry; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts new file mode 100644 index 000000000000..0831115d91fa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/expansion-entry-expanded.event.ts @@ -0,0 +1,12 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export class UmbExpansionEntryExpandedEvent extends Event { + public static readonly TYPE = 'expansion-entry-expanded'; + entry: EntryModelType; + + public constructor(entry: EntryModelType) { + // mimics the native change event + super(UmbExpansionEntryExpandedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.entry = entry; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts new file mode 100644 index 000000000000..7a4b74e03484 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/events/index.ts @@ -0,0 +1,3 @@ +export * from './expansion-change.event.js'; +export * from './expansion-entry-collapsed.event.js'; +export * from './expansion-entry-expanded.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts new file mode 100644 index 000000000000..ee4761b07d62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/index.ts @@ -0,0 +1,4 @@ +export * from './entity-expansion.manager.js'; +export * from './events/index.js'; +export * from './utils/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts new file mode 100644 index 000000000000..ada5d4b7dd4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/types.ts @@ -0,0 +1,9 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export type UmbEntityExpansionModel< + EntryModelType extends UmbEntityExpansionEntryModel = UmbEntityExpansionEntryModel, +> = Array; + +export interface UmbEntityExpansionEntryModel extends UmbEntityModel { + target?: UmbEntityModel; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts new file mode 100644 index 000000000000..510a193d399c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/index.ts @@ -0,0 +1 @@ +export * from './link-entity-expansion-entries.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts new file mode 100644 index 000000000000..5c9ec731832b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.test.ts @@ -0,0 +1,26 @@ +import { expect } from '@open-wc/testing'; +import { linkEntityExpansionEntries } from './link-entity-expansion-entries'; + +describe('LinkEntityExpansionEntries', () => { + const input = [ + { entityType: 'document', unique: '1' }, + { entityType: 'document', unique: '2' }, + { entityType: 'document', unique: '3' }, + ]; + + it('should return an array of expansion entries with target', () => { + const result = linkEntityExpansionEntries(input); + expect(result).to.be.an('array').that.has.lengthOf(3); + expect(result[0]).to.have.property('entityType', 'document'); + expect(result[0]).to.have.property('unique', '1'); + expect(result[0]).to.have.property('target').that.deep.equals({ entityType: 'document', unique: '2' }); + + expect(result[1]).to.have.property('entityType', 'document'); + expect(result[1]).to.have.property('unique', '2'); + expect(result[1]).to.have.property('target').that.deep.equals({ entityType: 'document', unique: '3' }); + + expect(result[2]).to.have.property('entityType', 'document'); + expect(result[2]).to.have.property('unique', '3'); + expect(result[2]).to.not.have.property('target'); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts new file mode 100644 index 000000000000..04759ec3a1f2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/expansion/utils/link-entity-expansion-entries.ts @@ -0,0 +1,22 @@ +import type { UmbEntityExpansionEntryModel, UmbEntityExpansionModel } from '../types.js'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export const linkEntityExpansionEntries = (data: Array): UmbEntityExpansionModel => { + return data.map((item, index) => { + const result: UmbEntityExpansionEntryModel = { + entityType: item.entityType, + unique: item.unique, + }; + + const next = data[index + 1]; + + if (next) { + result.target = { + entityType: next.entityType, + unique: next.unique, + }; + } + + return result; + }); +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts index da5a0ec8c6a4..99bd098a0225 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -5,6 +5,7 @@ export * from './deprecation/index.js'; export * from './diff/index.js'; export * from './direction/index.js'; export * from './download/blob-download.function.js'; +export * from './expansion/index.js'; export * from './get-guid-from-udi.function.js'; export * from './get-processed-image-url.function.js'; export * from './guard-manager/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts index c23a374acd0d..0fcb8deab4fe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-breadcrumb/workspace-menu-breadcrumb/workspace-menu-breadcrumb.element.ts @@ -14,6 +14,9 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { @state() private _structure: UmbStructureItemModel[] = []; + @state() + private _isNew: boolean = false; + // TODO: figure out the correct context type #workspaceContext?: any; #sectionContext?: typeof UMB_SECTION_CONTEXT.TYPE; @@ -28,6 +31,7 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { this.consumeContext(UMB_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance as any; + this.#observeIsNew(); this.#observeStructure(); this.#observeName(); }); @@ -38,16 +42,15 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { }); } - #observeStructure() { - if (!this.#structureContext || !this.#workspaceContext) return; - const isNew = this.#workspaceContext.getIsNew(); - + #observeIsNew() { this.observe( - this.#structureContext.structure, + this.#workspaceContext?.isNew, (value) => { - this._structure = isNew ? value : value.slice(0, -1); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._isNew = value || false; }, - 'menuStructureObserver', + 'breadcrumbWorkspaceIsNewObserver', ); } @@ -61,16 +64,30 @@ export class UmbWorkspaceBreadcrumbElement extends UmbLitElement { ); } + #observeStructure() { + if (!this.#structureContext || !this.#workspaceContext) return; + + this.observe( + this.#structureContext.structure, + (value) => { + this._structure = value; + }, + 'menuStructureObserver', + ); + } + #getHref(structureItem: UmbStructureItemModel) { if (structureItem.isFolder) return undefined; return `section/${this.#sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}`; } override render() { + const structure = this._isNew ? this._structure : this._structure.slice(0, -1); + return html` ${map( - this._structure, + structure, (structureItem) => html`${this.localize.string(structureItem.name)} = (manifest: unknown) => { return [{ manifest }]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/constants.ts index 71e09a57d2a9..3fa67e05c079 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/constants.ts @@ -1,6 +1,7 @@ export * from './collection/constants.js'; export * from './data-type-root/constants.js'; export * from './entity-actions/constants.js'; +export * from './menu/constants.js'; export * from './modals/constants.js'; export * from './paths.js'; export * from './reference/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/constants.ts new file mode 100644 index 000000000000..c6d19226cb19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_DATA_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.DataTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts index bcd1dda1babc..82e4e61b759b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts @@ -1,10 +1,11 @@ +import { UMB_DATA_TYPE_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.DataTypes', + alias: UMB_DATA_TYPE_MENU_ITEM_ALIAS, name: 'Data Types Menu Item', weight: 600, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Data Type Menu Structure Workspace Context', alias: 'Umb.Context.DataType.Menu.Structure', api: () => import('./data-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DATA_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts index eb8dd5d7b139..8ed0d150bdcd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/constants.ts @@ -6,5 +6,6 @@ export * from './repository/constants.js'; export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; +export * from './menu-item/constants.js'; export { UMB_DICTIONARY_ROOT_ENTITY_TYPE, UMB_DICTIONARY_ENTITY_TYPE } from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts new file mode 100644 index 000000000000..d2abd8712297 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/constants.ts @@ -0,0 +1 @@ +export const UMB_DICTIONARY_MENU_ITEM_ALIAS = 'Umb.MenuItem.Dictionary'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts index 03da8a1d404b..109608104cd6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts @@ -1,5 +1,6 @@ import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js'; +import { UMB_DICTIONARY_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_TRANSLATION_MENU_ALIAS } from '@umbraco-cms/backoffice/translation'; @@ -7,7 +8,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Dictionary', + alias: UMB_DICTIONARY_MENU_ITEM_ALIAS, name: 'Dictionary Menu Item', weight: 400, meta: { @@ -21,9 +22,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Dictionary Menu Structure Workspace Context', alias: 'Umb.Context.Dictionary.Menu.Structure', api: () => import('./dictionary-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DICTIONARY_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts index d07e60d1021c..c4577efe6ebb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/constants.ts @@ -5,5 +5,6 @@ export * from './repository/constants.js'; export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; +export * from './menu/constants.js'; export { UMB_DOCUMENT_TYPE_ENTITY_TYPE, UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE } from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts new file mode 100644 index 000000000000..6f56101e30f5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.DocumentTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts index 35ea56aca9f8..3712dcf334af 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts @@ -16,9 +16,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Document Type Menu Structure Workspace Context', alias: 'Umb.Context.DocumentType.Menu.Structure', api: () => import('./document-type-menu-structure.context.js'), + meta: { + menuItemAlias: 'Umb.MenuItem.DocumentTypes', + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts index b0cd03a572f1..8b2ea67a99c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/constants.ts @@ -4,6 +4,7 @@ export * from './collection/constants.js'; export * from './entity-actions/constants.js'; export * from './entity-bulk-actions/constants.js'; export * from './item/constants.js'; +export * from './menu/constants.js'; export * from './modals/constants.js'; export * from './paths.js'; export * from './property-dataset-context/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts new file mode 100644 index 000000000000..e2fd4065cc19 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_DOCUMENT_MENU_ITEM_ALIAS = 'Umb.MenuItem.Document'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts index dc764170937e..f50b53291103 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts @@ -1,4 +1,5 @@ import { UMB_DOCUMENT_TREE_ALIAS } from '../tree/index.js'; +import { UMB_DOCUMENT_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const UMB_CONTENT_MENU_ALIAS = 'Umb.Menu.Content'; @@ -12,7 +13,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Document', + alias: UMB_DOCUMENT_MENU_ITEM_ALIAS, name: 'Document Menu Item', weight: 200, meta: { @@ -24,9 +25,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Document Menu Structure Workspace Context', alias: 'Umb.Context.Document.Menu.Structure', api: () => import('./document-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_DOCUMENT_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts index c80e3ac5e4f6..86c491dd0079 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/constants.ts @@ -1,11 +1,12 @@ export * from './entity-actions/constants.js'; export * from './media-type-root/constants.js'; +export * from './menu/constants.js'; export * from './paths.js'; export * from './property-type/constants.js'; export * from './repository/constants.js'; +export * from './search/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; -export * from './search/constants.js'; export { UMB_MEDIA_TYPE_ENTITY_TYPE, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts new file mode 100644 index 000000000000..462c1600e7ba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_MEDIA_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.MediaTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts index 65107a259681..9272a5fafd40 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; +import { UMB_MEDIA_TYPE_MENU_ITEM_ALIAS } from './constants.js'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.MediaTypes', + alias: UMB_MEDIA_TYPE_MENU_ITEM_ALIAS, name: 'Media Types Menu Item', weight: 800, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Media Type Menu Structure Workspace Context', alias: 'Umb.Context.MediaType.Menu.Structure', api: () => import('./media-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEDIA_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts index 11ceb55bf092..34b70788f476 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/constants.ts @@ -1 +1,2 @@ export const UMB_MEDIA_MENU_ALIAS = 'Umb.Menu.Media'; +export const UMB_MEDIA_MENU_ITEM_ALIAS = 'Umb.MenuItem.Media'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts index a4bd3d6b8a4e..93bb4b6b5bdf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/menu/manifests.ts @@ -1,5 +1,5 @@ import { UMB_MEDIA_TREE_ALIAS } from '../constants.js'; -import { UMB_MEDIA_MENU_ALIAS } from './constants.js'; +import { UMB_MEDIA_MENU_ALIAS, UMB_MEDIA_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ @@ -11,7 +11,7 @@ export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Media', + alias: UMB_MEDIA_MENU_ITEM_ALIAS, name: 'Media Menu Item', weight: 100, meta: { @@ -23,9 +23,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Media Menu Structure Workspace Context', alias: 'Umb.Context.Media.Menu.Structure', api: () => import('./media-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEDIA_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts index 5f144502c4c1..1c409bd6f9d9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; export * from './member-type-root/constants.js'; +export * from './menu/constants.js'; export * from './paths.js'; export * from './property-type/constants.js'; export * from './repository/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts new file mode 100644 index 000000000000..9f23bcf60c88 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_MEMBER_TYPE_MENU_ITEM_ALIAS = 'Umb.MenuItem.MemberTypes'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts index 9c563c1bd2ae..b00f960418b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/menu/manifests.ts @@ -1,11 +1,11 @@ -import { UMB_MEMBER_TYPE_TREE_ALIAS } from '../constants.js'; +import { UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, UMB_MEMBER_TYPE_TREE_ALIAS } from '../constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.MemberTypes', + alias: UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, name: 'Member Type Menu Item', weight: 700, meta: { @@ -16,9 +16,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Member Type Menu Structure Workspace Context', alias: 'Umb.Context.MemberType.Menu.Structure', api: () => import('./member-type-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_MEMBER_TYPE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts index 6afae2870025..f582a679afdc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; +export * from './entity.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; -export * from './entity.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts new file mode 100644 index 000000000000..ad80a4635dea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS = 'Umb.MenuItem.PartialView'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts index 414875d9d6d8..7189714ccddf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/index.js'; +import { UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.PartialView', + alias: UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS, name: 'Partial View Menu Item', weight: 40, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Partial View Menu Structure Workspace Context', alias: 'Umb.Context.PartialView.Menu.Structure', api: () => import('./partial-view-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts index ae46cc2ec9b0..b7960bb90d80 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/menu/manifests.ts @@ -17,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Script Menu Structure Workspace Context', alias: 'Umb.Context.Script.Menu.Structure', api: () => import('./script-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_SCRIPT_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts index cb3c3c7348ec..1b0f9b850a05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/constants.ts @@ -1,5 +1,6 @@ export * from './entity-actions/constants.js'; export * from './global-components/constants.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './tree/constants.js'; export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts new file mode 100644 index 000000000000..ac1784982e9d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_STYLESHEET_MENU_ITEM_ALIAS = 'Umb.MenuItem.Stylesheets'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts index 6e69c7425824..b14e048c3b9b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_STYLESHEET_TREE_ALIAS } from '../constants.js'; +import { UMB_STYLESHEET_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Stylesheets', + alias: UMB_STYLESHEET_MENU_ITEM_ALIAS, name: 'Stylesheets Menu Item', weight: 20, meta: { @@ -16,9 +17,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Stylesheet Menu Structure Workspace Context', alias: 'Umb.Context.Stylesheet.Menu.Structure', api: () => import('./stylesheet-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_STYLESHEET_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts index 9879ba5d2ce6..eac8bbbe487f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/constants.ts @@ -1,4 +1,5 @@ export * from './conditions/constants.js'; +export * from './menu/constants.js'; export * from './repository/constants.js'; export * from './search/constants.js'; export * from './workspace/constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts new file mode 100644 index 000000000000..0b25774bd486 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/constants.ts @@ -0,0 +1 @@ +export const UMB_TEMPLATE_MENU_ITEM_ALIAS = 'Umb.MenuItem.Templates'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts index 3c63d3fe2bd8..02f0e9dd3d09 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/menu/manifests.ts @@ -1,11 +1,12 @@ import { UMB_TEMPLATE_TREE_ALIAS } from '../tree/index.js'; +import { UMB_TEMPLATE_MENU_ITEM_ALIAS } from './constants.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { type: 'menuItem', kind: 'tree', - alias: 'Umb.MenuItem.Templates', + alias: UMB_TEMPLATE_MENU_ITEM_ALIAS, name: 'Templates Menu Item', weight: 40, meta: { @@ -17,9 +18,13 @@ export const manifests: Array = [ }, { type: 'workspaceContext', + kind: 'menuStructure', name: 'Template Menu Structure Workspace Context', alias: 'Umb.Context.Template.Menu.Structure', api: () => import('./template-menu-structure.context.js'), + meta: { + menuItemAlias: UMB_TEMPLATE_MENU_ITEM_ALIAS, + }, conditions: [ { alias: UMB_WORKSPACE_CONDITION_ALIAS, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 32ed4bb4d7cc..fba443cc7d8e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.37", - "@umbraco/playwright-testhelpers": "^16.0.34", + "@umbraco/playwright-testhelpers": "^16.0.35", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -67,9 +67,10 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "16.0.34", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.34.tgz", - "integrity": "sha512-hCOqSUrTVZPNxD3DP+olYz/QFc8HwyZ1QZR6gTv87nIkAlvEjk44+7KblPartfBXQDd93uvasptr7dO3XCapZA==", + "version": "16.0.35", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.35.tgz", + "integrity": "sha512-GyyN/GA//uFt/hv7FT/jLN7loTYKYzdr0jq15Fr3lcBEzIjpYoBAb+7IlUjWzkZTkbxU6F0HvTrbTg2fZyyTBA==", + "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.37", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 09fce13b54af..694da1116902 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.37", - "@umbraco/playwright-testhelpers": "^16.0.34", + "@umbraco/playwright-testhelpers": "^16.0.35", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" } -} \ No newline at end of file +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts index 40e475199dbc..109ea86a0abf 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ChildrenContent.spec.ts @@ -41,10 +41,10 @@ test('can create child node', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = expect(await umbracoApi.document.doesNameExist(childContentName)).toBeTruthy(); const childData = await umbracoApi.document.getChildren(contentId); expect(childData[0].variants[0].name).toBe(childContentName); - // verify that the child content displays in the tree after reloading children + // Verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); await umbracoUi.content.clickReloadChildrenActionMenuOption(); - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.doesContentTreeHaveName(childContentName); // Clean @@ -67,7 +67,7 @@ test('can create child node in child node', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.goToSection(ConstantHelper.sections.content); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickCreateActionMenuOption(); await umbracoUi.content.chooseDocumentType(childOfChildDocumentTypeName); @@ -80,7 +80,7 @@ test('can create child node in child node', async ({umbracoApi, umbracoUi}) => { const childOfChildData = await umbracoApi.document.getChildren(childContentId); expect(childOfChildData[0].variants[0].name).toBe(childOfChildContentName); // verify that the child content displays in the tree - await umbracoUi.content.clickCaretButtonForContentName(childContentName); + await umbracoUi.content.openContentCaretButtonForName(childContentName); await umbracoUi.content.doesContentTreeHaveName(childOfChildContentName); // Clean @@ -98,7 +98,7 @@ test('cannot publish child if the parent is not published', async ({umbracoApi, await umbracoUi.content.goToSection(ConstantHelper.sections.content); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickPublishActionMenuOption(); await umbracoUi.content.clickConfirmToPublishButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts index 86124bbebd2d..65437ebcacd7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts @@ -84,7 +84,7 @@ test('can create multiple child nodes with different document types', async ({um // verify that the child content displays in the tree after reloading children await umbracoUi.content.clickActionsMenuForContent(contentName); await umbracoUi.content.clickReloadChildrenActionMenuOption(); - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.doesContentTreeHaveName(firstChildContentName); await umbracoUi.content.doesContentTreeHaveName(secondChildContentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts index 8d0af1b43bb0..6c509ff7bb88 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts @@ -164,7 +164,7 @@ test.skip('child is removed from list after child content is deleted', async ({u expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); // Act - await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.openContentCaretButtonForName(contentName); await umbracoUi.content.clickActionsMenuForContent(childContentName); await umbracoUi.content.clickTrashActionMenuOption(); await umbracoUi.content.clickConfirmTrashButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 7429e8b2e14b..0a274fdd1ba6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts @@ -114,7 +114,7 @@ test('can create a folder in a folder in a folder', async ({umbracoApi, umbracoU // Act await umbracoUi.dataType.clickRootFolderCaretButton(); - await umbracoUi.dataType.clickCaretButtonForName(dataTypeFolderName); + await umbracoUi.dataType.openCaretButtonForName(dataTypeFolderName); await umbracoUi.dataType.clickActionsMenuForDataType(childFolderName); await umbracoUi.dataType.createDataTypeFolder(childOfChildFolderName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts index 8861d71dcc8f..0e2560c55d49 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts @@ -126,7 +126,7 @@ test('can allow bulk move in the media section', async ({umbracoApi, umbracoUi}) await umbracoUi.media.selectMediaWithName(secondMediaFileName); await umbracoUi.waitForTimeout(200); await umbracoUi.media.clickBulkMoveToButton(); - await umbracoUi.media.clickCaretButtonForName('Media'); + await umbracoUi.media.openCaretButtonForName('Media'); await umbracoUi.media.clickModalTextByName(mediaFolderName); await umbracoUi.media.clickChooseModalButton(); await umbracoUi.waitForTimeout(500); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 954ce8827201..251f455072bd 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -137,10 +137,8 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - //await umbracoUi.media.waitForMediaItemToBeCreated(); // This is flaky, and Playwright seems to succeed even with its default timeout await umbracoUi.media.isMediaTreeItemVisible(parentFolderName); - await umbracoUi.media.isMediaTreeItemVisible(folderName, false); - await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); + await umbracoUi.media.openMediaCaretButtonForName(parentFolderName); await umbracoUi.media.isMediaTreeItemVisible(folderName, true); // Clean diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts index 7ce674fea0b5..4c571df2245f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts @@ -25,7 +25,7 @@ test('can create a empty document type folder', {tag: '@smoke'}, async ({umbraco await umbracoUi.documentType.waitForDocumentTypeToBeCreated(); expect(await umbracoApi.documentType.doesNameExist(documentFolderName)).toBeTruthy(); // Checks if the folder is in the root - await umbracoUi.documentType.clickCaretButtonForName('Document Types'); + await umbracoUi.documentType.openCaretButtonForName('Document Types'); await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName); }); @@ -105,7 +105,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); await umbracoUi.documentType.clickRootFolderCaretButton(); - await umbracoUi.documentType.clickCaretButtonForName(grandParentFolderName); + await umbracoUi.documentType.openCaretButtonForName(grandParentFolderName); await umbracoUi.documentType.clickActionsMenuForName(parentFolderName); await umbracoUi.documentType.clickCreateActionMenuOption(); await umbracoUi.documentType.clickCreateDocumentFolderButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts index f249e10f2379..fa19f68f4d2c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts @@ -82,7 +82,7 @@ test('can create a media type folder in a folder', async ({umbracoApi, umbracoUi // Assert await umbracoUi.mediaType.waitForMediaTypeToBeCreated(); - await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); + await umbracoUi.mediaType.openCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const parentFolderChildren = await umbracoApi.mediaType.getChildren(parentFolderId); expect(parentFolderChildren[0].name).toBe(childFolderName); @@ -102,7 +102,7 @@ test('can create a media type folder in a folder in a folder', async ({umbracoAp // Act await umbracoUi.mediaType.clickRootFolderCaretButton(); - await umbracoUi.mediaType.clickCaretButtonForName(grandparentFolderName); + await umbracoUi.mediaType.openCaretButtonForName(grandparentFolderName); await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeFolderName); await umbracoUi.mediaType.clickCreateActionMenuOption(); await umbracoUi.mediaType.clickFolderButton(); @@ -111,7 +111,7 @@ test('can create a media type folder in a folder in a folder', async ({umbracoAp // Assert await umbracoUi.mediaType.waitForMediaTypeToBeCreated(); - await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); + await umbracoUi.mediaType.openCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const grandParentFolderChildren = await umbracoApi.mediaType.getChildren(grandParentFolderId); expect(grandParentFolderChildren[0].name).toBe(mediaTypeFolderName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts index 3cd2806f9bd1..642cda5801e3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts @@ -69,8 +69,7 @@ test('can create a partial view in a folder', async ({umbracoApi, umbracoUi}) => const childrenData = await umbracoApi.partialView.getChildren(folderPath); expect(childrenData[0].name).toEqual(partialViewFileName); // Verify the partial view is displayed in the folder under the Partial Views section - await umbracoUi.partialView.isPartialViewRootTreeItemVisible(partialViewFileName, false, false); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(partialViewFileName, true, false); // Clean @@ -86,7 +85,7 @@ test('can create a partial view in a folder in a folder', async ({umbracoApi, um //Act await umbracoUi.partialView.reloadPartialViewTree(); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.clickActionsMenuForPartialView(childFolderName); await umbracoUi.partialView.clickCreateOptionsActionMenuOption(); await umbracoUi.partialView.clickNewEmptyPartialViewButton(); @@ -118,7 +117,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.partialView.doesNameExist(childFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(childFolderName, true, false); }); @@ -132,7 +131,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.partialView.reloadPartialViewTree(); - await umbracoUi.partialView.clickCaretButtonForName(folderName); + await umbracoUi.partialView.openCaretButtonForName(folderName); await umbracoUi.partialView.clickActionsMenuForPartialView(childFolderName); await umbracoUi.partialView.createPartialViewFolder(childOfChildFolderName); @@ -141,7 +140,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.partialView.doesNameExist(childOfChildFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName + '/' + childFolderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.partialView.clickCaretButtonForName(childFolderName); + await umbracoUi.partialView.openCaretButtonForName(childFolderName); await umbracoUi.partialView.isPartialViewRootTreeItemVisible(childOfChildFolderName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts index 9698acb51feb..88117c05b7a6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts @@ -65,7 +65,7 @@ test('can create a script in a folder', async ({umbracoApi, umbracoUi}) => { expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + scriptName); const scriptData = await umbracoApi.script.get(scriptChildren[0].path); expect(scriptData.content).toBe(scriptContent); - await umbracoUi.stylesheet.clickCaretButtonForName(scriptFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(scriptFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName, true, false); }); @@ -85,7 +85,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.script.doesNameExist(childFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(scriptFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(scriptFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(childFolderName, true, false); }); @@ -99,7 +99,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.script.reloadScriptTree(); - await umbracoUi.script.clickCaretButtonForName(scriptFolderName); + await umbracoUi.script.openCaretButtonForName(scriptFolderName); await umbracoUi.script.clickActionsMenuForScript(childFolderName); await umbracoUi.script.createScriptFolder(childOfChildFolderName); @@ -108,7 +108,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.script.doesNameExist(childOfChildFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(childOfChildFolderName, true, false); }); @@ -121,7 +121,7 @@ test('can create a script in a folder in a folder', async ({umbracoApi, umbracoU // Act await umbracoUi.script.reloadScriptTree(); - await umbracoUi.script.clickCaretButtonForName(scriptFolderName); + await umbracoUi.script.openCaretButtonForName(scriptFolderName); await umbracoUi.script.clickActionsMenuForScript(childFolderName); await umbracoUi.script.clickCreateOptionsActionMenuOption(); await umbracoUi.script.clickNewJavascriptFileButton(); @@ -133,7 +133,7 @@ test('can create a script in a folder in a folder', async ({umbracoApi, umbracoU expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + scriptName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts index 3c270b6d37c2..b2c9f045286f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts @@ -59,7 +59,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.stylesheet.doesNameExist(childFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(childFolderName, true, false); }); @@ -73,7 +73,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb // Act await umbracoUi.stylesheet.reloadStylesheetTree(); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.clickActionsMenuForStylesheet(childFolderName); await umbracoUi.stylesheet.createStylesheetFolder(childOfChildFolderName); @@ -82,7 +82,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb expect(await umbracoApi.stylesheet.doesNameExist(childOfChildFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName + '/' + childFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + childOfChildFolderName); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(childOfChildFolderName, true, false); }); @@ -108,7 +108,7 @@ test('can create a stylesheet in a folder', async ({umbracoApi, umbracoUi}) => { expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + stylesheetName); const stylesheetData = await umbracoApi.stylesheet.get(stylesheetChildren[0].path); expect(stylesheetData.content).toBe(stylesheetContent); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, true, false); }); @@ -122,7 +122,7 @@ test('can create a stylesheet in a folder in a folder', async ({umbracoApi, umbr //Act await umbracoUi.stylesheet.reloadStylesheetTree(); - await umbracoUi.stylesheet.clickCaretButtonForName(stylesheetFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(stylesheetFolderName); await umbracoUi.stylesheet.clickActionsMenuForStylesheet(childFolderName); await umbracoUi.stylesheet.clickCreateActionMenuOption(); await umbracoUi.stylesheet.clickNewStylesheetButton(); @@ -137,7 +137,7 @@ test('can create a stylesheet in a folder in a folder', async ({umbracoApi, umbr expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + stylesheetName); const stylesheetData = await umbracoApi.stylesheet.get(stylesheetChildren[0].path); expect(stylesheetData.content).toBe(stylesheetContent); - await umbracoUi.stylesheet.clickCaretButtonForName(childFolderName); + await umbracoUi.stylesheet.openCaretButtonForName(childFolderName); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, true, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts index ea84ad5161f3..b2445d458865 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts @@ -52,7 +52,7 @@ test('can see root start node and children', async ({umbracoApi, umbracoUi}) => // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); }); @@ -70,7 +70,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); await umbracoUi.content.doesDocumentWorkspaceHaveText('Access denied'); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts index c8720a7a410d..f16608353b50 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts @@ -45,7 +45,7 @@ test('can see root media start node and children', async ({umbracoApi, umbracoUi // Assert await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); }); @@ -63,7 +63,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.media.goToMediaWithName(rootFolderName); await umbracoUi.media.doesMediaWorkspaceHaveText('Access denied'); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts index 1648b0e6ff86..82178e73fce0 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts @@ -53,7 +53,7 @@ test('can see root start node and children', async ({umbracoApi, umbracoUi}) => // Assert await umbracoUi.content.isContentInTreeVisible(rootDocumentName); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); }); @@ -72,7 +72,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); await umbracoUi.content.doesDocumentWorkspaceHaveText('Access denied'); - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts index 2960ad27fa56..32837242d172 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts @@ -435,7 +435,7 @@ test.fixme('can move content with move to permission enabled', {tag: '@release'} await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); // Act - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.clickActionsMenuForContent(childDocumentOneName); await umbracoUi.content.clickMoveToActionMenuOption(); await umbracoUi.content.moveToContentWithName([], moveToDocumentName); @@ -444,7 +444,7 @@ test.fixme('can move content with move to permission enabled', {tag: '@release'} await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.moved); await umbracoUi.content.reloadContentTree(); await umbracoUi.content.isCaretButtonVisibleForContentName(moveToDocumentName, true); - await umbracoUi.content.clickCaretButtonForContentName(moveToDocumentName); + await umbracoUi.content.openContentCaretButtonForName(moveToDocumentName); await umbracoUi.content.isChildContentInTreeVisible(moveToDocumentName, childDocumentOneName, true); await umbracoUi.content.isCaretButtonVisibleForContentName(rootDocumentName, false); expect(await umbracoApi.document.getChildrenAmount(rootDocumentId)).toEqual(0); @@ -489,7 +489,7 @@ test.fixme('can sort children with sort children permission enabled', {tag: '@re // Assert // TODO: uncomment when it is not flaky - await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.openContentCaretButtonForName(rootDocumentName); await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentTwoName, 0); await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentOneName, 1); }); @@ -669,4 +669,4 @@ test('can create and update content with permission enabled', {tag: '@release'}, // Cleanup await umbracoApi.document.ensureNameNotExists(updatedDocumentName); -}); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts index e2cb5eedef93..bd5b2f94d1db 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/GranularPermissionsInContent.spec.ts @@ -223,7 +223,7 @@ test('can move a specific content with move to permission enabled', async ({umbr await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); // Act - await umbracoUi.content.clickCaretButtonForContentName(firstDocumentName); + await umbracoUi.content.openContentCaretButtonForName(firstDocumentName); await umbracoUi.content.clickActionsMenuForContent(childDocumentName); await umbracoUi.content.clickMoveToActionMenuOption(); await umbracoUi.content.moveToContentWithName([], moveToDocumentName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts index 7c71bb01fcb5..be8b531d44b5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts @@ -45,7 +45,7 @@ test('can see root media start node and children', {tag: '@release'}, async ({um // Assert await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); }); @@ -64,7 +64,7 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); await umbracoUi.media.goToMediaWithName(rootFolderName); await umbracoUi.media.doesMediaWorkspaceHaveText('Access denied'); - await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.openMediaCaretButtonForName(rootFolderName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); });