调整创作主页与项目入口

新增陶泥儿创作主页和用户素材瀑布流

新增项目入口并复用画布中心封面

保留创作主页左侧导航和上方栏

修复玩家社区在创作页原地弹窗

更新路由、测试和项目记忆文档
This commit is contained in:
2026-06-19 10:47:31 +08:00
parent 160c96a014
commit 8857e36e1a
20 changed files with 2362 additions and 313 deletions
@@ -1,4 +1,4 @@
import { Image as ImageIcon, Loader2 } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';
import {
@@ -534,6 +534,7 @@ import {
PlatformEntryHomeView,
type PlatformHomeTab,
} from './PlatformEntryHomeView';
import { PlatformProfileReferralModal } from './PlatformProfileReferralModal';
import { usePlatformDesktopLayout } from './platformEntryResponsive';
import {
buildCreationHubFallbackItems,
@@ -1285,6 +1286,13 @@ const ProjectGalleryView = lazy(async () => {
};
});
const CreationLandingView = lazy(async () => {
const module = await import('../creation-home/CreationLandingView');
return {
default: module.CreationLandingView,
};
});
const UnifiedCreationWorkspace = lazy(async () => {
const module = await import('../unified-creation/UnifiedCreationWorkspace');
return {
@@ -1620,6 +1628,8 @@ export function PlatformEntryFlowShellImpl({
: 'platform-theme--light';
const isDesktopLayout = usePlatformDesktopLayout();
const [showCreationTypeModal, setShowCreationTypeModal] = useState(false);
const [isCreationCommunityOpen, setIsCreationCommunityOpen] =
useState(false);
const [draftGenerationPointNotice, setDraftGenerationPointNotice] =
useState<DraftGenerationPointNotice | null>(null);
const [selectedDetailEntry, setSelectedDetailEntry] =
@@ -3597,6 +3607,24 @@ export function PlatformEntryFlowShellImpl({
]);
const openCreationTypePicker = useCallback(() => {
setSelectionStage('creation-home');
}, [setSelectionStage]);
const openProjectGallery = useCallback(() => {
setSelectionStage('project');
}, [setSelectionStage]);
const openCreationCommunity = useCallback(() => {
setIsCreationCommunityOpen(true);
}, []);
const openEditorProject = useCallback((projectId: string) => {
setSelectionStage('image-editor', {
path: `/editor/canvas?projectid=${encodeURIComponent(projectId)}`,
});
}, [setSelectionStage]);
const openCreationTypeModal = useCallback(() => {
if (!prepareCreationLaunch()) {
return;
}
@@ -15129,29 +15157,164 @@ export function PlatformEntryFlowShellImpl({
) : null}
</Suspense>
);
const creationStartContent = (
<div className="image-editor-creation-entry-stack">
<button
type="button"
className="image-editor-creation-entry"
onClick={() => setSelectionStage('image-editor')}
aria-label="打开图片编辑器"
>
<span className="image-editor-creation-entry__icon">
<ImageIcon className="h-5 w-5" />
</span>
<span className="image-editor-creation-entry__body">
<span>图片编辑器</span>
<span>画布工具</span>
</span>
</button>
{renderCreationHubContent('start-only', '正在加载创作大厅...')}
</div>
const creationStartContent = renderCreationHubContent(
'start-only',
'正在加载创作大厅...',
);
const draftHubContent = renderCreationHubContent(
'works-only',
'正在加载草稿列表...',
);
const creationLandingContent = (
<Suspense fallback={<LazyPanelFallback label="正在加载创作主页..." />}>
<CreationLandingView
onOpenProject={openEditorProject}
onOpenProjects={openProjectGallery}
onOpenCommunity={openCreationCommunity}
publicGalleryEntries={[
...featuredGalleryEntries,
...latestGalleryEntries,
]}
/>
</Suspense>
);
const handlePlatformHomeTabChange = useCallback(
(tab: PlatformHomeTab) => {
platformBootstrap.setPlatformTab(tab);
if (selectionStage === 'creation-home' && tab !== 'create') {
setSelectionStage('platform');
pushAppHistoryPath('/');
}
},
[platformBootstrap, selectionStage, setSelectionStage],
);
const platformHomeView = (
<PlatformEntryHomeView
activeTab={
selectionStage === 'creation-home'
? 'create'
: platformBootstrap.platformTab
}
onTabChange={handlePlatformHomeTabChange}
hasSavedGame={hasSavedGame}
savedSnapshot={savedSnapshot}
saveEntries={platformBootstrap.saveEntries}
saveError={platformBootstrap.saveError}
featuredEntries={featuredGalleryEntries}
latestEntries={latestGalleryEntries}
myEntries={platformBootstrap.savedCustomWorldEntries}
historyEntries={platformBootstrap.historyEntries}
profileDashboard={platformBootstrap.profileDashboard}
isLoadingPlatform={platformBootstrap.isLoadingPlatform}
isLoadingDashboard={platformBootstrap.isLoadingDashboard}
hasUnreadDraftUpdate={hasUnreadDraftUpdates}
profileTaskRefreshKey={profileTaskRefreshKey}
profileGenerationQueueStatus={profileExternalGenerationQueueStatus}
isDesktopLayout={isDesktopLayout}
isResumingSaveWorldKey={platformBootstrap.isResumingSaveWorldKey}
platformError={
platformBootstrap.isLoadingPlatform
? null
: (platformBootstrapErrorForDisplay ??
sessionController.agentWorkspaceRestoreError)
}
dashboardError={
platformBootstrap.isLoadingDashboard
? null
: platformBootstrap.dashboardError
}
createTabContent={
selectionStage === 'creation-home'
? creationLandingContent
: creationStartContent
}
draftTabContent={draftHubContent}
onContinueGame={handleContinueGame}
onResumeSave={(entry) => {
if (
(entry.worldType ?? '').toLowerCase() === 'puzzle' ||
entry.worldKey.startsWith('puzzle:')
) {
void resumePuzzleSaveArchive(entry);
return;
}
void platformBootstrap.handleResumeSaveEntry(entry);
}}
onOpenCreateWorld={openCreationTypeModal}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={openPublicGalleryDetail}
onOpenChildMotionDemo={startChildMotionDemo}
onOpenBabyLoveDrawing={startBabyLoveDrawingRuntime}
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
recommendRuntimeContent={recommendRuntimeContent}
activeRecommendEntryKey={activeRecommendEntryKey}
isRecommendRuntimeReady={isActiveRecommendRuntimeReady}
isStartingRecommendEntry={
isStartingRecommendEntry ||
isBigFishBusy ||
(isPuzzleBusy &&
!(activeRecommendRuntimeKind === 'puzzle' && puzzleRun)) ||
(isPuzzleClearBusy &&
!(
activeRecommendRuntimeKind === 'puzzle-clear' &&
puzzleClearRun
)) ||
isMatch3DBusy ||
isSquareHoleBusy ||
isVisualNovelBusy ||
isWoodenFishBusy
}
recommendRuntimeError={activeRecommendRuntimeError}
onSelectNextRecommendEntry={(activeEntryKey) =>
selectAdjacentRecommendRuntimeEntry(1, activeEntryKey)
}
onSelectPreviousRecommendEntry={(activeEntryKey) =>
selectAdjacentRecommendRuntimeEntry(-1, activeEntryKey)
}
onLikeRecommendEntry={(entry) => {
likePublicWork(entry);
}}
onShareRecommendEntry={(entry) => {
openRecommendShareModal(entry);
}}
onRemixRecommendEntry={(entry) => {
remixPublicWork(entry);
}}
onOpenLibraryDetail={(entry) => {
runProtectedAction(() => {
void detailNavigation.openLibraryDetail(entry);
});
}}
onDeleteLibraryEntry={(entry) => {
handleDeleteLibraryEntry(entry);
}}
deletingLibraryEntryId={deletingCreationWorkId}
onSearchPublicCode={(keyword) => {
void handlePublicCodeSearch(keyword);
}}
isSearchingPublicCode={isSearchingPublicCode}
profilePlayStats={profilePlayStats}
isProfilePlayStatsOpen={isProfilePlayStatsOpen}
isProfilePlayStatsLoading={isProfilePlayStatsLoading}
profilePlayStatsError={profilePlayStatsError}
onCloseProfilePlayStats={() => {
setIsProfilePlayStatsOpen(false);
}}
onOpenPlayedWork={openPlayedWork}
onOpenFeedback={openProfileFeedback}
onOpenProjects={openProjectGallery}
onOpenProfileDashboardCard={(cardKey) => {
if (cardKey === 'playedWorks') {
openProfilePlayedWorks();
return;
}
if (platformBootstrap.dashboardError) {
void platformBootstrap.refreshProfileDashboard();
}
}}
onRechargeSuccess={platformBootstrap.refreshProfileDashboard}
/>
);
return (
<>
@@ -15181,16 +15344,22 @@ export function PlatformEntryFlowShellImpl({
>
<Suspense fallback={<LazyPanelFallback label="正在加载项目..." />}>
<ProjectGalleryView
onOpenProject={(projectId) => {
setSelectionStage('image-editor');
pushAppHistoryPath(
`/editor/canvas?projectid=${encodeURIComponent(projectId)}`,
);
}}
onOpenProject={openEditorProject}
/>
</Suspense>
</motion.div>
)}
{selectionStage === 'creation-home' && (
<motion.div
key="creation-home"
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden"
>
{platformHomeView}
</motion.div>
)}
{selectionStage === 'platform' && (
<motion.div
key="platform-home"
@@ -15199,123 +15368,7 @@ export function PlatformEntryFlowShellImpl({
exit={{ opacity: 0, y: -12 }}
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden"
>
<PlatformEntryHomeView
activeTab={platformBootstrap.platformTab}
onTabChange={platformBootstrap.setPlatformTab}
hasSavedGame={hasSavedGame}
savedSnapshot={savedSnapshot}
saveEntries={platformBootstrap.saveEntries}
saveError={platformBootstrap.saveError}
featuredEntries={featuredGalleryEntries}
latestEntries={latestGalleryEntries}
myEntries={platformBootstrap.savedCustomWorldEntries}
historyEntries={platformBootstrap.historyEntries}
profileDashboard={platformBootstrap.profileDashboard}
isLoadingPlatform={platformBootstrap.isLoadingPlatform}
isLoadingDashboard={platformBootstrap.isLoadingDashboard}
hasUnreadDraftUpdate={hasUnreadDraftUpdates}
profileTaskRefreshKey={profileTaskRefreshKey}
profileGenerationQueueStatus={profileExternalGenerationQueueStatus}
isDesktopLayout={isDesktopLayout}
isResumingSaveWorldKey={platformBootstrap.isResumingSaveWorldKey}
platformError={
platformBootstrap.isLoadingPlatform
? null
: (platformBootstrapErrorForDisplay ??
sessionController.agentWorkspaceRestoreError)
}
dashboardError={
platformBootstrap.isLoadingDashboard
? null
: platformBootstrap.dashboardError
}
createTabContent={creationStartContent}
draftTabContent={draftHubContent}
onContinueGame={handleContinueGame}
onResumeSave={(entry) => {
if (
(entry.worldType ?? '').toLowerCase() === 'puzzle' ||
entry.worldKey.startsWith('puzzle:')
) {
void resumePuzzleSaveArchive(entry);
return;
}
void platformBootstrap.handleResumeSaveEntry(entry);
}}
onOpenCreateWorld={openCreationTypePicker}
onOpenCreateTypePicker={openCreationTypePicker}
onOpenGalleryDetail={openPublicGalleryDetail}
onOpenChildMotionDemo={startChildMotionDemo}
onOpenBabyLoveDrawing={startBabyLoveDrawingRuntime}
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
recommendRuntimeContent={recommendRuntimeContent}
activeRecommendEntryKey={activeRecommendEntryKey}
isRecommendRuntimeReady={isActiveRecommendRuntimeReady}
isStartingRecommendEntry={
isStartingRecommendEntry ||
isBigFishBusy ||
(isPuzzleBusy &&
!(activeRecommendRuntimeKind === 'puzzle' && puzzleRun)) ||
(isPuzzleClearBusy &&
!(
activeRecommendRuntimeKind === 'puzzle-clear' &&
puzzleClearRun
)) ||
isMatch3DBusy ||
isSquareHoleBusy ||
isVisualNovelBusy ||
isWoodenFishBusy
}
recommendRuntimeError={activeRecommendRuntimeError}
onSelectNextRecommendEntry={(activeEntryKey) =>
selectAdjacentRecommendRuntimeEntry(1, activeEntryKey)
}
onSelectPreviousRecommendEntry={(activeEntryKey) =>
selectAdjacentRecommendRuntimeEntry(-1, activeEntryKey)
}
onLikeRecommendEntry={(entry) => {
likePublicWork(entry);
}}
onShareRecommendEntry={(entry) => {
openRecommendShareModal(entry);
}}
onRemixRecommendEntry={(entry) => {
remixPublicWork(entry);
}}
onOpenLibraryDetail={(entry) => {
runProtectedAction(() => {
void detailNavigation.openLibraryDetail(entry);
});
}}
onDeleteLibraryEntry={(entry) => {
handleDeleteLibraryEntry(entry);
}}
deletingLibraryEntryId={deletingCreationWorkId}
onSearchPublicCode={(keyword) => {
void handlePublicCodeSearch(keyword);
}}
isSearchingPublicCode={isSearchingPublicCode}
profilePlayStats={profilePlayStats}
isProfilePlayStatsOpen={isProfilePlayStatsOpen}
isProfilePlayStatsLoading={isProfilePlayStatsLoading}
profilePlayStatsError={profilePlayStatsError}
onCloseProfilePlayStats={() => {
setIsProfilePlayStatsOpen(false);
}}
onOpenPlayedWork={openPlayedWork}
onOpenFeedback={openProfileFeedback}
onOpenProjects={() => setSelectionStage('project')}
onOpenProfileDashboardCard={(cardKey) => {
if (cardKey === 'playedWorks') {
openProfilePlayedWorks();
return;
}
if (platformBootstrap.dashboardError) {
void platformBootstrap.refreshProfileDashboard();
}
}}
onRechargeSuccess={platformBootstrap.refreshProfileDashboard}
/>
{platformHomeView}
</motion.div>
)}
@@ -17713,6 +17766,22 @@ export function PlatformEntryFlowShellImpl({
overlayClassName={`platform-theme ${platformThemeClass} !items-center`}
panelClassName="platform-remap-surface rounded-[1.5rem]"
/>
{isCreationCommunityOpen ? (
<PlatformProfileReferralModal
panel="community"
center={null}
isLoading={false}
isSubmittingRedeem={false}
redeemCode=""
copyInviteState="idle"
error={null}
success={null}
onClose={() => setIsCreationCommunityOpen(false)}
onCopyInvite={() => undefined}
onRedeemCodeChange={() => undefined}
onSubmitRedeemCode={() => undefined}
/>
) : null}
<PlatformAcknowledgeStatusDialog
open={Boolean(workNotFoundRecoveryDialog)}
status="error"