refactor: 收口公开作品流聚合
This commit is contained in:
@@ -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`。
|
||||
|
||||
|
||||
@@ -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)。
|
||||
|
||||
|
||||
@@ -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 启动意图
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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> = {},
|
||||
): 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(
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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<string, PlatformPublicGalleryCard>();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user