| 1 | import { create } from "zustand"; |
| 2 | import type { Drop } from "@/lib/types"; |
| 3 | import type { StreamStatus } from "@/lib/sse"; |
| 4 | |
| 5 | const BUFFER_CAP = 200; |
| 6 | |
| 7 | interface UiState { |
| 8 | sidebarCollapsed: boolean; |
| 9 | toggleSidebar: () => void; |
| 10 | setSidebar: (v: boolean) => void; |
| 11 | |
| 12 | commandOpen: boolean; |
| 13 | setCommandOpen: (v: boolean) => void; |
| 14 | |
| 15 | streamStatus: StreamStatus; |
| 16 | setStreamStatus: (s: StreamStatus) => void; |
| 17 | |
| 18 | liveDrops: Drop[]; |
| 19 | freshIds: Set<number>; |
| 20 | pushDrop: (drop: Drop) => void; |
| 21 | seedDrops: (drops: Drop[]) => void; |
| 22 | clearFresh: (id: number) => void; |
| 23 | } |
| 24 | |
| 25 | export const useUiStore = create<UiState>((set) => ({ |
| 26 | sidebarCollapsed: false, |
| 27 | toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), |
| 28 | setSidebar: (v) => set({ sidebarCollapsed: v }), |
| 29 | |
| 30 | commandOpen: false, |
| 31 | setCommandOpen: (v) => set({ commandOpen: v }), |
| 32 | |
| 33 | streamStatus: "connecting", |
| 34 | setStreamStatus: (s) => set({ streamStatus: s }), |
| 35 | |
| 36 | liveDrops: [], |
| 37 | freshIds: new Set<number>(), |
| 38 | pushDrop: (drop) => |
| 39 | set((s) => { |
| 40 | if (s.liveDrops.some((d) => d.id === drop.id)) return s; |
| 41 | const next = [drop, ...s.liveDrops].slice(0, BUFFER_CAP); |
| 42 | const fresh = new Set(s.freshIds); |
| 43 | fresh.add(drop.id); |
| 44 | return { liveDrops: next, freshIds: fresh }; |
| 45 | }), |
| 46 | seedDrops: (drops) => |
| 47 | set(() => ({ liveDrops: drops.slice(0, BUFFER_CAP), freshIds: new Set<number>() })), |
| 48 | clearFresh: (id) => |
| 49 | set((s) => { |
| 50 | if (!s.freshIds.has(id)) return s; |
| 51 | const fresh = new Set(s.freshIds); |
| 52 | fresh.delete(id); |
| 53 | return { freshIds: fresh }; |
| 54 | }), |
| 55 | })); |