From 78e48095e6b41c67e44dfc919e9bfc70a79a6e23 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 27 Feb 2021 23:39:02 +0100 Subject: [PATCH] prompt user when there are unsaved changes, #1692 --- src/public/app/services/app_context.js | 35 +++++++++++++++++++++++- src/public/app/services/spaced_update.js | 20 ++++++++++++-- src/public/app/services/tab_manager.js | 4 +++ src/public/app/widgets/note_detail.js | 8 +++--- src/public/app/widgets/note_title.js | 5 +++- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/public/app/services/app_context.js b/src/public/app/services/app_context.js index f2190c537..f1db21f6e 100644 --- a/src/public/app/services/app_context.js +++ b/src/public/app/services/app_context.js @@ -12,6 +12,7 @@ import keyboardActionsService from "./keyboard_actions.js"; import MobileScreenSwitcherExecutor from "../widgets/mobile_widgets/mobile_screen_switcher.js"; import MainTreeExecutors from "./main_tree_executors.js"; import protectedSessionHolder from "./protected_session_holder.js"; +import toast from "./toast.js"; class AppContext extends Component { constructor(isMainWindow) { @@ -19,6 +20,7 @@ class AppContext extends Component { this.isMainWindow = isMainWindow; this.executors = []; + this.beforeUnloadListeners = []; } setLayout(layout) { @@ -104,6 +106,15 @@ class AppContext extends Component { getComponentByEl(el) { return $(el).closest(".component").prop('component'); } + + addBeforeUnloadListener(obj) { + if (typeof WeakRef !== "function") { + // older browsers don't support WeakRef + return; + } + + this.beforeUnloadListeners.push(new WeakRef(obj)); + } } const appContext = new AppContext(window.glob.isMainWindow); @@ -112,7 +123,29 @@ const appContext = new AppContext(window.glob.isMainWindow); $(window).on('beforeunload', () => { protectedSessionHolder.resetSessionCookie(); - appContext.triggerEvent('beforeUnload'); + let allSaved = true; + + appContext.beforeUnloadListeners = appContext.beforeUnloadListeners.filter(wr => !!wr.deref()); + + for (const weakRef of appContext.beforeUnloadListeners) { + const component = weakRef.deref(); + + if (!component) { + continue; + } + + if (!component.beforeUnloadEvent()) { + console.log(`Component ${component.componentId} is not finished saving its state.`); + + toast.showMessage("Please wait for a couple of seconds for the save to finish, then you can try again.", 10000); + + allSaved = false; + } + } + + if (!allSaved) { + return "some string"; + } }); function isNotePathInAddress() { diff --git a/src/public/app/services/spaced_update.js b/src/public/app/services/spaced_update.js index e12aef32c..bf61550b2 100644 --- a/src/public/app/services/spaced_update.js +++ b/src/public/app/services/spaced_update.js @@ -15,11 +15,27 @@ export default class SpacedUpdate { async updateNowIfNecessary() { if (this.changed) { - this.changed = false; - await this.updater(); + this.changed = false; // optimistic...k + + try { + await this.updater(); + } + catch (e) { + this.changed = true; + + throw e; + } } } + isAllSavedAndTriggerUpdate() { + const allSaved = !this.changed; + + this.updateNowIfNecessary(); + + return allSaved; + } + triggerUpdate() { if (!this.changed) { return; diff --git a/src/public/app/services/tab_manager.js b/src/public/app/services/tab_manager.js index 5ff30bc39..7aa6fe2ec 100644 --- a/src/public/app/services/tab_manager.js +++ b/src/public/app/services/tab_manager.js @@ -27,6 +27,8 @@ export default class TabManager extends Component { openTabs: JSON.stringify(openTabs) }); }); + + appContext.addBeforeUnloadListener(this); } /** @type {TabContext[]} */ @@ -329,6 +331,8 @@ export default class TabManager extends Component { beforeUnloadEvent() { this.tabsUpdate.updateNowIfNecessary(); + + return true; // don't block closing the tab, this metadata is not that important } openNewTabCommand() { diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 665cd4ce5..42f9877a5 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -54,8 +54,6 @@ export default class NoteDetailWidget extends TabAwareWidget { this.typeWidgets = {}; - const updateInterval = utils.isDesktop() ? 1000 : 5000; - this.spacedUpdate = new SpacedUpdate(async () => { const {note} = this.tabContext; const {noteId} = note; @@ -66,7 +64,9 @@ export default class NoteDetailWidget extends TabAwareWidget { protectedSessionHolder.touchProtectedSessionIfNecessary(note); await server.put('notes/' + noteId, dto, this.componentId); - }, updateInterval); + }); + + appContext.addBeforeUnloadListener(this); } isEnabled() { @@ -295,7 +295,7 @@ export default class NoteDetailWidget extends TabAwareWidget { } beforeUnloadEvent() { - this.spacedUpdate.updateNowIfNecessary(); + return this.spacedUpdate.isAllSavedAndTriggerUpdate(); } textPreviewDisabledEvent({tabContext}) { diff --git a/src/public/app/widgets/note_title.js b/src/public/app/widgets/note_title.js index 23f031fc9..e3f6d4ce8 100644 --- a/src/public/app/widgets/note_title.js +++ b/src/public/app/widgets/note_title.js @@ -3,6 +3,7 @@ import utils from "../services/utils.js"; import protectedSessionHolder from "../services/protected_session_holder.js"; import server from "../services/server.js"; import SpacedUpdate from "../services/spaced_update.js"; +import appContext from "../services/app_context.js"; const TPL = `
@@ -37,6 +38,8 @@ export default class NoteTitleWidget extends TabAwareWidget { await server.put(`notes/${this.noteId}/change-title`, {title}); }); + + appContext.addBeforeUnloadListener(this); } doRender() { @@ -101,6 +104,6 @@ export default class NoteTitleWidget extends TabAwareWidget { } beforeUnloadEvent() { - this.spacedUpdate.updateNowIfNecessary(); + return this.spacedUpdate.isAllSavedAndTriggerUpdate(); } }