import { useState, useEffect, useRef, useMemo, memo, useCallback } 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"; import { PluginManager, type PluginInfo } from "../../plugins/PluginManager"; import { usePluginActions } from "../../plugins/PluginContext"; const SettingsView = memo(function SettingsView() { const { setActiveView } = useUI(); const { vfxEnabled, setVfxEnabled, animationsEnabled, setAnimationsEnabled, musicVol: musicVolume, setMusicVol: setMusicVolume, sfxVol: sfxVolume, setSfxVol: setSfxVolume, layout, linuxRunner, setLinuxRunner, perfBoost, setPerfBoost, rpcEnabled, setRpcEnabled, startFullscreen, setStartFullscreen, legacyMode, setLegacyMode, mangohudEnabled, setMangohudEnabled, extraLaunchArgs, setExtraLaunchArgs, launchPrefix, setLaunchPrefix, launchEnvVars, setLaunchEnvVars, skipIntro, setSkipIntro, profile, } = useConfig(); const { currentTrack, skipTrack, tracks, playPressSound, playBackSound } = useAudio(); const { isGameRunning, stopGame, isRunnerDownloading, runnerDownloadProgress, downloadRunner, } = useGame(); const { isLinux, isMac, isAndroid } = usePlatform(); const [focusIndex, setFocusIndex] = useState(null); const [currentSubMenu, setCurrentSubMenu] = useState< "main" | "audio" | "video" | "launcher" | "game" | "plugins" >("main"); const [runners, setRunners] = useState([]); const [pluginsInfo, setPluginsInfo] = useState([]); const pluginSettingsActions = usePluginActions("settings-tab"); const containerRef = useRef(null); const [argsInput, setArgsInput] = useState(""); const [prefixInput, setPrefixInput] = useState(""); const [envVarsInput, setEnvVarsInput] = useState(""); const [showModal, setShowModal] = useState< "args" | "prefix" | "envVars" | null >(null); //jandrozdz: Added so track doesn't skip forever after pressing once const isSkippingRef = useRef(false); useEffect(() => { TauriService.getAvailableRunners().then(setRunners); }, [isRunnerDownloading]); const refreshPlugins = useCallback(() => { setPluginsInfo(PluginManager.instance.getPluginInfoList()); }, []); useEffect(() => { refreshPlugins(); PluginManager.instance.setEnabledChangedCallback(refreshPlugins); return () => PluginManager.instance.setEnabledChangedCallback(null!); }, [refreshPlugins]); const handleVfxToggle = () => { playPressSound(); setVfxEnabled(!vfxEnabled); }; const handleAnimationsToggle = () => { playPressSound(); setAnimationsEnabled(!animationsEnabled); }; const handlePerfToggle = () => { playPressSound(); setPerfBoost(!perfBoost); }; const handleRpcToggle = () => { playPressSound(); setRpcEnabled(!rpcEnabled); }; const handleFullscreenToggle = () => { playPressSound(); setStartFullscreen(!startFullscreen); }; const handleLegacyToggle = () => { playPressSound(); setLegacyMode(!legacyMode); }; const handleMangohudToggle = () => { playPressSound(); setMangohudEnabled(!mangohudEnabled); }; const handleSkipIntroToggle = () => { playPressSound(); setSkipIntro(!skipIntro); }; 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 = () => { if (isSkippingRef.current) return; playPressSound(); isSkippingRef.current = true; skipTrack(); //jandrozdz: Use skipTrack here setTimeout(() => { isSkippingRef.current = false; }, 100); }; const handleResetSetup = () => { playPressSound(); 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 handleOk = () => { document.body.removeChild(dialog); showSecondConfirmation(); }; const handleCancel = () => { document.body.removeChild(dialog); }; dialog.querySelector("#reset-ok")?.addEventListener("click", handleOk); dialog .querySelector("#reset-cancel") ?.addEventListener("click", handleCancel); 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 handleFinalOk = () => { document.body.removeChild(dialog); performReset(); }; const handleFinalCancel = () => { document.body.removeChild(dialog); }; dialog .querySelector("#reset-final-ok") ?.addEventListener("click", handleFinalOk); dialog .querySelector("#reset-final-cancel") ?.addEventListener("click", handleFinalCancel); dialog.addEventListener("click", (e) => { if (e.target === dialog) { document.body.removeChild(dialog); } }); }; const performReset = () => { localStorage.clear(); localStorage.setItem("lce-setup-completed", "false"); 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: number) => 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: "launcher_menu", label: "Launcher", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("launcher"); setFocusIndex(0); }, }); items.push({ id: "game_menu", label: "Game", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("game"); setFocusIndex(0); }, }); items.push({ id: "plugins_menu", label: "Plugins", type: "button", onClick: () => { playPressSound(); setCurrentSubMenu("plugins"); setFocusIndex(0); }, }); if (isAndroid && profile) { items.push({ id: "container_settings", label: "Container Settings", type: "button", onClick: () => { playPressSound(); TauriService.openContainerSettings(profile).catch(console.error); }, }); items.push({ id: "open_container", label: "Open Container", type: "button", onClick: () => { playPressSound(); TauriService.openInstanceFolder(profile).catch(console.error); }, }); } for (const action of pluginSettingsActions) { items.push({ id: action.id, label: action.label, type: "button", onClick: () => { playPressSound(); action.onClick(); }, }); } } 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: `Click effects: ${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: `Apple silicon performance boost: ${perfBoost ? "Enabled" : "Disabled"}`, type: "button", onClick: handlePerfToggle, }); } } else if (currentSubMenu === "game") { const envVarsCount = launchEnvVars ? Object.keys(launchEnvVars).length : 0; items.push({ id: "extra_launch_args", label: extraLaunchArgs && extraLaunchArgs.length > 0 ? `Extra Args: ${extraLaunchArgs.join(" ")}` : "Extra Launch Args: None", type: "button", onClick: () => { playPressSound(); setArgsInput(extraLaunchArgs?.join(" ") ?? ""); setShowModal("args"); }, }); items.push({ id: "launch_prefix", label: launchPrefix ? `Prefix: ${launchPrefix}` : "Launch Prefix: None", type: "button", onClick: () => { playPressSound(); setPrefixInput(launchPrefix ?? ""); setShowModal("prefix"); }, }); items.push({ id: "launch_env_vars", label: `Launch Env Vars: ${envVarsCount > 0 ? `${envVarsCount} set` : "None"}`, type: "button", onClick: () => { playPressSound(); const current = launchEnvVars ? Object.entries(launchEnvVars) .map(([k, v]) => `${k}=${v}`) .join("\n") : ""; setEnvVarsInput(current); setShowModal("envVars"); }, }); } else if (currentSubMenu === "launcher") { if (!isAndroid) { items.push({ id: "fullscreen", label: `Start in Fullscreen: ${startFullscreen ? "ON" : "OFF"}`, type: "button", onClick: handleFullscreenToggle, }); items.push({ id: "rpc", label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`, type: "button", onClick: handleRpcToggle, }); } items.push({ id: "skip_intro", label: `Skip Intro: ${skipIntro ? "ON" : "OFF"}`, type: "button", onClick: handleSkipIntroToggle, }); items.push({ id: "legacy", label: `Legacy Mode: ${legacyMode ? "ON" : "OFF"}`, type: "button", onClick: handleLegacyToggle, }); if (isLinux && !isAndroid) { items.push({ id: "runner", label: `Runner: ${selectedRunnerName}`, type: "button", onClick: handleRunnerToggle, }); items.push({ id: "mangohud", label: `MangoHud: ${mangohudEnabled ? "ON" : "OFF"}`, type: "button", onClick: handleMangohudToggle, }); 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, }); } if (!isAndroid) { items.push({ id: "export_settings", label: "Export Settings", type: "button", onClick: async () => { playPressSound(); try { await TauriService.exportSettings(); } catch (e) { if (e !== "CANCELED") console.error(e); } }, }); } if (!isAndroid) { items.push({ id: "import_settings", label: "Import Settings", type: "button", onClick: async () => { playPressSound(); try { await TauriService.importSettings(); window.location.reload(); } catch (e) { if (e !== "CANCELED") console.error(e); } }, }); } items.push({ id: "reset_setup", label: "Reset Setup", type: "button", onClick: handleResetSetup, color: "orange", }); } 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, pluginSettingsActions, musicVolume, sfxVolume, trackName, vfxEnabled, rpcEnabled, legacyMode, animationsEnabled, layout, isLinux, isAndroid, mangohudEnabled, selectedRunnerName, isRunnerDownloading, runnerDownloadProgress, isMac, perfBoost, isGameRunning, handleTrackToggle, handleVfxToggle, handleRpcToggle, handleLegacyToggle, handleAnimationsToggle, handleRunnerToggle, handlePerfToggle, handleMangohudToggle, handleSkipIntroToggle, handleResetSetup, stopGame, downloadRunner, playPressSound, playBackSound, setActiveView, runners, extraLaunchArgs, launchPrefix, launchEnvVars, skipIntro, profile, ]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { if (showModal) { playBackSound(); setShowModal(null); return; } 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; const newVal = Math.max(0, Math.min(100, item.value + delta)); item.onChange(newVal); } } 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, showModal, ]); 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 ? "#ffff00" : "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 === "game" ? "Game" : currentSubMenu === "plugins" ? "Plugins" : "Launcher"}

{currentSubMenu === "main" ? (
{settingsItems.map((item, index) => { if (item.id === "back") return null; if (item.type === "slider") { return (
setFocusIndex(index)} className="relative w-[480px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#ffff00] 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 = "color" in item && (item as { color: string }).color === "red"; const isSmall = "small" in item && (item as { small: boolean }).small; return ( ); })}
) : currentSubMenu === "plugins" ? (
{pluginsInfo.length === 0 ? (
No plugins installed
) : ( pluginsInfo.map((p, index) => { const isFocused = focusIndex === index; return (
setFocusIndex(index)} className={`w-[600px] flex items-center gap-3 px-4 py-3 cursor-pointer outline-none border-none ${ isFocused ? "text-[#ffff00]" : "text-[#FFFFFF]" }`} style={{ backgroundImage: "url('/images/Button_Background2.png')", backgroundSize: "100% 100%", imageRendering: "pixelated", }} onClick={() => { playPressSound(); PluginManager.instance.setPluginEnabled( p.manifest.id, !p.enabled, ); }} >
checkbox {p.enabled && ( checked )}
{p.manifest.name} {p.manifest.description} by {p.manifest.author} · v{p.manifest.version}
); }) )}
) : (
{settingsItems.map((item, index) => { if (item.id === "back") return null; if (item.type === "slider") { return (
setFocusIndex(index)} className="relative w-[600px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#ffff00] 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.type === "button" && item.color === "red"; const isSmall = item.type === "button" && !!item.small; const isToggle = isToggleOption(item.label); const toggleState = isToggle ? getToggleState(item.label) : false; return ( ); })}
)} {!isAndroid && (() => { const backIndex = settingsItems.findIndex((i) => i.id === "back"); const backItem = settingsItems[backIndex]; if (!backItem || backItem.type !== "button") return null; return ( ); })()} {showModal === "args" && (

Extra Launch Args

Space-separated arguments passed to the game executable

setArgsInput(e.target.value)} placeholder="e.g. -quitondisconnect -ip 127.0.0.1" className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-['Mojangles'] text-center" style={{ imageRendering: "pixelated" }} />
)} {showModal === "prefix" && (

Launch Prefix

Command that wraps the entire launch (e.g. gamemoderun)

setPrefixInput(e.target.value)} placeholder="e.g. gamemoderun" className="w-full h-10 px-3 bg-black/40 border-2 border-[#373737] text-white text-base outline-none font-['Mojangles'] text-center" style={{ imageRendering: "pixelated" }} />
)} {showModal === "envVars" && (

Launch Env Vars

One KEY=VALUE per line