Add auto-save support and saving indicator
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing

This commit is contained in:
Garrett Mills 2020-10-14 11:25:26 -05:00
parent 9a53faf338
commit 9f80842b9f
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246
6 changed files with 64 additions and 6 deletions

View File

@ -199,12 +199,15 @@ export class CodeComponent extends EditorNodeContract implements OnInit {
public onEditorModelChange($event) { public onEditorModelChange($event) {
if ( this.editorValue !== this.dbRecord.code ) { if ( this.editorValue !== this.dbRecord.code ) {
this.dirty = true; this.dirty = true;
this.editorService.triggerSave();
} }
} }
public onSelectChange(updateDbRecord = true) { public onSelectChange(updateDbRecord = true) {
if ( updateDbRecord ) { if ( updateDbRecord ) {
this.dbRecord.Language = this.editorOptions.language; this.dbRecord.Language = this.editorOptions.language;
this.editorService.triggerSave();
this.dirty = true;
} }
this.editorOptions = {...this.editorOptions}; this.editorOptions = {...this.editorOptions};

View File

@ -71,6 +71,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit {
onCellValueChanged() { onCellValueChanged() {
this.dirty = true; this.dirty = true;
this.editorService.triggerSave();
} }
async onManageColumns() { async onManageColumns() {
@ -98,6 +99,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit {
this.rowData.push({}); this.rowData.push({});
this.agGridElement.api.setRowData(this.rowData); this.agGridElement.api.setRowData(this.rowData);
this.dirty = true; this.dirty = true;
this.editorService.triggerSave();
} }
async onRemoveRow() { async onRemoveRow() {
@ -122,6 +124,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit {
this.agGridElement.api.setRowData(this.rowData); this.agGridElement.api.setRowData(this.rowData);
this.lastClickRow = -1; this.lastClickRow = -1;
this.dirty = true; this.dirty = true;
this.editorService.triggerSave();
}, },
} }
], ],
@ -168,6 +171,7 @@ export class DatabaseComponent extends EditorNodeContract implements OnInit {
this.agGridElement.api.setColumnDefs(this.columnDefs); this.agGridElement.api.setColumnDefs(this.columnDefs);
this.dirty = true; this.dirty = true;
this.editorService.triggerSave();
} }
public async performLoad(): Promise<void> { public async performLoad(): Promise<void> {

View File

@ -62,6 +62,7 @@ export class NormComponent extends EditorNodeContract implements OnInit {
const innerHTML = this.editable.nativeElement.innerHTML; const innerHTML = this.editable.nativeElement.innerHTML;
if ( this.contents !== innerHTML ) { if ( this.contents !== innerHTML ) {
this.contents = innerHTML; this.contents = innerHTML;
this.editorService.triggerSave();
} }
} }

View File

@ -11,6 +11,12 @@
class="title-input" class="title-input"
></ion-input> ></ion-input>
</ion-title> </ion-title>
<ion-buttons slot="end">
<button class="save-button" (click)="editorService.triggerSave()" title="Manually save this note">
<i *ngIf="!(editorService.isSaving || editorService.willSave)" class="fa fa-check-circle"></i>
{{ (editorService.isSaving || editorService.willSave) ? 'Saving...' : 'Saved!' }}
</button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>

View File

@ -48,3 +48,11 @@ ion-icon.invisible {
color: #4d4d4d; color: #4d4d4d;
} }
} }
.save-button {
color: #777;
i {
margin-right: 5px;
}
}

View File

@ -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({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -21,6 +32,30 @@ export class EditorService {
protected dirtyOverride = false; protected dirtyOverride = false;
protected ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); protected ready$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
protected subs: Subscription[] = []; 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[] { public get immutableNodes(): HostRecord[] {
return [...this.currentNodes]; return [...this.currentNodes];
@ -46,9 +81,7 @@ export class EditorService {
constructor( constructor(
protected api: ApiService, protected api: ApiService,
) { ) { }
console.log('editor service', this);
}
async startEditing(pageId: string) { async startEditing(pageId: string) {
if ( this.currentPage ) { if ( this.currentPage ) {
@ -58,8 +91,6 @@ export class EditorService {
this.currentPage = await this.loadPage(pageId); this.currentPage = await this.loadPage(pageId);
this.currentNodes = await this.loadNodes(pageId); this.currentNodes = await this.loadNodes(pageId);
await this.ready$.next(true); await this.ready$.next(true);
console.log('editing', this.currentPage);
console.log('nodes', this.currentNodes);
} }
async stopEditing() { async stopEditing() {
@ -72,10 +103,11 @@ export class EditorService {
} }
async save() { async save() {
if ( !(await this.needsSave()) ) { if ( !(await this.needsSave()) || this.saving ) {
return; return;
} }
this.saving = true;
const editors = Object.values(this.nodeIdToEditorContract); const editors = Object.values(this.nodeIdToEditorContract);
// Save all editors that handle their data independently first // Save all editors that handle their data independently first
@ -92,6 +124,7 @@ export class EditorService {
await this.saveNodesAsPage(this.currentPage, this.currentNodes); await this.saveNodesAsPage(this.currentPage, this.currentNodes);
this.dirtyOverride = false; this.dirtyOverride = false;
this.saving = false;
} }
async moveNode(node: HostRecord, direction: 'up' | 'down') { async moveNode(node: HostRecord, direction: 'up' | 'down') {
@ -117,6 +150,7 @@ export class EditorService {
} }
this.dirtyOverride = true; this.dirtyOverride = true;
this.triggerSave();
} }
async saveNodesAsPage(page: PageRecord, nodes: HostRecord[]): Promise<HostRecord[]> { async saveNodesAsPage(page: PageRecord, nodes: HostRecord[]): Promise<HostRecord[]> {
@ -184,6 +218,7 @@ export class EditorService {
this.currentNodes = this.currentNodes.filter(x => x.UUID !== nodeId); this.currentNodes = this.currentNodes.filter(x => x.UUID !== nodeId);
this.dirtyOverride = true; this.dirtyOverride = true;
this.triggerSave();
} }
async addNode(type: 'paragraph' | 'code_ref' | 'database_ref' | 'file_ref', position?: 'before' | 'after', positionNodeId?: string) { 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.dirtyOverride = true;
this.triggerSave();
return host; return host;
} }