mirror of
https://github.com/MinecraftConsole/MinecraftLegacy.git
synced 2026-07-15 23:30:56 +00:00
Merge pull request #1 from CriadorMods/code-improvements
TS conversion 1/2, upgrade logic & state management, react-router-dom
This commit is contained in:
@@ -45,7 +45,7 @@ Push the built `dist/` output to your Cloudflare Pages project.
|
||||
## Stack
|
||||
|
||||
- React 18
|
||||
- Vite 5
|
||||
- Vite 8
|
||||
- Minecraft font (`typeface-minecraft`)
|
||||
- Cloudflare Pages
|
||||
|
||||
|
||||
@@ -70,6 +70,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
1970
package-lock.json
generated
1970
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -20,12 +20,19 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"path": "^0.12.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typeface-minecraft": "^1.0.0"
|
||||
"react-router-dom": "^7.13.2",
|
||||
"typeface-minecraft": "^1.0.0",
|
||||
"typescript": "^6.0.2",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"vite": "^5.4.11"
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"vite": "^8.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
src/App.jsx
117
src/App.jsx
@@ -1,40 +1,15 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import dirtBg from "../assets/images/backgrounds/Dirt_background_JE1.webp";
|
||||
import endPoemBg from "../assets/images/backgrounds/End_Poem_Background_JE.webp";
|
||||
import endStoneBg from "../assets/images/backgrounds/End_Stone_background_BE2.webp";
|
||||
import portalBg from "../assets/images/backgrounds/Portal_Backgrounid_Je1.webp";
|
||||
import steveAndAlex from "../assets/images/deco/steveandalex.png";
|
||||
import technoDeco from "../assets/images/deco/techno.png";
|
||||
import codLoader from "../assets/images/loading/Cod_loading.webp";
|
||||
import gearLoader from "../assets/images/loading/Gear_loading.webp";
|
||||
import logoLarge from "../assets/images/logos/MINECRAFT LEGACY EDITION logo large.png";
|
||||
import logoMedium from "../assets/images/logos/MINECRAFT LEGACY EDITION medium.png";
|
||||
import logoSmall from "../assets/images/logos/MINECRAFT LEGACY EDITION small.png";
|
||||
import clickSoundFile from "../assets/audio/sound/click.mp3";
|
||||
import pigSoundFile from "../assets/audio/sound/pig.mp3";
|
||||
import ariaMath from "../assets/audio/music/Aria Math.mp3";
|
||||
import beginning from "../assets/audio/music/Beginning.mp3";
|
||||
import biomeFest from "../assets/audio/music/Biome Fest.mp3";
|
||||
import blindSpots from "../assets/audio/music/Blind Spots.mp3";
|
||||
import clark from "../assets/audio/music/Clark.mp3";
|
||||
import danny from "../assets/audio/music/Danny.mp3";
|
||||
import dreiton from "../assets/audio/music/Dreiton.mp3";
|
||||
import dryHands from "../assets/audio/music/Dry Hands.mp3";
|
||||
import haggstrom from "../assets/audio/music/Haggstrom.mp3";
|
||||
import hauntMuskie from "../assets/audio/music/Haunt Muskie.mp3";
|
||||
import miceOnVenus from "../assets/audio/music/Mice On Venus.mp3";
|
||||
import minecraftSong from "../assets/audio/music/Minecraft.mp3";
|
||||
import moogCity from "../assets/audio/music/Moog City 2.mp3";
|
||||
import subwooferLullaby from "../assets/audio/music/Subwoofer Lullaby.mp3";
|
||||
import sweden from "../assets/audio/music/Sweden.mp3";
|
||||
import taswell from "../assets/audio/music/Taswell.mp3";
|
||||
import wetHands from "../assets/audio/music/Wet Hands.mp3";
|
||||
// Assets
|
||||
import * as Assets from "@/utils/ImportAssets";
|
||||
|
||||
// Zustand
|
||||
import useRepoStore from "@/zustand/ReposStore";
|
||||
|
||||
const logoSet = {
|
||||
small: logoSmall,
|
||||
medium: logoMedium,
|
||||
large: logoLarge
|
||||
small: Assets.logoSmall,
|
||||
medium: Assets.logoMedium,
|
||||
large: Assets.logoLarge
|
||||
};
|
||||
|
||||
const COD_EASTER_EGG_CHANCE = 0.0000003;
|
||||
@@ -42,37 +17,35 @@ const MIN_LOADING_MS = 1200;
|
||||
const LOADER_FADE_MS = 420;
|
||||
|
||||
const MUSIC_TRACKS = [
|
||||
ariaMath,
|
||||
beginning,
|
||||
biomeFest,
|
||||
blindSpots,
|
||||
clark,
|
||||
danny,
|
||||
dreiton,
|
||||
dryHands,
|
||||
haggstrom,
|
||||
hauntMuskie,
|
||||
miceOnVenus,
|
||||
minecraftSong,
|
||||
moogCity,
|
||||
subwooferLullaby,
|
||||
sweden,
|
||||
taswell,
|
||||
wetHands
|
||||
Assets.ariaMath,
|
||||
Assets.beginning,
|
||||
Assets.biomeFest,
|
||||
Assets.blindSpots,
|
||||
Assets.clark,
|
||||
Assets.danny,
|
||||
Assets.dreiton,
|
||||
Assets.dryHands,
|
||||
Assets.haggstrom,
|
||||
Assets.hauntMuskie,
|
||||
Assets.miceOnVenus,
|
||||
Assets.minecraftSong,
|
||||
Assets.moogCity,
|
||||
Assets.subwooferLullaby,
|
||||
Assets.sweden,
|
||||
Assets.taswell,
|
||||
Assets.wetHands
|
||||
];
|
||||
|
||||
const BACKGROUND_IMAGES = [dirtBg, endPoemBg, endStoneBg, portalBg];
|
||||
const BACKGROUND_IMAGES = [Assets.dirtBg, Assets.endPoemBg, Assets.endStoneBg, Assets.portalBg];
|
||||
|
||||
function randomItem(list) {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [repoConfig, setRepoConfig] = useState([]);
|
||||
const { projects, loading, error } = useRepoStore();
|
||||
const [query, setQuery] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [loaderFadingOut, setLoaderFadingOut] = useState(false);
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
|
||||
@@ -87,29 +60,9 @@ function App() {
|
||||
|
||||
const backgroundImage = useMemo(() => randomItem(BACKGROUND_IMAGES), []);
|
||||
const loadingImage = useMemo(() => {
|
||||
return Math.random() < COD_EASTER_EGG_CHANCE ? codLoader : gearLoader;
|
||||
}, []);
|
||||
const isCodLoadingScreen = loadingImage === codLoader;
|
||||
|
||||
useEffect(() => {
|
||||
const loadRepos = async () => {
|
||||
try {
|
||||
const jsonFile = window.location.hostname === "localhost" ? "placeholder.json" : "projects.json";
|
||||
const response = await fetch(`https://raw.githubusercontent.com/MinecraftConsole/json/refs/heads/main/${jsonFile}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Could not load project list (${response.status})`);
|
||||
}
|
||||
const data = await response.json();
|
||||
setRepoConfig(Array.isArray(data.repos) ? data.repos : []);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Unknown error loading repos");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadRepos();
|
||||
return Math.random() < COD_EASTER_EGG_CHANCE ? Assets.codLoader : Assets.gearLoader;
|
||||
}, []);
|
||||
const isCodLoadingScreen = loadingImage === Assets.codLoader;
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
@@ -137,7 +90,7 @@ function App() {
|
||||
backgroundMusicRef.current = music;
|
||||
|
||||
clickPoolRef.current = Array.from({ length: 5 }, () => {
|
||||
const click = new Audio(clickSoundFile);
|
||||
const click = new Audio(Assets.clickSoundFile);
|
||||
click.volume = 0.2;
|
||||
click.preload = "auto";
|
||||
return click;
|
||||
@@ -183,7 +136,7 @@ function App() {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const pig = new Audio(pigSoundFile);
|
||||
const pig = new Audio(Assets.pigSoundFile);
|
||||
pig.volume = 0.35;
|
||||
pig.preload = "auto";
|
||||
pigAudioRef.current = pig;
|
||||
@@ -208,13 +161,13 @@ function App() {
|
||||
};
|
||||
|
||||
const sortedRepos = useMemo(() => {
|
||||
return [...repoConfig].sort((a, b) => {
|
||||
return [...projects].sort((a, b) => {
|
||||
const aPriority = Number.isFinite(a.priority) ? a.priority : 9999;
|
||||
const bPriority = Number.isFinite(b.priority) ? b.priority : 9999;
|
||||
if (aPriority !== bPriority) return aPriority - bPriority;
|
||||
return String(a.name || "").localeCompare(String(b.name || ""));
|
||||
});
|
||||
}, [repoConfig]);
|
||||
}, [projects]);
|
||||
|
||||
const filteredRepos = useMemo(() => {
|
||||
const normalized = query.trim().toLowerCase();
|
||||
@@ -244,7 +197,7 @@ function App() {
|
||||
rel="noreferrer"
|
||||
aria-label="Visit Minecraft.net"
|
||||
>
|
||||
<img src={steveAndAlex} alt="Steve and Alex" className="top-link-image" />
|
||||
<img src={Assets.steveAndAlex} alt="Steve and Alex" className="top-link-image" />
|
||||
</a>
|
||||
|
||||
{showLoader && (
|
||||
@@ -376,7 +329,7 @@ function App() {
|
||||
onMouseEnter={handleTechnoMouseEnter}
|
||||
onMouseLeave={handleTechnoMouseLeave}
|
||||
>
|
||||
<img src={technoDeco} alt="Technoblade" className="techno-pig" />
|
||||
<img src={Assets.technoDeco} alt="Technoblade" className="techno-pig" />
|
||||
<div className="techno-text">
|
||||
<p className="techno-quote">"If you wish to defeat me, train for another 100 years."</p>
|
||||
<p className="techno-attr">- Technoblade</p>
|
||||
|
||||
10
src/global.d.ts
vendored
Normal file
10
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.png";
|
||||
declare module "*.jpg";
|
||||
declare module "*.jpeg";
|
||||
declare module "*.webp";
|
||||
declare module "*.svg";
|
||||
declare module "*.mp3";
|
||||
declare module "*.wav";
|
||||
declare module "*.ogg";
|
||||
11
src/main.jsx
11
src/main.jsx
@@ -1,11 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "typeface-minecraft";
|
||||
import App from "./App.jsx";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
35
src/main.tsx
Normal file
35
src/main.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import "typeface-minecraft";
|
||||
import "@/css/styles.css";
|
||||
|
||||
// Utils
|
||||
import FetchProjects from "@/utils/FetchProjects";
|
||||
|
||||
// Pages
|
||||
import Main from "@/App";
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
|
||||
const App = () => {
|
||||
const Fetch = async () => {
|
||||
await FetchProjects.SetProjects();
|
||||
};
|
||||
|
||||
Fetch();
|
||||
return (
|
||||
<>
|
||||
<Routes>
|
||||
<Route path="/" element={<Main />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
document.addEventListener("contextmenu", (e) => e.preventDefault());
|
||||
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
);
|
||||
7
src/types/projects.ts
Normal file
7
src/types/projects.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface IProjects {
|
||||
name: string;
|
||||
url: string;
|
||||
priority: number;
|
||||
tag: string;
|
||||
mirror: string;
|
||||
}
|
||||
27
src/utils/FetchProjects.ts
Normal file
27
src/utils/FetchProjects.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Types
|
||||
import { IProjects } from "@/types/projects";
|
||||
|
||||
// Zustand
|
||||
import useReposStore from "@/zustand/ReposStore";
|
||||
|
||||
type ReposResponse = {
|
||||
repos: IProjects[];
|
||||
};
|
||||
|
||||
export default new (class {
|
||||
public async SetProjects(): Promise<void> {
|
||||
try {
|
||||
const jsonFile = window.location.hostname === "localhost" ? "placeholder.json" : "projects.json";
|
||||
const res = await fetch(`https://raw.githubusercontent.com/MinecraftConsole/json/refs/heads/main/${jsonFile}`);
|
||||
if (!res.ok) throw new Error("Request failed");
|
||||
|
||||
const data: ReposResponse = await res.json();
|
||||
|
||||
useReposStore.setState({ projects: data.repos ?? [] });
|
||||
} catch (err) {
|
||||
useReposStore.setState({ error: err instanceof Error ? err.message : "An unknown error occurred" });
|
||||
} finally {
|
||||
useReposStore.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
})();
|
||||
35
src/utils/ImportAssets.ts
Normal file
35
src/utils/ImportAssets.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Backgrounds
|
||||
export { default as dirtBg } from "../../assets/images/backgrounds/Dirt_background_JE1.webp";
|
||||
export { default as endPoemBg } from "../../assets/images/backgrounds/End_Poem_Background_JE.webp";
|
||||
export { default as endStoneBg } from "../../assets/images/backgrounds/End_Stone_background_BE2.webp";
|
||||
export { default as portalBg } from "../../assets/images/backgrounds/Portal_Backgrounid_Je1.webp";
|
||||
|
||||
// Images
|
||||
export { default as steveAndAlex } from "../../assets/images/deco/steveandalex.png";
|
||||
export { default as technoDeco } from "../../assets/images/deco/techno.png";
|
||||
export { default as codLoader } from "../../assets/images/loading/Cod_loading.webp";
|
||||
export { default as gearLoader } from "../../assets/images/loading/Gear_loading.webp";
|
||||
export { default as logoLarge } from "../../assets/images/logos/MINECRAFT LEGACY EDITION logo large.png";
|
||||
export { default as logoMedium } from "../../assets/images/logos/MINECRAFT LEGACY EDITION medium.png";
|
||||
export { default as logoSmall } from "../../assets/images/logos/MINECRAFT LEGACY EDITION small.png";
|
||||
|
||||
// Audio
|
||||
export { default as clickSoundFile } from "../../assets/audio/sound/click.mp3";
|
||||
export { default as pigSoundFile } from "../../assets/audio/sound/pig.mp3";
|
||||
export { default as ariaMath } from "../../assets/audio/music/Aria Math.mp3";
|
||||
export { default as beginning } from "../../assets/audio/music/Beginning.mp3";
|
||||
export { default as biomeFest } from "../../assets/audio/music/Biome Fest.mp3";
|
||||
export { default as blindSpots } from "../../assets/audio/music/Blind Spots.mp3";
|
||||
export { default as clark } from "../../assets/audio/music/Clark.mp3";
|
||||
export { default as danny } from "../../assets/audio/music/Danny.mp3";
|
||||
export { default as dreiton } from "../../assets/audio/music/Dreiton.mp3";
|
||||
export { default as dryHands } from "../../assets/audio/music/Dry Hands.mp3";
|
||||
export { default as haggstrom } from "../../assets/audio/music/Haggstrom.mp3";
|
||||
export { default as hauntMuskie } from "../../assets/audio/music/Haunt Muskie.mp3";
|
||||
export { default as miceOnVenus } from "../../assets/audio/music/Mice On Venus.mp3";
|
||||
export { default as minecraftSong } from "../../assets/audio/music/Minecraft.mp3";
|
||||
export { default as moogCity } from "../../assets/audio/music/Moog City 2.mp3";
|
||||
export { default as subwooferLullaby } from "../../assets/audio/music/Subwoofer Lullaby.mp3";
|
||||
export { default as sweden } from "../../assets/audio/music/Sweden.mp3";
|
||||
export { default as taswell } from "../../assets/audio/music/Taswell.mp3";
|
||||
export { default as wetHands } from "../../assets/audio/music/Wet Hands.mp3";
|
||||
28
src/zustand/ReposStore.ts
Normal file
28
src/zustand/ReposStore.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
// Types
|
||||
import { IProjects } from "@/types/projects";
|
||||
|
||||
type State = {
|
||||
loading: boolean;
|
||||
projects: IProjects[];
|
||||
error: string;
|
||||
};
|
||||
|
||||
type Actions = {
|
||||
setLoading: (x: boolean) => void;
|
||||
setProjects: (x: IProjects[]) => void;
|
||||
setError: (x: string) => void;
|
||||
};
|
||||
|
||||
const useRepoStore = create<State & Actions>((set) => ({
|
||||
projects: [],
|
||||
loading: true,
|
||||
error: "",
|
||||
|
||||
setLoading: (loading) => set(() => ({ loading })),
|
||||
setProjects: (projects) => set(() => ({ projects })),
|
||||
setError: (error) => set(() => ({ error })),
|
||||
}));
|
||||
|
||||
export default useRepoStore;
|
||||
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
"allowJs": true,
|
||||
// "checkJs": true,
|
||||
"noEmit": true,
|
||||
|
||||
"outDir": "dist",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()]
|
||||
});
|
||||
14
vite.config.ts
Normal file
14
vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve("src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user