render section element when section changes
This commit is contained in:
@@ -1,31 +1,36 @@
|
||||
import '@umbraco-ui/uui';
|
||||
import '@umbraco-ui/uui-css/dist/uui-css.css';
|
||||
|
||||
// TODO: lazy load these
|
||||
import './installer/installer.element';
|
||||
import './auth/login/login.element';
|
||||
import './auth/auth-layout.element';
|
||||
import './backoffice/backoffice.element';
|
||||
import './installer/installer.element';
|
||||
|
||||
import { UmbSectionContext } from './section.context';
|
||||
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
|
||||
import { getInitStatus } from './api/fetcher';
|
||||
import { isUmbRouterBeforeEnterEvent, UmbRoute, UmbRouter, UmbRouterBeforeEnterEvent, umbRouterBeforeEnterEventType } from './core/router';
|
||||
import { isUmbRouterBeforeEnterEvent, UmbRoute, UmbRouteLocation, UmbRouter, UmbRouterBeforeEnterEvent, umbRouterBeforeEnterEventType } from './core/router';
|
||||
import { UmbContextProvideMixin } from './core/context';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
const routes: Array<UmbRoute> = [
|
||||
{
|
||||
path: '/login',
|
||||
elementName: 'umb-login',
|
||||
alias: 'login',
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/install',
|
||||
elementName: 'umb-installer',
|
||||
alias: 'install',
|
||||
meta: { requiresAuth: false },
|
||||
},
|
||||
{
|
||||
path: '/section/:section',
|
||||
elementName: 'umb-backoffice',
|
||||
alias: 'app',
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
];
|
||||
@@ -42,7 +47,11 @@ export class UmbApp extends UmbContextProvideMixin(LitElement) {
|
||||
}
|
||||
`;
|
||||
|
||||
_router?: UmbRouter;
|
||||
private _isInstalled = false;
|
||||
|
||||
private _view?: HTMLElement;
|
||||
private _router?: UmbRouter;
|
||||
private _locationSubscription?: Subscription;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -51,7 +60,10 @@ export class UmbApp extends UmbContextProvideMixin(LitElement) {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
const { extensionRegistry } = window.Umbraco;
|
||||
|
||||
this.provide('umbExtensionRegistry', window.Umbraco.extensionRegistry);
|
||||
this.provide('umbSectionContext', new UmbSectionContext(extensionRegistry));
|
||||
}
|
||||
|
||||
private _onBeforeEnter = (event: Event) => {
|
||||
@@ -71,10 +83,7 @@ export class UmbApp extends UmbContextProvideMixin(LitElement) {
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
const outlet = this.shadowRoot?.getElementById('outlet');
|
||||
if (!outlet) return;
|
||||
|
||||
this._router = new UmbRouter(this, outlet);
|
||||
this._router = new UmbRouter(this);
|
||||
this._router.setRoutes(routes);
|
||||
|
||||
// TODO: find a solution for magic strings
|
||||
@@ -83,27 +92,59 @@ export class UmbApp extends UmbContextProvideMixin(LitElement) {
|
||||
try {
|
||||
const { data } = await getInitStatus({});
|
||||
|
||||
if (!data.installed) {
|
||||
this._isInstalled = data.installed;
|
||||
|
||||
if (!this._isInstalled) {
|
||||
this._router.push('/install');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._isAuthorized()) {
|
||||
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);
|
||||
}
|
||||
|
||||
this._useLocation();
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _useLocation () {
|
||||
this._locationSubscription?.unsubscribe();
|
||||
|
||||
this._locationSubscription = this._router?.location
|
||||
.subscribe((location: UmbRouteLocation) => {
|
||||
if (location.route.alias === 'login') {
|
||||
this._renderView('umb-login');
|
||||
return;
|
||||
}
|
||||
|
||||
if (location.route.alias === 'install') {
|
||||
this._renderView('umb-installer');
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderView('umb-backoffice');
|
||||
});
|
||||
}
|
||||
|
||||
_renderView (view: string) {
|
||||
if (this._view?.tagName === view.toUpperCase()) return;
|
||||
this._view = document.createElement(view);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._locationSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="outlet"></div>
|
||||
`;
|
||||
return html`${this._view}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ export class UmbLogin extends UmbContextInjectMixin(LitElement) {
|
||||
await postUserLogin({ username, password, persist });
|
||||
this._loggingIn = false;
|
||||
// TODO: how do we know where to go?
|
||||
this._router?.push('/section/content');
|
||||
this._router?.push('/section/Content');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this._loggingIn = false;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Subscription } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
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 { UmbContextInjectMixin } from '../core/context';
|
||||
import { UmbExtensionManifest, UmbExtensionRegistry, UmbManifestSectionMeta } from '../core/extension';
|
||||
import { UmbRouter } from '../core/router';
|
||||
import { UmbExtensionManifest, UmbManifestSectionMeta } from '../core/extension';
|
||||
import { UmbRouteLocation, UmbRouter } from '../core/router';
|
||||
import { UmbSectionContext } from '../section.context';
|
||||
|
||||
// TODO: umb or not umb in file name?
|
||||
|
||||
@@ -81,9 +82,6 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
@state()
|
||||
private _open = false;
|
||||
|
||||
@state()
|
||||
private _availableSections: Array<UmbExtensionManifest<UmbManifestSectionMeta>> = [];
|
||||
|
||||
@state()
|
||||
private _allowedSection: Array<string> = [];
|
||||
|
||||
@@ -97,11 +95,14 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
private _extraSections: Array<UmbExtensionManifest<UmbManifestSectionMeta>> = [];
|
||||
|
||||
@state()
|
||||
private _activeSection = '';
|
||||
private _currentSectionAlias = '';
|
||||
|
||||
private _router?: UmbRouter;
|
||||
private _extensionRegistry?: UmbExtensionRegistry;
|
||||
private _subscription?: Subscription;
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _sectionSubscription?: Subscription;
|
||||
private _currentSectionSubscription?: Subscription;
|
||||
private _locationSubscription?: Subscription;
|
||||
private _location? : UmbRouteLocation;
|
||||
|
||||
private _handleMore(e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
@@ -118,12 +119,14 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
|
||||
// TODO: this could maybe be handled by an anchor tag
|
||||
this._router?.push(`/section/${section.name}`);
|
||||
this._activeSection = section.alias;
|
||||
this._sectionContext?.setCurrent(section.alias);
|
||||
}
|
||||
|
||||
private _handleLabelClick(e: PointerEvent) {
|
||||
const label = (e.target as any).label;
|
||||
this._activeSection = label;
|
||||
|
||||
// TODO: set current section
|
||||
//this._sectionContext?.setCurrent(section.alias);
|
||||
|
||||
const moreTab = this.shadowRoot?.getElementById('moreTab');
|
||||
moreTab?.setAttribute('active', 'true');
|
||||
@@ -135,40 +138,64 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
super.connectedCallback();
|
||||
|
||||
this.requestContext('umbRouter');
|
||||
this.requestContext('umbExtensionRegistry');
|
||||
this.requestContext('umbSectionContext');
|
||||
}
|
||||
|
||||
contextInjected(contexts: Map<string, any>): void {
|
||||
if (contexts.has('umbExtensionRegistry')) {
|
||||
this._extensionRegistry = contexts.get('umbExtensionRegistry');
|
||||
this._useSections();
|
||||
}
|
||||
|
||||
if (contexts.has('umbRouter')) {
|
||||
this._router = contexts.get('umbRouter');
|
||||
this._useLocation();
|
||||
}
|
||||
|
||||
if (contexts.has('umbSectionContext')) {
|
||||
this._sectionContext = contexts.get('umbSectionContext');
|
||||
this._useCurrentSection();
|
||||
this._useSections();
|
||||
}
|
||||
}
|
||||
|
||||
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._subscription?.unsubscribe();
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
|
||||
const { data } = await getUserSections({});
|
||||
this._allowedSection = data.sections;
|
||||
|
||||
this._subscription = this._extensionRegistry?.extensions
|
||||
.pipe(
|
||||
map((extensions: Array<UmbExtensionManifest<unknown>>) =>
|
||||
extensions.filter(extension => extension.type === 'section')
|
||||
))
|
||||
this._sectionSubscription = this._sectionContext?.getSections()
|
||||
.subscribe((sectionExtensions: any) => {
|
||||
this._availableSections = [...sectionExtensions];
|
||||
this._sections = this._availableSections.filter((section) => this._allowedSection.includes(section.alias));
|
||||
// TODO: implement resize observer
|
||||
this._sections = sectionExtensions.filter((section: any) => this._allowedSection.includes(section.alias));
|
||||
this._visibleSections = this._sections;
|
||||
this._activeSection = this._visibleSections?.[0].alias;
|
||||
|
||||
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,
|
||||
@@ -183,7 +210,7 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
${this._extraSections.map(
|
||||
(section) => html`
|
||||
<uui-menu-item
|
||||
?active="${this._activeSection === section.alias}"
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click-label="${this._handleLabelClick}"></uui-menu-item>
|
||||
`
|
||||
@@ -207,7 +234,7 @@ export class UmbBackofficeHeader extends UmbContextInjectMixin(LitElement) {
|
||||
${this._visibleSections.map(
|
||||
(section) => html`
|
||||
<uui-tab
|
||||
?active="${this._activeSection === section.alias}"
|
||||
?active="${this._currentSectionAlias === section.alias}"
|
||||
label="${section.name}"
|
||||
@click="${(e: PointerEvent) => this._handleTabClick(e, section)}"></uui-tab>
|
||||
`
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { UmbContextInjectMixin } from '../core/context';
|
||||
import { UmbSectionContext } from '../section.context';
|
||||
|
||||
import { UmbExtensionManifest, UmbManifestSectionMeta } from '../core/extension';
|
||||
|
||||
// TODO: lazy load these. How to we handle dynamic import of our typescript file?
|
||||
import '../content/content-section.element';
|
||||
import '../media/media-section.element';
|
||||
|
||||
@defineElement('umb-backoffice-main')
|
||||
export class UmbBackofficeMain extends LitElement {
|
||||
export class UmbBackofficeMain extends UmbContextInjectMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
@@ -70,8 +81,56 @@ export class UmbBackofficeMain extends LitElement {
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _sectionElement?: HTMLElement;
|
||||
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _currentSectionSubscription?: Subscription;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.requestContext('umbRouter');
|
||||
this.requestContext('umbSectionContext');
|
||||
}
|
||||
|
||||
contextInjected(contexts: Map<string, any>): void {
|
||||
if (contexts.has('umbSectionContext')) {
|
||||
this._sectionContext = contexts.get('umbSectionContext');
|
||||
this._useCurrentSection();
|
||||
}
|
||||
}
|
||||
|
||||
private _useCurrentSection () {
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
|
||||
this._currentSectionSubscription = this._sectionContext?.getCurrent()
|
||||
.subscribe(section => {
|
||||
this._createSectionElement(section);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._currentSectionSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private async _createSectionElement (section: UmbExtensionManifest<UmbManifestSectionMeta>) {
|
||||
if (!section) return;
|
||||
|
||||
// TODO: How do we handle dynamic imports of our files?
|
||||
if (section.js) {
|
||||
await import(/* @vite-ignore */section.js);
|
||||
}
|
||||
|
||||
if (section.elementName) {
|
||||
this._sectionElement = document.createElement(section.elementName);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${ this._sectionElement }
|
||||
<!--
|
||||
<div id="editor">
|
||||
<div id="editor-top">
|
||||
<uui-input value="Home"></uui-input>
|
||||
@@ -88,6 +147,7 @@ export class UmbBackofficeMain extends LitElement {
|
||||
<uui-button look="primary" color="positive">Save and publish</uui-button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UmbRouterBeforeLeaveEvent } from './router-before-leave.event';
|
||||
|
||||
export interface UmbRoute {
|
||||
path: string;
|
||||
elementName: string;
|
||||
alias: string;
|
||||
meta?: any;
|
||||
}
|
||||
|
||||
@@ -24,15 +24,13 @@ export interface UmbRouteElement extends HTMLElement {
|
||||
export class UmbRouter {
|
||||
private _routes: Array<UmbRoute> = [];
|
||||
private _host: HTMLElement;
|
||||
private _outlet: HTMLElement;
|
||||
private _element?: UmbRouteElement;
|
||||
|
||||
private _location: ReplaySubject<UmbRouteLocation> = new ReplaySubject(1);
|
||||
public readonly location: Observable<UmbRouteLocation> = this._location.asObservable();
|
||||
|
||||
constructor(host: HTMLElement, outlet: HTMLElement) {
|
||||
constructor(host: HTMLElement) {
|
||||
this._host = host;
|
||||
this._outlet = outlet;
|
||||
|
||||
// Anchor Hijacker
|
||||
this._host.addEventListener('click', async (event: any) => {
|
||||
@@ -110,15 +108,12 @@ export class UmbRouter {
|
||||
const canLeave = await this._requestLeave(location);
|
||||
if (!canLeave) return;
|
||||
|
||||
this._setupElement(location);
|
||||
|
||||
const canEnter = await this._requestEnter(location);
|
||||
if (!canEnter) return;
|
||||
|
||||
window.history.pushState(null, '', pathname);
|
||||
|
||||
this._location.next(location);
|
||||
this._render();
|
||||
}
|
||||
|
||||
private _resolve(pathname: string): UmbRouteLocation | null {
|
||||
@@ -144,20 +139,4 @@ export class UmbRouter {
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
private _setupElement(location: UmbRouteLocation) {
|
||||
this._element = document.createElement(location.route.elementName);
|
||||
this._element.location = location;
|
||||
}
|
||||
|
||||
private async _render() {
|
||||
if (!this._element) return;
|
||||
|
||||
const childNodes = this._outlet.childNodes;
|
||||
childNodes.forEach((node) => {
|
||||
this._outlet.removeChild(node);
|
||||
});
|
||||
|
||||
this._outlet.appendChild(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
31
src/Umbraco.Web.UI.Client/src/section.context.ts
Normal file
31
src/Umbraco.Web.UI.Client/src/section.context.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { firstValueFrom, map, Observable, ReplaySubject } from 'rxjs';
|
||||
import { UmbExtensionManifest, UmbExtensionRegistry, UmbManifestSectionMeta } from './core/extension';
|
||||
|
||||
export class UmbSectionContext {
|
||||
private _extensionRegistry!: UmbExtensionRegistry;
|
||||
|
||||
private _current: ReplaySubject<UmbExtensionManifest<UmbManifestSectionMeta>> = new ReplaySubject(1);
|
||||
public readonly current: Observable<UmbExtensionManifest<UmbManifestSectionMeta>> = this._current.asObservable();
|
||||
|
||||
constructor(_extensionRegistry: UmbExtensionRegistry) {
|
||||
this._extensionRegistry = _extensionRegistry;
|
||||
}
|
||||
|
||||
getSections () {
|
||||
return this._extensionRegistry.extensions
|
||||
.pipe(
|
||||
map((extensions: Array<UmbExtensionManifest<unknown>>) => extensions.filter(extension => extension.type === 'section'))
|
||||
);
|
||||
}
|
||||
|
||||
getCurrent () {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
async setCurrent (sectionAlias: string) {
|
||||
const sections = await firstValueFrom(this.getSections());
|
||||
const matchedSection = sections.find(section => section.alias === sectionAlias) as UmbExtensionManifest<UmbManifestSectionMeta>;
|
||||
this._current.next(matchedSection);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user