add tree and media trees

This commit is contained in:
Mads Rasmussen
2022-08-29 16:31:45 +02:00
parent 2402cf8279
commit c73074dc7c
16 changed files with 281 additions and 246 deletions

View File

@@ -18,10 +18,10 @@ export class UmbEditorContentElement extends LitElement {
];
@property()
id!: string;
entityKey!: string;
render() {
return html`<umb-editor-node id=${this.id} alias="Umb.Editor.Content"></umb-editor-node>`;
return html`<umb-editor-node .entityKey=${this.entityKey} alias="Umb.Editor.Content"></umb-editor-node>`;
}
}

View File

@@ -18,14 +18,14 @@ export class UmbEditorMediaElement extends LitElement {
];
@property()
id!: string;
entityKey!: string;
constructor() {
super();
}
render() {
return html`<umb-editor-node id=${this.id} alias="Umb.Editor.Media"></umb-editor-node>`;
return html`<umb-editor-node .entityKey=${this.entityKey} alias="Umb.Editor.Media"></umb-editor-node>`;
}
}

View File

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

View File

@@ -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<any> = [];
@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`
<div class="nav-list">
${this._tree.map(
(item) => html`
<uui-menu-item
?active="${parseInt(this.currentNodeId || '-1') === item.id}"
label="${item.name}"
href="/section/content/node/${item.id}">
<uui-icon slot="icon" name="${item.icon}"></uui-icon>
</uui-menu-item>
`
)}
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-content-section-tree': UmbContentSectionTree;
}
}

View File

@@ -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<IRoute> = [
{
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`
<umb-section-sidebar>
<umb-content-section-tree .currentNodeId="${this._currentNodeId}"></umb-content-section-tree>
</umb-section-sidebar>
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
`;
return html`<umb-section></umb-section>`;
}
}

View File

@@ -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<any> = [];
@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`
<div class="nav-list">
${this._tree.map(
(item) => html`
<uui-menu-item
?active="${parseInt(this.currentNodeId || '-1') === item.id}"
label="${item.name}"
href="/section/media/node/${item.id}">
<uui-icon slot="icon" name="${item.icon}"></uui-icon>
</uui-menu-item>
`
)}
</div>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-media-section-tree': UmbMediaSectionTree;
}
}

View File

@@ -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<IRoute> = [
{
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`
<umb-section-sidebar>
<umb-media-section-tree .currentNodeId="${this._currentNodeId}"></umb-media-section-tree>
</umb-section-sidebar>
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
`;
return html`<umb-section></umb-section>`;
}
}

View File

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

View File

@@ -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`<umb-tree-navigator></umb-tree-navigator>`;
}
}
export default UmbTreeContentElement;
declare global {
interface HTMLElementTagNameMap {
'umb-tree-content': UmbTreeContentElement;
}
}

View File

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

View File

@@ -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`<umb-tree-navigator></umb-tree-navigator>`;
}
}
export default UmbTreeMediaElement;
declare global {
interface HTMLElementTagNameMap {
'umb-tree-media': UmbTreeMediaElement;
}
}

View File

@@ -5,15 +5,17 @@ export class UmbNodeStore {
private _nodes: BehaviorSubject<Array<NodeEntity>> = new BehaviorSubject(<Array<NodeEntity>>[]);
public readonly nodes: Observable<Array<NodeEntity>> = this._nodes.asObservable();
getById(id: number): Observable<NodeEntity | null> {
getByKey(key: string): Observable<NodeEntity | null> {
// 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<NodeEntity>) => nodes.find((node: NodeEntity) => node.id === id) || null));
return this.nodes.pipe(
map((nodes: Array<NodeEntity>) => nodes.find((node: NodeEntity) => node.key === key) || null)
);
}
// TODO: temp solution until we know where to get tree data from

View File

@@ -107,6 +107,51 @@ export const data: Array<Entity> = [
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

View File

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

View File

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

View File

@@ -319,6 +319,32 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
sections: ['Umb.Section.Settings'],
},
},
{
type: 'tree',
alias: 'Umb.Tree.Media',
name: 'Media Tree',
loader: () => 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<ManifestTypes & { loader: () => Promise<ob
name: 'Extensions Editor',
loader: () => 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',