diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 2d86a0f1b1..4ef3a5db44 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -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) { diff --git a/src/Umbraco.Web.UI.Client/src/auth/login/login.element.ts b/src/Umbraco.Web.UI.Client/src/auth/login/login.element.ts index 1af0293e7d..f81f4f4210 100644 --- a/src/Umbraco.Web.UI.Client/src/auth/login/login.element.ts +++ b/src/Umbraco.Web.UI.Client/src/auth/login/login.element.ts @@ -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'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-sections.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-sections.element.ts new file mode 100644 index 0000000000..e34c0ce43a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-sections.element.ts @@ -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 = []; + + @state() + private _sections: Array = []; + + @state() + private _visibleSections: Array = []; + + @state() + private _extraSections: Array = []; + + @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` + + ${this._visibleSections.map( + (section) => html` + + ` + )} + ${this._renderExtraSections()} + + `; + } + + private _renderExtraSections() { + return when( + this._extraSections.length > 0, + () => html` + + + + + + + + + + ` + ); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-backoffice-header-sections': UmbBackofficeHeaderSections; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-tools.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-tools.element.ts new file mode 100644 index 0000000000..fdb90b3ce9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header-tools.element.ts @@ -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` +
+ + + + + + + + + +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-backoffice-header-tools': UmbBackofficeHeaderTools; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header.element.ts index 818be6ee88..393cb15405 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-header.element.ts @@ -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 = []; - - @state() - private _sections: Array = []; - - @state() - private _visibleSections: Array = []; - - @state() - private _extraSections: Array = []; - - @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` - - - - - - - - - - ` - ); - } - render() { return html`
@@ -225,31 +46,8 @@ export class UmbBackofficeHeader extends UmbContextConsumerMixin(LitElement) { Umbraco -
- - ${this._visibleSections.map( - (section) => html` - - ` - )} - ${this._renderExtraSections()} - -
- -
- - - - - - - - - -
+ +
`; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts index 3e0c6b7890..7a67950cac 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice.element.ts @@ -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` diff --git a/src/Umbraco.Web.UI.Client/src/content/content-dashboards.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-dashboards.element.ts index 62ff4e7929..f14f71af3a 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-dashboards.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-dashboards.element.ts @@ -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)}"> - `)} ${ this._outlet } diff --git a/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts index 9e529e051c..387b6ca4d0 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts @@ -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; }); } diff --git a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts index f4e23e93c3..f07f64d2b3 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts @@ -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` diff --git a/src/Umbraco.Web.UI.Client/src/content/content-tree.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-tree.element.ts index 8f917f4ebc..aae89e458d 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-tree.element.ts @@ -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` @@ -86,54 +88,14 @@ class UmbContentTree extends UmbContextConsumerMixin(LitElement) { ${ this._tree.map(item => html` `)} - - `; } diff --git a/src/Umbraco.Web.UI.Client/src/api/fetcher.ts b/src/Umbraco.Web.UI.Client/src/core/api/fetcher.ts similarity index 92% rename from src/Umbraco.Web.UI.Client/src/api/fetcher.ts rename to src/Umbraco.Web.UI.Client/src/core/api/fetcher.ts index 5f4e8c0d0b..c4c3b471f4 100644 --- a/src/Umbraco.Web.UI.Client/src/api/fetcher.ts +++ b/src/Umbraco.Web.UI.Client/src/core/api/fetcher.ts @@ -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(); diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts index 1f5aebb181..ee8a78929c 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts @@ -10,7 +10,7 @@ export interface UmbExtensionManifestBase { name: string; js?: string | (() => Promise); 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; + pathname: string; // TODO: how to we want to support pretty urls? weight: number; } diff --git a/src/Umbraco.Web.UI.Client/src/models/index.ts b/src/Umbraco.Web.UI.Client/src/core/models/index.ts similarity index 93% rename from src/Umbraco.Web.UI.Client/src/models/index.ts rename to src/Umbraco.Web.UI.Client/src/core/models/index.ts index 7c7e8f600d..f7f873f998 100644 --- a/src/Umbraco.Web.UI.Client/src/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/models/index.ts @@ -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']; diff --git a/src/Umbraco.Web.UI.Client/src/core/router/router.ts b/src/Umbraco.Web.UI.Client/src/core/router/router.ts index 5068b7aa65..cd1cbc2229 100644 --- a/src/Umbraco.Web.UI.Client/src/core/router/router.ts +++ b/src/Umbraco.Web.UI.Client/src/core/router/router.ts @@ -12,7 +12,6 @@ export interface UmbRoute { export interface UmbRouteLocation { pathname: string; params: Record; - 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, }; } diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/nodes.store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/nodes.store.ts new file mode 100644 index 0000000000..6c733470eb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/stores/nodes.store.ts @@ -0,0 +1,38 @@ +import { BehaviorSubject, map, Observable } from 'rxjs'; +import { DocumentNode } from '../../mocks/data/content.data'; + +export class UmbNodesStore { + + private _nodes: BehaviorSubject> = new BehaviorSubject(>[]); + public readonly nodes: Observable> = this._nodes.asObservable(); + + getById (id: number): Observable { + // 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) => nodes.find((node: DocumentNode) => node.id === id) || null))); + } + + private _updateStore (fetchedNodes: Array) { + 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]); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/index.ts b/src/Umbraco.Web.UI.Client/src/index.ts index 46dc8c6eb8..8900c9de0f 100644 --- a/src/Umbraco.Web.UI.Client/src/index.ts +++ b/src/Umbraco.Web.UI.Client/src/index.ts @@ -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)); diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts index b224ad6ec4..332dc793eb 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-database.element.ts @@ -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 { diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer-user.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer-user.element.ts index 4bbc979476..1c041a5f32 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer-user.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer-user.element.ts @@ -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 { diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts index 3988d29189..bdf80df88f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts @@ -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 { diff --git a/src/Umbraco.Web.UI.Client/src/media/media-section.element.ts b/src/Umbraco.Web.UI.Client/src/media/media-section.element.ts deleted file mode 100644 index edb74eb32c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/media/media-section.element.ts +++ /dev/null @@ -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`
Media Section
`; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-media-section': UmbMediaSection; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/content/content.service.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts similarity index 81% rename from src/Umbraco.Web.UI.Client/src/content/content.service.ts rename to src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts index d456307c59..34f2b84e0b 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content.service.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts @@ -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 = [ +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 = [ */ }, { - id: '2', + id: 2, key: '74e4008a-ea4f-4793-b924-15e02fd380d3', name: 'Document 2', alias: 'document2', @@ -122,17 +120,17 @@ export const data: Array = [ } ]; -export class UmbContentService { - - private _nodes: BehaviorSubject> = new BehaviorSubject(>[]); - public readonly nodes: Observable> = this._nodes.asObservable(); +// Temp mocked database +class UmbContentData { + private _data: Array = []; constructor () { - this._nodes.next(data); + this._data = data; } - getById (id: string): Observable { - return this.nodes.pipe(map(((nodes: Array) => nodes.find((node: DocumentNode) => node.id === id) || null))); + getById (id: number) { + return this._data.find(item => item.id === id); } +} -} \ No newline at end of file +export const umbContentData = new UmbContentData(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts new file mode 100644 index 0000000000..8354a74589 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts @@ -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]) + ); + }), +]; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts index a4452f69b6..0c937e7564 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/install.handlers.ts @@ -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) => { diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts index e25e02dfc0..13e5bd0707 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/manifests.handlers.ts @@ -14,6 +14,7 @@ export const handlers = [ name: 'Custom', elementName: 'umb-custom-section', meta: { + pathname: 'my-custom', weight: 30, }, }, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts index d3c4c30cb0..c870781a0f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/user.handlers.ts @@ -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({ - sections: ['Umb.Section.Content', 'Umb.Section.Media', 'Umb.Section.Settings', 'My.Section.Custom'], + sections: ['Umb.Section.Content', 'Umb.Section.Settings', 'My.Section.Custom'], }) ); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/version.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/version.handlers.ts index ad226310b8..c6af657ca8 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/version.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/version.handlers.ts @@ -1,5 +1,5 @@ import { rest } from 'msw'; -import { VersionResponse } from '../../models'; +import { VersionResponse } from '../../core/models'; // TODO: set up schema export const handlers = [ diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers.ts index 2a98e94547..28f80e2f09 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers.ts @@ -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,