93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useState,
|
|
type Dispatch,
|
|
type ReactNode,
|
|
type SetStateAction,
|
|
} from "react";
|
|
|
|
const SPLIT_STORAGE_KEY = "luxsin-ai-split-pct";
|
|
|
|
/** sessionStorage: left-pane route before jumping from split to full-screen `#/ai` (desktop). */
|
|
export const AI_SPLIT_RESTORE_PATH_KEY = "luxsin_ai_restore_path";
|
|
|
|
export function readAndClearAiSplitRestorePath(): string {
|
|
try {
|
|
const s = sessionStorage.getItem(AI_SPLIT_RESTORE_PATH_KEY);
|
|
sessionStorage.removeItem(AI_SPLIT_RESTORE_PATH_KEY);
|
|
if (s && typeof s === "string" && s.startsWith("/") && !s.includes("..")) return s;
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return "/";
|
|
}
|
|
const SPLIT_MIN = 28;
|
|
const SPLIT_MAX = 72;
|
|
|
|
function clampPct(n: number) {
|
|
return Math.min(SPLIT_MAX, Math.max(SPLIT_MIN, Math.round(n)));
|
|
}
|
|
|
|
function loadSplitPct(): number {
|
|
if (typeof window === "undefined") return 50;
|
|
try {
|
|
const v = sessionStorage.getItem(SPLIT_STORAGE_KEY);
|
|
if (v) {
|
|
const n = parseInt(v, 10);
|
|
if (!Number.isNaN(n)) return clampPct(n);
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return 50;
|
|
}
|
|
|
|
type AIDrawerContextValue = {
|
|
/** Desktop split panel visible (only used when lg+ and route !== /ai) */
|
|
open: boolean;
|
|
setOpen: Dispatch<SetStateAction<boolean>>;
|
|
toggle: () => void;
|
|
/** Width share of the AI column (percent), used when split view is shown */
|
|
splitPercent: number;
|
|
setSplitPercent: (pct: number) => void;
|
|
};
|
|
|
|
const AIDrawerContext = createContext<AIDrawerContextValue | null>(null);
|
|
|
|
export function AIDrawerProvider({ children }: { children: ReactNode }) {
|
|
const [open, setOpen] = useState(false);
|
|
const [splitPercent, setSplitPercentState] = useState(loadSplitPct);
|
|
|
|
const toggle = useCallback(() => setOpen((o) => !o), []);
|
|
|
|
const setSplitPercent = useCallback((pct: number) => {
|
|
const next = clampPct(pct);
|
|
setSplitPercentState(next);
|
|
try {
|
|
sessionStorage.setItem(SPLIT_STORAGE_KEY, String(next));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}, []);
|
|
|
|
const value = useMemo(
|
|
() => ({ open, setOpen, toggle, splitPercent, setSplitPercent }),
|
|
[open, splitPercent, setSplitPercent, toggle],
|
|
);
|
|
|
|
return (
|
|
<AIDrawerContext.Provider value={value}>{children}</AIDrawerContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAIDrawer() {
|
|
const ctx = useContext(AIDrawerContext);
|
|
if (!ctx) {
|
|
throw new Error("useAIDrawer must be used within AIDrawerProvider");
|
|
}
|
|
return ctx;
|
|
}
|