Files
Genarrative/src/ActiveApp.tsx
T
lhk229 e32823d389
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m29s
Project CI / Frontend tests (pull_request) Failing after 55s
Project CI / Backend tests (pull_request) Successful in 8m13s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m37s
Project CI / Repository checks (pull_request) Successful in 7m5s
Project CI / Native shell tests (pull_request) Successful in 9m2s
实现创作者主页与关注粉丝功能
新增创作者公开主页、第四导航入口、详情作者跳转及关注按钮
实现关注与粉丝列表、取消后重新关注、回关及移除粉丝交互
新增私有关注关系表、类型契约、受信过程和站内查询写入接口
按作者过滤公开游戏并保持既有四十八项读取边界
复用共享用户行、关注按钮及游戏卡,接入移动布局和页面深链
补充定向测试、隔离数据库及浏览器验收脚本并归并工程验证文档
2026-10-05 11:43:05 +01: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>
);
}