feat: refresh creation config and visual assets
This commit is contained in:
@@ -6069,6 +6069,10 @@ export function PlatformEntryFlowShellImpl({
|
||||
pushAppHistoryPath('/runtime/baby-love-drawing');
|
||||
}, [setSelectionStage]);
|
||||
|
||||
const startChildMotionDemo = useCallback(() => {
|
||||
window.location.assign('/child-motion-demo');
|
||||
}, []);
|
||||
|
||||
const resolveBabyObjectMatchRuntimeDraft = useCallback(
|
||||
async (entry: PlatformPublicGalleryCard) => {
|
||||
if (!isEdutainmentGalleryEntry(entry)) {
|
||||
@@ -11806,6 +11810,7 @@ export function PlatformEntryFlowShellImpl({
|
||||
onOpenCreateWorld={openCreationTypePicker}
|
||||
onOpenCreateTypePicker={openCreationTypePicker}
|
||||
onOpenGalleryDetail={openPublicGalleryDetail}
|
||||
onOpenChildMotionDemo={startChildMotionDemo}
|
||||
onOpenBabyLoveDrawing={startBabyLoveDrawingRuntime}
|
||||
onOpenRecommendGalleryDetail={openRecommendGalleryDetail}
|
||||
recommendRuntimeContent={recommendRuntimeContent}
|
||||
|
||||
@@ -182,3 +182,29 @@ test('edutainment switch hides baby object match creation entry from database co
|
||||
getVisiblePlatformCreationTypes(hiddenCards).map((item) => item.id),
|
||||
).toEqual(['puzzle']);
|
||||
});
|
||||
|
||||
test('baby object match entry is visible and open when database marks it creatable', () => {
|
||||
const cards = derivePlatformCreationTypes([
|
||||
{
|
||||
id: 'baby-object-match',
|
||||
title: '宝贝识物',
|
||||
subtitle: '亲子识物分类',
|
||||
badge: '可创建',
|
||||
imageSrc: '/child-motion-demo/picture-book-grass-stage.png',
|
||||
visible: true,
|
||||
open: true,
|
||||
sortOrder: 90,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(getVisiblePlatformCreationTypes(cards)).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'baby-object-match',
|
||||
hidden: false,
|
||||
locked: false,
|
||||
}),
|
||||
]);
|
||||
expect(isPlatformCreationTypeVisible(cards, 'baby-object-match')).toBe(true);
|
||||
expect(isPlatformCreationTypeOpen(cards, 'baby-object-match')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -761,6 +761,7 @@ function renderLoggedOutHomeView(
|
||||
| 'latestEntries'
|
||||
| 'onOpenGalleryDetail'
|
||||
| 'onOpenRecommendGalleryDetail'
|
||||
| 'onOpenChildMotionDemo'
|
||||
| 'onSearchPublicCode'
|
||||
| 'recommendRuntimeContent'
|
||||
| 'activeRecommendEntryKey'
|
||||
@@ -814,6 +815,7 @@ function renderLoggedOutHomeView(
|
||||
onOpenCreateWorld={vi.fn()}
|
||||
onOpenCreateTypePicker={vi.fn()}
|
||||
onOpenGalleryDetail={overrides.onOpenGalleryDetail ?? vi.fn()}
|
||||
onOpenChildMotionDemo={overrides.onOpenChildMotionDemo}
|
||||
onOpenRecommendGalleryDetail={overrides.onOpenRecommendGalleryDetail}
|
||||
recommendRuntimeContent={
|
||||
overrides.recommendRuntimeContent ?? (
|
||||
@@ -912,6 +914,7 @@ function renderStatefulLoggedOutHomeView(
|
||||
| 'latestEntries'
|
||||
| 'onOpenGalleryDetail'
|
||||
| 'onOpenRecommendGalleryDetail'
|
||||
| 'onOpenChildMotionDemo'
|
||||
| 'onSearchPublicCode'
|
||||
| 'recommendRuntimeContent'
|
||||
| 'activeRecommendEntryKey'
|
||||
@@ -970,6 +973,7 @@ function renderStatefulLoggedOutHomeView(
|
||||
onOpenCreateWorld={vi.fn()}
|
||||
onOpenCreateTypePicker={vi.fn()}
|
||||
onOpenGalleryDetail={overrides.onOpenGalleryDetail ?? vi.fn()}
|
||||
onOpenChildMotionDemo={overrides.onOpenChildMotionDemo}
|
||||
onOpenRecommendGalleryDetail={overrides.onOpenRecommendGalleryDetail}
|
||||
recommendRuntimeContent={
|
||||
overrides.recommendRuntimeContent ?? (
|
||||
@@ -2207,6 +2211,7 @@ test('discover search fuzzy matches public work id, name, author and description
|
||||
test('mobile discover keeps edutainment works in the last dedicated channel only', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSearchPublicCode = vi.fn();
|
||||
const onOpenChildMotionDemo = vi.fn();
|
||||
const generalEntry = buildTaggedPuzzleEntry('normal01', '普通拼图作品', [
|
||||
'儿童教育',
|
||||
]);
|
||||
@@ -2227,6 +2232,7 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
|
||||
|
||||
renderStatefulLoggedOutHomeView({
|
||||
latestEntries: [edutainmentEntry, generalEntry],
|
||||
onOpenChildMotionDemo,
|
||||
onSearchPublicCode,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
@@ -2259,6 +2265,12 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
|
||||
name: /儿童动作热身 Demo/u,
|
||||
}),
|
||||
).toBeTruthy();
|
||||
const warmupButton = within(discoverPanel).getByRole('button', {
|
||||
name: /热身关卡/u,
|
||||
});
|
||||
expect(warmupButton).toBeTruthy();
|
||||
await user.click(warmupButton);
|
||||
expect(onOpenChildMotionDemo).toHaveBeenCalledTimes(1);
|
||||
expect(within(discoverPanel).queryByText('普通拼图作品')).toBeNull();
|
||||
|
||||
const searchInput =
|
||||
@@ -2269,6 +2281,23 @@ test('mobile discover keeps edutainment works in the last dedicated channel only
|
||||
expect(onSearchPublicCode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('desktop discover shows child motion demo in edutainment channel', async () => {
|
||||
mockDesktopLayout();
|
||||
const user = userEvent.setup();
|
||||
const onOpenChildMotionDemo = vi.fn();
|
||||
|
||||
renderStatefulLoggedOutHomeView({
|
||||
onOpenChildMotionDemo,
|
||||
});
|
||||
await user.click(screen.getByRole('button', { name: '发现' }));
|
||||
await user.click(screen.getByRole('button', { name: '寓教于乐' }));
|
||||
|
||||
const warmupButton = screen.getByRole('button', { name: /热身关卡/u });
|
||||
expect(warmupButton).toBeTruthy();
|
||||
await user.click(warmupButton);
|
||||
expect(onOpenChildMotionDemo).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('mobile discover hides edutainment channel and work when switch is disabled', async () => {
|
||||
vi.stubEnv('VITE_ENABLE_EDUTAINMENT_ENTRY', 'false');
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -170,6 +170,7 @@ export interface RpgEntryHomeViewProps {
|
||||
onOpenCreateWorld: () => void;
|
||||
onOpenCreateTypePicker: () => void;
|
||||
onOpenGalleryDetail: (entry: PlatformPublicGalleryCard) => void;
|
||||
onOpenChildMotionDemo?: () => void;
|
||||
onOpenBabyLoveDrawing?: () => void;
|
||||
onOpenRecommendGalleryDetail?: (entry: PlatformPublicGalleryCard) => void;
|
||||
recommendRuntimeContent?: ReactNode;
|
||||
@@ -320,6 +321,11 @@ const BABY_LOVE_DRAWING_DEFAULT_CARD = {
|
||||
subtitle: '空白画板',
|
||||
summary: '挥动小手画一张画。',
|
||||
};
|
||||
const CHILD_MOTION_DEMO_DEFAULT_CARD = {
|
||||
title: '热身关卡',
|
||||
subtitle: '动作识别热身',
|
||||
summary: '站位、招手和左右手活动。',
|
||||
};
|
||||
|
||||
const PLATFORM_RANKING_TABS: Array<{
|
||||
id: PlatformRankingTab;
|
||||
@@ -3642,6 +3648,7 @@ export function RpgEntryHomeView({
|
||||
onResumeSave,
|
||||
onOpenCreateTypePicker,
|
||||
onOpenGalleryDetail,
|
||||
onOpenChildMotionDemo,
|
||||
onOpenBabyLoveDrawing,
|
||||
onOpenRecommendGalleryDetail,
|
||||
recommendRuntimeContent,
|
||||
@@ -5352,7 +5359,9 @@ export function RpgEntryHomeView({
|
||||
<section className="platform-mobile-home-feed">
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取公开作品..." />
|
||||
) : edutainmentFeedEntries.length > 0 || onOpenBabyLoveDrawing ? (
|
||||
) : edutainmentFeedEntries.length > 0 ||
|
||||
onOpenChildMotionDemo ||
|
||||
onOpenBabyLoveDrawing ? (
|
||||
<div className="grid min-w-0 gap-3">
|
||||
{edutainmentFeedEntries.map((entry) => {
|
||||
const cardKey = buildPublicGalleryCardKey(entry);
|
||||
@@ -5368,6 +5377,24 @@ export function RpgEntryHomeView({
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{onOpenChildMotionDemo ? (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-edutainment-level-card"
|
||||
onClick={onOpenChildMotionDemo}
|
||||
>
|
||||
<span className="platform-edutainment-level-card__icon">
|
||||
<Camera className="h-7 w-7" />
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__body">
|
||||
<strong>{CHILD_MOTION_DEMO_DEFAULT_CARD.title}</strong>
|
||||
<span>{CHILD_MOTION_DEMO_DEFAULT_CARD.subtitle}</span>
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__summary">
|
||||
{CHILD_MOTION_DEMO_DEFAULT_CARD.summary}
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
{onOpenBabyLoveDrawing ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -5530,7 +5557,9 @@ export function RpgEntryHomeView({
|
||||
<SectionHeader title={EDUTAINMENT_WORK_TAG} detail="EDUTAINMENT" />
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取公开作品..." />
|
||||
) : edutainmentFeedEntries.length > 0 || onOpenBabyLoveDrawing ? (
|
||||
) : edutainmentFeedEntries.length > 0 ||
|
||||
onOpenChildMotionDemo ||
|
||||
onOpenBabyLoveDrawing ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{edutainmentFeedEntries.map((entry) => (
|
||||
<WorldCard
|
||||
@@ -5541,6 +5570,24 @@ export function RpgEntryHomeView({
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
{onOpenChildMotionDemo ? (
|
||||
<button
|
||||
type="button"
|
||||
className="platform-edutainment-level-card"
|
||||
onClick={onOpenChildMotionDemo}
|
||||
>
|
||||
<span className="platform-edutainment-level-card__icon">
|
||||
<Camera className="h-7 w-7" />
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__body">
|
||||
<strong>{CHILD_MOTION_DEMO_DEFAULT_CARD.title}</strong>
|
||||
<span>{CHILD_MOTION_DEMO_DEFAULT_CARD.subtitle}</span>
|
||||
</span>
|
||||
<span className="platform-edutainment-level-card__summary">
|
||||
{CHILD_MOTION_DEMO_DEFAULT_CARD.summary}
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
{onOpenBabyLoveDrawing ? (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user