refactor(frontend): adopt store/storage helpers; centralize persistence
- errorDialog + hints now use createStore (drop their bespoke listener arrays). - theme/sidebarLayout/notifications/App filters/Playlists plSort use readMerged/ readJSON/writeJSON instead of inline try/JSON.parse/catch. - Stats, SettingsPanel and App's channel filter/view tabs use usePersistedState (the 3 sites that reinvented usePersistedTab inline). - Every siftlode.* key now sourced from the LS registry (no scattered literals).
This commit is contained in:
parent
634921b35a
commit
472aba6480
15 changed files with 80 additions and 147 deletions
|
|
@ -19,6 +19,7 @@ import {
|
|||
} from "./lib/sidebarLayout";
|
||||
import { configureNotifications, getNotifSettings, notify, removeByMetaKind, type NotifSettings } from "./lib/notifications";
|
||||
import { hintsEnabled, setHintsEnabled } from "./lib/hints";
|
||||
import { LS, readMerged, usePersistedState } from "./lib/storage";
|
||||
import { useConfirm } from "./components/ConfirmProvider";
|
||||
import type { PrefsController } from "./components/SettingsPanel";
|
||||
import Welcome from "./components/Welcome";
|
||||
|
|
@ -59,9 +60,9 @@ const DEFAULT_FILTERS: FeedFilters = {
|
|||
show: "unwatched",
|
||||
};
|
||||
|
||||
const FILTERS_KEY = "siftlode.filters";
|
||||
const PAGE_KEY = "siftlode.page";
|
||||
const PERF_KEY = "siftlode.perfMode";
|
||||
const FILTERS_KEY = LS.filters;
|
||||
const PAGE_KEY = LS.page;
|
||||
const PERF_KEY = LS.perfMode;
|
||||
|
||||
// The preferences edited on the Settings page. They apply locally for instant preview but
|
||||
// persist to the server only on an explicit Save — so App holds both the live "draft" (the
|
||||
|
|
@ -84,11 +85,7 @@ function loadInitialPage(): Page {
|
|||
}
|
||||
|
||||
function loadStoredFilters(): FeedFilters {
|
||||
try {
|
||||
return { ...DEFAULT_FILTERS, ...JSON.parse(localStorage.getItem(FILTERS_KEY) || "{}") };
|
||||
} catch {
|
||||
return DEFAULT_FILTERS;
|
||||
}
|
||||
return readMerged(FILTERS_KEY, DEFAULT_FILTERS);
|
||||
}
|
||||
|
||||
// URL wins over localStorage so a pasted link reproduces the exact view.
|
||||
|
|
@ -126,18 +123,13 @@ export default function App() {
|
|||
const [sidebarLayout, setSidebarLayoutState] = useState<SidebarLayout>(loadLayout);
|
||||
const [page, setPageState] = useState<Page>(loadInitialPage);
|
||||
const [wizardOpen, setWizardOpen] = useState(false);
|
||||
const CHANNEL_FILTER_KEY = "siftlode.channelFilter";
|
||||
const [channelFilter, setChannelFilterState] = useState<ChannelStatusFilter>(() => {
|
||||
const v = localStorage.getItem(CHANNEL_FILTER_KEY);
|
||||
return v === "needs_full" || v === "fully_synced" || v === "hidden" || v === "all"
|
||||
? v
|
||||
: "all";
|
||||
});
|
||||
// Persist the channel status chip so a reload (F5) keeps it.
|
||||
const setChannelFilter = (f: ChannelStatusFilter) => {
|
||||
setChannelFilterState(f);
|
||||
localStorage.setItem(CHANNEL_FILTER_KEY, f);
|
||||
};
|
||||
// Channel status chip, persisted so a reload (F5) keeps it; clamp a stale value at render.
|
||||
const [channelFilterRaw, setChannelFilter] = usePersistedState(LS.channelFilter, "all");
|
||||
const channelFilter: ChannelStatusFilter = (
|
||||
["needs_full", "fully_synced", "hidden", "all"] as const
|
||||
).includes(channelFilterRaw as ChannelStatusFilter)
|
||||
? (channelFilterRaw as ChannelStatusFilter)
|
||||
: "all";
|
||||
const [aboutOpen, setAboutOpen] = useState(false);
|
||||
const [notesOpen, setNotesOpen] = useState(false);
|
||||
const [notesHighlight, setNotesHighlight] = useState<string | undefined>(undefined);
|
||||
|
|
@ -145,14 +137,9 @@ export default function App() {
|
|||
// persisted so navigation intents that target the subscriptions table — the header's
|
||||
// "without full history" link, focus-channel — can force it back to "subscribed" instead
|
||||
// of dumping the user on whatever tab they last left open.
|
||||
const CHANNELS_VIEW_KEY = "siftlode.channelsView";
|
||||
const [channelsView, setChannelsViewState] = useState<ChannelsView>(() =>
|
||||
localStorage.getItem(CHANNELS_VIEW_KEY) === "discovery" ? "discovery" : "subscribed"
|
||||
);
|
||||
const setChannelsView = (v: ChannelsView) => {
|
||||
setChannelsViewState(v);
|
||||
localStorage.setItem(CHANNELS_VIEW_KEY, v);
|
||||
};
|
||||
const [channelsViewRaw, setChannelsView] = usePersistedState(LS.channelsView, "subscribed");
|
||||
const channelsView: ChannelsView =
|
||||
channelsViewRaw === "discovery" ? "discovery" : "subscribed";
|
||||
// Bumped to tell the channel manager to drop a stale column filter when we send the user
|
||||
// there to see a specific set (the header's "without full history" link).
|
||||
const [channelsFilterReset, setChannelsFilterReset] = useState(0);
|
||||
|
|
@ -318,7 +305,7 @@ export default function App() {
|
|||
if (result === "ok") {
|
||||
notify({ level: "success", message: t("settings.account.googleLink.linked") });
|
||||
void meQuery.refetch();
|
||||
localStorage.setItem("siftlode.settingsTab", "account");
|
||||
localStorage.setItem(LS.settingsTab, "account");
|
||||
setPage("settings");
|
||||
} else {
|
||||
const key = result === "conflict" || result === "mismatch" ? result : "error";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue