feat: add child motion entry and fix auth env
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
This commit is contained in:
@@ -285,8 +285,12 @@ import { useRpgCreationEnterWorld } from '../rpg-entry/useRpgCreationEnterWorld'
|
||||
import { useRpgCreationResultAutosave } from '../rpg-entry/useRpgCreationResultAutosave';
|
||||
import { useRpgCreationSessionController } from '../rpg-entry/useRpgCreationSessionController';
|
||||
import { createMockVisualNovelRunFromDraft } from '../visual-novel-runtime/visualNovelMockData';
|
||||
import {
|
||||
canExposePublicWork,
|
||||
EDUTAINMENT_HIDDEN_MESSAGE,
|
||||
filterGeneralPublicWorks,
|
||||
} from './platformEdutainmentVisibility';
|
||||
import { PlatformEntryCreationTypeModal } from './PlatformEntryCreationTypeModal';
|
||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||
import type { PlatformCreationTypeId } from './platformEntryCreationTypes';
|
||||
import {
|
||||
getVisiblePlatformCreationTypes,
|
||||
@@ -302,6 +306,7 @@ import {
|
||||
} from './platformEntryShared';
|
||||
import type { PlatformEntryFlowShellProps } from './platformEntryTypes';
|
||||
import { PlatformEntryWorldDetailView } from './PlatformEntryWorldDetailView';
|
||||
import { PlatformFeedbackView } from './PlatformFeedbackView';
|
||||
import { PlatformWorkDetailView } from './PlatformWorkDetailView';
|
||||
import { usePlatformCreationAgentFlowController } from './usePlatformCreationAgentFlowController';
|
||||
import { usePlatformEntryBootstrap } from './usePlatformEntryBootstrap';
|
||||
@@ -2158,7 +2163,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
const recommendRuntimeEntries = useMemo(
|
||||
() => {
|
||||
const entryMap = new Map<string, PlatformPublicGalleryCard>();
|
||||
[...featuredGalleryEntries, ...latestGalleryEntries].forEach((entry) => {
|
||||
filterGeneralPublicWorks([
|
||||
...featuredGalleryEntries,
|
||||
...latestGalleryEntries,
|
||||
]).forEach((entry) => {
|
||||
entryMap.set(getPlatformPublicGalleryEntryKey(entry), entry);
|
||||
});
|
||||
return Array.from(entryMap.values());
|
||||
@@ -5263,6 +5271,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const openPublicWorkDetail = useCallback(
|
||||
(entry: PlatformPublicGalleryCard) => {
|
||||
if (!canExposePublicWork(entry)) {
|
||||
setSelectedPublicWorkDetail(null);
|
||||
setPublicWorkDetailError(EDUTAINMENT_HIDDEN_MESSAGE);
|
||||
setSelectionStage('platform');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedPublicWorkDetail(entry);
|
||||
setPublicWorkDetailError(null);
|
||||
setSelectionStage('work-detail');
|
||||
@@ -5490,6 +5505,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
const openRpgPublicWorkDetail = useCallback(
|
||||
async (entry: CustomWorldGalleryCard) => {
|
||||
if (!canExposePublicWork(entry)) {
|
||||
setSelectedPublicWorkDetail(null);
|
||||
setPublicWorkDetailError(EDUTAINMENT_HIDDEN_MESSAGE);
|
||||
setSelectionStage('platform');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsPublicWorkDetailBusy(true);
|
||||
setPublicWorkDetailError(null);
|
||||
clearSelectedPublicWorkAuthor();
|
||||
@@ -5501,6 +5523,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
await detailNavigation.loadGalleryDetailEntry(entry);
|
||||
setSelectedDetailEntry(detailEntry);
|
||||
const detailCard = mapRpgGalleryCardToPublicWorkDetail(detailEntry);
|
||||
if (!canExposePublicWork(detailCard)) {
|
||||
setSelectedDetailEntry(null);
|
||||
setSelectedPublicWorkDetail(null);
|
||||
setPublicWorkDetailError(EDUTAINMENT_HIDDEN_MESSAGE);
|
||||
setSelectionStage('platform');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedPublicWorkDetail(detailCard);
|
||||
if (detailEntry.publicWorkCode?.trim()) {
|
||||
pushAppHistoryPath(
|
||||
@@ -5539,9 +5569,17 @@ export function PlatformEntryFlowShellImpl({
|
||||
|
||||
try {
|
||||
const { item } = await getPuzzleGalleryDetail(profileId);
|
||||
const detailEntry = mapPuzzleWorkToPublicWorkDetail(item);
|
||||
if (!canExposePublicWork(detailEntry)) {
|
||||
setSelectedPuzzleDetail(null);
|
||||
setPublicWorkDetailError(EDUTAINMENT_HIDDEN_MESSAGE);
|
||||
setSelectionStage('platform');
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedPuzzleDetail(item);
|
||||
setPuzzleDetailReturnTarget(returnTarget);
|
||||
openPublicWorkDetail(mapPuzzleWorkToPublicWorkDetail(item));
|
||||
openPublicWorkDetail(detailEntry);
|
||||
} catch (error) {
|
||||
if (isMissingPuzzleWorkError(error)) {
|
||||
setSelectedPuzzleDetail(null);
|
||||
@@ -6577,11 +6615,14 @@ export function PlatformEntryFlowShellImpl({
|
||||
match3dError,
|
||||
match3dFlow,
|
||||
match3dRun,
|
||||
platformBootstrap.platformTab,
|
||||
platformThemeClass,
|
||||
puzzleError,
|
||||
puzzleRun,
|
||||
recommendRuntimeEntries,
|
||||
remodelCurrentPuzzleRuntimeWork,
|
||||
resolveMatch3DErrorMessage,
|
||||
resolveSquareHoleErrorMessage,
|
||||
reportBigFishObservedPlayTime,
|
||||
restartBigFishRun,
|
||||
selectedPuzzleDetail,
|
||||
@@ -6930,6 +6971,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
remixCount: entry.remixCount ?? 0,
|
||||
likeCount: entry.likeCount ?? 0,
|
||||
} satisfies CustomWorldGalleryCard;
|
||||
if (!canExposePublicWork(card)) {
|
||||
throw new Error(EDUTAINMENT_HIDDEN_MESSAGE);
|
||||
}
|
||||
|
||||
setSelectedDetailEntry(entry);
|
||||
openPublicWorkDetail(card);
|
||||
};
|
||||
@@ -6938,9 +6983,12 @@ export function PlatformEntryFlowShellImpl({
|
||||
puzzleGalleryEntries.length > 0
|
||||
? puzzleGalleryEntries
|
||||
: await refreshPuzzleGallery();
|
||||
const matchedEntry = entries.find((entry) =>
|
||||
isSamePuzzlePublicWorkCode(normalizedKeyword, entry.profileId),
|
||||
);
|
||||
const matchedEntry = entries
|
||||
.map(mapPuzzleWorkToPublicWorkDetail)
|
||||
.filter(canExposePublicWork)
|
||||
.find((entry) =>
|
||||
isSamePuzzlePublicWorkCode(normalizedKeyword, entry.profileId),
|
||||
);
|
||||
|
||||
if (!matchedEntry) {
|
||||
throw new Error('未找到拼图作品。');
|
||||
@@ -6955,9 +7003,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
bigFishGalleryEntries.length > 0
|
||||
? bigFishGalleryEntries
|
||||
: await refreshBigFishGallery();
|
||||
const matchedEntry = entries.find((entry) =>
|
||||
isSameBigFishPublicWorkCode(normalizedKeyword, entry.sourceSessionId),
|
||||
);
|
||||
const matchedEntry = entries.find((entry) => {
|
||||
const detailEntry = mapBigFishWorkToPublicWorkDetail(entry);
|
||||
return (
|
||||
canExposePublicWork(detailEntry) &&
|
||||
isSameBigFishPublicWorkCode(normalizedKeyword, entry.sourceSessionId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!matchedEntry) {
|
||||
throw new Error('未找到大鱼吃小鱼作品。');
|
||||
@@ -6970,9 +7022,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
match3dGalleryEntries.length > 0
|
||||
? match3dGalleryEntries
|
||||
: await refreshMatch3DGallery();
|
||||
const matchedEntry = entries.find((entry) =>
|
||||
isSameMatch3DPublicWorkCode(normalizedKeyword, entry.profileId),
|
||||
);
|
||||
const matchedEntry = entries.find((entry) => {
|
||||
const detailEntry = mapMatch3DWorkToPublicWorkDetail(entry);
|
||||
return (
|
||||
canExposePublicWork(detailEntry) &&
|
||||
isSameMatch3DPublicWorkCode(normalizedKeyword, entry.profileId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!matchedEntry) {
|
||||
throw new Error('未找到抓大鹅作品。');
|
||||
@@ -6985,9 +7041,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
squareHoleGalleryEntries.length > 0
|
||||
? squareHoleGalleryEntries
|
||||
: await refreshSquareHoleGallery();
|
||||
const matchedEntry = entries.find((entry) =>
|
||||
isSameSquareHolePublicWorkCode(normalizedKeyword, entry.profileId),
|
||||
);
|
||||
const matchedEntry = entries.find((entry) => {
|
||||
const detailEntry = mapSquareHoleWorkToPublicWorkDetail(entry);
|
||||
return (
|
||||
canExposePublicWork(detailEntry) &&
|
||||
isSameSquareHolePublicWorkCode(normalizedKeyword, entry.profileId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!matchedEntry) {
|
||||
throw new Error('未找到方洞挑战作品。');
|
||||
@@ -7000,9 +7060,13 @@ export function PlatformEntryFlowShellImpl({
|
||||
visualNovelGalleryEntries.length > 0
|
||||
? visualNovelGalleryEntries
|
||||
: await refreshVisualNovelGallery();
|
||||
const matchedEntry = entries.find((entry) =>
|
||||
isSameVisualNovelPublicWorkCode(normalizedKeyword, entry.profileId),
|
||||
);
|
||||
const matchedEntry = entries.find((entry) => {
|
||||
const detailEntry = mapVisualNovelWorkToPublicWorkDetail(entry);
|
||||
return (
|
||||
canExposePublicWork(detailEntry) &&
|
||||
isSameVisualNovelPublicWorkCode(normalizedKeyword, entry.profileId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!matchedEntry) {
|
||||
throw new Error('未找到视觉小说作品。');
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { PlatformPublicGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
import {
|
||||
canExposePublicWork,
|
||||
filterEdutainmentPublicWorks,
|
||||
filterGeneralPublicWorks,
|
||||
isEdutainmentEntryEnabled,
|
||||
isEdutainmentPublicWork,
|
||||
} from './platformEdutainmentVisibility';
|
||||
|
||||
function buildPuzzleCard(themeTags: string[]): PlatformPublicGalleryCard {
|
||||
return {
|
||||
sourceType: 'puzzle',
|
||||
workId: 'puzzle-work-education-demo',
|
||||
profileId: 'puzzle-profile-education-demo',
|
||||
publicWorkCode: 'PZ-EDUDEMO',
|
||||
ownerUserId: 'user-education',
|
||||
authorDisplayName: '动作 Demo 作者',
|
||||
worldName: '儿童动作热身 Demo',
|
||||
subtitle: '拼图关卡',
|
||||
summaryText: '本地动作 Demo。',
|
||||
coverImageSrc: null,
|
||||
themeTags,
|
||||
visibility: 'published',
|
||||
publishedAt: '2026-05-09T10:00:00.000Z',
|
||||
updatedAt: '2026-05-09T10:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe('platformEdutainmentVisibility', () => {
|
||||
test('matches only the exact edutainment tag from full work tags', () => {
|
||||
const exact = buildPuzzleCard(['运动', '安全', '拼图', '寓教于乐']);
|
||||
const fuzzy = buildPuzzleCard(['儿童教育', '寓教于乐 ']);
|
||||
|
||||
expect(isEdutainmentPublicWork(exact)).toBe(true);
|
||||
expect(isEdutainmentPublicWork(fuzzy)).toBe(false);
|
||||
expect(filterEdutainmentPublicWorks([exact, fuzzy])).toEqual([exact]);
|
||||
expect(filterGeneralPublicWorks([exact, fuzzy])).toEqual([fuzzy]);
|
||||
});
|
||||
|
||||
test('defaults to enabled and blocks exact edutainment works only when disabled', () => {
|
||||
const exact = buildPuzzleCard(['寓教于乐']);
|
||||
const general = buildPuzzleCard(['儿童教育']);
|
||||
|
||||
expect(isEdutainmentEntryEnabled()).toBe(true);
|
||||
expect(canExposePublicWork(exact)).toBe(true);
|
||||
|
||||
vi.stubEnv('VITE_ENABLE_EDUTAINMENT_ENTRY', 'false');
|
||||
|
||||
expect(isEdutainmentEntryEnabled()).toBe(false);
|
||||
expect(canExposePublicWork(exact)).toBe(false);
|
||||
expect(canExposePublicWork(general)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { PlatformBrowseHistoryEntry } from '../../../packages/shared/src/contracts/runtime';
|
||||
import type { PlatformPublicGalleryCard } from '../rpg-entry/rpgEntryWorldPresentation';
|
||||
|
||||
export const EDUTAINMENT_WORK_TAG = '寓教于乐';
|
||||
export const EDUTAINMENT_HIDDEN_MESSAGE = '该内容暂不可见。';
|
||||
|
||||
const EDUTAINMENT_ENTRY_DISABLED_VALUES = new Set(['false', '0', 'off', 'no']);
|
||||
|
||||
// 中文注释:入口默认开启;只有明确写入关闭值时才完全隐藏寓教于乐内容。
|
||||
export function isEdutainmentEntryEnabled(
|
||||
rawValue = import.meta.env.VITE_ENABLE_EDUTAINMENT_ENTRY,
|
||||
) {
|
||||
const normalized = (rawValue ?? '').trim().toLowerCase();
|
||||
return !EDUTAINMENT_ENTRY_DISABLED_VALUES.has(normalized);
|
||||
}
|
||||
|
||||
function getPlatformPublicWorkTags(entry: PlatformPublicGalleryCard) {
|
||||
if ('themeTags' in entry) {
|
||||
return entry.themeTags;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function isEdutainmentPublicWork(entry: PlatformPublicGalleryCard) {
|
||||
return getPlatformPublicWorkTags(entry).some(
|
||||
(tag) => tag === EDUTAINMENT_WORK_TAG,
|
||||
);
|
||||
}
|
||||
|
||||
export function canExposePublicWork(entry: PlatformPublicGalleryCard) {
|
||||
return isEdutainmentEntryEnabled() || !isEdutainmentPublicWork(entry);
|
||||
}
|
||||
|
||||
export function filterGeneralPublicWorks(entries: PlatformPublicGalleryCard[]) {
|
||||
return entries.filter((entry) => !isEdutainmentPublicWork(entry));
|
||||
}
|
||||
|
||||
export function filterEdutainmentPublicWorks(
|
||||
entries: PlatformPublicGalleryCard[],
|
||||
) {
|
||||
return entries.filter(isEdutainmentPublicWork);
|
||||
}
|
||||
|
||||
export function filterVisiblePublicWorks(entries: PlatformPublicGalleryCard[]) {
|
||||
return entries.filter(canExposePublicWork);
|
||||
}
|
||||
|
||||
export function findPublicWorkForHistoryEntry(
|
||||
historyEntry: PlatformBrowseHistoryEntry,
|
||||
entries: PlatformPublicGalleryCard[],
|
||||
) {
|
||||
return entries.find(
|
||||
(entry) =>
|
||||
entry.ownerUserId === historyEntry.ownerUserId &&
|
||||
entry.profileId === historyEntry.profileId,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user