import { useState, useEffect, useRef, useMemo, memo } from "react"; import { motion } from "framer-motion"; import { TauriService, Runner } from "../../services/TauriService"; import { usePlatform } from "../../hooks/usePlatform"; import { useUI, useConfig, useAudio, useGame } from "../../context/LauncherContext"; const SettingsView = memo(function SettingsView() { const { setActiveView } = useUI(); const { vfxEnabled, setVfxEnabled, animationsEnabled, setAnimationsEnabled, musicVol: musicVolume, setMusicVol: setMusicVolume, sfxVol: sfxVolume, setSfxVol: setSfxVolume, layout, setLayout, linuxRunner, setLinuxRunner, perfBoost, setPerfBoost, rpcEnabled, setRpcEnabled, legacyMode, setLegacyMode, keepLauncherOpen, setKeepLauncherOpen, enableTrayIcon, setEnableTrayIcon } = useConfig(); const { currentTrack, setCurrentTrack, tracks, playPressSound, playBackSound } = useAudio(); const { isGameRunning, stopGame, isRunnerDownloading, runnerDownloadProgress, downloadRunner } = useGame(); const { isLinux, isMac } = usePlatform(); const [focusIndex, setFocusIndex] = useState(null); const [currentSubMenu, setCurrentSubMenu] = useState<"main" | "audio" | "video" | "controls" | "launcher">("main"); const [runners, setRunners] = useState([]); const containerRef = useRef(null); const layouts = ["KBM", "PLAYSTATION", "XBOX"]; useEffect(() => { TauriService.getAvailableRunners().then(setRunners); }, [isRunnerDownloading]); const handleLayoutToggle = () => { playPressSound(); const currentIndex = layouts.indexOf(layout); const nextIndex = (currentIndex + 1) % layouts.length; setLayout(layouts[nextIndex]); }; const handleVfxToggle = () => { playPressSound(); setVfxEnabled(!vfxEnabled); }; const handleAnimationsToggle = () => { playPressSound(); setAnimationsEnabled(!animationsEnabled); }; const handlePerfToggle = () => { playPressSound(); setPerfBoost(!perfBoost); }; const handleRpcToggle = () => { playPressSound(); setRpcEnabled(!rpcEnabled); }; const handleLegacyToggle = () => { playPressSound(); setLegacyMode(!legacyMode); }; const handleKeepOpenToggle = () => { playPressSound(); setKeepLauncherOpen(!keepLauncherOpen); }; const handleTrayToggle = () => { playPressSound(); setEnableTrayIcon(!enableTrayIcon); }; const handleRunnerToggle = () => { playPressSound(); if (runners.length === 0) return; const currentIndex = runners.findIndex((r) => r.id === linuxRunner); const nextIndex = (currentIndex + 1) % runners.length; setLinuxRunner(runners[nextIndex].id); }; const handleTrackToggle = () => { playPressSound(); setCurrentTrack((currentTrack + 1) % tracks.length); }; const handleResetSetup = () => { playPressSound(); // Create styled confirmation dialog const dialog = document.createElement('div'); dialog.className = 'fixed inset-0 bg-black/80 flex items-center justify-center z-50'; dialog.innerHTML = `

Reset Setup

Are you sure you want to reset launcher setup?

`; document.body.appendChild(dialog); const handleYes = () => { document.body.removeChild(dialog); showSecondConfirmation(); }; const handleNo = () => { document.body.removeChild(dialog); }; dialog.querySelector('#reset-yes')?.addEventListener('click', handleYes); dialog.querySelector('#reset-no')?.addEventListener('click', handleNo); dialog.addEventListener('click', (e) => { if (e.target === dialog) { document.body.removeChild(dialog); } }); }; const showSecondConfirmation = () => { const dialog = document.createElement('div'); dialog.className = 'fixed inset-0 bg-black/80 flex items-center justify-center z-50'; dialog.innerHTML = `

CONFIRM RESET

⚠️ This will:

  • Clear all launcher settings
  • Reset your username
  • Show setup screen again
  • Require reconfiguration

This action cannot be undone!

`; document.body.appendChild(dialog); const handleFinalYes = () => { document.body.removeChild(dialog); performReset(); }; const handleFinalNo = () => { document.body.removeChild(dialog); }; dialog.querySelector('#reset-final-yes')?.addEventListener('click', handleFinalYes); dialog.querySelector('#reset-final-no')?.addEventListener('click', handleFinalNo); dialog.addEventListener('click', (e) => { if (e.target === dialog) { document.body.removeChild(dialog); } }); }; const performReset = () => { // Clear all localStorage data localStorage.clear(); // Set setup as not completed localStorage.setItem('lce-setup-completed', 'false'); // Force reload to show setup screen window.location.reload(); }; let trackName = "Unknown"; if (tracks && tracks.length > 0) { const fullPath = tracks[currentTrack]; if (fullPath) { trackName = fullPath .split("/") .pop() ?.replace(".ogg", "") .replace(".wav", "") || "Unknown"; } } const selectedRunnerName = runners.find((r) => r.id === linuxRunner)?.name || "Native / Default"; type SettingsItem = | { id: string; label: string; type: "slider"; value: number; onChange: (val: any) => void; } | { id: string; label: string; type: "button"; onClick: () => void; small?: boolean; color?: string; }; const settingsItems = useMemo(() => { const items: SettingsItem[] = []; if (currentSubMenu === "main") { items.push({ id: "audio_menu", label: "Audio", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("audio"); setFocusIndex(0); }, }); items.push({ id: "video_menu", label: "Video", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("video"); setFocusIndex(0); }, }); items.push({ id: "controls_menu", label: "Controls", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("controls"); setFocusIndex(0); }, }); items.push({ id: "launcher_menu", label: "Launcher", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("launcher"); setFocusIndex(0); }, }); } else if (currentSubMenu === "audio") { items.push({ id: "music", label: `Music: ${musicVolume ?? 50}%`, type: "slider", value: musicVolume ?? 50, onChange: setMusicVolume, }); items.push({ id: "sfx", label: `Sound: ${sfxVolume ?? 100}%`, type: "slider", value: sfxVolume ?? 100, onChange: setSfxVolume, }); items.push({ id: "track", label: `${trackName} - C418`, type: "button", onClick: handleTrackToggle, }); } else if (currentSubMenu === "video") { items.push({ id: "vfx", label: `VFX: ${vfxEnabled ? "ON" : "OFF"}`, type: "button", onClick: handleVfxToggle, }); items.push({ id: "animations", label: `Animations: ${animationsEnabled ? "ON" : "OFF"}`, type: "button", onClick: handleAnimationsToggle, }); if (isMac) { items.push({ id: "perf", label: `M1/M2 Boost: ${perfBoost ? "Enabled" : "Disabled"}`, type: "button", onClick: handlePerfToggle, }); } } else if (currentSubMenu === "controls") { items.push({ id: "layout", label: `Layout: ${layout}`, type: "button", onClick: handleLayoutToggle, }); } else if (currentSubMenu === "launcher") { items.push({ id: "rpc", label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`, type: "button", onClick: handleRpcToggle, }); items.push({ id: "legacy", label: `Legacy Mode: ${legacyMode ? "ON" : "OFF"}`, type: "button", onClick: handleLegacyToggle, }); items.push({ id: "keep_open", label: `Keep Launcher Open: ${keepLauncherOpen ? "ON" : "OFF"}`, type: "button", onClick: handleKeepOpenToggle, }); items.push({ id: "tray_icon", label: `Tray Icon: ${enableTrayIcon ? "ON" : "OFF"}`, type: "button", onClick: handleTrayToggle, }); if (isLinux) { items.push({ id: "runner", label: `Runner: ${selectedRunnerName}`, type: "button", onClick: handleRunnerToggle, }); if (runners.length === 0 || runners.every(r => r.type !== 'proton')) { items.push({ id: "download_runner", label: isRunnerDownloading ? `Downloading Runner... ${Math.floor(runnerDownloadProgress || 0)}%` : "Download GE-Proton (Recommended)", type: "button", onClick: () => { if (!isRunnerDownloading) { downloadRunner("GE-Proton9-25", "https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton9-25/GE-Proton9-25.tar.gz"); } }, small: true, }); } } items.push({ id: "reset_setup", label: "Reset Setup", type: "button", onClick: handleResetSetup, color: "orange", small: true, }); } if (isGameRunning) { items.push({ id: "stop", label: "STOP GAME", type: "button", onClick: stopGame, color: "red", }); } items.push({ id: "back", label: currentSubMenu === "main" ? "Done" : "Back", type: "button", onClick: () => { playBackSound(); if (currentSubMenu === "main") { setActiveView("main"); } else { setCurrentSubMenu("main"); setFocusIndex(0); } }, }); return items; }, [ currentSubMenu, musicVolume, sfxVolume, trackName, vfxEnabled, rpcEnabled, legacyMode, animationsEnabled, keepLauncherOpen, enableTrayIcon, layout, isLinux, selectedRunnerName, isRunnerDownloading, runnerDownloadProgress, isMac, perfBoost, isGameRunning, handleTrackToggle, handleVfxToggle, handleRpcToggle, handleLegacyToggle, handleAnimationsToggle, handleKeepOpenToggle, handleTrayToggle, handleLayoutToggle, handleRunnerToggle, handlePerfToggle, handleResetSetup, stopGame, downloadRunner, playPressSound, playBackSound, setActiveView, runners, ]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" || e.key === "Backspace") { playBackSound(); if (currentSubMenu !== "main") { setCurrentSubMenu("main"); setFocusIndex(0); } else { setActiveView("main"); } return; } const itemCount = settingsItems.length; if (e.key === "ArrowDown") { setFocusIndex((prev) => prev === null || prev >= itemCount - 1 ? 0 : prev + 1, ); } else if (e.key === "ArrowUp") { setFocusIndex((prev) => prev === null || prev <= 0 ? itemCount - 1 : prev - 1, ); } else if (e.key === "ArrowRight" || e.key === "ArrowLeft") { if (focusIndex === null) return; const item = settingsItems[focusIndex]; if (item.type === "slider") { const delta = e.key === "ArrowRight" ? 5 : -5; item.onChange((v: number) => Math.max(0, Math.min(100, v + delta))); } } else if (e.key === "Enter" && focusIndex !== null) { const item = settingsItems[focusIndex]; if (item.type === "button") { item.onClick(); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [focusIndex, settingsItems, playBackSound, setActiveView, currentSubMenu]); useEffect(() => { if (focusIndex !== null) { const el = containerRef.current?.querySelector( `[data-index="${focusIndex}"]`, ) as HTMLElement; if (el) el.focus(); } }, [focusIndex]); const getItemStyle = (index: number) => ({ backgroundImage: focusIndex === index ? "url('/images/button_highlighted.png')" : "url('/images/Button_Background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" as const, }); const getSliderStyle = (index: number) => ({ backgroundImage: "url('/images/Button_Background2.png')", backgroundSize: "100% 100%", imageRendering: "pixelated" as const, color: focusIndex === index ? "#FFFF55" : "white", }); const isToggleOption = (label: string): boolean => { return label.includes("ON") || label.includes("OFF") || label.includes("Enabled") || label.includes("Disabled"); }; const getToggleState = (label: string): boolean => { return label.includes("ON") || label.includes("Enabled"); }; return (

{currentSubMenu === "main" ? "Settings" : currentSubMenu === "audio" ? "Audio" : currentSubMenu === "video" ? "Video" : currentSubMenu === "controls" ? "Controls" : "Launcher"}

{currentSubMenu === "main" ? (
{settingsItems.map((item, index) => { if (item.id === "back") return null; if (item.type === "slider") { return (
setFocusIndex(index)} className="relative w-[360px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#FFFF55] shrink-0" style={getSliderStyle(index)} > {item.label}
item.onChange(parseInt(e.target.value))} onMouseUp={playPressSound} className="mc-slider-custom w-[calc(100%+16px)] h-full opacity-100 cursor-pointer z-20 outline-none m-0" />
); } const isRed = (item as any).color === "red"; const isSmall = (item as any).small; return ( ); })}
) : (
{settingsItems.map((item, index) => { if (item.id === "back") return null; if (item.type === "slider") { return (
setFocusIndex(index)} className="relative w-[360px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#FFFF55] shrink-0" style={getSliderStyle(index)} > {item.label}
item.onChange(parseInt(e.target.value))} onMouseUp={playPressSound} className="mc-slider-custom w-[calc(100%+16px)] h-full opacity-100 cursor-pointer z-20 outline-none m-0" />
); } const isRed = (item as any).color === "red"; const isSmall = (item as any).small; const isToggle = isToggleOption(item.label); const toggleState = isToggle ? getToggleState(item.label) : false; return ( ); })}
)} {(() => { const backIndex = settingsItems.findIndex((i) => i.id === "back"); const backItem = settingsItems[backIndex]; if (!backItem || backItem.type !== "button") return null; return ( ); })()}
); }); export default SettingsView;