import React, { useState, useEffect, memo, useCallback, useRef, useContext, useMemo, } from "react"; import { motion, AnimatePresence } from "framer-motion"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { useUI, useAudio, useConfig, GameContext, useGame, } from "../../context/LauncherContext"; import { TauriService, InstalledWorkshopPackage, type CustomEdition, } from "../../services/TauriService"; import { PluginManager } from "../../plugins/PluginManager"; const REGISTRY_URL = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/registry.json"; const VERSIONS_URL = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/versions.json"; const PLUGINS_URL = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/plugins.json"; const RAW_BASE = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main"; const VERSIONS_BASE = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/.00versions"; const PLUGINS_BASE = "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/.00plugins"; const BYTEBUKKIT_BASE = "https://emerald-bytebukkit.onrender.com"; const SERVERS_URL = "https://raw.githubusercontent.com/bytebukkit/servers/refs/heads/main/servers.json"; const SERVERS_BASE = "https://raw.githubusercontent.com/bytebukkit/servers/refs/heads/main"; const CATEGORY_TABS = ["Skin", "Texture", "World", "Mod", "DLC", "Plugins"] as const; const UTILITY_TABS = ["Versions", "Installed", "Search"] as const; const SERVER_TABS = ["Server", "Server Plugins"] as const; const ALL_TABS = [...CATEGORY_TABS, ...UTILITY_TABS, ...SERVER_TABS] as const; type TabType = (typeof ALL_TABS)[number]; interface RegistryPackage { id: string; name: string; author: string; description: string; extended_description?: string; category: string[]; thumbnail: string; zips?: Record; version: string; logo?: string; url?: string; likes?: number; download_count?: number; game_version?: string; github_url?: string; file_name?: string; file_size?: number; server_address?: string; server_discord?: string; server_type?: string; main?: string; permissions?: string[]; files?: string[]; } interface ServerListing { server_name: string; server_type: string; server_address: string; server_owner: string; server_discord?: string; console_version: string; server_icon: string; } interface ByteBukkitAddon { id: string; name: string; short_description: string; description: string; category: string; game_version: string; visibility: string; github_url?: string; created_at: string; likes: number; downloads: number; file_name: string; file_size: number; has_image: boolean; username: string; displayName: string; } interface PluginRegistryEntry { id: string; name: string; version: string; author: string; description: string; extended_description?: string; main: string; permissions?: string[]; files?: string[]; } const COLS = 4; const WorkshopView = memo(function WorkshopView() { const { setActiveView } = useUI(); const { playPressSound, playBackSound } = useAudio(); const config = useConfig(); const containerRef = useRef(null); const gridRef = useRef(null); const searchRef = useRef(null); const [activeTab, setActiveTab] = useState("Skin"); const [allPackages, setAllPackages] = useState([]); const [versionPackages, setVersionPackages] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [focusedIdx, setFocusedIdx] = useState(null); const [search, setSearch] = useState(""); const [selectedPkg, setSelectedPkg] = useState(null); const [installedPkgs, setInstalledPkgs] = useState< InstalledWorkshopPackage[] >([]); const [serverPlugins, setServerPlugins] = useState([]); const [serverCategory, setServerCategory] = useState("all"); const [serverListings, setServerListings] = useState([]); const [serverListingCategory, setServerListingCategory] = useState("all"); const [savedServers, setSavedServers] = useState>(new Set()); const [pluginPackages, setPluginPackages] = useState([]); const [installedPluginIds, setInstalledPluginIds] = useState>(new Set()); const refreshInstalled = useCallback(async () => { try { const data = await TauriService.workshopListInstalled(); setInstalledPkgs(data); } catch { setInstalledPkgs([]); } }, []); const refreshInstalledPlugins = useCallback(() => { const ids = new Set(); PluginManager.instance.plugins.forEach((_, id) => ids.add(id)); setInstalledPluginIds(ids); }, []); useEffect(() => { containerRef.current?.focus(); refreshInstalled(); }, [refreshInstalled]); useEffect(() => { TauriService.loadConfig() .then((cfg) => { setSavedServers(new Set((cfg.savedServers || []).map((s) => s.ip))); }) .catch(() => {}); }, []); useEffect(() => { setLoading(true); setError(null); Promise.all([ fetch(REGISTRY_URL).then((r) => r.json()), fetch(VERSIONS_URL).then((r) => r.json()), fetch(PLUGINS_URL).then((r) => r.json()).catch(() => null), ]) .then(([registryData, versionsData, pluginsData]) => { setAllPackages(registryData.packages ?? []); setVersionPackages(versionsData.versionlist ?? []); if (pluginsData?.pluginlist) { setPluginPackages( pluginsData.pluginlist.map((entry: PluginRegistryEntry) => ({ id: entry.id, name: entry.name, version: entry.version, author: entry.author, description: entry.description, extended_description: entry.extended_description || "", category: ["Plugin"], thumbnail: "", main: entry.main, permissions: entry.permissions, files: entry.files, })), ); } setLoading(false); }) .catch((e) => { setError(e.message ?? "Failed to load registry"); setLoading(false); }); refreshInstalledPlugins(); }, [refreshInstalledPlugins]); useEffect(() => { fetch(`${BYTEBUKKIT_BASE}/api/addons?limit=500`) .then((r) => r.json()) .then((data: ByteBukkitAddon[]) => { setServerPlugins( data.map((a) => ({ id: a.id, name: a.name, author: a.displayName || a.username, description: a.short_description, extended_description: a.description, category: [a.category], thumbnail: `${BYTEBUKKIT_BASE}/api/addons/${a.id}/icon`, version: "1.0", likes: a.likes, download_count: a.downloads, game_version: a.game_version, github_url: a.github_url, file_name: a.file_name, file_size: a.file_size, })), ); }) .catch(() => {}); }, []); useEffect(() => { fetch(SERVERS_URL) .then((r) => r.json()) .then((data: { servers: ServerListing[] }) => { setServerListings( data.servers.map((s) => ({ id: s.server_name.toLowerCase().replace(/\s+/g, "-"), name: s.server_name, author: s.server_owner, description: s.server_address, extended_description: `**Server:** ${s.server_type}\n**Version:** ${s.console_version}\n**Owner:** ${s.server_owner}`, category: [s.server_type], thumbnail: `${SERVERS_BASE}${s.server_icon}`, version: s.console_version, server_address: s.server_address, server_discord: s.server_discord ?? "", server_type: s.server_type, })), ); }) .catch(() => {}); }, []); const serverCategories = useMemo(() => { const cats = new Set(serverPlugins.flatMap((p) => p.category)); return ["all", ...cats]; }, [serverPlugins]); const serverListingCategories = useMemo(() => { const cats = new Set(serverListings.flatMap((p) => p.category)); return ["all", ...cats]; }, [serverListings]); const getInstalledEntries = useCallback( (pkgId: string, pkgVersion?: string) => { if (activeTab === "Versions") { const isAdded = config.customEditions?.some( (e: CustomEdition) => e.id === pkgId || e.url === versionPackages.find((p) => p.id === pkgId)?.url, ); if (isAdded) { const vPkg = versionPackages.find((p) => p.id === pkgId); return [ { packageId: pkgId, instanceId: pkgId, version: vPkg?.version || "0.0.0", }, ] as InstalledWorkshopPackage[]; } return []; } if (activeTab === "Plugins") { return installedPluginIds.has(pkgId) ? [{ packageId: pkgId, instanceId: pkgId, version: pkgVersion || "0.0.0" }] as InstalledWorkshopPackage[] : []; } if (activeTab === "Server Plugins" || activeTab === "Server") return []; return installedPkgs.filter((p) => p.packageId === pkgId); }, [installedPkgs, activeTab, config.customEditions, versionPackages, installedPluginIds], ); const isInstalled = useCallback( (pkgId: string) => { if (activeTab === "Plugins") return installedPluginIds.has(pkgId); if (activeTab === "Server Plugins" || activeTab === "Server") return false; if (activeTab === "Versions") { return ( config.customEditions?.some( (e: CustomEdition) => e.id === pkgId || e.url === versionPackages.find((p) => p.id === pkgId)?.url, ) ?? false ); } return installedPkgs.some((p) => p.packageId === pkgId); }, [installedPkgs, activeTab, config.customEditions, versionPackages, installedPluginIds], ); const hasUpdate = useCallback( (pkg: RegistryPackage) => { if (activeTab === "Plugins") { return false; } if ( activeTab === "Versions" || activeTab === "Server Plugins" || activeTab === "Server" ) return false; const entries = installedPkgs.filter((p) => p.packageId === pkg.id); return ( entries.length > 0 && entries.some((e) => e.version !== pkg.version) ); }, [installedPkgs, activeTab], ); const installedPackageList = allPackages.filter((pkg) => isInstalled(pkg.id)); const filteredItems = activeTab === "Installed" ? search.trim() ? installedPackageList.filter((pkg) => { const q = search.toLowerCase(); return ( pkg.name.toLowerCase().includes(q) || pkg.author.toLowerCase().includes(q) || pkg.description.toLowerCase().includes(q) ); }) : installedPackageList : activeTab === "Server Plugins" ? search.trim() ? serverPlugins.filter((pkg) => { if ( serverCategory !== "all" && !pkg.category.includes(serverCategory) ) return false; const q = search.toLowerCase(); return ( pkg.name.toLowerCase().includes(q) || pkg.author.toLowerCase().includes(q) || pkg.description.toLowerCase().includes(q) ); }) : serverCategory === "all" ? serverPlugins : serverPlugins.filter((pkg) => pkg.category.includes(serverCategory), ) : activeTab === "Server" ? search.trim() ? serverListings.filter((pkg) => { if ( serverListingCategory !== "all" && !pkg.category.includes(serverListingCategory) ) return false; const q = search.toLowerCase(); return ( pkg.name.toLowerCase().includes(q) || pkg.author.toLowerCase().includes(q) || pkg.description.toLowerCase().includes(q) ); }) : serverListingCategory === "all" ? serverListings : serverListings.filter((pkg) => pkg.category.includes(serverListingCategory), ) : activeTab === "Plugins" ? search.trim() ? pluginPackages.filter((pkg) => { const q = search.toLowerCase(); return ( pkg.name.toLowerCase().includes(q) || pkg.author.toLowerCase().includes(q) || pkg.description.toLowerCase().includes(q) ); }) : pluginPackages : (activeTab === "Versions" ? versionPackages : allPackages).filter( (pkg) => { const matchesTab = activeTab === "Search" || activeTab === "Versions" ? true : pkg.category.includes(activeTab); if (!matchesTab) return false; if (!search.trim()) return activeTab === "Search" ? false : true; const q = search.toLowerCase(); return ( pkg.name.toLowerCase().includes(q) || pkg.author.toLowerCase().includes(q) || pkg.description.toLowerCase().includes(q) ); }, ); useEffect(() => { setFocusedIdx(null); if (activeTab === "Search") { setTimeout(() => searchRef.current?.focus(), 50); } else { setSearch(""); } }, [activeTab]); useEffect(() => { if (focusedIdx !== null && gridRef.current) { const el = gridRef.current.querySelector( `[data-card="${focusedIdx}"]`, ) as HTMLElement; el?.scrollIntoView({ block: "nearest" }); } }, [focusedIdx]); const cycleTab = useCallback( (direction: "next" | "prev") => { playPressSound(); setActiveTab((prev) => { const idx = ALL_TABS.indexOf(prev); if (direction === "next") return ALL_TABS[(idx + 1) % ALL_TABS.length]; return ALL_TABS[(idx - 1 + ALL_TABS.length) % ALL_TABS.length]; }); }, [playPressSound], ); const selectTab = useCallback( (tab: TabType) => { if (tab !== activeTab) { playPressSound(); setActiveTab(tab); } }, [activeTab, playPressSound], ); const openModal = useCallback( (pkg: RegistryPackage) => { playPressSound(); setSelectedPkg(pkg); }, [playPressSound], ); const closeModal = useCallback(() => { playBackSound(); setSelectedPkg(null); }, [playBackSound]); const toggleSavedServer = useCallback(async (serverPkg: RegistryPackage) => { if (!serverPkg.server_address) return; const cfg = await TauriService.loadConfig(); const current = cfg.savedServers || []; const exists = current.some((s) => s.ip === serverPkg.server_address); const newSaved = exists ? current.filter((s) => s.ip !== serverPkg.server_address) : [ ...current, { name: serverPkg.name, ip: serverPkg.server_address, port: 25565 }, ]; await TauriService.saveConfig({ ...cfg, savedServers: newSaved }); setSavedServers(new Set(newSaved.map((s) => s.ip))); }, []); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (selectedPkg) return; const isSearchInput = document.activeElement === searchRef.current; if (isSearchInput) { if (e.key === "Escape") { setSearch(""); containerRef.current?.focus(); } return; } const count = filteredItems.length; if (e.key === "Escape" || e.key === "Backspace") { playBackSound(); setActiveView("main"); return; } if (e.key === "e" || e.key === "E") { cycleTab("next"); return; } if (e.key === "q" || e.key === "Q") { cycleTab("prev"); return; } if (count === 0) return; if (e.key === "ArrowRight") { e.preventDefault(); setFocusedIdx((p) => Math.min((p ?? -1) + 1, count - 1)); playPressSound(); } else if (e.key === "ArrowLeft") { e.preventDefault(); setFocusedIdx((p) => Math.max((p ?? 1) - 1, 0)); playPressSound(); } else if (e.key === "ArrowDown") { e.preventDefault(); setFocusedIdx((p) => Math.min((p ?? -1) + (isPluginTab ? 1 : COLS), count - 1)); playPressSound(); } else if (e.key === "ArrowUp") { e.preventDefault(); setFocusedIdx((p) => Math.max((p ?? 1) - (isPluginTab ? 1 : COLS), 0)); playPressSound(); } else if (e.key === "Enter" && focusedIdx !== null) { const pkg = filteredItems[focusedIdx]; if (pkg) openModal(pkg); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [ playBackSound, playPressSound, setActiveView, cycleTab, filteredItems, focusedIdx, selectedPkg, openModal, ]); const isSearchTab = activeTab === "Search"; const isInstalledTab = activeTab === "Installed"; const isVersionTab = activeTab === "Versions"; const isPluginTab = activeTab === "Plugins"; const showSearch = isSearchTab || isInstalledTab || isVersionTab || isPluginTab || activeTab === "Server Plugins" || activeTab === "Server"; return (

Workshop

{CATEGORY_TABS.map((tab, i) => { const isActive = tab === activeTab; return ( {i > 0 &&
} ); })}
{UTILITY_TABS.map((tab, i) => { const isActive = tab === activeTab; const updateCount = tab === "Installed" ? allPackages.filter((p) => hasUpdate(p)).length : 0; return ( {i > 0 &&
} ); })}
{SERVER_TABS.map((tab, i) => { const isActive = tab === activeTab; return ( {i > 0 &&
} ); })}
{showSearch ? (
{ setSearch(e.target.value); setFocusedIdx(null); }} placeholder={ isInstalledTab ? "FILTER INSTALLED..." : isVersionTab ? "FILTER VERSIONS..." : isPluginTab ? "FILTER PLUGINS..." : activeTab === "Server Plugins" ? "FILTER PLUGINS..." : activeTab === "Server" ? "FILTER SERVERS..." : "ENTER KEYWORDS..." } spellCheck={false} autoFocus={isSearchTab} className="bg-transparent border-none outline-none text-white text-lg mc-text-shadow w-full placeholder-white/40 font-['Mojangles'] tracking-widest" /> {search && ( )}
{activeTab === "Server Plugins" && serverCategories.length > 1 && (
{serverCategories.map((cat) => ( ))}
)} {activeTab === "Server" && serverListingCategories.length > 1 && (
{serverListingCategories.map((cat) => ( ))}
)}
{isInstalledTab && !search.trim() && filteredItems.length > 0 && (
)} {isSearchTab && !search.trim() ? (
Start typing to search...
) : loading ? (
Searching Archives...
) : filteredItems.length === 0 ? (
{isInstalledTab ? "Nothing Installed" : activeTab === "Plugins" ? "No plugins available" : activeTab === "Server Plugins" ? "No plugins available" : activeTab === "Server" ? "No servers available" : "No results"}
) : isPluginTab ? (
{filteredItems.map((pkg, i) => ( setFocusedIdx(i)} onClick={() => openModal(pkg)} installed={isInstalled(pkg.id)} hasUpdate={hasUpdate(pkg)} isVersionTab={isVersionTab} isPluginTab={isPluginTab} /> ))}
) : (
{filteredItems.map((pkg, i) => ( setFocusedIdx(i)} onClick={() => openModal(pkg)} installed={isInstalled(pkg.id)} hasUpdate={hasUpdate(pkg)} isVersionTab={isVersionTab} /> ))}
)} {(activeTab === "Server Plugins" || activeTab === "Server") && (
ByteBukkit TauriService.openUrl("https://bytebukkit.github.io") } />
)}
) : loading ? (
Searching Archives...
) : error ? (
{error}
) : (
{filteredItems.length === 0 ? (
Empty category
) : isPluginTab ? (
{filteredItems.map((pkg, i) => ( setFocusedIdx(i)} onClick={() => openModal(pkg)} installed={isInstalled(pkg.id)} hasUpdate={hasUpdate(pkg)} isVersionTab={isVersionTab} isPluginTab={isPluginTab} /> ))}
) : (
{filteredItems.map((pkg, i) => ( setFocusedIdx(i)} onClick={() => openModal(pkg)} installed={isInstalled(pkg.id)} hasUpdate={hasUpdate(pkg)} isVersionTab={isVersionTab} isPluginTab={isPluginTab} /> ))}
)}
)}
{selectedPkg && ( { refreshInstalled(); refreshInstalledPlugins(); }} onUninstallComplete={() => { refreshInstalled(); refreshInstalledPlugins(); }} isVersionTab={activeTab === "Versions"} isServerTab={activeTab === "Server Plugins"} isGameServerTab={activeTab === "Server"} isPluginTab={isPluginTab} isSaved={ selectedPkg.server_address ? savedServers.has(selectedPkg.server_address) : false } onToggleSave={toggleSavedServer} /> )} ); }); function PackageCard({ pkg, index, focused, onHover, onClick, installed, hasUpdate, isVersionTab, isPluginTab, }: { pkg: RegistryPackage; index: number; focused: boolean; onHover: () => void; onClick: () => void; installed: boolean; hasUpdate: boolean; isVersionTab?: boolean; isPluginTab?: boolean; }) { const thumbnailUrl = pkg.thumbnail.startsWith("http") ? pkg.thumbnail : isVersionTab ? `${VERSIONS_BASE}/${pkg.id}/${pkg.thumbnail}` : `${RAW_BASE}/${pkg.id}/${pkg.thumbnail}`; const [imgError, setImgError] = useState(false); return (
{pkg.thumbnail ? (
{imgError ? ( No Image ) : ( {pkg.name} setImgError(true)} /> )}
{pkg.category.slice(0, 1).map((c) => ( {c} ))}
{hasUpdate && (
Update
)} {installed && !hasUpdate && (
{isVersionTab ? "Added" : "Installed"}
)}
) : (
)}
{pkg.name}
v{pkg.version} {pkg.author}

{pkg.description}

); } function PackageModal({ pkg, onClose, playPressSound, installedEntries, onInstallComplete, onUninstallComplete, isVersionTab, isServerTab, isGameServerTab, isPluginTab, isSaved, onToggleSave, }: { pkg: RegistryPackage; onClose: () => void; playPressSound: () => void; installedEntries: InstalledWorkshopPackage[]; onInstallComplete: () => void; onUninstallComplete: () => void; isVersionTab?: boolean; isServerTab?: boolean; isGameServerTab?: boolean; isPluginTab?: boolean; isSaved?: boolean; onToggleSave?: (pkg: RegistryPackage) => void; }) { const { addCustomEdition } = useGame(); const thumbnailUrl = pkg.thumbnail.startsWith("http") ? pkg.thumbnail : isVersionTab ? `${VERSIONS_BASE}/${pkg.id}/${pkg.thumbnail}` : isPluginTab || !pkg.thumbnail ? "" : `${RAW_BASE}/${pkg.id}/${pkg.thumbnail}`; const [imgError, setImgError] = useState(false); const [modalFocus, setModalFocus] = useState< "install" | "uninstall" | "close" >("install"); const [showInstall, setShowInstall] = useState(false); const [showUninstall, setShowUninstall] = useState(false); const hasInstalled = installedEntries.length > 0; const needsUpdate = hasInstalled && installedEntries.some((e) => e.version !== pkg.version); const focusOptions: Array<"install" | "uninstall" | "close"> = isGameServerTab ? ["install", "close"] : isServerTab ? ["install", "close"] : hasInstalled || isVersionTab ? ["install", "uninstall", "close"] : ["install", "close"]; useEffect(() => { if (showInstall || showUninstall) return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape" || e.key === "Backspace") { onClose(); } else if ( e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "Tab" ) { e.preventDefault(); playPressSound(); setModalFocus((p) => { const idx = focusOptions.indexOf(p); return focusOptions[(idx + 1) % focusOptions.length]; }); } else if (e.key === "Enter") { if (modalFocus === "close") onClose(); else if (modalFocus === "install") handleAction(); else if (modalFocus === "uninstall") setShowUninstall(true); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [ modalFocus, showInstall, showUninstall, onClose, playPressSound, focusOptions, ]); const handleAction = async () => { if (isGameServerTab) { playPressSound(); if (onToggleSave) onToggleSave(pkg); } else if (isServerTab) { playPressSound(); try { const path = await TauriService.saveFileDialog( "Save Server Plugin", pkg.file_name || `${pkg.name}.dll`, ["*.dll", "*"], ); if (!path) return; const response = await fetch( `${BYTEBUKKIT_BASE}/api/addons/${pkg.id}/download`, ); const blob = await response.blob(); const buffer = await blob.arrayBuffer(); await TauriService.writeBinaryFile(path, new Uint8Array(buffer)); } catch (e) { console.error(e); } } else if (isVersionTab) { if (hasInstalled) return; playPressSound(); try { const logoUrl = `${VERSIONS_BASE}/${pkg.id}/${pkg.logo}`; const localLogoPath = await TauriService.downloadLogo(pkg.id, logoUrl); addCustomEdition({ id: pkg.id, name: pkg.name, desc: pkg.description, url: pkg.url!, category: pkg.category, logo: localLogoPath, }); onInstallComplete(); } catch (e) { console.error(e); } } else if (isPluginTab) { setShowInstall(true); } else { setShowInstall(true); } }; const installLabel = isGameServerTab ? isSaved ? "ADDED" : "ADD" : isServerTab ? "DOWNLOAD" : isVersionTab ? hasInstalled ? "ADDED" : "ADD" : isPluginTab ? hasInstalled ? needsUpdate ? "UPDATE" : "REINSTALL" : "INSTALL" : !hasInstalled ? "INSTALL" : needsUpdate ? "UPDATE" : "REINSTALL"; return ( <>
e.stopPropagation()} className="flex flex-col w-[640px] max-h-[85vh] overflow-hidden font-['Mojangles'] mc-options-bg"> {isPluginTab ? (
{pkg.name} By {pkg.author}
) : (
{imgError ? (
No Image
) : ( {pkg.name} setImgError(true)} /> )}
{pkg.name} By {pkg.author}
{needsUpdate && (
Update Available
)} {hasInstalled && !needsUpdate && (
Installed
)} {isGameServerTab && isSaved && (
Saved
)}
)}
{pkg.extended_description && pkg.extended_description.trim() !== "" && (
Description
{pkg.extended_description}
)}
Metadata
{isGameServerTab ? ( <>
Address: {pkg.server_address || "N/A"}
Type: {pkg.server_type || "N/A"}
Console: {pkg.version}
Owner: {pkg.author}
{pkg.server_discord && ( )} ) : isServerTab ? ( <>
Downloads: {pkg.download_count?.toLocaleString() ?? 0}
Likes: {pkg.likes?.toLocaleString() ?? 0}
Server Type: {pkg.game_version || "N/A"}
File: {pkg.file_name || "N/A"}
{pkg.file_size && (
File Size: {(pkg.file_size / 1024).toFixed(1)} KB
)} {pkg.github_url && ( )} ) : ( <>
Version: v{pkg.version}
Package ID: {pkg.id}
)} {hasInstalled && (
Installed: v{installedEntries[0]?.version} {needsUpdate ? " (outdated)" : ""}
)}
Categories
{pkg.category.map((c) => ( {c} ))}
{pkg.zips && Object.keys(pkg.zips).length > 0 && (
Files
{Object.entries(pkg.zips).map(([file, dest]) => (
{file} {dest && ( {dest} )}
))}
)}
{hasInstalled && ( )}
{showInstall && ( { setShowInstall(false); onInstallComplete(); }} playPressSound={playPressSound} isPluginTab={isPluginTab} /> )} {showUninstall && ( { setShowUninstall(false); onUninstallComplete(); }} playPressSound={playPressSound} isVersionTab={isVersionTab} isPluginTab={isPluginTab} /> )} ); } function InstallModal({ pkg, onClose, playPressSound, isPluginTab, }: { pkg: RegistryPackage; onClose: () => void; playPressSound: () => void; isPluginTab?: boolean; }) { const game = useContext(GameContext); const availableEditions = game?.editions.filter((e) => game.installs.includes(e.id)) || []; const [focusedIdx, setFocusedIdx] = useState(0); const [status, setStatus] = useState< "idle" | "installing" | "success" | "error" >("idle"); const [errorMsg, setErrorMsg] = useState(null); const installPlugin = useCallback(async () => { setStatus("installing"); setErrorMsg(null); playPressSound(); try { const pluginsDir = await TauriService.getPluginsDir(); const pluginDir = `${pluginsDir}/${pkg.id}`; await TauriService.createPluginDir(pkg.id); const encoder = new TextEncoder(); const manifest = { id: pkg.id, name: pkg.name, version: pkg.version, author: pkg.author, description: pkg.description, extended_description: pkg.extended_description || "", main: pkg.main || "main.js", permissions: pkg.permissions || [], }; await TauriService.writeBinaryFile( `${pluginDir}/plugin.json`, encoder.encode(JSON.stringify(manifest, null, 2)), ); const pluginBaseUrl = `${RAW_BASE}/.00plugins/${pkg.id}`; const allFiles = [pkg.main || "main.js", ...(pkg.files || [])]; for (const file of allFiles) { const res = await TauriService.httpProxyRequest("GET", `${pluginBaseUrl}/${file}`, null, {}); if (res.status !== 200) throw new Error(`Failed to download ${file}`); await TauriService.writeBinaryFile( `${pluginDir}/${file}`, encoder.encode(res.body), ); } await PluginManager.instance.reload(); setStatus("success"); } catch (e: unknown) { console.error(e); setStatus("error"); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error"); } }, [pkg, playPressSound]); useEffect(() => { if (isPluginTab && status === "idle") { installPlugin(); } }, [isPluginTab, status, installPlugin]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { e.stopPropagation(); if (status === "installing") return; if (status === "success") { if (e.key === "Escape" || e.key === "Backspace" || e.key === "Enter") onClose(); return; } if (e.key === "Escape" || e.key === "Backspace") { onClose(); } else if (e.key === "ArrowUp") { e.preventDefault(); playPressSound(); setFocusedIdx((p) => Math.max(p - 1, 0)); } else if (e.key === "ArrowDown") { e.preventDefault(); playPressSound(); setFocusedIdx((p) => Math.min(p + 1, availableEditions.length - 1)); } else if (e.key === "Enter") { if (availableEditions.length > 0) { installTo(availableEditions[focusedIdx].id); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [availableEditions, focusedIdx, status, onClose, playPressSound]); const installTo = async (instanceId: string) => { setStatus("installing"); setErrorMsg(null); playPressSound(); try { await TauriService.workshopInstall( instanceId, pkg.id, pkg.zips!, pkg.version, ); setStatus("success"); } catch (e: unknown) { console.error(e); setStatus("error"); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error"); } }; return ( e.stopPropagation()} className="flex flex-col w-[520px] font-['Mojangles'] text-white border-2 border-[#555] rounded-sm overflow-hidden" style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated", }} >
{isPluginTab ? "INSTALL PLUGIN" : "INSTALL CONTENT"} {isPluginTab ? `Installing "${pkg.name}"` : `Target Edition for "${pkg.name}"`}
{status === "installing" && (
Installing... {isPluginTab ? "Downloading plugin files" : "Downloading and extracting assets"} {isPluginTab && pkg.permissions && pkg.permissions.length > 0 && (
Requested Permissions
{pkg.permissions.map((perm) => ( {perm} ))}
)}
)} {status === "success" && (
Installed Successfully! Press any key or click to continue
)} {status === "error" && (
Installation Failed {errorMsg}
)} {status === "idle" && !isPluginTab && (availableEditions.length === 0 ? (
No installed editions found
) : ( availableEditions.map((ed, i) => (
installTo(ed.id)} onMouseEnter={() => setFocusedIdx(i)} className={`flex flex-col p-3 cursor-pointer border-2 transition-none ${focusedIdx === i ? "border-[#FFFF55] bg-black/40" : "border-[#444] bg-black/20"}`} > {ed.name}
)) ))}
); } function UninstallModal({ pkg, installedEntries, onClose, playPressSound, isVersionTab, isPluginTab, }: { pkg: RegistryPackage; installedEntries: InstalledWorkshopPackage[]; onClose: () => void; playPressSound: () => void; isVersionTab?: boolean; isPluginTab?: boolean; }) { const { deleteCustomEdition } = useGame(); const game = useContext(GameContext); const [focusedIdx, setFocusedIdx] = useState(0); const [status, setStatus] = useState< "idle" | "removing" | "success" | "error" >("idle"); const [errorMsg, setErrorMsg] = useState(null); const editionName = (instanceId: string) => { const ed = game?.editions.find((e) => e.id === instanceId); return ed?.name ?? instanceId; }; const uninstallPlugin = useCallback(async () => { setStatus("removing"); setErrorMsg(null); playPressSound(); try { await TauriService.removePluginDir(pkg.id); await PluginManager.instance.reload(); setStatus("success"); } catch (e: unknown) { console.error(e); setStatus("error"); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error"); } }, [pkg.id, playPressSound]); useEffect(() => { if (isPluginTab && status === "idle") { uninstallPlugin(); } }, [isPluginTab, status, uninstallPlugin]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { e.stopPropagation(); if (status === "removing") return; if (status === "success") { if (e.key === "Escape" || e.key === "Backspace" || e.key === "Enter") onClose(); return; } if (e.key === "Escape" || e.key === "Backspace") { onClose(); } else if (e.key === "ArrowUp") { e.preventDefault(); playPressSound(); setFocusedIdx((p) => Math.max(p - 1, 0)); } else if (e.key === "ArrowDown") { e.preventDefault(); playPressSound(); setFocusedIdx((p) => Math.min(p + 1, installedEntries.length - 1)); } else if (e.key === "Enter") { if (installedEntries.length > 0) { uninstallFrom(installedEntries[focusedIdx].instanceId); } } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [installedEntries, focusedIdx, status, onClose, playPressSound]); const uninstallFrom = async (instanceId: string) => { setStatus("removing"); setErrorMsg(null); playPressSound(); try { if (isVersionTab) { deleteCustomEdition(pkg.id); } else { await TauriService.workshopUninstall(instanceId, pkg.id); } setStatus("success"); } catch (e: unknown) { console.error(e); setStatus("error"); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error"); } }; return ( e.stopPropagation()} className="flex flex-col w-[520px] font-['Mojangles'] text-white border-2 border-[#555] rounded-sm overflow-hidden" style={{ backgroundImage: "url('/images/frame_background.png')", backgroundSize: "100% 100%", imageRendering: "pixelated", }} >
{isPluginTab ? "REMOVE PLUGIN" : "REMOVE CONTENT"} {isPluginTab ? `Remove "${pkg.name}"` : `Select edition to remove "${pkg.name}"`}
{status === "removing" && (
Removing... Deleting installed files
)} {status === "success" && (
Removed Successfully! Press any key or click to continue
)} {status === "error" && (
Removal Failed {errorMsg}
)} {status === "idle" && !isPluginTab && installedEntries.map((entry, i) => (
uninstallFrom(entry.instanceId)} onMouseEnter={() => setFocusedIdx(i)} className={`flex items-center justify-between p-3 cursor-pointer border-2 transition-none ${focusedIdx === i ? "border-[#FF5555] bg-black/40" : "border-[#444] bg-black/20"}`} > {editionName(entry.instanceId)} v{entry.version}
))}
); } export default WorkshopView;