Merge branch 'main' of https://github.com/umbraco/Umbraco.CMS.Backoffice
This commit is contained in:
@@ -14,7 +14,7 @@ import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { getInitStatus } from './api/fetcher';
|
||||
import { getInitStatus } from './core/api/fetcher';
|
||||
import { UmbContextProviderMixin } from './core/context';
|
||||
import {
|
||||
isUmbRouterBeforeEnterEvent,
|
||||
@@ -125,7 +125,7 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
|
||||
if (!this._isAuthorized() || window.location.pathname === '/install') {
|
||||
this._router.push('/login');
|
||||
} else {
|
||||
const next = window.location.pathname === '/' ? '/section/Content' : window.location.pathname;
|
||||
const next = window.location.pathname === '/' ? '/section/content' : window.location.pathname;
|
||||
this._router.push(next);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
|
||||
import { postUserLogin } from '../../api/fetcher';
|
||||
import { postUserLogin } from '../../core/api/fetcher';
|
||||
import { UmbContextConsumerMixin } from '../../core/context';
|
||||
import { UmbRouter } from '../../core/router';
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { getUserSections } from '../core/api/fetcher';
|
||||
import { UmbExtensionManifest } from '../core/extension';
|
||||
import { UmbRouteLocation, UmbRouter } from '../core/router';
|
||||
import { UmbSectionContext } from '../section.context';
|
||||
import { UmbContextConsumerMixin } from '../core/context';
|
||||
|
||||
@customElement('umb-backoffice-header-sections')
|
||||
export class UmbBackofficeHeaderSections extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#tabs {
|
||||
color: var(--uui-look-primary-contrast);
|
||||
height: 60px;
|
||||
font-size: 16px;
|
||||
--uui-tab-text: var(--uui-look-primary-contrast);
|
||||
--uui-tab-text-hover: var(--uui-look-primary-contrast-hover);
|
||||
--uui-tab-text-active: var(--uui-interface-active);
|
||||
--uui-tab-background: var(--uui-look-primary-surface);
|
||||
}
|
||||
|
||||
#dropdown {
|
||||
background-color: white;
|
||||
border-radius: var(--uui-border-radius);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--uui-shadow-depth-3);
|
||||
min-width: 200px;
|
||||
color: black; /* Change to variable */
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
@state()
|
||||
private _allowedSection: Array<string> = [];
|
||||
|
||||
@state()
|
||||
private _sections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _visibleSections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _extraSections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _currentSectionAlias = '';
|
||||
|
||||
private _router?: UmbRouter;
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _sectionSubscription?: Subscription;
|
||||
private _currentSectionSubscription?: Subscription;
|
||||
private _locationSubscription?: Subscription;
|
||||
private _location? : UmbRouteLocation;
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbRouter', (_instance: UmbRouter) => {
|
||||
this._router = _instance;
|
||||
this._useLocation();
|
||||
});
|
||||
|
||||
this.consumeContext('umbSectionContext', (_instance: UmbSectionContext) => {
|
||||
this._sectionContext = _instance;
|
||||
this._useCurrentSection();
|
||||
this._useSections();
|
||||
});
|
||||
}
|
||||
|
||||
private _handleMore(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
this._open = !this._open;
|
||||
}
|
||||
|
||||
private _handleTabClick(e: PointerEvent, section: UmbExtensionManifest) {
|
||||
const tab = e.currentTarget as any;
|
||||
|
||||
// TODO: we need to be able to prevent the tab from setting the active state
|
||||
if (tab.id === 'moreTab') {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: this could maybe be handled by an anchor tag
|
||||
this._router?.push(`/section/${section.meta.pathname}`);
|
||||
this._sectionContext?.setCurrent(section.alias);
|
||||
}
|
||||
|
||||
private _handleLabelClick(e: PointerEvent) {
|
||||
const label = (e.target as any).label;
|
||||
|
||||
// TODO: set current section
|
||||
//this._sectionContext?.setCurrent(section.alias);
|
||||
|
||||
const moreTab = this.shadowRoot?.getElementById('moreTab');
|
||||
moreTab?.setAttribute('active', 'true');
|
||||
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _useLocation () {
|
||||
this._locationSubscription?.unsubscribe();
|
||||
|
||||
this._locationSubscription = this._router?.location
|
||||
.subscribe((location: UmbRouteLocation) => {
|
||||
this._location = location;
|
||||
});
|
||||
}
|
||||
|
||||
private _useCurrentSection () {
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
|
||||
this._currentSectionSubscription = this._sectionContext?.getCurrent()
|
||||
.subscribe(section => {
|
||||
this._currentSectionAlias = section.alias;
|
||||
});
|
||||
}
|
||||
|
||||
private async _useSections() {
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
|
||||
const { data } = await getUserSections({});
|
||||
this._allowedSection = data.sections;
|
||||
|
||||
this._sectionSubscription = this._sectionContext?.getSections()
|
||||
.subscribe((sectionExtensions: any) => {
|
||||
this._sections = sectionExtensions.filter((section: any) => this._allowedSection.includes(section.alias));
|
||||
this._visibleSections = this._sections;
|
||||
const currentSectionAlias = this._sections.find(section => section.meta.pathname === this._location?.params?.section)?.alias;
|
||||
if (!currentSectionAlias) return;
|
||||
this._sectionContext?.setCurrent(currentSectionAlias);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-tab-group id="tabs">
|
||||
${this._visibleSections.map(
|
||||
(section) => html`
|
||||
<uui-tab
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click="${(e: PointerEvent) => this._handleTabClick(e, section)}"></uui-tab>
|
||||
`
|
||||
)}
|
||||
${this._renderExtraSections()}
|
||||
</uui-tab-group>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderExtraSections() {
|
||||
return when(
|
||||
this._extraSections.length > 0,
|
||||
() => html`
|
||||
<uui-tab id="moreTab" @click="${this._handleTabClick}">
|
||||
<uui-popover .open=${this._open} placement="bottom-start" @close="${() => (this._open = false)}">
|
||||
<uui-button slot="trigger" look="primary" label="More" @click="${this._handleMore}" compact>
|
||||
<uui-symbol-more></uui-symbol-more>
|
||||
</uui-button>
|
||||
|
||||
<div slot="popover" id="dropdown">
|
||||
${this._extraSections.map(
|
||||
(section) => html`
|
||||
<uui-menu-item
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click-label="${this._handleLabelClick}"></uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</uui-popover>
|
||||
</uui-tab>
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-backoffice-header-sections': UmbBackofficeHeaderSections;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-backoffice-header-tools')
|
||||
export class UmbBackofficeHeaderTools extends LitElement {
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
.tool {
|
||||
font-size: 18px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="tools">
|
||||
<uui-button class="tool" look="primary" label="Search" compact>
|
||||
<uui-icon name="search"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button class="tool" look="primary" label="Help" compact>
|
||||
<uui-icon name="favorite"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button look="primary" style="font-size: 14px;" label="User" compact>
|
||||
<uui-avatar name="Mads Rasmussen"></uui-avatar>
|
||||
</uui-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-backoffice-header-tools': UmbBackofficeHeaderTools;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,19 @@
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { when } from 'lit/directives/when.js';
|
||||
|
||||
import { getUserSections } from '../api/fetcher';
|
||||
import { UmbExtensionManifest, UmbManifestSectionMeta } from '../core/extension';
|
||||
import { UmbRouteLocation, UmbRouter } from '../core/router';
|
||||
import { UmbSectionContext } from '../section.context';
|
||||
import { UmbContextConsumerMixin } from '../core/context';
|
||||
|
||||
// TODO: umb or not umb in file name?
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
|
||||
import './backoffice-header-sections.element';
|
||||
import './backoffice-header-tools.element';
|
||||
@customElement('umb-backoffice-header')
|
||||
export class UmbBackofficeHeader extends UmbContextConsumerMixin(LitElement) {
|
||||
export class UmbBackofficeHeader extends LitElement {
|
||||
static styles: CSSResultGroup = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#appHeader {
|
||||
background-color: var(--uui-look-primary-surface);
|
||||
display: flex;
|
||||
@@ -41,183 +35,10 @@ export class UmbBackofficeHeader extends UmbContextConsumerMixin(LitElement) {
|
||||
|
||||
#sections {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
#tabs {
|
||||
color: var(--uui-look-primary-contrast);
|
||||
height: 60px;
|
||||
font-size: 16px;
|
||||
--uui-tab-text: var(--uui-look-primary-contrast);
|
||||
--uui-tab-text-hover: var(--uui-look-primary-contrast-hover);
|
||||
--uui-tab-text-active: var(--uui-interface-active);
|
||||
--uui-tab-background: var(--uui-look-primary-surface);
|
||||
}
|
||||
|
||||
#tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--uui-size-space-2);
|
||||
}
|
||||
|
||||
.tool {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#dropdown {
|
||||
background-color: white;
|
||||
border-radius: var(--uui-border-radius);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: var(--uui-shadow-depth-3);
|
||||
min-width: 200px;
|
||||
color: black; /* Change to variable */
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
@state()
|
||||
private _allowedSection: Array<string> = [];
|
||||
|
||||
@state()
|
||||
private _sections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _visibleSections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _extraSections: Array<UmbExtensionManifest> = [];
|
||||
|
||||
@state()
|
||||
private _currentSectionAlias = '';
|
||||
|
||||
private _router?: UmbRouter;
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _sectionSubscription?: Subscription;
|
||||
private _currentSectionSubscription?: Subscription;
|
||||
private _locationSubscription?: Subscription;
|
||||
private _location? : UmbRouteLocation;
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbRouter', (_instance: UmbRouter) => {
|
||||
this._router = _instance;
|
||||
this._useLocation();
|
||||
});
|
||||
|
||||
this.consumeContext('umbSectionContext', (_instance: UmbSectionContext) => {
|
||||
this._sectionContext = _instance;
|
||||
this._useCurrentSection();
|
||||
this._useSections();
|
||||
});
|
||||
}
|
||||
|
||||
private _handleMore(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
this._open = !this._open;
|
||||
}
|
||||
|
||||
private _handleTabClick(e: PointerEvent, section: UmbExtensionManifest) {
|
||||
const tab = e.currentTarget as any;
|
||||
|
||||
// TODO: we need to be able to prevent the tab from setting the active state
|
||||
if (tab.id === 'moreTab') {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: this could maybe be handled by an anchor tag
|
||||
this._router?.push(`/section/${section.name}`);
|
||||
this._sectionContext?.setCurrent(section.alias);
|
||||
}
|
||||
|
||||
private _handleLabelClick(e: PointerEvent) {
|
||||
const label = (e.target as any).label;
|
||||
|
||||
// TODO: set current section
|
||||
//this._sectionContext?.setCurrent(section.alias);
|
||||
|
||||
const moreTab = this.shadowRoot?.getElementById('moreTab');
|
||||
moreTab?.setAttribute('active', 'true');
|
||||
|
||||
this._open = false;
|
||||
}
|
||||
|
||||
private _useLocation () {
|
||||
this._locationSubscription?.unsubscribe();
|
||||
|
||||
this._locationSubscription = this._router?.location
|
||||
.subscribe((location: UmbRouteLocation) => {
|
||||
this._location = location;
|
||||
});
|
||||
}
|
||||
|
||||
private _useCurrentSection () {
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
|
||||
this._currentSectionSubscription = this._sectionContext?.getCurrent()
|
||||
.subscribe(section => {
|
||||
this._currentSectionAlias = section.alias;
|
||||
});
|
||||
}
|
||||
|
||||
private async _useSections() {
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
|
||||
const { data } = await getUserSections({});
|
||||
this._allowedSection = data.sections;
|
||||
|
||||
this._sectionSubscription = this._sectionContext?.getSections()
|
||||
.subscribe((sectionExtensions: any) => {
|
||||
this._sections = sectionExtensions.filter((section: any) => this._allowedSection.includes(section.alias));
|
||||
this._visibleSections = this._sections;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const currentSectionAlias = this._sections.find(section => section.name === this._location?.params?.section)?.alias;
|
||||
if (!currentSectionAlias) return;
|
||||
this._sectionContext?.setCurrent(currentSectionAlias);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private _renderExtraSections() {
|
||||
return when(
|
||||
this._extraSections.length > 0,
|
||||
() => html`
|
||||
<uui-tab id="moreTab" @click="${this._handleTabClick}">
|
||||
<uui-popover .open=${this._open} placement="bottom-start" @close="${() => (this._open = false)}">
|
||||
<uui-button slot="trigger" look="primary" label="More" @click="${this._handleMore}" compact>
|
||||
<uui-symbol-more></uui-symbol-more>
|
||||
</uui-button>
|
||||
|
||||
<div slot="popover" id="dropdown">
|
||||
${this._extraSections.map(
|
||||
(section) => html`
|
||||
<uui-menu-item
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click-label="${this._handleLabelClick}"></uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</uui-popover>
|
||||
</uui-tab>
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="appHeader">
|
||||
@@ -225,31 +46,8 @@ export class UmbBackofficeHeader extends UmbContextConsumerMixin(LitElement) {
|
||||
<img src="/umbraco_logomark_white.svg" alt="Umbraco" />
|
||||
</uui-button>
|
||||
|
||||
<div id="sections">
|
||||
<uui-tab-group id="tabs">
|
||||
${this._visibleSections.map(
|
||||
(section) => html`
|
||||
<uui-tab
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click="${(e: PointerEvent) => this._handleTabClick(e, section)}"></uui-tab>
|
||||
`
|
||||
)}
|
||||
${this._renderExtraSections()}
|
||||
</uui-tab-group>
|
||||
</div>
|
||||
|
||||
<div id="tools">
|
||||
<uui-button class="tool" look="primary" label="Search" compact>
|
||||
<uui-icon name="search"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button class="tool" look="primary" label="Help" compact>
|
||||
<uui-icon name="favorite"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button look="primary" style="font-size: 14px;" label="User" compact>
|
||||
<uui-avatar name="Mads Rasmussen"></uui-avatar>
|
||||
</uui-button>
|
||||
</div>
|
||||
<umb-backoffice-header-sections id="sections"></umb-backoffice-header-sections>
|
||||
<umb-backoffice-header-tools></umb-backoffice-header-tools>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import './backoffice-header.element';
|
||||
import './backoffice-sidebar.element';
|
||||
import './backoffice-main.element';
|
||||
|
||||
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
|
||||
import './backoffice-header.element';
|
||||
import './backoffice-sidebar.element';
|
||||
import './backoffice-main.element';
|
||||
|
||||
@defineElement('umb-backoffice')
|
||||
export class UmbBackoffice extends LitElement {
|
||||
static styles = [
|
||||
@@ -20,18 +20,9 @@ export class UmbBackoffice extends LitElement {
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
// TODO: main div and then side and main again within? I propose the backoffice-main begin renamed to something less main-ish.
|
||||
// TODO: I would think umb-backoffice-header would be outside the router outlet? so its always present.
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-backoffice-header></umb-backoffice-header>
|
||||
|
||||
@@ -72,15 +72,15 @@ export class UmbContentDashboards extends UmbContextConsumerMixin(LitElement) {
|
||||
|
||||
// TODO: Temp redirect solution
|
||||
if (dashboardLocation === 'undefined') {
|
||||
this._router?.push(`/section/${sectionLocation}/dashboard/${this._dashboards[0].name}`);
|
||||
this._router?.push(`/section/${sectionLocation}/dashboard/${this._dashboards[0].meta.pathname}`);
|
||||
this._setCurrent(this._dashboards[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
const dashboard = this._dashboards.find(dashboard => dashboard.name === dashboardLocation);
|
||||
const dashboard = this._dashboards.find(dashboard => dashboard.meta.pathname === dashboardLocation);
|
||||
|
||||
if (!dashboard) {
|
||||
this._router?.push(`/section/${sectionLocation}/dashboard/${this._dashboards[0].name}`);
|
||||
this._router?.push(`/section/${sectionLocation}/dashboard/${this._dashboards[0].meta.pathname}`);
|
||||
this._setCurrent(this._dashboards[0]);
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ export class UmbContentDashboards extends UmbContextConsumerMixin(LitElement) {
|
||||
private _handleTabClick(e: PointerEvent, dashboard: UmbExtensionManifest) {
|
||||
// TODO: this could maybe be handled by an anchor tag
|
||||
const section = this._location?.params?.section;
|
||||
this._router?.push(`/section/${section}/dashboard/${dashboard.name}`);
|
||||
this._router?.push(`/section/${section}/dashboard/${dashboard.meta.pathname}`);
|
||||
this._setCurrent(dashboard);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,6 @@ export class UmbContentDashboards extends UmbContextConsumerMixin(LitElement) {
|
||||
label=${dashboard.name}
|
||||
?active="${this._current === dashboard.name}"
|
||||
@click="${(e: PointerEvent) => this._handleTabClick(e, dashboard)}"></uui-tab>
|
||||
|
||||
`)}
|
||||
</uui-tab-group>
|
||||
${ this._outlet }
|
||||
|
||||
@@ -2,8 +2,9 @@ import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../core/context';
|
||||
import { DocumentNode, UmbContentService } from './content.service';
|
||||
import { UmbNodesStore } from '../core/stores/nodes.store';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { DocumentNode } from '../mocks/data/content.data';
|
||||
|
||||
@customElement('umb-content-editor')
|
||||
class UmbContentEditor extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -44,19 +45,19 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) {
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
_node?: DocumentNode;
|
||||
|
||||
@property()
|
||||
id!: string;
|
||||
|
||||
private _contentService?: UmbContentService;
|
||||
@state()
|
||||
_node?: DocumentNode;
|
||||
|
||||
private _contentService?: UmbNodesStore;
|
||||
private _nodeSubscription?: Subscription;
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbContentService', (contentService: UmbContentService) => {
|
||||
this.consumeContext('umbContentService', (contentService: UmbNodesStore) => {
|
||||
this._contentService = contentService;
|
||||
this._useNode();
|
||||
});
|
||||
@@ -73,8 +74,8 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) {
|
||||
private _useNode() {
|
||||
this._nodeSubscription?.unsubscribe();
|
||||
|
||||
this._nodeSubscription = this._contentService?.getById(this.id).subscribe(node => {
|
||||
if (!node) return;
|
||||
this._nodeSubscription = this._contentService?.getById(parseInt(this.id)).subscribe(node => {
|
||||
if (!node) return; // TODO: Handle nicely if there is no node.
|
||||
this._node = node;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { css, html, LitElement } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../core/context';
|
||||
import { UmbRouteLocation, UmbRouter } from '../core/router';
|
||||
import { UmbContentService } from './content.service';
|
||||
import { UmbNodesStore } from '../core/stores/nodes.store';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import './content-tree.element';
|
||||
import './content-dashboards.element';
|
||||
@@ -23,12 +24,13 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume
|
||||
];
|
||||
|
||||
private _router?: UmbRouter;
|
||||
private _locationSubscription?: Subscription;
|
||||
private _outlet?: HTMLElement;
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
this.provideContext('umbContentService', new UmbContentService());
|
||||
this.provideContext('umbContentService', new UmbNodesStore());
|
||||
|
||||
this.consumeContext('umbRouter', (_instance: UmbRouter) => {
|
||||
this._router = _instance;
|
||||
@@ -36,8 +38,10 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume
|
||||
});
|
||||
}
|
||||
|
||||
private _useLocation () {
|
||||
this._router?.location
|
||||
private _useLocation () {
|
||||
this._locationSubscription?.unsubscribe();
|
||||
|
||||
this._locationSubscription = this._router?.location
|
||||
.subscribe((location: UmbRouteLocation) => {
|
||||
// TODO: temp outlet solution
|
||||
const nodeId = location.params.nodeId;
|
||||
@@ -56,6 +60,11 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<!-- TODO: Figure out how we name layout components -->
|
||||
|
||||
@@ -4,8 +4,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { UmbContextConsumerMixin } from '../core/context';
|
||||
import { UmbRouteLocation, UmbRouter } from '../core/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { data } from '../mocks/data/content.data';
|
||||
import { UUIMenuItemElement } from '@umbraco-ui/uui';
|
||||
import { data } from './content.service';
|
||||
|
||||
@customElement('umb-content-tree')
|
||||
class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -27,7 +27,7 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
_section?: string;
|
||||
|
||||
@state()
|
||||
_currentNodeId?: string;
|
||||
_currentNodeId?: number;
|
||||
|
||||
private _router?: UmbRouter;
|
||||
private _location?: UmbRouteLocation;
|
||||
@@ -51,10 +51,15 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
this._locationSubscription = this._router?.location.subscribe(location => {
|
||||
this._location = location;
|
||||
this._section = location.params.section;
|
||||
this._currentNodeId = location.params.nodeId;
|
||||
this._currentNodeId = parseInt(location.params.nodeId);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
/* TODO: there are some problems with menu items and click events. They can happen on element inside and outside of the shadow dom
|
||||
which makes it difficult to find the right href in the router.
|
||||
It might make sense to make it possible to use your own anchor tag or button inside a label slot instead.
|
||||
@@ -62,6 +67,8 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
*/
|
||||
private _handleMenuItemClick (e: PointerEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const target = e.target as UUIMenuItemElement;
|
||||
if (!target) return;
|
||||
|
||||
@@ -71,11 +78,6 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
this._router?.push(href);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<a href="${`/section/${this._section}`}">
|
||||
@@ -86,54 +88,14 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) {
|
||||
<!-- TODO: make menu item events bubble so we don't have to attach event listeners on every item -->
|
||||
${ this._tree.map(item => html`
|
||||
<uui-menu-item
|
||||
?active="${item.id === this._currentNodeId}"
|
||||
@click="${this._handleMenuItemClick}"
|
||||
?active="${item.id === this._currentNodeId}"
|
||||
data-id="${item.id}"
|
||||
label="${item.name}"
|
||||
href="/section/${this._section}/node/${item.id}">
|
||||
<uui-icon slot="icon" name="${item.icon}"></uui-icon>
|
||||
</uui-menu-item>
|
||||
`)}
|
||||
|
||||
<!--
|
||||
<uui-menu-item label="Hello World">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Home" active has-children show-children>
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
<uui-menu-item label="Products">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="People">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="About Us" disabled has-children>
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
<uui-menu-item label="History">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Team">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="MyMenuItem" selected has-children>
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
<uui-menu-item label="History">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Team">
|
||||
<uui-icon slot="icon" name="document"></uui-icon>
|
||||
</uui-menu-item>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Blog">
|
||||
<uui-icon slot="icon" name="calendar"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Contact"></uui-menu-item>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item label="Recycle Bin">
|
||||
<uui-icon slot="icon" name="delete"></uui-icon>
|
||||
</uui-menu-item>
|
||||
-->
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fetcher } from 'openapi-typescript-fetch';
|
||||
|
||||
import { paths } from '../../schemas/generated-schema';
|
||||
import { paths } from '../../../schemas/generated-schema';
|
||||
|
||||
const fetcher = Fetcher.for<paths>();
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface UmbExtensionManifestBase {
|
||||
name: string;
|
||||
js?: string | (() => Promise<unknown>);
|
||||
elementName?: string;
|
||||
meta: unknown;
|
||||
meta: any;
|
||||
}
|
||||
|
||||
export type UmbExtensionManifestSection = {
|
||||
@@ -31,6 +31,7 @@ export type UmbExtensionManifestDashboard = {
|
||||
export type UmbExtensionManifest = UmbExtensionManifestBase | UmbExtensionManifestSection | UmbExtensionManifestPropertyEditor;
|
||||
|
||||
export interface UmbManifestSectionMeta {
|
||||
pathname: string, // TODO: how to we want to support pretty urls?
|
||||
weight: number;
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ export interface UmbManifestPropertyEditorMeta {
|
||||
|
||||
export interface UmbManifestDashboardMeta {
|
||||
sections: Array<string>;
|
||||
pathname: string; // TODO: how to we want to support pretty urls?
|
||||
weight: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { components } from '../../schemas/generated-schema';
|
||||
import { components } from '../../../schemas/generated-schema';
|
||||
|
||||
export type PostInstallRequest = components['schemas']['UmbracoPerformInstallRequest'];
|
||||
export type InitResponse = components['schemas']['InitResponse'];
|
||||
@@ -12,7 +12,6 @@ export interface UmbRoute {
|
||||
export interface UmbRouteLocation {
|
||||
pathname: string;
|
||||
params: Record<string, any>;
|
||||
fullPath: string;
|
||||
route: UmbRoute;
|
||||
}
|
||||
|
||||
@@ -75,7 +74,6 @@ export class UmbRouter {
|
||||
}
|
||||
|
||||
public push(pathname: string) {
|
||||
history.pushState(null, '', pathname);
|
||||
this._navigate(pathname);
|
||||
}
|
||||
|
||||
@@ -137,7 +135,6 @@ export class UmbRouter {
|
||||
location = {
|
||||
pathname: result.pathname.input,
|
||||
params: result.pathname.groups,
|
||||
fullPath: result.pathname.input,
|
||||
route,
|
||||
};
|
||||
}
|
||||
|
||||
38
src/Umbraco.Web.UI.Client/src/core/stores/nodes.store.ts
Normal file
38
src/Umbraco.Web.UI.Client/src/core/stores/nodes.store.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BehaviorSubject, map, Observable } from 'rxjs';
|
||||
import { DocumentNode } from '../../mocks/data/content.data';
|
||||
|
||||
export class UmbNodesStore {
|
||||
|
||||
private _nodes: BehaviorSubject<Array<DocumentNode>> = new BehaviorSubject(<Array<DocumentNode>>[]);
|
||||
public readonly nodes: Observable<Array<DocumentNode>> = this._nodes.asObservable();
|
||||
|
||||
getById (id: number): Observable<DocumentNode | null> {
|
||||
// fetch from server and update store
|
||||
fetch(`/umbraco/backoffice/content/${id}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
this._updateStore(data);
|
||||
});
|
||||
|
||||
return this.nodes.pipe(map(((nodes: Array<DocumentNode>) => nodes.find((node: DocumentNode) => node.id === id) || null)));
|
||||
}
|
||||
|
||||
private _updateStore (fetchedNodes: Array<any>) {
|
||||
const storedNodes = this._nodes.getValue();
|
||||
let updated: any = [...storedNodes];
|
||||
|
||||
fetchedNodes.forEach(fetchedNode => {
|
||||
const index = storedNodes.map(storedNode => storedNode.id).indexOf(fetchedNode.id);
|
||||
|
||||
if (index !== -1) {
|
||||
// If the node is already in the store, update it
|
||||
updated[index] = fetchedNode;
|
||||
} else {
|
||||
// If the node is not in the store, add it
|
||||
updated = [...updated, fetchedNode];
|
||||
}
|
||||
})
|
||||
|
||||
this._nodes.next([...updated]);
|
||||
}
|
||||
}
|
||||
@@ -34,25 +34,17 @@ const registerInternalManifests = async () => {
|
||||
elementName: 'umb-content-section',
|
||||
js: () => import('./content/content-section.element'),
|
||||
meta: {
|
||||
pathname: 'content', // TODO: how to we want to support pretty urls?
|
||||
weight: 50
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'Umb.Section.Media',
|
||||
name: 'Media',
|
||||
elementName: 'umb-media-section',
|
||||
js: () => import('./media/media-section.element'),
|
||||
meta: {
|
||||
weight: 40
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'section',
|
||||
alias: 'Umb.Section.Members',
|
||||
name: 'Members',
|
||||
elementName: 'umb-members-section',
|
||||
meta: {
|
||||
pathname: 'members',
|
||||
weight: 30
|
||||
}
|
||||
},
|
||||
@@ -63,6 +55,7 @@ const registerInternalManifests = async () => {
|
||||
elementName: 'umb-settings-section',
|
||||
js: () => import('./settings/settings-section.element'),
|
||||
meta: {
|
||||
pathname: 'settings', // TODO: how to we want to support pretty urls?
|
||||
weight: 20
|
||||
}
|
||||
},
|
||||
@@ -74,6 +67,7 @@ const registerInternalManifests = async () => {
|
||||
js: () => import('./dashboards/dashboard-welcome.element'),
|
||||
meta: {
|
||||
sections: ['Umb.Section.Content'],
|
||||
pathname: 'welcome', // TODO: how to we want to support pretty urls?
|
||||
weight: 20
|
||||
}
|
||||
},
|
||||
@@ -85,9 +79,10 @@ const registerInternalManifests = async () => {
|
||||
js: () => import('./dashboards/dashboard-redirect-management.element'),
|
||||
meta: {
|
||||
sections: ['Umb.Section.Content'],
|
||||
pathname: 'redirect-management', // TODO: how to we want to support pretty urls?
|
||||
weight: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
||||
manifests.forEach((manifest: UmbExtensionManifest) => extensionRegistry.register(manifest));
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UUIBooleanInputEvent, UUISelectElement } from '@umbraco-ui/uui';
|
||||
import {
|
||||
PostInstallRequest,
|
||||
UmbracoInstallerDatabaseModel,
|
||||
UmbracoPerformInstallDatabaseConfiguration,
|
||||
} from '../models';
|
||||
import { UmbracoInstallerDatabaseModel, UmbracoPerformInstallDatabaseConfiguration } from '../core/models';
|
||||
|
||||
@customElement('umb-installer-database')
|
||||
export class UmbInstallerDatabase extends LitElement {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { PostInstallRequest, UmbracoInstallerUserModel } from '../models';
|
||||
import { PostInstallRequest, UmbracoInstallerUserModel } from '../core/models';
|
||||
|
||||
@customElement('umb-installer-user')
|
||||
export class UmbInstallerUser extends LitElement {
|
||||
|
||||
@@ -6,8 +6,8 @@ import './installer-user.element';
|
||||
import { css, CSSResultGroup, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { getInstall, postInstall } from '../api/fetcher';
|
||||
import { PostInstallRequest, UmbracoInstaller, UmbracoPerformInstallRequest } from '../models';
|
||||
import { getInstall, postInstall } from '../core/api/fetcher';
|
||||
import { PostInstallRequest, UmbracoInstaller, UmbracoPerformInstallRequest } from '../core/models';
|
||||
|
||||
@customElement('umb-installer')
|
||||
export class UmbInstaller extends LitElement {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
|
||||
@defineElement('umb-media-section')
|
||||
export class UmbMediaSection extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css``,
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`<div>Media Section</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-media-section': UmbMediaSection;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { BehaviorSubject, map, Observable } from 'rxjs';
|
||||
|
||||
export interface DocumentNode {
|
||||
id: string;
|
||||
id: number;
|
||||
key: string;
|
||||
name: string;
|
||||
alias: string;
|
||||
@@ -19,9 +17,9 @@ export interface NodeProperty {
|
||||
tempValue: string; // TODO: remove this - only used for testing
|
||||
}
|
||||
|
||||
export const data: Array<DocumentNode> = [
|
||||
export const data = [
|
||||
{
|
||||
id: '1',
|
||||
id: 1,
|
||||
key: '74e4008a-ea4f-4793-b924-15e02fd380d3',
|
||||
name: 'Document 1',
|
||||
alias: 'document1',
|
||||
@@ -71,7 +69,7 @@ export const data: Array<DocumentNode> = [
|
||||
*/
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
id: 2,
|
||||
key: '74e4008a-ea4f-4793-b924-15e02fd380d3',
|
||||
name: 'Document 2',
|
||||
alias: 'document2',
|
||||
@@ -122,17 +120,17 @@ export const data: Array<DocumentNode> = [
|
||||
}
|
||||
];
|
||||
|
||||
export class UmbContentService {
|
||||
|
||||
private _nodes: BehaviorSubject<Array<DocumentNode>> = new BehaviorSubject(<Array<DocumentNode>>[]);
|
||||
public readonly nodes: Observable<Array<DocumentNode>> = this._nodes.asObservable();
|
||||
// Temp mocked database
|
||||
class UmbContentData {
|
||||
private _data: Array<DocumentNode> = [];
|
||||
|
||||
constructor () {
|
||||
this._nodes.next(data);
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
getById (id: string): Observable<DocumentNode | null> {
|
||||
return this.nodes.pipe(map(((nodes: Array<DocumentNode>) => nodes.find((node: DocumentNode) => node.id === id) || null)));
|
||||
getById (id: number) {
|
||||
return this._data.find(item => item.id === id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export const umbContentData = new UmbContentData();
|
||||
@@ -0,0 +1,17 @@
|
||||
import { rest } from 'msw';
|
||||
import { umbContentData } from '../data/content.data';
|
||||
|
||||
// TODO: add schema
|
||||
export const handlers = [
|
||||
rest.get('/umbraco/backoffice/content/:id', (req, res, ctx) => {
|
||||
const id = req.params.id as string;
|
||||
if (!id) return;
|
||||
|
||||
const int = parseInt(id);
|
||||
const document = umbContentData.getById(int);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json([document])
|
||||
);
|
||||
}),
|
||||
];
|
||||
@@ -1,10 +1,5 @@
|
||||
import { rest } from 'msw';
|
||||
import {
|
||||
ErrorResponse,
|
||||
UmbracoInstaller,
|
||||
UmbracoPerformInstallDatabaseConfiguration,
|
||||
UmbracoPerformInstallRequest,
|
||||
} from '../../models';
|
||||
import { ErrorResponse, UmbracoInstaller, UmbracoPerformInstallRequest } from '../../core/models';
|
||||
|
||||
export const handlers = [
|
||||
rest.get('/umbraco/backoffice/install', (_req, res, ctx) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const handlers = [
|
||||
name: 'Custom',
|
||||
elementName: 'umb-custom-section',
|
||||
meta: {
|
||||
pathname: 'my-custom',
|
||||
weight: 30,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { rest } from 'msw';
|
||||
import { AllowedSectionsResponse, UserResponse } from '../../models';
|
||||
import { AllowedSectionsResponse, UserResponse } from '../../core/models';
|
||||
|
||||
export const handlers = [
|
||||
rest.post('/umbraco/backoffice/user/login', (_req, res, ctx) => {
|
||||
@@ -46,7 +46,7 @@ export const handlers = [
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json<AllowedSectionsResponse>({
|
||||
sections: ['Umb.Section.Content', 'Umb.Section.Media', 'Umb.Section.Settings', 'My.Section.Custom'],
|
||||
sections: ['Umb.Section.Content', 'Umb.Section.Settings', 'My.Section.Custom'],
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { rest } from 'msw';
|
||||
import { VersionResponse } from '../../models';
|
||||
import { VersionResponse } from '../../core/models';
|
||||
|
||||
// TODO: set up schema
|
||||
export const handlers = [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { rest } from 'msw';
|
||||
import { InitResponse } from '../models';
|
||||
import { InitResponse } from '../core/models';
|
||||
import { handlers as contentHandlers } from './domains/content.handlers';
|
||||
import { handlers as installHandlers } from './domains/install.handlers';
|
||||
import { handlers as manifestsHandlers } from './domains/manifests.handlers';
|
||||
import { handlers as userHandlers } from './domains/user.handlers';
|
||||
@@ -15,6 +16,7 @@ export const handlers = [
|
||||
})
|
||||
);
|
||||
}),
|
||||
...contentHandlers,
|
||||
...installHandlers,
|
||||
...manifestsHandlers,
|
||||
...userHandlers,
|
||||
|
||||
Reference in New Issue
Block a user