stores + RxJS

This commit is contained in:
Niels Lyngsø
2022-05-31 14:49:28 +02:00
parent 3fea6baf70
commit 88cc5f27fb
9 changed files with 151 additions and 51 deletions

View File

@@ -25,6 +25,8 @@ import {
umbRouterBeforeEnterEventType,
} from './core/router';
import { UmbSectionContext } from './section.context';
import { UmbNodeStore } from './core/stores/node.store';
import { UmbDataTypeStore } from './core/stores/data-type.store';
// TODO: lazy load these
const routes: Array<UmbRoute> = [
@@ -84,6 +86,10 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) {
this.provideContext('umbExtensionRegistry', window.Umbraco.extensionRegistry);
this.provideContext('umbSectionContext', new UmbSectionContext(extensionRegistry));
// TODO: consider providing somethings for install/login and some only for 'backoffice'.
this.provideContext('umbNodeStore', new UmbNodeStore());
this.provideContext('umbDataTypeStore', new UmbDataTypeStore());
}
private _onBeforeEnter = (event: Event) => {

View File

@@ -2,7 +2,7 @@ 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 { UmbNodesStore } from '../core/stores/nodes.store';
import { UmbNodeStore } from '../core/stores/node.store';
import { Subscription } from 'rxjs';
import { DocumentNode } from '../mocks/data/content.data';
@@ -51,13 +51,13 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) {
@state()
_node?: DocumentNode;
private _contentService?: UmbNodesStore;
private _contentService?: UmbNodeStore;
private _nodeSubscription?: Subscription;
constructor () {
super();
this.consumeContext('umbContentService', (contentService: UmbNodesStore) => {
this.consumeContext('umbNodeStore', (contentService: UmbNodeStore) => {
this._contentService = contentService;
this._useNode();
});

View File

@@ -3,7 +3,7 @@ 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 { UmbNodesStore } from '../core/stores/nodes.store';
import { UmbNodeStore as UmbNodeStore } from '../core/stores/node.store';
import { Subscription } from 'rxjs';
import './content-tree.element';
@@ -30,8 +30,6 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume
constructor () {
super();
this.provideContext('umbContentService', new UmbNodesStore());
this.consumeContext('umbRouter', (_instance: UmbRouter) => {
this._router = _instance;
this._useLocation();

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, map, Observable } from 'rxjs';
// TODO: how do we want to type extensions?
export type UmbExtensionType = 'startUp' | 'section' | 'propertyEditorUI' | 'dashboard';
@@ -48,7 +48,9 @@ export interface UmbManifestDashboardMeta {
weight: number;
}
// TODO: consider making a UmbStore base class for...
export class UmbExtensionRegistry {
private _extensions: BehaviorSubject<Array<UmbExtensionManifest>> = new BehaviorSubject(<Array<UmbExtensionManifest>>[]);
public readonly extensions: Observable<Array<UmbExtensionManifest>> = this._extensions.asObservable();
@@ -64,5 +66,12 @@ export class UmbExtensionRegistry {
this._extensions.next([...extensionsValues, manifest]);
}
getByAlias (alias: string): Observable<UmbExtensionManifest | null> {
// TODO: make pipes prettier/simpler/reuseable
return this.extensions.pipe(map(((dataTypes: Array<UmbExtensionManifest>) => dataTypes.find((extension: UmbExtensionManifest) => extension.alias === alias) || null)));
}
// TODO: implement unregister of extension
}

View File

@@ -0,0 +1,41 @@
import { BehaviorSubject, map, Observable } from 'rxjs';
import { DataTypeEntity } from '../../mocks/data/content.data';
export class UmbDataTypeStore {
private _dataTypes: BehaviorSubject<Array<DataTypeEntity>> = new BehaviorSubject(<Array<DataTypeEntity>>[]);
public readonly dataTypes: Observable<Array<DataTypeEntity>> = this._dataTypes.asObservable();
constructor() {
this._dataTypes.next([
{
id: 1245,
key: 'dt-1',
name: 'TextString (DataType)',
propertyEditorUIAlias: 'Umb.PropertyEditorUI.Text'
},
{
id: 1244,
key: 'dt-2',
name: 'Textarea (DataType)',
propertyEditorUIAlias: 'Umb.PropertyEditorUI.Textarea'
}
])
}
getById (id: number): Observable<DataTypeEntity | null> {
// no fetch..
// TODO: make pipes prettier/simpler/reuseable
return this.dataTypes.pipe(map(((dataTypes: Array<DataTypeEntity>) => dataTypes.find((node: DataTypeEntity) => node.id === id) || null)));
}
getByKey (key: string): Observable<DataTypeEntity | null> {
// no fetch..
// TODO: make pipes prettier/simpler/reuseable
return this.dataTypes.pipe(map(((dataTypes: Array<DataTypeEntity>) => dataTypes.find((node: DataTypeEntity) => node.key === key) || null)));
}
}

View File

@@ -1,7 +1,7 @@
import { BehaviorSubject, map, Observable } from 'rxjs';
import { DocumentNode } from '../../mocks/data/content.data';
export class UmbNodesStore {
export class UmbNodeStore {
private _nodes: BehaviorSubject<Array<DocumentNode>> = new BehaviorSubject(<Array<DocumentNode>>[]);
public readonly nodes: Observable<Array<DocumentNode>> = this._nodes.asObservable();

View File

@@ -9,11 +9,21 @@ export interface DocumentNode {
layout?: any; // TODO: define layout type - make it non-optional
}
export interface DataTypeEntity {
id: number;
key: string;
name: string;
//icon: string; // TODO: should come from the doc type?
//configUI: any; // this is the prevalues...
propertyEditorUIAlias: string;
}
export interface NodeProperty {
alias: string;
label: string;
description: string;
dataTypeAlias: string;
dataTypeKey: string;
tempValue: string; // TODO: remove this - only used for testing
}
@@ -29,14 +39,14 @@ export const data = [
alias: 'myHeadline',
label: 'Textarea label',
description: 'this is a textarea property',
dataTypeAlias: 'myTextStringEditor',
dataTypeKey: 'dt-1',
tempValue: 'hello world 1'
},
{
alias: 'myDescription',
label: 'Text string label',
description: 'This is the a text string property',
dataTypeAlias: 'myTextAreaEditor',
dataTypeKey: 'dt-2',
tempValue: 'Tex areaaaa 1'
},
],
@@ -79,14 +89,14 @@ export const data = [
alias: 'myHeadline',
label: 'Textarea label',
description: 'this is a textarea property',
dataTypeAlias: 'myTextStringEditor',
dataTypeKey: 'dt-1',
tempValue: 'hello world 2'
},
{
alias: 'myDescription',
label: 'Text string label',
description: 'This is the a text string property',
dataTypeAlias: 'myTextAreaEditor',
dataTypeKey: 'dt-2',
tempValue: 'Tex areaaaa 2'
},
],

View File

@@ -1,26 +1,14 @@
import { css, html, LitElement, PropertyValueMap } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
// TODO: get from Data Type Service?
// TODO: do not have elementName, instead use extension-alias(property editor UI alias) to retrieve element Name, and ensure loaded JS resource.
const DataTypeInGlobalService = [
{
alias: 'myTextStringEditor',
/*
TODO: use this instead, look up extension API.
propertyEditorUIAlias: 'Umb.PropertyEditorUI.TextString',
*/
elementName: 'umb-property-editor-text'
},
{
alias: 'myTextAreaEditor',
elementName: 'umb-property-editor-textarea'
}
];
import { UmbContextConsumerMixin } from '../core/context';
import { UmbDataTypeStore } from '../core/stores/data-type.store';
import { mergeMap, Subscription, map } from 'rxjs';
import { DataTypeEntity } from '../mocks/data/content.data';
import { UmbExtensionRegistry } from '../core/extension';
@customElement('umb-node-property-data-type')
class UmbNodePropertyDataType extends LitElement {
class UmbNodePropertyDataType extends UmbContextConsumerMixin(LitElement) {
static styles = [
UUITextStyles,
css`
@@ -33,26 +21,14 @@ class UmbNodePropertyDataType extends LitElement {
];
@property()
private _dataTypeAlias?: string | undefined;
public get dataTypeAlias(): string | undefined {
return this._dataTypeAlias;
private _dataTypeKey?: string | undefined;
public get dataTypeKey(): string | undefined {
return this._dataTypeKey;
}
public set dataTypeAlias(alias: string | undefined) {
const oldValue = this._dataTypeAlias
this._dataTypeAlias = alias;
const found = DataTypeInGlobalService.find(x => x.alias === alias);
if(!found) {
// TODO: did not find data-type..
// TODO: Consider error if undefined, showing a error-data-type, if super duper admin we might show a good error message(as always) and a editable textarea with the value, so there is some debug option available?
return;
}
this._element = document.createElement(found.elementName);
// TODO: Set/Parse Data-Type-UI-configuration
this._element.addEventListener('property-editor-change', this._onPropertyEditorChange);
this._element.value = this.value;// Be aware its duplicated code
this.requestUpdate('element', oldValue);
public set dataTypeKey(key: string | undefined) {
const oldValue = this._dataTypeKey
this._dataTypeKey = key;
this._useDataType();
}
@@ -63,6 +39,61 @@ class UmbNodePropertyDataType extends LitElement {
@property()
value?:string;
private _extensionRegistry?: UmbExtensionRegistry;
private _dataTypeStore?: UmbDataTypeStore;
private _dataTypeSubscription?: Subscription;
constructor() {
super();
this.consumeContext('umbDataTypeStore', (_instance: UmbDataTypeStore) => {
this._dataTypeStore = _instance;
this._useDataType();
})
this.consumeContext('umbExtensionRegistry', (_instance: UmbExtensionRegistry) => {
this._extensionRegistry = _instance;
this._useDataType();
})
// TODO: solution to know when both contexts are available
}
// TODO: use subscribtion, rename to _useDataType:
private _useDataType() {
this._dataTypeSubscription?.unsubscribe();
if(this._dataTypeKey && this._extensionRegistry && this._dataTypeStore) {
//this._dataTypeSubscription = this._dataTypeStore.getByKey(this._dataTypeKey).subscribe(this._gotDataType);
this._dataTypeSubscription = this._dataTypeStore.getByKey(this._dataTypeKey)
.pipe(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
map((dataTypeEntity: DataTypeEntity) => dataTypeEntity.propertyEditorUIAlias),
mergeMap((alias: string) => this._extensionRegistry?.getByAlias(alias) as any)
)
.subscribe((dataType) => {
console.log('dataType:', dataType);
});
}
}
private _gotDataType(_data: DataTypeEntity | null) {
if(_data === null) {
// TODO: if dataTypeKey didn't exist in store, we should do some nice UI.
return;
}
const oldValue = this._element;
this._element = document.createElement(_data.elementName);
// TODO: Set/Parse Data-Type-UI-configuration
this._element.addEventListener('property-editor-change', this._onPropertyEditorChange);
this._element.value = this.value;// Be aware its duplicated code
this.requestUpdate('element', oldValue);
}
private _onPropertyEditorChange = ( e:CustomEvent) => {
if(e.currentTarget === this._element) {
this.value = this._element.value;
@@ -85,6 +116,11 @@ class UmbNodePropertyDataType extends LitElement {
}
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._dataTypeSubscription?.unsubscribe();
}
render() {
return html`${this._element}`;

View File

@@ -44,7 +44,7 @@ class UmbNodeProperty extends LitElement {
<p>${this.property.description}</p>
</div>
<div class="editor">
<umb-node-property-data-type .dataTypeAlias=${this.property.dataTypeAlias} .value=${this.value} @property-data-type-change=${this._onPropertyDataTypeChange}></umb-node-property-data-type>
<umb-node-property-data-type .dataTypeKey=${this.property.dataTypeKey} .value=${this.value} @property-data-type-change=${this._onPropertyDataTypeChange}></umb-node-property-data-type>
</div>
</div>
`;