make section views and dashboards coexist

This commit is contained in:
Mads Rasmussen
2023-03-16 23:04:03 +01:00
parent f5a8849742
commit 8730060aa0
4 changed files with 99 additions and 292 deletions

View File

@@ -1,184 +0,0 @@
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { first, map } from 'rxjs';
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context';
import type { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent, IRoutingInfo } from '@umbraco-cms/router';
import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import type { ManifestDashboard, ManifestDashboardCollection } from '@umbraco-cms/models';
import { UmbLitElement } from '@umbraco-cms/element';
@customElement('umb-section-dashboards')
export class UmbSectionDashboardsElement extends UmbLitElement {
static styles = [
UUITextStyles,
css`
:host {
display: flex;
flex-direction: column;
height: 100%;
}
#tabs {
background-color: var(--uui-color-surface);
height: 70px;
border-bottom: 1px solid var(--uui-color-border);
box-sizing: border-box;
}
#scroll-container {
flex: 1;
position: relative;
}
#router-slot {
box-sizing: border-box;
display: block;
padding: var(--uui-size-5);
}
#header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-height: 60px;
box-sizing: border-box;
margin: 0;
padding: 0 var(--uui-size-5);
background-color: var(--uui-color-surface);
border-bottom: 1px solid var(--uui-color-border);
}
`,
];
@state()
private _dashboards?: Array<ManifestDashboard | ManifestDashboardCollection>;
@state()
private _routes: Array<any> = [];
@state()
private _routerPath?: string;
@state()
private _activePath?: string;
private _currentSectionAlias?: string;
private _sectionContext?: UmbSectionContext;
constructor() {
super();
this.consumeContext(UMB_SECTION_CONTEXT_TOKEN, (context) => {
this._sectionContext = context;
this._observeSectionContext();
});
}
private _observeSectionContext() {
if (!this._sectionContext) return;
this.observe(this._sectionContext.alias.pipe(first()), (alias) => {
this._currentSectionAlias = alias;
this._observeDashboards();
});
}
private _observeDashboards() {
if (!this._currentSectionAlias) return;
this.observe(
umbExtensionsRegistry
?.extensionsOfTypes<ManifestDashboard | ManifestDashboardCollection>(['dashboard', 'dashboardCollection'])
.pipe(
map((extensions) =>
extensions.filter((extension) => extension.conditions.sections.includes(this._currentSectionAlias ?? ''))
)
),
(dashboards) => {
this._dashboards = dashboards || undefined;
this._createRoutes();
}
);
}
private _createRoutes() {
this._routes = [];
if (this._dashboards) {
this._routes = this._dashboards.map((dashboard) => {
return {
path: `${dashboard.meta.pathname}`,
component: () => {
if (dashboard.type === 'dashboardCollection') {
return import('src/backoffice/shared/collection/dashboards/dashboard-collection.element');
}
return createExtensionElement(dashboard);
},
setup: (component: Promise<HTMLElement> | HTMLElement, info: IRoutingInfo) => {
// When its using import, we get an element, when using createExtensionElement we get a Promise.
// TODO: this is a bit hacky, can we do it in a more appropriate way:
if ((component as any).then) {
(component as any).then((el: any) => (el.manifest = dashboard));
} else {
(component as any).manifest = dashboard;
}
},
};
});
this._routes.push({
path: '**',
redirectTo: this._dashboards?.[0]?.meta.pathname,
});
}
}
private _renderNavigation() {
return html`
${this._dashboards && this._dashboards.length > 1
? html`
<uui-tab-group id="tabs">
${this._dashboards.map(
(dashboard) => html`
<uui-tab
href="${this._routerPath}/${dashboard.meta.pathname}"
label=${dashboard.meta.label || dashboard.name}
?active="${dashboard.meta.pathname === this._activePath}"></uui-tab>
`
)}
</uui-tab-group>
`
: this._dashboards?.length === 1
? html`<h3 id="header">${this._dashboards[0].meta.label || this._dashboards[0].name}</h3>`
: nothing}
`;
}
render() {
return html`
${this._renderNavigation()}
<uui-scroll-container id="scroll-container">
<umb-router-slot
id="router-slot"
.routes="${this._routes}"
@init=${(event: UmbRouterSlotInitEvent) => {
this._routerPath = event.target.absoluteRouterPath;
}}
@change=${(event: UmbRouterSlotChangeEvent) => {
this._activePath = event.target.localActiveViewPath;
}}></umb-router-slot>
</uui-scroll-container>
`;
}
}
export default UmbSectionDashboardsElement;
declare global {
interface HTMLElementTagNameMap {
'umb-section-dashboards': UmbSectionDashboardsElement;
}
}

View File

@@ -1,27 +0,0 @@
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit-html';
import { manifests } from '../../../../documents/section.manifests';
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context';
import type { UmbSectionDashboardsElement } from './section-dashboards.element';
import type { ManifestSection } from '@umbraco-cms/models';
import './section-dashboards.element';
const contentSectionManifest = manifests.find((m) => m.alias === 'Umb.Section.Content') as ManifestSection;
export default {
title: 'Sections/Shared/Section Dashboards',
component: 'umb-section-dashboards',
id: 'umb-section-dashboards',
decorators: [
(story) =>
html` <umb-context-provider
key=${UMB_SECTION_CONTEXT_TOKEN.toString()}
.value=${new UmbSectionContext(contentSectionManifest)}>
${story()}
</umb-context-provider>`,
],
} as Meta;
export const AAAOverview: Story<UmbSectionDashboardsElement> = () =>
html` <umb-section-dashboards></umb-section-dashboards> `;
AAAOverview.storyName = 'Overview';

View File

@@ -2,13 +2,14 @@ import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { map, of } from 'rxjs';
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section.context';
import type { ManifestSectionView } from '@umbraco-cms/models';
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
import type { ManifestDashboard, ManifestSectionView } from '@umbraco-cms/models';
import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbObserverController } from '@umbraco-cms/observable-api';
// TODO: this might need a new name, since it's both view and dashboard now
@customElement('umb-section-views')
export class UmbSectionViewsElement extends UmbLitElement {
static styles = [
@@ -17,14 +18,17 @@ export class UmbSectionViewsElement extends UmbLitElement {
#header {
background-color: var(--uui-color-surface);
border-bottom: 1px solid var(--uui-color-divider-standalone);
display: flex;
justify-content: space-between;
align-items: center;
}
uui-tab-group {
#views {
justify-content: flex-end;
--uui-tab-divider: var(--uui-color-divider-standalone);
}
uui-tab-group uui-tab:first-child {
#views uui-tab:first-child {
border-left: 1px solid var(--uui-color-divider-standalone);
}
`,
@@ -36,6 +40,9 @@ export class UmbSectionViewsElement extends UmbLitElement {
@state()
private _views: Array<ManifestSectionView> = [];
@state()
private _dashboards: Array<ManifestDashboard> = [];
@state()
private _routerPath?: string;
@@ -47,49 +54,76 @@ export class UmbSectionViewsElement extends UmbLitElement {
private _sectionContext?: UmbSectionContext;
private _extensionsObserver?: UmbObserverController<ManifestSectionView[]>;
private _viewsObserver?: UmbObserverController<ManifestSectionView[]>;
private _dashboardObserver?: UmbObserverController<ManifestDashboard[]>;
constructor() {
super();
this.consumeContext(UMB_SECTION_CONTEXT_TOKEN, (sectionContext) => {
this._sectionContext = sectionContext;
this._observeViews();
this._observeSectionAlias();
});
}
async #createRoutes(viewManifests: Array<ManifestSectionView>) {
if (!viewManifests) return;
const routes = viewManifests.map((manifest) => {
async #createRoutes() {
const dashboardRoutes = this._dashboards?.map((manifest) => {
return {
path: manifest.meta.pathname,
path: 'dashboard/' + manifest.meta.pathname,
component: () => createExtensionElement(manifest),
};
});
this._routes = [...routes, { path: '**', redirectTo: routes[0].path }];
const viewRoutes = this._views?.map((manifest) => {
return {
path: 'view/' + manifest.meta.pathname,
component: () => createExtensionElement(manifest),
};
});
const routes = [...dashboardRoutes, ...viewRoutes];
this._routes = routes?.length > 0 ? [...routes, { path: '**', redirectTo: routes?.[0]?.path }] : [];
}
private _observeViews() {
private _observeSectionAlias() {
if (!this._sectionContext) return;
this.observe(
this._sectionContext.alias,
(sectionAlias) => {
this._observeExtensions(sectionAlias);
this._observeViews(sectionAlias);
this._observeDashboards(sectionAlias);
},
'viewsObserver'
);
}
private _observeExtensions(sectionAlias?: string) {
this._extensionsObserver?.destroy();
private _observeViews(sectionAlias?: string) {
this._viewsObserver?.destroy();
if (sectionAlias) {
this._extensionsObserver = this.observe(
this._viewsObserver = this.observe(
umbExtensionsRegistry
?.extensionsOfType('sectionView')
.pipe(map((views) => views.filter((view) => view.conditions.sections.includes(sectionAlias)))) ?? of([]),
(views) => {
this._views = views;
this.#createRoutes(views);
this.#createRoutes();
}
);
}
}
private _observeDashboards(sectionAlias?: string) {
this._dashboardObserver?.destroy();
if (sectionAlias) {
this._dashboardObserver = this.observe(
umbExtensionsRegistry
?.extensionsOfType('dashboard')
.pipe(map((views) => views.filter((view) => view.conditions.sections.includes(sectionAlias)))) ?? of([]),
(views) => {
this._dashboards = views;
this.#createRoutes();
}
);
}
@@ -97,9 +131,9 @@ export class UmbSectionViewsElement extends UmbLitElement {
render() {
return html`
${this._views.length > 0
${this._routes.length > 0
? html`
<div id="header">${this.#renderTabs()}</div>
<div id="header">${this.#renderDashboards()} ${this.#renderViews()}</div>
<umb-router-slot
.routes=${this._routes}
@init=${(event: UmbRouterSlotInitEvent) => {
@@ -114,15 +148,32 @@ export class UmbSectionViewsElement extends UmbLitElement {
`;
}
#renderTabs() {
#renderDashboards() {
return html`
<uui-tab-group>
<uui-tab-group id="dashboards">
${this._dashboards.map(
(dashboard) => html`
<uui-tab
.label="${dashboard.meta.label || dashboard.name}"
href="${this._routerPath}/dashboard/${dashboard.meta.pathname}"
?active="${this._activePath === 'dashboard/' + dashboard.meta.pathname}">
${dashboard.meta.label || dashboard.name}
</uui-tab>
`
)}
</uui-tab-group>
`;
}
#renderViews() {
return html`
<uui-tab-group id="views">
${this._views.map(
(view: ManifestSectionView) => html`
(view) => html`
<uui-tab
.label="${view.meta.label || view.name}"
href="${this._routerPath}/${view.meta.pathname}"
?active="${this._activePath === view.meta.pathname}">
href="${this._routerPath}/view/${view.meta.pathname}"
?active="${this._activePath === 'view/' + view.meta.pathname}">
<uui-icon slot="icon" name=${view.meta.icon}></uui-icon>
${view.meta.label || view.name}
</uui-tab>

View File

@@ -2,12 +2,10 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { map } from 'rxjs';
import type { IRoutingInfo } from '@umbraco-cms/router';
import type { UmbWorkspaceElement } from '../workspace/workspace.element';
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from './section.context';
import type { UmbSectionViewsElement } from './section-views/section-views.element';
import type { ManifestSectionView, ManifestMenuSectionSidebarApp, ManifestSection } from '@umbraco-cms/models';
import type { IRoutingInfo } from '@umbraco-cms/router';
import type { ManifestMenuSectionSidebarApp, ManifestSection } from '@umbraco-cms/models';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { UmbLitElement } from '@umbraco-cms/element';
@@ -49,39 +47,16 @@ export class UmbSectionElement extends UmbLitElement {
@state()
private _menus?: Array<ManifestMenuSectionSidebarApp>;
@state()
private _views?: Array<ManifestSectionView>;
private _sectionContext?: UmbSectionContext;
private _sectionAlias?: string;
constructor() {
super();
this.consumeContext(UMB_SECTION_CONTEXT_TOKEN, (instance) => {
this._sectionContext = instance;
// TODO: currently they don't corporate, as they overwrite each other...
this.#observeSectionAlias();
this.#createRoutes();
});
connectedCallback() {
super.connectedCallback();
this.#observeSectionSidebarApps();
this.#createRoutes();
}
#createRoutes() {
this._routes = [];
this._routes = [
{
path: 'dashboard',
component: () => import('./section-dashboards/section-dashboards.element'),
},
{
path: 'view',
component: () => import('../section/section-views/section-views.element'),
setup: (element: UmbSectionViewsElement) => {
element.sectionAlias = this.manifest?.alias;
},
},
{
path: 'workspace/:entityType',
component: () => import('../workspace/workspace.element'),
@@ -91,35 +66,27 @@ export class UmbSectionElement extends UmbLitElement {
},
{
path: '**',
redirectTo: 'view',
component: () => import('../section/section-views/section-views.element'),
setup: (element: UmbSectionViewsElement) => {
element.sectionAlias = this.manifest?.alias;
},
},
];
}
#observeSectionAlias() {
if (!this._sectionContext) return;
this.observe(this._sectionContext.alias, (alias) => {
this._sectionAlias = alias;
this.#observeSectionSidebarApps(alias);
});
}
#observeSectionSidebarApps(sectionAlias?: string) {
if (sectionAlias) {
this.observe(
umbExtensionsRegistry
?.extensionsOfType('menuSectionSidebarApp')
.pipe(
map((manifests) => manifests.filter((manifest) => manifest.conditions.sections.includes(sectionAlias)))
),
(manifests) => {
this._menus = manifests;
}
);
} else {
this._menus = [];
}
#observeSectionSidebarApps() {
this.observe(
umbExtensionsRegistry
?.extensionsOfType('menuSectionSidebarApp')
.pipe(
map((manifests) =>
manifests.filter((manifest) => manifest.conditions.sections.includes(this.manifest?.alias || ''))
)
),
(manifests) => {
this._menus = manifests;
}
);
}
render() {
@@ -131,12 +98,12 @@ export class UmbSectionElement extends UmbLitElement {
<umb-extension-slot
type="sectionSidebarApp"
.filter=${(items: ManifestMenuSectionSidebarApp) =>
items.conditions.sections.includes(this._sectionAlias || '')}></umb-extension-slot>
items.conditions.sections.includes(this.manifest?.alias || '')}></umb-extension-slot>
<umb-extension-slot
type="menuSectionSidebarApp"
.filter=${(items: ManifestMenuSectionSidebarApp) =>
items.conditions.sections.includes(this._sectionAlias || '')}
items.conditions.sections.includes(this.manifest?.alias || '')}
default-element="umb-section-sidebar-menu"></umb-extension-slot>
</umb-section-sidebar>
`