refactor: 收口分类配置模型
This commit is contained in:
@@ -1257,6 +1257,14 @@
|
|||||||
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "bottom category tab becomes ranking and switches ranking metrics|ranking"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "bottom category tab becomes ranking and switches ranking metrics|ranking"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
- 关联文档:`docs/technical/【前端架构】RankingViewModel收口计划-2026-06-03.md`。
|
- 关联文档:`docs/technical/【前端架构】RankingViewModel收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
|
## 2026-06-03 Category Option ViewModel 收口
|
||||||
|
|
||||||
|
- 背景:分类频道的筛选选项、排序选项、默认值、active label fallback 和排序循环仍留在 `RpgEntryHomeView.tsx` 页面 Implementation 内,而玩法过滤、排序和主指标已经在 `rpgEntryPublicGalleryViewModel.ts`,同一分类 Interface 被拆成两处。
|
||||||
|
- 决策:在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 收口 `DEFAULT_PLATFORM_CATEGORY_KIND_FILTER`、`DEFAULT_PLATFORM_CATEGORY_SORT_MODE`、`PLATFORM_CATEGORY_KIND_FILTERS`、`PLATFORM_CATEGORY_SORT_OPTIONS`、`getPlatformCategoryKindFilterOption`、`getPlatformCategorySortOption` 与 `getNextPlatformCategorySortMode`;页面仅保留当前筛选 / 排序状态和渲染。
|
||||||
|
- 影响范围:发现页分类频道筛选弹窗、筛选按钮 label、排序按钮 label 与排序循环。
|
||||||
|
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
|
||||||
|
- 关联文档:`docs/technical/【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md`。
|
||||||
|
|
||||||
## 2026-06-03 Public Work Presentation 收口
|
## 2026-06-03 Public Work Presentation 收口
|
||||||
|
|
||||||
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_
|
|||||||
|
|
||||||
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
公开作品分类、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
公开作品的玩法类型 label 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
公开作品的玩法类型 label 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
- `parsePlatformEntryTimestamp(value)` / `getPlatformWorldTimestamp(entry)`:统一兼容 ISO 与后端 seconds.microsZ 时间戳。
|
- `parsePlatformEntryTimestamp(value)` / `getPlatformWorldTimestamp(entry)`:统一兼容 ISO 与后端 seconds.microsZ 时间戳。
|
||||||
- `filterTodayPublishedEntries(entries)`:统一“今日游戏”本地自然日筛选。
|
- `filterTodayPublishedEntries(entries)`:统一“今日游戏”本地自然日筛选。
|
||||||
- `getPlatformWorldLikeCount(entry)` / `getPlatformWorldPlayCount(entry)` / `getPlatformWorldRemixCount(entry)`、`buildPlatformRankingEntries(entries, tab)` 与 `getPlatformRankingMetricValue(entry, tab)`:统一公开卡片指标读取、排行 Tab 排序与取值。
|
- `getPlatformWorldLikeCount(entry)` / `getPlatformWorldPlayCount(entry)` / `getPlatformWorldRemixCount(entry)`、`buildPlatformRankingEntries(entries, tab)` 与 `getPlatformRankingMetricValue(entry, tab)`:统一公开卡片指标读取、排行 Tab 排序与取值。
|
||||||
|
- `DEFAULT_PLATFORM_CATEGORY_KIND_FILTER`、`DEFAULT_PLATFORM_CATEGORY_SORT_MODE`、`PLATFORM_CATEGORY_KIND_FILTERS`、`PLATFORM_CATEGORY_SORT_OPTIONS`、`getPlatformCategoryKindFilterOption(kindFilter)`、`getPlatformCategorySortOption(sortMode)` 与 `getNextPlatformCategorySortMode(sortMode)`:统一分类频道的筛选 / 排序选项、默认值、label 兜底和排序循环。
|
||||||
- `getPlatformCategoryKindFilter(entry)`、`matchesPlatformCategoryKindFilter(entry, kindFilter)`、`sortPlatformCategoryEntries(entries, sortMode)` 与 `getPlatformCategoryPrimaryMetric(entry)`:统一分类频道的玩法过滤、排序和主指标展示。
|
- `getPlatformCategoryKindFilter(entry)`、`matchesPlatformCategoryKindFilter(entry, kindFilter)`、`sortPlatformCategoryEntries(entries, sortMode)` 与 `getPlatformCategoryPrimaryMetric(entry)`:统一分类频道的玩法过滤、排序和主指标展示。
|
||||||
|
|
||||||
`RpgEntryHomeView.tsx` 只消费这些 ViewModel 函数,保留渲染、事件处理和账号状态。公开作品规则的 **Locality** 转移到 ViewModel **Module** 与其测试,页面不再持有这批纯规则。
|
`RpgEntryHomeView.tsx` 只消费这些 ViewModel 函数,保留渲染、事件处理和账号状态。公开作品规则的 **Locality** 转移到 ViewModel **Module** 与其测试,页面不再持有这批纯规则。
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
- 搜索应同时匹配作品号、`profileId`、`workId`、标题、作者、摘要和副标题。
|
- 搜索应同时匹配作品号、`profileId`、`workId`、标题、作者、摘要和副标题。
|
||||||
- 搜索排序先看标题前缀,再看作品号 compact 前缀,最后按发布时间 / 更新时间倒序。
|
- 搜索排序先看标题前缀,再看作品号 compact 前缀,最后按发布时间 / 更新时间倒序。
|
||||||
- 时间解析必须保留后端 `seconds.microsZ` 兼容。
|
- 时间解析必须保留后端 `seconds.microsZ` 兼容。
|
||||||
|
- 分类筛选与排序的选项顺序、默认值、中文 label 和“综合 -> 最新 -> 游玩 -> 点赞 -> 综合”循环属于 ViewModel **Interface**;页面只能消费该 **Interface**,不得在 `RpgEntryHomeView.tsx` 复写数组或 fallback 文案。
|
||||||
|
|
||||||
## 后续深化
|
## 后续深化
|
||||||
|
|
||||||
|
|||||||
@@ -160,11 +160,16 @@ import {
|
|||||||
buildPublicCategoryGroups,
|
buildPublicCategoryGroups,
|
||||||
buildPublicGalleryCardKey,
|
buildPublicGalleryCardKey,
|
||||||
dedupePlatformPublicGalleryEntries,
|
dedupePlatformPublicGalleryEntries,
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_KIND_FILTER,
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_SORT_MODE,
|
||||||
DEFAULT_PLATFORM_RANKING_TAB,
|
DEFAULT_PLATFORM_RANKING_TAB,
|
||||||
filterPlatformWorkSearchResults,
|
filterPlatformWorkSearchResults,
|
||||||
filterTodayPublishedEntries,
|
filterTodayPublishedEntries,
|
||||||
getAllPlatformPublicEntries,
|
getAllPlatformPublicEntries,
|
||||||
|
getNextPlatformCategorySortMode,
|
||||||
|
getPlatformCategoryKindFilterOption,
|
||||||
getPlatformCategoryPrimaryMetric,
|
getPlatformCategoryPrimaryMetric,
|
||||||
|
getPlatformCategorySortOption,
|
||||||
getPlatformPublicEntries,
|
getPlatformPublicEntries,
|
||||||
getPlatformRankingMetric,
|
getPlatformRankingMetric,
|
||||||
getPlatformRankingTabConfig,
|
getPlatformRankingTabConfig,
|
||||||
@@ -174,6 +179,8 @@ import {
|
|||||||
getPlatformWorldRemixCount,
|
getPlatformWorldRemixCount,
|
||||||
isExactPublicWorkCodeSearch,
|
isExactPublicWorkCodeSearch,
|
||||||
matchesPlatformCategoryKindFilter,
|
matchesPlatformCategoryKindFilter,
|
||||||
|
PLATFORM_CATEGORY_KIND_FILTERS,
|
||||||
|
PLATFORM_CATEGORY_SORT_OPTIONS,
|
||||||
PLATFORM_RANKING_TABS,
|
PLATFORM_RANKING_TABS,
|
||||||
type PlatformCategoryKindFilter,
|
type PlatformCategoryKindFilter,
|
||||||
type PlatformCategorySortMode,
|
type PlatformCategorySortMode,
|
||||||
@@ -374,30 +381,6 @@ const EDUTAINMENT_DISCOVER_CHANNEL = {
|
|||||||
id: 'edutainment',
|
id: 'edutainment',
|
||||||
label: EDUTAINMENT_WORK_TAG,
|
label: EDUTAINMENT_WORK_TAG,
|
||||||
} as const;
|
} as const;
|
||||||
const PLATFORM_CATEGORY_KIND_FILTERS: Array<{
|
|
||||||
id: PlatformCategoryKindFilter;
|
|
||||||
label: string;
|
|
||||||
}> = [
|
|
||||||
{ id: 'all', label: '全部' },
|
|
||||||
{ id: 'puzzle', label: '拼图' },
|
|
||||||
{ id: 'match3d', label: '抓鹅' },
|
|
||||||
{ id: 'square-hole', label: '方洞' },
|
|
||||||
{ id: 'visual-novel', label: '视觉' },
|
|
||||||
{ id: 'bark-battle', label: '汪汪' },
|
|
||||||
{ id: 'big-fish', label: '大鱼' },
|
|
||||||
{ id: 'jump-hop', label: '跳跃' },
|
|
||||||
{ id: 'wooden-fish', label: '木鱼' },
|
|
||||||
{ id: 'custom-world', label: 'RPG' },
|
|
||||||
];
|
|
||||||
const PLATFORM_CATEGORY_SORT_OPTIONS: Array<{
|
|
||||||
id: PlatformCategorySortMode;
|
|
||||||
label: string;
|
|
||||||
}> = [
|
|
||||||
{ id: 'composite', label: '综合' },
|
|
||||||
{ id: 'latest', label: '最新' },
|
|
||||||
{ id: 'play', label: '游玩' },
|
|
||||||
{ id: 'like', label: '点赞' },
|
|
||||||
];
|
|
||||||
const BABY_LOVE_DRAWING_DEFAULT_CARD = {
|
const BABY_LOVE_DRAWING_DEFAULT_CARD = {
|
||||||
title: '宝贝爱画',
|
title: '宝贝爱画',
|
||||||
subtitle: '空白画板',
|
subtitle: '空白画板',
|
||||||
@@ -3522,9 +3505,11 @@ export function RpgEntryHomeView({
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [categoryKindFilter, setCategoryKindFilter] =
|
const [categoryKindFilter, setCategoryKindFilter] =
|
||||||
useState<PlatformCategoryKindFilter>('all');
|
useState<PlatformCategoryKindFilter>(
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_KIND_FILTER,
|
||||||
|
);
|
||||||
const [categorySortMode, setCategorySortMode] =
|
const [categorySortMode, setCategorySortMode] =
|
||||||
useState<PlatformCategorySortMode>('composite');
|
useState<PlatformCategorySortMode>(DEFAULT_PLATFORM_CATEGORY_SORT_MODE);
|
||||||
const [isCategoryFilterPanelOpen, setIsCategoryFilterPanelOpen] =
|
const [isCategoryFilterPanelOpen, setIsCategoryFilterPanelOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [discoverChannel, setDiscoverChannel] =
|
const [discoverChannel, setDiscoverChannel] =
|
||||||
@@ -3674,15 +3659,12 @@ export function RpgEntryHomeView({
|
|||||||
}, [activeCategoryGroup, categoryKindFilter, categorySortMode]);
|
}, [activeCategoryGroup, categoryKindFilter, categorySortMode]);
|
||||||
const activeCategoryRawCount = activeCategoryGroup?.entries.length ?? 0;
|
const activeCategoryRawCount = activeCategoryGroup?.entries.length ?? 0;
|
||||||
const activeCategoryFilterLabel =
|
const activeCategoryFilterLabel =
|
||||||
PLATFORM_CATEGORY_KIND_FILTERS.find(
|
getPlatformCategoryKindFilterOption(categoryKindFilter).label;
|
||||||
(option) => option.id === categoryKindFilter,
|
|
||||||
)?.label ?? '全部';
|
|
||||||
const activeCategorySortLabel =
|
const activeCategorySortLabel =
|
||||||
PLATFORM_CATEGORY_SORT_OPTIONS.find(
|
getPlatformCategorySortOption(categorySortMode).label;
|
||||||
(option) => option.id === categorySortMode,
|
|
||||||
)?.label ?? '综合';
|
|
||||||
const activeCategoryFilterCount = activeCategoryEntries.length;
|
const activeCategoryFilterCount = activeCategoryEntries.length;
|
||||||
const categoryFilterApplied = categoryKindFilter !== 'all';
|
const categoryFilterApplied =
|
||||||
|
categoryKindFilter !== DEFAULT_PLATFORM_CATEGORY_KIND_FILTER;
|
||||||
const visibleTabs = useMemo<PlatformHomeTab[]>(
|
const visibleTabs = useMemo<PlatformHomeTab[]>(
|
||||||
() =>
|
() =>
|
||||||
isAuthenticated
|
isAuthenticated
|
||||||
@@ -4633,16 +4615,7 @@ export function RpgEntryHomeView({
|
|||||||
submitWorkSearch(mobileSearchKeyword);
|
submitWorkSearch(mobileSearchKeyword);
|
||||||
};
|
};
|
||||||
const cycleCategorySortMode = () => {
|
const cycleCategorySortMode = () => {
|
||||||
const currentIndex = PLATFORM_CATEGORY_SORT_OPTIONS.findIndex(
|
setCategorySortMode(getNextPlatformCategorySortMode(categorySortMode));
|
||||||
(option) => option.id === categorySortMode,
|
|
||||||
);
|
|
||||||
const nextIndex =
|
|
||||||
currentIndex >= 0
|
|
||||||
? (currentIndex + 1) % PLATFORM_CATEGORY_SORT_OPTIONS.length
|
|
||||||
: 0;
|
|
||||||
setCategorySortMode(
|
|
||||||
PLATFORM_CATEGORY_SORT_OPTIONS[nextIndex]?.id ?? 'composite',
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
const desktopHeroEntry =
|
const desktopHeroEntry =
|
||||||
featuredShelf[0] ?? generalLatestEntries[0] ?? myEntries[0] ?? null;
|
featuredShelf[0] ?? generalLatestEntries[0] ?? myEntries[0] ?? null;
|
||||||
|
|||||||
@@ -7,18 +7,27 @@ import {
|
|||||||
buildPublicCategoryGroups,
|
buildPublicCategoryGroups,
|
||||||
buildPublicGalleryCardKey,
|
buildPublicGalleryCardKey,
|
||||||
dedupePlatformPublicGalleryEntries,
|
dedupePlatformPublicGalleryEntries,
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_KIND_FILTER,
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_SORT_MODE,
|
||||||
DEFAULT_PLATFORM_RANKING_TAB,
|
DEFAULT_PLATFORM_RANKING_TAB,
|
||||||
filterPlatformWorkSearchResults,
|
filterPlatformWorkSearchResults,
|
||||||
filterTodayPublishedEntries,
|
filterTodayPublishedEntries,
|
||||||
|
getNextPlatformCategorySortMode,
|
||||||
getPlatformCategoryKindFilter,
|
getPlatformCategoryKindFilter,
|
||||||
|
getPlatformCategoryKindFilterOption,
|
||||||
getPlatformCategoryPrimaryMetric,
|
getPlatformCategoryPrimaryMetric,
|
||||||
|
getPlatformCategorySortOption,
|
||||||
getPlatformPublicEntries,
|
getPlatformPublicEntries,
|
||||||
getPlatformRankingMetric,
|
getPlatformRankingMetric,
|
||||||
getPlatformRankingMetricValue,
|
getPlatformRankingMetricValue,
|
||||||
getPlatformRankingTabConfig,
|
getPlatformRankingTabConfig,
|
||||||
matchesPlatformCategoryKindFilter,
|
matchesPlatformCategoryKindFilter,
|
||||||
parsePlatformEntryTimestamp,
|
parsePlatformEntryTimestamp,
|
||||||
|
PLATFORM_CATEGORY_KIND_FILTERS,
|
||||||
|
PLATFORM_CATEGORY_SORT_OPTIONS,
|
||||||
PLATFORM_RANKING_TABS,
|
PLATFORM_RANKING_TABS,
|
||||||
|
type PlatformCategoryKindFilter,
|
||||||
|
type PlatformCategorySortMode,
|
||||||
selectAdjacentPlatformRecommendEntry,
|
selectAdjacentPlatformRecommendEntry,
|
||||||
selectPlatformRecommendFeedWindow,
|
selectPlatformRecommendFeedWindow,
|
||||||
sortPlatformCategoryEntries,
|
sortPlatformCategoryEntries,
|
||||||
@@ -308,6 +317,52 @@ test('public gallery ViewModel keeps source kinds behind one category filter sea
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('public gallery ViewModel exposes category filter and sort option interface', () => {
|
||||||
|
expect(DEFAULT_PLATFORM_CATEGORY_KIND_FILTER).toBe('all');
|
||||||
|
expect(DEFAULT_PLATFORM_CATEGORY_SORT_MODE).toBe('composite');
|
||||||
|
expect(PLATFORM_CATEGORY_KIND_FILTERS.map((option) => option.label)).toEqual([
|
||||||
|
'全部',
|
||||||
|
'拼图',
|
||||||
|
'抓鹅',
|
||||||
|
'方洞',
|
||||||
|
'视觉',
|
||||||
|
'汪汪',
|
||||||
|
'大鱼',
|
||||||
|
'跳跃',
|
||||||
|
'木鱼',
|
||||||
|
'RPG',
|
||||||
|
]);
|
||||||
|
expect(PLATFORM_CATEGORY_SORT_OPTIONS.map((option) => option.label)).toEqual([
|
||||||
|
'综合',
|
||||||
|
'最新',
|
||||||
|
'游玩',
|
||||||
|
'点赞',
|
||||||
|
]);
|
||||||
|
expect(getPlatformCategoryKindFilterOption('match3d')).toEqual({
|
||||||
|
id: 'match3d',
|
||||||
|
label: '抓鹅',
|
||||||
|
});
|
||||||
|
expect(getPlatformCategorySortOption('latest')).toEqual({
|
||||||
|
id: 'latest',
|
||||||
|
label: '最新',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
getPlatformCategoryKindFilterOption(
|
||||||
|
'unknown' as PlatformCategoryKindFilter,
|
||||||
|
),
|
||||||
|
).toEqual({ id: 'all', label: '全部' });
|
||||||
|
expect(
|
||||||
|
getPlatformCategorySortOption('unknown' as PlatformCategorySortMode),
|
||||||
|
).toEqual({ id: 'composite', label: '综合' });
|
||||||
|
expect(getNextPlatformCategorySortMode('composite')).toBe('latest');
|
||||||
|
expect(getNextPlatformCategorySortMode('latest')).toBe('play');
|
||||||
|
expect(getNextPlatformCategorySortMode('play')).toBe('like');
|
||||||
|
expect(getNextPlatformCategorySortMode('like')).toBe('composite');
|
||||||
|
expect(
|
||||||
|
getNextPlatformCategorySortMode('unknown' as PlatformCategorySortMode),
|
||||||
|
).toBe('composite');
|
||||||
|
});
|
||||||
|
|
||||||
test('public gallery ViewModel ranks entries by selected metric', () => {
|
test('public gallery ViewModel ranks entries by selected metric', () => {
|
||||||
const playWinner = buildJumpHopEntry({
|
const playWinner = buildJumpHopEntry({
|
||||||
profileId: 'play-winner',
|
profileId: 'play-winner',
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ export type PlatformCategoryKindFilter =
|
|||||||
| 'wooden-fish'
|
| 'wooden-fish'
|
||||||
| 'custom-world';
|
| 'custom-world';
|
||||||
export type PlatformCategorySortMode = 'composite' | 'latest' | 'play' | 'like';
|
export type PlatformCategorySortMode = 'composite' | 'latest' | 'play' | 'like';
|
||||||
|
export type PlatformCategoryKindFilterOption = {
|
||||||
|
id: PlatformCategoryKindFilter;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
export type PlatformCategorySortOption = {
|
||||||
|
id: PlatformCategorySortMode;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type PlatformPublicCategoryGroup = {
|
export type PlatformPublicCategoryGroup = {
|
||||||
tag: string;
|
tag: string;
|
||||||
@@ -44,6 +52,10 @@ export type PlatformPublicCategoryGroup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_PLATFORM_RANKING_TAB: PlatformRankingTab = 'hot';
|
export const DEFAULT_PLATFORM_RANKING_TAB: PlatformRankingTab = 'hot';
|
||||||
|
export const DEFAULT_PLATFORM_CATEGORY_KIND_FILTER: PlatformCategoryKindFilter =
|
||||||
|
'all';
|
||||||
|
export const DEFAULT_PLATFORM_CATEGORY_SORT_MODE: PlatformCategorySortMode =
|
||||||
|
'composite';
|
||||||
|
|
||||||
export const PLATFORM_RANKING_TABS: PlatformRankingTabConfig[] = [
|
export const PLATFORM_RANKING_TABS: PlatformRankingTabConfig[] = [
|
||||||
{
|
{
|
||||||
@@ -77,6 +89,36 @@ const DEFAULT_PLATFORM_RANKING_CONFIG =
|
|||||||
(config) => config.id === DEFAULT_PLATFORM_RANKING_TAB,
|
(config) => config.id === DEFAULT_PLATFORM_RANKING_TAB,
|
||||||
) ?? PLATFORM_RANKING_TABS[0]!;
|
) ?? PLATFORM_RANKING_TABS[0]!;
|
||||||
|
|
||||||
|
export const PLATFORM_CATEGORY_KIND_FILTERS: PlatformCategoryKindFilterOption[] =
|
||||||
|
[
|
||||||
|
{ id: 'all', label: '全部' },
|
||||||
|
{ id: 'puzzle', label: '拼图' },
|
||||||
|
{ id: 'match3d', label: '抓鹅' },
|
||||||
|
{ id: 'square-hole', label: '方洞' },
|
||||||
|
{ id: 'visual-novel', label: '视觉' },
|
||||||
|
{ id: 'bark-battle', label: '汪汪' },
|
||||||
|
{ id: 'big-fish', label: '大鱼' },
|
||||||
|
{ id: 'jump-hop', label: '跳跃' },
|
||||||
|
{ id: 'wooden-fish', label: '木鱼' },
|
||||||
|
{ id: 'custom-world', label: 'RPG' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const PLATFORM_CATEGORY_SORT_OPTIONS: PlatformCategorySortOption[] = [
|
||||||
|
{ id: 'composite', label: '综合' },
|
||||||
|
{ id: 'latest', label: '最新' },
|
||||||
|
{ id: 'play', label: '游玩' },
|
||||||
|
{ id: 'like', label: '点赞' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_PLATFORM_CATEGORY_KIND_FILTER_OPTION =
|
||||||
|
PLATFORM_CATEGORY_KIND_FILTERS.find(
|
||||||
|
(option) => option.id === DEFAULT_PLATFORM_CATEGORY_KIND_FILTER,
|
||||||
|
) ?? PLATFORM_CATEGORY_KIND_FILTERS[0]!;
|
||||||
|
const DEFAULT_PLATFORM_CATEGORY_SORT_OPTION =
|
||||||
|
PLATFORM_CATEGORY_SORT_OPTIONS.find(
|
||||||
|
(option) => option.id === DEFAULT_PLATFORM_CATEGORY_SORT_MODE,
|
||||||
|
) ?? PLATFORM_CATEGORY_SORT_OPTIONS[0]!;
|
||||||
|
|
||||||
export type PlatformRecommendFeedWindow = {
|
export type PlatformRecommendFeedWindow = {
|
||||||
activeEntry: PlatformPublicGalleryCard | null;
|
activeEntry: PlatformPublicGalleryCard | null;
|
||||||
activeEntryKey: string | null;
|
activeEntryKey: string | null;
|
||||||
@@ -552,6 +594,41 @@ export function getPlatformCategoryPrimaryMetric(
|
|||||||
return { label: '游玩', value: getPlatformWorldPlayCount(entry) };
|
return { label: '游玩', value: getPlatformWorldPlayCount(entry) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPlatformCategoryKindFilterOption(
|
||||||
|
kindFilter: PlatformCategoryKindFilter,
|
||||||
|
): PlatformCategoryKindFilterOption {
|
||||||
|
return (
|
||||||
|
PLATFORM_CATEGORY_KIND_FILTERS.find((option) => option.id === kindFilter) ??
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_KIND_FILTER_OPTION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlatformCategorySortOption(
|
||||||
|
sortMode: PlatformCategorySortMode,
|
||||||
|
): PlatformCategorySortOption {
|
||||||
|
return (
|
||||||
|
PLATFORM_CATEGORY_SORT_OPTIONS.find((option) => option.id === sortMode) ??
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_SORT_OPTION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextPlatformCategorySortMode(
|
||||||
|
sortMode: PlatformCategorySortMode,
|
||||||
|
): PlatformCategorySortMode {
|
||||||
|
const currentIndex = PLATFORM_CATEGORY_SORT_OPTIONS.findIndex(
|
||||||
|
(option) => option.id === sortMode,
|
||||||
|
);
|
||||||
|
const nextIndex =
|
||||||
|
currentIndex >= 0
|
||||||
|
? (currentIndex + 1) % PLATFORM_CATEGORY_SORT_OPTIONS.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
PLATFORM_CATEGORY_SORT_OPTIONS[nextIndex]?.id ??
|
||||||
|
DEFAULT_PLATFORM_CATEGORY_SORT_MODE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function parsePlatformEntryTimestamp(value: string | null | undefined) {
|
export function parsePlatformEntryTimestamp(value: string | null | undefined) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user