add edit view to data type editor

This commit is contained in:
Mads Rasmussen
2022-07-05 10:51:25 +02:00
parent 6fd806c64a
commit a218a6a762
5 changed files with 224 additions and 14 deletions

View File

@@ -0,0 +1,24 @@
import { css, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property } from 'lit/decorators.js';
import { DataTypeEntity } from '../../mocks/data/content.data';
@customElement('umb-editor-view-data-type-edit')
export class UmbEditorViewDataTypeEditElement extends LitElement {
static styles = [UUITextStyles, css``];
@property({ type: Object })
dataType?: DataTypeEntity;
render() {
return html`<div>EDIT DATA TYPE: ${this.dataType?.id}</div> `;
}
}
export default UmbEditorViewDataTypeEditElement;
declare global {
interface HTMLElementTagNameMap {
'umb-editor-view-data-type-edit': UmbEditorViewDataTypeEditElement;
}
}

View File

@@ -1,11 +1,158 @@
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, LitElement } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { IRoute, IRoutingInfo, RouterSlot } from 'router-slot';
import { map, Subscription } from 'rxjs';
import { UmbContextConsumerMixin } from '../../core/context';
import { UmbExtensionManifest, UmbExtensionManifestEditorView, UmbExtensionRegistry } from '../../core/extension';
import { UmbDataTypeStore } from '../../core/stores/data-type.store';
import { DataTypeEntity } from '../../mocks/data/content.data';
// Lazy load
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
import '../editor-views/editor-view-data-type-edit.element';
@customElement('umb-editor-data-type')
export class UmbEditorDataTypeElement extends LitElement {
export class UmbEditorDataTypeElement extends UmbContextConsumerMixin(LitElement) {
static styles = [
UUITextStyles,
css`
:host {
display: block;
width: 100%;
height: 100%;
}
uui-input {
width: 100%;
margin-left: 16px;
}
uui-tab-group {
--uui-tab-divider: var(--uui-color-border);
border-left: 1px solid var(--uui-color-border);
flex-wrap: nowrap;
height: 60px;
}
uui-tab {
font-size: 0.8rem;
}
`,
];
@property()
id!: string;
@state()
_dataType!: DataTypeEntity;
@state()
private _editorViews: Array<UmbExtensionManifestEditorView> = [];
@state()
private _currentView = '';
@state()
private _routes: Array<IRoute> = [];
private _dataTypeStore?: UmbDataTypeStore;
private _dataTypeSubscription?: Subscription;
private _extensionRegistry?: UmbExtensionRegistry;
private _editorViewsSubscription?: Subscription;
private _routerFolder = '';
constructor() {
super();
this.consumeContext('umbDataTypeStore', (store: UmbDataTypeStore) => {
this._dataTypeStore = store;
this._useDataType();
});
this.consumeContext('umbExtensionRegistry', (extensionRegistry: UmbExtensionRegistry) => {
this._extensionRegistry = extensionRegistry;
this._useEditorViews();
});
}
connectedCallback(): void {
super.connectedCallback();
/* TODO: find a way to construct absolute urls */
this._routerFolder = window.location.pathname.split('/view')[0];
}
private _useDataType() {
this._dataTypeSubscription?.unsubscribe();
this._dataTypeSubscription = this._dataTypeStore?.getById(parseInt(this.id)).subscribe((dataType) => {
if (!dataType) return; // TODO: Handle nicely if there is no node.
this._dataType = dataType;
// TODO: merge observables
this._createRoutes();
});
}
// TODO: simplify setting up editors with views. This code has to be duplicated in each editor.
private _useEditorViews() {
this._editorViewsSubscription?.unsubscribe();
// TODO: how do we know which editor to show the views for?
this._editorViewsSubscription = this._extensionRegistry
?.extensionsOfType('editorView')
.pipe(
map((extensions) =>
extensions
.filter((extension) => extension.meta.editors.includes('Umb.Editor.DataType'))
.sort((a, b) => b.meta.weight - a.meta.weight)
)
)
.subscribe((editorViews) => {
this._editorViews = editorViews;
// TODO: merge observables
this._createRoutes();
});
}
private async _createRoutes() {
if (this._dataType && this._editorViews.length > 0) {
this._routes = [];
this._routes = this._editorViews.map((view) => {
return {
path: `view/${view.meta.pathname}`,
component: () => document.createElement(view.elementName),
setup: (element: HTMLElement, info: IRoutingInfo) => {
// TODO: make interface for EditorViews
const editorView = element as any;
// TODO: how do we pass data to views? Maybe we should use a context?
editorView.dataType = this._dataType;
this._currentView = info.match.route.path;
},
};
});
this._routes.push({
path: '**',
redirectTo: `view/${this._editorViews?.[0].meta.pathname}`,
});
this.requestUpdate();
await this.updateComplete;
this._forceRouteRender();
}
}
// TODO: Fgure out why this has been necessary for this case. Come up with another case
private _forceRouteRender() {
const routerSlotEl = this.shadowRoot?.querySelector('router-slot') as RouterSlot;
if (routerSlotEl) {
routerSlotEl.render();
}
}
private _onSave() {
console.log('SAVE DATA TYPE');
}
@@ -13,9 +160,23 @@ export class UmbEditorDataTypeElement extends LitElement {
render() {
return html`
<umb-editor-layout>
<uui-input slot="name" value="name"></uui-input>
<uui-input slot="name" .value="${this._dataType.name}"></uui-input>
<div>Some Content Here: ${this.id}</div>
<uui-tab-group slot="apps">
${this._editorViews.map(
(view: UmbExtensionManifestEditorView) => html`
<uui-tab
.label="${view.name}"
href="${this._routerFolder}/view/${view.meta.pathname}"
?active="${this._currentView.includes(view.meta.pathname)}">
<uui-icon slot="icon" name="${view.meta.icon}"></uui-icon>
${view.name}
</uui-tab>
`
)}
</uui-tab-group>
<router-slot .routes="${this._routes}"></router-slot>
<div slot="actions">
<uui-button @click=${this._onSave} look="primary" color="positive" label="Save"></uui-button>

View File

@@ -133,16 +133,17 @@ export class UmbEditorNodeElement extends UmbContextConsumerMixin(LitElement) {
this._editorViewsSubscription?.unsubscribe();
// TODO: how do we know which editor to show the views for?
this._editorViewsSubscription = this._extensionRegistry?.extensions
this._editorViewsSubscription = this._extensionRegistry
?.extensionsOfType('editorView')
.pipe(
map((extensions: Array<UmbExtensionManifest>) =>
map((extensions) =>
extensions
.filter((extension) => extension.type === 'editorView')
.sort((a: any, b: any) => b.meta.weight - a.meta.weight)
.filter((extension) => extension.meta.editors.includes('Umb.Editor.Node'))
.sort((a, b) => b.meta.weight - a.meta.weight)
)
)
.subscribe((dashboards: Array<UmbExtensionManifest>) => {
this._editorViews = dashboards as Array<UmbExtensionManifestEditorView>;
.subscribe((editorViews) => {
this._editorViews = editorViews;
// TODO: merge observables
this._createRoutes();
});
@@ -165,6 +166,7 @@ export class UmbEditorNodeElement extends UmbContextConsumerMixin(LitElement) {
this._onSave();
}
// TODO: simplify setting up editors with views. This code has to be duplicated in each editor.
private async _createRoutes() {
if (this._node && this._editorViews.length > 0) {
this._routes = [];

View File

@@ -40,12 +40,12 @@ export type UmbExtensionManifestPropertyEditor = {
// Property Actions
export type UmbExtensionManifestPropertyAction = {
type: 'propertyAction';
meta: UmbManifestPropertyActionMeta;
type: 'propertyAction';
meta: UmbManifestPropertyActionMeta;
} & UmbExtensionManifestBase;
export type UmbManifestPropertyActionMeta = {
propertyEditors: Array<string>;
propertyEditors: Array<string>;
};
// Dashboard:
@@ -61,6 +61,7 @@ export type UmbExtensionManifestDashboard = {
// Editor View:
export type UmbManifestEditorViewMeta = {
editors: Array<string>; // TODO: how to we want to filter views?
pathname: string; // TODO: how to we want to support pretty urls?
icon: string;
weight: number;
@@ -114,6 +115,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: 'dashboard'): Observable<Array<UmbExtensionManifestDashboard>>;
extensionsOfType(type: 'editorView'): Observable<Array<UmbExtensionManifestEditorView>>;
extensionsOfType(type: 'propertyEditor'): Observable<Array<UmbExtensionManifestPropertyEditor>>;
extensionsOfType(type: 'propertyAction'): Observable<Array<UmbExtensionManifestPropertyAction>>;
extensionsOfType(type: UmbExtensionManifestCoreTypes): Observable<Array<UmbExtensionManifestCore>>;

View File

@@ -95,6 +95,9 @@ export const internalManifests: Array<UmbExtensionManifestCore> = [
elementName: 'umb-editor-view-node-edit',
js: () => import('./backoffice/editor-views/editor-view-node-edit.element'),
meta: {
// TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin
// this is a temp solution
editors: ['Umb.Editor.Node'],
pathname: 'content',
weight: 100,
icon: 'document',
@@ -107,11 +110,29 @@ export const internalManifests: Array<UmbExtensionManifestCore> = [
elementName: 'umb-editor-view-node-info',
js: () => import('./backoffice/editor-views/editor-view-node-info.element'),
meta: {
// TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin
// this is a temp solution
editors: ['Umb.Editor.Node'],
pathname: 'info',
weight: 90,
icon: 'info',
},
},
{
type: 'editorView',
alias: 'Umb.EditorView.DataTypeEdit',
name: 'Edit',
elementName: 'umb-editor-view-data-type-edit',
js: () => import('./backoffice/editor-views/editor-view-data-type-edit.element'),
meta: {
// TODO: how do we want to filter where editor views are shown? https://our.umbraco.com/documentation/extending/Content-Apps/#setting-up-the-plugin
// this is a temp solution
editors: ['Umb.Editor.DataType'],
pathname: 'edit',
weight: 90,
icon: 'edit',
},
},
{
type: 'propertyAction',
alias: 'Umb.PropertyAction.Copy',