load trees from manifest

This commit is contained in:
Jesper Møller Jensen
2022-08-23 16:32:26 +02:00
parent 393731623c
commit ab072b74d4
11 changed files with 315 additions and 12 deletions

View File

@@ -5,8 +5,11 @@ import { data as dataTypeData } from '../../../mocks/data/data-type.data';
import { data as documentTypeData } from '../../../mocks/data/document-type.data';
import { UmbContextConsumerMixin } from '../../../core/context';
import { UmbDataTypeStore } from '../../../core/stores/data-type.store';
import { Subscription } from 'rxjs';
import { map, Subscription, first } from 'rxjs';
import { UmbDocumentTypeStore } from '../../../core/stores/document-type.store';
import { createExtensionElement, UmbExtensionRegistry } from '../../../core/extension';
import '../../tree/tree.element';
import { UmbSectionContext } from '../section.context';
@customElement('umb-settings-section-tree')
class UmbSettingsSectionTree extends UmbContextConsumerMixin(LitElement) {
@@ -26,15 +29,32 @@ class UmbSettingsSectionTree extends UmbContextConsumerMixin(LitElement) {
@state()
_documentTypes: Array<any> = [];
@state()
private _trees: Array<any> = [];
@state()
private _currentSectionAlias?: string;
private _dataTypeStore?: UmbDataTypeStore;
private _documentTypeStore?: UmbDocumentTypeStore;
private _dataTypesSubscription?: Subscription;
private _documentTypesSubscription?: Subscription;
private _extensionStore?: UmbExtensionRegistry;
private _treeSubscription?: Subscription;
private _sectionContextSubscription?: Subscription;
private _sectionContext?: UmbSectionContext;
constructor() {
super();
this.consumeContext('umbSectionContext', (context: UmbSectionContext) => {
this._sectionContext = context;
this._useSectionContext();
});
// TODO: temp solution until we know where to get tree data from
this.consumeContext('umbDataTypeStore', (store) => {
this._dataTypeStore = store;
@@ -52,6 +72,11 @@ class UmbSettingsSectionTree extends UmbContextConsumerMixin(LitElement) {
this._documentTypes = documentTypes;
});
});
this.consumeContext('umbExtensionRegistry', (store) => {
this._extensionStore = store;
this._useTrees();
});
}
disconnectedCallback(): void {
@@ -61,6 +86,35 @@ class UmbSettingsSectionTree extends UmbContextConsumerMixin(LitElement) {
this._documentTypesSubscription?.unsubscribe();
}
private _useSectionContext() {
this._sectionContextSubscription?.unsubscribe();
this._sectionContextSubscription = this._sectionContext?.data.pipe(first()).subscribe((section) => {
this._currentSectionAlias = section.alias;
});
}
private _useTrees() {
//TODO: Merge streams
if (this._extensionStore && this._currentSectionAlias) {
this._treeSubscription?.unsubscribe();
this._treeSubscription = this._extensionStore
?.extensionsOfType('tree')
.pipe(
map((extensions) =>
extensions
.filter((extension) => extension.meta.sections.includes(this._currentSectionAlias as string)) // TODO: Why do whe need "as string" here??
.sort((a, b) => b.meta.weight - a.meta.weight)
)
)
.subscribe((treeExtensions) => {
this._trees = treeExtensions;
console.log('Wrosk', this._trees);
});
}
}
render() {
return html`
<a href="${'/section/settings'}">
@@ -85,6 +139,8 @@ class UmbSettingsSectionTree extends UmbContextConsumerMixin(LitElement) {
`
)}
</uui-menu-item>
${this._trees.map((tree) => html`<umb-tree .tree=${tree}></umb-tree>`)}
`;
}
}

View File

@@ -0,0 +1,20 @@
import { css, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement } from 'lit/decorators.js';
import './tree-navigator.element';
import './tree-item.element';
@customElement('umb-datatype-tree')
export class UmbDatatypeTree extends LitElement {
static styles = [UUITextStyles, css``];
render() {
return html`<umb-tree-navigator></umb-tree-navigator>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-datatype-tree': UmbDatatypeTree;
}
}

View File

@@ -0,0 +1,23 @@
import { css, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property } from 'lit/decorators.js';
import './tree-navigator.element';
import './tree-item.element';
@customElement('umb-document-type-tree')
export class UmbDocumentTypeTree extends LitElement {
static styles = [UUITextStyles, css``];
@property({ type: String })
public alias = '';
render() {
return html`<umb-tree-navigator .label=${this.alias}></umb-tree-navigator>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-document-type-tree': UmbDocumentTypeTree;
}
}

View File

@@ -10,19 +10,35 @@ export class UmbTreeNavigator extends UmbContextProviderMixin(LitElement) {
private _treeService: UmbTreeService;
@state()
id = '2';
@state()
label = '';
@state()
hasChildren = false;
@state()
loading = true;
constructor() {
super();
this._treeService = new UmbTreeService();
this.provideContext('umbTreeService', this._treeService);
}
renderItems() {
return this._treeService.getRoot().map((item) => {
return html`<umb-tree-item .id=${item.id} .hasChildren=${item.hasChildren} .label=${item.name}></umb-tree-item>`;
this._treeService.getTreeItem(this.id).then((item) => {
this.label = item.name;
this.hasChildren = item.hasChildren;
this.loading = false;
});
}
render() {
return this.renderItems();
return html`<umb-tree-item
.id=${this.id}
.label=${this.label}
?hasChildren=${this.hasChildren}
.loading=${this.loading}></umb-tree-item> `;
}
}

View File

@@ -0,0 +1,46 @@
import { css, CSSResultGroup, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { createExtensionElement, UmbExtensionManifestTree } from '../../core/extension';
import { UmbTreeNavigator } from './tree-navigator.element';
@customElement('umb-tree')
export class UmbTree extends LitElement {
static styles: CSSResultGroup = [UUITextStyles];
private _tree?: UmbExtensionManifestTree;
@property({ type: Object })
public get tree(): UmbExtensionManifestTree | undefined {
return this._tree;
}
public set tree(value: UmbExtensionManifestTree | undefined) {
this._tree = value;
this._createElement();
}
@state()
private _element?: any;
private async _createElement() {
if (!this.tree) return;
try {
this._element = (await createExtensionElement(this.tree)) as any | undefined;
if (!this._element) return;
this._element.alias = this._tree?.alias;
} catch (error) {
// TODO: loading JS failed so we should do some nice UI. (This does only happen if extension has a js prop, otherwise we concluded that no source was needed resolved the load.)
}
}
render() {
return html`${this._element}`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-tree': UmbTree;
}
}

View File

@@ -5,13 +5,18 @@ export class UmbTreeService {
return fakeApi.getTreeRoot();
}
public async getTreeItem(id: string) {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 2000));
return fakeApi.getTreeItem(id);
}
public async getChildren(id: string) {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 2000));
return fakeApi.getTreeChildren(id);
}
}
// EVERYTHING BELOW IS FAKE DATA
// EVERYTHING BELOW IS FAKE MOCK DATA AND WILL BE REMOVED
const fakeApi = {
//find nested child id of array
@@ -28,6 +33,16 @@ const fakeApi = {
});
},
getTreeItem: (id: string) => {
const item = recursive(treeData, id);
if (!item) return 'not found';
return {
...item,
hasChildren: item.children.length > 0,
};
},
getTreeRoot: () => {
return treeData.map((item) => {
return {
@@ -79,20 +94,20 @@ const treeData = [
},
{
id: '2',
name: 'Templates',
name: 'DataTypes',
children: [
{
id: '2-1',
name: 'Templates-2-1',
name: 'DataTypes-2-1',
children: [],
},
{
id: '2-2',
name: 'Templates-2-2',
name: 'DataTypes-2-2',
children: [
{
id: '2-2-1',
name: 'Templates-2-2-1',
name: 'DataTypes-2-2-1',
children: [],
},
],

View File

@@ -25,6 +25,16 @@ export type UmbExtensionManifestSection = {
meta: UmbManifestSectionMeta;
} & UmbExtensionManifestBase;
//tree
export type UmbManifestTreeMeta = {
weight: number;
sections: Array<string>;
};
export type UmbExtensionManifestTree = {
type: 'tree';
meta: UmbManifestTreeMeta;
} & UmbExtensionManifestBase;
// propertyEditor:
export type UmbManifestPropertyEditorMeta = {
icon: string;
@@ -74,6 +84,7 @@ export type UmbExtensionManifestEditorView = {
export type UmbExtensionManifestCore =
| UmbExtensionManifestSection
| UmbExtensionManifestTree
| UmbExtensionManifestDashboard
| UmbExtensionManifestPropertyEditorUI
| UmbExtensionManifestPropertyAction
@@ -115,6 +126,7 @@ export class UmbExtensionRegistry {
// Typings concept, need to put all core types to get a good array return type for the provided type...
extensionsOfType(type: 'section'): Observable<Array<UmbExtensionManifestSection>>;
extensionsOfType(type: 'tree'): Observable<Array<UmbExtensionManifestTree>>;
extensionsOfType(type: 'dashboard'): Observable<Array<UmbExtensionManifestDashboard>>;
extensionsOfType(type: 'editorView'): Observable<Array<UmbExtensionManifestEditorView>>;
extensionsOfType(type: 'propertyEditorUI'): Observable<Array<UmbExtensionManifestPropertyEditorUI>>;

View File

@@ -0,0 +1,38 @@
import { BehaviorSubject, map, Observable } from 'rxjs';
import { Entity } from '../../mocks/data/entity.data';
import { umbNodeData } from '../../mocks/data/node.data';
export class UmbEntityStore {
private _entities: BehaviorSubject<Array<Entity>> = new BehaviorSubject(<Array<Entity>>[]);
public readonly entities: Observable<Array<Entity>> = this._entities.asObservable();
getById(id: number): Observable<Entity | null> {
// fetch from server and update store
fetch(`/umbraco/backoffice/content/${id}`)
.then((res) => res.json())
.then((data) => {
this._updateStore(data);
});
return this.entities.pipe(map((nodes: Array<Entity>) => nodes.find((node: Entity) => node.id === id) || null));
}
private _updateStore(fetchedNodes: Array<any>) {
const storedNodes = this._entities.getValue();
const updated: Entity[] = [...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.push(fetchedNode);
}
});
this._entities.next([...updated]);
}
}

View File

@@ -0,0 +1,43 @@
export interface Entity {
id: number;
key: string;
name: string;
icon: string; // TODO: Should this be here?
type: string;
hasChildren: boolean; // TODO: Should this be here?
}
export const data: Array<Entity> = [
{
id: 1,
key: '74e4008a-ea4f-4793-b924-15e02fd380d1',
name: 'Document 1',
type: 'document',
icon: 'document',
hasChildren: false,
},
{
id: 2,
key: '74e4008a-ea4f-4793-b924-15e02fd380d2',
name: 'Document 2',
type: 'document',
icon: 'favorite',
hasChildren: false,
},
{
id: 3,
key: 'cdd30288-2d1c-41b4-89a9-61647b4a10d5',
name: 'Document 3',
type: 'document',
icon: 'document',
hasChildren: false,
},
{
id: 2001,
key: 'f2f81a40-c989-4b6b-84e2-057cecd3adc1',
name: 'Media 1',
type: 'media',
icon: 'picture',
hasChildren: false,
},
];

View File

@@ -0,0 +1 @@
//TODO: MAKE

View File

@@ -241,4 +241,37 @@ export const internalManifests: Array<UmbExtensionManifestCore> = [
group: 'common',
},
},
{
type: 'tree',
alias: 'Umb.Tree.Datatypes',
name: 'DataTypes',
elementName: 'umb-datatype-tree',
js: () => import('./backoffice/tree/datatypes-tree.element'),
meta: {
weight: -10,
sections: ['Umb.Section.Settings'],
},
},
{
type: 'tree',
alias: 'Umb.Tree.DocumentTypes',
name: 'DocumentTypes',
elementName: 'umb-document-type-tree',
js: () => import('./backoffice/tree/document-type-tree.element'),
meta: {
weight: -10,
sections: ['Umb.Section.Settings'],
},
},
{
type: 'tree',
alias: 'Umb.Tree.DocumentTypes',
name: 'DocumentTypes',
elementName: 'umb-document-type-tree',
js: () => import('./backoffice/tree/document-type-tree.element'),
meta: {
weight: -10,
sections: ['Umb.Section.Content'],
},
},
];