From c73074dc7c40dd68fa180f0a8fdc90accc2d6416 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 29 Aug 2022 16:31:45 +0200 Subject: [PATCH] add tree and media trees --- .../editors/content/editor-content.element.ts | 4 +- .../editors/media/editor-media.element.ts | 4 +- .../shared/node/editor-node.element.ts | 4 +- .../content/content-section-tree.element.ts | 68 ------------------- .../content/content-section.element.ts | 52 ++------------ .../media/media-section-tree.element.ts | 68 ------------------- .../sections/media/media-section.element.ts | 52 ++------------ .../tree/content/tree-content.context.ts | 39 +++++++++++ .../tree/content/tree-content.element.ts | 44 ++++++++++++ .../tree/media/tree-media.context.ts | 39 +++++++++++ .../tree/media/tree-media.element.ts | 44 ++++++++++++ .../src/core/stores/node.store.ts | 8 ++- .../src/mocks/data/entity.data.ts | 45 ++++++++++++ .../src/mocks/domains/content.handlers.ts | 9 ++- .../src/mocks/domains/tree.handlers.ts | 9 +++ .../src/temp-internal-manifests.ts | 38 +++++++++++ 16 files changed, 281 insertions(+), 246 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section-tree.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section-tree.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.element.ts index e9f52283fc..35415277d8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/content/editor-content.element.ts @@ -18,10 +18,10 @@ export class UmbEditorContentElement extends LitElement { ]; @property() - id!: string; + entityKey!: string; render() { - return html``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.element.ts index 6c20a43fdf..399054edc7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/media/editor-media.element.ts @@ -18,14 +18,14 @@ export class UmbEditorMediaElement extends LitElement { ]; @property() - id!: string; + entityKey!: string; constructor() { super(); } render() { - return html``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/node/editor-node.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/node/editor-node.element.ts index 0a315d3c23..b29c7057ba 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/node/editor-node.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/shared/node/editor-node.element.ts @@ -51,7 +51,7 @@ export class UmbEditorNodeElement extends UmbContextProviderMixin(UmbContextCons ]; @property() - id!: string; + entityKey!: string; @property() alias!: string; @@ -105,7 +105,7 @@ export class UmbEditorNodeElement extends UmbContextProviderMixin(UmbContextCons private _useNode() { this._nodeStoreSubscription?.unsubscribe(); - this._nodeStoreSubscription = this._nodeStore?.getById(parseInt(this.id)).subscribe((node) => { + this._nodeStoreSubscription = this._nodeStore?.getByKey(this.entityKey).subscribe((node) => { if (!node) return; // TODO: Handle nicely if there is no node. this._nodeContextSubscription?.unsubscribe(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section-tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section-tree.element.ts deleted file mode 100644 index bb26fb6340..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section-tree.element.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { html, LitElement } from 'lit'; -import { customElement, state, property } from 'lit/decorators.js'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { UmbContextConsumerMixin } from '../../../core/context'; -import { UmbNodeStore } from '../../../core/stores/node.store'; -import { map, Subscription } from 'rxjs'; - -@customElement('umb-content-section-tree') -class UmbContentSectionTree extends UmbContextConsumerMixin(LitElement) { - static styles = [UUITextStyles]; - - @property() - public currentNodeId?: string; - - // simplified tree data for testing - @state() - _tree: Array = []; - - @state() - _section?: string; - - private _nodeStore?: UmbNodeStore; - private _nodesSubscription?: Subscription; - - constructor() { - super(); - - // TODO: temp solution until we know where to get tree data from - this.consumeContext('umbNodeStore', (store) => { - this._nodeStore = store; - - this._nodesSubscription = this._nodeStore - ?.getAll() - .pipe(map((nodes) => nodes.filter((node) => node.type === 'document'))) - .subscribe((mediaNodes) => { - this._tree = mediaNodes; - }); - }); - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - this._nodesSubscription?.unsubscribe(); - } - - render() { - return html` - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-content-section-tree': UmbContentSectionTree; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section.element.ts index cad3b5feb2..864514e751 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/content/content-section.element.ts @@ -1,57 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { IRoute, IRoutingInfo } from 'router-slot'; - -import './content-section-tree.element'; +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; @customElement('umb-content-section') export class UmbContentSection extends LitElement { - static styles = [ - UUITextStyles, - css` - :host, - #router-slot { - display: flex; - width: 100%; - height: 100%; - } - `, - ]; - - @state() - private _routes: Array = [ - { - path: 'dashboard', - component: () => import('../shared/section-dashboards.element'), - setup: () => { - this._currentNodeId = undefined; - }, - }, - { - path: 'node/:nodeId', - component: () => import('../../editors/content/editor-content.element'), - setup: (component: HTMLElement, info: IRoutingInfo) => { - this._currentNodeId = info.match.params.nodeId; - component.id = this._currentNodeId; - }, - }, - { - path: '**', - redirectTo: 'dashboard', - }, - ]; - - @state() - private _currentNodeId?: string; + static styles = [UUITextStyles]; render() { - return html` - - - - - `; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section-tree.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section-tree.element.ts deleted file mode 100644 index 4d82a86298..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section-tree.element.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { html, LitElement } from 'lit'; -import { customElement, state, property } from 'lit/decorators.js'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { UmbContextConsumerMixin } from '../../../core/context'; -import { UmbNodeStore } from '../../../core/stores/node.store'; -import { map, Subscription } from 'rxjs'; - -@customElement('umb-media-section-tree') -class UmbMediaSectionTree extends UmbContextConsumerMixin(LitElement) { - static styles = [UUITextStyles]; - - @property() - public currentNodeId?: string; - - // simplified tree data for testing - @state() - _tree: Array = []; - - @state() - _section?: string; - - private _nodeStore?: UmbNodeStore; - private _nodesSubscription?: Subscription; - - constructor() { - super(); - - // TODO: temp solution until we know where to get tree data from - this.consumeContext('umbNodeStore', (store) => { - this._nodeStore = store; - - this._nodesSubscription = this._nodeStore - ?.getAll() - .pipe(map((nodes) => nodes.filter((node) => node.type === 'media'))) - .subscribe((mediaNodes) => { - this._tree = mediaNodes; - }); - }); - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - this._nodesSubscription?.unsubscribe(); - } - - render() { - return html` - - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-media-section-tree': UmbMediaSectionTree; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section.element.ts index cd68dd51fc..cfd5a3f98a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/sections/media/media-section.element.ts @@ -1,57 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { IRoute, IRoutingInfo } from 'router-slot'; - -import './media-section-tree.element'; +import { html, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; @customElement('umb-media-section') export class UmbMediaSection extends LitElement { - static styles = [ - UUITextStyles, - css` - :host, - #router-slot { - display: flex; - width: 100%; - height: 100%; - } - `, - ]; - - @state() - private _routes: Array = [ - { - path: 'dashboard', - component: () => import('../shared/section-dashboards.element'), - setup: () => { - this._currentNodeId = undefined; - }, - }, - { - path: 'node/:nodeId', - component: () => import('../../editors/media/editor-media.element'), - setup: (component: HTMLElement, info: IRoutingInfo) => { - this._currentNodeId = info.match.params.nodeId; - component.id = this._currentNodeId; - }, - }, - { - path: '**', - redirectTo: 'dashboard', - }, - ]; - - @state() - private _currentNodeId?: string; + static styles = [UUITextStyles]; render() { - return html` - - - - - `; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.context.ts new file mode 100644 index 0000000000..4eeff7b481 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.context.ts @@ -0,0 +1,39 @@ +import { map } from 'rxjs'; +import { UmbEntityStore } from '../../../core/stores/entity.store'; +import { UmbTreeContext } from '../tree.context'; +import type { ManifestTree } from '../../../core/models'; + +export class UmbTreeContentContext implements UmbTreeContext { + public tree: ManifestTree; + public entityStore: UmbEntityStore; + + constructor(tree: ManifestTree, entityStore: UmbEntityStore) { + this.tree = tree; + this.entityStore = entityStore; + } + + public fetchRoot() { + const data = { + id: -1, + key: '485d49ef-a4aa-46ac-843f-4256fe167347', + name: 'Content', + hasChildren: true, + type: 'node', + icon: 'favorite', + parentKey: '', + }; + this.entityStore.update([data]); + return this.entityStore.entities.pipe(map((items) => items.filter((item) => item.key === data.key))); + } + + public fetchChildren(key: string) { + // TODO: figure out url structure + fetch(`/umbraco/backoffice/trees/node/${key}`) + .then((res) => res.json()) + .then((data) => { + this.entityStore.update(data); + }); + + return this.entityStore.entities.pipe(map((items) => items.filter((item) => item.parentKey === key))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.element.ts new file mode 100644 index 0000000000..42a54c48e9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/tree/content/tree-content.element.ts @@ -0,0 +1,44 @@ +import { css, html, LitElement } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property } from 'lit/decorators.js'; +import '../shared/tree-navigator.element'; +import { UmbTreeContentContext } from './tree-content.context'; +import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../../../core/context'; +import { UmbEntityStore } from '../../../core/stores/entity.store'; +import type { ManifestTree } from '../../../core/models'; + +@customElement('umb-tree-content') +export class UmbTreeContentElement extends UmbContextProviderMixin(UmbContextConsumerMixin(LitElement)) { + static styles = [UUITextStyles, css``]; + + private _treeContext?: UmbTreeContentContext; + + @property({ attribute: false }) + public tree?: ManifestTree; + + private _entityStore?: UmbEntityStore; + + constructor() { + super(); + + this.consumeContext('umbEntityStore', (entityStore: UmbEntityStore) => { + this._entityStore = entityStore; + if (!this.tree || !this._entityStore) return; + + this._treeContext = new UmbTreeContentContext(this.tree, this._entityStore); + this.provideContext('umbTreeContext', this._treeContext); + }); + } + + render() { + return html``; + } +} + +export default UmbTreeContentElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-tree-content': UmbTreeContentElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.context.ts new file mode 100644 index 0000000000..b2b0e24129 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.context.ts @@ -0,0 +1,39 @@ +import { map } from 'rxjs'; +import { UmbEntityStore } from '../../../core/stores/entity.store'; +import { UmbTreeContext } from '../tree.context'; +import type { ManifestTree } from '../../../core/models'; + +export class UmbTreeMediaContext implements UmbTreeContext { + public tree: ManifestTree; + public entityStore: UmbEntityStore; + + constructor(tree: ManifestTree, entityStore: UmbEntityStore) { + this.tree = tree; + this.entityStore = entityStore; + } + + public fetchRoot() { + const data = { + id: -1, + key: '05a8b8bc-bd90-47cc-a897-e67c8fa682ee', + name: 'Media', + hasChildren: true, + type: 'media', + icon: 'favorite', + parentKey: '', + }; + this.entityStore.update([data]); + return this.entityStore.entities.pipe(map((items) => items.filter((item) => item.key === data.key))); + } + + public fetchChildren(key: string) { + // TODO: figure out url structure + fetch(`/umbraco/backoffice/trees/node/${key}`) + .then((res) => res.json()) + .then((data) => { + this.entityStore.update(data); + }); + + return this.entityStore.entities.pipe(map((items) => items.filter((item) => item.parentKey === key))); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.element.ts new file mode 100644 index 0000000000..83c2423bef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/tree/media/tree-media.element.ts @@ -0,0 +1,44 @@ +import { css, html, LitElement } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property } from 'lit/decorators.js'; +import '../shared/tree-navigator.element'; +import { UmbTreeMediaContext } from './tree-media.context'; +import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../../../core/context'; +import { UmbEntityStore } from '../../../core/stores/entity.store'; +import type { ManifestTree } from '../../../core/models'; + +@customElement('umb-tree-data-types') +export class UmbTreeMediaElement extends UmbContextProviderMixin(UmbContextConsumerMixin(LitElement)) { + static styles = [UUITextStyles, css``]; + + private _treeContext?: UmbTreeMediaContext; + + @property({ attribute: false }) + public tree?: ManifestTree; + + private _entityStore?: UmbEntityStore; + + constructor() { + super(); + + this.consumeContext('umbEntityStore', (entityStore: UmbEntityStore) => { + this._entityStore = entityStore; + if (!this.tree || !this._entityStore) return; + + this._treeContext = new UmbTreeMediaContext(this.tree, this._entityStore); + this.provideContext('umbTreeContext', this._treeContext); + }); + } + + render() { + return html``; + } +} + +export default UmbTreeMediaElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-tree-media': UmbTreeMediaElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts index 6a0d3780ef..f025afde45 100644 --- a/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts +++ b/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts @@ -5,15 +5,17 @@ export class UmbNodeStore { private _nodes: BehaviorSubject> = new BehaviorSubject(>[]); public readonly nodes: Observable> = this._nodes.asObservable(); - getById(id: number): Observable { + getByKey(key: string): Observable { // fetch from server and update store - fetch(`/umbraco/backoffice/content/${id}`) + fetch(`/umbraco/backoffice/content/${key}`) .then((res) => res.json()) .then((data) => { this._updateStore(data); }); - return this.nodes.pipe(map((nodes: Array) => nodes.find((node: NodeEntity) => node.id === id) || null)); + return this.nodes.pipe( + map((nodes: Array) => nodes.find((node: NodeEntity) => node.key === key) || null) + ); } // TODO: temp solution until we know where to get tree data from diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts index d85333e189..7f59fd7d74 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts @@ -107,6 +107,51 @@ export const data: Array = [ hasChildren: false, parentKey: '055a17d0-525a-4d06-9f75-92dc174ab0bd', }, + { + id: 2001, + key: 'f2f81a40-c989-4b6b-84e2-057cecd3adc1', + name: 'Media 1', + type: 'media', + icon: 'picture', + hasChildren: false, + parentKey: '05a8b8bc-bd90-47cc-a897-e67c8fa682ee', + }, + { + id: 2002, + key: '69431027-8867-45bf-a93b-72bbdabfb177', + type: 'media', + name: 'Media 2', + icon: 'picture', + hasChildren: false, + parentKey: '05a8b8bc-bd90-47cc-a897-e67c8fa682ee', + }, + { + id: 1, + key: '74e4008a-ea4f-4793-b924-15e02fd380d1', + name: 'Document 1', + type: 'document', + icon: 'document', + hasChildren: false, + parentKey: '485d49ef-a4aa-46ac-843f-4256fe167347', + }, + { + id: 2, + key: '74e4008a-ea4f-4793-b924-15e02fd380d2', + name: 'Document 2', + type: 'document', + icon: 'favorite', + hasChildren: false, + parentKey: '485d49ef-a4aa-46ac-843f-4256fe167347', + }, + { + id: 3, + key: 'cdd30288-2d1c-41b4-89a9-61647b4a10d5', + name: 'Document 3', + type: 'document', + icon: 'document', + hasChildren: false, + parentKey: '485d49ef-a4aa-46ac-843f-4256fe167347', + }, ]; // Temp mocked database 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 index 6696c6069c..834502836a 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts @@ -4,13 +4,12 @@ import { NodeEntity, umbNodeData } from '../data/node.data'; // TODO: add schema export const handlers = [ - rest.get('/umbraco/backoffice/content/:id', (req, res, ctx) => { + rest.get('/umbraco/backoffice/content/:key', (req, res, ctx) => { console.warn('Please move to schema'); - const id = req.params.id as string; - if (!id) return; + const key = req.params.key as string; + if (!key) return; - const int = parseInt(id); - const document = umbNodeData.getById(int); + const document = umbNodeData.getByKey(key); return res(ctx.status(200), ctx.json([document])); }), diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/tree.handlers.ts index b4125c01eb..8dbe8da6cb 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/domains/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/tree.handlers.ts @@ -38,4 +38,13 @@ export const handlers = [ const entities = umbEntityData.getChildren(key); return res(ctx.status(200), ctx.json(entities)); }), + + rest.get('/umbraco/backoffice/trees/node/:key', (req, res, ctx) => { + console.warn('Please move to schema'); + const key = req.params.key as string; + if (!key) return; + + const entities = umbEntityData.getChildren(key); + return res(ctx.status(200), ctx.json(entities)); + }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts index b077ab63d7..91d21af562 100644 --- a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts @@ -319,6 +319,32 @@ export const internalManifests: Array Promise import('./backoffice/tree/media/tree-media.element'), + meta: { + editor: 'Umb.Editor.Media', + pathname: 'media', + label: 'Media', + weight: 100, + sections: ['Umb.Section.Media'], + }, + }, + { + type: 'tree', + alias: 'Umb.Tree.Content', + name: 'Content Tree', + loader: () => import('./backoffice/tree/content/tree-content.element'), + meta: { + editor: 'Umb.Editor.Content', + pathname: 'content', + label: 'Content', + weight: 100, + sections: ['Umb.Section.Content'], + }, + }, { type: 'editor', alias: 'Umb.Editor.Member', @@ -349,6 +375,18 @@ export const internalManifests: Array Promise import('./backoffice/editors/extensions/editor-extensions.element'), }, + { + type: 'editor', + alias: 'Umb.Editor.Media', + name: 'Media Editor', + loader: () => import('./backoffice/editors/media/editor-media.element'), + }, + { + type: 'editor', + alias: 'Umb.Editor.Content', + name: 'Content Editor', + loader: () => import('./backoffice/editors/content/editor-content.element'), + }, { type: 'entityAction', alias: 'Umb.EntityAction.Create',