import { useState } from "react"; import { Slider } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { motion, useReducedMotion } from "motion/react"; import { Camera, RotateCcw, Send, X } from "lucide-react"; import { CanvasAngleScene } from "@/components/canvas/canvas-angle-scene"; import { SpotlightSurface } from "@/components/ui/aceternity/spotlight-surface"; import { aceternityMotion } from "@/lib/aceternity-motion"; import { canvasThemes } from "@/lib/canvas-theme"; import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; export type CanvasImageAngleParams = { horizontalAngle: number; pitchAngle: number; cameraDistance: number; wideAngle: boolean; }; type AnglePresetId = "front" | "left" | "right" | "back" | "top" | "bottom"; const defaultParams: CanvasImageAngleParams = { horizontalAngle: 45, pitchAngle: 0, cameraDistance: 4.8, wideAngle: false }; const presets: Array<{ id: AnglePresetId; label: string; horizontalAngle: number; pitchAngle: number }> = [ { id: "front", label: "正面", horizontalAngle: 0, pitchAngle: 0 }, { id: "left", label: "左侧", horizontalAngle: -90, pitchAngle: 0 }, { id: "right", label: "右侧", horizontalAngle: 90, pitchAngle: 0 }, { id: "back", label: "背面", horizontalAngle: 180, pitchAngle: 0 }, { id: "top", label: "俯拍", horizontalAngle: 0, pitchAngle: 60 }, { id: "bottom", label: "仰拍", horizontalAngle: 0, pitchAngle: -60 }, ]; function distanceLabel(value: number) { if (value <= 3) return "近景"; if (value >= 7) return "全景"; return "中景"; } export function CanvasNodeAnglePanel({ dataUrl, onClose, onConfirm }: { dataUrl: string; onClose: () => void; onConfirm: (params: CanvasImageAngleParams) => void }) { const theme = canvasThemes[useActiveTheme()]; const reducedMotion = useReducedMotion(); const [params, setParams] = useState(defaultParams); const [mode, setMode] = useState<"camera" | "skybox">("camera"); const update = (key: Key, value: CanvasImageAngleParams[Key]) => setParams((current) => ({ ...current, [key]: value })); const activePreset = presets.find((preset) => preset.horizontalAngle === params.horizontalAngle && preset.pitchAngle === params.pitchAngle); const viewLabel = activePreset?.label || "自定义"; return ( event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} >
多角度编辑器
{presets.map((preset) => ( setParams((current) => ({ ...current, horizontalAngle: preset.horizontalAngle, pitchAngle: preset.pitchAngle }))} /> ))}
update("horizontalAngle", value)} onTiltChange={(value) => update("pitchAngle", value)} />
setMode(value as "camera" | "skybox")} /> 常用角度
{presets.map((preset) => ( ))}
update("horizontalAngle", value)} /> update("pitchAngle", value)} /> update("cameraDistance", value)} />
镜头 update("wideAngle", value === "wide")} />
当前视角 {viewLabel} {params.horizontalAngle}° / {params.pitchAngle}° · {params.cameraDistance.toFixed(1)} {distanceLabel(params.cameraDistance)} onConfirm(params)}>生成新角度
); } function AngleSeg({ value, options, onChange }: { value: string; options: { label: string; value: string }[]; onChange: (value: string) => void }) { return
{options.map((option) => )}
; } function AngleSlider({ label, value, min, max, step = 1, suffix, onChange }: { label: string; value: number; min: number; max: number; step?: number; suffix: string; onChange: (value: number) => void }) { return (
{label} {Number.isInteger(value) ? value : value.toFixed(1)}{suffix.startsWith("°") ? suffix : ` ${suffix}`}
); } function AnglePresetButton({ active, label, onClick }: { active: boolean; label: string; onClick?: () => void }) { return ; }