Merge pull request #1 from CriadorMods/code-improvements

TS conversion 1/2, upgrade logic & state management, react-router-dom
This commit is contained in:
Andrew P. Harper
2026-04-12 00:36:40 -04:00
committed by GitHub
16 changed files with 945 additions and 1356 deletions

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}
}

View File

@@ -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
View 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";

View File

@@ -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
View 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
View File

@@ -0,0 +1,7 @@
export interface IProjects {
name: string;
url: string;
priority: number;
tag: string;
mirror: string;
}

View 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
View 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
View 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
View 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"]
}

View File

@@ -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
View 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"),
},
},
});