diff --git a/src/app/components/editor/code/code.component.ts b/src/app/components/editor/code/code.component.ts index 0734426..4d559f5 100644 --- a/src/app/components/editor/code/code.component.ts +++ b/src/app/components/editor/code/code.component.ts @@ -199,12 +199,15 @@ export class CodeComponent extends EditorNodeContract implements OnInit { public onEditorModelChange($event) { if ( this.editorValue !== this.dbRecord.code ) { this.dirty = true; + this.editorService.triggerSave(); } } public onSelectChange(updateDbRecord = true) { if ( updateDbRecord ) { this.dbRecord.Language = this.editorOptions.language; + this.editorService.triggerSave(); + this.dirty = true; } this.editorOptions = {...this.editorOptions}; diff --git a/src/app/components/editor/database/database.component.ts b/src/app/components/editor/database/database.component.ts index fecf0d0..5687be4 100644 --- a/src/app/components/editor/database/database.component.ts +++ b/src/app/components/editor/database/database.component.ts @@ -71,6 +71,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit { onCellValueChanged() { this.dirty = true; + this.editorService.triggerSave(); } async onManageColumns() { @@ -98,6 +99,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit { this.rowData.push({}); this.agGridElement.api.setRowData(this.rowData); this.dirty = true; + this.editorService.triggerSave(); } async onRemoveRow() { @@ -122,6 +124,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit { this.agGridElement.api.setRowData(this.rowData); this.lastClickRow = -1; this.dirty = true; + this.editorService.triggerSave(); }, } ], @@ -168,6 +171,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit { this.agGridElement.api.setColumnDefs(this.columnDefs); this.dirty = true; + this.editorService.triggerSave(); } public async performLoad(): Promise { diff --git a/src/app/components/nodes/norm/norm.component.ts b/src/app/components/nodes/norm/norm.component.ts index 9ac48df..5807f3d 100644 --- a/src/app/components/nodes/norm/norm.component.ts +++ b/src/app/components/nodes/norm/norm.component.ts @@ -62,6 +62,7 @@ export class NormComponent extends EditorNodeContract implements OnInit { const innerHTML = this.editable.nativeElement.innerHTML; if ( this.contents !== innerHTML ) { this.contents = innerHTML; + this.editorService.triggerSave(); } } diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html index 2f9c150..3c0e3ea 100644 --- a/src/app/pages/editor/editor.page.html +++ b/src/app/pages/editor/editor.page.html @@ -11,6 +11,12 @@ class="title-input" > + + + diff --git a/src/app/pages/editor/editor.page.scss b/src/app/pages/editor/editor.page.scss index 6846c83..e5d6a1a 100644 --- a/src/app/pages/editor/editor.page.scss +++ b/src/app/pages/editor/editor.page.scss @@ -48,3 +48,11 @@ ion-icon.invisible { color: #4d4d4d; } } + +.save-button { + color: #777; + + i { + margin-right: 5px; + } +} diff --git a/src/app/service/editor.service.ts b/src/app/service/editor.service.ts index f82cea7..003ee95 100644 --- a/src/app/service/editor.service.ts +++ b/src/app/service/editor.service.ts @@ -11,6 +11,17 @@ export class NoPageLoadedError extends Error { } } +export function debounce(func: (...args: any[]) => any, timeout?: number) { + let timer: number | undefined; + return (...args: any[]) => { + const next = () => func(...args); + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(next, timeout > 0 ? timeout : 300); + }; +} + @Injectable({ providedIn: 'root' }) @@ -21,6 +32,30 @@ export class EditorService { protected dirtyOverride = false; protected ready$: BehaviorSubject = new BehaviorSubject(false); protected subs: Subscription[] = []; + protected saving = false; + protected saveTriggered = false; + protected privTriggerSave = debounce(() => { + if ( this.saving ) { + this.triggerSave(); + } else { + this.save(); + } + + this.saveTriggered = false; + }, 3000); + + public triggerSave() { + this.saveTriggered = true; + this.privTriggerSave(); + } + + public get isSaving() { + return this.saving; + } + + public get willSave() { + return this.saveTriggered; + } public get immutableNodes(): HostRecord[] { return [...this.currentNodes]; @@ -46,9 +81,7 @@ export class EditorService { constructor( protected api: ApiService, - ) { - console.log('editor service', this); - } + ) { } async startEditing(pageId: string) { if ( this.currentPage ) { @@ -58,8 +91,6 @@ export class EditorService { 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() { @@ -72,10 +103,11 @@ export class EditorService { } async save() { - if ( !(await this.needsSave()) ) { + if ( !(await this.needsSave()) || this.saving ) { return; } + this.saving = true; const editors = Object.values(this.nodeIdToEditorContract); // Save all editors that handle their data independently first @@ -92,6 +124,7 @@ export class EditorService { await this.saveNodesAsPage(this.currentPage, this.currentNodes); this.dirtyOverride = false; + this.saving = false; } async moveNode(node: HostRecord, direction: 'up' | 'down') { @@ -117,6 +150,7 @@ export class EditorService { } this.dirtyOverride = true; + this.triggerSave(); } async saveNodesAsPage(page: PageRecord, nodes: HostRecord[]): Promise { @@ -184,6 +218,7 @@ export class EditorService { this.currentNodes = this.currentNodes.filter(x => x.UUID !== nodeId); this.dirtyOverride = true; + this.triggerSave(); } async addNode(type: 'paragraph' | 'code_ref' | 'database_ref' | 'file_ref', position?: 'before' | 'after', positionNodeId?: string) { @@ -217,6 +252,7 @@ export class EditorService { } this.dirtyOverride = true; + this.triggerSave(); return host; }