fix: refine world 3d node controls
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-28 22:21:24 +08:00
parent 44dee6314a
commit dc5ec5f87a

View File

@ -258,6 +258,7 @@ export function World3DNode({
[overlayChromeScale]
);
const captureEnabled = data.status === "completed" && (activeView === "splat" || activeView === "mesh");
const showTopLeftControls = data.status === "completed";
useEffect(() => {
if (data.status !== "completed") return;
@ -535,8 +536,8 @@ export function World3DNode({
{data.status === "generating" ? (
<div className="relative flex h-full w-full select-none items-center justify-center overflow-hidden bg-[#f7f9fc] dark:bg-zinc-900">
<div className="absolute left-4 top-4" style={placeholderBadgeStyle}>
<span className="flex h-11 w-11 items-center justify-center rounded-full border border-[#dde5f4] bg-white text-[#566374] shadow-[0_0_0_1px_rgba(219,226,238,0.22)] dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200">
<Sparkles className="h-5 w-5" />
<span className="flex h-8 w-8 items-center justify-center rounded-full border border-[#dde5f4] bg-white text-[#566374] shadow-[0_0_0_1px_rgba(219,226,238,0.22)] dark:border-zinc-700 dark:bg-zinc-950 dark:text-zinc-200">
<Sparkles className="h-3.5 w-3.5" />
</span>
</div>
<div className="flex flex-col items-center gap-2 px-6 text-center" style={placeholderCenterStyle}>
@ -554,8 +555,8 @@ export function World3DNode({
) : data.status === "failed" ? (
<div className="relative flex h-full w-full select-none items-center justify-center overflow-hidden bg-[#fff7f8] dark:bg-red-950/30">
<div className="absolute left-4 top-4" style={placeholderBadgeStyle}>
<span className="flex h-11 w-11 items-center justify-center rounded-full border border-[#f2d7dd] bg-white text-[#d24f63] shadow-[0_0_0_1px_rgba(242,215,221,0.22)] dark:border-red-900/60 dark:bg-zinc-950 dark:text-red-300">
<AlertCircle className="h-5 w-5" />
<span className="flex h-8 w-8 items-center justify-center rounded-full border border-[#f2d7dd] bg-white text-[#d24f63] shadow-[0_0_0_1px_rgba(242,215,221,0.22)] dark:border-red-900/60 dark:bg-zinc-950 dark:text-red-300">
<AlertCircle className="h-3.5 w-3.5" />
</span>
</div>
<div className="flex flex-col items-center gap-2 px-6 text-center" style={placeholderCenterStyle}>
@ -598,91 +599,91 @@ export function World3DNode({
</>
)}
<div
className="absolute left-3 top-3 z-20 flex items-center gap-2"
style={topLeftControlsStyle}
>
<div className="flex rounded-full border border-white/10 bg-black/55 p-1 backdrop-blur-sm">
{VIEW_META.filter((view) => availableViews.includes(view.id)).map((view) => (
(() => {
{showTopLeftControls ? (
<div
className="absolute left-3 top-3 z-20 flex items-center gap-1.5"
style={topLeftControlsStyle}
>
<div className="flex rounded-full border border-white/10 bg-black/55 p-0.5 backdrop-blur-sm">
{VIEW_META.filter((view) => availableViews.includes(view.id)).map((view) => {
const viewItem = getCanvasToolbarItem(canvasToolbarConfig, view.toolbarKey);
return (
<Button
className="h-7 min-w-7 rounded-full px-2 text-[10px]"
key={view.id}
onClick={() => handleSetView(view.id)}
size="sm"
title={viewItem.label}
type="button"
variant={activeView === view.id ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={activeView !== view.id}
className="h-3.5 w-3.5"
fallback={view.icon}
item={viewItem}
/>
</Button>
<Button
className="h-5 min-w-5 rounded-full px-1.5 text-[9px]"
key={view.id}
onClick={() => handleSetView(view.id)}
size="sm"
title={viewItem.label}
type="button"
variant={activeView === view.id ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={activeView !== view.id}
className="h-2.5 w-2.5"
fallback={view.icon}
item={viewItem}
/>
</Button>
);
})()
))}
})}
</div>
{activeView === "splat" ? (
<div className="flex rounded-full border border-white/10 bg-black/55 p-0.5 backdrop-blur-sm">
<Button
className="h-5 rounded-full px-1.5"
onClick={handleToggleFirstPerson}
size="sm"
title={world3dFirstPersonItem.label}
type="button"
variant={data.firstPerson ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={!data.firstPerson}
className="h-2.5 w-2.5"
fallback={Waypoints}
item={world3dFirstPersonItem}
/>
</Button>
<Button
className="h-5 rounded-full px-1.5"
onClick={handleToggleHotspotMode}
size="sm"
title={world3dHotspotItem.label}
type="button"
variant={interactionMode === "hotspot" ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={interactionMode !== "hotspot"}
className="h-2.5 w-2.5"
fallback={Crosshair}
item={world3dHotspotItem}
/>
</Button>
</div>
) : null}
{captureEnabled ? (
<div className="flex rounded-full border border-white/10 bg-black/55 p-0.5 backdrop-blur-sm">
<Button
className="h-5 rounded-full px-1.5"
disabled={!viewerCapture || isCapturingSnapshot}
onClick={() => void handleCaptureSnapshot()}
size="sm"
title="拍照"
type="button"
variant="ghost"
>
{isCapturingSnapshot ? (
<Loader2 className="h-2.5 w-2.5 animate-spin text-white" />
) : (
<Camera className="h-2.5 w-2.5 text-white" />
)}
</Button>
</div>
) : null}
</div>
{activeView === "splat" && data.status === "completed" ? (
<div className="flex rounded-full border border-white/10 bg-black/55 p-1 backdrop-blur-sm">
<Button
className="h-7 rounded-full px-2"
onClick={handleToggleFirstPerson}
size="sm"
title={world3dFirstPersonItem.label}
type="button"
variant={data.firstPerson ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={!data.firstPerson}
className="h-3.5 w-3.5"
fallback={Waypoints}
item={world3dFirstPersonItem}
/>
</Button>
<Button
className="h-7 rounded-full px-2"
onClick={handleToggleHotspotMode}
size="sm"
title={world3dHotspotItem.label}
type="button"
variant={interactionMode === "hotspot" ? "secondary" : "ghost"}
>
<CanvasToolbarIcon
active={interactionMode !== "hotspot"}
className="h-3.5 w-3.5"
fallback={Crosshair}
item={world3dHotspotItem}
/>
</Button>
</div>
) : null}
{captureEnabled ? (
<div className="flex rounded-full border border-white/10 bg-black/55 p-1 backdrop-blur-sm">
<Button
className="h-7 rounded-full px-2"
disabled={!viewerCapture || isCapturingSnapshot}
onClick={() => void handleCaptureSnapshot()}
size="sm"
title="拍照"
type="button"
variant="ghost"
>
{isCapturingSnapshot ? (
<Loader2 className="h-3.5 w-3.5 animate-spin text-white" />
) : (
<Camera className="h-3.5 w-3.5 text-white" />
)}
</Button>
</div>
) : null}
</div>
) : null}
{isHovered && data.status === "completed" && (
<div