"use client"; import { Box, Download, ImageIcon, Link2, Sparkles, Waypoints, } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { World3DViewer } from "@/components/canvas/world3d/World3DViewer"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import type { World3DViewMode } from "@/types/canvas"; type SharedWorldPageProps = { title: string; prompt?: string | null; caption?: string | null; model?: string | null; worldUrl?: string | null; imageUrl?: string | null; meshUrl?: string | null; splatUrl?: string | null; previewImageUrl?: string | null; semanticsMetadata?: Record | null; shareUrl: string; worlds?: SharedWorldItem[]; }; type SharedWorldItem = { id: string; title: string; prompt?: string | null; caption?: string | null; model?: string | null; worldUrl?: string | null; imageUrl?: string | null; meshUrl?: string | null; splatUrl?: string | null; previewImageUrl?: string | null; semanticsMetadata?: Record | null; }; const VIEW_OPTIONS: Array<{ id: World3DViewMode; label: string; icon: typeof Sparkles; }> = [ { id: "splat", label: "World", icon: Sparkles }, { id: "image", label: "图像", icon: ImageIcon }, { id: "mesh", label: "Mesh", icon: Box }, ]; function resolveAvailableViews(params: { worldUrl?: string | null; imageUrl?: string | null; meshUrl?: string | null; splatUrl?: string | null; }) { const views: World3DViewMode[] = []; if (params.splatUrl) views.push("splat"); if (params.imageUrl) views.push("image"); if (params.meshUrl) views.push("mesh"); return views; } function inferInitialView(views: World3DViewMode[]) { if (views.includes("splat")) return "splat"; if (views.includes("image")) return "image"; if (views.includes("mesh")) return "mesh"; return "image"; } export function WorldSharePage({ title, prompt, caption, model, worldUrl, imageUrl, meshUrl, splatUrl, previewImageUrl, semanticsMetadata, shareUrl, worlds, }: SharedWorldPageProps) { const worldItems = useMemo( () => worlds?.length ? worlds : [ { id: "shared-world", title, prompt, caption, model, worldUrl, imageUrl, meshUrl, splatUrl, previewImageUrl, semanticsMetadata, }, ], [ caption, imageUrl, meshUrl, model, previewImageUrl, prompt, semanticsMetadata, splatUrl, title, worldUrl, worlds, ] ); const [activeWorldId, setActiveWorldId] = useState( () => worldItems[0]?.id || "shared-world" ); const activeWorld = worldItems.find((item) => item.id === activeWorldId) || worldItems[0]; const availableViews = useMemo( () => resolveAvailableViews({ worldUrl: activeWorld?.worldUrl, imageUrl: activeWorld?.imageUrl, meshUrl: activeWorld?.meshUrl, splatUrl: activeWorld?.splatUrl, }), [activeWorld?.imageUrl, activeWorld?.meshUrl, activeWorld?.splatUrl, activeWorld?.worldUrl] ); const [activeView, setActiveView] = useState( inferInitialView(availableViews) ); const [firstPerson, setFirstPerson] = useState(true); const [copied, setCopied] = useState(false); useEffect(() => { if (worldItems.some((item) => item.id === activeWorldId)) return; setActiveWorldId(worldItems[0]?.id || "shared-world"); }, [activeWorldId, worldItems]); useEffect(() => { if (availableViews.includes(activeView)) return; setActiveView(inferInitialView(availableViews)); }, [activeView, availableViews]); useEffect(() => { setFirstPerson(true); }, [activeWorld?.id]); const primaryDescription = activeWorld?.caption || activeWorld?.prompt || "这个 3D 世界已开启只读分享。"; const activeTitle = activeWorld?.title || title; const activeMeshUrl = activeWorld?.meshUrl || null; const handleCopyShareUrl = async () => { try { const absoluteUrl = new URL(shareUrl, window.location.origin).toString(); await navigator.clipboard.writeText(absoluteUrl); setCopied(true); window.setTimeout(() => setCopied(false), 1500); } catch { setCopied(false); } }; const handleDownloadMesh = () => { if (!activeMeshUrl) return; const link = document.createElement("a"); link.href = activeMeshUrl; link.download = `shared-world-${activeWorld?.id?.slice(0, 8) || "asset"}.glb`; link.click(); }; return (
{worldItems.length > 1 ? `${worldItems.length} 个 3D 世界分享` : "3D 世界分享"} {availableViews.map((view) => ( {VIEW_OPTIONS.find((item) => item.id === view)?.label || view.toUpperCase()} ))}

{activeTitle}

{primaryDescription}

{activeMeshUrl ? ( ) : null}
{VIEW_OPTIONS.filter((item) => availableViews.includes(item.id)).map((item) => ( ))}
{activeView === "splat" && activeWorld?.splatUrl ? ( ) : null}
{worldItems.length > 1 ? (

世界列表

{worldItems.map((item, index) => { const thumbnail = item.previewImageUrl || item.imageUrl || null; const isActive = item.id === activeWorld?.id; return ( ); })}
) : null}

查看说明

`World` 视图最接近原生 3D 世界体验,支持左键转向、右键平移、滚轮缩放,以及方向键或 `WASD` 六向移动。

当前页面是只读分享页,不会修改原项目或原画布内容。

资源状态

World
{activeWorld?.splatUrl ? "可查看" : "暂无"}
图像
{activeWorld?.imageUrl || activeWorld?.previewImageUrl ? "可查看" : "暂无"}
Mesh
{activeWorld?.meshUrl ? "可下载" : "暂无"}
); }