Ask before the filter editors lose your changes
Both editors on the Filters & rules page kept their edits in component state, so every way out of the page threw them away without a word: a settings link, the app rail, even the Rules/Scripts switch. The only sign there had been anything to lose was a Save button that a screenful of rules had already pushed below the fold. Editors now register what they have pending, and every in-app navigation asks first -- offering to save, rather than making "leave without saving" the easy answer and saving the one you have to back out and find. Wouter routes links, redirects and navigate() through one place, so the guard holds for the app rail and the settings nav without either knowing an editor exists. The Rules/Scripts switch asks for itself, since it never reaches the router. Reload and tab close get the browser's own prompt. The save bar is pinned to the foot of the pane, so "Unsaved changes" is on screen whether or not the rules fit in the window. Two things fixed on the way past, both in the raw script editor: saving cleared only the selection, which left the editor open with the name unlocked so a second save created a duplicate script instead of updating the one just written; and the pair of identical nested conditions that decided whether the editor was open at all is now the one question it was asking twice. Fixes #175
This commit is contained in:
+25
-3
@@ -1,5 +1,5 @@
|
||||
import { Fragment, lazy, Suspense, useEffect, useState } from "react";
|
||||
import { Route, Switch, Redirect, useLocation } from "wouter";
|
||||
import { Route, Switch, Redirect, useLocation, Router } from "wouter";
|
||||
import { useSession } from "@/store/session";
|
||||
import { useMail } from "@/store/mail";
|
||||
import { scheduleSupported, useScheduled } from "@/store/scheduled";
|
||||
@@ -21,6 +21,7 @@ import { PAINTED_FROM_CACHE, useSettings, syncedPart } from "@/store/settings";
|
||||
import { armSettingsSync, loadRemoteSettings, queueSettingsPush, settingsAlreadyLoadedFor, settingsSyncAvailable } from "@/lib/settingsSync";
|
||||
import { listenForVerification, renewWebPush } from "@/lib/webpushEnable";
|
||||
import { useLanguageVersion, whenLanguageReady } from "@/lib/i18n";
|
||||
import { confirmLeaveUnsaved, hasUnsavedChanges } from "@/lib/unsavedChanges";
|
||||
|
||||
const ContactsView = lazy(() => import("@/views/contacts/ContactsView").then((m) => ({ default: m.ContactsView })));
|
||||
const CalendarView = lazy(() => import("@/views/calendar/CalendarView").then((m) => ({ default: m.CalendarView })));
|
||||
@@ -70,11 +71,32 @@ export function App() {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
/*
|
||||
* Every in-app navigation runs through `aroundNav` -- links, redirects and
|
||||
* `navigate()` alike, since wouter routes them all through the same place.
|
||||
* That is what makes the guard hold for the app rail and the settings nav
|
||||
* without either of them knowing an editor exists.
|
||||
*
|
||||
* The back button is the gap: by the time `popstate` arrives the history
|
||||
* has already moved, and the only way to hold the page would be to push an
|
||||
* entry back, which breaks the button for everyone who has nothing pending.
|
||||
* Reload and tab close are covered by `beforeunload` instead.
|
||||
*/
|
||||
<Router
|
||||
aroundNav={(navigate, to, options) => {
|
||||
if (!hasUnsavedChanges()) {
|
||||
navigate(to, options);
|
||||
return;
|
||||
}
|
||||
void confirmLeaveUnsaved().then((ok) => {
|
||||
if (ok) navigate(to, options);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Fragment key={languageVersion}>{status === "anonymous" ? <LoginPage /> : <AuthedApp />}</Fragment>
|
||||
<ToastHost />
|
||||
<ConfirmHost />
|
||||
</>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user