Merge remote-tracking branch 'origin/master' into fix/agc-acceptance-followup
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 19s
Project CI / Native shell tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 19s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 19s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / Frontend tests (pull_request) Failing after 6s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 19s
Project CI / Native shell tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 19s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 19s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / Frontend tests (pull_request) Failing after 6s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
FolderKanban,
|
||||
Gamepad2,
|
||||
Home,
|
||||
Monitor,
|
||||
Palette,
|
||||
@@ -67,31 +66,6 @@ const ProjectGalleryView = lazy(async () => {
|
||||
return { default: module.ProjectGalleryView };
|
||||
});
|
||||
|
||||
const GameGalleryPage = lazy(async () => {
|
||||
const module = await import('../game-distribution/GameGalleryPage');
|
||||
return { default: module.GameGalleryPage };
|
||||
});
|
||||
|
||||
const GameDetailPage = lazy(async () => {
|
||||
const module = await import('../game-distribution/GameDetailPage');
|
||||
return { default: module.GameDetailPage };
|
||||
});
|
||||
|
||||
const GamePublishPage = lazy(async () => {
|
||||
const module = await import('../game-distribution/GamePublishPage');
|
||||
return { default: module.GamePublishPage };
|
||||
});
|
||||
|
||||
const MyGamesPage = lazy(async () => {
|
||||
const module = await import('../game-distribution/MyGamesPage');
|
||||
return { default: module.MyGamesPage };
|
||||
});
|
||||
|
||||
const GamePlayPage = lazy(async () => {
|
||||
const module = await import('../game-distribution/GamePlayPage');
|
||||
return { default: module.GamePlayPage };
|
||||
});
|
||||
|
||||
type ActiveRailButtonProps = {
|
||||
active: boolean;
|
||||
emphasized?: boolean;
|
||||
@@ -171,26 +145,16 @@ function ActiveBottomNavButton({
|
||||
function MobileProfileDock({
|
||||
active,
|
||||
onOpenProfile,
|
||||
gameActive,
|
||||
onOpenGames,
|
||||
}: {
|
||||
active: boolean;
|
||||
onOpenProfile: () => void;
|
||||
gameActive: boolean;
|
||||
onOpenGames: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="platform-mobile-bottom-dock min-w-0 shrink-0 lg:hidden">
|
||||
<nav
|
||||
className="platform-bottom-nav grid grid-cols-2"
|
||||
className="platform-bottom-nav grid grid-cols-1"
|
||||
aria-label="移动平台导航"
|
||||
>
|
||||
<ActiveBottomNavButton
|
||||
active={gameActive}
|
||||
icon={Gamepad2}
|
||||
label="游戏"
|
||||
onClick={onOpenGames}
|
||||
/>
|
||||
<ActiveBottomNavButton
|
||||
active={active}
|
||||
icon={UserRound}
|
||||
@@ -394,34 +358,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSelectionStage('profile', { path: '/profile' });
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const openGames = useCallback(() => {
|
||||
setSelectionStage('games', { path: '/games' });
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const openGamePublish = useCallback(() => {
|
||||
setSelectionStage('game-publish', { path: '/games/publish' });
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const openMyGames = useCallback(() => {
|
||||
setSelectionStage('game-mine', { path: '/games/mine' });
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const openGameDetail = useCallback(
|
||||
(gameId: string) => {
|
||||
const path = `/games/detail?id=${encodeURIComponent(gameId)}`;
|
||||
setSelectionStage('game-detail', { path });
|
||||
},
|
||||
[setSelectionStage],
|
||||
);
|
||||
|
||||
const openGamePlay = useCallback(
|
||||
(gameId: string) => {
|
||||
const path = `/games/play?id=${encodeURIComponent(gameId)}`;
|
||||
setSelectionStage('game-play', { path });
|
||||
},
|
||||
[setSelectionStage],
|
||||
);
|
||||
|
||||
const openEditorProject = useCallback(
|
||||
(projectId: string, options?: { guide?: boolean; tool?: string }) => {
|
||||
if (!isDesktopLayout) {
|
||||
@@ -466,12 +402,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
setSelectionStage('platform', { path: '/' });
|
||||
}}
|
||||
/>
|
||||
<MobileProfileDock
|
||||
active={false}
|
||||
gameActive={false}
|
||||
onOpenProfile={openProfile}
|
||||
onOpenGames={openGames}
|
||||
/>
|
||||
<MobileProfileDock active={false} onOpenProfile={openProfile} />
|
||||
<PlatformActiveMobileWelcomeDialog
|
||||
open={shouldOpenMobileHomeWelcome}
|
||||
platformThemeClass={platformThemeClass}
|
||||
@@ -499,15 +430,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
const isCreationStage =
|
||||
!isProfileStage &&
|
||||
(selectionStage === 'platform' || selectionStage === 'creation-home');
|
||||
const isGamesStage =
|
||||
selectionStage === 'games' ||
|
||||
selectionStage === 'game-detail' ||
|
||||
selectionStage === 'game-play' ||
|
||||
selectionStage === 'game-mine' ||
|
||||
selectionStage === 'game-publish';
|
||||
const gameId = new URLSearchParams(
|
||||
typeof window === 'undefined' ? '' : window.location.search,
|
||||
).get('id');
|
||||
const avatarUrl = authUi?.user?.avatarUrl?.trim() || null;
|
||||
const avatarLabel = resolveActiveUserAvatarLabel(authUi?.user);
|
||||
const publicUserCode = resolveActivePublicUserCode(authUi?.user);
|
||||
@@ -562,14 +484,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
label="项目"
|
||||
onClick={openProjects}
|
||||
/>
|
||||
<ActiveRailButton
|
||||
active={isGamesStage}
|
||||
emphasized={isGamesStage}
|
||||
icon={Gamepad2}
|
||||
iconSrc="/creation-home/nav-projects.png"
|
||||
label="游戏"
|
||||
onClick={openGames}
|
||||
/>
|
||||
<ActiveRailButton
|
||||
active={isProfileStage}
|
||||
icon={UserRound}
|
||||
@@ -601,12 +515,8 @@ export function PlatformEntryFlowShellImpl({
|
||||
<input
|
||||
type="search"
|
||||
value={searchInput}
|
||||
placeholder={
|
||||
isGamesStage
|
||||
? '搜索游戏、作者或标签'
|
||||
: '搜索项目、素材、作者或描述'
|
||||
}
|
||||
aria-label={isGamesStage ? '搜索游戏' : '搜索项目和素材'}
|
||||
placeholder="搜索项目、素材、作者或描述"
|
||||
aria-label="搜索项目和素材"
|
||||
className="min-w-0 flex-1 bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]"
|
||||
onChange={(event) => {
|
||||
const value = event.target.value;
|
||||
@@ -712,42 +622,6 @@ export function PlatformEntryFlowShellImpl({
|
||||
searchKeyword={activeSearchKeyword}
|
||||
/>
|
||||
</Suspense>
|
||||
) : isGamesStage ? (
|
||||
<Suspense
|
||||
fallback={<LoadingPanel label="正在加载游戏广场..." />}
|
||||
>
|
||||
{selectionStage === 'games' ? (
|
||||
<GameGalleryPage
|
||||
searchKeyword={activeSearchKeyword}
|
||||
onOpenDetail={openGameDetail}
|
||||
onOpenMyGames={openMyGames}
|
||||
onOpenPublish={openGamePublish}
|
||||
/>
|
||||
) : selectionStage === 'game-publish' ? (
|
||||
<GamePublishPage
|
||||
onBack={openGames}
|
||||
onOpenMyGames={openMyGames}
|
||||
/>
|
||||
) : selectionStage === 'game-mine' ? (
|
||||
<MyGamesPage
|
||||
onBack={openGames}
|
||||
onOpenDetail={openGameDetail}
|
||||
/>
|
||||
) : selectionStage === 'game-detail' ? (
|
||||
<GameDetailPage
|
||||
gameId={gameId}
|
||||
onBack={openGames}
|
||||
onPlay={openGamePlay}
|
||||
/>
|
||||
) : (
|
||||
<GamePlayPage
|
||||
gameId={gameId}
|
||||
onBack={() =>
|
||||
gameId ? openGameDetail(gameId) : openGames()
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
) : (
|
||||
<Suspense fallback={<LoadingPanel label="正在加载项目..." />}>
|
||||
<ProjectGalleryView
|
||||
@@ -761,9 +635,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
</div>
|
||||
<MobileProfileDock
|
||||
active={isProfileStage}
|
||||
gameActive={isGamesStage}
|
||||
onOpenProfile={openProfile}
|
||||
onOpenGames={openGames}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,7 @@ export type SelectionStage =
|
||||
| 'creation-home'
|
||||
| 'project'
|
||||
| 'profile'
|
||||
| 'image-editor'
|
||||
| 'games'
|
||||
| 'game-detail'
|
||||
| 'game-play'
|
||||
| 'game-mine'
|
||||
| 'game-publish';
|
||||
| 'image-editor';
|
||||
|
||||
export type PlatformEntryFlowShellProps = {
|
||||
selectionStage: SelectionStage;
|
||||
|
||||
@@ -22,6 +22,24 @@ describe('vite dev api proxy', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('forwards the public creation entry config route to the Rust API server', async () => {
|
||||
const resolvedConfig =
|
||||
typeof viteConfig === 'function'
|
||||
? await viteConfig({ command: 'serve', mode: 'test' })
|
||||
: viteConfig;
|
||||
|
||||
// 中文注释:创作入口配置是底部加号入口的首屏事实源,漏配代理会回退 index.html。
|
||||
expect(resolvedConfig.server?.proxy).toEqual(
|
||||
expect.objectContaining({
|
||||
'/api/creation-entry': expect.objectContaining({
|
||||
target: expect.any(String),
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('forwards the admin route to the admin dev server', async () => {
|
||||
const resolvedConfig =
|
||||
typeof viteConfig === 'function'
|
||||
@@ -39,23 +57,4 @@ describe('vite dev api proxy', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('forwards the game distribution route to the Rust API server', async () => {
|
||||
const resolvedConfig =
|
||||
typeof viteConfig === 'function'
|
||||
? await viteConfig({ command: 'serve', mode: 'test' })
|
||||
: viteConfig;
|
||||
|
||||
// 中文注释:游戏目录、详情、发行网关和发布写入都在 `/api/game-distribution/*`;
|
||||
// 漏配代理会回退 index.html,前端解析 JSON 时直接失败。
|
||||
expect(resolvedConfig.server?.proxy).toEqual(
|
||||
expect.objectContaining({
|
||||
'/api/game-distribution': expect.objectContaining({
|
||||
target: expect.any(String),
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,13 +22,6 @@ describe('appPageRoutes', () => {
|
||||
expect(resolveSelectionStageFromPath('/EDITOR/CANVAS/')).toBe(
|
||||
'image-editor',
|
||||
);
|
||||
expect(resolveSelectionStageFromPath('/games')).toBe('games');
|
||||
expect(resolveSelectionStageFromPath('/games/detail/')).toBe('game-detail');
|
||||
expect(resolveSelectionStageFromPath('/games/play')).toBe('game-play');
|
||||
expect(resolveSelectionStageFromPath('/games/mine')).toBe('game-mine');
|
||||
expect(resolveSelectionStageFromPath('/games/publish')).toBe(
|
||||
'game-publish',
|
||||
);
|
||||
expect(resolveSelectionStageFromPath('/creation/rpg')).toBe('platform');
|
||||
expect(resolveSelectionStageFromPath('/runtime/puzzle')).toBe('platform');
|
||||
expect(isKnownMainAppPagePath('/creation')).toBe(true);
|
||||
@@ -38,9 +31,6 @@ describe('appPageRoutes', () => {
|
||||
expect(resolvePathForSelectionStage('creation-home')).toBe('/creation');
|
||||
expect(resolvePathForSelectionStage('project')).toBe('/project');
|
||||
expect(resolvePathForSelectionStage('profile')).toBe('/profile');
|
||||
expect(resolvePathForSelectionStage('games')).toBe('/games');
|
||||
expect(resolvePathForSelectionStage('game-mine')).toBe('/games/mine');
|
||||
expect(resolvePathForSelectionStage('game-publish')).toBe('/games/publish');
|
||||
});
|
||||
|
||||
it('requires a project id for direct editor navigation', () => {
|
||||
|
||||
@@ -9,11 +9,6 @@ const STAGE_ROUTE_ENTRIES = [
|
||||
['project', '/project'],
|
||||
['profile', '/profile'],
|
||||
['image-editor', '/editor/canvas'],
|
||||
['games', '/games'],
|
||||
['game-detail', '/games/detail'],
|
||||
['game-play', '/games/play'],
|
||||
['game-mine', '/games/mine'],
|
||||
['game-publish', '/games/publish'],
|
||||
] as const satisfies readonly (readonly [SelectionStage, string])[];
|
||||
|
||||
export const APP_STAGE_ROUTES: Record<SelectionStage, string> =
|
||||
|
||||
@@ -32,13 +32,6 @@ describe('activeAppTitle', () => {
|
||||
expect(resolveAppTitleForSelectionStage('image-editor')).toBe(
|
||||
'美术编辑器 - 陶泥儿',
|
||||
);
|
||||
expect(resolveAppTitleForSelectionStage('games')).toBe('游戏 - 陶泥儿');
|
||||
expect(resolveAppTitleForSelectionStage('game-mine')).toBe(
|
||||
'我的游戏 - 陶泥儿',
|
||||
);
|
||||
expect(resolveAppTitleForSelectionStage('game-publish')).toBe(
|
||||
'发布游戏 - 陶泥儿',
|
||||
);
|
||||
});
|
||||
|
||||
test('syncs browser and host titles', () => {
|
||||
|
||||
@@ -10,11 +10,6 @@ const APP_TITLE_BY_SELECTION_STAGE: Record<SelectionStage, string> = {
|
||||
project: '项目 - 陶泥儿',
|
||||
profile: '我的 - 陶泥儿',
|
||||
'image-editor': '美术编辑器 - 陶泥儿',
|
||||
games: '游戏 - 陶泥儿',
|
||||
'game-detail': '游戏详情 - 陶泥儿',
|
||||
'game-play': '正在游玩 - 陶泥儿',
|
||||
'game-mine': '我的游戏 - 陶泥儿',
|
||||
'game-publish': '发布游戏 - 陶泥儿',
|
||||
};
|
||||
|
||||
export function resolveAppTitleForSelectionStage(stage: SelectionStage) {
|
||||
|
||||
Reference in New Issue
Block a user