diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-data-type-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-data-type-edit.element.ts new file mode 100644 index 0000000000..f2ddc6c8c4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-data-type-edit.element.ts @@ -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`
EDIT DATA TYPE: ${this.dataType?.id}
`; + } +} + +export default UmbEditorViewDataTypeEditElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-editor-view-data-type-edit': UmbEditorViewDataTypeEditElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-data-type.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-data-type.element.ts index 7c2bd3aead..f624e9348f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-data-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-data-type.element.ts @@ -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 = []; + + @state() + private _currentView = ''; + + @state() + private _routes: Array = []; + + 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` - + -
Some Content Here: ${this.id}
+ + ${this._editorViews.map( + (view: UmbExtensionManifestEditorView) => html` + + + ${view.name} + + ` + )} + + +
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-node.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-node.element.ts index 8865852d53..505cb8a221 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-node.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/editor-node.element.ts @@ -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) => + 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) => { - this._editorViews = dashboards as Array; + .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 = []; diff --git a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts index 7bece422e0..9ebd68541e 100644 --- a/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/core/extension/extension.registry.ts @@ -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; + propertyEditors: Array; }; // Dashboard: @@ -61,6 +61,7 @@ export type UmbExtensionManifestDashboard = { // Editor View: export type UmbManifestEditorViewMeta = { + editors: Array; // 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>; extensionsOfType(type: 'dashboard'): Observable>; + extensionsOfType(type: 'editorView'): Observable>; extensionsOfType(type: 'propertyEditor'): Observable>; extensionsOfType(type: 'propertyAction'): Observable>; extensionsOfType(type: UmbExtensionManifestCoreTypes): Observable>; diff --git a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts index bc65d8dff3..fe2760f555 100644 --- a/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/temp-internal-manifests.ts @@ -95,6 +95,9 @@ export const internalManifests: Array = [ 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 = [ 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',