diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-entity.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-entity.element.ts
index fe24d54bf2..97bd186b73 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-entity.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-entity.element.ts
@@ -17,14 +17,32 @@ class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) {
height: 100%;
}
+ #header {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ }
+
+ #footer {
+ display: flex;
+ height: 100%;
+ align-items: center;
+ gap: 16px;
+ }
+
+ #actions {
+ display: block;
+ margin-left: auto;
+ }
+
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);
+ border-right: 1px solid var(--uui-color-border);
flex-wrap: nowrap;
height: 60px;
}
@@ -126,27 +144,31 @@ class UmbEditorEntity extends UmbContextConsumerMixin(LitElement) {
render() {
return html`
-
-
-
- ${this._editorViews.map(
- (view: UmbExtensionManifestEditorView) => html`
-
-
- ${view.name}
-
- `
- )}
-
+
-
-
+
`;
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-layout.element.ts
index 8b8d299dc1..00f8add784 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-layout.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/components/editor-layout.element.ts
@@ -1,6 +1,6 @@
import { css, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
-import { customElement, property } from 'lit/decorators.js';
+import { customElement } from 'lit/decorators.js';
@customElement('umb-editor-layout')
class UmbEditorLayout extends LitElement {
@@ -24,11 +24,9 @@ class UmbEditorLayout extends LitElement {
#header {
background-color: var(--uui-color-surface);
width: 100%;
- display: flex;
- flex: none;
- gap: 16px;
- align-items: center;
border-bottom: 1px solid var(--uui-color-border);
+ box-sizing: border-box;
+ padding: 0 var(--uui-size-6);
}
#main {
@@ -40,14 +38,9 @@ class UmbEditorLayout extends LitElement {
}
#footer {
- display: flex;
- flex: none;
- justify-content: end;
- align-items: center;
height: 70px;
width: 100%;
- gap: 16px;
- padding-right: 24px;
+ padding: 0 var(--uui-size-6);
border-top: 1px solid var(--uui-color-border);
background-color: var(--uui-color-surface);
box-sizing: border-box;
@@ -59,15 +52,14 @@ class UmbEditorLayout extends LitElement {
return html`
`;
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-node-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-node-edit.element.ts
index 874943398a..c850d3c92f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-node-edit.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/editor-views/editor-view-node-edit.element.ts
@@ -1,10 +1,13 @@
import { css, html, LitElement } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
-import { customElement, property } from 'lit/decorators.js';
-import { DocumentNode, NodeProperty } from '../../mocks/data/content.data';
+import { customElement, state } from 'lit/decorators.js';
+import { NodeEntity, NodeProperty } from '../../mocks/data/content.data';
+import { UmbContextConsumerMixin } from '../../core/context';
+import { UmbNodeContext } from '../editors/node/node.context';
+import { Subscription, distinctUntilChanged } from 'rxjs';
@customElement('umb-editor-view-node-edit')
-export class UmbEditorViewNodeEdit extends LitElement {
+export class UmbEditorViewNodeEdit extends UmbContextConsumerMixin(LitElement) {
static styles = [
UUITextStyles,
css`
@@ -16,17 +19,40 @@ export class UmbEditorViewNodeEdit extends LitElement {
`,
];
- @property({ type: Object })
- node?: DocumentNode;
+ @state()
+ _node?: NodeEntity;
+
+ private _nodeContext?: UmbNodeContext;
+ private _nodeContextSubscription?: Subscription;
+
+ constructor() {
+ super();
+
+ this.consumeContext('umbNodeContext', (nodeContext) => {
+ this._nodeContext = nodeContext;
+ this._useNode();
+ });
+ }
+
+ private _useNode() {
+ this._nodeContextSubscription = this._nodeContext?.data.pipe(distinctUntilChanged()).subscribe((data) => {
+ this._node = data;
+ });
+ }
+
+ disconnectedCallback(): void {
+ super.disconnectedCallback();
+ this._nodeContextSubscription?.unsubscribe();
+ }
render() {
return html`
- ${this.node?.properties.map(
+ ${this._node?.properties.map(
(property: NodeProperty) => html`
data.alias === property.alias)?.value}>
+ .value=${this._node?.data.find((data) => data.alias === property.alias)?.value}>
`
)}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/data-type.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/data-type.context.ts
index a9ed0c2151..f114f92d1c 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/data-type.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/data-type/data-type.context.ts
@@ -17,7 +17,7 @@ export class UmbDataTypeContext {
}
// TODO: figure out how we want to update data
- public update(data: any) {
+ public update(data: Partial) {
this._data.next({ ...this._data.getValue(), ...data });
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/document-type.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/document-type.context.ts
index 4d04464440..600ec7ab2f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/document-type.context.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/document-type.context.ts
@@ -17,7 +17,7 @@ export class UmbDocumentTypeContext {
}
// TODO: figure out how we want to update data
- public update(data: any) {
+ public update(data: Partial) {
this._data.next({ ...this._data.getValue(), ...data });
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.element.ts
index beaf73816d..d9284e1d8f 100644
--- a/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.element.ts
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/document-type/editor-document-type.element.ts
@@ -111,6 +111,10 @@ export class UmbEditorDocumentTypeElement extends UmbContextProviderMixin(UmbCon
alias="Umb.Editor.DocumentType"
name="${ifDefined(this._documentType?.name)}"
@input="${this._handleInput}">
+ Icon
+
+ Keyboard Shortcuts
+
= [];
-
- @state()
- private _editorViews: Array = [];
-
- @state()
- private _currentView = '';
+ _node?: NodeEntity;
private _nodeStore?: UmbNodeStore;
- private _nodeSubscription?: Subscription;
+ private _nodeStoreSubscription?: Subscription;
+
+ private _nodeContext = new UmbNodeContext();
+ private _nodeContextSubscription?: Subscription;
private _notificationService?: UmbNotificationService;
- private _extensionRegistry?: UmbExtensionRegistry;
- private _editorViewsSubscription?: Subscription;
-
- private _routerFolder = '';
-
constructor() {
super();
@@ -84,18 +74,9 @@ export class UmbEditorNodeElement extends UmbContextConsumerMixin(LitElement) {
this._notificationService = service;
});
- this.consumeContext('umbExtensionRegistry', (extensionRegistry: UmbExtensionRegistry) => {
- this._extensionRegistry = extensionRegistry;
- this._useEditorViews();
- });
-
this.addEventListener('property-value-change', this._onPropertyValueChange);
- }
- connectedCallback(): void {
- super.connectedCallback();
- /* TODO: find a way to construct absolute urls */
- this._routerFolder = window.location.pathname.split('/view')[0];
+ this.provideContext('umbNodeContext', this._nodeContext);
}
private _onPropertyValueChange = (e: Event) => {
@@ -119,34 +100,19 @@ export class UmbEditorNodeElement extends UmbContextConsumerMixin(LitElement) {
}
private _useNode() {
- this._nodeSubscription?.unsubscribe();
+ this._nodeStoreSubscription?.unsubscribe();
- this._nodeSubscription = this._nodeStore?.getById(parseInt(this.id)).subscribe((node) => {
+ this._nodeStoreSubscription = this._nodeStore?.getById(parseInt(this.id)).subscribe((node) => {
if (!node) return; // TODO: Handle nicely if there is no node.
- this._node = node;
- // TODO: merge observables
- this._createRoutes();
- });
- }
- private _useEditorViews() {
- this._editorViewsSubscription?.unsubscribe();
+ this._nodeContextSubscription?.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.Node'))
- .sort((a, b) => b.meta.weight - a.meta.weight)
- )
- )
- .subscribe((editorViews) => {
- this._editorViews = editorViews;
- // TODO: merge observables
- this._createRoutes();
+ this._nodeContext?.update(node);
+
+ this._nodeContextSubscription = this._nodeContext.data.pipe(distinctUntilChanged()).subscribe((data) => {
+ this._node = data;
});
+ });
}
private _onSaveAndPublish() {
@@ -166,69 +132,17 @@ 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 = [];
-
- this._routes = this._editorViews.map((view) => {
- return {
- path: `view/${view.meta.pathname}`,
- component: () => document.createElement(view.elementName || 'div'),
- 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.node = this._node;
- 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();
- }
- }
-
disconnectedCallback(): void {
super.disconnectedCallback();
- this._nodeSubscription?.unsubscribe();
+ this._nodeStoreSubscription?.unsubscribe();
+ this._nodeContextSubscription?.unsubscribe();
delete this._node;
}
render() {
return html`
-
-
-
- ${this._editorViews.map(
- (view: UmbExtensionManifestEditorView) => html`
-
-
- ${view.name}
-
- `
- )}
-
-
-
+
+ Breadcrumbs
@@ -239,7 +153,7 @@ export class UmbEditorNodeElement extends UmbContextConsumerMixin(LitElement) {
color="positive"
label="Save and publish">
-
+
`;
}
}
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/editors/node/node.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/editors/node/node.context.ts
new file mode 100644
index 0000000000..530c212831
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/backoffice/editors/node/node.context.ts
@@ -0,0 +1,42 @@
+import { BehaviorSubject, Observable } from 'rxjs';
+import { NodeEntity } from '../../../mocks/data/content.data';
+
+export class UmbNodeContext {
+ // TODO: figure out how fine grained we want to make our observables.
+ private _data: BehaviorSubject = new BehaviorSubject({
+ id: -1,
+ key: '',
+ name: '',
+ alias: '',
+ icon: '',
+ properties: [
+ {
+ alias: '',
+ label: '',
+ description: '',
+ dataTypeKey: '',
+ },
+ ],
+ data: [
+ {
+ alias: '',
+ value: '',
+ },
+ ],
+ });
+ public readonly data: Observable = this._data.asObservable();
+
+ constructor(node?: NodeEntity) {
+ if (!node) return;
+ this._data.next(node);
+ }
+
+ // TODO: figure out how we want to update data
+ public update(data: Partial) {
+ this._data.next({ ...this._data.getValue(), ...data });
+ }
+
+ public getData() {
+ return this._data.getValue();
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts b/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts
index bd7a0cf961..da25798be4 100644
--- a/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts
+++ b/src/Umbraco.Web.UI.Client/src/core/stores/node.store.ts
@@ -1,11 +1,11 @@
import { BehaviorSubject, map, Observable } from 'rxjs';
-import { DocumentNode } from '../../mocks/data/content.data';
+import { NodeEntity } from '../../mocks/data/content.data';
export class UmbNodeStore {
- private _nodes: BehaviorSubject> = new BehaviorSubject(>[]);
- public readonly nodes: Observable> = this._nodes.asObservable();
+ private _nodes: BehaviorSubject> = new BehaviorSubject(>[]);
+ public readonly nodes: Observable> = this._nodes.asObservable();
- getById(id: number): Observable {
+ getById(id: number): Observable {
// fetch from server and update store
fetch(`/umbraco/backoffice/content/${id}`)
.then((res) => res.json())
@@ -13,14 +13,12 @@ export class UmbNodeStore {
this._updateStore(data);
});
- return this.nodes.pipe(
- map((nodes: Array) => nodes.find((node: DocumentNode) => node.id === id) || null)
- );
+ return this.nodes.pipe(map((nodes: Array) => nodes.find((node: NodeEntity) => node.id === id) || null));
}
// TODO: Use Node type, to not be specific about Document.
// TODO: make sure UI somehow can follow the status of this action.
- save(data: DocumentNode[]): Promise {
+ save(data: NodeEntity[]): Promise {
// fetch from server and update store
// TODO: use Fetcher API.
let body: string;
@@ -48,7 +46,7 @@ export class UmbNodeStore {
private _updateStore(fetchedNodes: Array) {
const storedNodes = this._nodes.getValue();
- const updated: DocumentNode[] = [...storedNodes];
+ const updated: NodeEntity[] = [...storedNodes];
fetchedNodes.forEach((fetchedNode) => {
const index = storedNodes.map((storedNode) => storedNode.id).indexOf(fetchedNode.id);
diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts
index 8a4a483e18..0436ce9a54 100644
--- a/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts
+++ b/src/Umbraco.Web.UI.Client/src/mocks/data/content.data.ts
@@ -1,6 +1,6 @@
import { UmbData } from './data';
-export interface DocumentNode {
+export interface NodeEntity {
id: number;
key: string;
name: string;
@@ -20,7 +20,7 @@ export interface NodeProperty {
export interface NodePropertyData {
alias: string;
- value: unknown;
+ value: any;
}
/* TODO:
@@ -30,7 +30,7 @@ We would like the tree items to stay up to date, without requesting the server a
If we split entityData into its own object, then that could go in the entityStore and be merged with the nodeStore (we would have a subscription on both).
*/
-export const data: Array = [
+export const data: Array = [
{
id: 1,
key: '74e4008a-ea4f-4793-b924-15e02fd380d1',
@@ -135,7 +135,7 @@ export const data: Array = [
];
// Temp mocked database
-class UmbContentData extends UmbData {
+class UmbContentData extends UmbData {
constructor() {
super(data);
}
diff --git a/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts
index 7159c0056f..ba97880e59 100644
--- a/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts
+++ b/src/Umbraco.Web.UI.Client/src/mocks/domains/content.handlers.ts
@@ -1,6 +1,6 @@
import { rest } from 'msw';
-import { DocumentNode, umbContentData } from '../data/content.data';
+import { NodeEntity, umbContentData } from '../data/content.data';
// TODO: add schema
export const handlers = [
@@ -14,7 +14,7 @@ export const handlers = [
return res(ctx.status(200), ctx.json([document]));
}),
- rest.post('/umbraco/backoffice/content/save', (req, res, ctx) => {
+ rest.post('/umbraco/backoffice/content/save', (req, res, ctx) => {
const data = req.body;
if (!data) return;