This commit is contained in:
JesmoDev
2022-05-31 12:52:20 +02:00
27 changed files with 385 additions and 362 deletions

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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>
`;
}

View File

@@ -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>

View File

@@ -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 }

View File

@@ -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;
});
}

View File

@@ -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 -->

View File

@@ -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>
`;
}

View File

@@ -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>();

View File

@@ -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;
}

View File

@@ -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'];

View File

@@ -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,
};
}

View 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]);
}
}

View File

@@ -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));

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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])
);
}),
];

View File

@@ -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) => {

View File

@@ -14,6 +14,7 @@ export const handlers = [
name: 'Custom',
elementName: 'umb-custom-section',
meta: {
pathname: 'my-custom',
weight: 30,
},
},

View File

@@ -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'],
})
);
}),

View File

@@ -1,5 +1,5 @@
import { rest } from 'msw';
import { VersionResponse } from '../../models';
import { VersionResponse } from '../../core/models';
// TODO: set up schema
export const handlers = [

View File

@@ -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,