chore: react code cleanup

This commit is contained in:
neoapps-dev 2026-04-16 12:34:11 +03:00
parent 51f2a78469
commit 1a0f51d698
17 changed files with 71 additions and 138 deletions

View file

@ -1,7 +1,6 @@
import { useState, useEffect } from "react";
import { useLocalStorage } from "./useLocalStorage";
import { TauriService } from "../services/TauriService";
export function useAppConfig() {
const [username, setUsername] = useLocalStorage("lce-username", "Steve");
const [theme, setTheme] = useLocalStorage("lce-theme", "Modern");
@ -15,12 +14,10 @@ export function useAppConfig() {
const [profile, setProfile] = useLocalStorage("lce-profile", "legacy_evolved");
const [legacyMode, setLegacyMode] = useLocalStorage("lce-legacy-mode", false);
const [hasCompletedSetup, setHasCompletedSetup] = useLocalStorage("lce-setup-completed", false);
const [isLoaded, setIsLoaded] = useState(false);
const [linuxRunner, setLinuxRunner] = useState<string | undefined>();
const [perfBoost, setPerfBoost] = useState(false);
const [customEditions, setCustomEditions] = useState<any[]>([]);
useEffect(() => {
TauriService.loadConfig().then((config) => {
if (config.username) setUsername(config.username);

View file

@ -61,7 +61,6 @@ export function useAudioController({ musicVol, sfxVol, showIntro, isGameRunning,
const [splashIndex, setSplashIndex] = useState(-1);
const musicPausedRef = useRef<{ at: number; track: number } | null>(null);
const fadeIntervalRef = useRef<any>(null);
const playSfx = useCallback((file: string) => {
const a = new Audio(`/sounds/${file}`);
a.volume = sfxVol / 100;
@ -71,7 +70,6 @@ export function useAudioController({ musicVol, sfxVol, showIntro, isGameRunning,
const playPressSound = useCallback(() => playSfx("press.wav"), [playSfx]);
const playBackSound = useCallback(() => playSfx("back.ogg"), [playSfx]);
const playSplashSound = useCallback(() => playSfx("orb.ogg"), [playSfx]);
const fadeOut = useCallback((audio: HTMLAudioElement, duration: number = 500) => {
return new Promise<void>((resolve) => {
if (fadeIntervalRef.current) clearInterval(fadeIntervalRef.current);
@ -132,16 +130,13 @@ export function useAudioController({ musicVol, sfxVol, showIntro, isGameRunning,
useEffect(() => {
if (showIntro) return;
if (audioElement) return;
const audio = new Audio(TRACKS[currentTrack]);
audio.volume = musicVol / 100;
const handleEnded = () => setCurrentTrack((prev) => (prev + 1) % TRACKS.length);
audio.addEventListener("ended", handleEnded);
const playPromise = audio.play();
if (playPromise !== undefined) {
playPromise.catch(() => {
console.log("Autoplay prevented, waiting for user interaction");
const startMusic = () => {
audio.play().catch(() => { });
document.removeEventListener("click", startMusic);

View file

@ -1,6 +1,5 @@
import { useEffect } from "react";
import RpcService from "../services/RpcService";
interface DiscordRPCProps {
rpcEnabled: boolean;
showIntro: boolean;
@ -29,7 +28,6 @@ export function useDiscordRPC({
useEffect(() => {
const updateRPC = async () => {
if (!rpcEnabled || showIntro || !username) return;
if (!isWindowVisible && !isGameRunning && downloadProgress === null)
return;

View file

@ -191,7 +191,7 @@ export function useGameManager({
const newEdition = {
...edition,
id,
titleImage: "/images/minecraft_title_tucustom.png",
titleImage: "/images/MenuTitle.png",
};
setCustomEditions([...customEditions, newEdition]);
return id;

View file

@ -1,5 +1,4 @@
import { useEffect, useRef, useState, useCallback } from 'react';
export interface UseGamepadProps {
playSfx: (file: string) => void;
isWindowVisible: boolean;
@ -57,18 +56,15 @@ export const useGamepad = ({ playSfx, isWindowVisible }: UseGamepadProps) => {
return typeof btn === "object" ? btn.value : (btn as any) ?? 0;
};
const justPressed = (i: number) => btnVal(i) > 0.5 && !lastButtons.current[i];
if (justPressed(1)) dispatchKey('Enter');
if (justPressed(2)) dispatchKey('Escape');
if (justPressed(4)) dispatchKey('Tab', true);
if (justPressed(5)) dispatchKey('Tab');
const newButtons: Record<number, boolean> = {};
gp.buttons.forEach((btn, i) => {
newButtons[i] = (typeof btn === "object" ? btn.value : btn) > 0.5;
});
lastButtons.current = newButtons;
const deadzone = 0.5;
const axisY = gp.axes[2] ?? 0;
const prevY = lastAxes.current[2] ?? 0;
@ -76,7 +72,6 @@ export const useGamepad = ({ playSfx, isWindowVisible }: UseGamepadProps) => {
dispatchKey(axisY < 0 ? 'ArrowDown' : 'ArrowUp');
}
lastAxes.current[2] = axisY;
const axisX = gp.axes[1] ?? 0;
const prevX = lastAxes.current[1] ?? 0;
if (Math.abs(axisX) > deadzone && Math.abs(prevX) <= deadzone) {
@ -94,17 +89,16 @@ export const useGamepad = ({ playSfx, isWindowVisible }: UseGamepadProps) => {
useEffect(() => {
const handleConnect = () => setConnected(true);
const handleDisconnect = () => {
const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
const hasGamepads = Array.from(gamepads).some(gp => gp !== null);
setConnected(hasGamepads);
const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];
const hasGamepads = Array.from(gamepads).some(gp => gp !== null);
setConnected(hasGamepads);
};
window.addEventListener("gamepadconnected", handleConnect);
window.addEventListener("gamepaddisconnected", handleDisconnect);
const initialGamepads = navigator.getGamepads ? navigator.getGamepads() : [];
if (Array.from(initialGamepads).some(gp => gp !== null)) {
setConnected(true);
setConnected(true);
}
return () => {

View file

@ -1,5 +1,4 @@
import { useState, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T | ((val: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {

View file

@ -1,17 +1,12 @@
import { useMemo } from 'react';
export function usePlatform() {
const platform = useMemo(() => {
if (typeof window === 'undefined') return { isLinux: false, isMac: false, isWindows: false };
const ua = window.navigator.userAgent.toLowerCase();
const plat = window.navigator.platform.toLowerCase();
const isLinux = plat.includes('linux') || ua.includes('linux');
const isMac = plat.includes('mac') || ua.includes('mac');
const isWindows = plat.includes('win') || ua.includes('win');
return { isLinux, isMac, isWindows };
}, []);

View file

@ -17,12 +17,9 @@ export function useSkinSync({ profile, editions }: UseSkinSyncProps) {
const [skinUrl, setSkinUrl] = useLocalStorage("lce-skin", "/images/Default.png");
const [skinIsSlim, setSkinIsSlim] = useLocalStorage("lce-skin-slim", false);
const [skinBase64, setSkinBase64] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
if (!skinUrl) return;
const edition = editions.find((e) => e.id === profile);
const supportsSlim = edition?.supportsSlimSkins ?? false;
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = async () => {
@ -38,7 +35,7 @@ export function useSkinSync({ profile, editions }: UseSkinSyncProps) {
try {
const res = await fetch(b64);
const buf = await res.arrayBuffer();
const animValue = (supportsSlim && skinIsSlim) ? (1 << 19) : 0;
const animValue = (skinIsSlim) ? (1 << 19) : 0;
const pckBuf = PckService.serializePCK({
version: 2,
endianness: "little",

View file

@ -1,17 +1,13 @@
import { useState, useEffect, useCallback } from "react";
import pkg from "../../package.json";
const CURRENT_VERSION = pkg.version;
const REPO_URL = "https://api.github.com/repos/LCE-Hub/LCE-Emerald-Launcher/releases/latest";
function isNewerVersion(latest: string, current: string): boolean {
const latestParts = latest.split('.').map(Number);
const currentParts = current.split('.').map(Number);
for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
const latestPart = latestParts[i] || 0;
const currentPart = currentParts[i] || 0;
if (latestPart > currentPart) return true;
if (latestPart < currentPart) return false;
}
@ -21,15 +17,12 @@ function isNewerVersion(latest: string, current: string): boolean {
export function useUpdateCheck() {
const [updateMessage, setUpdateMessage] = useState<string | null>(null);
const checkUpdates = useCallback(async () => {
try {
const response = await fetch(REPO_URL);
if (!response.ok) return;
const data = await response.json();
const latestVersion = data.tag_name.replace(/^v/, '');
if (isNewerVersion(latestVersion, CURRENT_VERSION)) {
setUpdateMessage(`Version ${data.tag_name} is now available!`);
}

View file

@ -29,7 +29,6 @@ import {
} from "../context/LauncherContext";
import { TauriService } from "../services/TauriService";
import pkg from "../../package.json";
export default function App() {
const {
showIntro,
@ -47,15 +46,12 @@ export default function App() {
updateMessage,
clearUpdateMessage,
} = useUI();
const config = useConfig();
const audio = useAudio();
const game = useGame();
const { skinUrl, setSkinUrl } = useSkin();
const [showSetup, setShowSetup] = useState(true);
const [displayIsDay, setDisplayIsDay] = useState(config.isDayTime);
useEffect(() => {
setDisplayIsDay(config.isDayTime);
}, [config.isDayTime]);
@ -65,7 +61,6 @@ export default function App() {
);
const selectedVersionName = selectedEdition?.name || "";
const hasAnyInstall = game.installs.length > 0;
const titleImage = hasAnyInstall
? (selectedEdition?.titleImage || "/images/MenuTitle.png")
: "/images/MenuTitle.png";

View file

@ -1,9 +1,7 @@
import { ArcEntry, ArcFile, LocFile, LocLanguage, LocString } from "../types/arc";
export class ArcService {
private static enc = new TextEncoder();
private static dec = new TextDecoder('utf-8');
private static readUTF(view: DataView, off: number): { str: string; next: number } {
const len = view.getUint16(off, false);
const bytes = new Uint8Array(view.buffer, view.byteOffset + off + 2, len);
@ -22,12 +20,9 @@ export class ArcService {
const view = new DataView(buffer);
const raw = new Uint8Array(buffer);
let off = 0;
const count = view.getInt32(off, false);
off += 4;
if (count < 0 || count > 200000) throw new Error("Invalid file count");
const entries: ArcEntry[] = [];
for (let i = 0; i < count; i++) {
const { str: rawName, next } = this.readUTF(view, off);
@ -36,7 +31,6 @@ export class ArcService {
off += 4;
const size = view.getInt32(off, false);
off += 4;
let filename = rawName;
let isCompressed = false;
if (filename.charCodeAt(0) === 42) {
@ -69,10 +63,8 @@ export class ArcService {
const view = new DataView(buffer);
const out = new Uint8Array(buffer);
let pos = 0;
view.setInt32(pos, arc.entries.length, false);
pos += 4;
arc.entries.forEach((e, i) => {
const nb = this.enc.encode((e.isCompressed ? "*" : "") + e.filename);
view.setUint16(pos, nb.length, false);
@ -98,57 +90,53 @@ export class ArcService {
const buf = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
const view = new DataView(buf);
let off = 0;
const version = view.getInt32(off, false);
off += 4;
const langCount = view.getInt32(off, false);
off += 4;
const langMeta: { id: string, size: number }[] = [];
for (let i = 0; i < langCount; i++) {
const { str: id, next } = this.readUTF(view, off);
off = next;
const size = view.getInt32(off, false);
off += 4;
langMeta.push({ id, size });
const { str: id, next } = this.readUTF(view, off);
off = next;
const size = view.getInt32(off, false);
off += 4;
langMeta.push({ id, size });
}
const languages: LocLanguage[] = [];
for (let i = 0; i < langMeta.length; i++) {
const blobView = new DataView(buf, off, langMeta[i].size);
let boff = 0;
const langVer = blobView.getInt32(boff, false);
boff += 4;
let isStatic = false;
if (langVer > 0) {
isStatic = blobView.getUint8(boff) !== 0;
boff += 1;
const blobView = new DataView(buf, off, langMeta[i].size);
let boff = 0;
const langVer = blobView.getInt32(boff, false);
boff += 4;
let isStatic = false;
if (langVer > 0) {
isStatic = blobView.getUint8(boff) !== 0;
boff += 1;
}
const { str: langId, next: n2 } = this.readUTF(blobView, boff);
boff = n2;
const total = blobView.getInt32(boff, false);
boff += 4;
const strings: LocString[] = [];
if (!isStatic) {
for (let j = 0; j < total; j++) {
const { str: key, next: nk } = this.readUTF(blobView, boff);
boff = nk;
const { str: val, next: nv } = this.readUTF(blobView, boff);
boff = nv;
strings.push({ key, value: val });
}
const { str: langId, next: n2 } = this.readUTF(blobView, boff);
boff = n2;
const total = blobView.getInt32(boff, false);
boff += 4;
const strings: LocString[] = [];
if (!isStatic) {
for (let j = 0; j < total; j++) {
const { str: key, next: nk } = this.readUTF(blobView, boff);
boff = nk;
const { str: val, next: nv } = this.readUTF(blobView, boff);
boff = nv;
strings.push({ key, value: val });
}
} else {
for (let j = 0; j < total; j++) {
const { str: val, next: nv } = this.readUTF(blobView, boff);
boff = nv;
strings.push({ value: val });
}
} else {
for (let j = 0; j < total; j++) {
const { str: val, next: nv } = this.readUTF(blobView, boff);
boff = nv;
strings.push({ value: val });
}
}
languages.push({ id: langMeta[i].id, version: langVer, isStatic, langId, strings });
off += langMeta[i].size;
languages.push({ id: langMeta[i].id, version: langVer, isStatic, langId, strings });
off += langMeta[i].size;
}
return { version, languages };
@ -156,53 +144,47 @@ export class ArcService {
static serializeLOC(loc: LocFile): Uint8Array {
const blobs = loc.languages.map(lang => {
const parts: Uint8Array[] = [];
const vh = new Uint8Array(4);
new DataView(vh.buffer).setInt32(0, lang.version, false);
parts.push(vh);
const parts: Uint8Array[] = [];
const vh = new Uint8Array(4);
new DataView(vh.buffer).setInt32(0, lang.version, false);
parts.push(vh);
if (lang.version > 0) {
parts.push(new Uint8Array([lang.isStatic ? 1 : 0]));
}
if (lang.version > 0) {
parts.push(new Uint8Array([lang.isStatic ? 1 : 0]));
parts.push(this.writeUTF(lang.langId));
const sc = new Uint8Array(4);
new DataView(sc.buffer).setInt32(0, lang.strings.length, false);
parts.push(sc);
lang.strings.forEach(s => {
if (!lang.isStatic && s.key) {
parts.push(this.writeUTF(s.key));
}
parts.push(this.writeUTF(s.value));
});
parts.push(this.writeUTF(lang.langId));
const sc = new Uint8Array(4);
new DataView(sc.buffer).setInt32(0, lang.strings.length, false);
parts.push(sc);
lang.strings.forEach(s => {
if (!lang.isStatic && s.key) {
parts.push(this.writeUTF(s.key));
}
parts.push(this.writeUTF(s.value));
});
const total = parts.reduce((a, b) => a + b.length, 0);
const blob = new Uint8Array(total);
let p = 0;
parts.forEach(b => {
blob.set(b, p);
p += b.length;
});
return blob;
const total = parts.reduce((a, b) => a + b.length, 0);
const blob = new Uint8Array(total);
let p = 0;
parts.forEach(b => {
blob.set(b, p);
p += b.length;
});
return blob;
});
const headerParts: Uint8Array[] = [];
const vh = new Uint8Array(4);
new DataView(vh.buffer).setInt32(0, loc.version, false);
headerParts.push(vh);
const lc = new Uint8Array(4);
new DataView(lc.buffer).setInt32(0, loc.languages.length, false);
headerParts.push(lc);
loc.languages.forEach((lang, i) => {
headerParts.push(this.writeUTF(lang.id));
const sz = new Uint8Array(4);
new DataView(sz.buffer).setInt32(0, blobs[i].length, false);
headerParts.push(sz);
headerParts.push(this.writeUTF(lang.id));
const sz = new Uint8Array(4);
new DataView(sz.buffer).setInt32(0, blobs[i].length, false);
headerParts.push(sz);
});
const allParts = [...headerParts, ...blobs];
@ -210,8 +192,8 @@ export class ArcService {
const out = new Uint8Array(total);
let p = 0;
allParts.forEach(b => {
out.set(b, p);
p += b.length;
out.set(b, p);
p += b.length;
});
return out;
}

View file

@ -1,5 +1,4 @@
import { ColFile, ColColor, ColWorldColor } from "../types/col";
export class ColService {
private static textDecoder = new TextDecoder("utf-8");
private static textEncoder = new TextEncoder();

View file

@ -7,7 +7,6 @@ import {
GrfHeader,
GrfNode
} from "../types/grf";
export class GrfService {
private static textDecoder = new TextDecoder("ascii");
private static textEncoder = new TextEncoder();
@ -133,7 +132,7 @@ export class GrfService {
let bodyView: DataView;
let bodyOff = { p: 0 };
if (header.compressionLevel !== GrfCompressionLevel.None) {
view.getInt32(off.p, false); // decompressedSize
view.getInt32(off.p, false); //neo: this is decompressedSize
off.p += 4;
const compressedSize = view.getInt32(off.p, false);
off.p += 4;

View file

@ -1,11 +1,9 @@
import { OptionsFile } from "../types/options";
export class OptionsService {
public static readOptions(buffer: ArrayBuffer, endianness: "little" | "big" = "little"): OptionsFile {
const rawData = new Uint8Array(buffer).slice();
const view = new DataView(buffer);
const little = endianness === "little";
const opt: Partial<OptionsFile> = {
endianness,
rawData

View file

@ -1,11 +1,9 @@
import { setActivity, start } from "tauri-plugin-drpc";
import { Activity, ActivityType, Assets, Timestamps, Button } from "tauri-plugin-drpc/activity";
class RPC {
private startTime: number = Date.now();
private initializationPromise: Promise<void> | null = null;
private initialized: boolean = false;
public async StartRPC() {
if (this.initialized) return;
if (sessionStorage.getItem('lce_rpc_started') === 'true') {
@ -38,16 +36,13 @@ class RPC {
activity.setDetails(details);
activity.setState(state);
activity.setActivity(ActivityType.Playing);
const assets = new Assets();
assets.setLargeImage("logo");
assets.setLargeText("LCE Emerald Launcher");
assets.setSmallImage("app-icon");
assets.setSmallText(isPlaying ? "Playing" : "In Menus");
activity.setAssets(assets);
activity.setTimestamps(new Timestamps(this.startTime));
activity.setButton([
new Button("Discord", "https://discord.gg/vD8akRU24f"),
new Button("GitHub", "https://github.com/LCE-Hub/LCE-Emerald-Launcher")

View file

@ -1,2 +0,0 @@
// empty :P
// what is this file lol ? - kay

View file

@ -1 +0,0 @@
// empty :P