render editor based on entity type
This commit is contained in:
@@ -393,8 +393,6 @@ components:
|
||||
MetaTree:
|
||||
type: object
|
||||
properties:
|
||||
editor:
|
||||
type: string
|
||||
pathname:
|
||||
type: string
|
||||
label:
|
||||
@@ -407,7 +405,6 @@ components:
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- editor
|
||||
- pathname
|
||||
- label
|
||||
- weight
|
||||
@@ -434,6 +431,13 @@ components:
|
||||
- meta
|
||||
- name
|
||||
- alias
|
||||
MetaEditor:
|
||||
type: object
|
||||
properties:
|
||||
entityType:
|
||||
type: string
|
||||
required:
|
||||
- entityType
|
||||
IManifestEditor:
|
||||
type: object
|
||||
properties:
|
||||
@@ -441,18 +445,19 @@ components:
|
||||
type: string
|
||||
enum:
|
||||
- editor
|
||||
meta:
|
||||
$ref: '#/components/schemas/MetaEditor'
|
||||
name:
|
||||
type: string
|
||||
js:
|
||||
type: string
|
||||
elementName:
|
||||
type: string
|
||||
meta:
|
||||
type: object
|
||||
alias:
|
||||
type: string
|
||||
required:
|
||||
- type
|
||||
- meta
|
||||
- name
|
||||
- alias
|
||||
MetaEntityAction:
|
||||
|
||||
@@ -120,7 +120,6 @@ export interface components {
|
||||
alias: string;
|
||||
};
|
||||
MetaTree: {
|
||||
editor: string;
|
||||
pathname: string;
|
||||
label: string;
|
||||
/** Format: float */
|
||||
@@ -136,13 +135,16 @@ export interface components {
|
||||
elementName?: string;
|
||||
alias: string;
|
||||
};
|
||||
MetaEditor: {
|
||||
entityType: string;
|
||||
};
|
||||
IManifestEditor: {
|
||||
/** @enum {string} */
|
||||
type: "editor";
|
||||
meta: components["schemas"]["MetaEditor"];
|
||||
name: string;
|
||||
js?: string;
|
||||
elementName?: string;
|
||||
meta?: { [key: string]: unknown };
|
||||
alias: string;
|
||||
};
|
||||
MetaEntityAction: {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { UmbDataTypeStore } from '../../../core/stores/data-type.store';
|
||||
import { DataTypeEntity } from '../../../mocks/data/data-type.data';
|
||||
import { UmbDataTypeContext } from './data-type.context';
|
||||
|
||||
import '../shared/editor-entity/editor-entity.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
// Lazy load
|
||||
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
|
||||
@@ -124,7 +124,7 @@ export class UmbEditorDataTypeElement extends UmbContextProviderMixin(UmbContext
|
||||
return html`
|
||||
${this._dataType
|
||||
? html`
|
||||
<umb-editor-entity alias="Umb.Editor.DataType">
|
||||
<umb-editor-entity-layout alias="Umb.Editor.DataType">
|
||||
<uui-input id="name" slot="name" .value=${this._dataType?.name} @input="${this._handleInput}"></uui-input>
|
||||
<!-- TODO: these could be extensions points too -->
|
||||
<div slot="actions">
|
||||
@@ -135,7 +135,7 @@ export class UmbEditorDataTypeElement extends UmbContextProviderMixin(UmbContext
|
||||
label="Save"
|
||||
.state="${this._saveButtonState}"></uui-button>
|
||||
</div>
|
||||
</umb-editor-entity>
|
||||
</umb-editor-entity-layout>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { UmbDocumentTypeStore } from '../../../core/stores/document-type.store';
|
||||
import { DocumentTypeEntity } from '../../../mocks/data/document-type.data';
|
||||
import { UmbDocumentTypeContext } from './document-type.context';
|
||||
|
||||
import '../shared/editor-entity/editor-entity.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
// Lazy load
|
||||
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
|
||||
@@ -124,7 +124,7 @@ export class UmbEditorDocumentTypeElement extends UmbContextProviderMixin(UmbCon
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-entity alias="Umb.Editor.DocumentType">
|
||||
<umb-editor-entity-layout alias="Umb.Editor.DocumentType">
|
||||
<div slot="icon">Icon</div>
|
||||
|
||||
<div slot="name">
|
||||
@@ -144,7 +144,7 @@ export class UmbEditorDocumentTypeElement extends UmbContextProviderMixin(UmbCon
|
||||
label="Save"
|
||||
.state="${this._saveButtonState}"></uui-button>
|
||||
</div>
|
||||
</umb-editor-entity>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../shared/editor-entity/editor-entity.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
@@ -52,7 +52,7 @@ export class UmbEditorExtensionsElement extends UmbContextConsumerMixin(LitEleme
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-entity alias="Umb.Editor.Extensions">
|
||||
<umb-editor-entity-layout alias="Umb.Editor.Extensions">
|
||||
<h3 slot="name">Extensions</h3>
|
||||
<uui-box headline="Extensions">
|
||||
<uui-table>
|
||||
@@ -75,7 +75,7 @@ export class UmbEditorExtensionsElement extends UmbContextConsumerMixin(LitEleme
|
||||
)}
|
||||
</uui-table>
|
||||
</uui-box>
|
||||
</umb-editor-entity>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
import '../shared/editor-entity/editor-entity.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
@customElement('umb-editor-member-group')
|
||||
export class UmbEditorMemberGroupElement extends LitElement {
|
||||
@@ -21,7 +21,9 @@ export class UmbEditorMemberGroupElement extends LitElement {
|
||||
id!: string;
|
||||
|
||||
render() {
|
||||
return html` <umb-editor-entity alias="Umb.Editor.MemberGroup">Member Group Editor</umb-editor-entity> `;
|
||||
return html`
|
||||
<umb-editor-entity-layout alias="Umb.Editor.MemberGroup">Member Group Editor</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
import '../shared/editor-entity/editor-entity.element';
|
||||
import '../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
@customElement('umb-editor-member')
|
||||
export class UmbEditorMemberElement extends LitElement {
|
||||
@@ -21,7 +21,7 @@ export class UmbEditorMemberElement extends LitElement {
|
||||
id!: string;
|
||||
|
||||
render() {
|
||||
return html` <umb-editor-entity alias="Umb.Editor.Member">Member Editor</umb-editor-entity> `;
|
||||
return html` <umb-editor-entity-layout alias="Umb.Editor.Member">Member Editor</umb-editor-entity-layout> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import '../editor-layout/editor-layout.element';
|
||||
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement, nothing } 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 { UmbExtensionRegistry } from '../../../../core/extension';
|
||||
import type { ManifestEditorView } from '../../../../core/models';
|
||||
|
||||
@customElement('umb-editor-entity-layout')
|
||||
export class UmbEditorEntityLayout extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
#name {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#actions {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
uui-tab-group {
|
||||
--uui-tab-divider: var(--uui-color-border);
|
||||
border-left: 1px solid var(--uui-color-border);
|
||||
border-right: 1px solid var(--uui-color-border);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
alias = '';
|
||||
|
||||
@property()
|
||||
name = '';
|
||||
|
||||
@state()
|
||||
private _editorViews: Array<ManifestEditorView> = [];
|
||||
|
||||
@state()
|
||||
private _currentView = '';
|
||||
|
||||
@state()
|
||||
private _routes: Array<IRoute> = [];
|
||||
|
||||
private _extensionRegistry?: UmbExtensionRegistry;
|
||||
private _editorViewsSubscription?: Subscription;
|
||||
private _routerFolder = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
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 _useEditorViews() {
|
||||
this._editorViewsSubscription?.unsubscribe();
|
||||
|
||||
this._editorViewsSubscription = this._extensionRegistry
|
||||
?.extensionsOfType('editorView')
|
||||
.pipe(
|
||||
map((extensions) =>
|
||||
extensions
|
||||
.filter((extension) => extension.meta.editors.includes(this.alias))
|
||||
.sort((a, b) => b.meta.weight - a.meta.weight)
|
||||
)
|
||||
)
|
||||
.subscribe((editorViews) => {
|
||||
this._editorViews = editorViews;
|
||||
this._createRoutes();
|
||||
});
|
||||
}
|
||||
|
||||
private async _createRoutes() {
|
||||
if (this._editorViews.length > 0) {
|
||||
this._routes = [];
|
||||
|
||||
this._routes = this._editorViews.map((view) => {
|
||||
return {
|
||||
path: `view/${view.meta.pathname}`,
|
||||
component: () => document.createElement(view.elementName || 'div'),
|
||||
setup: (element: HTMLElement, info: IRoutingInfo) => {
|
||||
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: Figure 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 _renderViews() {
|
||||
return html`
|
||||
${this._editorViews?.length > 0
|
||||
? html`
|
||||
<uui-tab-group slot="views">
|
||||
${this._editorViews.map(
|
||||
(view: ManifestEditorView) => 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>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-layout>
|
||||
<div id="header" slot="header">
|
||||
<slot id="icon" name="icon"></slot>
|
||||
<slot id="name" name="name"></slot>
|
||||
${this._renderViews()}
|
||||
</div>
|
||||
|
||||
<router-slot .routes="${this._routes}"></router-slot>
|
||||
<slot></slot>
|
||||
|
||||
<div id="footer" slot="footer">
|
||||
<slot name="footer"></slot>
|
||||
<slot id="actions" name="actions"></slot>
|
||||
</div>
|
||||
</umb-editor-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-entity-layout': UmbEditorEntityLayout;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import './editor-entity.element';
|
||||
import './editor-entity-layout.element';
|
||||
|
||||
import { Meta, Story } from '@storybook/web-components';
|
||||
import { html } from 'lit-html';
|
||||
|
||||
import type { UmbEditorEntity } from './editor-entity.element';
|
||||
import type { UmbEditorEntityLayout } from './editor-entity-layout.element';
|
||||
|
||||
export default {
|
||||
title: 'Editors/Shared/Editor Entity',
|
||||
component: 'umb-editor-entity',
|
||||
id: 'umb-editor-entity',
|
||||
title: 'Editors/Shared/Editor Entity Layout',
|
||||
component: 'umb-editor-entity-layout',
|
||||
id: 'umb-editor-entity-layout',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbEditorEntity> = () => html` <umb-editor-entity>
|
||||
export const AAAOverview: Story<UmbEditorEntityLayout> = () => html` <umb-editor-entity-layout>
|
||||
<div slot="icon"><uui-button color="" look="placeholder">Icon slot</uui-button></div>
|
||||
<div slot="name"><uui-button color="" look="placeholder">Name slot</uui-button></div>
|
||||
<div slot="footer"><uui-button color="" look="placeholder">Footer slot</uui-button></div>
|
||||
<div slot="actions"><uui-button color="" look="placeholder">Actions slot</uui-button></div>
|
||||
<uui-button color="" look="placeholder">Main slot</uui-button>
|
||||
</umb-editor-entity>`;
|
||||
</umb-editor-entity-layout>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,17 +1,13 @@
|
||||
import '../editor-layout/editor-layout.element';
|
||||
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, LitElement, nothing } 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 { UmbExtensionRegistry } from '../../../../core/extension';
|
||||
import type { ManifestEditorView } from '../../../../core/models';
|
||||
import { createExtensionElement, UmbExtensionRegistry } from '../../../../core/extension';
|
||||
import { map } from 'rxjs';
|
||||
import { ManifestEditor } from '../../../../core/models';
|
||||
|
||||
@customElement('umb-editor-entity')
|
||||
export class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) {
|
||||
export class UmbEditorEntityElement extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
@@ -20,175 +16,74 @@ export class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
#name {
|
||||
display: block;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#footer {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#actions {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
uui-tab-group {
|
||||
--uui-tab-divider: var(--uui-color-border);
|
||||
border-left: 1px solid var(--uui-color-border);
|
||||
border-right: 1px solid var(--uui-color-border);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property()
|
||||
alias = '';
|
||||
public entityKey!: string;
|
||||
|
||||
private _entityType = '';
|
||||
@property()
|
||||
name = '';
|
||||
public get entityType(): string {
|
||||
return this._entityType;
|
||||
}
|
||||
public set entityType(value: string) {
|
||||
this._entityType = value;
|
||||
this._useEditors();
|
||||
}
|
||||
|
||||
@state()
|
||||
private _editorViews: Array<ManifestEditorView> = [];
|
||||
|
||||
@state()
|
||||
private _currentView = '';
|
||||
|
||||
@state()
|
||||
private _routes: Array<IRoute> = [];
|
||||
private _element?: any;
|
||||
|
||||
private _extensionRegistry?: UmbExtensionRegistry;
|
||||
private _editorViewsSubscription?: Subscription;
|
||||
private _routerFolder = '';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbExtensionRegistry', (extensionRegistry: UmbExtensionRegistry) => {
|
||||
this._extensionRegistry = extensionRegistry;
|
||||
this._useEditorViews();
|
||||
this._useEditors();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
/* TODO: find a way to construct absolute urls */
|
||||
this._routerFolder = window.location.pathname.split('/view')[0];
|
||||
}
|
||||
private _useEditors() {
|
||||
if (!this._extensionRegistry) return;
|
||||
|
||||
private _useEditorViews() {
|
||||
this._editorViewsSubscription?.unsubscribe();
|
||||
|
||||
this._editorViewsSubscription = this._extensionRegistry
|
||||
?.extensionsOfType('editorView')
|
||||
.pipe(
|
||||
map((extensions) =>
|
||||
extensions
|
||||
.filter((extension) => extension.meta.editors.includes(this.alias))
|
||||
.sort((a, b) => b.meta.weight - a.meta.weight)
|
||||
)
|
||||
)
|
||||
.subscribe((editorViews) => {
|
||||
this._editorViews = editorViews;
|
||||
this._createRoutes();
|
||||
this._extensionRegistry
|
||||
.extensionsOfType('editor')
|
||||
.pipe(map((editors) => editors.find((editor) => editor.meta.entityType === this.entityType)))
|
||||
.subscribe((editor) => {
|
||||
this._createElement(editor);
|
||||
});
|
||||
}
|
||||
|
||||
private async _createRoutes() {
|
||||
if (this._editorViews.length > 0) {
|
||||
this._routes = [];
|
||||
private async _createElement(editor?: ManifestEditor) {
|
||||
// TODO: implement fallback editor
|
||||
const fallbackEditor = document.createElement('div');
|
||||
fallbackEditor.innerHTML = '<p>No editor found</p>';
|
||||
|
||||
this._routes = this._editorViews.map((view) => {
|
||||
return {
|
||||
path: `view/${view.meta.pathname}`,
|
||||
component: () => document.createElement(view.elementName || 'div'),
|
||||
setup: (element: HTMLElement, info: IRoutingInfo) => {
|
||||
this._currentView = info.match.route.path;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this._routes.push({
|
||||
path: '**',
|
||||
redirectTo: `view/${this._editorViews?.[0].meta.pathname}`,
|
||||
});
|
||||
|
||||
this.requestUpdate();
|
||||
await this.updateComplete;
|
||||
|
||||
this._forceRouteRender();
|
||||
if (!editor) {
|
||||
this._element = fallbackEditor;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Figure 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();
|
||||
try {
|
||||
this._element = (await createExtensionElement(editor)) as any;
|
||||
this._element.entityKey = this.entityKey;
|
||||
} catch (error) {
|
||||
this._element = fallbackEditor;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderViews() {
|
||||
return html`
|
||||
${this._editorViews?.length > 0
|
||||
? html`
|
||||
<uui-tab-group slot="views">
|
||||
${this._editorViews.map(
|
||||
(view: ManifestEditorView) => 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>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-layout>
|
||||
<div id="header" slot="header">
|
||||
<slot id="icon" name="icon"></slot>
|
||||
<slot id="name" name="name"></slot>
|
||||
${this._renderViews()}
|
||||
</div>
|
||||
|
||||
<router-slot .routes="${this._routes}"></router-slot>
|
||||
<slot></slot>
|
||||
|
||||
<div id="footer" slot="footer">
|
||||
<slot name="footer"></slot>
|
||||
<slot id="actions" name="actions"></slot>
|
||||
</div>
|
||||
</umb-editor-layout>
|
||||
`;
|
||||
return html`${this._element}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorEntityElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-entity': UmbEditorEntity;
|
||||
'umb-editor-entity': UmbEditorEntityElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { NodeEntity } from '../../../../mocks/data/node.data';
|
||||
import type { UmbNotificationService } from '../../../../core/services/notification';
|
||||
import { UmbNodeContext } from './node.context';
|
||||
|
||||
import '../../shared/editor-entity/editor-entity.element';
|
||||
import '../../shared/editor-entity-layout/editor-entity-layout.element';
|
||||
|
||||
// Lazy load
|
||||
// TODO: Make this dynamic, use load-extensions method to loop over extensions for this node.
|
||||
@@ -172,7 +172,7 @@ export class UmbEditorNodeElement extends UmbContextProviderMixin(UmbContextCons
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-editor-entity alias=${this.alias}>
|
||||
<umb-editor-entity-layout alias=${this.alias}>
|
||||
<div slot="name">
|
||||
<uui-input .value=${this._node?.name} @input="${this._handleInput}">
|
||||
<!-- Implement Variant Selector -->
|
||||
@@ -215,7 +215,7 @@ export class UmbEditorNodeElement extends UmbContextProviderMixin(UmbContextCons
|
||||
color="positive"
|
||||
label="Save and publish"></uui-button>
|
||||
</div>
|
||||
</umb-editor-entity>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { css, html, LitElement } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { Subscription, map, switchMap, EMPTY, of } from 'rxjs';
|
||||
import { UmbContextConsumerMixin } from '../../../core/context';
|
||||
import { createExtensionElement, UmbExtensionRegistry } from '../../../core/extension';
|
||||
import { UmbExtensionRegistry } from '../../../core/extension';
|
||||
import { UmbSectionContext } from '../section.context';
|
||||
import type { ManifestTree, ManifestEditor } from '../../../core/models';
|
||||
|
||||
import '../shared/section-trees.element.ts';
|
||||
import { UmbEditorElement } from '../../editors/shared/editor-entity/editor-entity.element';
|
||||
import { UmbEntityStore } from '../../../core/stores/entity.store';
|
||||
|
||||
@customElement('umb-section')
|
||||
export class UmbSectionElement extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -31,6 +33,8 @@ export class UmbSectionElement extends UmbContextConsumerMixin(LitElement) {
|
||||
private _editors?: Array<ManifestEditor>;
|
||||
private _editorsSubscription?: Subscription;
|
||||
|
||||
private _entityStore?: UmbEntityStore;
|
||||
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _extensionRegistry?: UmbExtensionRegistry;
|
||||
private _treesSubscription?: Subscription;
|
||||
@@ -40,24 +44,24 @@ export class UmbSectionElement extends UmbContextConsumerMixin(LitElement) {
|
||||
|
||||
// TODO: wait for more contexts
|
||||
this.consumeContext('umbExtensionRegistry', (extensionsRegistry: UmbExtensionRegistry) => {
|
||||
this.consumeContext('umbSectionContext', (sectionContext: UmbSectionContext) => {
|
||||
this._extensionRegistry = extensionsRegistry;
|
||||
this._sectionContext = sectionContext;
|
||||
this._useEditors();
|
||||
this._useTrees();
|
||||
});
|
||||
this._extensionRegistry = extensionsRegistry;
|
||||
this._useTrees();
|
||||
});
|
||||
}
|
||||
|
||||
private _useEditors() {
|
||||
this._editorsSubscription?.unsubscribe();
|
||||
this.consumeContext('umbSectionContext', (sectionContext: UmbSectionContext) => {
|
||||
this._sectionContext = sectionContext;
|
||||
this._useTrees();
|
||||
});
|
||||
|
||||
this._extensionRegistry?.extensionsOfType('editor').subscribe((editors) => {
|
||||
this._editors = editors;
|
||||
this.consumeContext('umbEntityStore', (entityStore: UmbEntityStore) => {
|
||||
this._entityStore = entityStore;
|
||||
this._useTrees();
|
||||
});
|
||||
}
|
||||
|
||||
private _useTrees() {
|
||||
if (!this._sectionContext || !this._extensionRegistry || !this._entityStore) return;
|
||||
|
||||
this._treesSubscription?.unsubscribe();
|
||||
|
||||
this._treesSubscription = this._sectionContext?.data
|
||||
@@ -87,20 +91,12 @@ export class UmbSectionElement extends UmbContextConsumerMixin(LitElement) {
|
||||
private _createRoutes() {
|
||||
const treeRoutes =
|
||||
this._trees?.map((tree) => {
|
||||
// TODO: make this code respond to updates in editor extensions
|
||||
const editor = this._editors?.find((editor) => editor.alias === tree.meta.editor);
|
||||
// TODO: implement fallback editor
|
||||
const fallbackEditor = document.createElement('div');
|
||||
fallbackEditor.innerHTML = '<p>No editor found</p>';
|
||||
|
||||
return {
|
||||
path: `${tree.meta.pathname}/:id`,
|
||||
component: () => (editor ? createExtensionElement(editor) : fallbackEditor),
|
||||
async setup(component: any, info: any) {
|
||||
// TODO: temp hack - we need to make sure it's the component and not a promise
|
||||
const hello = await component;
|
||||
hello.entityId = parseInt(info.match.params.id);
|
||||
hello.entityKey = info.match.params.id;
|
||||
path: `:entityType/:key`,
|
||||
component: () => import('../../editors/shared/editor-entity/editor-entity.element'),
|
||||
setup: (component: UmbEditorElement, info: any) => {
|
||||
component.entityKey = info.match.params.key;
|
||||
component.entityType = info.match.params.entityType;
|
||||
},
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
@@ -18,7 +18,7 @@ export class UmbTreeContentContext implements UmbTreeContext {
|
||||
key: '485d49ef-a4aa-46ac-843f-4256fe167347',
|
||||
name: 'Content',
|
||||
hasChildren: true,
|
||||
type: 'node',
|
||||
type: 'document',
|
||||
icon: 'favorite',
|
||||
parentKey: '',
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export class UmbTreeMemberGroupsContext implements UmbTreeContext {
|
||||
key: 'd46d144e-33d8-41e3-bf7a-545287e16e3c',
|
||||
name: 'Member Groups',
|
||||
hasChildren: true,
|
||||
type: 'member-group',
|
||||
type: 'memberGroup',
|
||||
icon: 'favorite',
|
||||
parentKey: '',
|
||||
};
|
||||
|
||||
@@ -5,56 +5,45 @@ import { UmbContextConsumerMixin } from '../../../core/context';
|
||||
import { UmbTreeContext } from '../tree.context';
|
||||
import { UUIMenuItemEvent } from '@umbraco-ui/uui';
|
||||
import { UmbSectionContext } from '../../sections/section.context';
|
||||
import { map, Subscription } from 'rxjs';
|
||||
import { UmbEntityStore } from '../../../core/stores/entity.store';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Entity } from '../../../mocks/data/entity.data';
|
||||
|
||||
@customElement('umb-tree-item')
|
||||
export class UmbTreeItem extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [UUITextStyles, css``];
|
||||
|
||||
@property({ type: Boolean })
|
||||
hasChildren = false;
|
||||
|
||||
@property({ type: Number })
|
||||
itemId = -1;
|
||||
|
||||
@property()
|
||||
itemKey = '';
|
||||
|
||||
@property()
|
||||
itemType = '';
|
||||
|
||||
@property({ type: String })
|
||||
label = '';
|
||||
|
||||
@property({ type: String })
|
||||
href = '';
|
||||
@property({ type: Boolean })
|
||||
hasChildren = false;
|
||||
|
||||
@state()
|
||||
childItems: any[] = [];
|
||||
private _childItems: Entity[] = [];
|
||||
|
||||
@state()
|
||||
private _href = '';
|
||||
|
||||
@state()
|
||||
private _loading = false;
|
||||
|
||||
@state()
|
||||
private _pathName? = '';
|
||||
|
||||
@state()
|
||||
private _sectionPathname?: string;
|
||||
|
||||
private _treeContext?: UmbTreeContext;
|
||||
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
private _sectionSubscription?: Subscription;
|
||||
|
||||
private _entitySubscription?: Subscription;
|
||||
|
||||
@state()
|
||||
private _itemName = '';
|
||||
private _childrenSubscription?: Subscription;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext('umbTreeContext', (treeContext: UmbTreeContext) => {
|
||||
this._treeContext = treeContext;
|
||||
this._pathName = this._treeContext?.tree?.meta?.pathname;
|
||||
});
|
||||
|
||||
this.consumeContext('umbSectionContext', (sectionContext: UmbSectionContext) => {
|
||||
@@ -67,25 +56,26 @@ export class UmbTreeItem extends UmbContextConsumerMixin(LitElement) {
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
|
||||
this._sectionSubscription = this._sectionContext?.data.subscribe((section) => {
|
||||
this._sectionPathname = section.meta.pathname;
|
||||
this._href = this._constructPath(section.meta.pathname, this.itemType, this.itemKey);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: how do we handle this?
|
||||
private _constructPath(key: string) {
|
||||
return `/section/${this._sectionPathname}/${this._pathName}/${key}`;
|
||||
private _constructPath(sectionPathname: string, type: string, key: string) {
|
||||
return `/section/${sectionPathname}/${type}/${key}`;
|
||||
}
|
||||
|
||||
private _onShowChildren(event: UUIMenuItemEvent) {
|
||||
event.stopPropagation();
|
||||
if (this.childItems.length > 0) return;
|
||||
if (this._childItems.length > 0) return;
|
||||
|
||||
this._loading = true;
|
||||
|
||||
this._treeContext?.fetchChildren(this.itemKey).subscribe((items) => {
|
||||
if (items?.length === 0) return;
|
||||
this._childrenSubscription?.unsubscribe();
|
||||
|
||||
this.childItems = items;
|
||||
this._childrenSubscription = this._treeContext?.fetchChildren(this.itemKey).subscribe((items) => {
|
||||
if (items?.length === 0) return;
|
||||
this._childItems = items;
|
||||
this._loading = false;
|
||||
});
|
||||
}
|
||||
@@ -93,15 +83,16 @@ export class UmbTreeItem extends UmbContextConsumerMixin(LitElement) {
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._sectionSubscription?.unsubscribe();
|
||||
this._childrenSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private _renderChildItems() {
|
||||
return this.childItems.map((item) => {
|
||||
return this._childItems.map((item) => {
|
||||
return html`<umb-tree-item
|
||||
.label=${item.name}
|
||||
.hasChildren=${item.hasChildren}
|
||||
.itemKey=${item.key}
|
||||
href="${this._constructPath(item.key)}">
|
||||
.itemType=${item.type}>
|
||||
</umb-tree-item>`;
|
||||
});
|
||||
}
|
||||
@@ -113,7 +104,7 @@ export class UmbTreeItem extends UmbContextConsumerMixin(LitElement) {
|
||||
.loading=${this._loading}
|
||||
.hasChildren=${this.hasChildren}
|
||||
label="${this.label}"
|
||||
href="${this._constructPath(this.itemKey)}">
|
||||
href="${this._href}">
|
||||
${this._renderChildItems()}
|
||||
</uui-menu-item>
|
||||
`;
|
||||
|
||||
@@ -14,6 +14,9 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(LitElement) {
|
||||
@state()
|
||||
private _entityKey = '';
|
||||
|
||||
@state()
|
||||
private _entityType = '';
|
||||
|
||||
@state()
|
||||
private _label = '';
|
||||
|
||||
@@ -40,6 +43,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(LitElement) {
|
||||
|
||||
this._loading = false;
|
||||
this._entityKey = items[0].key;
|
||||
this._entityType = items[0].type;
|
||||
this._label = items[0].name;
|
||||
this._hasChildren = items[0].hasChildren;
|
||||
});
|
||||
@@ -54,6 +58,7 @@ export class UmbTreeNavigator extends UmbContextConsumerMixin(LitElement) {
|
||||
render() {
|
||||
return html`<umb-tree-item
|
||||
.itemKey=${this._entityKey}
|
||||
.itemType=${this._entityType}
|
||||
.label=${this._label}
|
||||
?hasChildren=${this._hasChildren}
|
||||
.loading=${this._loading}></umb-tree-item> `;
|
||||
|
||||
@@ -89,7 +89,7 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement<Um
|
||||
render() {
|
||||
return html`
|
||||
<!-- TODO: maybe we need a layout component between umb-editor-layout and umb-editor-entity? -->
|
||||
<umb-editor-entity>
|
||||
<umb-editor-entity-layout>
|
||||
<h3 slot="name">Select content</h3>
|
||||
<uui-box>
|
||||
<uui-input></uui-input>
|
||||
@@ -110,7 +110,7 @@ export class UmbModalLayoutContentPickerElement extends UmbModalLayoutElement<Um
|
||||
<uui-button label="Close" @click=${this._close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this._submit}></uui-button>
|
||||
</div>
|
||||
</umb-editor-entity>
|
||||
</umb-editor-entity-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
loader: () => import('./backoffice/tree/data-types/tree-data-types.element'),
|
||||
meta: {
|
||||
pathname: 'data-types',
|
||||
editor: 'Umb.Editor.DataType',
|
||||
label: 'Data Types',
|
||||
weight: 1,
|
||||
sections: ['Umb.Section.Settings'],
|
||||
@@ -262,7 +261,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
loader: () => import('./backoffice/tree/document-types/tree-document-types.element'),
|
||||
meta: {
|
||||
pathname: 'document-types',
|
||||
editor: 'Umb.Editor.DocumentType',
|
||||
label: 'Document Types',
|
||||
weight: 2,
|
||||
sections: ['Umb.Section.Settings'],
|
||||
@@ -286,7 +284,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
name: 'Members Tree',
|
||||
loader: () => import('./backoffice/tree/members/tree-members.element'),
|
||||
meta: {
|
||||
editor: 'Umb.Editor.Member',
|
||||
pathname: 'members',
|
||||
label: 'Members',
|
||||
weight: 0,
|
||||
@@ -299,7 +296,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
name: 'Members Groups Tree',
|
||||
loader: () => import('./backoffice/tree/member-groups/tree-member-groups.element'),
|
||||
meta: {
|
||||
editor: 'Umb.Editor.MemberGroup',
|
||||
pathname: 'member-groups',
|
||||
label: 'Member Groups',
|
||||
weight: 1,
|
||||
@@ -312,7 +308,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
name: 'Extensions Tree',
|
||||
loader: () => import('./backoffice/tree/extensions/tree-extensions.element'),
|
||||
meta: {
|
||||
editor: 'Umb.Editor.Extensions',
|
||||
pathname: 'extensions',
|
||||
label: 'Extensions',
|
||||
weight: 3,
|
||||
@@ -325,7 +320,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
name: 'Media Tree',
|
||||
loader: () => import('./backoffice/tree/media/tree-media.element'),
|
||||
meta: {
|
||||
editor: 'Umb.Editor.Media',
|
||||
pathname: 'media',
|
||||
label: 'Media',
|
||||
weight: 100,
|
||||
@@ -338,7 +332,6 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
name: 'Content Tree',
|
||||
loader: () => import('./backoffice/tree/content/tree-content.element'),
|
||||
meta: {
|
||||
editor: 'Umb.Editor.Content',
|
||||
pathname: 'content',
|
||||
label: 'Content',
|
||||
weight: 100,
|
||||
@@ -350,42 +343,63 @@ export const internalManifests: Array<ManifestTypes & { loader: () => Promise<ob
|
||||
alias: 'Umb.Editor.Member',
|
||||
name: 'Member Editor',
|
||||
loader: () => import('./backoffice/editors/member/editor-member.element'),
|
||||
meta: {
|
||||
entityType: 'member',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.MemberGroup',
|
||||
name: 'Member Group Editor',
|
||||
loader: () => import('./backoffice/editors/member-group/editor-member-group.element'),
|
||||
meta: {
|
||||
entityType: 'memberGroup',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.DataType',
|
||||
name: 'Data Type Editor',
|
||||
loader: () => import('./backoffice/editors/data-type/editor-data-type.element'),
|
||||
meta: {
|
||||
entityType: 'dataType',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.DocumentType',
|
||||
name: 'Document Type Editor',
|
||||
loader: () => import('./backoffice/editors/document-type/editor-document-type.element'),
|
||||
meta: {
|
||||
entityType: 'documentType',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Extensions',
|
||||
name: 'Extensions Editor',
|
||||
loader: () => import('./backoffice/editors/extensions/editor-extensions.element'),
|
||||
meta: {
|
||||
entityType: 'extensions',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Media',
|
||||
name: 'Media Editor',
|
||||
loader: () => import('./backoffice/editors/media/editor-media.element'),
|
||||
meta: {
|
||||
entityType: 'media',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'editor',
|
||||
alias: 'Umb.Editor.Content',
|
||||
name: 'Content Editor',
|
||||
loader: () => import('./backoffice/editors/content/editor-content.element'),
|
||||
meta: {
|
||||
entityType: 'document',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'entityAction',
|
||||
|
||||
@@ -49,13 +49,16 @@ export interface MetaSection {
|
||||
}
|
||||
|
||||
export interface MetaTree {
|
||||
editor: string;
|
||||
pathname: string;
|
||||
label: string;
|
||||
weight: number;
|
||||
sections: Array<string>;
|
||||
}
|
||||
|
||||
export interface MetaEditor {
|
||||
entityType: string;
|
||||
}
|
||||
|
||||
export interface MetaEntityAction {
|
||||
label: string;
|
||||
icon: string;
|
||||
@@ -110,6 +113,7 @@ export interface IManifestTree extends IManifestElement {
|
||||
|
||||
export interface IManifestEditor extends IManifestElement {
|
||||
type: 'editor';
|
||||
meta: MetaEditor;
|
||||
}
|
||||
|
||||
export interface IManifestEntityAction extends IManifestElement {
|
||||
|
||||
Reference in New Issue
Block a user