From b324775f655a1a2cdfaeb0c53c9e2ce94590bb98 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Mon, 20 Jul 2026 14:29:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A8=98=E4=BD=8F=E4=B8=BB=E9=A1=8C/=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F/=E9=9D=A2=E6=9D=BF=E5=81=8F=E5=A5=BD,=E9=87=8D?= =?UTF-8?q?=E9=96=8B=E9=A0=81=E9=9D=A2=E8=87=AA=E5=8B=95=E9=82=84=E5=8E=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 摘要: 深色模式、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 --- src/App.tsx | 5 +++-- src/store/office-store.ts | 45 ++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b9083ca..37c1ebd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 ( diff --git a/src/store/office-store.ts b/src/store/office-store.ts index ee5075a..648fb83 100644 --- a/src/store/office-store.ts +++ b/src/store/office-store.ts @@ -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()((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 }),