diff --git a/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/hint-workspace-view.ts b/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/hint-workspace-view.ts index 6f5b7775c2b5..b88e911f56d3 100644 --- a/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/hint-workspace-view.ts +++ b/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/hint-workspace-view.ts @@ -1,8 +1,6 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; -import { UMB_WORKSPACE_VIEW_CONTEXT } from '@umbraco-cms/backoffice/workspace'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; @customElement('example-hint-workspace-view') diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts index a9bcb5a78b9a..9dc5b6b7fd5b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-main-views/section-main-views.element.ts @@ -1,12 +1,15 @@ -import type { ManifestSectionView, UmbSectionViewElement } from '../extensions/index.js'; +import type { ManifestSectionView } from '../extensions/index.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router'; -import type { ManifestDashboard, UmbDashboardElement } from '@umbraco-cms/backoffice/dashboard'; +import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbExtensionsManifestInitializer, createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { pathFolderName } from '@umbraco-cms/backoffice/utils'; +import { UmbViewController } from '@umbraco-cms/backoffice/view'; +import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbVariantHint } from '@umbraco-cms/backoffice/hint'; @customElement('umb-section-main-views') export class UmbSectionMainViewElement extends UmbLitElement { @@ -31,6 +34,13 @@ export class UmbSectionMainViewElement extends UmbLitElement { @state() private _routes: Array = []; + @state() + private _hintMap: Map = new Map(); + + #viewContexts = new Map(); + #hintObservers: Array = []; + #currentProvidedView?: UmbViewController; + constructor() { super(); @@ -45,6 +55,46 @@ export class UmbSectionMainViewElement extends UmbLitElement { }); } + #getOrCreateViewContext(alias: string): UmbViewController { + let context = this.#viewContexts.get(alias); + if (!context) { + context = new UmbViewController(this, alias); + context.inherit(); + this.#viewContexts.set(alias, context); + } + return context; + } + + #cleanupViewContexts(viewAliases: Set) { + // Remove contexts that are no longer needed + for (const [alias, context] of this.#viewContexts) { + if (!viewAliases.has(alias)) { + context.destroy(); + this.#viewContexts.delete(alias); + } + } + this.#observeHints(); + } + + #observeHints() { + this.#hintObservers.forEach((observer) => observer.destroy()); + this._hintMap = new Map(); + this.#hintObservers = [...this.#viewContexts.entries()].map(([alias, context]) => + this.observe( + context.hints.firstHint, + (hint) => { + if (hint) { + this._hintMap.set(alias, hint); + } else { + this._hintMap.delete(alias); + } + this.requestUpdate('_hintMap'); + }, + 'umbObserveHint_' + alias, + ), + ); + } + #constructDashboardPath(manifest: ManifestDashboard) { const dashboardName = manifest.meta.label ?? manifest.name ?? manifest.alias; return 'dashboard/' + (manifest.meta.pathname ? manifest.meta.pathname : pathFolderName(dashboardName)); @@ -55,27 +105,55 @@ export class UmbSectionMainViewElement extends UmbLitElement { return 'view/' + (manifest.meta.pathname ? manifest.meta.pathname : pathFolderName(viewName)); } + #getViewName(view: ManifestSectionView) { + return view.meta?.label ? this.localize.string(view.meta.label) : (view.name ?? view.alias); + } + async #createRoutes() { + const viewAliases = new Set(); + const dashboardRoutes = this._dashboards?.map((manifest) => { + viewAliases.add(manifest.alias); + const context = this.#getOrCreateViewContext(manifest.alias); + context.setTitle(this.#getDashboardName(manifest)); return { path: this.#constructDashboardPath(manifest), component: () => createExtensionElement(manifest), - setup: (component: UmbDashboardElement) => { - component.manifest = manifest; + setup: (component?: any) => { + if (this.#currentProvidedView !== context) { + this.#currentProvidedView?.unprovide(); + } + if (component) { + this.#currentProvidedView = context; + context.provideAt(component); + component.manifest = manifest; + } }, } as UmbRoute; }); const viewRoutes = this._views?.map((manifest) => { + viewAliases.add(manifest.alias); + const context = this.#getOrCreateViewContext(manifest.alias); + context.setTitle(this.#getViewName(manifest)); return { path: this.#constructViewPath(manifest), component: () => createExtensionElement(manifest), - setup: (component: UmbSectionViewElement) => { - component.manifest = manifest; + setup: async (component?: any) => { + if (this.#currentProvidedView !== context) { + this.#currentProvidedView?.unprovide(); + } + if (component) { + this.#currentProvidedView = context; + context.provideAt(component); + component.manifest = manifest; + } }, } as UmbRoute; }); + this.#cleanupViewContexts(viewAliases); + const routes = [...dashboardRoutes, ...viewRoutes]; if (routes.length > 0) { this._defaultView = routes[0].path; @@ -122,16 +200,23 @@ export class UmbSectionMainViewElement extends UmbLitElement { ${this._dashboards.map((dashboard) => { const dashboardPath = this.#constructDashboardPath(dashboard); + const dashboardName = this.#getDashboardName(dashboard); + const hint = this._hintMap.get(dashboard.alias); // If this path matches, or if this is the default view and the active path is empty. const isActive = this._activePath === dashboardPath || (this._defaultView === dashboardPath && this._activePath === ''); return html` - ${this.#getDashboardName(dashboard)} + + ${dashboardName} + ${hint && !isActive + ? html`${hint.text}` + : nothing} + `; })} @@ -145,14 +230,22 @@ export class UmbSectionMainViewElement extends UmbLitElement { ? html` ${this._views.map((view) => { - const viewName = view.meta.label ? this.localize.string(view.meta.label) : (view.name ?? view.alias); + const viewName = this.#getViewName(view); const viewPath = this.#constructViewPath(view); + const hint = this._hintMap.get(view.alias); // If this path matches, or if this is the default view and the active path is empty. const isActive = this._activePath === viewPath || (this._defaultView === viewPath && this._activePath === ''); return html` - +
+ + ${hint && !isActive + ? html`${hint.text}` + : nothing} +
${viewName}
`; @@ -179,6 +272,20 @@ export class UmbSectionMainViewElement extends UmbLitElement { #views uui-tab:first-child { border-left: 1px solid var(--uui-color-divider-standalone); } + + div[slot='icon'] { + position: relative; + } + + umb-badge { + top: var(--uui-size-2); + right: calc(var(--uui-size-2) * -1); + } + + div[slot='icon'] umb-badge { + right: -1em; + top: -0.5em; + } `, ]; }