mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-07-18 00:10:45 +00:00
feat: devtools menu placeholder (#35)
This commit is contained in:
@@ -13,8 +13,8 @@ export function AchievementToast({
|
||||
message,
|
||||
onClose,
|
||||
onClick,
|
||||
title = "Error Get!",
|
||||
variant = "error"
|
||||
title = "Error!",
|
||||
variant = "error",
|
||||
}: AchievementToastProps) {
|
||||
useEffect(() => {
|
||||
if (message) {
|
||||
@@ -69,10 +69,14 @@ export function AchievementToast({
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
exit={{ x: 400, opacity: 0 }}
|
||||
transition={{ type: "spring", damping: 20, stiffness: 100 }}
|
||||
onClick={onClick ? () => {
|
||||
onClick();
|
||||
onClose();
|
||||
} : undefined}
|
||||
onClick={
|
||||
onClick
|
||||
? () => {
|
||||
onClick();
|
||||
onClose();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={`fixed top-6 right-6 z-[9999] ${onClick ? "cursor-pointer" : ""}`}
|
||||
>
|
||||
<div
|
||||
|
||||
86
src/components/views/DevtoolsView.tsx
Normal file
86
src/components/views/DevtoolsView.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { TauriService } from "../../services/TauriService";
|
||||
import { useUI, useAudio, useConfig } from "../../context/LauncherContext";
|
||||
export default function DevtoolsView() {
|
||||
const { setActiveView } = useUI();
|
||||
const [backHover, setBackHover] = useState(false);
|
||||
const { playBackSound, playClickSound } = useAudio();
|
||||
const [focusIndex, setFocusIndex] = useState<number | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" || e.key === "Backspace") {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [playBackSound, setActiveView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusIndex !== null) {
|
||||
const el = containerRef.current?.querySelector(
|
||||
`[data-index="${focusIndex}"]`,
|
||||
) as HTMLElement;
|
||||
if (el) el.focus();
|
||||
}
|
||||
}, [focusIndex]);
|
||||
|
||||
const getItemStyle = (index: number) => ({
|
||||
backgroundImage:
|
||||
focusIndex === index
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated" as const,
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
tabIndex={0}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
|
||||
className="flex flex-col items-center w-full max-w-4xl outline-none"
|
||||
>
|
||||
<h2 className="text-2xl text-white mc-text-shadow mt-2 mb-4 border-b-2 border-[#373737] pb-2 w-[60%] max-w-[300px] text-center tracking-widest uppercase opacity-80">
|
||||
Developer Tools
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className="w-full max-w-135 h-48 mb-6 p-8 shadow-2xl flex items-center justify-center"
|
||||
style={{
|
||||
backgroundImage: "url('/images/frame_background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<span className="text-[#E0E0E0] text-xl mc-text-shadow tracking-wide">
|
||||
Developer Tools coming soon...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onMouseEnter={() => setBackHover(true)}
|
||||
onMouseLeave={() => setBackHover(false)}
|
||||
onClick={() => {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}}
|
||||
className={`w-72 h-12 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] ${backHover ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={{
|
||||
backgroundImage: backHover
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
import { useState, useEffect, useMemo, memo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useUI, useConfig, useAudio, useGame } from "../../context/LauncherContext";
|
||||
import {
|
||||
useUI,
|
||||
useConfig,
|
||||
useAudio,
|
||||
useGame,
|
||||
} from "../../context/LauncherContext";
|
||||
|
||||
const HomeView = memo(function HomeView() {
|
||||
const { setActiveView, setShowCredits, 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();
|
||||
const {
|
||||
handleLaunch,
|
||||
isGameRunning,
|
||||
editions,
|
||||
installs,
|
||||
toggleInstall,
|
||||
downloadProgress,
|
||||
downloadingId,
|
||||
} = useGame();
|
||||
|
||||
const isFocusedSection = focusSection === "menu";
|
||||
const selectedEdition = editions.find((e: any) => e.id === profile);
|
||||
@@ -32,7 +46,7 @@ const HomeView = memo(function HomeView() {
|
||||
{ label: "Help & Options", action: () => setActiveView("settings") },
|
||||
{ label: "Versions", action: () => setActiveView("versions") },
|
||||
{ label: "Workshop", action: () => setActiveView("workshop") },
|
||||
{ label: "Themes & Tools", action: () => setActiveView("themes") },
|
||||
{ label: "Developer Tools", action: () => setActiveView("devtools") },
|
||||
],
|
||||
[
|
||||
isGameRunning,
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import { useState, useEffect, useRef, memo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { TauriService, ThemePalette } from "../../services/TauriService";
|
||||
import { useUI, useConfig, useAudio } from "../../context/LauncherContext";
|
||||
|
||||
const ThemesView = memo(function ThemesView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { theme: currentTheme, setTheme } = useConfig();
|
||||
const { playClickSound, playBackSound } = useAudio();
|
||||
|
||||
const [focusIndex, setFocusIndex] = useState<number | null>(null);
|
||||
const [externalPalettes, setExternalPalettes] = useState<ThemePalette[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const baseThemes = ["Default", "Modern"];
|
||||
|
||||
useEffect(() => {
|
||||
TauriService.getExternalPalettes().then(setExternalPalettes);
|
||||
}, []);
|
||||
|
||||
const totalPalettes = [...baseThemes, ...externalPalettes.map((p) => p.name)];
|
||||
const ITEM_COUNT = 3; // Theme Cycle, Import Theme, Back
|
||||
|
||||
const handleImport = async () => {
|
||||
playClickSound();
|
||||
try {
|
||||
const result = await TauriService.importTheme();
|
||||
if (result === "success") {
|
||||
const updated = await TauriService.getExternalPalettes();
|
||||
setExternalPalettes(updated);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" || e.key === "Backspace") {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown") {
|
||||
setFocusIndex((prev) =>
|
||||
prev === null || prev >= ITEM_COUNT - 1 ? 0 : prev + 1,
|
||||
);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
setFocusIndex((prev) =>
|
||||
prev === null || prev <= 0 ? ITEM_COUNT - 1 : prev - 1,
|
||||
);
|
||||
} else if (e.key === "Enter" && focusIndex !== null) {
|
||||
if (focusIndex === 0) {
|
||||
playClickSound();
|
||||
const currentIndex = totalPalettes.indexOf(currentTheme);
|
||||
const nextIndex = (currentIndex + 1) % totalPalettes.length;
|
||||
setTheme(totalPalettes[nextIndex]);
|
||||
} else if (focusIndex === 1) {
|
||||
handleImport();
|
||||
} else {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [
|
||||
focusIndex,
|
||||
currentTheme,
|
||||
playClickSound,
|
||||
playBackSound,
|
||||
setActiveView,
|
||||
setTheme,
|
||||
totalPalettes,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusIndex !== null) {
|
||||
const el = containerRef.current?.querySelector(
|
||||
`[data-index="${focusIndex}"]`,
|
||||
) as HTMLElement;
|
||||
if (el) el.focus();
|
||||
}
|
||||
}, [focusIndex]);
|
||||
|
||||
const getItemStyle = (index: number) => ({
|
||||
backgroundImage:
|
||||
focusIndex === index
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated" as const,
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={containerRef}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
|
||||
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-[60%] max-w-[300px] text-center tracking-widest uppercase opacity-80">
|
||||
Themes & Styles
|
||||
</h2>
|
||||
|
||||
<div className="w-full max-w-[540px] flex flex-col items-center gap-4 mt-4 mb-8">
|
||||
<button
|
||||
data-index="0"
|
||||
onMouseEnter={() => setFocusIndex(0)}
|
||||
onClick={() => {
|
||||
playClickSound();
|
||||
const currentIndex = totalPalettes.indexOf(currentTheme);
|
||||
const nextIndex = (currentIndex + 1) % totalPalettes.length;
|
||||
setTheme(totalPalettes[nextIndex]);
|
||||
}}
|
||||
className={`w-72 h-12 flex items-center justify-center px-4 relative transition-colors outline-none border-none hover:text-[#FFFF55] ${focusIndex === 0 ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={getItemStyle(0)}
|
||||
>
|
||||
<span className="text-2xl mc-text-shadow tracking-widest uppercase">
|
||||
{currentTheme}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
data-index="1"
|
||||
onMouseEnter={() => setFocusIndex(1)}
|
||||
onClick={handleImport}
|
||||
className={`w-72 h-12 flex items-center justify-center px-4 relative transition-colors outline-none border-none hover:text-[#FFFF55] ${focusIndex === 1 ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={getItemStyle(1)}
|
||||
>
|
||||
<span className="text-xl mc-text-shadow tracking-widest uppercase">
|
||||
Import Theme
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
data-index="2"
|
||||
onMouseEnter={() => setFocusIndex(2)}
|
||||
onClick={() => {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
}}
|
||||
className={`w-72 h-12 flex items-center justify-center transition-colors text-2xl mc-text-shadow outline-none border-none hover:text-[#FFFF55] ${focusIndex === 2 ? "text-[#FFFF55]" : "text-white"}`}
|
||||
style={getItemStyle(2)}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ThemesView;
|
||||
@@ -30,24 +30,28 @@ export function useDiscordRPC({
|
||||
const updateRPC = async () => {
|
||||
if (!rpcEnabled || showIntro || !username) return;
|
||||
|
||||
if (!isWindowVisible && !isGameRunning && downloadProgress === null) return;
|
||||
if (!isWindowVisible && !isGameRunning && downloadProgress === null)
|
||||
return;
|
||||
|
||||
const version = editions.find((e) => e.id === profile);
|
||||
const versionName = version ? version.name : "Unknown Version";
|
||||
let details = "In Menus";
|
||||
let state = isGameRunning ? `Playing as ${username}` : `Logged in as ${username}`;
|
||||
let state = isGameRunning
|
||||
? `Playing as ${username}`
|
||||
: `Logged in as ${username}`;
|
||||
|
||||
if (isGameRunning) {
|
||||
details = `Playing ${versionName}`;
|
||||
} else if (downloadProgress !== null) {
|
||||
const downloadingName = editions.find((e) => e.id === downloadingId)?.name || "Game Files";
|
||||
const downloadingName =
|
||||
editions.find((e) => e.id === downloadingId)?.name || "Game Files";
|
||||
details = `Downloading ${downloadingName} (${downloadProgress.toFixed(0)}%)`;
|
||||
} else {
|
||||
const tabNames: Record<string, string> = {
|
||||
main: "Main Menu",
|
||||
versions: "Selecting Version",
|
||||
settings: "In Settings",
|
||||
themes: "Browsing Themes",
|
||||
devtools: "Developing for LCE",
|
||||
skins: "Browsing Skins",
|
||||
workshop: "Browsing Workshop",
|
||||
};
|
||||
@@ -58,5 +62,16 @@ export function useDiscordRPC({
|
||||
};
|
||||
|
||||
updateRPC();
|
||||
}, [rpcEnabled, showIntro, username, profile, activeView, isGameRunning, isWindowVisible, Math.floor(downloadProgress || 0), downloadingId, editions]);
|
||||
}, [
|
||||
rpcEnabled,
|
||||
showIntro,
|
||||
username,
|
||||
profile,
|
||||
activeView,
|
||||
isGameRunning,
|
||||
isWindowVisible,
|
||||
Math.floor(downloadProgress || 0),
|
||||
downloadingId,
|
||||
editions,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { motion, AnimatePresence, MotionConfig } from "framer-motion";
|
||||
import HomeView from "../components/views/HomeView";
|
||||
import SettingsView from "../components/views/SettingsView";
|
||||
import VersionsView from "../components/views/VersionsView";
|
||||
import ThemesView from "../components/views/ThemesView";
|
||||
import DevtoolsView from "../components/views/DevtoolsView";
|
||||
import SkinsView from "../components/views/SkinsView";
|
||||
import WorkshopView from "../components/views/WorkshopView";
|
||||
import SetupView from "../components/views/SetupView";
|
||||
@@ -14,7 +14,13 @@ import { ClickParticles } from "../components/common/ClickParticles";
|
||||
import { AppHeader } from "../components/layout/AppHeader";
|
||||
import { DownloadOverlay } from "../components/layout/DownloadOverlay";
|
||||
import { AchievementToast } from "../components/common/AchievementToast";
|
||||
import { useUI, useConfig, useAudio, useGame, useSkin } from "../context/LauncherContext";
|
||||
import {
|
||||
useUI,
|
||||
useConfig,
|
||||
useAudio,
|
||||
useGame,
|
||||
useSkin,
|
||||
} from "../context/LauncherContext";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { TauriService } from "../services/TauriService";
|
||||
import pkg from "../../package.json";
|
||||
@@ -22,10 +28,20 @@ const appWindow = getCurrentWindow();
|
||||
|
||||
export default function App() {
|
||||
const {
|
||||
showIntro, setShowIntro, logoAnimDone, setLogoAnimDone,
|
||||
activeView, setActiveView, isUiHidden, setIsUiHidden,
|
||||
showCredits, setShowCredits, focusSection,
|
||||
onNavigateToMenu, updateMessage, clearUpdateMessage
|
||||
showIntro,
|
||||
setShowIntro,
|
||||
logoAnimDone,
|
||||
setLogoAnimDone,
|
||||
activeView,
|
||||
setActiveView,
|
||||
isUiHidden,
|
||||
setIsUiHidden,
|
||||
showCredits,
|
||||
setShowCredits,
|
||||
focusSection,
|
||||
onNavigateToMenu,
|
||||
updateMessage,
|
||||
clearUpdateMessage,
|
||||
} = useUI();
|
||||
|
||||
const config = useConfig();
|
||||
@@ -40,30 +56,25 @@ export default function App() {
|
||||
setDisplayIsDay(config.isDayTime);
|
||||
}, [config.isDayTime]);
|
||||
|
||||
const selectedEdition = game.editions.find((e: any) => e.id === config.profile);
|
||||
const selectedEdition = game.editions.find(
|
||||
(e: any) => e.id === config.profile,
|
||||
);
|
||||
const selectedVersionName = selectedEdition?.name || "";
|
||||
|
||||
const titleImage = selectedEdition?.titleImage || "/images/MenuTitle.png";
|
||||
|
||||
useEffect(() => {
|
||||
if (config.isLoaded) {
|
||||
// Check localStorage directly to ensure accurate setup state
|
||||
const setupCompleted = localStorage.getItem('lce-setup-completed') === 'true';
|
||||
const setupCompleted =
|
||||
localStorage.getItem("lce-setup-completed") === "true";
|
||||
setShowSetup(!setupCompleted);
|
||||
}
|
||||
}, [config.isLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
appWindow.show();
|
||||
// Only start intro timing if setup is not shown
|
||||
if (!showSetup) {
|
||||
setTimeout(() => setShowIntro(false), 2400);
|
||||
setTimeout(() => setLogoAnimDone(true), 3400);
|
||||
} else if (showSetup) {
|
||||
// Skip intro entirely if setup is shown
|
||||
setShowIntro(false);
|
||||
setLogoAnimDone(true);
|
||||
}
|
||||
setTimeout(() => setShowIntro(false), 2400);
|
||||
setTimeout(() => setLogoAnimDone(true), 3400);
|
||||
}, [showSetup]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -76,21 +87,21 @@ export default function App() {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
transition: { duration: config.animationsEnabled ? 0.5 : 0 }
|
||||
transition: { duration: config.animationsEnabled ? 0.5 : 0 },
|
||||
};
|
||||
|
||||
const backgroundFade = {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
transition: { duration: config.animationsEnabled ? 0.8 : 0 }
|
||||
transition: { duration: config.animationsEnabled ? 0.8 : 0 },
|
||||
};
|
||||
|
||||
return (
|
||||
<MotionConfig transition={config.animationsEnabled ? {} : { duration: 0 }}>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={`w-screen h-screen overflow-hidden select-none flex flex-col relative bg-black text-white font-['Mojangles'] outline-none focus:outline-none ${!config.animationsEnabled ? 'no-animations' : ''}`}
|
||||
className={`w-screen h-screen overflow-hidden select-none flex flex-col relative bg-black text-white font-['Mojangles'] outline-none focus:outline-none ${!config.animationsEnabled ? "no-animations" : ""}`}
|
||||
>
|
||||
<style>{`
|
||||
@keyframes splashPulse { 0% { transform: scale(0.95) rotate(-20deg); } 100% { transform: scale(1.08) rotate(-20deg); } }
|
||||
@@ -106,11 +117,14 @@ export default function App() {
|
||||
<div className="absolute inset-0">
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
key={displayIsDay ? 'day' : 'night'}
|
||||
key={displayIsDay ? "day" : "night"}
|
||||
className="absolute inset-0"
|
||||
{...backgroundFade}
|
||||
>
|
||||
<PanoramaBackground profile={config.profile} isDay={displayIsDay} />
|
||||
<PanoramaBackground
|
||||
profile={config.profile}
|
||||
isDay={displayIsDay}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
@@ -143,7 +157,9 @@ export default function App() {
|
||||
<AchievementToast
|
||||
message={updateMessage}
|
||||
onClose={clearUpdateMessage}
|
||||
onClick={() => TauriService.openUrl("https://emerald-legacy-launcher.github.io/")}
|
||||
onClick={() =>
|
||||
TauriService.openUrl("https://emerald-legacy-launcher.github.io/")
|
||||
}
|
||||
title="Update Available!"
|
||||
variant="update"
|
||||
/>
|
||||
@@ -181,7 +197,12 @@ export default function App() {
|
||||
className="flex flex-col h-full z-10 w-full relative"
|
||||
>
|
||||
<AnimatePresence>
|
||||
{logoAnimDone && <AppHeader playClickSound={audio.playClickSound} uiFade={uiFade} />}
|
||||
{logoAnimDone && (
|
||||
<AppHeader
|
||||
playClickSound={audio.playClickSound}
|
||||
uiFade={uiFade}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
@@ -201,7 +222,11 @@ export default function App() {
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={isUiHidden ? "/images/Unhide_UI_Button.png" : "/images/Hide_UI_Button.png"}
|
||||
src={
|
||||
isUiHidden
|
||||
? "/images/Unhide_UI_Button.png"
|
||||
: "/images/Hide_UI_Button.png"
|
||||
}
|
||||
className="w-10 h-10 cursor-pointer object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
@@ -226,7 +251,11 @@ export default function App() {
|
||||
className="hover:scale-110 active:scale-95 transition-transform outline-none bg-transparent border-none"
|
||||
>
|
||||
<img
|
||||
src={displayIsDay ? "/images/Day_Toggle.png" : "/images/Night_Toggle.png"}
|
||||
src={
|
||||
displayIsDay
|
||||
? "/images/Day_Toggle.png"
|
||||
: "/images/Night_Toggle.png"
|
||||
}
|
||||
alt="Toggle Time"
|
||||
className="w-12 h-12 cursor-pointer block object-contain"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
@@ -273,15 +302,16 @@ export default function App() {
|
||||
: audio.splashes[audio.splashIndex]}
|
||||
</div>
|
||||
</motion.div>
|
||||
{activeView === "main" && titleImage === "/images/MenuTitle.png" && (
|
||||
<motion.div
|
||||
key="tu-subtitle"
|
||||
{...uiFade}
|
||||
className="absolute -bottom-6 text-[#A0A0A0] text-sm mc-text-shadow tracking-widest uppercase opacity-80 font-['Mojangles']"
|
||||
>
|
||||
{selectedVersionName}
|
||||
</motion.div>
|
||||
)}
|
||||
{activeView === "main" &&
|
||||
titleImage === "/images/MenuTitle.png" && (
|
||||
<motion.div
|
||||
key="tu-subtitle"
|
||||
{...uiFade}
|
||||
className="absolute -bottom-6 text-[#A0A0A0] text-sm mc-text-shadow tracking-widest uppercase opacity-80 font-['Mojangles']"
|
||||
>
|
||||
{selectedVersionName}
|
||||
</motion.div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -310,9 +340,7 @@ export default function App() {
|
||||
|
||||
<div className="w-full max-w-4xl relative flex justify-center items-center">
|
||||
<AnimatePresence mode="wait">
|
||||
{activeView === "main" && (
|
||||
<HomeView key="main-view" />
|
||||
)}
|
||||
{activeView === "main" && <HomeView key="main-view" />}
|
||||
{activeView === "settings" && (
|
||||
<SettingsView key="settings-view" />
|
||||
)}
|
||||
@@ -322,12 +350,10 @@ export default function App() {
|
||||
{activeView === "workshop" && (
|
||||
<WorkshopView key="workshop-view" />
|
||||
)}
|
||||
{activeView === "themes" && (
|
||||
<ThemesView key="themes-view" />
|
||||
)}
|
||||
{activeView === "skins" && (
|
||||
<SkinsView key="skins-view" />
|
||||
{activeView === "devtools" && (
|
||||
<DevtoolsView key="devtools-view" />
|
||||
)}
|
||||
{activeView === "skins" && <SkinsView key="skins-view" />}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
@@ -341,9 +367,12 @@ export default function App() {
|
||||
className="shrink-0 p-4 flex justify-between items-end text-[10px] text-[#A0A0A0] mc-text-shadow bg-gradient-to-t from-black/80 to-transparent uppercase tracking-widest opacity-60 font-['Mojangles']"
|
||||
style={{ fontWeight: "normal" }}
|
||||
>
|
||||
<div className="flex-1 text-left whitespace-nowrap">Version: {pkg.version}</div>
|
||||
<div className="flex-1 text-left whitespace-nowrap">
|
||||
Version: {pkg.version}
|
||||
</div>
|
||||
<div className="flex-[2] text-center whitespace-nowrap">
|
||||
Not affiliated with Mojang AB or Microsoft. "Minecraft" is a trademark of Mojang Synergies AB.
|
||||
Not affiliated with Mojang AB or Microsoft. "Minecraft" is
|
||||
a trademark of Mojang Synergies AB.
|
||||
</div>
|
||||
<div className="flex-1 text-right whitespace-nowrap">
|
||||
{useUI().connected && "CONTROLLER CONNECTED"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
export interface McServer {
|
||||
name: string;
|
||||
@@ -50,7 +50,7 @@ export interface Runner {
|
||||
id: string;
|
||||
name: string;
|
||||
path: string;
|
||||
type: 'wine' | 'proton';
|
||||
type: "wine" | "proton";
|
||||
}
|
||||
|
||||
export interface MacOSSetupProgress {
|
||||
@@ -61,113 +61,114 @@ export interface MacOSSetupProgress {
|
||||
|
||||
export class TauriService {
|
||||
static async saveConfig(config: AppConfig): Promise<void> {
|
||||
return invoke('save_config', { config });
|
||||
return invoke("save_config", { config });
|
||||
}
|
||||
|
||||
static async loadConfig(): Promise<AppConfig> {
|
||||
return invoke('load_config');
|
||||
return invoke("load_config");
|
||||
}
|
||||
|
||||
static async getExternalPalettes(): Promise<ThemePalette[]> {
|
||||
return invoke('get_external_palettes');
|
||||
return invoke("get_external_palettes");
|
||||
}
|
||||
|
||||
static async importTheme(): Promise<string> {
|
||||
return invoke('import_theme');
|
||||
return invoke("import_theme");
|
||||
}
|
||||
|
||||
static async getAvailableRunners(): Promise<Runner[]> {
|
||||
return invoke('get_available_runners');
|
||||
return invoke("get_available_runners");
|
||||
}
|
||||
|
||||
static async downloadRunner(name: string, url: string): Promise<string> {
|
||||
return invoke('download_runner', { name, url });
|
||||
return invoke("download_runner", { name, url });
|
||||
}
|
||||
|
||||
static async checkGameInstalled(instanceId: string): Promise<boolean> {
|
||||
return invoke('check_game_installed', { instanceId });
|
||||
return invoke("check_game_installed", { instanceId });
|
||||
}
|
||||
|
||||
static async openInstanceFolder(instanceId: string): Promise<void> {
|
||||
return invoke('open_instance_folder', { instanceId });
|
||||
return invoke("open_instance_folder", { instanceId });
|
||||
}
|
||||
|
||||
static async deleteInstance(instanceId: string): Promise<void> {
|
||||
return invoke('delete_instance', { instanceId });
|
||||
return invoke("delete_instance", { instanceId });
|
||||
}
|
||||
|
||||
static async cancelDownload(): Promise<void> {
|
||||
return invoke('cancel_download');
|
||||
return invoke("cancel_download");
|
||||
}
|
||||
|
||||
static async setupMacosRuntime(): Promise<void> {
|
||||
return invoke('setup_macos_runtime');
|
||||
return invoke("setup_macos_runtime");
|
||||
}
|
||||
|
||||
static async downloadAndInstall(url: string, instanceId: string): Promise<string> {
|
||||
return invoke('download_and_install', { url, instanceId });
|
||||
static async downloadAndInstall(
|
||||
url: string,
|
||||
instanceId: string,
|
||||
): Promise<string> {
|
||||
return invoke("download_and_install", { url, instanceId });
|
||||
}
|
||||
|
||||
static async launchGame(instanceId: string, servers: McServer[]): Promise<void> {
|
||||
return invoke('launch_game', { instanceId, servers });
|
||||
static async launchGame(
|
||||
instanceId: string,
|
||||
servers: McServer[],
|
||||
): Promise<void> {
|
||||
return invoke("launch_game", { instanceId, servers });
|
||||
}
|
||||
|
||||
static async stopGame(instanceId: string): Promise<void> {
|
||||
return invoke('stop_game', { instanceId });
|
||||
return invoke("stop_game", { instanceId });
|
||||
}
|
||||
|
||||
static async syncDlc(instanceId: string): Promise<void> {
|
||||
return invoke('sync_dlc', { instanceId });
|
||||
return invoke("sync_dlc", { instanceId });
|
||||
}
|
||||
|
||||
static async updateTrayIcon(visible: boolean): Promise<void> {
|
||||
return invoke('update_tray_icon', { visible });
|
||||
return invoke("update_tray_icon", { visible });
|
||||
}
|
||||
|
||||
static onDownloadProgress(callback: (percent: number) => void) {
|
||||
return listen<number>('download-progress', (event) => callback(event.payload));
|
||||
return listen<number>("download-progress", (event) =>
|
||||
callback(event.payload),
|
||||
);
|
||||
}
|
||||
|
||||
static onRunnerDownloadProgress(callback: (percent: number) => void) {
|
||||
return listen<number>('runner-download-progress', (event) => callback(event.payload));
|
||||
return listen<number>("runner-download-progress", (event) =>
|
||||
callback(event.payload),
|
||||
);
|
||||
}
|
||||
|
||||
static onMacosProgress(callback: (payload: MacOSSetupProgress) => void) {
|
||||
return listen<MacOSSetupProgress>('macos-setup-progress', (event) => callback(event.payload));
|
||||
return listen<MacOSSetupProgress>("macos-setup-progress", (event) =>
|
||||
callback(event.payload),
|
||||
);
|
||||
}
|
||||
|
||||
static async openUrl(url: string): Promise<void> {
|
||||
return invoke('plugin:opener|open_url', { url });
|
||||
return invoke("plugin:opener|open_url", { url });
|
||||
}
|
||||
|
||||
static async restartLauncher(): Promise<void> {
|
||||
return invoke('restart_launcher');
|
||||
return invoke("restart_launcher");
|
||||
}
|
||||
|
||||
static async checkMacOSRuntimeInstalled(): Promise<boolean> {
|
||||
return invoke('check_macos_runtime_installed');
|
||||
return invoke("check_macos_runtime_installed");
|
||||
}
|
||||
|
||||
static async checkMacOSRuntimeInstalledFast(): Promise<boolean> {
|
||||
return invoke('check_macos_runtime_installed_fast');
|
||||
return invoke("check_macos_runtime_installed_fast");
|
||||
}
|
||||
|
||||
static async setupMacOSRuntimeOptimized(): Promise<void> {
|
||||
return invoke('setup_macos_runtime_optimized');
|
||||
return invoke("setup_macos_runtime_optimized");
|
||||
}
|
||||
|
||||
static async fetchSkin(username: string): Promise<[string, string]> {
|
||||
return invoke('fetch_skin', { username });
|
||||
}
|
||||
|
||||
static async pathExists(_path: string): Promise<boolean> {
|
||||
// Simple web implementation using fetch to check if path exists
|
||||
try {
|
||||
// This is a simplified check - in a real implementation you'd use the Tauri API
|
||||
// For now, we'll just check common paths via heuristics
|
||||
return false; // Placeholder - will be implemented properly if needed
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return invoke("fetch_skin", { username });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user