import { useState, useEffect, useRef, memo } from "react"; import { motion } from "framer-motion"; import { TauriService } from "../../services/TauriService"; import CustomTUModal from "../modals/CustomTUModal"; import SetUidModal from "../modals/SetUidModal"; import { useUI, useConfig, useAudio, useGame, } from "../../context/LauncherContext"; import { ScreenshotImage } from "../common/ScreenshotImage"; import type { Edition } from "../../types/edition"; interface DeleteConfirmButtonProps { label: string; onClick: () => void; isDanger?: boolean; } const DeleteConfirmButton = memo(function DeleteConfirmButton({ label, onClick, isDanger = false, }: DeleteConfirmButtonProps) { const [isHovered, setIsHovered] = useState(false); return ( ); }); const VersionsView = memo(function VersionsView() { const { setActiveView } = useUI(); const { profile: selectedProfile, setProfile: setSelectedProfile, animationsEnabled, } = useConfig(); const { playPressSound, playBackSound } = useAudio(); const { editions, installs: installedVersions, toggleInstall, handleUninstall, handleCancelDownload, deleteCustomEdition: onDeleteEdition, addCustomEdition: onAddEdition, updateCustomEdition: onUpdateEdition, downloadingId, downloadProgress, updatesAvailable, addToSteam, cycleBranch, } = useGame(); const { isDayTime } = useConfig(); const [focusIndex, setFocusIndex] = useState(0); const [focusBtn, setFocusBtn] = useState(0); const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [isSetUidModalOpen, setIsSetUidModalOpen] = useState(false); const [setUidTargetId, setSetUidTargetId] = useState(""); const [editingEdition, setEditingEdition] = useState(null); const [initialPath, setInitialPath] = useState(""); const [hoveredBtn, setHoveredBtn] = useState<{ row: number; btn: string; } | null>(null); const [openMenuId, setOpenMenuId] = useState(null); const [deleteConfirmEdition, setDeleteConfirmEdition] = useState(null); const containerRef = useRef(null); const listRef = useRef(null); const ITEM_COUNT = editions.length + 3; useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (document.activeElement?.tagName === "INPUT") return; if (e.key === "Escape" || e.key === "Backspace") { playBackSound(); setActiveView("main"); return; } if (e.key === "ArrowDown") { e.preventDefault(); setFocusIndex((prev) => (prev >= ITEM_COUNT - 1 ? 0 : prev + 1)); setFocusBtn(0); } else if (e.key === "ArrowUp") { e.preventDefault(); setFocusIndex((prev) => (prev <= 0 ? ITEM_COUNT - 1 : prev - 1)); setFocusBtn(0); } else if (e.key === "ArrowLeft") { e.preventDefault(); if (focusIndex < editions.length) { const edition = editions[focusIndex]; const isInstalled = installedVersions.includes(edition.id); const isCustom = edition.id.startsWith("custom_"); const maxBtn = isInstalled ? (isCustom ? 6 : 4) : 1; setFocusBtn((prev) => (prev <= 0 ? maxBtn : prev - 1)); } } else if (e.key === "ArrowRight") { e.preventDefault(); if (focusIndex < editions.length) { const edition = editions[focusIndex]; const isInstalled = installedVersions.includes(edition.id); const isCustom = edition.id.startsWith("custom_"); const maxBtn = isInstalled ? (isCustom ? 6 : 4) : 1; setFocusBtn((prev) => (prev >= maxBtn ? 0 : prev + 1)); } } else if (e.key === "Enter") { e.preventDefault(); if (focusIndex < editions.length) { const edition = editions[focusIndex]; const isInstalled = installedVersions.includes(edition.instanceId); const isDownloading = downloadingId === edition.instanceId; if (focusBtn === 0) { if (isInstalled) { playPressSound(); setOpenMenuId(openMenuId === edition.id ? null : edition.id); } else { if (!isDownloading && !downloadingId) { playPressSound(); toggleInstall(edition.instanceId); } else if (isDownloading) { handleCancelDownload(); } } } else if (focusBtn === 2) { playPressSound(); cycleBranch(edition.id); } } else if (focusIndex === editions.length) { playPressSound(); setIsImportModalOpen(true); } else if (focusIndex === editions.length + 1) { playPressSound(); handleImportFolder(); } else { playBackSound(); setActiveView("main"); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [ focusIndex, focusBtn, editions, installedVersions, downloadingId, ITEM_COUNT, playPressSound, playBackSound, setSelectedProfile, setActiveView, toggleInstall, handleCancelDownload, addToSteam, isDayTime, ]); useEffect(() => { if (focusIndex < editions.length && listRef.current) { const el = listRef.current.querySelector( `[data-index="${focusIndex}"]`, ) as HTMLElement; if (el) { el.scrollIntoView({ behavior: "smooth", block: "nearest" }); } } }, [focusIndex]); const handleEditionClick = (edition: Edition, index: number) => { const isInstalled = installedVersions.includes(edition.instanceId); if (isInstalled) { playPressSound(); setSelectedProfile(edition.instanceId); } setFocusIndex(index); }; const handleImportFolder = async () => { try { const folder = await TauriService.pickFolder(); if (folder) { setInitialPath(folder); setIsImportModalOpen(true); } } catch (e) { if (e !== "CANCELED") console.error(e); } }; return (

Versions

{editions.map((edition: Edition, i: number) => { const isInstalled = installedVersions.includes( edition.instanceId, ); const hasAnyInstall = installedVersions.length > 0; const isSelected = hasAnyInstall && selectedProfile === edition.instanceId; const isFocused = focusIndex === i; const isCustom = edition.id.startsWith("custom_"); const isDownloading = downloadingId === edition.instanceId; const isComingSoon = edition.comingSoon; return (
!isComingSoon && setFocusIndex(i)} >
{isComingSoon ? ( Coming Soon ) : isDownloading ? ( {Math.floor(downloadProgress || 0)}% ) : isInstalled ? ( Installed ) : ( Not installed )}
!isComingSoon && handleEditionClick(edition, i) } className={`flex-1 text-left min-w-0 outline-none rounded cursor-pointer ${ focusIndex === i && focusBtn === 0 && !isComingSoon ? "ring-2 ring-white" : "" } ${isComingSoon ? "cursor-not-allowed" : ""}`} >
{edition.logo && (edition.logo.startsWith("http") || edition.logo.startsWith("/images") ? ( ) : ( ))} {edition.name} {edition.category && edition.category.map((cat: string) => ( {cat} ))} {isCustom && !edition.category && ( Custom )}

{edition.desc}

{!isInstalled ? ( ) : ( )} {openMenuId === edition.id && (
{updatesAvailable?.[edition.instanceId] && ( )} {Array.isArray(edition.branches) && edition.branches.length > 0 && ( )}
{isCustom ? ( ) : null}
)}
); })}
{ setIsImportModalOpen(false); setEditingEdition(null); setInitialPath(""); }} onImport={(ed: { name: string; desc: string; url: string; path?: string }) => { if (editingEdition) { onUpdateEdition(editingEdition.id, ed); } else { const id = onAddEdition(ed); setSelectedProfile(id); } }} playPressSound={playPressSound} playBackSound={playBackSound} editingEdition={editingEdition} initialPath={initialPath} /> setIsSetUidModalOpen(false)} playPressSound={playPressSound} playBackSound={playBackSound} instances={editions} installedVersions={installedVersions} targetInstanceId={setUidTargetId} /> {deleteConfirmEdition && (

Delete {deleteConfirmEdition.name}?

Warning: All your saves and worlds for this version will be permanently deleted!

{ playBackSound(); setDeleteConfirmEdition(null); }} /> { playPressSound(); handleUninstall(deleteConfirmEdition.id); setDeleteConfirmEdition(null); }} />
)} ); }); export default VersionsView;