diff --git a/src/packages/core/collection/types.ts b/src/packages/core/collection/types.ts index ecd2225151..822d309abe 100644 --- a/src/packages/core/collection/types.ts +++ b/src/packages/core/collection/types.ts @@ -1,5 +1,5 @@ -import type { ManifestCollection } from '@umbraco-cms/backoffice/extension-registry'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { ManifestCollection } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; export interface UmbCollectionBulkActionPermissions { diff --git a/src/packages/core/workspace/components/workspace-collection/workspace-view-collection.element.ts b/src/packages/core/workspace/components/workspace-collection/workspace-view-collection.element.ts index 7d027b22c9..94c3c3c4b1 100644 --- a/src/packages/core/workspace/components/workspace-collection/workspace-view-collection.element.ts +++ b/src/packages/core/workspace/components/workspace-collection/workspace-view-collection.element.ts @@ -1,11 +1,12 @@ +import { UMB_WORKSPACE_EDITOR_CONTEXT } from '../workspace-editor/workspace-editor.context.js'; import type { UmbCollectionBulkActionPermissions, UmbCollectionConfiguration } from '../../../collection/types.js'; import { customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { UMB_COLLECTION_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; -import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestWorkspaceView, UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-workspace-view-collection') export class UmbWorkspaceViewCollectionElement extends UmbLitElement implements UmbWorkspaceViewElement { @@ -21,8 +22,12 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement implements @state() private _documentUnique?: string; + manifest?: ManifestWorkspaceView; + #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); + #workspaceEditorContext?: typeof UMB_WORKSPACE_EDITOR_CONTEXT.TYPE; + constructor() { super(); this.#observeConfig(); @@ -56,11 +61,23 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement implements '_observeConfigContentType', ); }); + + this.consumeContext(UMB_WORKSPACE_EDITOR_CONTEXT, (workspaceEditorContext) => { + this.#workspaceEditorContext = workspaceEditorContext; + }); } #mapDataTypeConfigToCollectionConfig(dataType: UmbDataTypeDetailModel): UmbCollectionConfiguration { const config = new UmbPropertyEditorConfigCollection(dataType.values); const pageSize = Number(config.getValueByAlias('pageSize')); + + if (this.manifest?.alias) { + const showContentFirst = Boolean(config?.getValueByAlias('showContentFirst')); + const icon = config?.getValueByAlias('icon'); + const label = config?.getValueByAlias('tabName'); + this.#workspaceEditorContext?.setWorkspaceViewMeta(this.manifest.alias, icon, label, showContentFirst); + } + return { unique: this._documentUnique, allowedEntityBulkActions: config?.getValueByAlias('bulkActionPermissions'), diff --git a/src/packages/core/workspace/components/workspace-editor/workspace-editor.context.ts b/src/packages/core/workspace/components/workspace-editor/workspace-editor.context.ts new file mode 100644 index 0000000000..a7afd18fa2 --- /dev/null +++ b/src/packages/core/workspace/components/workspace-editor/workspace-editor.context.ts @@ -0,0 +1,108 @@ +import { createExtensionElement, UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbRoute } from '@umbraco-cms/backoffice/router'; + +export type UmbWorkspaceEditorView = { + alias: string; + label: string; + icon: string; + pathName: string; +}; + +export class UmbWorkspaceEditorContext extends UmbContextBase { + #routes = new UmbArrayState([], (x) => x.path); + public readonly routes = this.#routes.asObservable(); + + #views = new UmbArrayState([], (x) => x.alias); + public readonly views = this.#views.asObservable(); + + constructor(host: UmbControllerHost) { + super(host, UMB_WORKSPACE_EDITOR_CONTEXT); + + new UmbExtensionsManifestInitializer(this, umbExtensionsRegistry, 'workspaceView', null, (workspaceViews) => { + const manifests = workspaceViews.map((view) => view.manifest); + this.#createRoutes(manifests); + this.#createViews(manifests); + }); + } + + #createRoutes(manifests: Array | null) { + let routes: UmbRoute[] = []; + + if (manifests?.length) { + routes = manifests.map((manifest) => { + return { + path: `view/${manifest.meta.pathname}`, + component: () => createExtensionElement(manifest), + setup: (component) => { + if (component) { + (component as any).manifest = manifest; + } + }, + } as UmbRoute; + }); + + // Duplicate first workspace and use it for the empty path scenario. [NL] + routes.push({ ...routes[0], path: '' }); + + routes.push({ + path: `**`, + component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, + }); + } + + this.#routes.setValue(routes); + } + + #createViews(manifests: Array | null) { + if (!manifests?.length) return; + + const views = manifests + ? manifests.map((manifest) => ({ + alias: manifest.alias, + icon: manifest.meta.icon, + label: manifest.meta.label ?? manifest.name, + pathName: manifest.meta.pathname, + })) + : []; + + this.#views.setValue(views); + } + + setWorkspaceViewMeta(alias: string, icon: string | undefined, label: string | undefined, showContentFirst: boolean) { + if (!alias) return; + + // NOTE: Edge-case, as the server sets "icon-badge" as the default icon, but that particular icon doesn't exist in the backoffice. [LK] + // https://github.com/umbraco/Umbraco-CMS/blob/release-14.0.0/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/MigrateDataTypeConfigurations.cs#L718 + if (icon === 'icon-badge color-black') { + icon = 'icon-grid'; + } + + const views = [...this.#views.getValue()].map((view) => + view.alias === alias ? { ...view, ...(icon && { icon }), ...(label && { label }) } : view, + ); + + this.#views.setValue(views); + + // TODO: Review how `showContentFirst` is implemented, and how to re-route/redirect the path. + // e.g. the "Content" tab shows the "Collection" view. [LK] + if (showContentFirst) { + // const views = [...this.#views.getValue()]; + // const idx = views.findIndex((view) => view === 'Umb.WorkspaceView.Document.Edit'); + // if (idx > -1) { + // const [edit] = views.splice(idx, 1); + // views.unshift(edit); + // this.#views.setValue(views); + // } + } + } +} + +export default UmbWorkspaceEditorContext; + +export const UMB_WORKSPACE_EDITOR_CONTEXT = new UmbContextToken('UmbWorkspaceEditorContext'); diff --git a/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts b/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts index 3a8195caf2..d76ec63725 100644 --- a/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts +++ b/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts @@ -1,11 +1,12 @@ +import { UmbWorkspaceEditorContext } from './workspace-editor.context.js'; +import type { UmbWorkspaceEditorView } from './workspace-editor.context.js'; import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; -import { createExtensionElement, UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbRoute, UmbRouterSlotInitEvent, UmbRouterSlotChangeEvent } from '@umbraco-cms/backoffice/router'; +const elementName = 'umb-workspace-editor'; + /** * @element umb-workspace-editor * @description @@ -20,7 +21,7 @@ import type { UmbRoute, UmbRouterSlotInitEvent, UmbRouterSlotChangeEvent } from * @extends {UmbLitElement} */ // TODO: This element has a bug in the tabs. After the url changes - for example a new entity/file is chosen in the tree and loaded to the workspace the links in the tabs still point to the previous url and therefore views do not change correctly -@customElement('umb-workspace-editor') +@customElement(elementName) export class UmbWorkspaceEditorElement extends UmbLitElement { @property() public headline = ''; @@ -35,7 +36,7 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { public backPath?: string; @state() - private _workspaceViews: Array = []; + private _workspaceViews: Array = []; @state() private _routes?: UmbRoute[]; @@ -46,41 +47,18 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { @state() private _activePath?: string; + #workspaceEditorContext = new UmbWorkspaceEditorContext(this); + constructor() { super(); - new UmbExtensionsManifestInitializer(this, umbExtensionsRegistry, 'workspaceView', null, (workspaceViews) => { - this._workspaceViews = workspaceViews.map((view) => view.manifest); - this._createRoutes(); + this.observe(this.#workspaceEditorContext.views, (workspaceViews) => { + this._workspaceViews = workspaceViews; }); - } - private _createRoutes() { - let newRoutes: UmbRoute[] = []; - - if (this._workspaceViews.length > 0) { - newRoutes = this._workspaceViews.map((manifest) => { - return { - path: `view/${manifest.meta.pathname}`, - component: () => createExtensionElement(manifest), - setup: (component) => { - if (component) { - (component as any).manifest = manifest; - } - }, - } as UmbRoute; - }); - - // Duplicate first workspace and use it for the empty path scenario. [NL] - newRoutes.push({ ...newRoutes[0], path: '' }); - - newRoutes.push({ - path: `**`, - component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, - }); - } - - this._routes = newRoutes; + this.observe(this.#workspaceEditorContext.routes, (routes) => { + this._routes = routes; + }); } override render() { @@ -112,19 +90,8 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { ${repeat( this._workspaceViews, - (view) => view.alias, - (view, index) => - // Notice how we use index 0 to determine which workspace that is active with empty path. [NL] - html` - - - ${view.meta.label ? this.localize.string(view.meta.label) : view.name} - - `, + (view) => view, + (view, index) => this.#renderView(view, index), )} ` @@ -132,6 +99,18 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { `; } + #renderView(view: UmbWorkspaceEditorView, index: number) { + // Notice how we use index 0 to determine which workspace that is active with empty path. [NL] + return html` + + + ${this.localize.string(view.label)} + + `; + } + #renderBackButton() { if (!this.backPath) return nothing; return html` @@ -200,6 +179,6 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { declare global { interface HTMLElementTagNameMap { - 'umb-workspace-editor': UmbWorkspaceEditorElement; + [elementName]: UmbWorkspaceEditorElement; } }