mirror of
https://github.com/LCE-Hub/LCE-Emerald-Launcher.git
synced 2026-09-23 08:32:51 +00:00
feat(react): typescriptify everything
This commit is contained in:
@@ -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,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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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("");
|
||||||
|
|||||||
@@ -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,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 = [
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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={() =>
|
||||||
|
|||||||
@@ -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();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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,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
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user