From 00820e6571ceb6849ab84b7f00963eacfa56c2c7 Mon Sep 17 00:00:00 2001 From: kdletters Date: Wed, 3 Jun 2026 22:22:13 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=94=B6=E5=8F=A3=E5=85=AC?= =?UTF-8?q?=E5=BC=80=E4=BD=9C=E5=93=81=E8=AF=A6=E6=83=85=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hermes/shared-memory/decision-log.md | 8 + docs/README.md | 2 + ...atformPublicWorkDetailFlow收口计划-2026-06-03.md | 41 ++++ .../PlatformEntryFlowShellImpl.tsx | 89 +++---- .../platformPublicWorkDetailFlow.test.ts | 229 ++++++++++++++++++ .../platformPublicWorkDetailFlow.ts | 190 +++++++++++++++ 6 files changed, 506 insertions(+), 53 deletions(-) create mode 100644 docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md create mode 100644 src/components/platform-entry/platformPublicWorkDetailFlow.test.ts create mode 100644 src/components/platform-entry/platformPublicWorkDetailFlow.ts diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index d1663fbf..d9f8192f 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -16,6 +16,14 @@ --- +## 2026-06-03 平台入口公开作品详情 Strategy 收口 + +- 背景:平台壳层直接判断公开作品详情入口的玩法类型、是否需要补读完整详情,以及自有作品按钮显示“编辑”还是“改造”,导致统一作品详情的纯决策散落在巨型 Implementation 内。 +- 决策:新增 `src/components/platform-entry/platformPublicWorkDetailFlow.ts`,以 `getPlatformPublicWorkDetailKind`、`resolvePlatformPublicWorkDetailOpenStrategy` 和 `resolvePlatformPublicWorkActionMode` 收口公开作品详情 Strategy。`PlatformEntryFlowShellImpl.tsx` 只按 Strategy 调用现有详情读取 / 直接展示 Adapter;启动、点赞、remix 和编辑副作用暂不抽走。 +- 影响范围:统一作品详情入口、公开详情打开策略、自有公开作品编辑 / 改造动作模式,以及后续新增玩法公开详情接入。 +- 验证方式:`npm run test -- src/components/platform-entry/platformPublicWorkDetailFlow.test.ts`、公开详情壳层交互回归、`npm run typecheck`、`npm run check:encoding`。 +- 关联文档:`docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md`。 + ## 2026-06-03 平台入口弹窗状态规则收口 - 背景:`PlatformEntryFlowShellImpl.tsx` 曾同时持有平台级错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key、后台生成 still-running 识别和任务完成文案,导致壳层 Interface 偏浅,测试面不稳定。 diff --git a/docs/README.md b/docs/README.md index adec95d3..cf10707b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -41,6 +41,8 @@ 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/platformPublicWorkDetailFlow.ts`,规则见 [【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md](./technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-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)。 diff --git a/docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md b/docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md new file mode 100644 index 00000000..effc3c3a --- /dev/null +++ b/docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md @@ -0,0 +1,41 @@ +# PlatformPublicWorkDetailFlow 收口计划 + +## 背景 + +`PlatformEntryFlowShellImpl.tsx` 已把公开作品身份、去重和推荐 runtime kind 收口到 `platformPublicGalleryFlow.ts`,但统一作品详情入口仍在壳层 Implementation 内直接判断 RPG、拼图、跳一跳、敲木鱼、视觉小说和其它玩法。壳层既要知道哪些公开详情可直接使用当前 entry,又要知道哪些玩法必须先补读完整详情,还要按当前用户判断详情按钮是“编辑”还是“改造”。这些是纯决策规则,继续留在巨型壳层会削弱 Locality。 + +## 决策 + +- 新增 `src/components/platform-entry/platformPublicWorkDetailFlow.ts` 作为 Platform Public Work Detail Flow Module。 +- Module Interface 收口: + - `getPlatformPublicWorkDetailKind(entry)` + - `resolvePlatformPublicWorkDetailOpenStrategy(entry)` + - `resolvePlatformPublicWorkActionMode(entry, viewerUserId)` +- `PlatformEntryFlowShellImpl.tsx` 继续作为 Adapter:根据 open strategy 调用 `openPublicWorkDetail`、`openPuzzlePublicWorkDetail`、`openJumpHopPublicWorkDetail`、`openWoodenFishPublicWorkDetail`、`openVisualNovelPublicWorkDetail` 或 `openRpgPublicWorkDetail`。 +- 本次不抽 `startSelectedPublicWork`、`likePublicWork`、`remixPublicWork`、`editOwnedPublicWork`。这些函数牵涉运行态启动、计数写入、草稿恢复、作品架缓存和多路错误 setter;若直接搬进一个 Hook,会形成浅 Interface。 + +## Interface 约束 + +- `getPlatformPublicWorkDetailKind` 只根据 `PlatformPublicGalleryCard` 的玩法判定 helper 归一 kind;没有 `sourceType` 的公开 RPG 作品回退为 `rpg`。 +- `resolvePlatformPublicWorkDetailOpenStrategy` 只表达“如何打开详情”,不执行网络请求或 state setter。 +- 拼图、跳一跳、敲木鱼、视觉小说需要按 `profileId` 补读完整详情;返回对应 `load-*` strategy。 +- 大鱼吃小鱼、抓大鹅、方洞挑战、汪汪声浪、宝贝识物和其它可直接展示的公开 entry 返回 `use-entry` strategy。 +- RPG 返回 `load-rpg-detail` strategy,由壳层 Adapter 继续调用 RPG 详情读取流程。 +- `resolvePlatformPublicWorkActionMode` 只比较 `entry.ownerUserId` 与当前 viewer user id;当前用户拥有该公开作品时返回 `edit`,否则返回 `remix`。 + +## Depth / Leverage / Locality + +- **Depth**:壳层传入公开作品 entry 和当前用户 id,即可得到详情打开策略和动作模式;玩法判定细则藏在 Module Implementation 内。 +- **Leverage**:新增玩法公开详情时先补 Strategy 单测,再接壳层 Adapter,不必在多个 JSX / callback 位置重复 sourceType 判断。 +- **Locality**:公开作品详情入口的纯策略集中到一个小 Module;启动运行态、点赞、改造、编辑等副作用仍留在壳层,避免伪 Seam。 + +## 验收 + +- `npm run test -- src/components/platform-entry/platformPublicWorkDetailFlow.test.ts` +- `npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts` +- `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx` +- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "public detail|owned public puzzle detail|direct missing public work detail"` +- `npx eslint src/components/platform-entry/platformPublicWorkDetailFlow.ts src/components/platform-entry/platformPublicWorkDetailFlow.test.ts --max-warnings 0` +- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet` +- `npm run typecheck` +- `npm run check:encoding` diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx index 1540a5f8..98366607 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl.tsx @@ -515,6 +515,10 @@ import { mergePlatformPublicGalleryEntries, type RecommendRuntimeKind, } from './platformPublicGalleryFlow'; +import { + resolvePlatformPublicWorkActionMode, + resolvePlatformPublicWorkDetailOpenStrategy, +} from './platformPublicWorkDetailFlow'; import { buildPuzzleResultProfileId, buildPuzzleResultWorkId, @@ -4025,13 +4029,13 @@ export function PlatformEntryFlowShellImpl({ const resultViewError = autosaveCoordinator.customWorldAutoSaveError ?? sessionController.customWorldError; - const isSelectedPublicWorkOwned = Boolean( - authUi?.user?.id && - selectedPublicWorkDetail?.ownerUserId === authUi.user.id, - ); - const selectedPublicWorkActionMode = isSelectedPublicWorkOwned - ? 'edit' + const selectedPublicWorkActionMode = selectedPublicWorkDetail + ? resolvePlatformPublicWorkActionMode( + selectedPublicWorkDetail, + authUi?.user?.id, + ) : 'remix'; + const isSelectedPublicWorkOwned = selectedPublicWorkActionMode === 'edit'; useEffect(() => { if ( @@ -11574,54 +11578,33 @@ export function PlatformEntryFlowShellImpl({ const openPublicGalleryDetail = useCallback( (entry: PlatformPublicGalleryCard) => { - if (isBigFishGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; + const strategy = resolvePlatformPublicWorkDetailOpenStrategy(entry); + switch (strategy.type) { + case 'use-entry': + openPublicWorkDetail(entry); + return; + case 'load-puzzle-detail': + void openPuzzlePublicWorkDetail(strategy.profileId, { + tab: platformBootstrap.platformTab, + }); + return; + case 'load-jump-hop-detail': + void openJumpHopPublicWorkDetail(strategy.profileId); + return; + case 'load-wooden-fish-detail': + void openWoodenFishPublicWorkDetail(strategy.profileId); + return; + case 'load-visual-novel-detail': + void openVisualNovelPublicWorkDetail(strategy.profileId); + return; + case 'load-rpg-detail': + void openRpgPublicWorkDetail(strategy.entry); + return; + default: { + const exhaustive: never = strategy; + return exhaustive; + } } - - if (isPuzzleGalleryEntry(entry)) { - void openPuzzlePublicWorkDetail(entry.profileId, { - tab: platformBootstrap.platformTab, - }); - return; - } - - if (isMatch3DGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } - - if (isSquareHoleGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } - - if (isJumpHopGalleryEntry(entry)) { - void openJumpHopPublicWorkDetail(entry.profileId); - return; - } - - if (isWoodenFishGalleryEntry(entry)) { - void openWoodenFishPublicWorkDetail(entry.profileId); - return; - } - - if (isVisualNovelGalleryEntry(entry)) { - void openVisualNovelPublicWorkDetail(entry.profileId); - return; - } - - if (isBarkBattleGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } - - if (isEdutainmentGalleryEntry(entry)) { - openPublicWorkDetail(entry); - return; - } - - void openRpgPublicWorkDetail(entry); }, [ openPuzzlePublicWorkDetail, diff --git a/src/components/platform-entry/platformPublicWorkDetailFlow.test.ts b/src/components/platform-entry/platformPublicWorkDetailFlow.test.ts new file mode 100644 index 00000000..994fb7b0 --- /dev/null +++ b/src/components/platform-entry/platformPublicWorkDetailFlow.test.ts @@ -0,0 +1,229 @@ +import { expect, test } from 'vitest'; + +import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime'; +import { + EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID, + EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME, + type PlatformPublicGalleryCard, +} from '../rpg-entry/rpgEntryWorldPresentation'; +import { + getPlatformPublicWorkDetailKind, + type PlatformPublicWorkDetailKind, + type PlatformPublicWorkDetailOpenStrategy, + resolvePlatformPublicWorkActionMode, + resolvePlatformPublicWorkDetailOpenStrategy, +} from './platformPublicWorkDetailFlow'; + +type TypedPlatformPublicGalleryCard = Extract< + PlatformPublicGalleryCard, + { sourceType: string } +>; +type PlatformGallerySourceType = TypedPlatformPublicGalleryCard['sourceType']; +type TypedPlatformPublicGalleryCardOverrides = Partial< + Omit +>; + +function buildRpgEntry( + overrides: Partial = {}, +): CustomWorldGalleryCard { + return { + ownerUserId: 'user-1', + profileId: 'rpg-profile', + publicWorkCode: 'CW-RPG', + authorPublicUserCode: null, + visibility: 'published', + publishedAt: '2026-06-01T00:00:00.000Z', + updatedAt: '2026-06-01T01:00:00.000Z', + authorDisplayName: '玩家', + worldName: 'RPG 世界', + subtitle: '公开作品', + summaryText: '公开作品摘要', + coverImageSrc: null, + themeMode: 'martial', + playableNpcCount: 1, + landmarkCount: 1, + ...overrides, + }; +} + +function buildTypedEntry( + sourceType: PlatformGallerySourceType, + overrides: TypedPlatformPublicGalleryCardOverrides = {}, +): PlatformPublicGalleryCard { + const common = { + workId: `${sourceType}-work`, + profileId: `${sourceType}-profile`, + publicWorkCode: `${sourceType}-code`, + ownerUserId: 'user-1', + authorDisplayName: '玩家', + worldName: `${sourceType} 作品`, + subtitle: '公开作品', + summaryText: '公开作品摘要', + coverImageSrc: null, + themeTags: [sourceType], + visibility: 'published' as const, + publishedAt: '2026-06-01T00:00:00.000Z', + updatedAt: '2026-06-01T01:00:00.000Z', + }; + + switch (sourceType) { + case 'puzzle': + return { ...common, ...overrides, sourceType }; + case 'big-fish': + return { ...common, ...overrides, sourceType }; + case 'match3d': + return { ...common, ...overrides, sourceType }; + case 'square-hole': + return { ...common, ...overrides, sourceType }; + case 'visual-novel': + return { ...common, ...overrides, sourceType }; + case 'jump-hop': + return { ...common, ...overrides, sourceType }; + case 'wooden-fish': + return { ...common, ...overrides, sourceType }; + case 'edutainment': + return { + ...common, + ...overrides, + sourceType, + templateId: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_ID, + templateName: EDUTAINMENT_BABY_OBJECT_MATCH_TEMPLATE_NAME, + }; + case 'bark-battle': + return { + ...common, + ...overrides, + sourceType, + authorPublicUserCode: null, + coverRenderMode: 'image', + coverCharacterImageSrcs: [], + themeMode: 'martial', + playableNpcCount: 1, + landmarkCount: 1, + }; + default: { + const exhaustive: never = sourceType; + return exhaustive; + } + } +} + +test('platform public work detail flow resolves detail kind for every play kind', () => { + const cases: Array< + [sourceType: PlatformGallerySourceType, kind: PlatformPublicWorkDetailKind] + > = [ + ['big-fish', 'big-fish'], + ['puzzle', 'puzzle'], + ['jump-hop', 'jump-hop'], + ['wooden-fish', 'wooden-fish'], + ['match3d', 'match3d'], + ['square-hole', 'square-hole'], + ['visual-novel', 'visual-novel'], + ['bark-battle', 'bark-battle'], + ['edutainment', 'edutainment'], + ]; + + cases.forEach(([sourceType, kind]) => { + expect(getPlatformPublicWorkDetailKind(buildTypedEntry(sourceType))).toBe( + kind, + ); + }); + + expect(getPlatformPublicWorkDetailKind(buildRpgEntry())).toBe('rpg'); +}); + +test('platform public work detail flow resolves open strategy', () => { + const rpgEntry = buildRpgEntry(); + const cases: Array< + [ + entry: PlatformPublicGalleryCard, + strategy: PlatformPublicWorkDetailOpenStrategy, + ] + > = [ + [ + buildTypedEntry('big-fish'), + { + type: 'use-entry', + kind: 'big-fish', + }, + ], + [ + buildTypedEntry('match3d'), + { + type: 'use-entry', + kind: 'match3d', + }, + ], + [ + buildTypedEntry('square-hole'), + { + type: 'use-entry', + kind: 'square-hole', + }, + ], + [ + buildTypedEntry('bark-battle'), + { + type: 'use-entry', + kind: 'bark-battle', + }, + ], + [ + buildTypedEntry('edutainment'), + { + type: 'use-entry', + kind: 'edutainment', + }, + ], + [ + buildTypedEntry('puzzle'), + { + type: 'load-puzzle-detail', + profileId: 'puzzle-profile', + }, + ], + [ + buildTypedEntry('jump-hop'), + { + type: 'load-jump-hop-detail', + profileId: 'jump-hop-profile', + }, + ], + [ + buildTypedEntry('wooden-fish'), + { + type: 'load-wooden-fish-detail', + profileId: 'wooden-fish-profile', + }, + ], + [ + buildTypedEntry('visual-novel'), + { + type: 'load-visual-novel-detail', + profileId: 'visual-novel-profile', + }, + ], + [ + rpgEntry, + { + type: 'load-rpg-detail', + entry: rpgEntry, + }, + ], + ]; + + cases.forEach(([entry, strategy]) => { + expect(resolvePlatformPublicWorkDetailOpenStrategy(entry)).toEqual( + strategy, + ); + }); +}); + +test('platform public work detail flow resolves edit mode only for owned works', () => { + const entry = buildTypedEntry('puzzle'); + + expect(resolvePlatformPublicWorkActionMode(entry, 'user-1')).toBe('edit'); + expect(resolvePlatformPublicWorkActionMode(entry, ' user-1 ')).toBe('edit'); + expect(resolvePlatformPublicWorkActionMode(entry, 'user-2')).toBe('remix'); + expect(resolvePlatformPublicWorkActionMode(entry, null)).toBe('remix'); +}); diff --git a/src/components/platform-entry/platformPublicWorkDetailFlow.ts b/src/components/platform-entry/platformPublicWorkDetailFlow.ts new file mode 100644 index 00000000..942f220e --- /dev/null +++ b/src/components/platform-entry/platformPublicWorkDetailFlow.ts @@ -0,0 +1,190 @@ +import type { CustomWorldGalleryCard } from '../../../packages/shared/src/contracts/runtime'; +import { + isBarkBattleGalleryEntry, + isBigFishGalleryEntry, + isEdutainmentGalleryEntry, + isJumpHopGalleryEntry, + isMatch3DGalleryEntry, + isPuzzleGalleryEntry, + isSquareHoleGalleryEntry, + isVisualNovelGalleryEntry, + isWoodenFishGalleryEntry, + type PlatformPublicGalleryCard, +} from '../rpg-entry/rpgEntryWorldPresentation'; + +export type PlatformPublicWorkDetailKind = + | 'bark-battle' + | 'big-fish' + | 'edutainment' + | 'jump-hop' + | 'match3d' + | 'puzzle' + | 'rpg' + | 'square-hole' + | 'visual-novel' + | 'wooden-fish'; + +export type PlatformPublicWorkDetailOpenStrategy = + | { + type: 'use-entry'; + kind: Exclude< + PlatformPublicWorkDetailKind, + 'jump-hop' | 'puzzle' | 'rpg' | 'visual-novel' | 'wooden-fish' + >; + } + | { + type: 'load-puzzle-detail'; + profileId: string; + } + | { + type: 'load-jump-hop-detail'; + profileId: string; + } + | { + type: 'load-wooden-fish-detail'; + profileId: string; + } + | { + type: 'load-visual-novel-detail'; + profileId: string; + } + | { + type: 'load-rpg-detail'; + entry: CustomWorldGalleryCard; + }; + +export type PlatformPublicWorkActionMode = 'edit' | 'remix'; + +export function isRpgPublicWorkDetailEntry( + entry: PlatformPublicGalleryCard, +): entry is CustomWorldGalleryCard { + return !('sourceType' in entry); +} + +export function getPlatformPublicWorkDetailKind( + entry: PlatformPublicGalleryCard, +): PlatformPublicWorkDetailKind { + if (isBigFishGalleryEntry(entry)) { + return 'big-fish'; + } + + if (isPuzzleGalleryEntry(entry)) { + return 'puzzle'; + } + + if (isJumpHopGalleryEntry(entry)) { + return 'jump-hop'; + } + + if (isWoodenFishGalleryEntry(entry)) { + return 'wooden-fish'; + } + + if (isMatch3DGalleryEntry(entry)) { + return 'match3d'; + } + + if (isSquareHoleGalleryEntry(entry)) { + return 'square-hole'; + } + + if (isVisualNovelGalleryEntry(entry)) { + return 'visual-novel'; + } + + if (isBarkBattleGalleryEntry(entry)) { + return 'bark-battle'; + } + + if (isEdutainmentGalleryEntry(entry)) { + return 'edutainment'; + } + + return 'rpg'; +} + +export function resolvePlatformPublicWorkDetailOpenStrategy( + entry: PlatformPublicGalleryCard, +): PlatformPublicWorkDetailOpenStrategy { + if (isBigFishGalleryEntry(entry)) { + return { + type: 'use-entry', + kind: 'big-fish', + }; + } + + if (isPuzzleGalleryEntry(entry)) { + return { + type: 'load-puzzle-detail', + profileId: entry.profileId, + }; + } + + if (isJumpHopGalleryEntry(entry)) { + return { + type: 'load-jump-hop-detail', + profileId: entry.profileId, + }; + } + + if (isWoodenFishGalleryEntry(entry)) { + return { + type: 'load-wooden-fish-detail', + profileId: entry.profileId, + }; + } + + if (isMatch3DGalleryEntry(entry)) { + return { + type: 'use-entry', + kind: 'match3d', + }; + } + + if (isSquareHoleGalleryEntry(entry)) { + return { + type: 'use-entry', + kind: 'square-hole', + }; + } + + if (isVisualNovelGalleryEntry(entry)) { + return { + type: 'load-visual-novel-detail', + profileId: entry.profileId, + }; + } + + if (isBarkBattleGalleryEntry(entry)) { + return { + type: 'use-entry', + kind: 'bark-battle', + }; + } + + if (isEdutainmentGalleryEntry(entry)) { + return { + type: 'use-entry', + kind: 'edutainment', + }; + } + + if (isRpgPublicWorkDetailEntry(entry)) { + return { + type: 'load-rpg-detail', + entry, + }; + } + + const exhaustive: never = entry; + return exhaustive; +} + +export function resolvePlatformPublicWorkActionMode( + entry: PlatformPublicGalleryCard, + viewerUserId: string | null | undefined, +): PlatformPublicWorkActionMode { + return viewerUserId?.trim() && entry.ownerUserId === viewerUserId.trim() + ? 'edit' + : 'remix'; +}