|
|
|
import {Injectable} from '@angular/core';
|
|
|
|
import {ApiService} from './api.service';
|
|
|
|
import PageRecord from '../structures/PageRecord';
|
|
|
|
import HostRecord from '../structures/HostRecord';
|
|
|
|
import {EditorNodeContract} from '../components/nodes/EditorNode.contract';
|
|
|
|
import {BehaviorSubject, Subscription} from 'rxjs';
|
|
|
|
|
|
|
|
export class NoPageLoadedError extends Error {
|
|
|
|
constructor(msg = 'There is no page open for editing.') {
|
|
|
|
super(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Injectable({
|
|
|
|
providedIn: 'root'
|
|
|
|
})
|
|
|
|
export class EditorService {
|
|
|
|
protected currentPage?: PageRecord;
|
|
|
|
protected currentNodes: HostRecord[] = [];
|
|
|
|
protected nodeIdToEditorContract: { [key: string]: EditorNodeContract } = {};
|
|
|
|
protected dirtyOverride = false;
|
|
|
|
protected ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
|
|
protected subs: Subscription[] = [];
|
|
|
|
|
|
|
|
public get immutableNodes(): HostRecord[] {
|
|
|
|
return [...this.currentNodes];
|
|
|
|
}
|
|
|
|
|
|
|
|
public get mutablePageName(): string {
|
|
|
|
if ( this.currentPage ) {
|
|
|
|
return this.currentPage.Name;
|
|
|
|
}
|
|
|
|
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
public set mutablePageName(name: string) {
|
|
|
|
if ( this.currentPage && this.canEdit() ) {
|
|
|
|
if ( this.currentPage.Name !== name ) {
|
|
|
|
this.dirtyOverride = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.currentPage.Name = name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
protected api: ApiService,
|
|
|
|
) {
|
|
|
|
console.log('editor service', this);
|
|
|
|
}
|
|
|
|
|
|
|
|
async startEditing(pageId: string) {
|
|
|
|
if ( this.currentPage ) {
|
|
|
|
await this.stopEditing();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.currentPage = await this.loadPage(pageId);
|
|
|
|
this.currentNodes = await this.loadNodes(pageId);
|
|
|
|
await this.ready$.next(true);
|
|
|
|
console.log('editing', this.currentPage);
|
|
|
|
console.log('nodes', this.currentNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
async stopEditing() {
|
|
|
|
delete this.currentPage;
|
|
|
|
this.currentNodes = [];
|
|
|
|
this.nodeIdToEditorContract = {};
|
|
|
|
this.subs.forEach(sub => sub.unsubscribe());
|
|
|
|
this.subs = [];
|
|
|
|
this.ready$.next(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
async save() {
|
|
|
|
if ( !(await this.needsSave()) ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const editors = Object.values(this.nodeIdToEditorContract);
|
|
|
|
|
|
|
|
// Save all editors that handle their data independently first
|
|
|
|
await Promise.all(editors.map(async editor => {
|
|
|
|
if ( await editor.needsSave() ) {
|
|
|
|
await editor.performSave();
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Tell the editors to write their state changes to the HostRecords
|
|
|
|
await Promise.all(editors.map(async editor => {
|
|
|
|
await editor.writeChangesToNode();
|
|
|
|
}));
|
|
|
|
|
|
|
|
await this.saveNodesAsPage(this.currentPage, this.currentNodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
async saveNodesAsPage(page: PageRecord, nodes: HostRecord[]) {
|
|
|
|
await new Promise((res, rej) => {
|
|
|
|
const saveNodes = nodes.map(x => {
|
|
|
|
x.PageId = page.UUID;
|
|
|
|
return x.toSave();
|
|
|
|
});
|
|
|
|
|
|
|
|
this.api.post(`/page/${page.UUID}/nodes/save`, saveNodes).subscribe({
|
|
|
|
next: result => {
|
|
|
|
res(); // TODO load in returned data!!
|
|
|
|
},
|
|
|
|
error: rej,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async needsSave() {
|
|
|
|
if ( this.dirtyOverride ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dirties = await Promise.all(Object.values(this.nodeIdToEditorContract).map(editor => editor.isDirty()));
|
|
|
|
const needSaves = await Promise.all(Object.values(this.nodeIdToEditorContract).map(editor => editor.needsSave()));
|
|
|
|
|
|
|
|
return dirties.some(Boolean) || needSaves.some(Boolean);
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteNode(nodeId: string) {
|
|
|
|
if ( !this.currentPage ) {
|
|
|
|
throw new NoPageLoadedError();
|
|
|
|
}
|
|
|
|
|
|
|
|
const node = this.currentNodes.find(maybeNode => maybeNode.UUID === nodeId);
|
|
|
|
if ( !node ) {
|
|
|
|
throw new Error('Invalid node ID.');
|
|
|
|
}
|
|
|
|
|
|
|
|
const editor = this.nodeIdToEditorContract[nodeId];
|
|
|
|
if ( editor ) {
|
|
|
|
await editor.performDelete();
|
|
|
|
delete this.nodeIdToEditorContract[nodeId];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.currentNodes = this.currentNodes.filter(x => x.UUID !== nodeId);
|
|
|
|
this.dirtyOverride = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
canEdit() {
|
|
|
|
if ( !this.currentPage ) {
|
|
|
|
throw new NoPageLoadedError();
|
|
|
|
}
|
|
|
|
|
|
|
|
return !this.currentPage.isViewOnly();
|
|
|
|
}
|
|
|
|
|
|
|
|
async registerNodeEditor(nodeId: string, editor: EditorNodeContract) {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
const sub = this.ready$.subscribe(async val => {
|
|
|
|
if ( val ) {
|
|
|
|
try {
|
|
|
|
if ( !this.currentPage ) {
|
|
|
|
return rej(new NoPageLoadedError());
|
|
|
|
}
|
|
|
|
|
|
|
|
const node = this.currentNodes.find(maybeNode => maybeNode.UUID === nodeId);
|
|
|
|
if ( !node ) {
|
|
|
|
return rej(new Error('Invalid node ID.'));
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.page = this.currentPage;
|
|
|
|
editor.node = node;
|
|
|
|
this.nodeIdToEditorContract[nodeId] = editor;
|
|
|
|
|
|
|
|
if ( editor.needsLoad() ) {
|
|
|
|
await editor.performLoad();
|
|
|
|
}
|
|
|
|
|
|
|
|
res();
|
|
|
|
} catch (e) {
|
|
|
|
rej(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
this.subs.push(sub);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async unregisterNodeEditor(nodeId: string) {
|
|
|
|
if ( !this.currentPage ) {
|
|
|
|
throw new NoPageLoadedError();
|
|
|
|
}
|
|
|
|
|
|
|
|
delete this.nodeIdToEditorContract[nodeId];
|
|
|
|
}
|
|
|
|
|
|
|
|
async loadPage(pageId: string): Promise<PageRecord> {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
this.api.get(`/page/${pageId}`).subscribe({
|
|
|
|
next: result => {
|
|
|
|
res(new PageRecord(result.data));
|
|
|
|
},
|
|
|
|
error: rej,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async loadNodes(pageId: string): Promise<HostRecord[]> {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
this.api.get(`/page/${pageId}/nodes`).subscribe({
|
|
|
|
next: result => {
|
|
|
|
res(result.data.map(rec => {
|
|
|
|
const host = new HostRecord(rec.Value.Value);
|
|
|
|
host.load(rec);
|
|
|
|
return host;
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
error: rej,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|