初始仓库迁移
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-04 23:57:06 +08:00
parent 80986b790d
commit c49c64896a
18446 changed files with 532435 additions and 2 deletions

View File

@@ -0,0 +1,16 @@
export function RouteLoadingScreen({
eyebrow,
text,
}: {
eyebrow: string;
text: string;
}) {
return (
<div className="flex min-h-screen items-center justify-center bg-[#0d1016] px-6 text-zinc-200">
<div className="text-center">
<div className="text-sm tracking-[0.26em] text-zinc-500">{eyebrow}</div>
<div className="mt-3 text-lg font-semibold text-white">{text}</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';
import { matchAppRoute } from './appRoutes';
describe('matchAppRoute', () => {
it('routes the main app by default', () => {
expect(matchAppRoute('/')).toEqual({
kind: 'game',
});
});
it('routes item editor paths to the preset editor items tab', () => {
expect(matchAppRoute('/item-editor/tools')).toEqual({
kind: 'preset-editor',
initialTab: 'items',
});
});
it('routes behavior editor paths to the functions tab', () => {
expect(matchAppRoute('/behavior-editor')).toEqual({
kind: 'preset-editor',
initialTab: 'functions',
});
});
it('accepts nested preset editor paths with trailing slashes', () => {
expect(matchAppRoute('/NPC-EDITOR/profiles/')).toEqual({
kind: 'preset-editor',
initialTab: 'npcs',
});
});
it('does not treat unrelated prefixes as preset editor routes', () => {
expect(matchAppRoute('/npc-editorial')).toEqual({
kind: 'game',
});
});
});

122
src/routing/appRoutes.tsx Normal file
View File

@@ -0,0 +1,122 @@
/* eslint-disable react-refresh/only-export-components */
import { type ComponentType, lazy, type LazyExoticComponent } from 'react';
import type { PresetEditorTab } from '../components/PresetEditor';
type AppRouteComponent = LazyExoticComponent<
ComponentType<Record<string, unknown>>
>;
export type AppRouteMatch =
| {
kind: 'game';
}
| {
kind: 'preset-editor';
initialTab: PresetEditorTab;
};
export type ResolvedAppRoute = {
kind: AppRouteMatch['kind'];
loadingEyebrow: string;
loadingText: string;
Component: AppRouteComponent;
componentProps?: Record<string, unknown>;
};
const GameApp = lazy(() => import('../App')) as AppRouteComponent;
const PresetEditorApp = lazy(async () => {
const module = await import('../components/PresetEditor');
return {
default: module.PresetEditor,
};
}) as AppRouteComponent;
const PRESET_EDITOR_ROUTES: Array<{
prefixes: string[];
initialTab: PresetEditorTab;
}> = [
{
prefixes: ['/character-asset-studio', '/asset-studio'],
initialTab: 'assets',
},
{
prefixes: ['/function-editor', '/behavior-editor'],
initialTab: 'functions',
},
{
prefixes: ['/item-editor'],
initialTab: 'items',
},
{
prefixes: ['/npc-editor'],
initialTab: 'npcs',
},
{
prefixes: ['/preset-editor'],
initialTab: 'characters',
},
];
function normalizeRoutePath(pathname: string) {
const trimmedPathname = pathname.trim().toLowerCase();
if (!trimmedPathname || trimmedPathname === '/') {
return '/';
}
return trimmedPathname.replace(/\/+$/u, '');
}
function matchesRoutePrefix(pathname: string, prefix: string) {
const normalizedPrefix = normalizeRoutePath(prefix);
return (
pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)
);
}
export function matchAppRoute(pathname: string): AppRouteMatch {
const normalizedPathname = normalizeRoutePath(pathname);
const presetRoute = PRESET_EDITOR_ROUTES.find((route) =>
route.prefixes.some((prefix) =>
matchesRoutePrefix(normalizedPathname, prefix),
),
);
if (presetRoute) {
return {
kind: 'preset-editor',
initialTab: presetRoute.initialTab,
};
}
return {
kind: 'game',
};
}
export function resolveAppRoute(pathname: string): ResolvedAppRoute {
const matchedRoute = matchAppRoute(pathname);
if (matchedRoute.kind === 'preset-editor') {
return {
kind: matchedRoute.kind,
loadingEyebrow: '正在载入编辑器',
loadingText: '正在载入编辑器...',
Component: PresetEditorApp,
componentProps: {
initialTab: matchedRoute.initialTab,
},
};
}
return {
kind: 'game',
loadingEyebrow: '正在载入游戏',
loadingText: '正在载入冒险...',
Component: GameApp,
};
}