Files
Genarrative/src/ActiveApp.tsx
T
kdletters e9c3dc1120 退役旧创作模板业务
保留 SpacetimeDB 历史表、迁移白名单与旧业务源码
切换前端 active 入口并解除旧创作页面和路由编译链
移除旧后端路由、worker 与纯业务 crate 依赖
收敛 SpacetimeDB 模块为历史数据壳
同步 Nginx、Pingora、验证门禁与架构文档
2026-07-17 22:07:52 +08:00

137 lines
4.0 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 [, setHostRuntimeRevision] = useState(0);
const [selectionStage, setRawSelectionStage] = useState<SelectionStage>(
resolveInitialAppSelectionStage,
);
const setSelectionStage = useCallback(
(stage: SelectionStage, options?: { path?: string }) => {
setRawSelectionStage(stage);
pushAppHistoryPath(options?.path ?? resolvePathForSelectionStage(stage));
},
[],
);
useEffect(() => {
const unsubscribe = subscribeHostRuntimeChange(() => {
setHostRuntimeRevision((revision) => revision + 1);
});
void refreshNativeAppHostRuntime();
return unsubscribe;
}, []);
useEffect(() => {
const syncStageFromHistory = () => {
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>
);
}