/* ============================================================ SELECT PAGE — Universal full-screen option picker Design: iOS Settings style selection list Usage: Navigate to /select with state object State params: title - Page title shown in header options - Array of option strings selected - Currently selected index (number) back - Route to navigate back to after selection key - Identifier for which setting this controls ============================================================ */ import { useLocation } from "wouter"; import { ChevronLeft, Check } from "lucide-react"; import { useDevice } from "@/contexts/DeviceContext"; import { cn } from "@/lib/utils"; import { useEffect, useMemo, useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import localeZh from "@/locales/data-zh.json"; import localeEn from "@/locales/data-en.json"; import { resolveLocalePack } from "@/locales/resolveLocale"; import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection"; type DacVolumeDirectConfirmLocale = NonNullable< NonNullable<(typeof localeZh)["dac"]>["dacVolumeDirectConfirm"] >; const iisModeImageUrl = (index: number) => `${import.meta.env.BASE_URL}images/IIS/iis_app${index + 1}.png`; /* ── Key → API field mapping ── */ const KEY_TO_SETTING: Record = { effect_value: "effect_value", crossfeed_value: "crossfeed_value", hearing_select: "hearing_select", language: "language", analogGain: "analogGain", soundStep: "soundStep", bootSound: "bootSound", filterCharacteristic: "pcm", mutePolar: "hdmimutepolar", IISMode: "hdmiType", xlr: "xlr", dacGain: "dacGain", dacImpedance: "dacImpedance", dacVolumeDirect: "dacVolumeDirect", uacVer: "uac_ver", vu: "vu", input: "input", output: "output", screenLight: "screenLight", screenOff: "screenOff", buttonLight: "buttonLight", autoHome: "autoHome", sleep: "sleep", headphone: "peqSelect", }; // Global state for select page (to avoid URL params) export let selectPageState: { title: string; options: string[]; selected: number; back: string; key: string; } | null = null; // Last select result for pages that need local state update export let selectPageResult: { key: string; selected: number } | null = null; export function consumeSelectPageResult() { const result = selectPageResult; selectPageResult = null; return result; } // Scroll position of the calling page, captured before navigating to /select. // The select page is short, so the browser clamps window scroll to 0 while on // it; without restoring, the calling page (e.g. Effects) reopens at the top. let selectReturnScroll: number | null = null; /** Consume the stored scroll position (once). Returns null when absent. */ export function consumeSelectReturnScroll(): number | null { const y = selectReturnScroll; selectReturnScroll = null; return y; } // Helper function to set select page state export function navigateToSelect(title: string, options: string[], selected: number, back: string, key: string) { selectReturnScroll = window.scrollY; selectPageState = { title, options, selected, back, key }; } export default function SelectPage() { const [location, setLocation] = useLocation(); const { updateSetting, deviceState } = useDevice(); const [state, setState] = useState<{ title: string; options: string[]; selected: number; back: string; key: string; } | null>(() => selectPageState); const [saving, setSaving] = useState(false); const [volumeDirectConfirmIdx, setVolumeDirectConfirmIdx] = useState<1 | 2 | null>(null); const pageTitle = usePageTitleWithConnection(state?.title ?? ""); const volumeDirectConfirmText = useMemo((): DacVolumeDirectConfirmLocale => { const pack = resolveLocalePack(deviceState?.language); return (pack.dac?.dacVolumeDirectConfirm ?? localeEn.dac!.dacVolumeDirectConfirm) as DacVolumeDirectConfirmLocale; }, [deviceState?.language]); // Re-read global state whenever we enter /select (avoids stale key/options after remount) useEffect(() => { if (location === "/select" && selectPageState) { setState({ ...selectPageState }); } }, [location]); if (!state) { return null; } const { title, options, selected: selectedIdx, back, key } = state; const commitSelect = async (idx: number) => { if (saving) return; selectPageResult = { key, selected: idx }; const apiField = KEY_TO_SETTING[key]; if (apiField) { setSaving(true); try { await updateSetting({ [apiField]: idx }); } finally { setSaving(false); } } selectPageState = null; setLocation(back); }; const handleSelect = async (idx: number) => { if ( key === "dacVolumeDirect" && idx !== selectedIdx && (idx === 1 || idx === 2) ) { setVolumeDirectConfirmIdx(idx); return; } await commitSelect(idx); }; const volumeDirectDialogCopy = volumeDirectConfirmIdx === 1 ? { title: volumeDirectConfirmText.title0db ?? "开启 0dB 直通模式?", desc: volumeDirectConfirmText.desc0db ?? "直通模式会将音量调到最大,并且不可调节。", } : volumeDirectConfirmIdx === 2 ? { title: volumeDirectConfirmText.title12db ?? "开启 -12dB 直通模式?", desc: volumeDirectConfirmText.desc12db ?? "直通模式会将音量调到 -12dB,并且不可调节。", } : null; return (
{/* ── Header ── */}

{pageTitle}

{key === "IISMode" ? (
{options.map((opt, idx) => { const isSelected = idx === selectedIdx; return ( ); })}
) : (
{options.map((opt, idx) => { const isSelected = idx === selectedIdx; return ( ); })}
)}
{ if (!open) setVolumeDirectConfirmIdx(null); }} > {volumeDirectDialogCopy?.title} {volumeDirectDialogCopy?.desc} {volumeDirectConfirmText.cancel ?? "取消"} { const idx = volumeDirectConfirmIdx; setVolumeDirectConfirmIdx(null); if (idx !== null) void commitSelect(idx); }} > {volumeDirectConfirmText.confirm ?? "确认"}
); }