This commit is contained in:
neoapps-dev
2026-04-02 16:10:12 +03:00
parent 535735fcb2
commit 340f636b8e
14 changed files with 7776 additions and 1242 deletions
@@ -1,98 +0,0 @@
import { motion } from "framer-motion";
import { useState, useEffect } from "react";
export default function SpecialThanksModal({
isOpen,
onClose,
playSfx,
}: any) {
const [focusIndex, setFocusIndex] = useState(0);
const contributors = [
{ name: "smartcmd & LCE Community", desc: "Research & Foundations" },
{ name: "Andi_pog & AFanFromWalmart", desc: "Project Testers" }
];
useEffect(() => {
if (!isOpen) {
setFocusIndex(0);
return;
}
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
playSfx("close_click.wav");
onClose();
} else if (e.key === "ArrowDown" || e.key === "Tab") {
e.preventDefault();
setFocusIndex((prev) => (prev + 1) % (contributors.length + 1));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setFocusIndex((prev) => (prev - 1 + (contributors.length + 1)) % (contributors.length + 1));
} else if (e.key === "Enter") {
if (focusIndex === contributors.length) {
playSfx("close_click.wav");
onClose();
}
}
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, [isOpen, focusIndex]);
if (!isOpen) return null;
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 z-[110] flex items-center justify-center bg-black/80 backdrop-blur-sm outline-none border-none"
>
<div
className="relative w-[400px] p-6 flex flex-col items-center shadow-2xl"
style={{
backgroundImage: "url('/images/frame_background.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<h2 className="text-[#FFFF55] text-2xl mc-text-shadow mb-4 border-b-2 border-[#373737] pb-2 w-full text-center uppercase">
Special Thanks
</h2>
<div className="flex flex-col gap-2 w-full items-center max-h-[320px] overflow-y-auto overflow-x-hidden scrollbar-hide py-2">
{contributors.map((item, idx) => (
<div
key={item.name}
onMouseEnter={() => setFocusIndex(idx)}
className={`w-[90%] p-2 flex flex-col items-center justify-center mc-text-shadow transition-transform outline-none border-none bg-transparent ${focusIndex === idx ? "scale-105 text-[#FFFF55]" : "opacity-80 text-white"}`}
>
<span className="text-xl">
{item.name}
</span>
<span className="text-[10px] text-[#A0A0A0] uppercase tracking-widest mt-1">
{item.desc}
</span>
</div>
))}
</div>
<button
onMouseEnter={() => setFocusIndex(contributors.length)}
onClick={() => {
playSfx("close_click.wav");
onClose();
}}
className={`mt-6 w-56 h-12 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusIndex === contributors.length ? "text-[#FFFF55]" : "text-white"}`}
style={{
backgroundImage: focusIndex === contributors.length
? "url('/images/button_highlighted.png')"
: "url('/images/Button_Background.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
Close
</button>
</div>
</motion.div>
);
}
+1 -12
View File
@@ -3,7 +3,7 @@ import { motion } from "framer-motion";
import { useUI, useConfig, useAudio, useGame } from "../../context/LauncherContext";
const HomeView = memo(function HomeView() {
const { setActiveView, setShowCredits, setShowSpecialThanks, focusSection, onNavigateToSkin } = useUI();
const { setActiveView, setShowCredits, focusSection, onNavigateToSkin } = useUI();
const { profile, legacyMode } = useConfig();
const { playClickSound, playSfx } = useAudio();
const { handleLaunch, isGameRunning, editions, installs, toggleInstall, downloadProgress, downloadingId } = useGame();
@@ -155,17 +155,6 @@ const HomeView = memo(function HomeView() {
>
EMERALD TEAM
</button>
<button
onClick={() => {
if (isFocusedSection) {
playSfx("orb.ogg");
setShowSpecialThanks(true);
}
}}
className={`text-white/60 hover:text-[#FFFF55] text-xs mc-text-shadow tracking-[0.2em] transition-colors bg-transparent border-none outline-none ${!isFocusedSection ? "cursor-default pointer-events-none" : ""}`}
>
SPECIAL THANKS
</button>
</div>
)}
</motion.div>
+8 -8
View File
@@ -207,13 +207,13 @@ const SettingsView = memo(function SettingsView() {
if (currentSubMenu === "main") {
items.push({
id: "audio_menu",
label: "Audio",
label: "Audio Settings",
type: "button",
onClick: () => { playClickSound(); setCurrentSubMenu("audio"); setFocusIndex(0); },
});
items.push({
id: "video_menu",
label: "User Interface",
label: "Video Settings",
type: "button",
onClick: () => { playClickSound(); setCurrentSubMenu("video"); setFocusIndex(0); },
});
@@ -225,23 +225,23 @@ const SettingsView = memo(function SettingsView() {
});
items.push({
id: "launcher_menu",
label: "Options",
label: "Launcher Settings",
type: "button",
onClick: () => { playClickSound(); setCurrentSubMenu("launcher"); setFocusIndex(0); },
});
} else if (currentSubMenu === "audio") {
items.push({
id: "music",
label: `Music: ${musicVolume ?? 50}%`,
label: `Music: ${musicVolume}%`,
type: "slider",
value: musicVolume ?? 50,
value: musicVolume,
onChange: setMusicVolume,
});
items.push({
id: "sfx",
label: `SFX: ${sfxVolume ?? 100}%`,
label: `SFX: ${sfxVolume}%`,
type: "slider",
value: sfxVolume ?? 100,
value: sfxVolume,
onChange: setSfxVolume,
});
items.push({
@@ -480,7 +480,7 @@ const SettingsView = memo(function SettingsView() {
className="flex flex-col items-center w-full max-w-2xl outline-none"
>
<h2 className="text-2xl text-white mc-text-shadow mt-2 mb-4 border-b-2 border-[#373737] pb-2 w-[40%] max-w-[200px] text-center tracking-widest uppercase opacity-80 font-bold whitespace-nowrap px-4">
{currentSubMenu === "main" ? "Settings" : currentSubMenu === "audio" ? "Audio" : currentSubMenu === "video" ? "User Interface" : currentSubMenu === "controls" ? "Controls" : "Options"}
{currentSubMenu === "main" ? "Settings" : currentSubMenu === "audio" ? "Audio Settings" : currentSubMenu === "video" ? "Video Settings" : currentSubMenu === "controls" ? "Controls" : "Launcher"}
</h2>
<div className="w-full max-w-[540px] space-y-2 mb-4 p-6 flex flex-col items-center overflow-y-auto max-h-[55vh]">
+300 -380
View File
@@ -8,7 +8,7 @@ const VersionsView = memo(function VersionsView() {
const { setActiveView } = useUI();
const { profile: selectedProfile, setProfile: setSelectedProfile } = useConfig();
const { playClickSound, playBackSound, playSfx } = useAudio();
const { editions, installs: installedVersions, toggleInstall, handleUninstall: onUninstall, deleteCustomEdition: onDeleteEdition, addCustomEdition: onAddEdition, updateCustomEdition: onUpdateEdition, downloadingId } = useGame();
const { editions, installs: installedVersions, toggleInstall, handleUninstall: onUninstall, deleteCustomEdition: onDeleteEdition, addCustomEdition: onAddEdition, updateCustomEdition: onUpdateEdition, downloadingId, downloadProgress } = useGame();
const [focusRow, setFocusRow] = useState<number>(0);
const [focusCol, setFocusCol] = useState<number>(0);
@@ -35,49 +35,34 @@ const VersionsView = memo(function VersionsView() {
setFocusCol(0);
} else if (e.key === "ArrowRight") {
if (focusRow < editions.length) {
const edition = editions[focusRow];
const isInstalled = installedVersions.includes(edition.id);
const isCustom = edition.id.startsWith("custom_");
const hasCredits = !isCustom && edition.credits;
const id = editions[focusRow].id;
const isInstalled = installedVersions.includes(id);
const isCustom = id.startsWith("custom_");
let maxCol = 1;
if (isInstalled) maxCol = 3;
if (isCustom) maxCol = isInstalled ? 5 : 3;
if (hasCredits) maxCol = Math.max(maxCol, 0); // credits button is at col -1
setFocusCol((prev) => (prev < maxCol ? prev + 1 : prev));
}
} else if (e.key === "ArrowLeft") {
if (focusRow < editions.length) {
const edition = editions[focusRow];
const isCustom = edition.id.startsWith("custom_");
const hasCredits = !isCustom && edition.credits;
if (hasCredits && focusCol > -1) {
setFocusCol(-1);
} else if (focusCol > 0) {
setFocusCol((prev) => prev - 1);
}
} else {
setFocusCol((prev) => (prev > 0 ? prev - 1 : prev));
}
setFocusCol((prev) => (prev > 0 ? prev - 1 : prev));
} else if (e.key === "Enter") {
if (focusRow < editions.length) {
const edition = editions[focusRow];
const isInstalled = installedVersions.includes(edition.id);
const isCustom = edition.id.startsWith("custom_");
if (focusCol === -1) {
// Credits button
if (edition.credits) {
if (focusCol === 0) {
if (isInstalled) {
playClickSound();
window.open(edition.credits.url, '_blank');
}
} else if (focusCol === 1) {
if (!downloadingId) {
setSelectedProfile(edition.id);
} else if (!downloadingId) {
playClickSound();
toggleInstall(edition.id);
}
} else if (focusCol === 1 && !downloadingId) {
playClickSound();
toggleInstall(edition.id);
} else if (focusCol === 2) {
if (isInstalled) {
playClickSound();
@@ -116,10 +101,22 @@ const VersionsView = memo(function VersionsView() {
}
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [editions, focusRow, focusCol, downloadingId, installedVersions, onUninstall, onDeleteEdition, ITEM_COUNT]);
}, [
focusRow,
focusCol,
editions,
installedVersions,
playClickSound,
playBackSound,
setSelectedProfile,
setActiveView,
toggleInstall,
onUninstall,
onDeleteEdition,
ITEM_COUNT,
]);
useEffect(() => {
const el = containerRef.current?.querySelector(
@@ -142,381 +139,304 @@ const VersionsView = memo(function VersionsView() {
</h2>
<div className="w-full max-w-[740px] h-[380px] overflow-y-auto mb-6 p-6 relative">
<div
className="w-full p-6"
style={{
backgroundImage: "url('/images/frame_background.png')",
backgroundSize: "100% 100%",
backgroundRepeat: "no-repeat",
imageRendering: "pixelated",
minHeight: "340px",
}}
>
<div className="flex flex-col gap-3">
{editions.map((edition: any, i: number) => {
const isInstalled = installedVersions.includes(edition.id);
const isSelected = selectedProfile === edition.id;
const isRowFocused = focusRow === i;
const isCustom = edition.id.startsWith("custom_");
const isPlaceholder = edition.id === "lmrp_placeholder";
<div className="flex flex-col gap-3">
{editions.map((edition: any, i: number) => {
const isInstalled = installedVersions.includes(edition.id);
const isSelected = selectedProfile === edition.id;
const isRowFocused = focusRow === i;
const isCustom = edition.id.startsWith("custom_");
return (
return (
<div
key={edition.id}
className={`w-full flex items-center transition-all border-none outline-none overflow-hidden`}
style={{
backgroundImage:
isSelected || (isRowFocused && focusCol === 0)
? "url('/images/button_highlighted.png')"
: "url('/images/Button_Background.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<div
key={edition.id}
className={`w-full p-4 flex items-center transition-all border-none outline-none overflow-hidden relative ${isPlaceholder ? 'bg-gray-800/50 border-2 border-gray-600 opacity-50' : isSelected ? 'bg-[#50C878]/20 border-2 border-[#50C878]' :
isRowFocused ? 'bg-white/5 border-2 border-white/50' :
'bg-black/30 border-2 border-transparent hover:bg-white/5'
}`}
data-row={i}
data-col={0}
tabIndex={0}
onMouseEnter={() => {
if (!isPlaceholder) {
setFocusRow(i);
setFocusCol(0);
}
setFocusRow(i);
setFocusCol(0);
}}
onClick={() => {
if (!isPlaceholder && isInstalled) {
if (isInstalled) {
playClickSound();
setSelectedProfile(edition.id);
} else {
toggleInstall(edition.id);
}
}}
style={{
backdropFilter: 'blur(4px)',
cursor: isPlaceholder ? 'not-allowed' : isInstalled ? 'pointer' : 'default',
imageRendering: 'pixelated',
borderRadius: '0'
}}
className="flex-1 p-4 flex items-center cursor-pointer outline-none pl-6 text-left relative"
>
<div className="flex flex-col flex-1">
<div className="flex items-center gap-3 mb-1">
<div className="flex flex-col">
<div className="flex items-center gap-3">
<span
className={`text-xl mc-text-shadow ${isSelected ? "text-[#50C878]" : "text-white"}`}
style={{ imageRendering: 'pixelated' }}
className={`text-2xl mc-text-shadow ${isSelected || (isRowFocused && focusCol === 0) ? "text-[#FFFF55]" : "text-white"}`}
>
{edition.name}
</span>
{isCustom && (
<span
className="text-[10px] bg-[#50C878] text-black px-1 font-bold uppercase mc-text-shadow-none"
style={{ imageRendering: 'pixelated' }}
>
<span className="text-[10px] bg-[#FFFF55] text-black px-1 font-bold uppercase mc-text-shadow-none">
Custom
</span>
)}
{edition.id === "revelations_edition" && (
<>
<span
className="text-[10px] bg-[#50C878] text-white px-1 font-bold uppercase mc-text-shadow-none"
style={{ imageRendering: 'pixelated' }}
>
New
</span>
<span
className="text-[10px] bg-[#FFD700] text-black px-1 font-bold uppercase mc-text-shadow-none"
style={{ imageRendering: 'pixelated' }}
>
Recommended
</span>
</>
)}
{edition.id === "360revived" && (
<span
className="text-[10px] bg-[#50C878] text-white px-1 font-bold uppercase mc-text-shadow-none"
style={{ imageRendering: 'pixelated' }}
>
New
</span>
)}
{edition.id === "lmrp_placeholder" && (
<span
className="text-[10px] bg-gray-500 text-white px-1 font-bold uppercase mc-text-shadow-none"
style={{ imageRendering: 'pixelated' }}
>
Coming Soon
</span>
)}
{!isCustom && edition.credits && (
<span
className="text-xs text-[#B0B0B0] mc-text-shadow"
style={{ imageRendering: 'pixelated' }}
>
by {edition.credits.developer}
</span>
)}
</div>
<div
className="text-sm text-[#E0E0E0] mc-text-shadow"
style={{ imageRendering: 'pixelated' }}
>
<span className="text-base text-[#E0E0E0] mc-text-shadow truncate w-full">
{edition.desc}
</div>
</span>
</div>
</div>
{!isPlaceholder && (
<div className="flex items-center gap-2">
{!isCustom && edition.credits && (
<button
data-row={i}
data-col={-1}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(-1);
}}
onClick={(e) => {
e.stopPropagation();
playClickSound();
window.open(edition.credits.url, '_blank');
}}
className={`mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all`}
style={{
backgroundImage:
isRowFocused && focusCol === -1
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
title={`Credits: ${edition.credits.developer} (${edition.credits.platform})`}
>
{edition.credits?.platform === "codeberg" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
className="text-white drop-shadow-md"
>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-white drop-shadow-md"
>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
)}
</button>
)}
{!isInstalled ? (
<button
data-row={i}
data-col={1}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(1);
}}
onClick={(e) => {
e.stopPropagation();
if (!downloadingId) {
playClickSound();
toggleInstall(edition.id);
}
}}
className={`mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all ${downloadingId === edition.id ? "opacity-100" : downloadingId ? "opacity-50" : ""}`}
style={{
backgroundImage:
isRowFocused && focusCol === 1
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
{downloadingId === edition.id ? (
<img
src="/images/loading.gif"
alt="Loading"
className="w-6 h-6 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
) : (
<img
src="/images/Download_Icon.png"
alt="Download"
className="w-6 h-6 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
)}
</button>
<div className="flex items-center gap-3 p-3 pr-6">
{!isInstalled ? (
<button
data-row={i}
data-col={1}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(1);
}}
onClick={(e) => {
e.stopPropagation();
if (!downloadingId) {
playClickSound();
toggleInstall(edition.id);
}
}}
className={`mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all ${downloadingId === edition.id ? "opacity-100" : downloadingId ? "opacity-50" : ""}`}
style={{
backgroundImage:
isRowFocused && focusCol === 1
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
{downloadingId === edition.id ? (
<div className="flex flex-col items-center justify-center">
<span className="text-[10px] text-white font-bold leading-none">
{Math.floor(downloadProgress || 0)}%
</span>
</div>
) : (
<>
<button
data-row={i}
data-col={2}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(2);
}}
onClick={(e) => {
e.stopPropagation();
playClickSound();
TauriService.openInstanceFolder(edition.id);
}}
className="mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === 2
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<img
src="/images/Folder_Icon.png"
alt="Folder"
className="w-6 h-6 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
</button>
<button
data-row={i}
data-col={3}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(3);
}}
onClick={(e) => {
e.stopPropagation();
playBackSound();
onUninstall(edition.id);
}}
className="mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === 3
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="square"
className="text-white drop-shadow-md"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</>
<img
src="/images/Download_Icon.png"
alt="Download"
className="w-8 h-8 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
)}
{isCustom && (
<>
<button
data-row={i}
data-col={isInstalled ? 4 : 2}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(isInstalled ? 4 : 2);
}}
onClick={(e) => {
e.stopPropagation();
playClickSound();
setEditingEdition(edition);
setIsImportModalOpen(true);
}}
className="mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === (isInstalled ? 4 : 2)
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="square"
className="text-white drop-shadow-md"
>
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
</svg>
</button>
<button
data-row={i}
data-col={isInstalled ? 5 : 3}
onMouseEnter={(e) => {
e.stopPropagation();
setFocusRow(i);
setFocusCol(isInstalled ? 5 : 3);
}}
onClick={(e) => {
e.stopPropagation();
playBackSound();
onDeleteEdition(edition.id);
}}
className="mc-sq-btn w-8 h-8 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === (isInstalled ? 5 : 3)
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="square"
className="text-red-500 drop-shadow-md"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</>
)}
</div>
</button>
) : (
<>
<button
data-row={i}
data-col={1}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(1);
}}
onClick={(e) => {
e.stopPropagation();
if (!downloadingId) {
playClickSound();
toggleInstall(edition.id);
}
}}
className={`mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all ${downloadingId === edition.id ? "opacity-100" : downloadingId ? "opacity-50" : ""}`}
style={{
backgroundImage:
isRowFocused && focusCol === 1
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
{downloadingId === edition.id ? (
<div className="flex flex-col items-center justify-center">
<span className="text-[10px] text-white font-bold leading-none">
{Math.floor(downloadProgress || 0)}%
</span>
</div>
) : (
<img
src="/images/Update_Icon.png"
alt="Update"
className="w-8 h-8 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
)}
</button>
<button
data-row={i}
data-col={2}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(2);
}}
onClick={(e) => {
e.stopPropagation();
playClickSound();
TauriService.openInstanceFolder(edition.id);
}}
className="mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === 2
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<img
src="/images/Folder_Icon.png"
alt="Folder"
className="w-8 h-8 object-contain pointer-events-none drop-shadow-md"
style={{ imageRendering: "pixelated" }}
loading="lazy"
decoding="async"
/>
</button>
<button
data-row={i}
data-col={3}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(3);
}}
onClick={(e) => {
e.stopPropagation();
playBackSound();
onUninstall(edition.id);
}}
className="mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === 3
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="square"
className="text-white drop-shadow-md"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</>
)}
{isCustom && (
<>
<button
data-row={i}
data-col={isInstalled ? 4 : 2}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(isInstalled ? 4 : 2);
}}
onClick={(e) => {
e.stopPropagation();
playClickSound();
setEditingEdition(edition);
setIsImportModalOpen(true);
}}
className="mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === (isInstalled ? 4 : 2)
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="square"
className="text-white drop-shadow-md"
>
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
</svg>
</button>
<button
data-row={i}
data-col={isInstalled ? 5 : 3}
onMouseEnter={() => {
setFocusRow(i);
setFocusCol(isInstalled ? 5 : 3);
}}
onClick={(e) => {
e.stopPropagation();
playBackSound();
onDeleteEdition(edition.id);
}}
className="mc-sq-btn w-10 h-10 flex items-center justify-center outline-none border-none transition-all"
style={{
backgroundImage:
isRowFocused && focusCol === (isInstalled ? 5 : 3)
? "url('/images/Button_Square_Highlighted.png')"
: "url('/images/Button_Square.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="square"
className="text-red-500 drop-shadow-md"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</>
)}
</div>
);
})}
</div>
</div>
);
})}
</div>
</div>
@@ -532,7 +452,7 @@ const VersionsView = memo(function VersionsView() {
playClickSound();
setIsImportModalOpen(true);
}}
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusRow === editions.length ? "text-[#50C878]" : "text-white"}`}
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusRow === editions.length ? "text-[#FFFF55]" : "text-white"}`}
style={{
backgroundImage:
focusRow === editions.length
@@ -556,7 +476,7 @@ const VersionsView = memo(function VersionsView() {
playBackSound();
setActiveView("main");
}}
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusRow === editions.length + 1 ? "text-[#50C878]" : "text-white"}`}
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none ${focusRow === editions.length + 1 ? "text-[#FFFF55]" : "text-white"}`}
style={{
backgroundImage:
focusRow === editions.length + 1