diff --git a/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts index 9bd38fdc79..c3aff9e9ab 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts @@ -51,30 +51,34 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { @state() _node?: DocumentNode; - private _contentService?: UmbNodeStore; + private _nodeStore?: UmbNodeStore; private _nodeSubscription?: Subscription; constructor () { super(); - this.consumeContext('umbNodeStore', (contentService: UmbNodeStore) => { - this._contentService = contentService; + this.consumeContext('umbNodeStore', (nodeStore: UmbNodeStore) => { + this._nodeStore = nodeStore; this._useNode(); }); } - private _onPropertyDataTypeChange(e: CustomEvent) { - const target = (e.target as any) - console.log(target.value) + private _onPropertyValueChange(e: CustomEvent) { + const target = (e.target as any); // TODO: Set value. - //this.nodeData.properties.find(x => x.propertyAlias === target.propertyAlias)?.tempValue = target.value; + const property = this._node?.properties.find(x => x.alias === target.property.alias); + if(property) { + property.tempValue = target.value; + } else { + console.error('property was not found', target.property.alias); + } } private _useNode() { this._nodeSubscription?.unsubscribe(); - this._nodeSubscription = this._contentService?.getById(parseInt(this.id)).subscribe(node => { + this._nodeSubscription = this._nodeStore?.getById(parseInt(this.id)).subscribe(node => { if (!node) return; // TODO: Handle nicely if there is no node. this._node = node; }); @@ -85,7 +89,10 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { } private _onSave() { - console.log('Save'); + // TODO: What if store is not present, what if node is not loaded.... + if(this._node) { + this._nodeStore?.save([this._node]); + } } private _onSaveAndPreview() { @@ -95,6 +102,7 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { disconnectedCallback(): void { super.disconnectedCallback(); this._nodeSubscription?.unsubscribe(); + delete this._node; } render() { @@ -114,7 +122,7 @@ class UmbContentEditor extends UmbContextConsumerMixin(LitElement) { + @property-value-change=${this._onPropertyValueChange}>
`)} diff --git a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts index 54f7128bf1..aea51ccbd1 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts @@ -3,7 +3,6 @@ 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 { UmbNodeStore as UmbNodeStore } from '../core/stores/node.store'; import { Subscription } from 'rxjs'; import './content-tree.element'; @@ -44,6 +43,8 @@ export class UmbContentSection extends UmbContextProviderMixin(UmbContextConsume // TODO: temp outlet solution const nodeId = location.params.nodeId; + this._outlet?.parentNode?.removeChild(this._outlet); + if (nodeId !== undefined) { const contentEditor = document.createElement('umb-content-editor'); contentEditor.id = nodeId; 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 14dbebccc6..3a84161e56 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 @@ -17,9 +17,39 @@ export class UmbNodeStore { return this.nodes.pipe(map(((nodes: Array) => nodes.find((node: DocumentNode) => 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[]) { + // fetch from server and update store + // TODO: use Fetcher API. + let body:string; + + try { + body = JSON.stringify(data); + } catch (error) { + console.error(error); + return; + } + + // TODO: Use node type to hit the right API, or have a general Node API? + fetch('/umbraco/backoffice/content/save', + { + method: 'POST', + body: body, + headers: { + 'Content-Type': 'application/json', + }, + } + ) + .then(res => res.json()) + .then(data => { + this._updateStore(data); + }); + } + private _updateStore (fetchedNodes: Array) { const storedNodes = this._nodes.getValue(); - let updated: any = [...storedNodes]; + const updated: DocumentNode[] = [...storedNodes]; fetchedNodes.forEach(fetchedNode => { const index = storedNodes.map(storedNode => storedNode.id).indexOf(fetchedNode.id); @@ -29,7 +59,7 @@ export class UmbNodeStore { updated[index] = fetchedNode; } else { // If the node is not in the store, add it - updated = [...updated, fetchedNode]; + updated.push(fetchedNode); } }) 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 30078f0a73..0d436fc663 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 @@ -5,8 +5,8 @@ export interface DocumentNode { alias: string; icon: string; // TODO: should come from the doc type? properties: NodeProperty[]; - data: any; // TODO: define data type - layout?: any; // TODO: define layout type - make it non-optional + //data: any; // TODO: define data type + //layout?: any; // TODO: define layout type - make it non-optional } export interface DataTypeEntity { @@ -27,29 +27,32 @@ export interface NodeProperty { tempValue: string; // TODO: remove this - only used for testing } -export const data = [ + +export const data: Array = [ { id: 1, - key: '74e4008a-ea4f-4793-b924-15e02fd380d3', + key: '74e4008a-ea4f-4793-b924-15e02fd380d1', name: 'Document 1', alias: 'document1', icon: 'document', properties: [ { alias: 'myHeadline', - label: 'Textarea label', + label: 'Textarea label 1', description: 'this is a textarea property', dataTypeKey: 'dt-1', tempValue: 'hello world 1' }, { alias: 'myDescription', - label: 'Text string label', + label: 'Text string label 1', description: 'This is the a text string property', dataTypeKey: 'dt-2', tempValue: 'Tex areaaaa 1' }, ], + /* + // Concept for stored values, better approach for variants, separating data from structure/configuration data: [ { alias: 'myHeadline', @@ -60,7 +63,9 @@ export const data = [ value: 'Teeeeexxxt areaaaaaa', }, ], + */ /* + // Concept for node layout, separation of design from config and data. layout: [ { type: 'group', @@ -80,53 +85,26 @@ export const data = [ }, { id: 2, - key: '74e4008a-ea4f-4793-b924-15e02fd380d3', + key: '74e4008a-ea4f-4793-b924-15e02fd380d2', name: 'Document 2', alias: 'document2', icon: 'favorite', properties: [ { alias: 'myHeadline', - label: 'Textarea label', + label: 'Textarea label 2', description: 'this is a textarea property', dataTypeKey: 'dt-1', tempValue: 'hello world 2' }, { alias: 'myDescription', - label: 'Text string label', + label: 'Text string label 2', description: 'This is the a text string property', dataTypeKey: 'dt-2', tempValue: 'Tex areaaaa 2' }, ], - data: [ - { - alias: 'myHeadline', - value: 'hello world', - }, - { - alias: 'myDescription', - value: 'Teeeeexxxt areaaaaaa', - }, - ], - /* - layout: [ - { - type: 'group', - children: [ - { - type: 'property', - alias: 'myHeadline' - }, - { - type: 'property', - alias: 'myDescription' - } - ] - } - ], - */ } ]; @@ -141,6 +119,21 @@ class UmbContentData { getById (id: number) { return this._data.find(item => item.id === id); } + + save(nodes: DocumentNode[]) { + nodes.forEach( node => { + const foundIndex = this._data.findIndex(item => item.id === node.id); + if(foundIndex !== -1) { + // replace + this._data[foundIndex] = node; + } else { + // new + this._data.push(node); + } + }); + //console.log('save:', nodes); + return nodes; + } } export const umbContentData = new UmbContentData(); \ No newline at end of file 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 8354a74589..1663e7af82 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,5 +1,6 @@ import { rest } from 'msw'; -import { umbContentData } from '../data/content.data'; +import { DocumentNode, umbContentData } from '../data/content.data'; + // TODO: add schema export const handlers = [ @@ -9,9 +10,23 @@ export const handlers = [ const int = parseInt(id); const document = umbContentData.getById(int); + return res( ctx.status(200), ctx.json([document]) ); }), + + rest.post('/umbraco/backoffice/content/save', (req, res, ctx) => { + const data = req.body as any; + if (!data) return; + + + umbContentData.save(data); + + return res( + ctx.status(200), + ctx.json(data) + ); + }), ]; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/node-editor/node-property.element.ts b/src/Umbraco.Web.UI.Client/src/node-editor/node-property.element.ts index 19a9e876f9..c5d8a83113 100644 --- a/src/Umbraco.Web.UI.Client/src/node-editor/node-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/node-editor/node-property.element.ts @@ -32,8 +32,11 @@ class UmbNodeProperty extends LitElement { value?:string; // TODO: maybe a bit messy with all the event listeners on the different levels: - private _onPropertyDataTypeChange = (e :CustomEvent) => { + private _onPropertyDataTypeChange = ( e:CustomEvent) => { this.value = (e.target as any).value; + this.dispatchEvent(new CustomEvent('property-value-change', { bubbles: true, composed: true })); + // No need for this event to leave scope. + e.stopPropagation(); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/property-editors/property-editor-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/property-editors/property-editor-textarea.element.ts index 30cc3e34d8..90e5ba71da 100644 --- a/src/Umbraco.Web.UI.Client/src/property-editors/property-editor-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/property-editors/property-editor-textarea.element.ts @@ -16,8 +16,13 @@ class UmbPropertyEditorTextarea extends LitElement { @property() value = ''; + private onInput(e: InputEvent) { + this.value = (e.target as HTMLInputElement).value; + this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true })); + } + render() { - return html``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/section.context.ts b/src/Umbraco.Web.UI.Client/src/section.context.ts index 5ca94e7179..ff6be9e81f 100644 --- a/src/Umbraco.Web.UI.Client/src/section.context.ts +++ b/src/Umbraco.Web.UI.Client/src/section.context.ts @@ -1,5 +1,5 @@ import { firstValueFrom, map, Observable, ReplaySubject } from 'rxjs'; -import { UmbExtensionManifest, UmbExtensionRegistry, UmbManifestSectionMeta } from './core/extension'; +import { UmbExtensionManifest, UmbExtensionRegistry } from './core/extension'; export class UmbSectionContext { private _extensionRegistry!: UmbExtensionRegistry;