refactor: 收口公开作品流聚合
This commit is contained in:
@@ -74,8 +74,8 @@
|
|||||||
## 2026-06-03 平台入口公开作品流身份规则收口
|
## 2026-06-03 平台入口公开作品流身份规则收口
|
||||||
|
|
||||||
- 背景:平台入口公开作品推荐流需要同时处理 RPG、拼图、抓大鹅、跳一跳、敲木鱼、视觉小说、Bark Battle、宝贝识物等卡片,公开作品身份、跨玩法去重、排序和推荐运行态 kind 判定曾放在 `PlatformEntryFlowShellImpl.tsx` 巨型实现里。
|
- 背景:平台入口公开作品推荐流需要同时处理 RPG、拼图、抓大鹅、跳一跳、敲木鱼、视觉小说、Bark Battle、宝贝识物等卡片,公开作品身份、跨玩法去重、排序和推荐运行态 kind 判定曾放在 `PlatformEntryFlowShellImpl.tsx` 巨型实现里。
|
||||||
- 决策:公开作品身份、排序规则、推荐 runtime 启动意图和 ready 判定统一收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`;入口壳层只调用该 Module 的 `getPlatformPublicGalleryEntryKey`、`getPlatformRecommendRuntimeKind`、`resolvePlatformRecommendRuntimeStartIntent`、`isPlatformRecommendRuntimeReadyForEntry`、`isSamePlatformPublicGalleryEntry` 和 `mergePlatformPublicGalleryEntries`。`edutainment` key 必须带 `templateId`,RPG 卡片回退为 `rpg`。推荐 runtime 启动 intent 只返回启动目标、`embedded` / `returnStage` 参数、阻断文案和错误落点;ready 判定只接布尔值与拼图 profile id,避免把各玩法 run snapshot 类型拖入 Module。壳层仍执行 request key、运行态 API、错误 setter 与 UI 状态。
|
- 决策:公开作品身份、排序规则、公开作品流聚合矩阵、推荐 runtime 启动意图和 ready 判定统一收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`;入口壳层只调用该 Module 的 `getPlatformPublicGalleryEntryKey`、`getPlatformRecommendRuntimeKind`、`buildPlatformPublicGalleryFeeds`、`resolvePlatformRecommendRuntimeStartIntent`、`isPlatformRecommendRuntimeReadyForEntry`、`isSamePlatformPublicGalleryEntry` 和 `mergePlatformPublicGalleryEntries`。`edutainment` key 必须带 `templateId`,RPG 卡片回退为 `rpg`。公开作品流聚合负责 featured / latest、玩法可见性 gate、汪汪声浪 works fallback 和首屏 `slice(0, 6)`;推荐 runtime 启动 intent 只返回启动目标、`embedded` / `returnStage` 参数、阻断文案和错误落点;ready 判定只接布尔值与拼图 profile id,避免把各玩法 run snapshot 类型拖入 Module。壳层仍执行 request key、运行态 API、错误 setter 与 UI 状态。
|
||||||
- 影响范围:平台入口推荐流、公开作品详情、推荐 runtime 启动、跨玩法公开作品合并,以及后续新增玩法的入口接入。
|
- 影响范围:平台入口推荐流、最新公开作品流、公开作品详情、推荐 runtime 启动、跨玩法公开作品合并,以及后续新增玩法的入口接入。
|
||||||
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
|
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
|
||||||
- 关联文档:`docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
前端 Server-Sent Events 客户端传输层收口到 `src/services/sseStream.ts`,事件边界、UTF-8 flush、JSON 解析跳过和提前取消约定见 [【前端架构】SSE客户端传输层收口约定-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91SSE%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BC%A0%E8%BE%93%E5%B1%82%E6%94%B6%E5%8F%A3%E7%BA%A6%E5%AE%9A-2026-06-03.md)。
|
前端 Server-Sent Events 客户端传输层收口到 `src/services/sseStream.ts`,事件边界、UTF-8 flush、JSON 解析跳过和提前取消约定见 [【前端架构】SSE客户端传输层收口约定-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91SSE%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BC%A0%E8%BE%93%E5%B1%82%E6%94%B6%E5%8F%A3%E7%BA%A6%E5%AE%9A-2026-06-03.md)。
|
||||||
|
|
||||||
平台入口公开作品身份、跨玩法去重、推荐运行态 kind 判定、推荐 runtime 启动意图、ready 判定和最新排序收口到 `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 判定、推荐 runtime 启动意图、ready 判定和最新排序收口到 `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/platformPublicWorkDetailFlow.ts`;抓大鹅公开详情映射与启动 / 编辑 Adapter 的素材归一仍归 `platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md](./technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md)。
|
统一作品详情页的玩法 kind、详情打开策略、自有作品动作模式、编辑 / 点赞 / 改造 / 启动意图和公开详情映射收口到 `src/components/platform-entry/platformPublicWorkDetailFlow.ts`;抓大鹅公开详情映射与启动 / 编辑 Adapter 的素材归一仍归 `platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md](./technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md)。
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,9 @@
|
|||||||
- `isPlatformRecommendRuntimeReadyForEntry(entry, state)`:用标量 ready state 判定当前推荐 runtime 是否已能承接该公开作品。
|
- `isPlatformRecommendRuntimeReadyForEntry(entry, state)`:用标量 ready state 判定当前推荐 runtime 是否已能承接该公开作品。
|
||||||
- `isSamePlatformPublicGalleryEntry(left, right)`:按公开作品身份比较。
|
- `isSamePlatformPublicGalleryEntry(left, right)`:按公开作品身份比较。
|
||||||
- `mergePlatformPublicGalleryEntries(rpgEntries, puzzleEntries)`:统一完成 RPG 与各玩法公开作品去重、覆盖和倒序排序。
|
- `mergePlatformPublicGalleryEntries(rpgEntries, puzzleEntries)`:统一完成 RPG 与各玩法公开作品去重、覆盖和倒序排序。
|
||||||
|
- `buildPlatformPublicGalleryFeeds(input)`:统一构造 `featuredEntries` 与 `latestEntries`,收口各玩法可见性 gate、mapper 矩阵、汪汪声浪 works fallback 和推荐首屏 `slice(0, 6)`。
|
||||||
|
|
||||||
入口壳层只调用这些函数,不再在 `PlatformEntryFlowShellImpl.tsx` 内手写公开作品身份、排序规则、推荐 runtime 启动能力矩阵和 ready 判定。ready 判定只接布尔值与拼图 profile id,不把各玩法 run snapshot 类型拖入 Module。
|
入口壳层只调用这些函数,不再在 `PlatformEntryFlowShellImpl.tsx` 内手写公开作品身份、排序规则、公开作品流聚合矩阵、推荐 runtime 启动能力矩阵和 ready 判定。ready 判定只接布尔值与拼图 profile id,不把各玩法 run snapshot 类型拖入 Module。
|
||||||
|
|
||||||
## 玩法身份规则
|
## 玩法身份规则
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
- 没有 `sourceType` 的 RPG 公开作品回退为 `rpg`。
|
- 没有 `sourceType` 的 RPG 公开作品回退为 `rpg`。
|
||||||
- 最终 key 格式为 `${kind}:${ownerUserId}:${profileId}`。
|
- 最终 key 格式为 `${kind}:${ownerUserId}:${profileId}`。
|
||||||
- 合并时后进入的相同 key 会覆盖先进入的卡片,然后按 `publishedAt ?? updatedAt` 新到旧排序;非法时间按 `0` 处理。
|
- 合并时后进入的相同 key 会覆盖先进入的卡片,然后按 `publishedAt ?? updatedAt` 新到旧排序;非法时间按 `0` 处理。
|
||||||
|
- 公开作品流聚合时,大鱼吃小鱼、宝贝识物和视觉小说必须受各自可见性 gate 控制;汪汪声浪优先用 gallery entries,gallery 为空时才从 works 中筛 `status === 'published'` 作为 fallback。
|
||||||
|
|
||||||
## 推荐 runtime 启动意图
|
## 推荐 runtime 启动意图
|
||||||
|
|
||||||
|
|||||||
@@ -361,12 +361,7 @@ import {
|
|||||||
isEdutainmentGalleryEntry,
|
isEdutainmentGalleryEntry,
|
||||||
mapBabyObjectMatchDraftToPlatformGalleryCard,
|
mapBabyObjectMatchDraftToPlatformGalleryCard,
|
||||||
mapBarkBattleWorkToPlatformGalleryCard,
|
mapBarkBattleWorkToPlatformGalleryCard,
|
||||||
mapBigFishWorkToPlatformGalleryCard,
|
|
||||||
mapJumpHopWorkToPlatformGalleryCard,
|
|
||||||
mapPuzzleWorkToPlatformGalleryCard,
|
mapPuzzleWorkToPlatformGalleryCard,
|
||||||
mapSquareHoleWorkToPlatformGalleryCard,
|
|
||||||
mapVisualNovelWorkToPlatformGalleryCard,
|
|
||||||
mapWoodenFishWorkToPlatformGalleryCard,
|
|
||||||
type PlatformPublicGalleryCard,
|
type PlatformPublicGalleryCard,
|
||||||
resolvePlatformPublicWorkCode,
|
resolvePlatformPublicWorkCode,
|
||||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||||
@@ -551,11 +546,11 @@ import {
|
|||||||
resolvePlatformPublicCodeSearchPlan,
|
resolvePlatformPublicCodeSearchPlan,
|
||||||
} from './platformPublicCodeSearchModel';
|
} from './platformPublicCodeSearchModel';
|
||||||
import {
|
import {
|
||||||
|
buildPlatformPublicGalleryFeeds,
|
||||||
getPlatformPublicGalleryEntryKey,
|
getPlatformPublicGalleryEntryKey,
|
||||||
getPlatformRecommendRuntimeKind,
|
getPlatformRecommendRuntimeKind,
|
||||||
isPlatformRecommendRuntimeReadyForEntry,
|
isPlatformRecommendRuntimeReadyForEntry,
|
||||||
isSamePlatformPublicGalleryEntry,
|
isSamePlatformPublicGalleryEntry,
|
||||||
mergePlatformPublicGalleryEntries,
|
|
||||||
type RecommendRuntimeKind,
|
type RecommendRuntimeKind,
|
||||||
resolvePlatformRecommendRuntimeStartIntent,
|
resolvePlatformRecommendRuntimeStartIntent,
|
||||||
} from './platformPublicGalleryFlow';
|
} from './platformPublicGalleryFlow';
|
||||||
@@ -2795,123 +2790,43 @@ export function PlatformEntryFlowShellImpl({
|
|||||||
agentResultPreview?.source,
|
agentResultPreview?.source,
|
||||||
);
|
);
|
||||||
}, [agentResultPreview]);
|
}, [agentResultPreview]);
|
||||||
const featuredGalleryEntries = useMemo(() => {
|
const publicGalleryFeeds = useMemo(
|
||||||
const bigFishPublicEntries = isBigFishCreationVisible
|
|
||||||
? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard)
|
|
||||||
: [];
|
|
||||||
const babyObjectMatchPublicEntries = isBabyObjectMatchVisible
|
|
||||||
? babyObjectMatchDrafts
|
|
||||||
.filter((draft) => draft.publicationStatus === 'published')
|
|
||||||
.map(mapBabyObjectMatchDraftToPlatformGalleryCard)
|
|
||||||
: [];
|
|
||||||
const match3dPublicEntries = match3dGalleryEntries.map(
|
|
||||||
mapMatch3DWorkToPublicWorkDetail,
|
|
||||||
);
|
|
||||||
const puzzlePublicEntries = puzzleGalleryEntries.map(
|
|
||||||
mapPuzzleWorkToPlatformGalleryCard,
|
|
||||||
);
|
|
||||||
const barkBattlePublicEntries =
|
|
||||||
barkBattleGalleryEntries.length > 0
|
|
||||||
? barkBattleGalleryEntries.map(mapBarkBattleWorkToPlatformGalleryCard)
|
|
||||||
: barkBattleWorks
|
|
||||||
.filter((work) => work.status === 'published')
|
|
||||||
.map(mapBarkBattleWorkToPlatformGalleryCard);
|
|
||||||
const squareHolePublicEntries = squareHoleGalleryEntries.map(
|
|
||||||
mapSquareHoleWorkToPlatformGalleryCard,
|
|
||||||
);
|
|
||||||
const jumpHopPublicEntries = jumpHopGalleryEntries.map(
|
|
||||||
mapJumpHopWorkToPlatformGalleryCard,
|
|
||||||
);
|
|
||||||
const woodenFishPublicEntries = woodenFishGalleryEntries.map(
|
|
||||||
mapWoodenFishWorkToPlatformGalleryCard,
|
|
||||||
);
|
|
||||||
const visualNovelPublicEntries = visualNovelGalleryEntries.map(
|
|
||||||
mapVisualNovelWorkToPlatformGalleryCard,
|
|
||||||
);
|
|
||||||
return mergePlatformPublicGalleryEntries(
|
|
||||||
platformBootstrap.publishedGalleryEntries,
|
|
||||||
[
|
|
||||||
...bigFishPublicEntries,
|
|
||||||
...match3dPublicEntries,
|
|
||||||
...puzzlePublicEntries,
|
|
||||||
...barkBattlePublicEntries,
|
|
||||||
...squareHolePublicEntries,
|
|
||||||
...jumpHopPublicEntries,
|
|
||||||
...woodenFishPublicEntries,
|
|
||||||
...(isVisualNovelCreationOpen ? visualNovelPublicEntries : []),
|
|
||||||
...babyObjectMatchPublicEntries,
|
|
||||||
],
|
|
||||||
).slice(0, 6);
|
|
||||||
}, [
|
|
||||||
babyObjectMatchDrafts,
|
|
||||||
isBigFishCreationVisible,
|
|
||||||
isBabyObjectMatchVisible,
|
|
||||||
isVisualNovelCreationOpen,
|
|
||||||
bigFishGalleryEntries,
|
|
||||||
jumpHopGalleryEntries,
|
|
||||||
match3dGalleryEntries,
|
|
||||||
platformBootstrap.publishedGalleryEntries,
|
|
||||||
puzzleGalleryEntries,
|
|
||||||
barkBattleGalleryEntries,
|
|
||||||
barkBattleWorks,
|
|
||||||
squareHoleGalleryEntries,
|
|
||||||
visualNovelGalleryEntries,
|
|
||||||
woodenFishGalleryEntries,
|
|
||||||
]);
|
|
||||||
const latestGalleryEntries = useMemo(
|
|
||||||
() =>
|
() =>
|
||||||
mergePlatformPublicGalleryEntries(
|
buildPlatformPublicGalleryFeeds({
|
||||||
platformBootstrap.publishedGalleryEntries,
|
rpgEntries: platformBootstrap.publishedGalleryEntries,
|
||||||
[
|
bigFishEntries: bigFishGalleryEntries,
|
||||||
...(isBigFishCreationVisible
|
match3dEntries: match3dGalleryEntries,
|
||||||
? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard)
|
puzzleEntries: puzzleGalleryEntries,
|
||||||
: []),
|
barkBattleGalleryEntries,
|
||||||
...match3dGalleryEntries.map(mapMatch3DWorkToPublicWorkDetail),
|
barkBattleWorks,
|
||||||
...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard),
|
jumpHopEntries: jumpHopGalleryEntries,
|
||||||
...barkBattleGalleryEntries.map(
|
woodenFishEntries: woodenFishGalleryEntries,
|
||||||
mapBarkBattleWorkToPlatformGalleryCard,
|
squareHoleEntries: squareHoleGalleryEntries,
|
||||||
),
|
visualNovelEntries: visualNovelGalleryEntries,
|
||||||
...jumpHopGalleryEntries.map(mapJumpHopWorkToPlatformGalleryCard),
|
babyObjectMatchDrafts,
|
||||||
...(barkBattleGalleryEntries.length === 0
|
isBigFishCreationVisible,
|
||||||
? barkBattleWorks
|
isBabyObjectMatchVisible,
|
||||||
.filter((work) => work.status === 'published')
|
isVisualNovelCreationOpen,
|
||||||
.map(mapBarkBattleWorkToPlatformGalleryCard)
|
}),
|
||||||
: []),
|
|
||||||
...woodenFishGalleryEntries.map(
|
|
||||||
mapWoodenFishWorkToPlatformGalleryCard,
|
|
||||||
),
|
|
||||||
...squareHoleGalleryEntries.map(
|
|
||||||
mapSquareHoleWorkToPlatformGalleryCard,
|
|
||||||
),
|
|
||||||
...(isVisualNovelCreationOpen
|
|
||||||
? visualNovelGalleryEntries.map(
|
|
||||||
mapVisualNovelWorkToPlatformGalleryCard,
|
|
||||||
)
|
|
||||||
: []),
|
|
||||||
...(isBabyObjectMatchVisible
|
|
||||||
? babyObjectMatchDrafts
|
|
||||||
.filter((draft) => draft.publicationStatus === 'published')
|
|
||||||
.map(mapBabyObjectMatchDraftToPlatformGalleryCard)
|
|
||||||
: []),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
[
|
[
|
||||||
babyObjectMatchDrafts,
|
babyObjectMatchDrafts,
|
||||||
|
barkBattleGalleryEntries,
|
||||||
|
barkBattleWorks,
|
||||||
|
bigFishGalleryEntries,
|
||||||
isBabyObjectMatchVisible,
|
isBabyObjectMatchVisible,
|
||||||
isBigFishCreationVisible,
|
isBigFishCreationVisible,
|
||||||
isVisualNovelCreationOpen,
|
isVisualNovelCreationOpen,
|
||||||
bigFishGalleryEntries,
|
|
||||||
jumpHopGalleryEntries,
|
jumpHopGalleryEntries,
|
||||||
match3dGalleryEntries,
|
match3dGalleryEntries,
|
||||||
platformBootstrap.publishedGalleryEntries,
|
platformBootstrap.publishedGalleryEntries,
|
||||||
puzzleGalleryEntries,
|
puzzleGalleryEntries,
|
||||||
squareHoleGalleryEntries,
|
squareHoleGalleryEntries,
|
||||||
visualNovelGalleryEntries,
|
visualNovelGalleryEntries,
|
||||||
barkBattleGalleryEntries,
|
|
||||||
barkBattleWorks,
|
|
||||||
woodenFishGalleryEntries,
|
woodenFishGalleryEntries,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
const { featuredEntries: featuredGalleryEntries, latestEntries: latestGalleryEntries } =
|
||||||
|
publicGalleryFeeds;
|
||||||
const recommendRuntimeEntries = useMemo(
|
const recommendRuntimeEntries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
buildPlatformRecommendFeedEntries(
|
buildPlatformRecommendFeedEntries(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { expect, test } from 'vitest';
|
import { expect, test } from 'vitest';
|
||||||
|
|
||||||
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
||||||
|
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||||
|
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
||||||
|
import type { JumpHopGalleryCardResponse } from '../../../packages/shared/src/contracts/jumpHop';
|
||||||
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
||||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||||
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
||||||
@@ -10,6 +13,7 @@ import {
|
|||||||
type PlatformPublicGalleryCard,
|
type PlatformPublicGalleryCard,
|
||||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||||
import {
|
import {
|
||||||
|
buildPlatformPublicGalleryFeeds,
|
||||||
getPlatformPublicGalleryEntryKey,
|
getPlatformPublicGalleryEntryKey,
|
||||||
getPlatformPublicGalleryEntryTime,
|
getPlatformPublicGalleryEntryTime,
|
||||||
getPlatformRecommendRuntimeKind,
|
getPlatformRecommendRuntimeKind,
|
||||||
@@ -59,6 +63,85 @@ function buildRpgEntry(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBigFishWork(
|
||||||
|
overrides: Partial<BigFishWorkSummary> = {},
|
||||||
|
): BigFishWorkSummary {
|
||||||
|
return {
|
||||||
|
workId: 'big-fish-work',
|
||||||
|
sourceSessionId: 'big-fish-session',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '玩家',
|
||||||
|
title: '大鱼吃小鱼',
|
||||||
|
subtitle: '海湾',
|
||||||
|
summary: '一路长大。',
|
||||||
|
coverImageSrc: '/big-fish-cover.png',
|
||||||
|
status: 'published',
|
||||||
|
updatedAt: '2026-06-01T02:00:00.000Z',
|
||||||
|
publishedAt: '2026-06-01T02:00:00.000Z',
|
||||||
|
publishReady: true,
|
||||||
|
levelCount: 1,
|
||||||
|
levelMainImageReadyCount: 1,
|
||||||
|
levelMotionReadyCount: 1,
|
||||||
|
backgroundReady: true,
|
||||||
|
playCount: 1,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBabyObjectMatchDraft(
|
||||||
|
overrides: Partial<BabyObjectMatchDraft> = {},
|
||||||
|
): BabyObjectMatchDraft {
|
||||||
|
const itemAsset = {
|
||||||
|
itemId: 'item-a',
|
||||||
|
itemName: '苹果',
|
||||||
|
imageSrc: '/apple.png',
|
||||||
|
assetObjectId: null,
|
||||||
|
generationProvider: 'placeholder' as const,
|
||||||
|
prompt: '苹果',
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
draftId: 'baby-draft',
|
||||||
|
profileId: 'baby-profile',
|
||||||
|
templateId: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID,
|
||||||
|
templateName: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME,
|
||||||
|
workTitle: '宝贝识物',
|
||||||
|
workDescription: '认识水果。',
|
||||||
|
itemNames: ['苹果', '香蕉'],
|
||||||
|
itemAssets: [itemAsset, { ...itemAsset, itemId: 'item-b', itemName: '香蕉' }],
|
||||||
|
visualPackage: null,
|
||||||
|
themeTags: ['寓教于乐'],
|
||||||
|
publicationStatus: 'published',
|
||||||
|
createdAt: '2026-06-01T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-06-01T03:00:00.000Z',
|
||||||
|
publishedAt: '2026-06-01T03:00:00.000Z',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildJumpHopEntry(
|
||||||
|
overrides: Partial<JumpHopGalleryCardResponse> = {},
|
||||||
|
): JumpHopGalleryCardResponse {
|
||||||
|
return {
|
||||||
|
publicWorkCode: 'JH-JUMP',
|
||||||
|
workId: 'jump-hop-work',
|
||||||
|
profileId: 'jump-hop-profile',
|
||||||
|
ownerUserId: 'user-1',
|
||||||
|
authorDisplayName: '玩家',
|
||||||
|
workTitle: '跳一跳',
|
||||||
|
workDescription: '一路向前。',
|
||||||
|
coverImageSrc: '/jump-hop-cover.png',
|
||||||
|
themeTags: ['跳一跳'],
|
||||||
|
difficulty: 'standard',
|
||||||
|
stylePreset: 'minimal-blocks',
|
||||||
|
publicationStatus: 'published',
|
||||||
|
playCount: 1,
|
||||||
|
updatedAt: '2026-06-04T00:00:00.000Z',
|
||||||
|
publishedAt: '2026-06-04T00:00:00.000Z',
|
||||||
|
generationStatus: 'ready',
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildTypedEntry(
|
function buildTypedEntry(
|
||||||
sourceType: PlatformGallerySourceType,
|
sourceType: PlatformGallerySourceType,
|
||||||
overrides: TypedPlatformPublicGalleryCardOverrides = {},
|
overrides: TypedPlatformPublicGalleryCardOverrides = {},
|
||||||
@@ -570,3 +653,144 @@ test('platform public gallery flow merges duplicate identities and sorts newest
|
|||||||
expect(merged[0]?.worldName).toBe('新版 RPG');
|
expect(merged[0]?.worldName).toBe('新版 RPG');
|
||||||
expect(getPlatformPublicGalleryEntryTime(invalidTimeEntry)).toBe(0);
|
expect(getPlatformPublicGalleryEntryTime(invalidTimeEntry)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('platform public gallery flow builds feeds with visibility gates and bark battle fallback', () => {
|
||||||
|
const hiddenBigFish = buildBigFishWork({
|
||||||
|
workId: 'hidden-big-fish',
|
||||||
|
sourceSessionId: 'hidden-big-fish-session',
|
||||||
|
});
|
||||||
|
const hiddenBabyDraft = buildBabyObjectMatchDraft({
|
||||||
|
profileId: 'hidden-baby',
|
||||||
|
});
|
||||||
|
const publishedBarkFallback = buildBarkBattleWork({
|
||||||
|
workId: 'fallback-bark',
|
||||||
|
publishedAt: '2026-06-04T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-06-04T00:00:00.000Z',
|
||||||
|
});
|
||||||
|
const draftBarkFallback = buildBarkBattleWork({
|
||||||
|
workId: 'draft-bark',
|
||||||
|
status: 'draft',
|
||||||
|
});
|
||||||
|
|
||||||
|
const hiddenFeeds = buildPlatformPublicGalleryFeeds({
|
||||||
|
rpgEntries: [
|
||||||
|
buildRpgEntry({
|
||||||
|
profileId: 'rpg-visible',
|
||||||
|
publishedAt: '2026-06-01T00:00:00.000Z',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
bigFishEntries: [hiddenBigFish],
|
||||||
|
match3dEntries: [],
|
||||||
|
puzzleEntries: [],
|
||||||
|
barkBattleGalleryEntries: [],
|
||||||
|
barkBattleWorks: [draftBarkFallback, publishedBarkFallback],
|
||||||
|
jumpHopEntries: [],
|
||||||
|
woodenFishEntries: [],
|
||||||
|
squareHoleEntries: [],
|
||||||
|
visualNovelEntries: [],
|
||||||
|
babyObjectMatchDrafts: [hiddenBabyDraft],
|
||||||
|
isBigFishCreationVisible: false,
|
||||||
|
isBabyObjectMatchVisible: false,
|
||||||
|
isVisualNovelCreationOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
hiddenFeeds.latestEntries.map((entry) =>
|
||||||
|
'sourceType' in entry ? entry.sourceType : 'rpg',
|
||||||
|
),
|
||||||
|
).toEqual(['bark-battle', 'rpg']);
|
||||||
|
expect(hiddenFeeds.latestEntries[0]?.profileId).toBe('fallback-bark');
|
||||||
|
|
||||||
|
const visibleFeeds = buildPlatformPublicGalleryFeeds({
|
||||||
|
rpgEntries: [],
|
||||||
|
bigFishEntries: [hiddenBigFish],
|
||||||
|
match3dEntries: [],
|
||||||
|
puzzleEntries: [],
|
||||||
|
barkBattleGalleryEntries: [
|
||||||
|
buildBarkBattleWork({
|
||||||
|
workId: 'gallery-bark',
|
||||||
|
publishedAt: '2026-06-05T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-06-05T00:00:00.000Z',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
barkBattleWorks: [publishedBarkFallback],
|
||||||
|
jumpHopEntries: [],
|
||||||
|
woodenFishEntries: [],
|
||||||
|
squareHoleEntries: [],
|
||||||
|
visualNovelEntries: [],
|
||||||
|
babyObjectMatchDrafts: [hiddenBabyDraft],
|
||||||
|
isBigFishCreationVisible: true,
|
||||||
|
isBabyObjectMatchVisible: true,
|
||||||
|
isVisualNovelCreationOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(visibleFeeds.latestEntries.map((entry) => entry.profileId)).toEqual([
|
||||||
|
'gallery-bark',
|
||||||
|
'hidden-baby',
|
||||||
|
'hidden-big-fish-session',
|
||||||
|
]);
|
||||||
|
expect(visibleFeeds.featuredEntries).toEqual(
|
||||||
|
visibleFeeds.latestEntries.slice(0, 6),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform public gallery flow preserves feed tie order and featured slice', () => {
|
||||||
|
const sameTime = '2026-06-04T00:00:00.000Z';
|
||||||
|
const tieFeeds = buildPlatformPublicGalleryFeeds({
|
||||||
|
rpgEntries: [],
|
||||||
|
bigFishEntries: [],
|
||||||
|
match3dEntries: [],
|
||||||
|
puzzleEntries: [],
|
||||||
|
barkBattleGalleryEntries: [],
|
||||||
|
barkBattleWorks: [
|
||||||
|
buildBarkBattleWork({
|
||||||
|
workId: 'fallback-bark',
|
||||||
|
publishedAt: sameTime,
|
||||||
|
updatedAt: sameTime,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
jumpHopEntries: [
|
||||||
|
buildJumpHopEntry({
|
||||||
|
profileId: 'jump-hop',
|
||||||
|
publishedAt: sameTime,
|
||||||
|
updatedAt: sameTime,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
woodenFishEntries: [],
|
||||||
|
squareHoleEntries: [],
|
||||||
|
visualNovelEntries: [],
|
||||||
|
babyObjectMatchDrafts: [],
|
||||||
|
isBigFishCreationVisible: false,
|
||||||
|
isBabyObjectMatchVisible: false,
|
||||||
|
isVisualNovelCreationOpen: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tieFeeds.latestEntries.map((entry) => entry.profileId)).toEqual([
|
||||||
|
'jump-hop',
|
||||||
|
'fallback-bark',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const sliceFeeds = buildPlatformPublicGalleryFeeds({
|
||||||
|
rpgEntries: Array.from({ length: 7 }, (_, index) =>
|
||||||
|
buildRpgEntry({
|
||||||
|
profileId: `rpg-${index}`,
|
||||||
|
publishedAt: `2026-06-0${index + 1}T00:00:00.000Z`,
|
||||||
|
updatedAt: `2026-06-0${index + 1}T00:00:00.000Z`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
bigFishEntries: [],
|
||||||
|
match3dEntries: [],
|
||||||
|
puzzleEntries: [],
|
||||||
|
barkBattleGalleryEntries: [],
|
||||||
|
barkBattleWorks: [],
|
||||||
|
jumpHopEntries: [],
|
||||||
|
woodenFishEntries: [],
|
||||||
|
squareHoleEntries: [],
|
||||||
|
visualNovelEntries: [],
|
||||||
|
babyObjectMatchDrafts: [],
|
||||||
|
isBigFishCreationVisible: false,
|
||||||
|
isBabyObjectMatchVisible: false,
|
||||||
|
isVisualNovelCreationOpen: false,
|
||||||
|
});
|
||||||
|
expect(sliceFeeds.featuredEntries).toHaveLength(6);
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
|
||||||
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
|
||||||
|
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
|
||||||
|
import type { JumpHopGalleryCardResponse } from '../../../packages/shared/src/contracts/jumpHop';
|
||||||
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
import type { Match3DWorkSummary } from '../../../packages/shared/src/contracts/match3dWorks';
|
||||||
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
import type { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary';
|
||||||
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime';
|
||||||
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
|
||||||
|
import type { VisualNovelWorkSummary } from '../../../packages/shared/src/contracts/visualNovel';
|
||||||
|
import type { WoodenFishGalleryCardResponse } from '../../../packages/shared/src/contracts/woodenFish';
|
||||||
import {
|
import {
|
||||||
isBarkBattleGalleryEntry,
|
isBarkBattleGalleryEntry,
|
||||||
isBigFishGalleryEntry,
|
isBigFishGalleryEntry,
|
||||||
@@ -14,8 +18,17 @@ import {
|
|||||||
isSquareHoleGalleryEntry,
|
isSquareHoleGalleryEntry,
|
||||||
isVisualNovelGalleryEntry,
|
isVisualNovelGalleryEntry,
|
||||||
isWoodenFishGalleryEntry,
|
isWoodenFishGalleryEntry,
|
||||||
|
mapBabyObjectMatchDraftToPlatformGalleryCard,
|
||||||
|
mapBarkBattleWorkToPlatformGalleryCard,
|
||||||
|
mapBigFishWorkToPlatformGalleryCard,
|
||||||
|
mapJumpHopWorkToPlatformGalleryCard,
|
||||||
|
mapPuzzleWorkToPlatformGalleryCard,
|
||||||
|
mapSquareHoleWorkToPlatformGalleryCard,
|
||||||
|
mapVisualNovelWorkToPlatformGalleryCard,
|
||||||
|
mapWoodenFishWorkToPlatformGalleryCard,
|
||||||
type PlatformPublicGalleryCard,
|
type PlatformPublicGalleryCard,
|
||||||
} from '../rpg-entry/rpgEntryWorldPresentation';
|
} from '../rpg-entry/rpgEntryWorldPresentation';
|
||||||
|
import { mapMatch3DWorkToPublicWorkDetail } from './platformMatch3DRuntimeProfile';
|
||||||
import {
|
import {
|
||||||
mapBarkBattlePublicDetailToWorkSummary,
|
mapBarkBattlePublicDetailToWorkSummary,
|
||||||
mapPublicWorkDetailToBigFishWork,
|
mapPublicWorkDetailToBigFishWork,
|
||||||
@@ -127,6 +140,28 @@ export type PlatformRecommendRuntimeReadyState = {
|
|||||||
puzzleRunCurrentLevelProfileId?: string | null;
|
puzzleRunCurrentLevelProfileId?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PlatformPublicGalleryFeedsInput = {
|
||||||
|
rpgEntries: readonly CustomWorldGalleryCard[];
|
||||||
|
bigFishEntries: readonly BigFishWorkSummary[];
|
||||||
|
match3dEntries: readonly Match3DWorkSummary[];
|
||||||
|
puzzleEntries: readonly PuzzleWorkSummary[];
|
||||||
|
barkBattleGalleryEntries: readonly BarkBattleWorkSummary[];
|
||||||
|
barkBattleWorks: readonly BarkBattleWorkSummary[];
|
||||||
|
jumpHopEntries: readonly JumpHopGalleryCardResponse[];
|
||||||
|
woodenFishEntries: readonly WoodenFishGalleryCardResponse[];
|
||||||
|
squareHoleEntries: readonly SquareHoleWorkSummary[];
|
||||||
|
visualNovelEntries: readonly VisualNovelWorkSummary[];
|
||||||
|
babyObjectMatchDrafts: readonly BabyObjectMatchDraft[];
|
||||||
|
isBigFishCreationVisible: boolean;
|
||||||
|
isBabyObjectMatchVisible: boolean;
|
||||||
|
isVisualNovelCreationOpen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PlatformPublicGalleryFeeds = {
|
||||||
|
featuredEntries: PlatformPublicGalleryCard[];
|
||||||
|
latestEntries: PlatformPublicGalleryCard[];
|
||||||
|
};
|
||||||
|
|
||||||
export function getPlatformPublicGalleryEntryTime(
|
export function getPlatformPublicGalleryEntryTime(
|
||||||
entry: PlatformPublicGalleryCard,
|
entry: PlatformPublicGalleryCard,
|
||||||
) {
|
) {
|
||||||
@@ -399,8 +434,8 @@ export function isSamePlatformPublicGalleryEntry(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function mergePlatformPublicGalleryEntries(
|
export function mergePlatformPublicGalleryEntries(
|
||||||
rpgEntries: CustomWorldGalleryCard[],
|
rpgEntries: readonly CustomWorldGalleryCard[],
|
||||||
puzzleEntries: PlatformPublicGalleryCard[],
|
puzzleEntries: readonly PlatformPublicGalleryCard[],
|
||||||
) {
|
) {
|
||||||
const entryMap = new Map<string, PlatformPublicGalleryCard>();
|
const entryMap = new Map<string, PlatformPublicGalleryCard>();
|
||||||
|
|
||||||
@@ -414,3 +449,58 @@ export function mergePlatformPublicGalleryEntries(
|
|||||||
getPlatformPublicGalleryEntryTime(left),
|
getPlatformPublicGalleryEntryTime(left),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildPlatformPublicGalleryFeeds(
|
||||||
|
input: PlatformPublicGalleryFeedsInput,
|
||||||
|
): PlatformPublicGalleryFeeds {
|
||||||
|
const bigFishEntries = input.isBigFishCreationVisible
|
||||||
|
? input.bigFishEntries.map(mapBigFishWorkToPlatformGalleryCard)
|
||||||
|
: [];
|
||||||
|
const babyObjectMatchEntries = input.isBabyObjectMatchVisible
|
||||||
|
? input.babyObjectMatchDrafts
|
||||||
|
.filter((draft) => draft.publicationStatus === 'published')
|
||||||
|
.map(mapBabyObjectMatchDraftToPlatformGalleryCard)
|
||||||
|
: [];
|
||||||
|
const barkBattleGalleryEntries = input.barkBattleGalleryEntries.map(
|
||||||
|
mapBarkBattleWorkToPlatformGalleryCard,
|
||||||
|
);
|
||||||
|
const barkBattleFallbackEntries =
|
||||||
|
input.barkBattleGalleryEntries.length === 0
|
||||||
|
? input.barkBattleWorks
|
||||||
|
.filter((work) => work.status === 'published')
|
||||||
|
.map(mapBarkBattleWorkToPlatformGalleryCard)
|
||||||
|
: [];
|
||||||
|
const visualNovelEntries = input.isVisualNovelCreationOpen
|
||||||
|
? input.visualNovelEntries.map(mapVisualNovelWorkToPlatformGalleryCard)
|
||||||
|
: [];
|
||||||
|
const latestEntries = mergePlatformPublicGalleryEntries(input.rpgEntries, [
|
||||||
|
...bigFishEntries,
|
||||||
|
...input.match3dEntries.map(mapMatch3DWorkToPublicWorkDetail),
|
||||||
|
...input.puzzleEntries.map(mapPuzzleWorkToPlatformGalleryCard),
|
||||||
|
...barkBattleGalleryEntries,
|
||||||
|
...input.jumpHopEntries.map(mapJumpHopWorkToPlatformGalleryCard),
|
||||||
|
...barkBattleFallbackEntries,
|
||||||
|
...input.woodenFishEntries.map(mapWoodenFishWorkToPlatformGalleryCard),
|
||||||
|
...input.squareHoleEntries.map(mapSquareHoleWorkToPlatformGalleryCard),
|
||||||
|
...visualNovelEntries,
|
||||||
|
...babyObjectMatchEntries,
|
||||||
|
]);
|
||||||
|
const featuredEntries = mergePlatformPublicGalleryEntries(input.rpgEntries, [
|
||||||
|
...bigFishEntries,
|
||||||
|
...input.match3dEntries.map(mapMatch3DWorkToPublicWorkDetail),
|
||||||
|
...input.puzzleEntries.map(mapPuzzleWorkToPlatformGalleryCard),
|
||||||
|
...(barkBattleGalleryEntries.length > 0
|
||||||
|
? barkBattleGalleryEntries
|
||||||
|
: barkBattleFallbackEntries),
|
||||||
|
...input.squareHoleEntries.map(mapSquareHoleWorkToPlatformGalleryCard),
|
||||||
|
...input.jumpHopEntries.map(mapJumpHopWorkToPlatformGalleryCard),
|
||||||
|
...input.woodenFishEntries.map(mapWoodenFishWorkToPlatformGalleryCard),
|
||||||
|
...visualNovelEntries,
|
||||||
|
...babyObjectMatchEntries,
|
||||||
|
]).slice(0, 6);
|
||||||
|
|
||||||
|
return {
|
||||||
|
featuredEntries,
|
||||||
|
latestEntries,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user