import { ArrowLeft, Loader2, Play, Send, Shuffle, } from 'lucide-react'; import { type CSSProperties, useState } from 'react'; import type { JumpHopDraftResponse, JumpHopPath, JumpHopTileAsset, JumpHopWorkProfileResponse, } from '../../../packages/shared/src/contracts/jumpHop'; import { formatJumpHopDurationLabel, selectJumpHopTileAsset, } from '../../services/jump-hop/jumpHopRuntimeModel'; import { useJumpHopLeaderboard } from '../../services/jump-hop/useJumpHopLeaderboard'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; type JumpHopResultViewProps = { profile: | (JumpHopDraftResponse & { characterImageSrc?: string | null; tileAtlasImageSrc?: string | null; pathPreviewImageSrc?: string | null; }) | (JumpHopWorkProfileResponse & { characterImageSrc?: string | null; tileAtlasImageSrc?: string | null; pathPreviewImageSrc?: string | null; }); isBusy?: boolean; error?: string | null; onBack: () => void; onEdit: () => void; onStartTestRun: () => void; onPublish: () => void; onRegenerateTiles: () => void; }; function isJumpHopWorkProfile( profile: JumpHopResultViewProps['profile'], ): profile is JumpHopWorkProfileResponse { return 'summary' in profile; } const tileToneByType: Record = { accent: '#c4b5fd', bonus: '#fde68a', finish: '#86efac', normal: '#e0f2fe', start: '#bae6fd', target: '#fecdd3', }; const JUMP_HOP_TAONIER_CHARACTER_IMAGE_SRC = '/branding/jump-hop-taonier-character.png'; function JumpHopDefaultCharacterPreview() { return (
); } function JumpHopTilePoolPreview({ tileAssets, tileAtlasAsset, tileAtlasFallbackSrc, }: { tileAssets: JumpHopTileAsset[]; tileAtlasAsset?: JumpHopDraftResponse['tileAtlasAsset'] | null; tileAtlasFallbackSrc?: string | null; }) { const visibleTiles = tileAssets.slice(0, 25); const atlasSrc = tileAtlasAsset?.imageSrc?.trim() || tileAtlasFallbackSrc?.trim() || ''; const atlasRefreshKey = tileAtlasAsset?.assetObjectId || atlasSrc; if (visibleTiles.length > 0) { return (
{visibleTiles.map((tile, index) => (
{tile.imageSrc ? ( ) : ( )}
))}
); } if (atlasSrc) { return ( ); } return (
{Array.from({ length: 25 }).map((_, index) => ( ))}
); } function JumpHopFirstPlatformsPreview({ path, tileAssets, }: { path: JumpHopPath | null | undefined; tileAssets: JumpHopTileAsset[]; }) { const platforms = (path?.platforms ?? []).slice(0, 3); return (
{platforms.map((platform, index) => { const asset = selectJumpHopTileAsset( tileAssets, path?.seed, index, platform.platformId, ); const style = { left: `${50 + (index - 1) * 24}%`, top: `${68 - index * 22}%`, width: `${34 - index * 3}%`, zIndex: 10 + index, } as CSSProperties; return (
{asset?.imageSrc ? ( ) : (
)}
); })} {platforms.length === 0 ? (
路径
) : null}
); } function JumpHopResultLeaderboard({ profileId, }: { profileId?: string | null; }) { const { leaderboard, isLoading, error } = useJumpHopLeaderboard(profileId); const items = leaderboard?.items ?? []; return (
排行榜
{isLoading ? ( ) : null}
{items.slice(0, 5).map((entry) => (
{entry.rank} {entry.playerId} {entry.successfulJumpCount} 跳 {formatJumpHopDurationLabel(entry.durationMs)}
))} {items.length === 0 ? (
{error ?? '暂无成绩'}
) : null}
); } export function JumpHopResultView({ profile, isBusy = false, error = null, onBack, onEdit, onStartTestRun, onPublish, onRegenerateTiles, }: JumpHopResultViewProps) { const [isPublishing, setIsPublishing] = useState(false); const isWorkProfile = isJumpHopWorkProfile(profile); const draft = isWorkProfile ? profile.draft : profile; const safeDraft = draft as JumpHopDraftResponse & { characterAsset: NonNullable; tileAtlasAsset: NonNullable; path: NonNullable; }; const path = isWorkProfile ? profile.path : safeDraft.path; const tileAtlasAsset = isWorkProfile ? profile.tileAtlasAsset : safeDraft.tileAtlasAsset; const tileAssets = isWorkProfile ? profile.tileAssets : safeDraft.tileAssets; const profileId = isWorkProfile ? profile.summary.profileId : safeDraft.profileId; const canShowLeaderboard = isWorkProfile && profile.summary.publicationStatus === 'published'; const titleSource = isWorkProfile ? profile.summary.workTitle : profile.workTitle; const summarySource = isWorkProfile ? profile.summary.workDescription : profile.workDescription; const title = titleSource?.trim() || safeDraft.workTitle.trim() || '跳一跳'; const summary = summarySource?.trim() || safeDraft.workDescription.trim(); const hasAssets = Boolean( profile.tileAtlasImageSrc?.trim() || profile.pathPreviewImageSrc?.trim() || tileAtlasAsset?.imageSrc?.trim() || tileAssets.length > 0 || path?.platforms.length, ); const handlePublish = async () => { setIsPublishing(true); try { await Promise.resolve(onPublish()); } finally { setIsPublishing(false); } }; return (
{title}
{summary ? (
{summary}
) : null}
{!hasAssets ? (
生成资源尚未准备完成。
) : null}
结果操作
{canShowLeaderboard ? ( ) : null} {error ? (
{error}
) : null}
); } export default JumpHopResultView;