mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-03-16 19:01:00 +00:00
fix title and meta tags, remove more Next.js stuff
This commit is contained in:
parent
5025065188
commit
842ddb7df7
13 changed files with 17 additions and 774 deletions
11
app/global.d.ts
vendored
11
app/global.d.ts
vendored
|
|
@ -1,11 +0,0 @@
|
|||
import type { getMissionList, getMissionInfo } from "@/src/manifest";
|
||||
import type { DemoRecording } from "@/src/demo/types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
setMissionName?: (missionName: string) => void;
|
||||
getMissionList?: typeof getMissionList;
|
||||
getMissionInfo?: typeof getMissionInfo;
|
||||
loadDemoRecording?: (recording: DemoRecording) => void;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { ReactNode } from "react";
|
||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
import "./style.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "MapGenius – Explore maps for Tribes 2",
|
||||
description: "Tribes 2 forever.",
|
||||
};
|
||||
|
||||
export const viewport = {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<NuqsAdapter defaultOptions={{ clearOnDefault: false }}>
|
||||
{children}
|
||||
</NuqsAdapter>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
24
app/page.tsx
24
app/page.tsx
|
|
@ -1,24 +0,0 @@
|
|||
"use client";
|
||||
import { Suspense } from "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>
|
||||
<FeaturesProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SettingsProvider>
|
||||
<MapInspector />
|
||||
</SettingsProvider>
|
||||
</QueryClientProvider>
|
||||
</FeaturesProvider>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
.CanvasContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.LoadingIndicator {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.LoadingIndicator[data-complete="true"] {
|
||||
animation: loadingComplete 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.Spinner {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.2);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loadingComplete {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 260px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.SidebarSection {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.SidebarSection:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.SectionLabel {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.AnimationList {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.AnimationItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.AnimationItem:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.AnimationItem[data-active="true"] {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.PlayButton {
|
||||
flex-shrink: 0;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.PlayButton:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.AnimationItem[data-active="true"] .PlayButton {
|
||||
background: rgba(100, 180, 255, 0.3);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.AnimationName {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ClipName {
|
||||
flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CyclicIcon {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
title: "Cyclic (looping)";
|
||||
}
|
||||
|
||||
.CheckboxField {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
|
@ -1,441 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useCallback,
|
||||
Suspense,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { Canvas, GLProps } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { NoToneMapping, SRGBColorSpace, PCFShadowMap } from "three";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { OrbitControls, Center, Bounds, useBounds } from "@react-three/drei";
|
||||
import { SettingsProvider, useDebug } from "@/src/components/SettingsProvider";
|
||||
import { ShapeRenderer, useStaticShape } from "@/src/components/GenericShape";
|
||||
import { ShapeInfoProvider } from "@/src/components/ShapeInfoProvider";
|
||||
import { DebugElements } from "@/src/components/DebugElements";
|
||||
import { TickProvider } from "@/src/components/TickProvider";
|
||||
import { ShapeSelect } from "@/src/components/ShapeSelect";
|
||||
import { engineStore, useEngineSelector } from "@/src/state/engineStore";
|
||||
import {
|
||||
getResourceList,
|
||||
getResourceMap,
|
||||
getResourceKey,
|
||||
getSourceAndPath,
|
||||
} from "@/src/manifest";
|
||||
import { createParser, useQueryState } from "nuqs";
|
||||
import { createScriptLoader } from "@/src/torqueScript/scriptLoader.browser";
|
||||
import picomatch from "picomatch";
|
||||
import {
|
||||
createScriptCache,
|
||||
type FileSystemHandler,
|
||||
runServer,
|
||||
type TorqueObject,
|
||||
type TorqueRuntime,
|
||||
} from "@/src/torqueScript";
|
||||
import styles from "./page.module.css";
|
||||
import { ignoreScripts } from "@/src/torqueScript/ignoreScripts";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const sceneBg = new THREE.Color(0.1, 0.1, 0.1);
|
||||
|
||||
const glSettings: GLProps = {
|
||||
toneMapping: NoToneMapping,
|
||||
outputColorSpace: SRGBColorSpace,
|
||||
};
|
||||
|
||||
const loadScript = createScriptLoader();
|
||||
const scriptCache = createScriptCache();
|
||||
const fileSystem: FileSystemHandler = {
|
||||
findFiles: (pattern) => {
|
||||
const isMatch = picomatch(pattern, { nocase: true });
|
||||
return getResourceList()
|
||||
.filter((path) => isMatch(path))
|
||||
.map((resourceKey) => {
|
||||
const [, actualPath] = getSourceAndPath(resourceKey);
|
||||
return actualPath;
|
||||
});
|
||||
},
|
||||
isFile: (resourcePath) => {
|
||||
const resourceKeys = getResourceMap();
|
||||
const resourceKey = getResourceKey(resourcePath);
|
||||
return resourceKeys[resourceKey] != null;
|
||||
},
|
||||
};
|
||||
|
||||
const defaultShape = "deploy_inventory.dts";
|
||||
|
||||
const parseAsShape = createParser<string>({
|
||||
parse: (query: string) => query,
|
||||
serialize: (value: string) => value,
|
||||
eq: (a, b) => a === b,
|
||||
}).withDefault(defaultShape);
|
||||
|
||||
/**
|
||||
* Hook to run the TorqueScript runtime once (hardcoded to SC_Normal/CTF)
|
||||
* so deploy animations and other script-driven behaviors work.
|
||||
*/
|
||||
function useShapeRuntime(): TorqueRuntime | null {
|
||||
const [runtime, setRuntime] = useState<TorqueRuntime | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
let isDisposed = false;
|
||||
|
||||
const { runtime, ready } = runServer({
|
||||
missionName: "SC_Normal",
|
||||
missionType: "CTF",
|
||||
runtimeOptions: {
|
||||
loadScript,
|
||||
fileSystem,
|
||||
cache: scriptCache,
|
||||
signal: controller.signal,
|
||||
ignoreScripts,
|
||||
},
|
||||
});
|
||||
|
||||
void ready
|
||||
.then(() => {
|
||||
if (isDisposed || controller.signal.aborted) return;
|
||||
engineStore.getState().setRuntime(runtime);
|
||||
setRuntime(runtime);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof Error && err.name === "AbortError") return;
|
||||
console.error("Shape runtime failed:", err);
|
||||
});
|
||||
|
||||
// Seed store immediately
|
||||
engineStore.getState().setRuntime(runtime);
|
||||
|
||||
const unsubscribe = runtime.subscribeRuntimeEvents((event) => {
|
||||
if (event.type !== "batch.flushed") return;
|
||||
engineStore.getState().applyRuntimeBatch(event.events, {
|
||||
tick: event.tick,
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
isDisposed = true;
|
||||
controller.abort();
|
||||
unsubscribe();
|
||||
engineStore.getState().clearRuntime();
|
||||
runtime.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return runtime;
|
||||
}
|
||||
|
||||
/** Create a minimal TorqueObject for the shape viewer. */
|
||||
function createFakeObject(
|
||||
runtime: TorqueRuntime | null,
|
||||
shapeName: string,
|
||||
): TorqueObject {
|
||||
// Try to find a matching datablock for this shape so deploy animations work.
|
||||
let datablockName: string | undefined;
|
||||
if (runtime) {
|
||||
for (const obj of runtime.state.objectsById.values()) {
|
||||
if (
|
||||
obj.shapeFile &&
|
||||
String(obj.shapeFile).toLowerCase() === shapeName.toLowerCase()
|
||||
) {
|
||||
datablockName = obj._name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
_id: 99999,
|
||||
_class: "StaticShapeData",
|
||||
_className: "StaticShape",
|
||||
...(datablockName ? { datablock: datablockName } : {}),
|
||||
} as TorqueObject;
|
||||
}
|
||||
|
||||
function FitOnLoad() {
|
||||
const bounds = useBounds();
|
||||
useEffect(() => {
|
||||
bounds.refresh().fit();
|
||||
}, [bounds]);
|
||||
return null;
|
||||
}
|
||||
|
||||
interface AnimationInfo {
|
||||
name: string;
|
||||
alias: string | null;
|
||||
cyclic: boolean | null;
|
||||
}
|
||||
|
||||
/** Reports available animations (with cyclic and alias info when available). */
|
||||
function AnimationReporter({
|
||||
shapeName,
|
||||
onAnimations,
|
||||
}: {
|
||||
shapeName: string;
|
||||
onAnimations: (anims: AnimationInfo[]) => void;
|
||||
}) {
|
||||
const gltf = useStaticShape(shapeName);
|
||||
const shapeAliases = useEngineSelector((state) =>
|
||||
state.runtime.sequenceAliases.get(shapeName.toLowerCase()),
|
||||
);
|
||||
const anims = useMemo(() => {
|
||||
// Collect cyclic info from vis_sequence nodes on the scene
|
||||
const visCyclic = new Map<string, boolean>();
|
||||
gltf.scene.traverse((node: any) => {
|
||||
const ud = node.userData;
|
||||
if (ud?.vis_sequence && ud.vis_cyclic != null) {
|
||||
visCyclic.set(ud.vis_sequence.toLowerCase(), !!ud.vis_cyclic);
|
||||
}
|
||||
});
|
||||
// Build reverse alias map: clip name -> alias
|
||||
let reverseAliases: Map<string, string> | undefined;
|
||||
if (shapeAliases) {
|
||||
reverseAliases = new Map();
|
||||
for (const [alias, clipName] of shapeAliases) {
|
||||
reverseAliases.set(clipName, alias);
|
||||
}
|
||||
}
|
||||
return gltf.animations.map((clip) => ({
|
||||
name: clip.name,
|
||||
alias: reverseAliases?.get(clip.name.toLowerCase()) ?? null,
|
||||
cyclic: visCyclic.get(clip.name.toLowerCase()) ?? null,
|
||||
}));
|
||||
}, [gltf, shapeAliases]);
|
||||
const reportAnimations = useEffectEvent(onAnimations);
|
||||
useEffect(() => {
|
||||
reportAnimations(anims);
|
||||
}, [anims]);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Plays the selected animation via the TorqueScript runtime. */
|
||||
function AnimationPlayer({
|
||||
object,
|
||||
runtime,
|
||||
animation,
|
||||
}: {
|
||||
object: TorqueObject;
|
||||
runtime: TorqueRuntime | null;
|
||||
animation: string;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
if (!runtime || !animation) return;
|
||||
// Use nsCall to dispatch directly on the ShapeBase namespace, bypassing
|
||||
// class chain resolution (the fake object's _className won't resolve
|
||||
// to ShapeBase through the namespace parent chain).
|
||||
for (let slot = 0; slot < 4; slot++) {
|
||||
runtime.$.nsCall("ShapeBase", "stopThread", object, slot);
|
||||
}
|
||||
runtime.$.nsCall("ShapeBase", "playThread", object, 0, animation);
|
||||
return () => {
|
||||
for (let slot = 0; slot < 4; slot++) {
|
||||
runtime.$.nsCall("ShapeBase", "stopThread", object, slot);
|
||||
}
|
||||
};
|
||||
}, [runtime, object, animation]);
|
||||
return null;
|
||||
}
|
||||
|
||||
function ShapeViewer({
|
||||
shapeName,
|
||||
runtime,
|
||||
onAnimations,
|
||||
selectedAnimation,
|
||||
}: {
|
||||
shapeName: string;
|
||||
runtime: TorqueRuntime | null;
|
||||
onAnimations: (anims: AnimationInfo[]) => void;
|
||||
selectedAnimation: string;
|
||||
}) {
|
||||
const object = useMemo(
|
||||
() => createFakeObject(runtime, shapeName),
|
||||
[runtime, shapeName],
|
||||
);
|
||||
|
||||
return (
|
||||
<ShapeInfoProvider type="StaticShape" object={object} shapeName={shapeName}>
|
||||
<Center>
|
||||
<ShapeRenderer />
|
||||
<AnimationReporter shapeName={shapeName} onAnimations={onAnimations} />
|
||||
<AnimationPlayer
|
||||
object={object}
|
||||
runtime={runtime}
|
||||
animation={selectedAnimation}
|
||||
/>
|
||||
<FitOnLoad />
|
||||
</Center>
|
||||
</ShapeInfoProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function SceneLighting() {
|
||||
return (
|
||||
<>
|
||||
<ambientLight intensity={0.6} />
|
||||
<directionalLight position={[50, 80, 30]} intensity={1.2} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ShapeInspector() {
|
||||
const [currentShape, setCurrentShape] = useQueryState("shape", parseAsShape);
|
||||
const runtime = useShapeRuntime();
|
||||
const [availableAnimations, setAvailableAnimations] = useState<
|
||||
AnimationInfo[]
|
||||
>([]);
|
||||
const [selectedAnimation, setSelectedAnimation] = useState<string>("");
|
||||
|
||||
const handleAnimations = useCallback((anims: AnimationInfo[]) => {
|
||||
setAvailableAnimations(anims);
|
||||
setSelectedAnimation("");
|
||||
}, []);
|
||||
|
||||
const [showLoading, setShowLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
if (runtime) {
|
||||
const timer = setTimeout(() => setShowLoading(false), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [runtime]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<main>
|
||||
<SettingsProvider>
|
||||
<div className={styles.CanvasContainer}>
|
||||
{showLoading && (
|
||||
<div
|
||||
className={styles.LoadingIndicator}
|
||||
data-complete={!!runtime}
|
||||
>
|
||||
<div className={styles.Spinner} />
|
||||
</div>
|
||||
)}
|
||||
<Canvas
|
||||
frameloop="always"
|
||||
gl={glSettings}
|
||||
shadows={{ type: PCFShadowMap }}
|
||||
scene={{ background: sceneBg }}
|
||||
camera={{ position: [5, 3, 5], fov: 90 }}
|
||||
>
|
||||
<TickProvider>
|
||||
<SceneLighting />
|
||||
<Bounds fit clip observe margin={1.5}>
|
||||
<Suspense>
|
||||
<ShapeViewer
|
||||
key={currentShape}
|
||||
shapeName={currentShape}
|
||||
runtime={runtime}
|
||||
onAnimations={handleAnimations}
|
||||
selectedAnimation={selectedAnimation}
|
||||
/>
|
||||
</Suspense>
|
||||
</Bounds>
|
||||
<DebugElements />
|
||||
<OrbitControls makeDefault />
|
||||
</TickProvider>
|
||||
</Canvas>
|
||||
</div>
|
||||
<ShapeControls
|
||||
currentShape={currentShape}
|
||||
onChangeShape={setCurrentShape}
|
||||
animations={availableAnimations}
|
||||
selectedAnimation={selectedAnimation}
|
||||
onChangeAnimation={setSelectedAnimation}
|
||||
/>
|
||||
</SettingsProvider>
|
||||
</main>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function ShapeControls({
|
||||
currentShape,
|
||||
onChangeShape,
|
||||
animations,
|
||||
selectedAnimation,
|
||||
onChangeAnimation,
|
||||
}: {
|
||||
currentShape: string;
|
||||
onChangeShape: (shape: string) => void;
|
||||
animations: AnimationInfo[];
|
||||
selectedAnimation: string;
|
||||
onChangeAnimation: (name: string) => void;
|
||||
}) {
|
||||
const { debugMode, setDebugMode } = useDebug();
|
||||
|
||||
return (
|
||||
<div className={styles.Sidebar}>
|
||||
<div className={styles.SidebarSection}>
|
||||
<ShapeSelect value={currentShape} onChange={onChangeShape} />
|
||||
</div>
|
||||
<div className={styles.SidebarSection}>
|
||||
<div className={styles.CheckboxField}>
|
||||
<input
|
||||
id="debugInput"
|
||||
type="checkbox"
|
||||
checked={debugMode}
|
||||
onChange={(e) => setDebugMode(e.target.checked)}
|
||||
/>
|
||||
<label htmlFor="debugInput">Debug</label>
|
||||
</div>
|
||||
</div>
|
||||
{animations.length > 0 && (
|
||||
<>
|
||||
<div className={styles.SidebarSection}>
|
||||
<div className={styles.SectionLabel}>Animations</div>
|
||||
</div>
|
||||
<div className={styles.AnimationList}>
|
||||
{animations.map((anim) => (
|
||||
<div
|
||||
key={anim.name}
|
||||
className={styles.AnimationItem}
|
||||
data-active={selectedAnimation === anim.name}
|
||||
onClick={() =>
|
||||
onChangeAnimation(
|
||||
selectedAnimation === anim.name ? "" : anim.name,
|
||||
)
|
||||
}
|
||||
>
|
||||
<button
|
||||
className={styles.PlayButton}
|
||||
title={`Play ${anim.alias ?? anim.name}`}
|
||||
>
|
||||
{selectedAnimation === anim.name ? "\u25A0" : "\u25B6"}
|
||||
</button>
|
||||
<span className={styles.AnimationName}>
|
||||
{anim.alias ?? anim.name}
|
||||
</span>
|
||||
{anim.alias && (
|
||||
<span
|
||||
className={styles.ClipName}
|
||||
title={`GLB clip: ${anim.name}`}
|
||||
>
|
||||
{anim.name}
|
||||
</span>
|
||||
)}
|
||||
{anim.cyclic === true && (
|
||||
<span className={styles.CyclicIcon} title="Cyclic (looping)">
|
||||
{"\u221E"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ShapesPage() {
|
||||
return (
|
||||
<Suspense>
|
||||
<ShapeInspector />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,10 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>t2-mapper</title>
|
||||
<meta charset="utf-8" />
|
||||
<title>MapGenius – Explore maps for Tribes 2</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="description" content="Tribes 2 forever." />
|
||||
<link rel="icon" type="image/png" href="/t2-mapper/icon.png" />
|
||||
<script type="module" crossorigin src="/t2-mapper/assets/index-ClGJzuqQ.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/chunk-DECur_0Z.js">
|
||||
<link rel="modulepreload" crossorigin href="/t2-mapper/assets/logger-DePRU8Hm.js">
|
||||
|
|
|
|||
12
index.html
12
index.html
|
|
@ -1,10 +1,14 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>t2-mapper</title>
|
||||
<meta charset="utf-8" />
|
||||
<title>MapGenius – Explore maps for Tribes 2</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="description" content="Tribes 2 forever." />
|
||||
<link rel="icon" type="image/png" href="/icon.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
6
next-env.d.ts
vendored
6
next-env.d.ts
vendored
|
|
@ -1,6 +0,0 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import { NextConfig } from "next";
|
||||
import { PHASE_DEVELOPMENT_SERVER } from "next/constants";
|
||||
|
||||
const nextConfig = (phase, { defaultConfig }): NextConfig => {
|
||||
return {
|
||||
// Suppress static export config warnings in dev mode as they are not relevant.
|
||||
output: phase === PHASE_DEVELOPMENT_SERVER ? undefined : "export",
|
||||
distDir: "./docs",
|
||||
basePath: "/t2-mapper",
|
||||
assetPrefix: "/t2-mapper/",
|
||||
trailingSlash: true,
|
||||
reactCompiler: true,
|
||||
experimental: { viewTransition: true },
|
||||
headers:
|
||||
// TorqueScript files should be served as text. This won't affect what
|
||||
// GitHub Pages does with the static export, but it'll at least improve
|
||||
// the dev server. Otherwise, the responses can't be easily inspected in
|
||||
// the Network tab.
|
||||
phase === PHASE_DEVELOPMENT_SERVER
|
||||
? async () => {
|
||||
return [
|
||||
{
|
||||
source: "/:path*.cs",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Type",
|
||||
value: "text/plain; charset=utf-8",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
: undefined,
|
||||
// For the dev server, redirect / to the `basePath` for convenience, so you
|
||||
// can just open localhost:3000.
|
||||
redirects:
|
||||
phase === PHASE_DEVELOPMENT_SERVER
|
||||
? async () => {
|
||||
return [
|
||||
{
|
||||
source: "/",
|
||||
destination: "/t2-mapper/",
|
||||
basePath: false,
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -5,6 +5,7 @@ import babel from "@rolldown/plugin-babel";
|
|||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: "/t2-mapper/",
|
||||
server: { port: 3000 },
|
||||
build: {
|
||||
outDir: "docs",
|
||||
emptyOutDir: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue