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..d8c962bf44 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 { UmbContentService } from './content.service'; import { Subscription } from 'rxjs'; +import { DocumentNode } from '../mocks/data/content.data'; @customElement('umb-content-editor') class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { @@ -44,12 +45,12 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { `, ]; - @state() - _node?: DocumentNode; - @property() id!: string; + @state() + _node?: DocumentNode; + private _contentService?: UmbContentService; private _nodeSubscription?: Subscription; @@ -73,7 +74,7 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { private _useNode() { this._nodeSubscription?.unsubscribe(); - this._nodeSubscription = this._contentService?.getById(this.id).subscribe(node => { + this._nodeSubscription = this._contentService?.getById(parseInt(this.id)).subscribe(node => { if (!node) return; 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..7982d59352 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 @@ -4,6 +4,7 @@ import { customElement } from 'lit/decorators.js'; import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../core/context'; import { UmbRouteLocation, UmbRouter } from '../core/router'; import { UmbContentService } from './content.service'; +import { Subscription } from 'rxjs'; import './content-tree.element'; import './content-dashboards.element'; @@ -23,6 +24,7 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume ]; private _router?: UmbRouter; + private _locationSubscription?: Subscription; private _outlet?: HTMLElement; constructor () { @@ -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/content/content.service.ts b/src/Umbraco.Web.UI.Client/src/content/content.service.ts index d456307c59..5c37053309 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content.service.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content.service.ts @@ -1,138 +1,37 @@ import { BehaviorSubject, map, Observable } from 'rxjs'; - -export interface DocumentNode { - id: string; - key: string; - name: string; - alias: string; - icon: string; // TODO: should come from the doc type? - properties: NodeProperty[]; - data: any; // TODO: define data type - layout?: any; // TODO: define layout type - make it non-optional -} - -export interface NodeProperty { - alias: string; - label: string; - description: string; - dataTypeAlias: string; - tempValue: string; // TODO: remove this - only used for testing -} - -export const data: Array = [ - { - id: '1', - key: '74e4008a-ea4f-4793-b924-15e02fd380d3', - name: 'Document 1', - alias: 'document1', - icon: 'document', - properties: [ - { - alias: 'myHeadline', - label: 'Textarea label', - description: 'this is a textarea property', - dataTypeAlias: 'myTextStringEditor', - tempValue: 'hello world 1' - }, - { - alias: 'myDescription', - label: 'Text string label', - description: 'This is the a text string property', - dataTypeAlias: 'myTextAreaEditor', - tempValue: 'Tex areaaaa 1' - }, - ], - data: [ - { - alias: 'myHeadline', - value: 'hello world', - }, - { - alias: 'myDescription', - value: 'Teeeeexxxt areaaaaaa', - }, - ], - /* - layout: [ - { - type: 'group', - children: [ - { - type: 'property', - alias: 'myHeadline' - }, - { - type: 'property', - alias: 'myDescription' - } - ] - } - ], - */ - }, - { - id: '2', - key: '74e4008a-ea4f-4793-b924-15e02fd380d3', - name: 'Document 2', - alias: 'document2', - icon: 'favorite', - properties: [ - { - alias: 'myHeadline', - label: 'Textarea label', - description: 'this is a textarea property', - dataTypeAlias: 'myTextStringEditor', - tempValue: 'hello world 2' - }, - { - alias: 'myDescription', - label: 'Text string label', - description: 'This is the a text string property', - dataTypeAlias: 'myTextAreaEditor', - tempValue: 'Tex areaaaa 2' - }, - ], - data: [ - { - alias: 'myHeadline', - value: 'hello world', - }, - { - alias: 'myDescription', - value: 'Teeeeexxxt areaaaaaa', - }, - ], - /* - layout: [ - { - type: 'group', - children: [ - { - type: 'property', - alias: 'myHeadline' - }, - { - type: 'property', - alias: 'myDescription' - } - ] - } - ], - */ - } -]; +import { DocumentNode } from '../mocks/data/content.data'; export class UmbContentService { - private _nodes: BehaviorSubject> = new BehaviorSubject(>[]); public readonly nodes: Observable> = this._nodes.asObservable(); - constructor () { - this._nodes.next(data); - } + getById (id: number): Observable { + // fetch from server and update store + fetch(`/umbraco/backoffice/content/${id}`) + .then(res => res.json()) + .then(data => { + this._updateStore(data); + }); - getById (id: string): Observable { return this.nodes.pipe(map(((nodes: Array) => nodes.find((node: DocumentNode) => node.id === id) || null))); } + _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/mocks/data/content.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts new file mode 100644 index 0000000000..34f2b84e0b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts @@ -0,0 +1,136 @@ +export interface DocumentNode { + id: number; + key: string; + name: string; + alias: string; + icon: string; // TODO: should come from the doc type? + properties: NodeProperty[]; + data: any; // TODO: define data type + layout?: any; // TODO: define layout type - make it non-optional +} + +export interface NodeProperty { + alias: string; + label: string; + description: string; + dataTypeAlias: string; + tempValue: string; // TODO: remove this - only used for testing +} + +export const data = [ + { + id: 1, + key: '74e4008a-ea4f-4793-b924-15e02fd380d3', + name: 'Document 1', + alias: 'document1', + icon: 'document', + properties: [ + { + alias: 'myHeadline', + label: 'Textarea label', + description: 'this is a textarea property', + dataTypeAlias: 'myTextStringEditor', + tempValue: 'hello world 1' + }, + { + alias: 'myDescription', + label: 'Text string label', + description: 'This is the a text string property', + dataTypeAlias: 'myTextAreaEditor', + tempValue: 'Tex areaaaa 1' + }, + ], + data: [ + { + alias: 'myHeadline', + value: 'hello world', + }, + { + alias: 'myDescription', + value: 'Teeeeexxxt areaaaaaa', + }, + ], + /* + layout: [ + { + type: 'group', + children: [ + { + type: 'property', + alias: 'myHeadline' + }, + { + type: 'property', + alias: 'myDescription' + } + ] + } + ], + */ + }, + { + id: 2, + key: '74e4008a-ea4f-4793-b924-15e02fd380d3', + name: 'Document 2', + alias: 'document2', + icon: 'favorite', + properties: [ + { + alias: 'myHeadline', + label: 'Textarea label', + description: 'this is a textarea property', + dataTypeAlias: 'myTextStringEditor', + tempValue: 'hello world 2' + }, + { + alias: 'myDescription', + label: 'Text string label', + description: 'This is the a text string property', + dataTypeAlias: 'myTextAreaEditor', + tempValue: 'Tex areaaaa 2' + }, + ], + data: [ + { + alias: 'myHeadline', + value: 'hello world', + }, + { + alias: 'myDescription', + value: 'Teeeeexxxt areaaaaaa', + }, + ], + /* + layout: [ + { + type: 'group', + children: [ + { + type: 'property', + alias: 'myHeadline' + }, + { + type: 'property', + alias: 'myDescription' + } + ] + } + ], + */ + } +]; + +// Temp mocked database +class UmbContentData { + private _data: Array = []; + + constructor () { + this._data = data; + } + + getById (id: number) { + return this._data.find(item => item.id === id); + } +} + +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/handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers.ts index 2a98e94547..c973a63407 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 { 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,