mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-24 17:11:00 +00:00
v1.2.0 (#33)
This commit is contained in:
@@ -48,17 +48,37 @@ const HomeView = memo(function HomeView() {
|
||||
: isGameRunning
|
||||
? stopGame
|
||||
: isDownloading
|
||||
? () => { }
|
||||
? () => {}
|
||||
: isInstalled
|
||||
? handleLaunch
|
||||
: () => toggleInstall(profile),
|
||||
isDanger: isGameRunning,
|
||||
disabled: isDownloading,
|
||||
},
|
||||
{ label: "Help & Options", action: () => setActiveView("settings"), disabled: false, id: "settings" },
|
||||
{ label: "Versions", action: () => setActiveView("versions"), disabled: false, id: "versions" },
|
||||
{ label: "Workshop", action: () => setActiveView("workshop"), disabled: false, id: "workshop" },
|
||||
{ label: "Developer Tools", action: () => setActiveView("devtools"), disabled: false, id: "devtools" },
|
||||
{
|
||||
label: "Help & Options",
|
||||
action: () => setActiveView("settings"),
|
||||
disabled: false,
|
||||
id: "settings",
|
||||
},
|
||||
{
|
||||
label: "Versions",
|
||||
action: () => setActiveView("versions"),
|
||||
disabled: false,
|
||||
id: "versions",
|
||||
},
|
||||
{
|
||||
label: "Workshop",
|
||||
action: () => setActiveView("workshop"),
|
||||
disabled: false,
|
||||
id: "workshop",
|
||||
},
|
||||
{
|
||||
label: "Developer Tools",
|
||||
action: () => setActiveView("devtools"),
|
||||
disabled: false,
|
||||
id: "devtools",
|
||||
},
|
||||
],
|
||||
[
|
||||
isDownloading,
|
||||
@@ -111,7 +131,9 @@ const HomeView = memo(function HomeView() {
|
||||
{buttons.map((btn: any, i: number) => (
|
||||
<div key={i} className="relative w-full group">
|
||||
<button
|
||||
onMouseEnter={() => isFocusedSection && !btn.disabled && setMenuFocus(i)}
|
||||
onMouseEnter={() =>
|
||||
isFocusedSection && !btn.disabled && setMenuFocus(i)
|
||||
}
|
||||
onMouseLeave={() => setMenuFocus(null)}
|
||||
onClick={() => {
|
||||
if (isFocusedSection && !btn.disabled) {
|
||||
@@ -133,14 +155,19 @@ const HomeView = memo(function HomeView() {
|
||||
}}
|
||||
>
|
||||
<div className="w-full h-full flex items-center justify-center relative">
|
||||
<span>{btn.label}</span>
|
||||
{btn.id === "versions" && Object.values(updatesAvailable || {}).some((v) => v) && (
|
||||
<img
|
||||
src="/images/Update_Icon.png"
|
||||
className="absolute right-4 w-6 h-6 object-contain"
|
||||
style={{ imageRendering: "pixelated", filter: "drop-shadow(0 0 2px rgba(255, 255, 0, 0.8)) sepia(100%) saturate(500%) hue-rotate(5deg) brightness(1.2)" }}
|
||||
/>
|
||||
)}
|
||||
<span>{btn.label}</span>
|
||||
{btn.id === "versions" &&
|
||||
Object.values(updatesAvailable || {}).some((v) => v) && (
|
||||
<img
|
||||
src="/images/Update_Icon.png"
|
||||
className="absolute right-4 w-6 h-6 object-contain"
|
||||
style={{
|
||||
imageRendering: "pixelated",
|
||||
filter:
|
||||
"drop-shadow(0 0 2px rgba(255, 255, 0, 0.8)) sepia(100%) saturate(500%) hue-rotate(5deg) brightness(1.2)",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -150,7 +177,7 @@ const HomeView = memo(function HomeView() {
|
||||
<div className="pt-4 flex flex-col items-center w-full gap-3">
|
||||
<div className="flex gap-8">
|
||||
<a
|
||||
href="https://discord.gg/NPMp9JhGYH"
|
||||
href="https://discord.gg/cQVKhQXcCx"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ import {
|
||||
ScreenshotService,
|
||||
ScreenshotInfo,
|
||||
} from "../../services/ScreenshotService";
|
||||
import { ScreenshotImage } from "../common/ScreenshotImage";
|
||||
const ScreenshotsView = memo(function ScreenshotsView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { playPressSound, playBackSound } = useAudio();
|
||||
@@ -207,15 +208,12 @@ const ScreenshotsView = memo(function ScreenshotsView() {
|
||||
}}
|
||||
>
|
||||
<div className="w-full h-full relative overflow-hidden bg-black/50">
|
||||
<img
|
||||
src={`screenshots://localhost/${ss.path.replace(/\\/g, "/")}`}
|
||||
<ScreenshotImage
|
||||
path={ss.path}
|
||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||
loading="lazy"
|
||||
alt={ss.name}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/images/Folder_Icon.png";
|
||||
}}
|
||||
fallbackSrc="/images/Folder_Icon.png"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-60" />
|
||||
|
||||
@@ -288,13 +286,10 @@ const ScreenshotsView = memo(function ScreenshotsView() {
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="relative w-full aspect-video bg-black/60 overflow-hidden border border-[#444] rounded-sm">
|
||||
<img
|
||||
src={`screenshots://localhost/${selectedScreenshot.path.replace(/\\/g, "/")}`}
|
||||
<ScreenshotImage
|
||||
path={selectedScreenshot.path}
|
||||
className="w-full h-full object-contain"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/images/Pack_Icon.png";
|
||||
}}
|
||||
fallbackSrc="/images/Pack_Icon.png"
|
||||
/>
|
||||
<div className="absolute bottom-4 left-6 right-6 flex items-end justify-between pointer-events-none">
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@@ -2,16 +2,56 @@ 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";
|
||||
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 } = useConfig();
|
||||
const { currentTrack, setCurrentTrack, tracks, playPressSound, playBackSound } = useAudio();
|
||||
const { isGameRunning, stopGame, isRunnerDownloading, runnerDownloadProgress, downloadRunner } = useGame();
|
||||
const {
|
||||
vfxEnabled,
|
||||
setVfxEnabled,
|
||||
animationsEnabled,
|
||||
setAnimationsEnabled,
|
||||
musicVol: musicVolume,
|
||||
setMusicVol: setMusicVolume,
|
||||
sfxVol: sfxVolume,
|
||||
setSfxVol: setSfxVolume,
|
||||
layout,
|
||||
setLayout,
|
||||
linuxRunner,
|
||||
setLinuxRunner,
|
||||
perfBoost,
|
||||
setPerfBoost,
|
||||
rpcEnabled,
|
||||
setRpcEnabled,
|
||||
legacyMode,
|
||||
setLegacyMode,
|
||||
mangohudEnabled,
|
||||
setMangohudEnabled,
|
||||
} = useConfig();
|
||||
const {
|
||||
currentTrack,
|
||||
setCurrentTrack,
|
||||
tracks,
|
||||
playPressSound,
|
||||
playBackSound,
|
||||
} = useAudio();
|
||||
const {
|
||||
isGameRunning,
|
||||
stopGame,
|
||||
isRunnerDownloading,
|
||||
runnerDownloadProgress,
|
||||
downloadRunner,
|
||||
} = useGame();
|
||||
const { isLinux, isMac } = usePlatform();
|
||||
const [focusIndex, setFocusIndex] = useState<number | null>(null);
|
||||
const [currentSubMenu, setCurrentSubMenu] = useState<"main" | "audio" | "video" | "controls" | "launcher">("main");
|
||||
const [currentSubMenu, setCurrentSubMenu] = useState<
|
||||
"main" | "audio" | "video" | "controls" | "launcher"
|
||||
>("main");
|
||||
const [runners, setRunners] = useState<Runner[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -53,6 +93,11 @@ const SettingsView = memo(function SettingsView() {
|
||||
setLegacyMode(!legacyMode);
|
||||
};
|
||||
|
||||
const handleMangohudToggle = () => {
|
||||
playPressSound();
|
||||
setMangohudEnabled(!mangohudEnabled);
|
||||
};
|
||||
|
||||
const handleRunnerToggle = () => {
|
||||
playPressSound();
|
||||
if (runners.length === 0) return;
|
||||
@@ -69,35 +114,37 @@ const SettingsView = memo(function SettingsView() {
|
||||
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';
|
||||
const dialog = document.createElement("div");
|
||||
dialog.className =
|
||||
"fixed inset-0 bg-black/80 flex items-center justify-center z-50";
|
||||
dialog.innerHTML = `
|
||||
<div class="relative p-8 max-w-md mx-4" style="background-image: url('/images/frame_background.png'); background-size: 100% 100%; background-repeat: no-repeat; image-rendering: pixelated;">
|
||||
<h3 class="text-2xl font-bold text-white mb-4 text-center" style="text-shadow: 2px 2px 0px rgba(0,0,0,0.8)">Reset Setup</h3>
|
||||
<p class="text-white mb-6 text-center">Are you sure you want to reset launcher setup?</p>
|
||||
<div class="flex gap-4 justify-center">
|
||||
<button id="reset-yes" class="mc-sq-btn px-6 py-3 text-white hover:scale-105 active:scale-95 transition-transform">Yes</button>
|
||||
<button id="reset-no" class="mc-sq-btn px-6 py-3 text-white hover:scale-105 active:scale-95 transition-transform">No</button>
|
||||
<div class="w-[420px] p-4 flex flex-col items-center mc-options-bg">
|
||||
<h3 class="text-2xl font-bold text-[#333333] mb-4 text-left w-full px-4 mc-text-shadow">Reset Setup</h3>
|
||||
<p class="text-[#333333] mb-8 text-left w-full px-4">Are you sure you want to reset launcher setup?</p>
|
||||
<div class="flex flex-col gap-3 w-full px-4">
|
||||
<button id="reset-cancel" class="w-full h-10 flex items-center justify-center text-lg mc-text-shadow text-white hover:text-[#ffff00]" style="background-image: url('/images/Button_Background.png'); background-size: 100% 100%; image-rendering: pixelated; border: none; cursor: pointer;" onmouseenter="this.style.backgroundImage='url(/images/button_highlighted.png)'" onmouseleave="this.style.backgroundImage='url(/images/Button_Background.png)'">Cancel</button>
|
||||
<button id="reset-ok" class="w-full h-10 flex items-center justify-center text-lg mc-text-shadow text-white hover:text-[#ffff00]" style="background-image: url('/images/Button_Background.png'); background-size: 100% 100%; image-rendering: pixelated; border: none; cursor: pointer;" onmouseenter="this.style.backgroundImage='url(/images/button_highlighted.png)'" onmouseleave="this.style.backgroundImage='url(/images/Button_Background.png)'">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
const handleYes = () => {
|
||||
const handleOk = () => {
|
||||
document.body.removeChild(dialog);
|
||||
showSecondConfirmation();
|
||||
};
|
||||
|
||||
const handleNo = () => {
|
||||
const handleCancel = () => {
|
||||
document.body.removeChild(dialog);
|
||||
};
|
||||
|
||||
dialog.querySelector('#reset-yes')?.addEventListener('click', handleYes);
|
||||
dialog.querySelector('#reset-no')?.addEventListener('click', handleNo);
|
||||
dialog.querySelector("#reset-ok")?.addEventListener("click", handleOk);
|
||||
dialog
|
||||
.querySelector("#reset-cancel")
|
||||
?.addEventListener("click", handleCancel);
|
||||
|
||||
dialog.addEventListener('click', (e) => {
|
||||
dialog.addEventListener("click", (e) => {
|
||||
if (e.target === dialog) {
|
||||
document.body.removeChild(dialog);
|
||||
}
|
||||
@@ -105,43 +152,48 @@ const SettingsView = memo(function SettingsView() {
|
||||
};
|
||||
|
||||
const showSecondConfirmation = () => {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'fixed inset-0 bg-black/80 flex items-center justify-center z-50';
|
||||
const dialog = document.createElement("div");
|
||||
dialog.className =
|
||||
"fixed inset-0 bg-black/80 flex items-center justify-center z-50";
|
||||
dialog.innerHTML = `
|
||||
<div class="relative p-8 max-w-md mx-4" style="background-image: url('/images/frame_background.png'); background-size: 100% 100%; background-repeat: no-repeat; image-rendering: pixelated;">
|
||||
<h3 class="text-2xl font-bold text-yellow-400 mb-4 text-center" style="text-shadow: 2px 2px 0px rgba(0,0,0,0.8)">CONFIRM RESET</h3>
|
||||
<div class="text-white mb-6 text-left">
|
||||
<div class="w-[420px] p-4 flex flex-col items-center mc-options-bg">
|
||||
<h3 class="text-2xl font-bold text-[#333333] mb-2 text-left w-full px-4 mc-text-shadow">CONFIRM RESET</h3>
|
||||
<div class="text-[#333333] mb-6 text-left w-full px-4">
|
||||
<p class="mb-2">⚠️ This will:</p>
|
||||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||||
<ul class="list-none space-y-1 text-sm">
|
||||
<li>Clear all launcher settings</li>
|
||||
<li>Reset your username</li>
|
||||
<li>Show setup screen again</li>
|
||||
<li>Require reconfiguration</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-yellow-400 font-bold">This action cannot be undone!</p>
|
||||
<p class="mt-3 text-[#333333] font-bold">This action cannot be undone!</p>
|
||||
</div>
|
||||
<div class="flex gap-4 justify-center">
|
||||
<button id="reset-final-yes" class="mc-sq-btn px-6 py-3 text-yellow-400 hover:scale-105 active:scale-95 transition-transform">YES, RESET</button>
|
||||
<button id="reset-final-no" class="mc-sq-btn px-6 py-3 text-white hover:scale-105 active:scale-95 transition-transform">Cancel</button>
|
||||
<div class="flex flex-col gap-3 w-full px-4">
|
||||
<button id="reset-final-cancel" class="w-full h-10 flex items-center justify-center text-lg mc-text-shadow text-white hover:text-[#ffff00]" style="background-image: url('/images/Button_Background.png'); background-size: 100% 100%; image-rendering: pixelated; border: none; cursor: pointer;" onmouseenter="this.style.backgroundImage='url(/images/button_highlighted.png)'" onmouseleave="this.style.backgroundImage='url(/images/Button_Background.png)'">Cancel</button>
|
||||
<button id="reset-final-ok" class="w-full h-10 flex items-center justify-center text-lg mc-text-shadow text-white hover:text-[#ffff00]" style="background-image: url('/images/Button_Background.png'); background-size: 100% 100%; image-rendering: pixelated; border: none; cursor: pointer;" onmouseenter="this.style.backgroundImage='url(/images/button_highlighted.png)'" onmouseleave="this.style.backgroundImage='url(/images/Button_Background.png)'">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
const handleFinalYes = () => {
|
||||
const handleFinalOk = () => {
|
||||
document.body.removeChild(dialog);
|
||||
performReset();
|
||||
};
|
||||
|
||||
const handleFinalNo = () => {
|
||||
const handleFinalCancel = () => {
|
||||
document.body.removeChild(dialog);
|
||||
};
|
||||
|
||||
dialog.querySelector('#reset-final-yes')?.addEventListener('click', handleFinalYes);
|
||||
dialog.querySelector('#reset-final-no')?.addEventListener('click', handleFinalNo);
|
||||
dialog
|
||||
.querySelector("#reset-final-ok")
|
||||
?.addEventListener("click", handleFinalOk);
|
||||
dialog
|
||||
.querySelector("#reset-final-cancel")
|
||||
?.addEventListener("click", handleFinalCancel);
|
||||
|
||||
dialog.addEventListener('click', (e) => {
|
||||
dialog.addEventListener("click", (e) => {
|
||||
if (e.target === dialog) {
|
||||
document.body.removeChild(dialog);
|
||||
}
|
||||
@@ -149,13 +201,10 @@ const SettingsView = memo(function SettingsView() {
|
||||
};
|
||||
|
||||
const performReset = () => {
|
||||
// Clear all localStorage data
|
||||
localStorage.clear();
|
||||
|
||||
// Set setup as not completed
|
||||
localStorage.setItem('lce-setup-completed', 'false');
|
||||
localStorage.setItem("lce-setup-completed", "false");
|
||||
|
||||
// Force reload to show setup screen
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
@@ -163,11 +212,9 @@ const SettingsView = memo(function SettingsView() {
|
||||
if (tracks && tracks.length > 0) {
|
||||
const fullPath = tracks[currentTrack];
|
||||
if (fullPath) {
|
||||
trackName = fullPath
|
||||
.split("/")
|
||||
.pop()
|
||||
?.replace(".ogg", "")
|
||||
.replace(".wav", "") || "Unknown";
|
||||
trackName =
|
||||
fullPath.split("/").pop()?.replace(".ogg", "").replace(".wav", "") ||
|
||||
"Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,20 +223,20 @@ const SettingsView = memo(function SettingsView() {
|
||||
|
||||
type SettingsItem =
|
||||
| {
|
||||
id: string;
|
||||
label: string;
|
||||
type: "slider";
|
||||
value: number;
|
||||
onChange: (val: any) => void;
|
||||
}
|
||||
id: string;
|
||||
label: string;
|
||||
type: "slider";
|
||||
value: number;
|
||||
onChange: (val: any) => void;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
label: string;
|
||||
type: "button";
|
||||
onClick: () => void;
|
||||
small?: boolean;
|
||||
color?: string;
|
||||
};
|
||||
id: string;
|
||||
label: string;
|
||||
type: "button";
|
||||
onClick: () => void;
|
||||
small?: boolean;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
const settingsItems = useMemo<SettingsItem[]>(() => {
|
||||
const items: SettingsItem[] = [];
|
||||
@@ -199,25 +246,41 @@ const SettingsView = memo(function SettingsView() {
|
||||
id: "audio_menu",
|
||||
label: "Audio",
|
||||
type: "button",
|
||||
onClick: () => { playPressSound(); setCurrentSubMenu("audio"); setFocusIndex(0); },
|
||||
onClick: () => {
|
||||
playPressSound();
|
||||
setCurrentSubMenu("audio");
|
||||
setFocusIndex(0);
|
||||
},
|
||||
});
|
||||
items.push({
|
||||
id: "video_menu",
|
||||
label: "Video",
|
||||
type: "button",
|
||||
onClick: () => { playPressSound(); setCurrentSubMenu("video"); setFocusIndex(0); },
|
||||
onClick: () => {
|
||||
playPressSound();
|
||||
setCurrentSubMenu("video");
|
||||
setFocusIndex(0);
|
||||
},
|
||||
});
|
||||
items.push({
|
||||
id: "controls_menu",
|
||||
label: "Controls",
|
||||
type: "button",
|
||||
onClick: () => { playPressSound(); setCurrentSubMenu("controls"); setFocusIndex(0); },
|
||||
onClick: () => {
|
||||
playPressSound();
|
||||
setCurrentSubMenu("controls");
|
||||
setFocusIndex(0);
|
||||
},
|
||||
});
|
||||
items.push({
|
||||
id: "launcher_menu",
|
||||
label: "Launcher",
|
||||
type: "button",
|
||||
onClick: () => { playPressSound(); setCurrentSubMenu("launcher"); setFocusIndex(0); },
|
||||
onClick: () => {
|
||||
playPressSound();
|
||||
setCurrentSubMenu("launcher");
|
||||
setFocusIndex(0);
|
||||
},
|
||||
});
|
||||
} else if (currentSubMenu === "audio") {
|
||||
items.push({
|
||||
@@ -288,6 +351,12 @@ const SettingsView = memo(function SettingsView() {
|
||||
type: "button",
|
||||
onClick: handleRunnerToggle,
|
||||
});
|
||||
items.push({
|
||||
id: "mangohud",
|
||||
label: `MangoHud: ${mangohudEnabled ? "ON" : "OFF"}`,
|
||||
type: "button",
|
||||
onClick: handleMangohudToggle,
|
||||
});
|
||||
items.push({
|
||||
id: "download_runner",
|
||||
label: isRunnerDownloading
|
||||
@@ -296,7 +365,10 @@ const SettingsView = memo(function SettingsView() {
|
||||
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");
|
||||
downloadRunner(
|
||||
"GE-Proton9-25",
|
||||
"https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton9-25/GE-Proton9-25.tar.gz",
|
||||
);
|
||||
}
|
||||
},
|
||||
small: true,
|
||||
@@ -349,6 +421,7 @@ const SettingsView = memo(function SettingsView() {
|
||||
animationsEnabled,
|
||||
layout,
|
||||
isLinux,
|
||||
mangohudEnabled,
|
||||
selectedRunnerName,
|
||||
isRunnerDownloading,
|
||||
runnerDownloadProgress,
|
||||
@@ -363,6 +436,7 @@ const SettingsView = memo(function SettingsView() {
|
||||
handleLayoutToggle,
|
||||
handleRunnerToggle,
|
||||
handlePerfToggle,
|
||||
handleMangohudToggle,
|
||||
handleResetSetup,
|
||||
stopGame,
|
||||
downloadRunner,
|
||||
@@ -435,11 +509,16 @@ const SettingsView = memo(function SettingsView() {
|
||||
backgroundImage: "url('/images/Button_Background2.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated" as const,
|
||||
color: focusIndex === index ? "#FFFF55" : "white",
|
||||
color: focusIndex === index ? "#ffff00" : "white",
|
||||
});
|
||||
|
||||
const isToggleOption = (label: string): boolean => {
|
||||
return label.includes("ON") || label.includes("OFF") || label.includes("Enabled") || label.includes("Disabled");
|
||||
return (
|
||||
label.includes("ON") ||
|
||||
label.includes("OFF") ||
|
||||
label.includes("Enabled") ||
|
||||
label.includes("Disabled")
|
||||
);
|
||||
};
|
||||
|
||||
const getToggleState = (label: string): boolean => {
|
||||
@@ -454,10 +533,18 @@ const SettingsView = memo(function SettingsView() {
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
|
||||
className="flex flex-col items-center w-full max-w-2xl outline-none"
|
||||
className="flex flex-col items-center w-full max-w-3xl 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" ? "Video" : currentSubMenu === "controls" ? "Controls" : "Launcher"}
|
||||
{currentSubMenu === "main"
|
||||
? "Settings"
|
||||
: currentSubMenu === "audio"
|
||||
? "Audio"
|
||||
: currentSubMenu === "video"
|
||||
? "Video"
|
||||
: currentSubMenu === "controls"
|
||||
? "Controls"
|
||||
: "Launcher"}
|
||||
</h2>
|
||||
|
||||
{currentSubMenu === "main" ? (
|
||||
@@ -472,11 +559,11 @@ const SettingsView = memo(function SettingsView() {
|
||||
data-index={index}
|
||||
tabIndex={0}
|
||||
onMouseEnter={() => 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"
|
||||
className="relative w-[360px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#ffff00] shrink-0"
|
||||
style={getSliderStyle(index)}
|
||||
>
|
||||
<span
|
||||
className={`absolute z-10 text-xl mc-text-shadow pointer-events-none transition-colors tracking-widest ${focusIndex === index ? "text-[#FFFF55]" : "text-white"}`}
|
||||
className={`absolute z-10 text-xl mc-text-shadow pointer-events-none transition-colors tracking-widest ${focusIndex === index ? "text-[#ffff00]" : "text-white"}`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
@@ -505,14 +592,15 @@ const SettingsView = memo(function SettingsView() {
|
||||
data-index={index}
|
||||
onMouseEnter={() => setFocusIndex(index)}
|
||||
onClick={item.onClick}
|
||||
className={`w-[360px] h-10 flex items-center justify-center px-4 relative z-30 transition-colors outline-none border-none shrink-0 ${isRed
|
||||
? focusIndex === index
|
||||
? "text-red-400"
|
||||
: "text-red-200"
|
||||
: focusIndex === index
|
||||
? "text-[#FFFF55]"
|
||||
: "text-white"
|
||||
} ${isRed ? "hover:text-red-500" : "hover:text-[#FFFF55]"}`}
|
||||
className={`w-[360px] h-10 flex items-center justify-center px-4 relative z-30 transition-colors outline-none border-none shrink-0 ${
|
||||
isRed
|
||||
? focusIndex === index
|
||||
? "text-red-400"
|
||||
: "text-red-200"
|
||||
: focusIndex === index
|
||||
? "text-[#ffff00]"
|
||||
: "text-white"
|
||||
} ${isRed ? "hover:text-red-500" : "hover:text-[#ffff00]"}`}
|
||||
style={getItemStyle(index)}
|
||||
>
|
||||
<span
|
||||
@@ -525,15 +613,7 @@ const SettingsView = memo(function SettingsView() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="min-w-[420px] w-fit p-6 flex flex-col items-center"
|
||||
style={{
|
||||
backgroundImage: "url('/images/background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
backgroundRepeat: "no-repeat",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<div className="min-w-[420px] w-fit p-4 flex flex-col items-center mc-options-bg">
|
||||
<div className="w-full space-y-3 flex flex-col items-center overflow-y-auto max-h-[50vh] py-2">
|
||||
{settingsItems.map((item, index) => {
|
||||
if (item.id === "back") return null;
|
||||
@@ -545,11 +625,11 @@ const SettingsView = memo(function SettingsView() {
|
||||
data-index={index}
|
||||
tabIndex={0}
|
||||
onMouseEnter={() => 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"
|
||||
className="relative w-[360px] h-10 flex items-center justify-center cursor-pointer transition-all outline-none border-none hover:text-[#ffff00] shrink-0"
|
||||
style={getSliderStyle(index)}
|
||||
>
|
||||
<span
|
||||
className={`absolute z-10 text-xl pointer-events-none transition-colors tracking-widest ${focusIndex === index ? "text-[#FFFF55]" : item.id === "music" || item.id === "sfx" ? "text-white" : "text-[#2a2a2a]"}`}
|
||||
className={`absolute z-10 text-xl pointer-events-none transition-colors tracking-widest ${focusIndex === index ? "text-[#ffff00]" : item.id === "music" || item.id === "sfx" ? "text-white" : "text-[#2a2a2a]"}`}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
@@ -560,7 +640,9 @@ const SettingsView = memo(function SettingsView() {
|
||||
max="100"
|
||||
step="1"
|
||||
value={item.value}
|
||||
onChange={(e) => item.onChange(parseInt(e.target.value))}
|
||||
onChange={(e) =>
|
||||
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"
|
||||
/>
|
||||
@@ -580,21 +662,35 @@ const SettingsView = memo(function SettingsView() {
|
||||
data-index={index}
|
||||
onMouseEnter={() => setFocusIndex(index)}
|
||||
onClick={item.onClick}
|
||||
className={`w-[360px] h-10 flex items-center justify-between px-4 relative z-30 transition-all outline-none border-none shrink-0 rounded ${focusIndex === index ? "bg-black/10" : "bg-transparent"} ${isRed ? "text-red-600" : "text-[#2a2a2a]"} hover:bg-black/15`}
|
||||
className={`w-[360px] h-10 flex items-center pl-1.5 pr-4 relative z-30 outline-none border-none shrink-0 rounded ${focusIndex === index ? "text-[#ffff00]" : isRed ? "text-red-600" : "text-[#333333]"}`}
|
||||
>
|
||||
{isToggle && (
|
||||
<div className="relative w-6 h-6 mr-3 shrink-0">
|
||||
<img
|
||||
src={
|
||||
focusIndex === index
|
||||
? "/images/checkbox_highlighted.png"
|
||||
: "/images/checkbox.png"
|
||||
}
|
||||
alt="checkbox"
|
||||
className="w-full h-full object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
{toggleState && (
|
||||
<img
|
||||
src="/images/check.png"
|
||||
alt="checked"
|
||||
className="absolute inset-0 w-full h-full object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<span
|
||||
className={`tracking-widest uppercase ${isSmall ? "text-xs" : item.label.length > 25 ? "text-base" : "text-lg"} truncate text-left flex-1`}
|
||||
className={`tracking-widest text-xl mc-text-shadow ${isSmall ? "text-xs" : item.label.length > 25 ? "text-base" : "text-lg"} truncate text-left`}
|
||||
>
|
||||
{isToggle ? item.label.split(":")[0] : item.label}
|
||||
</span>
|
||||
{isToggle && (
|
||||
<img
|
||||
src={toggleState ? "/images/Toggle_Switch_On.png" : "/images/Toggle_Switch_Off.png"}
|
||||
alt={toggleState ? "ON" : "OFF"}
|
||||
className="w-12 h-6 object-contain shrink-0 ml-2"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
@@ -612,11 +708,12 @@ const SettingsView = memo(function SettingsView() {
|
||||
data-index={backIndex}
|
||||
onMouseEnter={() => setFocusIndex(backIndex)}
|
||||
onClick={backItem.onClick}
|
||||
className={`w-72 h-10 flex items-center justify-center transition-colors text-xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] mt-4 ${focusIndex === backIndex ? "text-[#FFFF55]" : "text-white"}`}
|
||||
className={`w-40 h-10 flex items-center justify-center transition-colors text-xl mc-text-shadow outline-none border-none hover:text-[#ffff00] mt-4 ${focusIndex === backIndex ? "text-[#ffff00]" : "text-white"}`}
|
||||
style={{
|
||||
backgroundImage: focusIndex === backIndex
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundImage:
|
||||
focusIndex === backIndex
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
|
||||
@@ -46,7 +46,14 @@ const DEFAULT_SKINS: SavedSkin[] = [
|
||||
},
|
||||
{ id: "peter", name: "Peter", url: "/Skins/Peter.png", isSlim: false },
|
||||
{ id: "piebot", name: "piebot", url: "/Skins/piebot.png", isSlim: false },
|
||||
{ id: "andipog", name: "Andi_Pog", url: "/Skins/andi.png", isSlim: false },
|
||||
{ id: "sevenhundred", name: "700", url: "/Skins/700.png", isSlim: false },
|
||||
{
|
||||
id: "prismachunk0",
|
||||
name: "PrismaChunk0",
|
||||
url: "/Skins/PrismaChunk0.png",
|
||||
isSlim: false,
|
||||
},
|
||||
];
|
||||
|
||||
const SkinsView = memo(function SkinsView() {
|
||||
|
||||
@@ -2,12 +2,14 @@ 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";
|
||||
interface DeleteConfirmButtonProps {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
@@ -26,8 +28,9 @@ const DeleteConfirmButton = memo(function DeleteConfirmButton({
|
||||
onClick={onClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className={`w-24 h-10 flex items-center justify-center mc-text-shadow transition-colors ${isDanger ? "text-red-500" : "text-white"
|
||||
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
||||
className={`w-24 h-10 flex items-center justify-center mc-text-shadow transition-colors ${
|
||||
isDanger ? "text-red-500" : "text-white"
|
||||
} ${isHovered ? (isDanger ? "text-red-400" : "text-[#FFFF55]") : ""}`}
|
||||
style={{
|
||||
backgroundImage: isHovered
|
||||
? "url('/images/button_highlighted.png')"
|
||||
@@ -68,6 +71,8 @@ const VersionsView = memo(function VersionsView() {
|
||||
const [focusIndex, setFocusIndex] = useState<number>(0);
|
||||
const [focusBtn, setFocusBtn] = useState<number>(0);
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
const [isSetUidModalOpen, setIsSetUidModalOpen] = useState(false);
|
||||
const [setUidTargetId, setSetUidTargetId] = useState("");
|
||||
const [editingEdition, setEditingEdition] = useState<any>(null);
|
||||
const [initialPath, setInitialPath] = useState<string>("");
|
||||
const [hoveredBtn, setHoveredBtn] = useState<{
|
||||
@@ -166,7 +171,7 @@ const VersionsView = memo(function VersionsView() {
|
||||
toggleInstall,
|
||||
handleCancelDownload,
|
||||
addToSteam,
|
||||
isDayTime
|
||||
isDayTime,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -208,7 +213,7 @@ const VersionsView = memo(function VersionsView() {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 20 }}
|
||||
transition={{ duration: animationsEnabled ? 0.25 : 0 }}
|
||||
className="flex flex-col items-center w-full max-w-2xl outline-none"
|
||||
className="flex flex-col items-center w-full max-w-5xl outline-none"
|
||||
>
|
||||
<h2 className="text-2xl text-white mc-text-shadow mt-2 mb-4 pb-2 w-[40%] max-w-[200px] text-center tracking-widest uppercase font-bold">
|
||||
Versions
|
||||
@@ -229,7 +234,9 @@ const VersionsView = memo(function VersionsView() {
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{editions.map((edition: any, i: number) => {
|
||||
const isInstalled = installedVersions.includes(edition.instanceId);
|
||||
const isInstalled = installedVersions.includes(
|
||||
edition.instanceId,
|
||||
);
|
||||
const hasAnyInstall = installedVersions.length > 0;
|
||||
const isSelected =
|
||||
hasAnyInstall && selectedProfile === edition.instanceId;
|
||||
@@ -242,9 +249,11 @@ const VersionsView = memo(function VersionsView() {
|
||||
<div
|
||||
key={edition.id}
|
||||
data-index={i}
|
||||
className={`w-[calc(100%-16px)] mx-2 flex items-center gap-3 p-2 rounded-sm ${isSelected && !isComingSoon ? "bg-[#404040]/50" : ""
|
||||
} ${isFocused && !isComingSoon ? "ring-2 ring-white" : ""} ${isComingSoon ? "opacity-50 cursor-not-allowed" : ""
|
||||
} relative ${openMenuId === edition.id ? "z-50" : "z-0"}`}
|
||||
className={`w-[calc(100%-16px)] mx-2 flex items-center gap-3 p-2 rounded-sm ${
|
||||
isSelected && !isComingSoon ? "bg-[#404040]/50" : ""
|
||||
} ${isFocused && !isComingSoon ? "ring-2 ring-white" : ""} ${
|
||||
isComingSoon ? "opacity-50 cursor-not-allowed" : ""
|
||||
} relative ${openMenuId === edition.id ? "z-50" : "z-0"}`}
|
||||
onMouseEnter={() => !isComingSoon && setFocusIndex(i)}
|
||||
>
|
||||
<div className="w-6 flex items-center justify-center flex-shrink-0">
|
||||
@@ -280,28 +289,34 @@ const VersionsView = memo(function VersionsView() {
|
||||
onClick={() =>
|
||||
!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" : ""}`}
|
||||
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" : ""}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{edition.logo && (
|
||||
<img
|
||||
src={
|
||||
edition.logo.startsWith("http") ||
|
||||
edition.logo.startsWith("/images")
|
||||
? edition.logo
|
||||
: `screenshots://localhost/${edition.logo.replace(/\\/g, "/")}`
|
||||
}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain flex-shrink-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
)}
|
||||
{edition.logo &&
|
||||
(edition.logo.startsWith("http") ||
|
||||
edition.logo.startsWith("/images") ? (
|
||||
<img
|
||||
src={edition.logo}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain flex-shrink-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
) : (
|
||||
<ScreenshotImage
|
||||
path={edition.logo}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain flex-shrink-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
))}
|
||||
<span
|
||||
className={`text-xl tracking-wide truncate ${isSelected ? "text-white" : "text-black"
|
||||
}`}
|
||||
className={`text-xl tracking-wide truncate ${
|
||||
isSelected ? "text-white" : "text-black"
|
||||
}`}
|
||||
style={{ textShadow: "none" }}
|
||||
>
|
||||
{edition.name}
|
||||
@@ -322,8 +337,9 @@ const VersionsView = memo(function VersionsView() {
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className={`text-base font-medium leading-tight ${isSelected ? "text-[#DDDDDD]" : "text-[#666666]"
|
||||
}`}
|
||||
className={`text-base font-medium leading-tight ${
|
||||
isSelected ? "text-[#DDDDDD]" : "text-[#666666]"
|
||||
}`}
|
||||
>
|
||||
{edition.desc}
|
||||
</p>
|
||||
@@ -340,23 +356,40 @@ const VersionsView = memo(function VersionsView() {
|
||||
handleCancelDownload();
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => setHoveredBtn({ row: i, btn: "main" })}
|
||||
onMouseEnter={() =>
|
||||
setHoveredBtn({ row: i, btn: "main" })
|
||||
}
|
||||
onMouseLeave={() => setHoveredBtn(null)}
|
||||
className={`w-9 h-9 flex items-center justify-center ${(isDownloading || (!!downloadingId && !isInstalled)) ? "opacity-50" : ""
|
||||
}`}
|
||||
className={`w-9 h-9 flex items-center justify-center ${
|
||||
isDownloading || (!!downloadingId && !isInstalled)
|
||||
? "opacity-50"
|
||||
: ""
|
||||
}`}
|
||||
style={{
|
||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === "main") || (focusIndex === i && focusBtn === 0)
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundImage:
|
||||
(hoveredBtn?.row === i &&
|
||||
hoveredBtn?.btn === "main") ||
|
||||
(focusIndex === i && focusBtn === 0)
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={isDownloading ? "/images/Trash_Bin_Icon.png" : "/images/Download_Icon.png"}
|
||||
src={
|
||||
isDownloading
|
||||
? "/images/Trash_Bin_Icon.png"
|
||||
: "/images/Download_Icon.png"
|
||||
}
|
||||
alt=""
|
||||
className="w-5 h-5 object-contain"
|
||||
style={{ imageRendering: "pixelated", filter: isDownloading ? "hue-rotate(300deg)" : "none" }}
|
||||
style={{
|
||||
imageRendering: "pixelated",
|
||||
filter: isDownloading
|
||||
? "hue-rotate(300deg)"
|
||||
: "none",
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
@@ -364,23 +397,39 @@ const VersionsView = memo(function VersionsView() {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
playPressSound();
|
||||
setOpenMenuId(openMenuId === edition.id ? null : edition.id);
|
||||
setOpenMenuId(
|
||||
openMenuId === edition.id ? null : edition.id,
|
||||
);
|
||||
}}
|
||||
onMouseEnter={() => setHoveredBtn({ row: i, btn: "menu" })}
|
||||
onMouseEnter={() =>
|
||||
setHoveredBtn({ row: i, btn: "menu" })
|
||||
}
|
||||
onMouseLeave={() => setHoveredBtn(null)}
|
||||
className="w-9 h-9 flex flex-col items-center justify-center gap-1 transition-colors relative"
|
||||
style={{
|
||||
backgroundImage: (hoveredBtn?.row === i && hoveredBtn?.btn === "menu") || (focusIndex === i && (focusBtn === 0 || focusBtn === 1))
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundImage:
|
||||
(hoveredBtn?.row === i &&
|
||||
hoveredBtn?.btn === "menu") ||
|
||||
(focusIndex === i &&
|
||||
(focusBtn === 0 || focusBtn === 1))
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
filter: updatesAvailable?.[edition.instanceId] ? "drop-shadow(0 0 4px rgba(255,255,0,0.8))" : "none"
|
||||
filter: updatesAvailable?.[edition.instanceId]
|
||||
? "drop-shadow(0 0 4px rgba(255,255,0,0.8))"
|
||||
: "none",
|
||||
}}
|
||||
>
|
||||
<div className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`} />
|
||||
<div className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`} />
|
||||
<div className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`} />
|
||||
<div
|
||||
className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`}
|
||||
/>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`}
|
||||
/>
|
||||
<div
|
||||
className={`w-1.5 h-1.5 ${updatesAvailable?.[edition.instanceId] ? "bg-[#ffff55]" : "bg-white"}`}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -388,7 +437,7 @@ const VersionsView = memo(function VersionsView() {
|
||||
<div
|
||||
className="absolute right-0 top-11 w-48 bg-[#1a1a1a] border-2 border-[#555] z-[100] shadow-2xl p-0.5 animate-in fade-in zoom-in duration-75"
|
||||
style={{
|
||||
imageRendering: "pixelated"
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
{updatesAvailable?.[edition.instanceId] && (
|
||||
@@ -401,27 +450,33 @@ const VersionsView = memo(function VersionsView() {
|
||||
}}
|
||||
className="w-full text-left px-3 py-1.5 text-xs text-[#ffff55] hover:text-white hover:bg-[#ffff55]/20 flex items-center gap-2 group transition-colors mc-text-shadow font-bold border-b border-white/5 mb-1"
|
||||
>
|
||||
<img src="/images/Download_Icon.png" alt="" className="w-3 h-3 object-contain" style={{ imageRendering: "pixelated" }} />
|
||||
<img
|
||||
src="/images/Download_Icon.png"
|
||||
alt=""
|
||||
className="w-3 h-3 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
Update Available!
|
||||
</button>
|
||||
)}
|
||||
{Array.isArray(edition.branches) && edition.branches.length > 0 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
playPressSound();
|
||||
cycleBranch(edition.id);
|
||||
}}
|
||||
className="w-full text-left px-3 py-1.5 text-[10px] text-white mc-text-shadow hover:bg-white/10 flex items-center justify-between group transition-colors"
|
||||
>
|
||||
<span className="text-[#AAAAAA] group-hover:text-white font-bold">
|
||||
Channel
|
||||
</span>
|
||||
<span className="text-[#ffff55] font-bold">
|
||||
{edition.selectedBranch ?? "Latest"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
{Array.isArray(edition.branches) &&
|
||||
edition.branches.length > 0 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
playPressSound();
|
||||
cycleBranch(edition.id);
|
||||
}}
|
||||
className="w-full text-left px-3 py-1.5 text-[10px] text-white mc-text-shadow hover:bg-white/10 flex items-center justify-between group transition-colors"
|
||||
>
|
||||
<span className="text-[#AAAAAA] group-hover:text-white font-bold">
|
||||
Channel
|
||||
</span>
|
||||
<span className="text-[#ffff55] font-bold">
|
||||
{edition.selectedBranch ?? "Latest"}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="h-[1px] bg-white/5 my-0.5 mx-1" />
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -432,24 +487,72 @@ const VersionsView = memo(function VersionsView() {
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] hover:text-white hover:bg-white/10 flex items-center gap-2 transition-colors mc-text-shadow"
|
||||
>
|
||||
<img src="/images/Folder_Icon.png" alt="" className="w-3.5 h-3.5 object-contain" style={{ imageRendering: "pixelated" }} />
|
||||
<img
|
||||
src="/images/Folder_Icon.png"
|
||||
alt=""
|
||||
className="w-3.5 h-3.5 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
Open Folder
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
playPressSound();
|
||||
const PANORAMA_PROFILES = ['legacy_evolved', 'vanilla_tu19', '360revived', 'vanilla_tu24'];
|
||||
const panoId = PANORAMA_PROFILES.includes(edition.id) ? edition.id : 'vanilla_tu19';
|
||||
const panoramaUrl = `/panorama/${panoId}_Panorama_Background_${isDayTime ? 'Day' : 'Night'}.png`;
|
||||
addToSteam(edition.instanceId, edition.name, edition.titleImage, panoramaUrl);
|
||||
const PANORAMA_PROFILES = [
|
||||
"legacy_evolved",
|
||||
"vanilla_tu19",
|
||||
"360revived",
|
||||
"vanilla_tu24",
|
||||
];
|
||||
const panoId = PANORAMA_PROFILES.includes(
|
||||
edition.id,
|
||||
)
|
||||
? edition.id
|
||||
: "vanilla_tu19";
|
||||
const panoramaUrl = `/panorama/${panoId}_Panorama_Background_${isDayTime ? "Day" : "Night"}.png`;
|
||||
addToSteam(
|
||||
edition.instanceId,
|
||||
edition.name,
|
||||
edition.titleImage,
|
||||
panoramaUrl,
|
||||
);
|
||||
setOpenMenuId(null);
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] hover:text-white hover:bg-white/10 flex items-center gap-2 transition-colors mc-text-shadow"
|
||||
>
|
||||
<img src="/images/steam.png" alt="" className="w-3.5 h-3.5 object-contain invert brightness-0" style={{ imageRendering: "pixelated" }} />
|
||||
<img
|
||||
src="/images/steam.png"
|
||||
alt=""
|
||||
className="w-3.5 h-3.5 object-contain invert brightness-0"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
Add to Steam
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
playPressSound();
|
||||
setSetUidTargetId(edition.instanceId);
|
||||
setIsSetUidModalOpen(true);
|
||||
setOpenMenuId(null);
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] hover:text-white hover:bg-white/10 flex items-center gap-2 transition-colors mc-text-shadow"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
className="w-3.5 h-3.5"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
Set UID
|
||||
</button>
|
||||
{isCustom ? (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -461,7 +564,17 @@ const VersionsView = memo(function VersionsView() {
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 text-xs text-[#aaaaaa] hover:text-white hover:bg-white/10 flex items-center gap-2 transition-colors mc-text-shadow"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="w-3.5 h-3.5"><path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" /></svg>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
className="w-3.5 h-3.5"
|
||||
>
|
||||
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
|
||||
</svg>
|
||||
Edit Custom
|
||||
</button>
|
||||
) : null}
|
||||
@@ -479,7 +592,12 @@ const VersionsView = memo(function VersionsView() {
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 text-xs text-red-500 hover:text-red-400 hover:bg-red-500/10 flex items-center gap-2 transition-colors mc-text-shadow font-bold"
|
||||
>
|
||||
<img src="/images/Trash_Bin_Icon.png" alt="" className="w-3.5 h-3.5 object-contain" style={{ imageRendering: "pixelated" }} />
|
||||
<img
|
||||
src="/images/Trash_Bin_Icon.png"
|
||||
alt=""
|
||||
className="w-3.5 h-3.5 object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
{isCustom ? "Remove Custom" : "Uninstall"}
|
||||
</button>
|
||||
</div>
|
||||
@@ -503,7 +621,7 @@ const VersionsView = memo(function VersionsView() {
|
||||
backgroundImage:
|
||||
(hoveredBtn?.row === editions.length &&
|
||||
hoveredBtn?.btn === "add") ||
|
||||
focusIndex === editions.length
|
||||
focusIndex === editions.length
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
@@ -536,7 +654,7 @@ const VersionsView = memo(function VersionsView() {
|
||||
backgroundImage:
|
||||
(hoveredBtn?.row === editions.length &&
|
||||
hoveredBtn?.btn === "folder_import") ||
|
||||
focusIndex === editions.length + 1
|
||||
focusIndex === editions.length + 1
|
||||
? "url('/images/Button_Square_Highlighted.png')"
|
||||
: "url('/images/Button_Square.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
@@ -598,6 +716,16 @@ const VersionsView = memo(function VersionsView() {
|
||||
initialPath={initialPath}
|
||||
/>
|
||||
|
||||
<SetUidModal
|
||||
isOpen={isSetUidModalOpen}
|
||||
onClose={() => setIsSetUidModalOpen(false)}
|
||||
playPressSound={playPressSound}
|
||||
playBackSound={playBackSound}
|
||||
instances={editions}
|
||||
installedVersions={installedVersions}
|
||||
targetInstanceId={setUidTargetId}
|
||||
/>
|
||||
|
||||
{deleteConfirmEdition && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user