feat(react): typescriptify everything

This commit is contained in:
neoapps-dev
2026-05-23 18:31:49 +03:00
parent 043d8445f2
commit 736376dc04
33 changed files with 223 additions and 150 deletions
+5 -3
View File
@@ -2,6 +2,8 @@ import { useEffect, useRef, memo } from 'react';
import * as THREE from 'three'; import * as THREE from 'three';
import { PCKAsset, PCKAssetType } from '../../types/pck'; import { PCKAsset, PCKAssetType } from '../../types/pck';
type UVSet = Record<string, number[]>;
interface SkinPreview3DProps { interface SkinPreview3DProps {
asset: PCKAsset; asset: PCKAsset;
previewUrl?: string; previewUrl?: string;
@@ -73,7 +75,7 @@ const SkinPreview3D = memo(function SkinPreview3D({ asset, previewUrl, className
}; };
const isFallbackUrl = !previewUrl; const isFallbackUrl = !previewUrl;
const url = previewUrl || URL.createObjectURL(new Blob([asset.data as any], { type: 'image/png' })); const url = previewUrl || URL.createObjectURL(new Blob([asset.data], { type: 'image/png' }));
const textureLoader = new THREE.TextureLoader(); const textureLoader = new THREE.TextureLoader();
let active = true; let active = true;
textureLoader.load(url, (texture) => { textureLoader.load(url, (texture) => {
@@ -96,10 +98,10 @@ const SkinPreview3D = memo(function SkinPreview3D({ asset, previewUrl, className
return new THREE.MeshLambertMaterial({ map: matTex, transparent: true, alphaTest: 0.5, side: THREE.FrontSide }); return new THREE.MeshLambertMaterial({ map: matTex, transparent: true, alphaTest: 0.5, side: THREE.FrontSide });
}; };
const createPart = (w: number, h: number, d: number, uv: any, overlayUv?: any, isMirror = false) => { const createPart = (w: number, h: number, d: number, uv: UVSet, overlayUv?: UVSet, isMirror = false) => {
const group = new THREE.Group(); const group = new THREE.Group();
const geo = new THREE.BoxGeometry(w, h, d); const geo = new THREE.BoxGeometry(w, h, d);
const getMats = (uvSet: any) => { const getMats = (uvSet: UVSet) => {
return [ return [
createFaceMaterial(uvSet.right[0], uvSet.right[1], uvSet.right[2], uvSet.right[3], isMirror), // +x createFaceMaterial(uvSet.right[0], uvSet.right[1], uvSet.right[2], uvSet.right[3], isMirror), // +x
createFaceMaterial(uvSet.left[0], uvSet.left[1], uvSet.left[2], uvSet.left[3], isMirror), // -x createFaceMaterial(uvSet.left[0], uvSet.left[1], uvSet.left[2], uvSet.left[3], isMirror), // -x
+4 -2
View File
@@ -4,6 +4,8 @@ import * as THREE from 'three';
import { useLocalStorage } from '../../hooks/useLocalStorage'; import { useLocalStorage } from '../../hooks/useLocalStorage';
import { useConfig } from '../../context/LauncherContext'; import { useConfig } from '../../context/LauncherContext';
type UVSet = Record<string, number[]>;
interface SkinViewerProps { interface SkinViewerProps {
username: string; username: string;
setUsername: (name: string) => void; setUsername: (name: string) => void;
@@ -60,10 +62,10 @@ const SkinViewer = memo(function SkinViewer({ username, setUsername, playPressSo
return new THREE.MeshLambertMaterial({ map: matTex, transparent: true, alphaTest: 0.5, side: THREE.FrontSide }); return new THREE.MeshLambertMaterial({ map: matTex, transparent: true, alphaTest: 0.5, side: THREE.FrontSide });
}; };
const createPart = (w: number, h: number, d: number, uv: any, overlayUv?: any, swapMats = false, isLegacyMirror = false) => { const createPart = (w: number, h: number, d: number, uv: UVSet, overlayUv?: UVSet, swapMats = false, isLegacyMirror = false) => {
const group = new THREE.Group(); const group = new THREE.Group();
const geo = new THREE.BoxGeometry(w, h, d); const geo = new THREE.BoxGeometry(w, h, d);
const getMats = (uvSet: any) => { const getMats = (uvSet: UVSet) => {
const flipX = isLegacyMirror; const flipX = isLegacyMirror;
return [ return [
createFaceMaterial(swapMats ? uvSet.right[0] : uvSet.left[0], uvSet.left[1], uvSet.left[2], uvSet.left[3], flipX), // +x (L) createFaceMaterial(swapMats ? uvSet.right[0] : uvSet.left[0], uvSet.left[1], uvSet.left[2], uvSet.left[3], flipX), // +x (L)
+1 -1
View File
@@ -6,7 +6,7 @@ const appWindow = getCurrentWindow();
interface AppHeaderProps { interface AppHeaderProps {
playPressSound: () => void; playPressSound: () => void;
uiFade: any; uiFade: Record<string, unknown>;
} }
export const AppHeader = memo(function AppHeader({ playPressSound, uiFade }: AppHeaderProps) { export const AppHeader = memo(function AppHeader({ playPressSound, uiFade }: AppHeaderProps) {
+2 -1
View File
@@ -1,10 +1,11 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { memo } from "react"; import { memo } from "react";
import type { Edition } from "../../types/edition";
interface DownloadOverlayProps { interface DownloadOverlayProps {
downloadProgress: number | null; downloadProgress: number | null;
downloadingId: string | null; downloadingId: string | null;
editions: any[]; editions: Edition[];
} }
export const DownloadOverlay = memo(function DownloadOverlay({ downloadProgress, downloadingId, editions }: DownloadOverlayProps) { export const DownloadOverlay = memo(function DownloadOverlay({ downloadProgress, downloadingId, editions }: DownloadOverlayProps) {
+12 -10
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { TauriService } from "../../services/TauriService"; import { TauriService } from "../../services/TauriService";
import { lceLiveService, GameInvite } from "../../services/LceLiveService"; import { lceLiveService, GameInvite } from "../../services/LceLiveService";
import type { Edition } from "../../types/edition";
export default function ChooseInstanceModal({ export default function ChooseInstanceModal({
isOpen, isOpen,
@@ -16,7 +17,7 @@ export default function ChooseInstanceModal({
onClose: () => void; onClose: () => void;
playPressSound: (s?: string) => void; playPressSound: (s?: string) => void;
playBackSound: (s?: string) => void; playBackSound: (s?: string) => void;
editions: any[]; editions: Edition[];
installs: string[]; installs: string[];
invite: GameInvite | null; invite: GameInvite | null;
}) { }) {
@@ -26,7 +27,7 @@ export default function ChooseInstanceModal({
const [isJoining, setIsJoining] = useState(false); const [isJoining, setIsJoining] = useState(false);
const [focusIndex, setFocusIndex] = useState(0); const [focusIndex, setFocusIndex] = useState(0);
const validInstances = editions.filter((e: any) => const validInstances = editions.filter((e: Edition) =>
installs.includes(e.instanceId) installs.includes(e.instanceId)
); );
@@ -53,10 +54,11 @@ export default function ChooseInstanceModal({
setError(""); setError("");
setStatus("Accepting invite..."); setStatus("Accepting invite...");
try { try {
const inviteData = await lceLiveService.acceptGameInvite(invite.inviteId); const inviteData = await lceLiveService.acceptGameInvite(invite.inviteId) as Record<string, unknown>;
const hostIp = inviteData.hostIp || (typeof invite.from !== 'string' && (invite as any).from?.hostIp); const fromIp = typeof invite.from !== 'string' ? (invite.from as unknown as Record<string, string>).hostIp : undefined;
const hostPort = inviteData.hostPort || invite.hostPort; const hostIp: string = (inviteData.hostIp as string) || fromIp || invite.hostIp;
const sessionId = inviteData.signalingSessionId || invite.signalingSessionId || ""; const hostPort: number = (inviteData.hostPort as number) || invite.hostPort;
const sessionId = (inviteData.signalingSessionId as string) || invite.signalingSessionId || "";
if (sessionId) { if (sessionId) {
setStatus("Connecting via relay..."); setStatus("Connecting via relay...");
@@ -77,8 +79,8 @@ export default function ChooseInstanceModal({
]); ]);
} }
onClose(); onClose();
} catch (e: any) { } catch (e: unknown) {
setError(e.toString()); setError(e instanceof Error ? e.message : String(e));
setStatus(""); setStatus("");
setIsJoining(false); setIsJoining(false);
} }
@@ -101,7 +103,7 @@ export default function ChooseInstanceModal({
setFocusIndex((prev) => (prev - 1 + max) % max); setFocusIndex((prev) => (prev - 1 + max) % max);
} else if (e.key === "Enter") { } else if (e.key === "Enter") {
if (focusIndex === 0 && validInstances.length > 0) { if (focusIndex === 0 && validInstances.length > 0) {
const currentIdx = validInstances.findIndex((i: any) => i.instanceId === selectedInstance); const currentIdx = validInstances.findIndex((i: Edition) => i.instanceId === selectedInstance);
const next = (currentIdx + 1) % validInstances.length; const next = (currentIdx + 1) % validInstances.length;
setSelectedInstance(validInstances[next].instanceId); setSelectedInstance(validInstances[next].instanceId);
playPressSound(); playPressSound();
@@ -147,7 +149,7 @@ export default function ChooseInstanceModal({
{validInstances.length > 0 ? ( {validInstances.length > 0 ? (
<div className="w-full mb-4 flex flex-col gap-2 max-h-[300px] overflow-y-auto" <div className="w-full mb-4 flex flex-col gap-2 max-h-[300px] overflow-y-auto"
style={{ scrollbarWidth: "thin", scrollbarColor: "#373737 transparent" }}> style={{ scrollbarWidth: "thin", scrollbarColor: "#373737 transparent" }}>
{validInstances.map((inst: any) => { {validInstances.map((inst: Edition) => {
const isSelected = selectedInstance === inst.instanceId; const isSelected = selectedInstance === inst.instanceId;
return ( return (
<div <div
+9 -1
View File
@@ -41,7 +41,15 @@ export default function CustomTUModal({
playBackSound, playBackSound,
editingEdition = null, editingEdition = null,
initialPath = "", initialPath = "",
}: any) { }: {
isOpen: boolean;
onClose: () => void;
onImport: (ed: { name: string; desc: string; url: string; path?: string }) => void;
playPressSound: (sound?: string) => void;
playBackSound: (sound?: string) => void;
editingEdition?: { name: string; desc: string; url: string; path?: string } | null;
initialPath?: string;
}) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [desc, setDesc] = useState(""); const [desc, setDesc] = useState("");
const [url, setUrl] = useState(""); const [url, setUrl] = useState("");
+15 -6
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { TauriService } from "../../services/TauriService"; import { TauriService } from "../../services/TauriService";
import type { Edition } from "../../types/edition";
export default function SetUidModal({ export default function SetUidModal({
isOpen, isOpen,
@@ -10,7 +11,15 @@ export default function SetUidModal({
instances, instances,
installedVersions, installedVersions,
targetInstanceId, targetInstanceId,
}: any) { }: {
isOpen: boolean;
onClose: () => void;
playPressSound: (s?: string) => void;
playBackSound: (s?: string) => void;
instances: Edition[];
installedVersions: string[];
targetInstanceId: string;
}) {
const [mode, setMode] = useState<"manual" | "copy">("manual"); const [mode, setMode] = useState<"manual" | "copy">("manual");
const [uid, setUid] = useState("0xFF02F0C87E8AC1F2"); const [uid, setUid] = useState("0xFF02F0C87E8AC1F2");
const [selectedInstance, setSelectedInstance] = useState(""); const [selectedInstance, setSelectedInstance] = useState("");
@@ -44,7 +53,7 @@ export default function SetUidModal({
} }
}, [isOpen, targetInstanceId]); }, [isOpen, targetInstanceId]);
const validInstances = instances.filter((i: any) => installedVersions.includes(i.instanceId) && i.instanceId !== targetInstanceId); const validInstances = instances.filter((i: Edition) => installedVersions.includes(i.instanceId) && i.instanceId !== targetInstanceId);
const handleSave = async () => { const handleSave = async () => {
playPressSound("save_click.wav"); playPressSound("save_click.wav");
try { try {
@@ -74,8 +83,8 @@ export default function SetUidModal({
await TauriService.writeBinaryFile(`${targetPath}/uid.dat`, encodedUid); await TauriService.writeBinaryFile(`${targetPath}/uid.dat`, encodedUid);
onClose(); onClose();
} catch (e: any) { } catch (e: unknown) {
setError(e.toString()); setError(e instanceof Error ? e.message : String(e));
} }
}; };
@@ -198,7 +207,7 @@ export default function SetUidModal({
<span className="truncate"> <span className="truncate">
{selectedInstance {selectedInstance
? (() => { ? (() => {
const i = validInstances.find((inst: any) => inst.instanceId === selectedInstance); const i = validInstances.find((inst: Edition) => inst.instanceId === selectedInstance);
return i ? `${i.name} ${i.selectedBranch ? `(${i.selectedBranch})` : ""}` : "-- Select an Instance --"; return i ? `${i.name} ${i.selectedBranch ? `(${i.selectedBranch})` : ""}` : "-- Select an Instance --";
})() })()
: "-- Select an Instance --"} : "-- Select an Instance --"}
@@ -208,7 +217,7 @@ export default function SetUidModal({
{isDropdownOpen && validInstances.length > 0 && ( {isDropdownOpen && validInstances.length > 0 && (
<div className="absolute top-[60px] left-0 w-full max-h-40 overflow-y-auto bg-black/90 border-2 border-[#373737] z-50 flex flex-col custom-scrollbar shadow-xl" style={{ imageRendering: "pixelated" }}> <div className="absolute top-[60px] left-0 w-full max-h-40 overflow-y-auto bg-black/90 border-2 border-[#373737] z-50 flex flex-col custom-scrollbar shadow-xl" style={{ imageRendering: "pixelated" }}>
{validInstances.map((i: any) => ( {validInstances.map((i: Edition) => (
<div <div
key={i.instanceId} key={i.instanceId}
onClick={() => { onClick={() => {
+6 -1
View File
@@ -6,7 +6,12 @@ export default function TeamModal({
onClose, onClose,
playPressSound, playPressSound,
playSfx, playSfx,
}: any) { }: {
isOpen: boolean;
onClose: () => void;
playPressSound: () => void;
playSfx: (sound: string) => void;
}) {
const [focusIndex, setFocusIndex] = useState(0); const [focusIndex, setFocusIndex] = useState(0);
const team = [ const team = [
+7 -7
View File
@@ -71,7 +71,7 @@ export const ArcEditorView: React.FC = () => {
} }
setSelectedEntryIdx(null); setSelectedEntryIdx(null);
showNotification(`Loaded ${parsed.name}`); showNotification(`Loaded ${parsed.name}`);
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") { if (err !== "CANCELED") {
console.error("Failed to parse ARC", err); console.error("Failed to parse ARC", err);
showNotification("Failed to parse ARC", "error"); showNotification("Failed to parse ARC", "error");
@@ -96,7 +96,7 @@ export const ArcEditorView: React.FC = () => {
setOpenedPath(targetPath); setOpenedPath(targetPath);
showNotification("ARC Saved Successfully"); showNotification("ARC Saved Successfully");
} }
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Save failed", "error"); if (err !== "CANCELED") showNotification("Save failed", "error");
} }
}; };
@@ -109,7 +109,7 @@ export const ArcEditorView: React.FC = () => {
playPressSound(); playPressSound();
await TauriService.writeBinaryFile(path, entry.data); await TauriService.writeBinaryFile(path, entry.data);
showNotification(`Extracted: ${entry.filename}`); showNotification(`Extracted: ${entry.filename}`);
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Extraction failed", "error"); if (err !== "CANCELED") showNotification("Extraction failed", "error");
} }
}; };
@@ -212,7 +212,7 @@ export const ArcEditorView: React.FC = () => {
}; };
const treeData = useMemo(() => { const treeData = useMemo(() => {
const root: any = { name: "<root>", children: {}, isFolder: true }; const root: Record<string, any> = { name: "<root>", children: {}, isFolder: true };
filteredEntries.forEach((entry) => { filteredEntries.forEach((entry) => {
const parts = entry.filename.split(/\//); const parts = entry.filename.split(/\//);
let current = root; let current = root;
@@ -237,7 +237,7 @@ export const ArcEditorView: React.FC = () => {
setExpandedNodes(newExpanded); setExpandedNodes(newExpanded);
}; };
const renderTree = (node: any, path: string = "") => { const renderTree = (node: Record<string, any>, path: string = "") => {
const nodePath = path ? `${path}/${node.name}` : node.name; const nodePath = path ? `${path}/${node.name}` : node.name;
const isExpanded = expandedNodes.has(nodePath); const isExpanded = expandedNodes.has(nodePath);
@@ -285,7 +285,7 @@ export const ArcEditorView: React.FC = () => {
exit={{ height: 0, opacity: 0 }} exit={{ height: 0, opacity: 0 }}
className="ml-4 border-l border-white/10 overflow-hidden" className="ml-4 border-l border-white/10 overflow-hidden"
> >
{Object.values(node.children).map((child: any) => renderTree(child, nodePath))} {Object.values(node.children as Record<string, any>).map((child: Record<string, any>) => renderTree(child, nodePath))}
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
@@ -306,7 +306,7 @@ export const ArcEditorView: React.FC = () => {
await TauriService.writeBinaryFile(`${baseFolder}/${fileName}`, entry.data); await TauriService.writeBinaryFile(`${baseFolder}/${fileName}`, entry.data);
} }
showNotification("All Entries Exported"); showNotification("All Entries Exported");
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error"); if (err !== "CANCELED") showNotification("Export failed", "error");
} }
}; };
+4 -4
View File
@@ -47,9 +47,9 @@ export default function ColEditorView() {
const parsedCol = ColService.readCOL(buffer); const parsedCol = ColService.readCOL(buffer);
setCol(parsedCol); setCol(parsedCol);
showNotification(`Loaded ${file.name}`); showNotification(`Loaded ${file.name}`);
} catch (err: any) { } catch (err: unknown) {
console.error("Failed to parse COL", err); console.error("Failed to parse COL", err);
showNotification(err.message || "Failed to parse COL", "error"); showNotification(err instanceof Error ? err.message : "Failed to parse COL", "error");
} }
e.target.value = ""; e.target.value = "";
}; };
@@ -67,9 +67,9 @@ export default function ColEditorView() {
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showNotification("COL Saved Successfully"); showNotification("COL Saved Successfully");
} catch (err: any) { } catch (err: unknown) {
console.error("Failed to save COL", err); console.error("Failed to save COL", err);
showNotification(err.message || "Failed to save COL", "error"); showNotification(err instanceof Error ? err.message : "Failed to save COL", "error");
} }
}; };
+5 -5
View File
@@ -28,9 +28,9 @@ export default function GrfEditorView() {
setGrf(parsedGrf); setGrf(parsedGrf);
setFilename(file.name); setFilename(file.name);
showNotification(`Loaded ${file.name}`); showNotification(`Loaded ${file.name}`);
} catch (err: any) { } catch (err: unknown) {
console.error("Failed to parse GRF", err); console.error("Failed to parse GRF", err);
showNotification(err.message || "Failed to parse GRF", "error"); showNotification(err instanceof Error ? err.message : "Failed to parse GRF", "error");
} }
e.target.value = ""; e.target.value = "";
}; };
@@ -94,7 +94,7 @@ export default function GrfEditorView() {
playPressSound(); playPressSound();
try { try {
const buffer = GrfService.serializeGRF(grf); const buffer = GrfService.serializeGRF(grf);
const blob = new Blob([buffer as any]); const blob = new Blob([buffer]);
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
@@ -102,9 +102,9 @@ export default function GrfEditorView() {
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showNotification("GRF Saved Successfully"); showNotification("GRF Saved Successfully");
} catch (err: any) { } catch (err: unknown) {
console.error("Failed to save GRF", err); console.error("Failed to save GRF", err);
showNotification(err.message || "Failed to save GRF", "error"); showNotification(err instanceof Error ? err.message : "Failed to save GRF", "error");
} }
}; };
+3 -2
View File
@@ -6,6 +6,7 @@ import {
useAudio, useAudio,
useGame, useGame,
} from "../../context/LauncherContext"; } from "../../context/LauncherContext";
import type { Edition } from "../../types/edition";
const HomeView = memo(function HomeView() { const HomeView = memo(function HomeView() {
const { setActiveView, setShowCredits, focusSection, onNavigateToSkin } = const { setActiveView, setShowCredits, focusSection, onNavigateToSkin } =
@@ -24,7 +25,7 @@ const HomeView = memo(function HomeView() {
} = useGame(); } = useGame();
const isFocusedSection = focusSection === "menu"; const isFocusedSection = focusSection === "menu";
const selectedEdition = editions.find((e: any) => e.id === profile); const selectedEdition = editions.find((e: Edition) => e.id === profile);
const selectedVersionName = selectedEdition?.name || "Game"; const selectedVersionName = selectedEdition?.name || "Game";
const isInstalled = installs.includes(profile); const isInstalled = installs.includes(profile);
const isDownloading = downloadingId === profile; const isDownloading = downloadingId === profile;
@@ -128,7 +129,7 @@ const HomeView = memo(function HomeView() {
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }} transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
className="relative w-full max-w-[540px] flex flex-col space-y-3 outline-none" className="relative w-full max-w-[540px] flex flex-col space-y-3 outline-none"
> >
{buttons.map((btn: any, i: number) => ( {buttons.map((btn: { label: string; action: () => void; isDanger?: boolean; disabled: boolean; id?: string }, i: number) => (
<div key={i} className="relative w-full group"> <div key={i} className="relative w-full group">
<button <button
onMouseEnter={() => onMouseEnter={() =>
+25 -23
View File
@@ -11,6 +11,7 @@ import {
LceLiveAccount, LceLiveAccount,
FriendRequest, FriendRequest,
GameInvite, GameInvite,
DeviceLinkStartResponse,
} from "../../services/LceLiveService"; } from "../../services/LceLiveService";
import { TauriService } from "../../services/TauriService"; import { TauriService } from "../../services/TauriService";
import ChooseInstanceModal from "../modals/ChooseInstanceModal"; import ChooseInstanceModal from "../modals/ChooseInstanceModal";
@@ -31,7 +32,7 @@ const LceLiveView = memo(function LceLiveView() {
const [incomingReqs, setIncomingReqs] = useState<FriendRequest[]>([]); const [incomingReqs, setIncomingReqs] = useState<FriendRequest[]>([]);
const [outgoingReqs, setOutgoingReqs] = useState<FriendRequest[]>([]); const [outgoingReqs, setOutgoingReqs] = useState<FriendRequest[]>([]);
const [invites, setInvites] = useState<GameInvite[]>([]); const [invites, setInvites] = useState<GameInvite[]>([]);
const [linkData, setLinkData] = useState<any>(null); const [linkData, setLinkData] = useState<DeviceLinkStartResponse | null>(null);
const [linkError, setLinkError] = useState<string | null>(null); const [linkError, setLinkError] = useState<string | null>(null);
const [isHosting, setIsHosting] = useState(false); const [isHosting, setIsHosting] = useState(false);
const [hostStatus, setHostStatus] = useState(""); const [hostStatus, setHostStatus] = useState("");
@@ -59,7 +60,7 @@ const LceLiveView = memo(function LceLiveView() {
setIncomingReqs(reqs.incoming); setIncomingReqs(reqs.incoming);
setOutgoingReqs(reqs.outgoing); setOutgoingReqs(reqs.outgoing);
setInvites(invs.filter((i: GameInvite) => i.status === "pending")); setInvites(invs.filter((i: GameInvite) => i.status === "pending"));
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
} }
}; };
@@ -86,15 +87,15 @@ const LceLiveView = memo(function LceLiveView() {
useEffect(() => { useEffect(() => {
if (currentTab !== "device_link") return; if (currentTab !== "device_link") return;
let mounted = true; let mounted = true;
let pollInterval: any = null; let pollInterval: ReturnType<typeof setInterval> | null = null;
const startLink = async () => { const startLink = async () => {
try { try {
if (!linkData) { if (!linkData) {
const data = await lceLiveService.startDeviceLink(); const data = await lceLiveService.startDeviceLink();
if (mounted) setLinkData(data); if (mounted) setLinkData(data);
} }
} catch (e: any) { } catch (e: unknown) {
if (mounted) setLinkError(e.message); if (mounted) setLinkError(e instanceof Error ? e.message : String(e));
} }
}; };
@@ -109,9 +110,9 @@ const LceLiveView = memo(function LceLiveView() {
if (res.isLinked && mounted) { if (res.isLinked && mounted) {
setIsSignedIn(true); setIsSignedIn(true);
setLinkData(null); setLinkData(null);
clearInterval(pollInterval); if (pollInterval !== null) clearInterval(pollInterval);
} }
} catch (e: any) { } catch (e: unknown) {
console.warn("Poll failed", e); console.warn("Poll failed", e);
} }
}, },
@@ -121,7 +122,7 @@ const LceLiveView = memo(function LceLiveView() {
return () => { return () => {
mounted = false; mounted = false;
if (pollInterval) clearInterval(pollInterval); if (pollInterval !== null) clearInterval(pollInterval);
}; };
}, [currentTab, linkData]); }, [currentTab, linkData]);
@@ -151,8 +152,8 @@ const LceLiveView = memo(function LceLiveView() {
try { try {
await action(); await action();
fetchSocialData(); fetchSocialData();
} catch (e: any) { } catch (e: unknown) {
setErrorModal(e.message || "An error occurred"); setErrorModal(e instanceof Error ? e.message : "An error occurred");
} }
}; };
@@ -174,8 +175,8 @@ const LceLiveView = memo(function LceLiveView() {
setIsHosting(true); setIsHosting(true);
setHostStatus(`Hosting at ${endpoint.ip}:25565`); setHostStatus(`Hosting at ${endpoint.ip}:25565`);
setInvitedFriends(new Set()); setInvitedFriends(new Set());
} catch (e: any) { } catch (e: unknown) {
const msg = typeof e === "string" ? e : e?.message || "Unknown error"; const msg = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
setErrorModal("STUN discovery failed: " + msg); setErrorModal("STUN discovery failed: " + msg);
setHostStatus(""); setHostStatus("");
} finally { } finally {
@@ -207,7 +208,7 @@ const LceLiveView = memo(function LceLiveView() {
try { try {
await TauriService.stopAllProxies(); await TauriService.stopAllProxies();
await lceLiveService.deactivateGameInvites(); await lceLiveService.deactivateGameInvites();
} catch (e: any) { } catch (e: unknown) {
console.warn("Stop hosting failed", e); console.warn("Stop hosting failed", e);
} }
setIsHosting(false); setIsHosting(false);
@@ -237,16 +238,17 @@ const LceLiveView = memo(function LceLiveView() {
25565, 25565,
) )
.then(() => setHostStatus("Relay active")) .then(() => setHostStatus("Relay active"))
.catch((relayErr: any) => { .catch((relayErr: unknown) => {
const relayMsg = const relayMsg =
typeof relayErr === "string" relayErr instanceof Error ? relayErr.message
? relayErr : typeof relayErr === "string"
: relayErr?.message || "Unknown error"; ? relayErr
: "Unknown error";
console.warn("Relay failed:", relayMsg); console.warn("Relay failed:", relayMsg);
setHostStatus("Relay disconnected"); setHostStatus("Relay disconnected");
}); });
} catch (e: any) { } catch (e: unknown) {
const msg = typeof e === "string" ? e : e?.message || "Unknown error"; const msg = e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error";
setErrorModal("Failed to send invite: " + msg); setErrorModal("Failed to send invite: " + msg);
} }
}; };
@@ -377,7 +379,7 @@ const LceLiveView = memo(function LceLiveView() {
showHostMethodPicker, showHostMethodPicker,
]); ]);
const tabs = ["friends", "requests", "invites"]; const tabs: ("friends" | "requests" | "invites")[] = ["friends", "requests", "invites"];
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (errorModal) { if (errorModal) {
@@ -433,14 +435,14 @@ const LceLiveView = memo(function LceLiveView() {
const curIdx = tabs.indexOf(currentTab); const curIdx = tabs.indexOf(currentTab);
if (e.key === "q" || e.key === "Q" || e.key === "ArrowLeft") { if (e.key === "q" || e.key === "Q" || e.key === "ArrowLeft") {
const next = curIdx > 0 ? tabs[curIdx - 1] : tabs[tabs.length - 1]; const next = curIdx > 0 ? tabs[curIdx - 1] : tabs[tabs.length - 1];
setCurrentTab(next as any); setCurrentTab(next);
setFocusIndex(0); setFocusIndex(0);
playPressSound(); playPressSound();
return; return;
} }
if (e.key === "e" || e.key === "E" || e.key === "ArrowRight") { if (e.key === "e" || e.key === "E" || e.key === "ArrowRight") {
const next = curIdx < tabs.length - 1 ? tabs[curIdx + 1] : tabs[0]; const next = curIdx < tabs.length - 1 ? tabs[curIdx + 1] : tabs[0];
setCurrentTab(next as any); setCurrentTab(next);
setFocusIndex(0); setFocusIndex(0);
playPressSound(); playPressSound();
return; return;
@@ -774,7 +776,7 @@ const LceLiveView = memo(function LceLiveView() {
imageRendering: "pixelated", imageRendering: "pixelated",
}} }}
onClick={() => { onClick={() => {
setCurrentTab(t as any); setCurrentTab(t);
setFocusIndex(0); setFocusIndex(0);
playPressSound(); playPressSound();
}} }}
+1 -1
View File
@@ -50,7 +50,7 @@ export default function LocEditorView() {
if (!loc) return; if (!loc) return;
playPressSound(); playPressSound();
const buffer = ArcService.serializeLOC(loc); const buffer = ArcService.serializeLOC(loc);
const blob = new Blob([buffer as any]); const blob = new Blob([buffer]);
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
+4 -4
View File
@@ -32,9 +32,9 @@ export default function OptionsEditorView() {
const parsed = OptionsService.readOptions(buffer); const parsed = OptionsService.readOptions(buffer);
setOpt(parsed); setOpt(parsed);
showNotification(`Loaded options.dat`); showNotification(`Loaded options.dat`);
} catch (err: any) { } catch (err: unknown) {
console.error("Failed to parse Options", err); console.error("Failed to parse Options", err);
showNotification(err.message || "Failed to parse Options", "error"); showNotification(err instanceof Error ? err.message : "Failed to parse Options", "error");
} }
e.target.value = ""; e.target.value = "";
}; };
@@ -52,12 +52,12 @@ export default function OptionsEditorView() {
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showNotification("Options Saved"); showNotification("Options Saved");
} catch (err: any) { } catch (err: unknown) {
showNotification("Failed to save", "error"); showNotification("Failed to save", "error");
} }
}; };
const updateSetting = (field: keyof OptionsFile, value: any) => { const updateSetting = (field: keyof OptionsFile, value: string | number | boolean | number[]) => {
if (!opt) return; if (!opt) return;
setOpt({ ...opt, [field]: value }); setOpt({ ...opt, [field]: value });
}; };
+13 -11
View File
@@ -68,7 +68,7 @@ export default function PckEditorView() {
}); });
}); });
const convert = (nodes: Record<string, TempNode>): any[] => { const convert = (nodes: Record<string, TempNode>): TreeNode[] => {
return Object.values(nodes) return Object.values(nodes)
.sort((a, b) => { .sort((a, b) => {
if (a.isFolder && !b.isFolder) return -1; if (a.isFolder && !b.isFolder) return -1;
@@ -107,7 +107,7 @@ export default function PckEditorView() {
return; return;
} }
const blob = new Blob([selectedAsset.data as any], { type: "image/png" }); const blob = new Blob([selectedAsset.data], { type: "image/png" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
setAssetPreview({ id: selectedAsset.id, url }); setAssetPreview({ id: selectedAsset.id, url });
@@ -128,17 +128,19 @@ export default function PckEditorView() {
setExpandedFolders(next); setExpandedFolders(next);
}; };
const renderTree = (nodes: any[], depth = 0) => { type TreeNode = { name: string; path: string; isFolder: boolean; children: TreeNode[]; asset?: PCKAsset };
const renderTree = (nodes: TreeNode[], depth = 0) => {
return nodes.map((node) => { return nodes.map((node) => {
const isExpanded = expandedFolders.has(node.path) || !!searchTerm; const isExpanded = expandedFolders.has(node.path) || !!searchTerm;
const isSelected = selectedAssetId === node.asset?.id; const isSelected = node.asset ? selectedAssetId === node.asset.id : false;
return ( return (
<div key={node.path} className="flex flex-col"> <div key={node.path} className="flex flex-col">
<div <div
onClick={() => { onClick={() => {
if (node.isFolder) { if (node.isFolder) {
toggleFolder(node.path); toggleFolder(node.path);
} else { } else if (node.asset) {
playPressSound(); playPressSound();
setSelectedAssetId(node.asset.id); setSelectedAssetId(node.asset.id);
} }
@@ -175,7 +177,7 @@ export default function PckEditorView() {
<span className="truncate mc-text-shadow text-base"> <span className="truncate mc-text-shadow text-base">
{node.name} {node.name}
</span> </span>
{!node.isFolder && ( {!node.isFolder && node.asset && (
<span className="ml-auto text-[10px] opacity-40 uppercase"> <span className="ml-auto text-[10px] opacity-40 uppercase">
{(node.asset.size / 1024).toFixed(1)} KB {(node.asset.size / 1024).toFixed(1)} KB
</span> </span>
@@ -202,7 +204,7 @@ export default function PckEditorView() {
setOpenedPath(path); setOpenedPath(path);
setSelectedAssetId(parsed.files[0]?.id || null); setSelectedAssetId(parsed.files[0]?.id || null);
setExpandedFolders(new Set()); setExpandedFolders(new Set());
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") { if (err !== "CANCELED") {
console.error("Failed to parse PCK", err); console.error("Failed to parse PCK", err);
showNotification("Failed to parse PCK", "error"); showNotification("Failed to parse PCK", "error");
@@ -246,7 +248,7 @@ export default function PckEditorView() {
playPressSound(); playPressSound();
await TauriService.writeBinaryFile(path, asset.data); await TauriService.writeBinaryFile(path, asset.data);
showNotification(`Exported: ${fileName}`); showNotification(`Exported: ${fileName}`);
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error"); if (err !== "CANCELED") showNotification("Export failed", "error");
} }
}; };
@@ -413,7 +415,7 @@ export default function PckEditorView() {
); );
} }
showNotification("All Assets Exported"); showNotification("All Assets Exported");
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Export failed", "error"); if (err !== "CANCELED") showNotification("Export failed", "error");
} }
}; };
@@ -438,8 +440,8 @@ export default function PckEditorView() {
setOpenedPath(targetPath); setOpenedPath(targetPath);
showNotification("PCK Saved Successfully"); showNotification("PCK Saved Successfully");
} }
} catch (err: any) { } catch (err: unknown) {
if (err !== "CANCELED") showNotification("Save failed", "error"); if (err !== "CANCELED") showNotification("Export failed", "error");
} }
}; };
+2 -1
View File
@@ -6,6 +6,7 @@ import {
useGame, useGame,
useConfig, useConfig,
} from "../../context/LauncherContext"; } from "../../context/LauncherContext";
import type { Edition } from "../../types/edition";
import { import {
ScreenshotService, ScreenshotService,
ScreenshotInfo, ScreenshotInfo,
@@ -147,7 +148,7 @@ const ScreenshotsView = memo(function ScreenshotsView() {
}, [gridFocusIndex, selectedScreenshot]); }, [gridFocusIndex, selectedScreenshot]);
const getEditionLogo = (instanceId: string) => { const getEditionLogo = (instanceId: string) => {
const edition = editions.find((e: any) => e.id === instanceId); const edition = editions.find((e: Edition) => e.id === instanceId);
return edition?.logo || edition?.titleImage; return edition?.logo || edition?.titleImage;
}; };
+7 -6
View File
@@ -227,7 +227,7 @@ const SettingsView = memo(function SettingsView() {
label: string; label: string;
type: "slider"; type: "slider";
value: number; value: number;
onChange: (val: any) => void; onChange: (val: number) => void;
} }
| { | {
id: string; id: string;
@@ -474,7 +474,8 @@ const SettingsView = memo(function SettingsView() {
const item = settingsItems[focusIndex]; const item = settingsItems[focusIndex];
if (item.type === "slider") { if (item.type === "slider") {
const delta = e.key === "ArrowRight" ? 5 : -5; const delta = e.key === "ArrowRight" ? 5 : -5;
item.onChange((v: number) => Math.max(0, Math.min(100, v + delta))); const newVal = Math.max(0, Math.min(100, item.value + delta));
item.onChange(newVal);
} }
} else if (e.key === "Enter" && focusIndex !== null) { } else if (e.key === "Enter" && focusIndex !== null) {
const item = settingsItems[focusIndex]; const item = settingsItems[focusIndex];
@@ -583,8 +584,8 @@ const SettingsView = memo(function SettingsView() {
); );
} }
const isRed = (item as any).color === "red"; const isRed = ("color" in item && (item as { color: string }).color === "red");
const isSmall = (item as any).small; const isSmall = "small" in item && (item as { small: boolean }).small;
return ( return (
<button <button
@@ -651,8 +652,8 @@ const SettingsView = memo(function SettingsView() {
); );
} }
const isRed = (item as any).color === "red"; const isRed = item.type === "button" && item.color === "red";
const isSmall = (item as any).small; const isSmall = item.type === "button" && !!item.small;
const isToggle = isToggleOption(item.label); const isToggle = isToggleOption(item.label);
const toggleState = isToggle ? getToggleState(item.label) : false; const toggleState = isToggle ? getToggleState(item.label) : false;
+2 -2
View File
@@ -165,9 +165,9 @@ const SkinsView = memo(function SkinsView() {
const skinBase64 = `data:image/png;base64,${base64Raw}`; const skinBase64 = `data:image/png;base64,${base64Raw}`;
processSkinImage(skinBase64, exactName.substring(0, 16)); processSkinImage(skinBase64, exactName.substring(0, 16));
} }
} catch (e: any) { } catch (e: unknown) {
setImportError( setImportError(
typeof e === "string" ? e : e.message || "Failed to fetch", e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to fetch",
); );
} finally { } finally {
setIsImporting(false); setIsImporting(false);
+6 -6
View File
@@ -40,7 +40,7 @@ export default function SwfView() {
if (imageUrls[img.id]) return imageUrls[img.id]; if (imageUrls[img.id]) return imageUrls[img.id];
let url = ""; let url = "";
if (img.type === "jpeg") { if (img.type === "jpeg") {
const blob = new Blob([img.data as any], { type: "image/jpeg" }); const blob = new Blob([img.data], { type: "image/jpeg" });
url = URL.createObjectURL(blob); url = URL.createObjectURL(blob);
} else if (img.type === "lossless") { } else if (img.type === "lossless") {
const rgba = await SwfService.decodeLosslessToRGBA(img); const rgba = await SwfService.decodeLosslessToRGBA(img);
@@ -80,7 +80,7 @@ export default function SwfView() {
setSelectedImageId(extracted[0].id); setSelectedImageId(extracted[0].id);
} }
showNotification(`Loaded ${file.name}`); showNotification(`Loaded ${file.name}`);
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
showNotification("Failed to process SWF", "error"); showNotification("Failed to process SWF", "error");
setImages([]); setImages([]);
@@ -99,7 +99,7 @@ export default function SwfView() {
let blob: Blob; let blob: Blob;
let ext = "png"; let ext = "png";
if (img.type === "jpeg") { if (img.type === "jpeg") {
blob = new Blob([img.data as any], { type: "image/jpeg" }); blob = new Blob([img.data], { type: "image/jpeg" });
ext = "jpg"; ext = "jpg";
} else if (img.type === "lossless") { } else if (img.type === "lossless") {
const rgba = await SwfService.decodeLosslessToRGBA(img); const rgba = await SwfService.decodeLosslessToRGBA(img);
@@ -114,11 +114,11 @@ export default function SwfView() {
const res = await fetch(dataUrl); const res = await fetch(dataUrl);
blob = await res.blob(); blob = await res.blob();
} else { } else {
blob = new Blob([rgba as any], { type: "application/octet-stream" }); blob = new Blob([rgba], { type: "application/octet-stream" });
ext = "bin"; ext = "bin";
} }
} else { } else {
blob = new Blob([img.data as any], { type: "application/octet-stream" }); blob = new Blob([img.data], { type: "application/octet-stream" });
ext = "bin"; ext = "bin";
} }
@@ -162,7 +162,7 @@ export default function SwfView() {
if (!swfData) return; if (!swfData) return;
playPressSound(); playPressSound();
const result = SwfService.serialize(swfData.version, swfData.compressed, swfData.frameHeader, swfData.tags); const result = SwfService.serialize(swfData.version, swfData.compressed, swfData.frameHeader, swfData.tags);
const blob = new Blob([result as any], { type: "application/x-shockwave-flash" }); const blob = new Blob([result], { type: "application/x-shockwave-flash" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
+7 -6
View File
@@ -10,6 +10,7 @@ import {
useGame, useGame,
} from "../../context/LauncherContext"; } from "../../context/LauncherContext";
import { ScreenshotImage } from "../common/ScreenshotImage"; import { ScreenshotImage } from "../common/ScreenshotImage";
import type { Edition } from "../../types/edition";
interface DeleteConfirmButtonProps { interface DeleteConfirmButtonProps {
label: string; label: string;
onClick: () => void; onClick: () => void;
@@ -73,14 +74,14 @@ const VersionsView = memo(function VersionsView() {
const [isImportModalOpen, setIsImportModalOpen] = useState(false); const [isImportModalOpen, setIsImportModalOpen] = useState(false);
const [isSetUidModalOpen, setIsSetUidModalOpen] = useState(false); const [isSetUidModalOpen, setIsSetUidModalOpen] = useState(false);
const [setUidTargetId, setSetUidTargetId] = useState(""); const [setUidTargetId, setSetUidTargetId] = useState("");
const [editingEdition, setEditingEdition] = useState<any>(null); const [editingEdition, setEditingEdition] = useState<Edition | null>(null);
const [initialPath, setInitialPath] = useState<string>(""); const [initialPath, setInitialPath] = useState<string>("");
const [hoveredBtn, setHoveredBtn] = useState<{ const [hoveredBtn, setHoveredBtn] = useState<{
row: number; row: number;
btn: string; btn: string;
} | null>(null); } | null>(null);
const [openMenuId, setOpenMenuId] = useState<string | null>(null); const [openMenuId, setOpenMenuId] = useState<string | null>(null);
const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<any>(null); const [deleteConfirmEdition, setDeleteConfirmEdition] = useState<Edition | null>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const listRef = useRef<HTMLDivElement>(null); const listRef = useRef<HTMLDivElement>(null);
const ITEM_COUNT = editions.length + 3; const ITEM_COUNT = editions.length + 3;
@@ -185,7 +186,7 @@ const VersionsView = memo(function VersionsView() {
} }
}, [focusIndex]); }, [focusIndex]);
const handleEditionClick = (edition: any, index: number) => { const handleEditionClick = (edition: Edition, index: number) => {
const isInstalled = installedVersions.includes(edition.instanceId); const isInstalled = installedVersions.includes(edition.instanceId);
if (isInstalled) { if (isInstalled) {
playPressSound(); playPressSound();
@@ -233,7 +234,7 @@ const VersionsView = memo(function VersionsView() {
className="w-full max-h-[45vh] overflow-y-auto py-2 custom-scrollbar" className="w-full max-h-[45vh] overflow-y-auto py-2 custom-scrollbar"
> >
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{editions.map((edition: any, i: number) => { {editions.map((edition: Edition, i: number) => {
const isInstalled = installedVersions.includes( const isInstalled = installedVersions.includes(
edition.instanceId, edition.instanceId,
); );
@@ -514,7 +515,7 @@ const VersionsView = memo(function VersionsView() {
addToSteam( addToSteam(
edition.instanceId, edition.instanceId,
edition.name, edition.name,
edition.titleImage, edition.titleImage ?? "",
panoramaUrl, panoramaUrl,
); );
setOpenMenuId(null); setOpenMenuId(null);
@@ -702,7 +703,7 @@ const VersionsView = memo(function VersionsView() {
setEditingEdition(null); setEditingEdition(null);
setInitialPath(""); setInitialPath("");
}} }}
onImport={(ed: any) => { onImport={(ed: { name: string; desc: string; url: string; path?: string }) => {
if (editingEdition) { if (editingEdition) {
onUpdateEdition(editingEdition.id, ed); onUpdateEdition(editingEdition.id, ed);
} else { } else {
+7 -6
View File
@@ -20,6 +20,7 @@ import {
import { import {
TauriService, TauriService,
InstalledWorkshopPackage, InstalledWorkshopPackage,
type CustomEdition,
} from "../../services/TauriService"; } from "../../services/TauriService";
const REGISTRY_URL = const REGISTRY_URL =
"https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/registry.json"; "https://raw.githubusercontent.com/LCE-Hub/LCE-Workshop/refs/heads/main/registry.json";
@@ -224,7 +225,7 @@ const WorkshopView = memo(function WorkshopView() {
(pkgId: string) => { (pkgId: string) => {
if (activeTab === "Versions") { if (activeTab === "Versions") {
const isAdded = config.customEditions?.some( const isAdded = config.customEditions?.some(
(e: any) => (e: CustomEdition) =>
e.id === pkgId || e.id === pkgId ||
e.url === versionPackages.find((p) => p.id === pkgId)?.url, e.url === versionPackages.find((p) => p.id === pkgId)?.url,
); );
@@ -252,7 +253,7 @@ const WorkshopView = memo(function WorkshopView() {
if (activeTab === "Versions") { if (activeTab === "Versions") {
return ( return (
config.customEditions?.some( config.customEditions?.some(
(e: any) => (e: CustomEdition) =>
e.id === pkgId || e.id === pkgId ||
e.url === versionPackages.find((p) => p.id === pkgId)?.url, e.url === versionPackages.find((p) => p.id === pkgId)?.url,
) ?? false ) ?? false
@@ -1471,10 +1472,10 @@ function InstallModal({
pkg.version, pkg.version,
); );
setStatus("success"); setStatus("success");
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setStatus("error"); setStatus("error");
setErrorMsg(typeof e === "string" ? e : e.message); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error");
} }
}; };
@@ -1646,10 +1647,10 @@ function UninstallModal({
await TauriService.workshopUninstall(instanceId, pkg.id); await TauriService.workshopUninstall(instanceId, pkg.id);
} }
setStatus("success"); setStatus("success");
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setStatus("error"); setStatus("error");
setErrorMsg(typeof e === "string" ? e : e.message); setErrorMsg(e instanceof Error ? e.message : typeof e === "string" ? e : "Unknown error");
} }
}; };
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useLocalStorage } from "./useLocalStorage"; import { useLocalStorage } from "./useLocalStorage";
import { TauriService } from "../services/TauriService"; import { TauriService, type CustomEdition } from "../services/TauriService";
export function useAppConfig() { export function useAppConfig() {
const [username, setUsername] = useLocalStorage("lce-username", "Steve"); const [username, setUsername] = useLocalStorage("lce-username", "Steve");
const [theme, setTheme] = useLocalStorage("lce-theme", "Modern"); const [theme, setTheme] = useLocalStorage("lce-theme", "Modern");
@@ -17,7 +17,7 @@ export function useAppConfig() {
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const [linuxRunner, setLinuxRunner] = useState<string | undefined>(); const [linuxRunner, setLinuxRunner] = useState<string | undefined>();
const [perfBoost, setPerfBoost] = useState(false); const [perfBoost, setPerfBoost] = useState(false);
const [customEditions, setCustomEditions] = useState<any[]>([]); const [customEditions, setCustomEditions] = useState<CustomEdition[]>([]);
const [mangohudEnabled, setMangohudEnabled] = useState(false); const [mangohudEnabled, setMangohudEnabled] = useState(false);
useEffect(() => { useEffect(() => {
TauriService.loadConfig().then((config) => { TauriService.loadConfig().then((config) => {
+2 -1
View File
@@ -1,5 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import RpcService from "../services/RpcService"; import RpcService from "../services/RpcService";
import type { Edition } from "../types/edition";
interface DiscordRPCProps { interface DiscordRPCProps {
rpcEnabled: boolean; rpcEnabled: boolean;
showIntro: boolean; showIntro: boolean;
@@ -10,7 +11,7 @@ interface DiscordRPCProps {
isWindowVisible: boolean; isWindowVisible: boolean;
downloadProgress: number | null; downloadProgress: number | null;
downloadingId: string | null; downloadingId: string | null;
editions: any[]; editions: Edition[];
} }
export function useDiscordRPC({ export function useDiscordRPC({
+15 -14
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { TauriService } from "../services/TauriService"; import { TauriService, type CustomEdition } from "../services/TauriService";
import { getCurrentWindow } from "@tauri-apps/api/window"; import { getCurrentWindow } from "@tauri-apps/api/window";
import type { Edition } from "../types/edition";
async function imageUrlToBase64(url: string): Promise<string> { async function imageUrlToBase64(url: string): Promise<string> {
const response = await fetch(url); const response = await fetch(url);
@@ -74,8 +75,8 @@ const PARTNERSHIP_SERVERS = [
interface GameManagerProps { interface GameManagerProps {
profile: string; profile: string;
setProfile: (id: string) => void; setProfile: (id: string) => void;
customEditions: any[]; customEditions: CustomEdition[];
setCustomEditions: (editions: any[]) => void; setCustomEditions: (editions: CustomEdition[]) => void;
} }
function compareVersions(v1: string, v2: string) { function compareVersions(v1: string, v2: string) {
@@ -146,7 +147,7 @@ export function useGameManager({
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
const asset = data.assets.find( const asset = data.assets.find(
(a: any) => a.name === "neoLegacyWindows64.zip", (a: { name: string }) => a.name === "neoLegacyWindows64.zip",
); );
if (asset) { if (asset) {
setDynamicUrls((prev) => ({ setDynamicUrls((prev) => ({
@@ -176,7 +177,7 @@ export function useGameManager({
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
let tags: string[] = data let tags: string[] = data
.map((r: any) => r.tag_name) .map((r: { tag_name: string }) => r.tag_name)
.filter((t: string) => !t.toLowerCase().includes("server")); .filter((t: string) => !t.toLowerCase().includes("server"));
const vTags = tags const vTags = tags
@@ -231,7 +232,7 @@ export function useGameManager({
[branches, profile, setProfile], [branches, profile, setProfile],
); );
const editions = useMemo(() => { const editions = useMemo((): Edition[] => {
return [ return [
...BASE_EDITIONS.map((e) => { ...BASE_EDITIONS.map((e) => {
const availableBranches = branches[e.id] || ["Stable"]; const availableBranches = branches[e.id] || ["Stable"];
@@ -343,10 +344,10 @@ export function useGameManager({
try { try {
await TauriService.downloadRunner(name, url); await TauriService.downloadRunner(name, url);
setRunnerDownloadProgress(null); setRunnerDownloadProgress(null);
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setError( setError(
typeof e === "string" ? e : e.message || "Failed to download runner", e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to download runner",
); );
} finally { } finally {
setIsRunnerDownloading(false); setIsRunnerDownloading(false);
@@ -370,10 +371,10 @@ export function useGameManager({
setProfile(id); setProfile(id);
setDownloadProgress(null); setDownloadProgress(null);
setDownloadingId(null); setDownloadingId(null);
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setError( setError(
typeof e === "string" ? e : e.message || "Failed to install version", e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to install version",
); );
setDownloadProgress(null); setDownloadProgress(null);
setDownloadingId(null); setDownloadingId(null);
@@ -410,10 +411,10 @@ export function useGameManager({
try { try {
getCurrentWindow().minimize(); getCurrentWindow().minimize();
await TauriService.launchGame(profile, PARTNERSHIP_SERVERS); await TauriService.launchGame(profile, PARTNERSHIP_SERVERS);
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setError( setError(
typeof e === "string" ? e : e.message || "Failed to launch game", e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to launch game",
); );
} finally { } finally {
setIsGameRunning(false); setIsGameRunning(false);
@@ -485,10 +486,10 @@ export function useGameManager({
setSteamSuccessMessage( setSteamSuccessMessage(
`Added ${name} to Steam! (Restart Steam to see it)`, `Added ${name} to Steam! (Restart Steam to see it)`,
); );
} catch (e: any) { } catch (e: unknown) {
console.error(e); console.error(e);
setError( setError(
typeof e === "string" ? e : e.message || "Failed to add to Steam", e instanceof Error ? e.message : typeof e === "string" ? e : "Failed to add to Steam",
); );
} }
}, },
+1 -1
View File
@@ -53,7 +53,7 @@ export const useGamepad = ({ playSfx, isWindowVisible }: UseGamepadProps) => {
const btnVal = (i: number): number => { const btnVal = (i: number): number => {
const btn = gp.buttons[i]; const btn = gp.buttons[i];
if (!btn) return 0; if (!btn) return 0;
return typeof btn === "object" ? btn.value : (btn as any) ?? 0; return typeof btn === "object" ? btn.value : 0;
}; };
const justPressed = (i: number) => btnVal(i) > 0.5 && !lastButtons.current[i]; const justPressed = (i: number) => btnVal(i) > 0.5 && !lastButtons.current[i];
if (justPressed(1)) dispatchKey('Enter'); if (justPressed(1)) dispatchKey('Enter');
+6 -6
View File
@@ -1,12 +1,12 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { lceLiveService } from "../services/LceLiveService"; import { lceLiveService, type FriendRequest, type GameInvite } from "../services/LceLiveService";
export function useLceLiveNotifications() { export function useLceLiveNotifications() {
const [friendRequestMessage, setFriendRequestMessage] = useState<string | null>(null); const [friendRequestMessage, setFriendRequestMessage] = useState<string | null>(null);
const [gameInviteMessage, setGameInviteMessage] = useState<string | null>(null); const [gameInviteMessage, setGameInviteMessage] = useState<string | null>(null);
const seenRequests = useRef<Set<string>>(new Set()); const seenRequests = useRef<Set<string>>(new Set());
const seenInvites = useRef<Set<string>>(new Set()); const seenInvites = useRef<Set<string>>(new Set());
useEffect(() => { useEffect(() => {
let pollInterval: any; let pollInterval: ReturnType<typeof setInterval>;
const init = async () => { const init = async () => {
if (lceLiveService.signedIn) { if (lceLiveService.signedIn) {
try { try {
@@ -20,8 +20,8 @@ export function useLceLiveNotifications() {
lceLiveService.getPendingRequests(), lceLiveService.getPendingRequests(),
lceLiveService.getGameInvites() lceLiveService.getGameInvites()
]); ]);
reqs.incoming.forEach((r: any) => seenRequests.current.add(r.accountId)); reqs.incoming.forEach((r: FriendRequest) => seenRequests.current.add(r.accountId));
invs.filter((i: any) => i.status === "pending").forEach((i: any) => seenInvites.current.add(i.inviteId)); invs.filter((i: GameInvite) => i.status === "pending").forEach((i: GameInvite) => seenInvites.current.add(i.inviteId));
} catch (e) { } } catch (e) { }
} }
@@ -33,14 +33,14 @@ export function useLceLiveNotifications() {
lceLiveService.getGameInvites() lceLiveService.getGameInvites()
]); ]);
reqs.incoming.forEach((r: any) => { reqs.incoming.forEach((r: FriendRequest) => {
if (!seenRequests.current.has(r.accountId)) { if (!seenRequests.current.has(r.accountId)) {
seenRequests.current.add(r.accountId); seenRequests.current.add(r.accountId);
setFriendRequestMessage(`New request from ${r.displayName}`); setFriendRequestMessage(`New request from ${r.displayName}`);
} }
}); });
invs.filter((i: any) => i.status === "pending").forEach((i: any) => { invs.filter((i: GameInvite) => i.status === "pending").forEach((i: GameInvite) => {
if (!seenInvites.current.has(i.inviteId)) { if (!seenInvites.current.has(i.inviteId)) {
seenInvites.current.add(i.inviteId); seenInvites.current.add(i.inviteId);
const fromName = typeof i.from === 'string' ? "Unknown" : i.from.displayName; const fromName = typeof i.from === 'string' ? "Unknown" : i.from.displayName;
+2 -2
View File
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import { useLocalStorage } from "./useLocalStorage"; import { useLocalStorage } from "./useLocalStorage";
import { PckService } from "../services/PckService"; import { PckService } from "../services/PckService";
import { TauriService } from "../services/TauriService"; import { TauriService } from "../services/TauriService";
import { PCKAssetType, PCKProperty } from "../types/pck"; import { PCKAsset, PCKAssetType, PCKProperty } from "../types/pck";
interface Edition { interface Edition {
id: string; id: string;
supportsSlimSkins?: boolean; supportsSlimSkins?: boolean;
@@ -99,7 +99,7 @@ export function useSkinSync({ username, profile, editions }: UseSkinSyncProps) {
const seededId = getSeededId(username); const seededId = getSeededId(username);
const packId = seededId.slice(-4); const packId = seededId.slice(-4);
const files: any[] = [ const files: PCKAsset[] = [
{ {
id: "0", id: "0",
path: "0", path: "0",
+3 -2
View File
@@ -31,6 +31,7 @@ import {
} from "../context/LauncherContext"; } from "../context/LauncherContext";
import { TauriService } from "../services/TauriService"; import { TauriService } from "../services/TauriService";
import { useLceLiveNotifications } from "../hooks/useLceLiveNotifications"; import { useLceLiveNotifications } from "../hooks/useLceLiveNotifications";
import type { Edition } from "../types/edition";
import pkg from "../../package.json"; import pkg from "../../package.json";
export default function App() { export default function App() {
const { const {
@@ -67,7 +68,7 @@ export default function App() {
}, [config.isDayTime]); }, [config.isDayTime]);
const selectedEdition = game.editions.find( const selectedEdition = game.editions.find(
(e: any) => e.instanceId === config.profile, (e: Edition) => e.instanceId === config.profile,
); );
const selectedVersionName = selectedEdition?.name || ""; const selectedVersionName = selectedEdition?.name || "";
const hasAnyInstall = game.installs.length > 0; const hasAnyInstall = game.installs.length > 0;
@@ -132,7 +133,7 @@ export default function App() {
{...backgroundFade} {...backgroundFade}
> >
<PanoramaBackground <PanoramaBackground
profile={selectedEdition?.panorama} profile={selectedEdition?.panorama ?? "vanilla_tu19"}
isDay={displayIsDay} isDay={displayIsDay}
/> />
</motion.div> </motion.div>
+1 -1
View File
@@ -298,7 +298,7 @@ export class GrfService {
fw.setInt32(poff, compressedSize, false); poff += 4; fw.setInt32(poff, compressedSize, false); poff += 4;
} }
finalBuffer.set(bodyData as any, poff); finalBuffer.set(bodyData, poff);
return finalBuffer.buffer; return finalBuffer.buffer;
} }
+11 -11
View File
@@ -122,13 +122,13 @@ export class LceLiveService {
} }
} }
private async request( private async request<T = any>(
method: string, method: string,
path: string, path: string,
body?: any, body?: unknown,
authed: boolean = true, authed: boolean = true,
retryCount: number = 0, retryCount: number = 0,
): Promise<any> { ): Promise<T> {
if (authed && this._session?.refreshToken && retryCount === 0) { if (authed && this._session?.refreshToken && retryCount === 0) {
try { try {
await this.refreshSession(); //neo: i do this on every request only because it doesnt always return 401 await this.refreshSession(); //neo: i do this on every request only because it doesnt always return 401
@@ -198,7 +198,7 @@ export class LceLiveService {
} }
async startDeviceLink(): Promise<DeviceLinkStartResponse> { async startDeviceLink(): Promise<DeviceLinkStartResponse> {
return this.request( return this.request<DeviceLinkStartResponse>(
"POST", "POST",
"/api/auth/device/start", "/api/auth/device/start",
{ {
@@ -210,7 +210,7 @@ export class LceLiveService {
} }
async pollDeviceLink(deviceCode: string): Promise<DeviceLinkPollResponse> { async pollDeviceLink(deviceCode: string): Promise<DeviceLinkPollResponse> {
const data = await this.request( const data = await this.request<DeviceLinkPollResponse>(
"GET", "GET",
`/api/auth/device/poll/${deviceCode}`, `/api/auth/device/poll/${deviceCode}`,
null, null,
@@ -219,8 +219,8 @@ export class LceLiveService {
if (data.isLinked && data.accessToken) { if (data.isLinked && data.accessToken) {
this._session = { this._session = {
accessToken: data.accessToken, accessToken: data.accessToken,
refreshToken: data.refreshToken, refreshToken: data.refreshToken ?? "",
account: data.account, account: data.account ?? { accountId: "", username: "", displayName: "" },
}; };
this.saveSession(); this.saveSession();
} }
@@ -297,12 +297,12 @@ export class LceLiveService {
async getPendingRequests(): Promise<PendingRequests> { async getPendingRequests(): Promise<PendingRequests> {
const data = await this.request("GET", "/api/social/requests"); const data = await this.request("GET", "/api/social/requests");
return { return {
incoming: (data.incoming || []).map((r: any) => ({ incoming: (data.incoming || []).map((r: Record<string, string>) => ({
accountId: r.requesterUserId || r.accountId || r.userId, accountId: r.requesterUserId || r.accountId || r.userId,
username: r.requesterUsername || r.username, username: r.requesterUsername || r.username,
displayName: r.requesterDisplayName || r.displayName, displayName: r.requesterDisplayName || r.displayName,
})), })),
outgoing: (data.outgoing || []).map((r: any) => ({ outgoing: (data.outgoing || []).map((r: Record<string, string>) => ({
accountId: r.targetUserId || r.accountId || r.userId, accountId: r.targetUserId || r.accountId || r.userId,
username: r.targetUsername || r.username, username: r.targetUsername || r.username,
displayName: r.targetDisplayName || r.displayName, displayName: r.targetDisplayName || r.displayName,
@@ -321,7 +321,7 @@ export class LceLiveService {
async getGameInvites(): Promise<GameInvite[]> { async getGameInvites(): Promise<GameInvite[]> {
const data = await this.request("GET", "/api/sessions/invites"); const data = await this.request("GET", "/api/sessions/invites");
const incoming = data.incoming || []; const incoming = data.incoming || [];
return incoming.map((inv: any) => ({ return incoming.map((inv: Record<string, unknown>) => ({
inviteId: inv.inviteId, inviteId: inv.inviteId,
from: { from: {
accountId: inv.senderAccountId, accountId: inv.senderAccountId,
@@ -352,7 +352,7 @@ export class LceLiveService {
}); });
} }
async acceptGameInvite(inviteId: string): Promise<any> { async acceptGameInvite(inviteId: string): Promise<Record<string, unknown>> {
return this.request("POST", `/api/sessions/invites/${inviteId}/accept`, {}); return this.request("POST", `/api/sessions/invites/${inviteId}/accept`, {});
} }
+1 -1
View File
@@ -46,7 +46,7 @@ export interface AppConfig {
export interface ThemePalette { export interface ThemePalette {
id: string; id: string;
name: string; name: string;
colors: any; colors: Record<string, string>;
} }
export interface Runner { export interface Runner {
+32
View File
@@ -0,0 +1,32 @@
export interface Edition {
id: string;
name: string;
desc: string;
url: string;
titleImage?: string;
supportsSlimSkins?: boolean;
logo?: string;
panorama?: string;
branches?: string[];
selectedBranch?: string;
instanceId: string;
comingSoon?: boolean;
category?: string[];
}
export interface CustomEditionInput {
name: string;
desc: string;
url: string;
path?: string;
category?: string[];
logo?: string;
id?: string;
}
export interface EditionUpdate {
name: string;
desc: string;
url: string;
path?: string;
}