記住主題/模式/面板偏好,重開頁面自動還原

摘要:
深色模式、LIVE 模式與面板收合狀態存入 localStorage,
頁面載入時自動套用,不需每次手動切換。

根本原因:
theme/mode/panelOpen 只存在 Zustand 記憶體 state,
重新整理即重置為淺色 + 模擬模式。

影響:
使用者偏好跨重整保留;LIVE 偏好在開機時由 runtime.setMode 套用,
確保 SSE 連線流程正確執行。無痕模式等 localStorage 不可用時安全降級。

修法:
- office-store:loadPref/savePref 包裝(try/catch),
  theme/panelOpen 初始值讀取,setTheme/setMode/togglePanel 寫入
- App:啟動時 loadPreferredMode() === "live" 則呼叫 setMode("live")

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 14:29:38 +08:00
co-authored by Claude Fable 5
parent a8cb82cd6f
commit b324775f65
2 changed files with 43 additions and 7 deletions
+3 -2
View File
@@ -2,8 +2,8 @@ import { useEffect } from "react";
import { FloorPlan } from "./components/FloorPlan";
import { HeaderBar } from "./components/HeaderBar";
import { SidePanel } from "./components/SidePanel";
import { startRuntime } from "./sim/runtime";
import { useOfficeStore } from "./store/office-store";
import { setMode, startRuntime } from "./sim/runtime";
import { loadPreferredMode, useOfficeStore } from "./store/office-store";
export default function App() {
const theme = useOfficeStore((s) => s.theme);
@@ -11,6 +11,7 @@ export default function App() {
useEffect(() => {
startRuntime();
if (loadPreferredMode() === "live") setMode("live");
}, []);
return (
+40 -5
View File
@@ -71,25 +71,60 @@ interface OfficeState {
let eventSeq = 0;
/* UI preferences persisted across reloads. */
function loadPref(key: string): string | null {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function savePref(key: string, value: string) {
try {
localStorage.setItem(key, value);
} catch {
/* ignore */
}
}
const savedTheme = loadPref("office-theme");
const savedPanel = loadPref("office-panel");
/** Read the preferred data source ("live" | "sim"); the runtime applies it on boot. */
export function loadPreferredMode(): "sim" | "live" {
return loadPref("office-mode") === "live" ? "live" : "sim";
}
export const useOfficeStore = create<OfficeState>()((set, get) => ({
agents: new Map(),
links: [],
meetings: [],
events: [],
selectedAgentId: null,
theme: "light",
theme: savedTheme === "dark" ? "dark" : "light",
paused: false,
speed: 1,
mode: "sim",
panelOpen: true,
panelOpen: savedPanel !== "0",
clock: 0,
selectAgent: (id) => set({ selectedAgentId: id }),
setTheme: (t) => set({ theme: t }),
setTheme: (t) => {
savePref("office-theme", t);
set({ theme: t });
},
setPaused: (p) => set({ paused: p }),
setSpeed: (s) => set({ speed: s }),
setMode: (m) => set({ mode: m }),
togglePanel: () => set((state) => ({ panelOpen: !state.panelOpen })),
setMode: (m) => {
savePref("office-mode", m);
set({ mode: m });
},
togglePanel: () =>
set((state) => {
savePref("office-panel", state.panelOpen ? "0" : "1");
return { panelOpen: !state.panelOpen };
}),
clearWorld: () =>
set({ agents: new Map(), links: [], meetings: [], events: [], selectedAgentId: null }),