diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index 2316b2f3..2a6e4dd8 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -74,8 +74,8 @@ ## 2026-06-03 平台入口公开作品流身份规则收口 - 背景:平台入口公开作品推荐流需要同时处理 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 启动、跨玩法公开作品合并,以及后续新增玩法的入口接入。 +- 决策:公开作品身份、排序规则、公开作品流聚合矩阵、推荐 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 启动、跨玩法公开作品合并,以及后续新增玩法的入口接入。 - 验证方式:`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`。 diff --git a/docs/README.md b/docs/README.md index 26416215..728645d3 100644 --- a/docs/README.md +++ b/docs/README.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)。 -平台入口公开作品身份、跨玩法去重、推荐运行态 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)。 diff --git a/docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md b/docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md index 4fdebc80..8d109725 100644 --- a/docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md +++ b/docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md @@ -14,8 +14,9 @@ - `isPlatformRecommendRuntimeReadyForEntry(entry, state)`:用标量 ready state 判定当前推荐 runtime 是否已能承接该公开作品。 - `isSamePlatformPublicGalleryEntry(left, right)`:按公开作品身份比较。 - `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`。 - 最终 key 格式为 `${kind}:${ownerUserId}:${profileId}`。 - 合并时后进入的相同 key 会覆盖先进入的卡片,然后按 `publishedAt ?? updatedAt` 新到旧排序;非法时间按 `0` 处理。 +- 公开作品流聚合时,大鱼吃小鱼、宝贝识物和视觉小说必须受各自可见性 gate 控制;汪汪声浪优先用 gallery entries,gallery 为空时才从 works 中筛 `status === 'published'` 作为 fallback。 ## 推荐 runtime 启动意图 diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 57581040..89f42158 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -361,12 +361,7 @@ import { isEdutainmentGalleryEntry, mapBabyObjectMatchDraftToPlatformGalleryCard, mapBarkBattleWorkToPlatformGalleryCard, - mapBigFishWorkToPlatformGalleryCard, - mapJumpHopWorkToPlatformGalleryCard, mapPuzzleWorkToPlatformGalleryCard, - mapSquareHoleWorkToPlatformGalleryCard, - mapVisualNovelWorkToPlatformGalleryCard, - mapWoodenFishWorkToPlatformGalleryCard, type PlatformPublicGalleryCard, resolvePlatformPublicWorkCode, } from '../rpg-entry/rpgEntryWorldPresentation'; @@ -551,11 +546,11 @@ import { resolvePlatformPublicCodeSearchPlan, } from './platformPublicCodeSearchModel'; import { + buildPlatformPublicGalleryFeeds, getPlatformPublicGalleryEntryKey, getPlatformRecommendRuntimeKind, isPlatformRecommendRuntimeReadyForEntry, isSamePlatformPublicGalleryEntry, - mergePlatformPublicGalleryEntries, type RecommendRuntimeKind, resolvePlatformRecommendRuntimeStartIntent, } from './platformPublicGalleryFlow'; @@ -2795,123 +2790,43 @@ export function PlatformEntryFlowShellImpl({ agentResultPreview?.source, ); }, [agentResultPreview]); - const featuredGalleryEntries = 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( + const publicGalleryFeeds = useMemo( () => - mergePlatformPublicGalleryEntries( - platformBootstrap.publishedGalleryEntries, - [ - ...(isBigFishCreationVisible - ? bigFishGalleryEntries.map(mapBigFishWorkToPlatformGalleryCard) - : []), - ...match3dGalleryEntries.map(mapMatch3DWorkToPublicWorkDetail), - ...puzzleGalleryEntries.map(mapPuzzleWorkToPlatformGalleryCard), - ...barkBattleGalleryEntries.map( - mapBarkBattleWorkToPlatformGalleryCard, - ), - ...jumpHopGalleryEntries.map(mapJumpHopWorkToPlatformGalleryCard), - ...(barkBattleGalleryEntries.length === 0 - ? barkBattleWorks - .filter((work) => work.status === 'published') - .map(mapBarkBattleWorkToPlatformGalleryCard) - : []), - ...woodenFishGalleryEntries.map( - mapWoodenFishWorkToPlatformGalleryCard, - ), - ...squareHoleGalleryEntries.map( - mapSquareHoleWorkToPlatformGalleryCard, - ), - ...(isVisualNovelCreationOpen - ? visualNovelGalleryEntries.map( - mapVisualNovelWorkToPlatformGalleryCard, - ) - : []), - ...(isBabyObjectMatchVisible - ? babyObjectMatchDrafts - .filter((draft) => draft.publicationStatus === 'published') - .map(mapBabyObjectMatchDraftToPlatformGalleryCard) - : []), - ], - ), + buildPlatformPublicGalleryFeeds({ + rpgEntries: platformBootstrap.publishedGalleryEntries, + bigFishEntries: bigFishGalleryEntries, + match3dEntries: match3dGalleryEntries, + puzzleEntries: puzzleGalleryEntries, + barkBattleGalleryEntries, + barkBattleWorks, + jumpHopEntries: jumpHopGalleryEntries, + woodenFishEntries: woodenFishGalleryEntries, + squareHoleEntries: squareHoleGalleryEntries, + visualNovelEntries: visualNovelGalleryEntries, + babyObjectMatchDrafts, + isBigFishCreationVisible, + isBabyObjectMatchVisible, + isVisualNovelCreationOpen, + }), [ babyObjectMatchDrafts, + barkBattleGalleryEntries, + barkBattleWorks, + bigFishGalleryEntries, isBabyObjectMatchVisible, isBigFishCreationVisible, isVisualNovelCreationOpen, - bigFishGalleryEntries, jumpHopGalleryEntries, match3dGalleryEntries, platformBootstrap.publishedGalleryEntries, puzzleGalleryEntries, squareHoleGalleryEntries, visualNovelGalleryEntries, - barkBattleGalleryEntries, - barkBattleWorks, woodenFishGalleryEntries, ], ); + const { featuredEntries: featuredGalleryEntries, latestEntries: latestGalleryEntries } = + publicGalleryFeeds; const recommendRuntimeEntries = useMemo( () => buildPlatformRecommendFeedEntries( diff --git a/src/components/platform-entry/platformPublicGalleryFlow.test.ts b/src/components/platform-entry/platformPublicGalleryFlow.test.ts index 5c53d3e6..c3c1f3bc 100644 --- a/src/components/platform-entry/platformPublicGalleryFlow.test.ts +++ b/src/components/platform-entry/platformPublicGalleryFlow.test.ts @@ -1,6 +1,9 @@ import { expect, test } from 'vitest'; 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 { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime'; @@ -10,6 +13,7 @@ import { type PlatformPublicGalleryCard, } from '../rpg-entry/rpgEntryWorldPresentation'; import { + buildPlatformPublicGalleryFeeds, getPlatformPublicGalleryEntryKey, getPlatformPublicGalleryEntryTime, getPlatformRecommendRuntimeKind, @@ -59,6 +63,85 @@ function buildRpgEntry( }; } +function buildBigFishWork( + overrides: Partial = {}, +): 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 { + 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 { + 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( sourceType: PlatformGallerySourceType, overrides: TypedPlatformPublicGalleryCardOverrides = {}, @@ -570,3 +653,144 @@ test('platform public gallery flow merges duplicate identities and sorts newest expect(merged[0]?.worldName).toBe('新版 RPG'); 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); +}); diff --git a/src/components/platform-entry/platformPublicGalleryFlow.ts b/src/components/platform-entry/platformPublicGalleryFlow.ts index 914d64bb..c1475d5a 100644 --- a/src/components/platform-entry/platformPublicGalleryFlow.ts +++ b/src/components/platform-entry/platformPublicGalleryFlow.ts @@ -1,9 +1,13 @@ 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 { PuzzleWorkSummary } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import type { CustomWorldGalleryCard } 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 { WoodenFishGalleryCardResponse } from '../../../packages/shared/src/contracts/woodenFish'; import { isBarkBattleGalleryEntry, isBigFishGalleryEntry, @@ -14,8 +18,17 @@ import { isSquareHoleGalleryEntry, isVisualNovelGalleryEntry, isWoodenFishGalleryEntry, + mapBabyObjectMatchDraftToPlatformGalleryCard, + mapBarkBattleWorkToPlatformGalleryCard, + mapBigFishWorkToPlatformGalleryCard, + mapJumpHopWorkToPlatformGalleryCard, + mapPuzzleWorkToPlatformGalleryCard, + mapSquareHoleWorkToPlatformGalleryCard, + mapVisualNovelWorkToPlatformGalleryCard, + mapWoodenFishWorkToPlatformGalleryCard, type PlatformPublicGalleryCard, } from '../rpg-entry/rpgEntryWorldPresentation'; +import { mapMatch3DWorkToPublicWorkDetail } from './platformMatch3DRuntimeProfile'; import { mapBarkBattlePublicDetailToWorkSummary, mapPublicWorkDetailToBigFishWork, @@ -127,6 +140,28 @@ export type PlatformRecommendRuntimeReadyState = { 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( entry: PlatformPublicGalleryCard, ) { @@ -399,8 +434,8 @@ export function isSamePlatformPublicGalleryEntry( } export function mergePlatformPublicGalleryEntries( - rpgEntries: CustomWorldGalleryCard[], - puzzleEntries: PlatformPublicGalleryCard[], + rpgEntries: readonly CustomWorldGalleryCard[], + puzzleEntries: readonly PlatformPublicGalleryCard[], ) { const entryMap = new Map(); @@ -414,3 +449,58 @@ export function mergePlatformPublicGalleryEntries( 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, + }; +}