mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-05-21 17:54:30 +00:00
feat: initial implementation of DEV TOOLS!!
This commit is contained in:
BIN
public/images/tools/arc.png
Normal file
BIN
public/images/tools/arc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
public/images/tools/loc.png
Normal file
BIN
public/images/tools/loc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
public/images/tools/pck.png
Normal file
BIN
public/images/tools/pck.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
89
src/components/views/ArcEditorView.tsx
Normal file
89
src/components/views/ArcEditorView.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useUI, useAudio, useConfig } from "../../context/LauncherContext";
|
||||
|
||||
export default function ArcEditorView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { playBackSound } = useAudio();
|
||||
const { animationsEnabled } = useConfig();
|
||||
const [focusIndex, setFocusIndex] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" || e.key === "Backspace") {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
if (focusIndex === 0) {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [playBackSound, setActiveView, focusIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current?.querySelector(`[data-index="${focusIndex}"]`) as HTMLElement;
|
||||
if (el) el.focus();
|
||||
}, [focusIndex]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={containerRef}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
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-3xl 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-75 text-center tracking-widest uppercase opacity-80 font-bold">
|
||||
ARC Editor
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className="w-full max-w-160 h-85 mb-4 p-8 shadow-2xl flex flex-col items-center justify-center p-12"
|
||||
style={{
|
||||
backgroundImage: "url('/images/frame_background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/tools/arc.png"
|
||||
className="w-24 h-24 mb-6 opacity-20 grayscale"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
<h3 className="text-xl text-[#FFFF55] mc-text-shadow mb-2">ARC Editor Coming Soon</h3>
|
||||
<p className="text-center text-white/60 mc-text-shadow max-w-md">
|
||||
This tool will allow you to edit ARC files.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
data-index="0"
|
||||
onMouseEnter={() => setFocusIndex(0)}
|
||||
onClick={() => {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
}}
|
||||
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow mt-2 outline-none border-none ${focusIndex === 0 ? "text-[#FFFF55]" : "text-white"
|
||||
}`}
|
||||
style={{
|
||||
backgroundImage:
|
||||
focusIndex === 0
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -1,70 +1,146 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useUI, useAudio, useConfig } from "../../context/LauncherContext";
|
||||
|
||||
interface DevTool {
|
||||
id: string;
|
||||
name: string;
|
||||
view: string;
|
||||
comingSoon: boolean;
|
||||
}
|
||||
|
||||
const DEV_TOOLS: DevTool[] = [
|
||||
{ id: "pck", name: "PCK Editor", view: "pck-editor", comingSoon: false },
|
||||
{ id: "arc", name: "ARC Editor", view: "arc-editor", comingSoon: false },
|
||||
{ id: "loc", name: "LOC Editor", view: "devtools", comingSoon: false }
|
||||
];
|
||||
|
||||
export default function DevtoolsView() {
|
||||
const { setActiveView } = useUI();
|
||||
const [backHover, setBackHover] = useState(false);
|
||||
const { playPressSound } = useAudio();
|
||||
const [focusIndex] = useState<number | null>(null);
|
||||
const { playPressSound, playBackSound } = useAudio();
|
||||
const { animationsEnabled } = useConfig();
|
||||
const [focusIndex, setFocusIndex] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const BACK_BUTTON_INDEX = DEV_TOOLS.length;
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" || e.key === "Backspace") {
|
||||
playPressSound();
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowRight") {
|
||||
setFocusIndex((prev) => (prev >= BACK_BUTTON_INDEX ? 0 : prev + 1));
|
||||
} else if (e.key === "ArrowLeft") {
|
||||
setFocusIndex((prev) => (prev <= 0 ? BACK_BUTTON_INDEX : prev - 1));
|
||||
} else if (e.key === "ArrowDown") {
|
||||
if (focusIndex < BACK_BUTTON_INDEX) {
|
||||
setFocusIndex(BACK_BUTTON_INDEX);
|
||||
}
|
||||
} else if (e.key === "ArrowUp") {
|
||||
if (focusIndex === BACK_BUTTON_INDEX) {
|
||||
setFocusIndex(0);
|
||||
}
|
||||
} else if (e.key === "Enter") {
|
||||
if (focusIndex === BACK_BUTTON_INDEX) {
|
||||
playBackSound();
|
||||
setActiveView("main");
|
||||
} else {
|
||||
playPressSound();
|
||||
const tool = DEV_TOOLS[focusIndex];
|
||||
setActiveView(tool.view);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [playPressSound, setActiveView]);
|
||||
}, [focusIndex, playPressSound, playBackSound, setActiveView, BACK_BUTTON_INDEX]);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusIndex !== null) {
|
||||
const el = containerRef.current?.querySelector(
|
||||
`[data-index="${focusIndex}"]`,
|
||||
) as HTMLElement;
|
||||
const el = containerRef.current?.querySelector(`[data-index="${focusIndex}"]`) as HTMLElement;
|
||||
if (el) el.focus();
|
||||
}
|
||||
}, [focusIndex]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
tabIndex={0}
|
||||
ref={containerRef}
|
||||
tabIndex={-1}
|
||||
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"
|
||||
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
|
||||
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-[60%] max-w-[300px] text-center tracking-widest uppercase opacity-80">
|
||||
<h2 className="text-2xl text-white mc-text-shadow mt-2 mb-4 border-b-2 border-[#373737] pb-2 w-[60%] max-w-75 text-center tracking-widest uppercase opacity-80 font-bold">
|
||||
Developer Tools
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className="w-full max-w-135 h-48 mb-6 p-8 shadow-2xl flex items-center justify-center"
|
||||
className="w-full max-w-160 h-85 mb-4 p-8 shadow-2xl flex flex-col items-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 className="flex flex-wrap gap-8 justify-center items-start w-full h-full overflow-y-auto pt-4">
|
||||
{DEV_TOOLS.map((tool, i) => (
|
||||
<div
|
||||
key={tool.id}
|
||||
data-index={i}
|
||||
tabIndex={0}
|
||||
onMouseEnter={() => setFocusIndex(i)}
|
||||
onClick={() => {
|
||||
playPressSound();
|
||||
setActiveView(tool.view);
|
||||
}}
|
||||
className={`group flex flex-col items-center gap-3 w-40 p-4 relative transition-all cursor-pointer outline-none border-2 ${focusIndex === i ? "border-[#FFFF55] bg-white/5" : "border-transparent"
|
||||
}`}
|
||||
>
|
||||
<div className="w-20 h-20 bg-black/40 border-2 border-[#373737] flex items-center justify-center relative shadow-inner">
|
||||
<img
|
||||
src={`/images/tools/${tool.id}.png`}
|
||||
alt={tool.name}
|
||||
className="w-12 h-12 object-contain opacity-50 grayscale"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
{tool.comingSoon && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/60">
|
||||
<span className="text-[10px] text-[#FFFF55] mc-text-shadow uppercase tracking-tighter text-center px-1">
|
||||
Coming Soon
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={`text-center text-lg mc-text-shadow transition-colors ${focusIndex === i ? "text-[#FFFF55]" : "text-white"
|
||||
}`}
|
||||
>
|
||||
{tool.name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onMouseEnter={() => setBackHover(true)}
|
||||
onMouseLeave={() => setBackHover(false)}
|
||||
data-index={BACK_BUTTON_INDEX}
|
||||
onMouseEnter={() => setFocusIndex(BACK_BUTTON_INDEX)}
|
||||
onClick={() => {
|
||||
playPressSound();
|
||||
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"}`}
|
||||
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow mt-2 outline-none border-none ${focusIndex === BACK_BUTTON_INDEX ? "text-[#FFFF55]" : "text-white"
|
||||
}`}
|
||||
style={{
|
||||
backgroundImage: backHover
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundImage:
|
||||
focusIndex === BACK_BUTTON_INDEX
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
|
||||
89
src/components/views/PckEditorView.tsx
Normal file
89
src/components/views/PckEditorView.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useUI, useAudio, useConfig } from "../../context/LauncherContext";
|
||||
|
||||
export default function PckEditorView() {
|
||||
const { setActiveView } = useUI();
|
||||
const { playBackSound } = useAudio();
|
||||
const { animationsEnabled } = useConfig();
|
||||
const [focusIndex, setFocusIndex] = useState<number>(0);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" || e.key === "Backspace") {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
if (focusIndex === 0) {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [playBackSound, setActiveView, focusIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = containerRef.current?.querySelector(`[data-index="${focusIndex}"]`) as HTMLElement;
|
||||
if (el) el.focus();
|
||||
}, [focusIndex]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={containerRef}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
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-3xl 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-75 text-center tracking-widest uppercase opacity-80 font-bold">
|
||||
PCK Editor
|
||||
</h2>
|
||||
|
||||
<div
|
||||
className="w-full max-w-160 h-85 mb-4 p-8 shadow-2xl flex flex-col items-center justify-center p-12"
|
||||
style={{
|
||||
backgroundImage: "url('/images/frame_background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="/images/tools/pck.png"
|
||||
className="w-24 h-24 mb-6 opacity-20 grayscale"
|
||||
style={{ imageRendering: "pixelated" }}
|
||||
/>
|
||||
<h3 className="text-xl text-[#FFFF55] mc-text-shadow mb-2">PCK Editor Coming Soon</h3>
|
||||
<p className="text-center text-white/60 mc-text-shadow max-w-md">
|
||||
This tool will allow you to explore and modify PCK archive files.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
data-index="0"
|
||||
onMouseEnter={() => setFocusIndex(0)}
|
||||
onClick={() => {
|
||||
playBackSound();
|
||||
setActiveView("devtools");
|
||||
}}
|
||||
className={`w-72 h-14 flex items-center justify-center transition-colors text-2xl mc-text-shadow mt-2 outline-none border-none ${focusIndex === 0 ? "text-[#FFFF55]" : "text-white"
|
||||
}`}
|
||||
style={{
|
||||
backgroundImage:
|
||||
focusIndex === 0
|
||||
? "url('/images/button_highlighted.png')"
|
||||
: "url('/images/Button_Background.png')",
|
||||
backgroundSize: "100% 100%",
|
||||
imageRendering: "pixelated",
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import DevtoolsView from "../components/views/DevtoolsView";
|
||||
import SkinsView from "../components/views/SkinsView";
|
||||
import WorkshopView from "../components/views/WorkshopView";
|
||||
import SetupView from "../components/views/SetupView";
|
||||
import PckEditorView from "../components/views/PckEditorView";
|
||||
import ArcEditorView from "../components/views/ArcEditorView";
|
||||
import SkinViewer from "../components/common/SkinViewer";
|
||||
import TeamModal from "../components/modals/TeamModal";
|
||||
import PanoramaBackground from "../components/common/PanoramaBackground";
|
||||
@@ -339,6 +341,12 @@ export default function App() {
|
||||
{activeView === "devtools" && (
|
||||
<DevtoolsView key="devtools-view" />
|
||||
)}
|
||||
{activeView === "pck-editor" && (
|
||||
<PckEditorView key="pck-editor-view" />
|
||||
)}
|
||||
{activeView === "arc-editor" && (
|
||||
<ArcEditorView key="arc-editor-view" />
|
||||
)}
|
||||
{activeView === "skins" && <SkinsView key="skins-view" />}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user