feat(feed): pick the feed's view from its own toolbar
The view mode moves out of Settings into a switcher next to sort, on the feed and channel pages alike. It leaves the Settings prefs draft for its own eager save: a toolbar click must not mark that page dirty and trip its leave-page guard. lib/feedView.ts owns the vocabulary and maps the old grid/list prefs forward on read, so nobody's saved choice is lost.
This commit is contained in:
+19
-11
@@ -30,6 +30,7 @@ import {
|
||||
} from "./lib/panelLayout";
|
||||
import { configureNotifications, getNotifSettings, notify, removeByMetaKind, type NotifSettings } from "./lib/notifications";
|
||||
import { hintsEnabled, setHintsEnabled } from "./lib/hints";
|
||||
import { parseFeedView, type FeedView } from "./lib/feedView";
|
||||
import {
|
||||
accountKey,
|
||||
getAccountRaw,
|
||||
@@ -108,7 +109,6 @@ const PERF_KEY = LS.perfMode;
|
||||
// individual states below) and the last-saved "baseline" to compute dirty / revert on discard.
|
||||
type EditablePrefs = {
|
||||
theme: ThemePrefs;
|
||||
view: "grid" | "list";
|
||||
performanceMode: boolean;
|
||||
hints: boolean;
|
||||
notifications: NotifSettings;
|
||||
@@ -170,14 +170,16 @@ export default function App() {
|
||||
const [initialHadUrlFilters] = useState(() =>
|
||||
hasFilterParams(new URLSearchParams(window.location.search))
|
||||
);
|
||||
const [view, setView] = useState<"grid" | "list">("grid");
|
||||
// The feed's view mode is picked from the feed's own toolbar, so — unlike the Settings prefs
|
||||
// below — it persists the moment you choose it. It is NOT part of the EditablePrefs draft:
|
||||
// that would mark the Settings page dirty from a toolbar click and trip its leave-page guard.
|
||||
const [view, setViewState] = useState<FeedView>(() => parseFeedView(readAccount(LS.feedView, null)));
|
||||
// Settings-page prefs draft (apply live, persist on Save — see EditablePrefs).
|
||||
const [perf, setPerf] = useState(() => getAccountRaw(PERF_KEY) === "1");
|
||||
const [hints, setHints] = useState(() => hintsEnabled());
|
||||
const [notif, setNotif] = useState<NotifSettings>(() => getNotifSettings());
|
||||
const [savedPrefs, setSavedPrefs] = useState<EditablePrefs>(() => ({
|
||||
theme: loadLocalTheme(),
|
||||
view: "grid",
|
||||
performanceMode: getAccountRaw(PERF_KEY) === "1",
|
||||
hints: hintsEnabled(),
|
||||
notifications: getNotifSettings(),
|
||||
@@ -387,6 +389,11 @@ export default function App() {
|
||||
writeAccount(LS.navCollapsed, next);
|
||||
api.savePrefs({ navCollapsed: next }).catch(() => {});
|
||||
}
|
||||
function setView(next: FeedView) {
|
||||
setViewState(next);
|
||||
writeAccount(LS.feedView, next);
|
||||
api.savePrefs({ view: next }).catch(() => {});
|
||||
}
|
||||
function setFilterCollapsed(next: boolean) {
|
||||
setFilterCollapsedState(next);
|
||||
writeAccount(LS.filterCollapsed, next);
|
||||
@@ -603,7 +610,6 @@ export default function App() {
|
||||
// effects above apply the draft (theme/perf/hints/notifications) for display.
|
||||
const adopted: EditablePrefs = {
|
||||
theme: prefs.theme ? { ...DEFAULT_THEME, ...prefs.theme } : loadLocalTheme(),
|
||||
view: prefs.view === "grid" || prefs.view === "list" ? prefs.view : "grid",
|
||||
performanceMode:
|
||||
typeof prefs.performanceMode === "boolean"
|
||||
? prefs.performanceMode
|
||||
@@ -615,13 +621,16 @@ export default function App() {
|
||||
};
|
||||
setThemeState(adopted.theme);
|
||||
saveLocalTheme(adopted.theme);
|
||||
setView(adopted.view);
|
||||
setPerf(adopted.performanceMode);
|
||||
setHints(adopted.hints);
|
||||
setNotif(adopted.notifications);
|
||||
setSavedPrefs(adopted);
|
||||
// Out-of-scope prefs stay instant (edited outside the Settings page).
|
||||
const prefsRec = prefs as Record<string, unknown>;
|
||||
// parseFeedView also maps the pre-0.45 "grid" / "list" values forward.
|
||||
const adoptedView = parseFeedView(prefs.view);
|
||||
setViewState(adoptedView);
|
||||
writeAccount(LS.feedView, adoptedView);
|
||||
(["feed", "plex", "playlists"] as PanelId[]).forEach((p) => {
|
||||
const raw = prefsRec[PREF_KEY[p]];
|
||||
if (raw) {
|
||||
@@ -689,11 +698,11 @@ export default function App() {
|
||||
}
|
||||
|
||||
const prefsDirty =
|
||||
JSON.stringify({ theme, view, performanceMode: perf, hints, notifications: notif }) !==
|
||||
JSON.stringify({ theme, performanceMode: perf, hints, notifications: notif }) !==
|
||||
JSON.stringify(savedPrefs);
|
||||
|
||||
const savePrefs = useCallback(() => {
|
||||
const payload: EditablePrefs = { theme, view, performanceMode: perf, hints, notifications: notif };
|
||||
const payload: EditablePrefs = { theme, performanceMode: perf, hints, notifications: notif };
|
||||
window.clearTimeout(saveMsgTimer.current);
|
||||
setPrefsSaveState("saving");
|
||||
api
|
||||
@@ -709,12 +718,11 @@ export default function App() {
|
||||
setPrefsSaveState("error");
|
||||
saveMsgTimer.current = window.setTimeout(() => setPrefsSaveState("idle"), 4000);
|
||||
});
|
||||
}, [theme, view, perf, hints, notif]);
|
||||
}, [theme, perf, hints, notif]);
|
||||
|
||||
const discardPrefs = useCallback(() => {
|
||||
setThemeState(savedPrefs.theme);
|
||||
saveLocalTheme(savedPrefs.theme);
|
||||
setView(savedPrefs.view);
|
||||
setPerf(savedPrefs.performanceMode);
|
||||
setHints(savedPrefs.hints);
|
||||
setNotif(savedPrefs.notifications);
|
||||
@@ -725,8 +733,6 @@ export default function App() {
|
||||
const prefsCtl: PrefsController = {
|
||||
theme,
|
||||
setTheme,
|
||||
view,
|
||||
setView,
|
||||
perf,
|
||||
setPerf,
|
||||
hints,
|
||||
@@ -842,6 +848,7 @@ export default function App() {
|
||||
initialName={channelView.name}
|
||||
me={meQuery.data!}
|
||||
view={view}
|
||||
setView={setView}
|
||||
onBack={closeChannel}
|
||||
onOpenChannel={openChannel}
|
||||
onShowInFeed={() => {
|
||||
@@ -962,6 +969,7 @@ export default function App() {
|
||||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
view={view}
|
||||
setView={setView}
|
||||
canRead={meQuery.data!.can_read}
|
||||
isDemo={meQuery.data!.is_demo}
|
||||
onOpenWizard={() => setWizardOpen(true)}
|
||||
|
||||
Reference in New Issue
Block a user