migrate to vite (#16)

This commit is contained in:
Brian Beck 2026-03-13 23:00:08 -07:00 committed by GitHub
parent 1373f12ab4
commit 5025065188
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
165 changed files with 56708 additions and 8931 deletions

27
src/components/App.tsx Normal file
View file

@ -0,0 +1,27 @@
"use client";
import { Suspense } from "react";
import { NuqsAdapter } from "nuqs/adapters/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { FeaturesProvider } from "@/src/components/FeaturesProvider";
import { MapInspector } from "@/src/components/MapInspector";
import { SettingsProvider } from "@/src/components/SettingsProvider";
// Three.js has its own loaders for textures and models, but we need to load other
// stuff too, e.g. missions, terrains, and more. This client is used for those.
const queryClient = new QueryClient();
export default function HomePage() {
return (
<Suspense>
<NuqsAdapter>
<FeaturesProvider>
<QueryClientProvider client={queryClient}>
<SettingsProvider>
<MapInspector />
</SettingsProvider>
</QueryClientProvider>
</FeaturesProvider>
</NuqsAdapter>
</Suspense>
);
}

View file

@ -133,11 +133,7 @@ function FlagMarkerSlot({ entity }: { entity: GameEntity }) {
return <FlagMarker entity={entity} />;
}
function PositionedEntityWrapper({
entity,
}: {
entity: PositionedEntity;
}) {
function PositionedEntityWrapper({ entity }: { entity: PositionedEntity }) {
const position = entity.position;
const scale = entity.scale;
const quaternion = useMemo(() => {

View file

@ -23,7 +23,11 @@ import { setupTexture } from "../textureUtils";
import { useAnisotropy } from "./useAnisotropy";
import { useDebug, useSettings } from "./SettingsProvider";
import { useShapeInfo, isOrganicShape } from "./ShapeInfoProvider";
import { useEngineSelector, effectNow, engineStore } from "../state/engineStore";
import {
useEngineSelector,
effectNow,
engineStore,
} from "../state/engineStore";
import { FloatingLabel } from "./FloatingLabel";
import {
useIflTexture,
@ -296,7 +300,10 @@ const StaticTexture = memo(function StaticTexture({
const url = useMemo(() => {
if (!resourcePath) {
log.warn("No resource_path found on \"%s\" — rendering fallback", shapeName);
log.warn(
'No resource_path found on "%s" — rendering fallback',
shapeName,
);
}
return resourcePath ? textureToUrl(resourcePath) : FALLBACK_TEXTURE_URL;
}, [resourcePath, shapeName]);

View file

@ -50,7 +50,9 @@ function InteriorTexture({
const debugMode = debugContext?.debugMode ?? false;
const anisotropy = useAnisotropy();
const url = textureToUrl(materialName);
const texture = useTexture(url, (texture) => setupTexture(texture, { anisotropy }));
const texture = useTexture(url, (texture) =>
setupTexture(texture, { anisotropy }),
);
// Check for self-illuminating flag in material userData
// Note: The io_dif Blender add-on needs to be updated to export material flags
const flagNames = new Set<string>(material?.userData?.flag_names ?? []);

View file

@ -8,7 +8,8 @@ import {
useRef,
lazy,
Activity,
ViewTransition,
ReactNode,
// ViewTransition,
} from "react";
import { Camera } from "three";
import { InspectorControls } from "@/src/components/InspectorControls";
@ -59,6 +60,10 @@ import {
import styles from "./MapInspector.module.css";
import { useTouchDevice } from "./useTouchDevice";
function ViewTransition({ children }: { children: ReactNode }) {
return children;
}
function createLazy(
name: string,
loader: () => Promise<{

View file

@ -23,7 +23,10 @@ export function Turret({ object }: { object: TorqueObject }) {
// `initialBarrel` is optional - turrets can exist without a barrel mounted.
// But if we do have one, it needs a shape name.
if (barrelDatablockName && !barrelShapeName) {
log.error("Turret missing shape for barrel datablock: %s", barrelDatablockName);
log.error(
"Turret missing shape for barrel datablock: %s",
barrelDatablockName,
);
}
return (
<ShapeInfoProvider type="Turret" object={object} shapeName={shapeName}>

View file

@ -63,7 +63,9 @@ export function WaterMaterial({
}) {
const url = textureToUrl(surfaceTexture);
const anisotropy = useAnisotropy();
const texture = useTexture(url, (texture) => setupTexture(texture, { anisotropy }));
const texture = useTexture(url, (texture) =>
setupTexture(texture, { anisotropy }),
);
return (
<meshStandardMaterial

View file

@ -32,7 +32,7 @@ function parseLogConfig(): {
globalLevel: string;
modules: Map<string, string>;
} {
const raw = process.env.NEXT_PUBLIC_LOG?.trim();
const raw = import.meta.env.NEXT_PUBLIC_LOG?.trim();
if (!raw) return { globalLevel: "info", modules: new Map() };
let globalLevel: string | null = null;

44
src/main.css Normal file
View file

@ -0,0 +1,44 @@
html {
box-sizing: border-box;
margin: 0;
padding: 0;
background: black;
overflow: hidden;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
user-select: none;
-webkit-touch-callout: none;
}
html {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
Oxygen,
Ubuntu,
Cantarell,
"Open Sans",
"Helvetica Neue",
sans-serif;
font-size: 100%;
}
body {
margin: 0;
padding: 0;
overflow: hidden;
}
input[type="range"] {
max-width: 80px;
}

10
src/main.tsx Normal file
View file

@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./main.css";
import App from "./components/App.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);

49549
src/manifest.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import untypedManifest from "@/public/manifest.json";
import untypedManifest from "./manifest.json";
import { normalizePath } from "./stringUtils";
// Source tuple: [sourcePath] or [sourcePath, actualPath] if casing differs

View file

@ -234,11 +234,19 @@ export function ghostToSceneObject(
switch (className) {
case "TerrainBlock":
result = terrainFromGhost(ghostIndex, data);
log.debug("TerrainBlock #%d: terrFileName=%s", ghostIndex, (result as SceneTerrainBlock).terrFileName);
log.debug(
"TerrainBlock #%d: terrFileName=%s",
ghostIndex,
(result as SceneTerrainBlock).terrFileName,
);
return result;
case "InteriorInstance":
result = interiorFromGhost(ghostIndex, data);
log.debug("InteriorInstance #%d: interiorFile=%s", ghostIndex, (result as SceneInteriorInstance).interiorFile);
log.debug(
"InteriorInstance #%d: interiorFile=%s",
ghostIndex,
(result as SceneInteriorInstance).interiorFile,
);
return result;
case "TSStatic":
return tsStaticFromGhost(ghostIndex, data);
@ -247,9 +255,14 @@ export function ghostToSceneObject(
const sky = result as SceneSky;
log.debug(
"Sky #%d: materialList=%s fogColor=(%s, %s, %s) visibleDist=%d fogDist=%d useSkyTextures=%s",
ghostIndex, sky.materialList,
sky.fogColor.r.toFixed(3), sky.fogColor.g.toFixed(3), sky.fogColor.b.toFixed(3),
sky.visibleDistance, sky.fogDistance, sky.useSkyTextures,
ghostIndex,
sky.materialList,
sky.fogColor.r.toFixed(3),
sky.fogColor.g.toFixed(3),
sky.fogColor.b.toFixed(3),
sky.visibleDistance,
sky.fogDistance,
sky.useSkyTextures,
);
return result;
}
@ -259,9 +272,15 @@ export function ghostToSceneObject(
log.debug(
"Sun #%d: dir=(%s, %s, %s) color=(%s, %s, %s) ambient=(%s, %s, %s)",
ghostIndex,
sun.direction.x.toFixed(3), sun.direction.y.toFixed(3), sun.direction.z.toFixed(3),
sun.color.r.toFixed(3), sun.color.g.toFixed(3), sun.color.b.toFixed(3),
sun.ambient.r.toFixed(3), sun.ambient.g.toFixed(3), sun.ambient.b.toFixed(3),
sun.direction.x.toFixed(3),
sun.direction.y.toFixed(3),
sun.direction.z.toFixed(3),
sun.color.r.toFixed(3),
sun.color.g.toFixed(3),
sun.color.b.toFixed(3),
sun.ambient.r.toFixed(3),
sun.ambient.g.toFixed(3),
sun.ambient.b.toFixed(3),
);
return result;
}

View file

@ -262,9 +262,7 @@ export const engineStore = createStore<EngineStoreState>()(
rate: recording ? 1 : state.playback.rate,
durationMs,
// Preserve the last snapshot so HUD/chat persist after unload.
streamSnapshot: recording
? null
: state.playback.streamSnapshot,
streamSnapshot: recording ? null : state.playback.streamSnapshot,
},
}));
},

View file

@ -424,26 +424,17 @@ export function useSceneMissionArea(): SceneMissionArea | null {
/** Hook returning which data source is currently populating entities. */
export function useDataSource(): DataSource | null {
return useStoreWithEqualityFn(
gameEntityStore,
(state) => state.dataSource,
);
return useStoreWithEqualityFn(gameEntityStore, (state) => state.dataSource);
}
/** Hook returning the current mission name. */
export function useMissionName(): string | null {
return useStoreWithEqualityFn(
gameEntityStore,
(state) => state.missionName,
);
return useStoreWithEqualityFn(gameEntityStore, (state) => state.missionName);
}
/** Hook returning the mission type short code (e.g. "CTF"). */
export function useMissionType(): string | null {
return useStoreWithEqualityFn(
gameEntityStore,
(state) => state.missionType,
);
return useStoreWithEqualityFn(gameEntityStore, (state) => state.missionType);
}
/** Hook returning the mission type display name (e.g. "Capture the Flag"). */
@ -480,10 +471,7 @@ export function useServerDisplayName(): string | null {
/** Hook returning the name of the player who recorded the demo / connected. */
export function useRecorderName(): string | null {
return useStoreWithEqualityFn(
gameEntityStore,
(state) => state.recorderName,
);
return useStoreWithEqualityFn(gameEntityStore, (state) => state.recorderName);
}
/** Hook returning the demo recording date string. */

View file

@ -50,7 +50,7 @@ export interface LiveConnectionStore extends LiveConnectionState {
}
const DEFAULT_RELAY_URL =
process.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
import.meta.env.NEXT_PUBLIC_RELAY_URL || "ws://localhost:8765";
export const liveConnectionStore = createStore<LiveConnectionStore>(
(set, get) => ({

View file

@ -482,15 +482,19 @@ export abstract class StreamEngine implements StreamingPlayback {
const classId = data.classId as number | undefined;
const objectData = data.objectData as Record<string, unknown> | undefined;
const hasData = data._hasObjectData as boolean | undefined;
const className = typeof classId === "number"
? this.registry.getGhostParser(classId)?.name ?? `classId=${classId}`
: "?";
const className =
typeof classId === "number"
? (this.registry.getGhostParser(classId)?.name ??
`classId=${classId}`)
: "?";
log.debug(
"GhostAlwaysObjectEvent: ghost=%d class=%s hasData=%s %s",
ghostIndex,
className,
hasData,
objectData ? `keys=[${Object.keys(objectData).join(",")}]` : "(no data)",
objectData
? `keys=[${Object.keys(objectData).join(",")}]`
: "(no data)",
);
if (ghostIndex != null && classId != null) {
this.processGhostUpdate({

View file

@ -94,7 +94,15 @@ function extractMissionInfo(demoValues: string[]): DemoMissionInfo {
}
}
return { missionDisplayName, missionType, gameClassName, serverDisplayName, mod, recorderName, recordingDate };
return {
missionDisplayName,
missionType,
gameClassName,
serverDisplayName,
mod,
recorderName,
recordingDate,
};
}
interface ParsedDemoValues {

View file

@ -357,7 +357,10 @@ export function replaceWithShapeMaterial(
const texture = loadTexture(url);
const isTranslucent = flagNames.has("Translucent");
if (isOrganic || isTranslucent) {
setupTexture(texture, { disableMipmaps: true, anisotropy: options.anisotropy });
setupTexture(texture, {
disableMipmaps: true,
anisotropy: options.anisotropy,
});
} else {
setupTexture(texture, { anisotropy: options.anisotropy });
}
@ -474,7 +477,12 @@ export function processShapeScene(
return result.material;
});
} else if (node.material) {
const result = replaceWithShapeMaterial(node.material, vis, isOrganic, options);
const result = replaceWithShapeMaterial(
node.material,
vis,
isOrganic,
options,
);
if (result.initialize) {
iflInitializers.push({ mesh: node, initialize: result.initialize });
}

View file

@ -121,7 +121,9 @@ export function registerEngineStubs(runtime: TorqueRuntime): void {
} catch (err) {
log.error(
"schedule: error calling %s on %s: %o",
methodName, this_._id, err,
methodName,
this_._id,
err,
);
}
}, ms);

View file

@ -1,4 +1,4 @@
import TorqueScript from "@/generated/TorqueScript.cjs";
import { parse as torqueScriptParse } from "@/generated/TorqueScript.js";
import { generate, type GeneratorOptions } from "./codegen";
import type { Program } from "./ast";
import { createRuntime } from "./runtime";
@ -40,7 +40,7 @@ export type TranspileOptions = ParseOptions & GeneratorOptions;
export function parse(source: string, options?: ParseOptions): Program {
try {
return TorqueScript.parse(source);
return torqueScriptParse(source);
} catch (error: any) {
if (options?.filename && error.location) {
throw new Error(