refactor: 收口创作作品架Hub接口

This commit is contained in:
2026-06-03 21:36:06 +08:00
parent c238ef9b40
commit 3efbb6882c
8 changed files with 306 additions and 319 deletions

View File

@@ -1289,6 +1289,14 @@
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts``npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`、针对新 Module 执行 ESLint、`npm run typecheck``npm run check:encoding` - 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts``npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`、针对新 Module 执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md` - 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`
## 2026-06-03 Creation Hub Shelf Items Interface 收口
- 背景:`creationWorkShelf.ts` 已把各玩法作品映射为 `CreationWorkShelfItem.actions`,但 `CustomWorldCreationHub.tsx` 的生产 Interface 仍接收 raw items 与 open/delete/claim 回调列阵,新增玩法时 Hub props 继续膨胀。
- 决策:`CustomWorldCreationHub.tsx` 生产 Interface 收敛为 `shelfItems: CreationWorkShelfItem[]` 与少量 UI 状态;`PlatformEntryFlowShellImpl.tsx` 在外层作为 Adapter 调用 `buildCreationWorkShelfItems` 注入完整 actionsHub 测试改经 `CustomWorldCreationHub.testAdapter.tsx` 把旧 fixture 转成 shelf items不让测试继续依赖旧浅 Interface。
- 影响范围:创作 Tab / 草稿 Tab 作品架、RPG / 拼图 / 抓大鹅 / 方洞 / 跳一跳 / 敲木鱼 / 视觉小说 / Bark Battle / 宝贝识物作品打开、删除、生成态与拼图奖励领取。
- 验证方式:`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts``npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx``npm run test -- src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、相关 FlowShell creation hub 交互片段、针对变更文件执行 ESLint、`npm run typecheck``npm run check:encoding`
- 关联文档:`docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md`
## 2026-06-03 Creation URL State Model 收口 ## 2026-06-03 Creation URL State Model 收口
- 背景:平台壳内散落各玩法创作恢复 URL 的 `sessionId` / `profileId` / `draftId` / `workId` 组装、空值归一化、拼图 runtime query key 与拼图稳定身份互推,导致刷新恢复规则缺少稳定测试面。 - 背景:平台壳内散落各玩法创作恢复 URL 的 `sessionId` / `profileId` / `draftId` / `workId` 组装、空值归一化、拼图 runtime query key 与拼图稳定身份互推,导致刷新恢复规则缺少稳定测试面。

View File

@@ -41,7 +41,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
平台入口公开作品身份、跨玩法去重、推荐运行态 kind 判定和最新排序收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%B9%B3%E5%8F%B0%E5%85%A5%E5%8F%A3PublicGalleryFlowModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 平台入口公开作品身份、跨玩法去重、推荐运行态 kind 判定和最新排序收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%B9%B3%E5%8F%B0%E5%85%A5%E5%8F%A3PublicGalleryFlowModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
创作中心作品架打开动作由 `CreationWorkShelfItem.actions.open` 统一承载Hub 不再按玩法 `kind` 分发,规则见 [【前端架构】WorkShelfModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91WorkShelfModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 创作中心作品架打开动作由 `CreationWorkShelfItem.actions.open` 统一承载,生产 Hub 只接收 `CreationWorkShelfItem[]` 与 UI 状态,不再接收各玩法 raw items 和回调列阵,规则见 [【前端架构】WorkShelfModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91WorkShelfModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
平台入口创作生成通知、pending 作品架占位、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 平台入口创作生成通知、pending 作品架占位、失败覆盖、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。

View File

@@ -2,7 +2,7 @@
## 背景 ## 背景
创作中心作品架需要同时展示 RPG、拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、Bark Battle 和宝贝识物等作品。`creationWorkShelf.ts` 已经统一了卡片标题、摘要、封面、发布码、分享路径、指标、生成态和动作 Adapter`CustomWorldCreationHub.tsx` 仍在点击作品卡时按玩法 `kind` 再写一遍打开逻辑,导致调用方仍须理解每种玩法 创作中心作品架需要同时展示 RPG、拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、Bark Battle 和宝贝识物等作品。`creationWorkShelf.ts` 已经统一了卡片标题、摘要、封面、发布码、分享路径、指标、生成态和动作 Adapter。后续深化前`CustomWorldCreationHub.tsx` 虽已不再按玩法 `kind` 分发点击,但生产调用仍向 Hub 传入多玩法 raw items 与 open/delete/claim 回调列阵Hub Interface 仍偏 shallow
## 决策 ## 决策
@@ -10,20 +10,25 @@
`buildCreationWorkShelfItemsFromSources` 是作品架 source registry 的正式 **Interface**。每个玩法提供一个 `CreationWorkShelfSourceAdapter`Adapter 负责把玩法数据、删除权限、打开动作和特殊动作映射为 `CreationWorkShelfItem[]`。registry 统一执行 flatten、运行态覆盖、持久化生成态兜底和更新时间排序。 `buildCreationWorkShelfItemsFromSources` 是作品架 source registry 的正式 **Interface**。每个玩法提供一个 `CreationWorkShelfSourceAdapter`Adapter 负责把玩法数据、删除权限、打开动作和特殊动作映射为 `CreationWorkShelfItem[]`。registry 统一执行 flatten、运行态覆盖、持久化生成态兜底和更新时间排序。
`CustomWorldCreationHub.tsx` 的生产 **Interface** 收敛为 `shelfItems: CreationWorkShelfItem[]``loading/error/onRetry/mode/recentWorkItems/onOpenShelfItem/deletingWorkId/claimingPuzzleProfileId` 等 UI 状态。平台壳 `PlatformEntryFlowShellImpl.tsx` 在外层作为 Adapter 调用 `buildCreationWorkShelfItems` 注入完整 open/delete/claim actions 后再传给 HubHub 不再接触各玩法 raw items、删除权限布尔值或玩法专属打开回调。
测试文件通过 `CustomWorldCreationHub.testAdapter.tsx` 把旧 fixture 转成 `shelfItems`,避免测试继续强化生产 Hub 的旧浅 Interface。
此决策让 `creationWorkShelf.ts`**Module** 更 deep 此决策让 `creationWorkShelf.ts`**Module** 更 deep
- **Implementation**:玩法差异、草稿 / 已发布分支、profileId 进入方式和回调绑定都留在 Work Shelf Adapter 内。 - **Implementation**:玩法差异、草稿 / 已发布分支、profileId 进入方式和回调绑定都留在 Work Shelf Adapter 内。
- **Interface**Hub 只需要 `CreationWorkShelfItem`;后续调用方也可只传 `CreationWorkShelfSourceAdapter[]`,不需要知道每种玩法的打开规则、状态覆盖和排序规则。 - **Interface**Hub 只需要 `CreationWorkShelfItem[]`;后续调用方也可只传 `CreationWorkShelfSourceAdapter[]`,不需要知道每种玩法的打开规则、状态覆盖和排序规则。
- **Leverage**:新增玩法时只补 shelf item 映射与 AdapterHub 不再新增 switch 分支。 - **Leverage**:新增玩法时只补 shelf item 映射与 AdapterHub 不再新增 switch 分支。
- **Locality**作品架点击行为、source flatten、运行态覆盖和排序错误集中在 `creationWorkShelf.ts` 与其测试里定位。 - **Locality**作品架点击行为、source flatten、运行态覆盖和排序错误集中在 `creationWorkShelf.ts` 与其测试里定位。
## 后续深化 ## 后续深化
`buildCreationWorkShelfItems` 仍保留旧长参数兼容入口,但其 **Implementation** 已改为组装 `CreationWorkShelfSourceAdapter[]` 后复用 `buildCreationWorkShelfItemsFromSources`。下一步可让 Hub / 平台壳逐步直接传入 source adapters从而减少按玩法平铺的参数数量。删除、刷新和直达恢复也可沿同一 seam 收口 `buildCreationWorkShelfItems` 仍保留旧长参数兼容入口,但其 **Implementation** 已改为组装 `CreationWorkShelfSourceAdapter[]` 后复用 `buildCreationWorkShelfItemsFromSources`。下一步可让平台壳直接传入 source adapters从而继续减少按玩法平铺的参数数量。`deletingWorkId``claimingPuzzleProfileId` 仍是 Hub UI 状态,可后续下沉到 shelf item/action busy state
## 验证 ## 验证
- `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx` - `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "creation hub published work can open detail view before deleting from detail page|creation hub published work enters existing detail view|creation hub published work card reveals delete action after card action reveal"`
- `npm run typecheck` - `npm run typecheck`
- `npm run check:encoding` - `npm run check:encoding`
- 针对变更文件执行 ESLint - 针对变更文件执行 ESLint

View File

@@ -10,7 +10,7 @@ import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contract
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks'; import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { CreationEntryConfig } from '../../services/creationEntryConfigService'; import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes'; import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
import { CustomWorldCreationHub } from './CustomWorldCreationHub'; import { CustomWorldCreationHub } from './CustomWorldCreationHub.testAdapter';
const noopCreateType = () => {}; const noopCreateType = () => {};

View File

@@ -4,7 +4,7 @@ import { expect, test } from 'vitest';
import type { CreationEntryConfig } from '../../services/creationEntryConfigService'; import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes'; import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
import { buildCreationWorkShelfItems } from './creationWorkShelf'; import { buildCreationWorkShelfItems } from './creationWorkShelf';
import { CustomWorldCreationHub } from './CustomWorldCreationHub'; import { CustomWorldCreationHub } from './CustomWorldCreationHub.testAdapter';
const noopCreateType = () => {}; const noopCreateType = () => {};
const DAY_MS = 24 * 60 * 60 * 1000; const DAY_MS = 24 * 60 * 60 * 1000;

View File

@@ -0,0 +1,128 @@
import { isPlatformCreationTypeVisible } from '../platform-entry/platformEntryCreationTypes';
import {
buildCreationWorkShelfItems,
type CreationWorkShelfItem,
} from './creationWorkShelf';
import {
CustomWorldCreationHub as CustomWorldCreationHubView,
} from './CustomWorldCreationHub';
type ShelfBuilderParams = Parameters<typeof buildCreationWorkShelfItems>[0];
type HubViewProps = Parameters<typeof CustomWorldCreationHubView>[0];
type LegacyCustomWorldCreationHubProps = Omit<HubViewProps, 'shelfItems'> &
Partial<
Omit<ShelfBuilderParams, 'rpgItems' | 'bigFishItems' | 'puzzleItems'>
> & {
shelfItems?: CreationWorkShelfItem[];
items?: ShelfBuilderParams['rpgItems'];
bigFishItems?: ShelfBuilderParams['bigFishItems'];
puzzleItems?: ShelfBuilderParams['puzzleItems'];
onOpenDraft?: ShelfBuilderParams['onOpenRpgDraft'];
onEnterPublished?: ShelfBuilderParams['onEnterRpgPublished'];
onDeletePublished?: ShelfBuilderParams['onDeleteRpg'] | null;
getWorkState?: ShelfBuilderParams['getItemState'];
};
/** 测试用 Adapter旧 fixture 先转成 shelfItems生产 Hub Interface 保持窄面。 */
export function CustomWorldCreationHub({
shelfItems,
items = [],
rpgLibraryEntries = [],
bigFishItems = [],
match3dItems = [],
squareHoleItems = [],
jumpHopItems = [],
woodenFishItems = [],
puzzleItems = [],
babyObjectMatchItems = [],
barkBattleItems = [],
visualNovelItems = [],
onOpenDraft,
onEnterPublished,
onDeletePublished = null,
onOpenBigFishDetail,
onDeleteBigFish,
onOpenMatch3DDetail,
onDeleteMatch3D,
onOpenSquareHoleDetail,
onDeleteSquareHole,
onOpenJumpHopDetail,
onDeleteJumpHop,
onOpenWoodenFishDetail,
onDeleteWoodenFish,
onOpenPuzzleDetail,
onDeletePuzzle,
onClaimPuzzlePointIncentive,
onOpenBabyObjectMatchDetail,
onDeleteBabyObjectMatch,
onOpenBarkBattleDetail,
onDeleteBarkBattle,
onOpenVisualNovelDetail,
onDeleteVisualNovel,
getItemState,
getWorkState,
creationTypes,
...props
}: LegacyCustomWorldCreationHubProps) {
const isSquareHoleCreationVisible = isPlatformCreationTypeVisible(
creationTypes,
'square-hole',
);
const resolvedShelfItems =
shelfItems ??
buildCreationWorkShelfItems({
rpgItems: items,
rpgLibraryEntries,
bigFishItems,
match3dItems,
squareHoleItems: isSquareHoleCreationVisible ? squareHoleItems : [],
jumpHopItems,
woodenFishItems,
puzzleItems,
babyObjectMatchItems,
barkBattleItems,
visualNovelItems,
canDeleteRpg: Boolean(onDeletePublished),
canDeleteBigFish: Boolean(onDeleteBigFish),
canDeleteMatch3D: Boolean(onDeleteMatch3D),
canDeleteSquareHole: Boolean(onDeleteSquareHole),
canDeleteJumpHop: Boolean(onDeleteJumpHop),
canDeleteWoodenFish: Boolean(onDeleteWoodenFish),
canDeletePuzzle: Boolean(onDeletePuzzle),
canDeleteBabyObjectMatch: Boolean(onDeleteBabyObjectMatch),
canDeleteBarkBattle: Boolean(onDeleteBarkBattle),
canDeleteVisualNovel: Boolean(onDeleteVisualNovel),
onOpenRpgDraft: onOpenDraft,
onEnterRpgPublished: onEnterPublished,
onDeleteRpg: onDeletePublished ?? undefined,
onOpenBigFishDetail,
onDeleteBigFish,
onOpenMatch3DDetail,
onDeleteMatch3D,
onOpenSquareHoleDetail,
onDeleteSquareHole,
onOpenJumpHopDetail,
onDeleteJumpHop,
onOpenWoodenFishDetail,
onDeleteWoodenFish,
onOpenPuzzleDetail,
onDeletePuzzle,
onClaimPuzzlePointIncentive,
onOpenBabyObjectMatchDetail,
onDeleteBabyObjectMatch,
onOpenBarkBattleDetail,
onDeleteBarkBattle,
onOpenVisualNovelDetail,
onDeleteVisualNovel,
getItemState: getItemState ?? getWorkState,
});
return (
<CustomWorldCreationHubView
{...props}
creationTypes={creationTypes}
shelfItems={resolvedShelfItems}
/>
);
}

View File

@@ -1,28 +1,13 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type { JumpHopWorkSummaryResponse } from '../../../packages/shared/src/contracts/jumpHop';
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
import type { CustomWorldLibraryEntry } from '../../../packages/shared/src/contracts/runtime';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
import type { WoodenFishWorkSummaryResponse } from '../../../packages/shared/src/contracts/woodenFish';
import type { CreationEntryConfig } from '../../services/creationEntryConfigService'; import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
import type { CustomWorldProfile } from '../../types';
import type { import type {
PlatformCreationTypeCard, PlatformCreationTypeCard,
PlatformCreationTypeId, PlatformCreationTypeId,
} from '../platform-entry/platformEntryCreationTypes'; } from '../platform-entry/platformEntryCreationTypes';
import { isPlatformCreationTypeVisible } from '../platform-entry/platformEntryCreationTypes';
import { import {
buildCreationWorkShelfItems,
type CreationWorkShelfItem, type CreationWorkShelfItem,
type CreationWorkShelfMetricId, type CreationWorkShelfMetricId,
type CreationWorkShelfRuntimeState,
getCreationWorkShelfItemTime, getCreationWorkShelfItemTime,
} from './creationWorkShelf'; } from './creationWorkShelf';
import { import {
@@ -47,7 +32,7 @@ type WorkMetricSnapshot = Record<
>; >;
type CustomWorldCreationHubProps = { type CustomWorldCreationHubProps = {
items: CustomWorldWorkSummary[]; shelfItems: CreationWorkShelfItem[];
loading: boolean; loading: boolean;
error: string | null; error: string | null;
onRetry: () => void; onRetry: () => void;
@@ -55,45 +40,8 @@ type CustomWorldCreationHubProps = {
entryConfig: CreationEntryConfig; entryConfig: CreationEntryConfig;
creationTypes: readonly PlatformCreationTypeCard[]; creationTypes: readonly PlatformCreationTypeCard[];
onCreateType: (type: PlatformCreationTypeId) => void; onCreateType: (type: PlatformCreationTypeId) => void;
onOpenDraft: (item: CustomWorldWorkSummary) => void;
onEnterPublished: (profileId: string) => void;
onDeletePublished?: ((item: CustomWorldWorkSummary) => void) | null;
deletingWorkId?: string | null; deletingWorkId?: string | null;
rpgLibraryEntries?: CustomWorldLibraryEntry<CustomWorldProfile>[];
bigFishItems?: BigFishWorkSummary[];
onOpenBigFishDetail?: (item: BigFishWorkSummary) => void;
onDeleteBigFish?: ((item: BigFishWorkSummary) => void) | null;
match3dItems?: Match3DWorkSummary[];
onOpenMatch3DDetail?: (item: Match3DWorkSummary) => void;
onDeleteMatch3D?: ((item: Match3DWorkSummary) => void) | null;
squareHoleItems?: SquareHoleWorkSummary[];
onOpenSquareHoleDetail?: (item: SquareHoleWorkSummary) => void;
onDeleteSquareHole?: ((item: SquareHoleWorkSummary) => void) | null;
jumpHopItems?: JumpHopWorkSummaryResponse[];
onOpenJumpHopDetail?: (item: JumpHopWorkSummaryResponse) => void;
onDeleteJumpHop?: ((item: JumpHopWorkSummaryResponse) => void) | null;
woodenFishItems?: WoodenFishWorkSummaryResponse[];
onOpenWoodenFishDetail?:
| ((item: WoodenFishWorkSummaryResponse) => void)
| null;
onDeleteWoodenFish?: ((item: WoodenFishWorkSummaryResponse) => void) | null;
puzzleItems?: PuzzleWorkSummary[];
onOpenPuzzleDetail?: (item: PuzzleWorkSummary) => void;
onDeletePuzzle?: ((item: PuzzleWorkSummary) => void) | null;
onClaimPuzzlePointIncentive?: ((item: PuzzleWorkSummary) => void) | null;
claimingPuzzleProfileId?: string | null; claimingPuzzleProfileId?: string | null;
babyObjectMatchItems?: BabyObjectMatchDraft[];
onOpenBabyObjectMatchDetail?: ((item: BabyObjectMatchDraft) => void) | null;
onDeleteBabyObjectMatch?: ((item: BabyObjectMatchDraft) => void) | null;
barkBattleItems?: BarkBattleWorkSummary[];
onOpenBarkBattleDetail?: ((item: BarkBattleWorkSummary) => void) | null;
onDeleteBarkBattle?: ((item: BarkBattleWorkSummary) => void) | null;
visualNovelItems?: VisualNovelWorkSummary[];
onOpenVisualNovelDetail?: ((item: VisualNovelWorkSummary) => void) | null;
onDeleteVisualNovel?: ((item: VisualNovelWorkSummary) => void) | null;
getWorkState?: (
item: CreationWorkShelfItem,
) => CreationWorkShelfRuntimeState | null;
onOpenShelfItem?: (item: CreationWorkShelfItem) => void; onOpenShelfItem?: (item: CreationWorkShelfItem) => void;
// 中文注释:底部加号入口可传入后端作品架摘要,用于推导最近使用过的模板。 // 中文注释:底部加号入口可传入后端作品架摘要,用于推导最近使用过的模板。
recentWorkItems?: CreationWorkShelfItem[]; recentWorkItems?: CreationWorkShelfItem[];
@@ -165,7 +113,7 @@ function writeWorkMetricSnapshot(items: CreationWorkShelfItem[]) {
/** 渲染底部加号创作入口页与草稿作品架,最近创作复用最近使用过的模板入口。 */ /** 渲染底部加号创作入口页与草稿作品架,最近创作复用最近使用过的模板入口。 */
export function CustomWorldCreationHub({ export function CustomWorldCreationHub({
items, shelfItems,
loading, loading,
error, error,
onRetry, onRetry,
@@ -173,138 +121,14 @@ export function CustomWorldCreationHub({
entryConfig, entryConfig,
creationTypes, creationTypes,
onCreateType, onCreateType,
onOpenDraft,
onEnterPublished,
onDeletePublished = null,
deletingWorkId = null, deletingWorkId = null,
rpgLibraryEntries = [],
bigFishItems = [],
onOpenBigFishDetail,
onDeleteBigFish = null,
match3dItems = [],
onOpenMatch3DDetail,
onDeleteMatch3D = null,
squareHoleItems = [],
onOpenSquareHoleDetail,
onDeleteSquareHole = null,
jumpHopItems = [],
onOpenJumpHopDetail,
onDeleteJumpHop = null,
woodenFishItems = [],
onOpenWoodenFishDetail = null,
onDeleteWoodenFish = null,
puzzleItems = [],
onOpenPuzzleDetail,
onDeletePuzzle = null,
onClaimPuzzlePointIncentive = null,
claimingPuzzleProfileId = null, claimingPuzzleProfileId = null,
babyObjectMatchItems = [],
onOpenBabyObjectMatchDetail = null,
onDeleteBabyObjectMatch = null,
barkBattleItems = [],
onOpenBarkBattleDetail = null,
onDeleteBarkBattle = null,
visualNovelItems = [],
onOpenVisualNovelDetail = null,
onDeleteVisualNovel = null,
getWorkState,
onOpenShelfItem, onOpenShelfItem,
recentWorkItems: recentWorkSourceItems, recentWorkItems: recentWorkSourceItems,
mode = 'full', mode = 'full',
}: CustomWorldCreationHubProps) { }: CustomWorldCreationHubProps) {
const [activeFilter, setActiveFilter] = const [activeFilter, setActiveFilter] =
useState<CustomWorldWorkFilter>('all'); useState<CustomWorldWorkFilter>('all');
const isSquareHoleCreationVisible = isPlatformCreationTypeVisible(
creationTypes,
'square-hole',
);
const shelfItems = useMemo(
() =>
buildCreationWorkShelfItems({
rpgItems: items,
rpgLibraryEntries,
bigFishItems,
match3dItems,
squareHoleItems: isSquareHoleCreationVisible ? squareHoleItems : [],
jumpHopItems,
woodenFishItems,
puzzleItems,
babyObjectMatchItems,
barkBattleItems,
visualNovelItems,
canDeleteRpg: Boolean(onDeletePublished),
canDeleteBigFish: Boolean(onDeleteBigFish),
canDeleteMatch3D: Boolean(onDeleteMatch3D),
canDeleteSquareHole:
isSquareHoleCreationVisible && Boolean(onDeleteSquareHole),
canDeleteJumpHop: Boolean(onDeleteJumpHop),
canDeleteWoodenFish: Boolean(onDeleteWoodenFish),
canDeletePuzzle: Boolean(onDeletePuzzle),
canDeleteBabyObjectMatch: Boolean(onDeleteBabyObjectMatch),
canDeleteBarkBattle: Boolean(onDeleteBarkBattle),
canDeleteVisualNovel: Boolean(onDeleteVisualNovel),
onOpenRpgDraft: onOpenDraft,
onEnterRpgPublished: onEnterPublished,
onDeleteRpg: onDeletePublished ?? undefined,
onOpenBigFishDetail,
onDeleteBigFish: onDeleteBigFish ?? undefined,
onOpenMatch3DDetail,
onDeleteMatch3D: onDeleteMatch3D ?? undefined,
onOpenSquareHoleDetail,
onDeleteSquareHole: onDeleteSquareHole ?? undefined,
onOpenJumpHopDetail: onOpenJumpHopDetail ?? undefined,
onDeleteJumpHop: onDeleteJumpHop ?? undefined,
onOpenWoodenFishDetail: onOpenWoodenFishDetail ?? undefined,
onDeleteWoodenFish: onDeleteWoodenFish ?? undefined,
onOpenPuzzleDetail,
onDeletePuzzle: onDeletePuzzle ?? undefined,
onClaimPuzzlePointIncentive: onClaimPuzzlePointIncentive ?? undefined,
onOpenBabyObjectMatchDetail: onOpenBabyObjectMatchDetail ?? undefined,
onDeleteBabyObjectMatch: onDeleteBabyObjectMatch ?? undefined,
onOpenBarkBattleDetail: onOpenBarkBattleDetail ?? undefined,
onDeleteBarkBattle: onDeleteBarkBattle ?? undefined,
onOpenVisualNovelDetail: onOpenVisualNovelDetail ?? undefined,
onDeleteVisualNovel: onDeleteVisualNovel ?? undefined,
getItemState: getWorkState,
}),
[
bigFishItems,
isSquareHoleCreationVisible,
babyObjectMatchItems,
barkBattleItems,
items,
match3dItems,
squareHoleItems,
onDeleteBigFish,
onDeleteMatch3D,
onDeleteSquareHole,
onDeletePublished,
onDeletePuzzle,
onDeleteBabyObjectMatch,
onDeleteBarkBattle,
onDeleteVisualNovel,
onDeleteJumpHop,
onDeleteWoodenFish,
onClaimPuzzlePointIncentive,
onOpenBigFishDetail,
onOpenDraft,
onOpenMatch3DDetail,
onOpenBabyObjectMatchDetail,
onOpenBarkBattleDetail,
onOpenPuzzleDetail,
onOpenSquareHoleDetail,
onOpenVisualNovelDetail,
onOpenWoodenFishDetail,
onEnterPublished,
getWorkState,
puzzleItems,
rpgLibraryEntries,
onOpenJumpHopDetail,
jumpHopItems,
woodenFishItems,
visualNovelItems,
],
);
const [metricSnapshot] = useState<WorkMetricSnapshot>(() => const [metricSnapshot] = useState<WorkMetricSnapshot>(() =>
readWorkMetricSnapshot(), readWorkMetricSnapshot(),
); );

View File

@@ -14733,6 +14733,161 @@ export function PlatformEntryFlowShellImpl({
selectionStage, selectionStage,
]); ]);
const creationHubShelfItems = useMemo(
() =>
buildCreationWorkShelfItems({
rpgItems: creationHubItems,
rpgLibraryEntries: platformBootstrap.savedCustomWorldEntries,
bigFishItems: isBigFishCreationVisible ? bigFishShelfItems : [],
jumpHopItems: isJumpHopCreationVisible ? jumpHopShelfItems : [],
woodenFishItems: woodenFishShelfItems,
match3dItems: match3dShelfItems,
squareHoleItems: isSquareHoleCreationVisible ? squareHoleShelfItems : [],
puzzleItems: puzzleShelfItems,
babyObjectMatchItems: isBabyObjectMatchVisible
? babyObjectMatchDrafts
: [],
barkBattleItems: barkBattleShelfItems,
visualNovelItems: visualNovelShelfItems,
canDeleteRpg: true,
canDeleteBigFish: isBigFishCreationVisible,
canDeleteMatch3D: true,
canDeleteSquareHole: isSquareHoleCreationVisible,
canDeletePuzzle: true,
canDeleteBabyObjectMatch: isBabyObjectMatchVisible,
canDeleteVisualNovel: true,
onOpenRpgDraft: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void detailNavigation.handleOpenCreationWork(item);
});
},
onEnterRpgPublished: (profileId) => {
runProtectedAction(() => {
const matchedWork = creationHubItems.find(
(entry) => entry.profileId === profileId,
);
if (!matchedWork) {
return;
}
markCreationFlowReturnToDraftShelf();
void detailNavigation.handleOpenCreationWork(matchedWork);
});
},
onDeleteRpg: handleDeletePublishedWork,
onOpenBigFishDetail: isBigFishCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openBigFishDraft(item);
});
}
: undefined,
onDeleteBigFish: isBigFishCreationVisible
? handleDeleteBigFishWork
: undefined,
onOpenJumpHopDetail: isJumpHopCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openJumpHopDraft(item);
});
}
: undefined,
onOpenWoodenFishDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openWoodenFishDraft(item);
});
},
onOpenMatch3DDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openMatch3DDraft(item);
});
},
onDeleteMatch3D: handleDeleteMatch3DWork,
onOpenSquareHoleDetail: isSquareHoleCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openSquareHoleDraft(item);
});
}
: undefined,
onDeleteSquareHole: isSquareHoleCreationVisible
? handleDeleteSquareHoleWork
: undefined,
onOpenPuzzleDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openPuzzleDraft(item);
});
},
onDeletePuzzle: handleDeletePuzzleWork,
onClaimPuzzlePointIncentive: handleClaimPuzzlePointIncentive,
onOpenBabyObjectMatchDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
openBabyObjectMatchDraft(item);
});
},
onDeleteBabyObjectMatch: handleDeleteBabyObjectMatchWork,
onOpenBarkBattleDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
openBarkBattleDraft(item);
});
},
onOpenVisualNovelDetail: (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openVisualNovelDraft(item);
});
},
onDeleteVisualNovel: handleDeleteVisualNovelWork,
getItemState: getCreationWorkShelfState,
}),
[
barkBattleShelfItems,
babyObjectMatchDrafts,
bigFishShelfItems,
creationHubItems,
detailNavigation,
getCreationWorkShelfState,
handleClaimPuzzlePointIncentive,
handleDeleteBabyObjectMatchWork,
handleDeleteBigFishWork,
handleDeleteMatch3DWork,
handleDeletePublishedWork,
handleDeletePuzzleWork,
handleDeleteSquareHoleWork,
handleDeleteVisualNovelWork,
isBabyObjectMatchVisible,
isBigFishCreationVisible,
isJumpHopCreationVisible,
isSquareHoleCreationVisible,
jumpHopShelfItems,
markCreationFlowReturnToDraftShelf,
match3dShelfItems,
openBabyObjectMatchDraft,
openBarkBattleDraft,
openBigFishDraft,
openJumpHopDraft,
openMatch3DDraft,
openPuzzleDraft,
openSquareHoleDraft,
openVisualNovelDraft,
openWoodenFishDraft,
platformBootstrap.savedCustomWorldEntries,
puzzleShelfItems,
runProtectedAction,
squareHoleShelfItems,
visualNovelShelfItems,
woodenFishShelfItems,
],
);
// 中文注释:最近创作必须由真实作品架/后端草稿摘要决定,不能混入本地生成中占位。 // 中文注释:最近创作必须由真实作品架/后端草稿摘要决定,不能混入本地生成中占位。
const backendRecentCreationShelfItems = useMemo( const backendRecentCreationShelfItems = useMemo(
() => () =>
@@ -14781,7 +14936,7 @@ export function PlatformEntryFlowShellImpl({
{creationEntryConfig ? ( {creationEntryConfig ? (
<CustomWorldCreationHub <CustomWorldCreationHub
mode={mode} mode={mode}
items={creationHubItems} shelfItems={creationHubShelfItems}
loading={ loading={
platformBootstrap.isLoadingPlatform || platformBootstrap.isLoadingPlatform ||
isBigFishLoadingLibrary || isBigFishLoadingLibrary ||
@@ -14871,144 +15026,11 @@ export function PlatformEntryFlowShellImpl({
creationTypes={creationEntryTypes} creationTypes={creationEntryTypes}
recentWorkItems={backendRecentCreationShelfItems} recentWorkItems={backendRecentCreationShelfItems}
onCreateType={handleCreationHubCreateType} onCreateType={handleCreationHubCreateType}
getWorkState={getCreationWorkShelfState}
onOpenShelfItem={(item) => { onOpenShelfItem={(item) => {
markDraftNoticeSeen(getGenerationNoticeShelfKeys(item)); markDraftNoticeSeen(getGenerationNoticeShelfKeys(item));
}} }}
onOpenDraft={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void detailNavigation.handleOpenCreationWork(item);
});
}}
onEnterPublished={(profileId) => {
runProtectedAction(() => {
const matchedWork = creationHubItems.find(
(entry) => entry.profileId === profileId,
);
if (!matchedWork) {
return;
}
markCreationFlowReturnToDraftShelf();
void detailNavigation.handleOpenCreationWork(matchedWork);
});
}}
onDeletePublished={(item) => {
handleDeletePublishedWork(item);
}}
deletingWorkId={deletingCreationWorkId} deletingWorkId={deletingCreationWorkId}
rpgLibraryEntries={platformBootstrap.savedCustomWorldEntries}
bigFishItems={isBigFishCreationVisible ? bigFishShelfItems : []}
jumpHopItems={isJumpHopCreationVisible ? jumpHopShelfItems : []}
woodenFishItems={woodenFishShelfItems}
onOpenBigFishDetail={
isBigFishCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openBigFishDraft(item);
});
}
: undefined
}
onOpenJumpHopDetail={
isJumpHopCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openJumpHopDraft(item);
});
}
: undefined
}
onDeleteBigFish={
isBigFishCreationVisible
? (item) => {
handleDeleteBigFishWork(item);
}
: null
}
onDeleteJumpHop={null}
onOpenWoodenFishDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openWoodenFishDraft(item);
});
}}
onDeleteWoodenFish={null}
match3dItems={match3dShelfItems}
onOpenMatch3DDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openMatch3DDraft(item);
});
}}
onDeleteMatch3D={(item) => {
handleDeleteMatch3DWork(item);
}}
squareHoleItems={
isSquareHoleCreationVisible ? squareHoleShelfItems : []
}
onOpenSquareHoleDetail={
isSquareHoleCreationVisible
? (item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openSquareHoleDraft(item);
});
}
: undefined
}
onDeleteSquareHole={
isSquareHoleCreationVisible
? (item) => {
handleDeleteSquareHoleWork(item);
}
: null
}
puzzleItems={puzzleShelfItems}
onOpenPuzzleDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openPuzzleDraft(item);
});
}}
onDeletePuzzle={(item) => {
handleDeletePuzzleWork(item);
}}
onClaimPuzzlePointIncentive={(item) => {
handleClaimPuzzlePointIncentive(item);
}}
claimingPuzzleProfileId={claimingPuzzlePointIncentiveProfileId} claimingPuzzleProfileId={claimingPuzzlePointIncentiveProfileId}
babyObjectMatchItems={
isBabyObjectMatchVisible ? babyObjectMatchDrafts : []
}
onOpenBabyObjectMatchDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
openBabyObjectMatchDraft(item);
});
}}
onDeleteBabyObjectMatch={(item) => {
handleDeleteBabyObjectMatchWork(item);
}}
barkBattleItems={barkBattleShelfItems}
onOpenBarkBattleDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
openBarkBattleDraft(item);
});
}}
visualNovelItems={visualNovelShelfItems}
onOpenVisualNovelDetail={(item) => {
runProtectedAction(() => {
markCreationFlowReturnToDraftShelf();
void openVisualNovelDraft(item);
});
}}
onDeleteVisualNovel={(item) => {
handleDeleteVisualNovelWork(item);
}}
/> />
) : null} ) : null}
</Suspense> </Suspense>