Files
Genarrative/src/ActiveApp.tsx
T
lhk229 51cb05f418
Project CI / AI game creator shell Rust crates (push) Successful in 5m56s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m37s
Project CI / Backend tests (push) Successful in 9m22s
Project CI / Frontend tests (push) Successful in 4m40s
Project CI / AI game creator shell web tests (push) Successful in 3m50s
Project CI / Repository checks (push) Successful in 6m30s
Project CI / Native shell tests (push) Successful in 8m32s
补充创作者主页与关注粉丝工程文档 (#638)
本 PR 补齐创作者主页与关注粉丝功能的产品合同和工程设计,供后续按里程碑实施;本次只修改文档。

桌面导航第四项进入自己的创作者主页,“我的”移到第五项。游戏详情可进入作者主页并关注/取消关注;自己或他人的关注、粉丝列表均公开可查看,点击用户头像或昵称可进入其主页。本人支持取消/重新关注、回关和移除粉丝,并明确两个关注方向独立。

工程设计包含关系表与 DTO、认证和事务边界、跨页状态同步、共享组件、深链及验收要求,拆分关系查询和页面交互两个里程碑。按用户要求,游戏列表沿用最多 48 项的现有限制,不增加游戏目录分页改造。

验证:
- 已合入最新 origin/master(21c79f54a);共享决策记录冲突保留双方新增内容。
- npm run check:encoding 通过。
- npm run check:doc-index 通过。
- git diff --check 与暂存差异检查通过。
- 相对 master 仅六份文档发生变化;没有业务代码或 schema 修改,未执行业务测试或部署。

Reviewed-on: #638
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-10-05 22:05:11 +08:00

141 lines
4.3 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { useAuthUi } from './components/auth/AuthUiContext';
import type { SelectionStage } from './components/platform-entry/platformEntryActiveTypes';
import { PlatformEntryFlowShell } from './components/platform-entry/PlatformEntryFlowShell';
import { useHostNavigationCanGoBack } from './hooks/useHostNavigationCanGoBack';
import {
isAppHistoryState,
isKnownMainAppPagePath,
normalizeAppPath,
pushAppHistoryPath,
replaceAppHistoryPath,
resolveInitialSelectionStageFromPath,
resolvePathForSelectionStage,
shouldRedirectEditorCanvasWithoutProject,
} from './routing/activeAppPageRoutes';
import {
resolveAppTitleForSelectionStage,
syncAppTitle,
} from './services/activeAppTitle';
import {
refreshNativeAppHostRuntime,
subscribeHostRuntimeChange,
} from './services/host-bridge/hostBridge';
function resolveInitialAppSelectionStage() {
if (
shouldRedirectEditorCanvasWithoutProject(
window.location.pathname,
window.location.search,
)
) {
replaceAppHistoryPath('/project');
return 'project';
}
if (!isKnownMainAppPagePath(window.location.pathname)) {
replaceAppHistoryPath('/');
return 'platform';
}
return resolveInitialSelectionStageFromPath(window.location.pathname, false);
}
export default function App() {
const authUi = useAuthUi();
const hostNavigation = useHostNavigationCanGoBack();
const [, setLocationRevision] = useState(0);
const [, setHostRuntimeRevision] = useState(0);
const [selectionStage, setRawSelectionStage] = useState<SelectionStage>(
resolveInitialAppSelectionStage,
);
const setSelectionStage = useCallback(
(stage: SelectionStage, options?: { path?: string }) => {
setRawSelectionStage(stage);
setLocationRevision((revision) => revision + 1);
pushAppHistoryPath(options?.path ?? resolvePathForSelectionStage(stage));
},
[],
);
useEffect(() => {
const unsubscribe = subscribeHostRuntimeChange(() => {
setHostRuntimeRevision((revision) => revision + 1);
});
void refreshNativeAppHostRuntime();
return unsubscribe;
}, []);
useEffect(() => {
const syncStageFromHistory = () => {
// 同一舞台切换用户、列表方向或浏览器返回也要重读 URL。
setLocationRevision((revision) => revision + 1);
if (
shouldRedirectEditorCanvasWithoutProject(
window.location.pathname,
window.location.search,
)
) {
replaceAppHistoryPath('/project');
setRawSelectionStage('project');
return;
}
if (!isKnownMainAppPagePath(window.location.pathname)) {
replaceAppHistoryPath('/');
setRawSelectionStage('platform');
return;
}
setRawSelectionStage(
resolveInitialSelectionStageFromPath(window.location.pathname, false),
);
};
window.addEventListener('popstate', syncStageFromHistory);
return () => window.removeEventListener('popstate', syncStageFromHistory);
}, []);
useEffect(() => {
if (
!hostNavigation.isSupported ||
hostNavigation.canGoBack ||
selectionStage === 'platform' ||
isAppHistoryState(window.history.state)
) {
return;
}
const currentPath = normalizeAppPath(window.location.pathname);
const currentSearch = window.location.search;
replaceAppHistoryPath('/');
pushAppHistoryPath(`${currentPath}${currentSearch}`);
}, [hostNavigation.canGoBack, hostNavigation.isSupported, selectionStage]);
const platformThemeClass =
authUi?.platformTheme === 'dark'
? 'platform-theme--dark'
: 'platform-theme--light';
const isImageEditorStage = selectionStage === 'image-editor';
const platformShellSurfaceClass = isImageEditorStage
? 'bg-white p-0'
: 'bg-[image:var(--platform-body-fill)] p-2 sm:p-4';
useEffect(() => {
syncAppTitle(resolveAppTitleForSelectionStage(selectionStage));
}, [selectionStage]);
return (
<div
className={`platform-ui-shell platform-viewport-shell platform-theme ${platformThemeClass} flex flex-col overflow-hidden ${platformShellSurfaceClass} font-sans text-[var(--platform-text-strong)]`}
>
<PlatformEntryFlowShell
selectionStage={selectionStage}
setSelectionStage={setSelectionStage}
/>
</div>
);
}