import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactNode, RefObject } from "react"; import { useEffect, useRef, useState } from "react"; import { Button, Segmented, Switch } from "antd"; import { CircleDot, Eraser, Grid2x2, Group, Hand, Image as ImageIcon, Info, Moon, Music2, Palette, Puzzle, Redo2, Settings2, Square, Sun, Trash2, Type, Undo2, Upload, Video } from "lucide-react"; import { canvasThemes, type CanvasBackgroundMode, type CanvasColorTheme, type CanvasTheme } from "@/lib/canvas-theme"; import { getNodePluginId, listNodeDefinitions, useNodeRegistryVersion } from "@/lib/canvas/node-registry"; import { useThemeStore } from "@/stores/use-theme-store"; import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler"; export function CanvasToolbar({ selectedCount, canUndo, canRedo, backgroundMode, showImageInfo, onAddImage, onAddVideo, onAddAudio, onAddText, onAddConfig, onAddGroup, onAddExtensionNode, onUndo, onRedo, onUpload, onDelete, onClear, onDeselect, onBackgroundModeChange, onShowImageInfoChange, }: { selectedCount: number; canUndo: boolean; canRedo: boolean; backgroundMode: CanvasBackgroundMode; showImageInfo: boolean; onAddImage: () => void; onAddVideo: () => void; onAddAudio: () => void; onAddText: () => void; onAddConfig: () => void; onAddGroup: () => void; onAddExtensionNode: (type: string) => void; onUndo: () => void; onRedo: () => void; onUpload: () => void; onDelete: () => void; onClear: () => void; onDeselect: () => void; onBackgroundModeChange: (mode: CanvasBackgroundMode) => void; onShowImageInfoChange: (show: boolean) => void; }) { const wrapRef = useRef(null); const rootRef = useRef(null); const colorTheme = useThemeStore((state) => state.theme); const setTheme = useThemeStore((state) => state.setTheme); const theme = canvasThemes[colorTheme]; const [hovered, setHovered] = useState(null); const [tipX, setTipX] = useState(0); const [appearanceOpen, setAppearanceOpen] = useState(false); const [panelX, setPanelX] = useState(0); const [extensionsOpen, setExtensionsOpen] = useState(false); const [extPanelX, setExtPanelX] = useState(0); // 扩展(插件)节点,随注册表变化实时更新 useNodeRegistryVersion(); const extensionDefs = listNodeDefinitions().filter((def) => def.showInCreateMenu !== false && getNodePluginId(def.type) !== "builtin"); const dockStyle = { background: theme.toolbar.panel, borderColor: theme.toolbar.border, color: theme.toolbar.item, boxShadow: colorTheme === "dark" ? "0 18px 45px rgba(0,0,0,.32)" : "0 16px 40px rgba(28,25,23,.12)" }; const hoverStyle = { background: theme.toolbar.itemHover, color: theme.toolbar.activeText }; const activeStyle = { background: theme.toolbar.activeBg, color: theme.toolbar.activeText }; const tip = hovered ? toolLabel(hovered) : ""; // 点击工具栏(含弹出面板)以外的地方,关闭弹出的扩展节点/画布外观面板 useEffect(() => { if (!extensionsOpen && !appearanceOpen) return; const handlePointerDown = (event: PointerEvent) => { if (rootRef.current && !rootRef.current.contains(event.target as Node)) { setExtensionsOpen(false); setAppearanceOpen(false); } }; document.addEventListener("pointerdown", handlePointerDown, true); return () => document.removeEventListener("pointerdown", handlePointerDown, true); }, [extensionsOpen, appearanceOpen]); return (
{tip ? : null}
{extensionDefs.length ? ( { setExtPanelX(getTipX(wrapRef.current, event.currentTarget)); setAppearanceOpen(false); setExtensionsOpen((value) => !value); }} > ) : null} { setPanelX(getTipX(wrapRef.current, event.currentTarget)); setExtensionsOpen(false); setAppearanceOpen((value) => !value); }} > {selectedCount ? ( <> ) : null}
{extensionsOpen && extensionDefs.length ? (
扩展节点
{extensionDefs.map((def) => ( ))}
) : null} {appearanceOpen ? (
画布外观
主题模式
浅色 深色
网格样式
onBackgroundModeChange(value as CanvasBackgroundMode)} options={[ { value: "dots", label: ( ), }, { value: "lines", label: ( 线 ), }, { value: "blank", label: ( 空白 ), }, ]} />
图片信息
) : null}
); } function ToolbarButton({ id, label, active, hovered, activeStyle, hoverStyle, wrapRef, onTipX, onHover, onClick, disabled = false, danger = false, children, }: { id: string; label: string; active?: boolean; hovered: string | null; activeStyle?: CSSProperties; hoverStyle: CSSProperties; wrapRef: RefObject; onTipX: (x: number) => void; onHover: (id: string | null) => void; onClick?: (event: ReactMouseEvent) => void; disabled?: boolean; danger?: boolean; children: ReactNode; }) { const theme = canvasThemes[useThemeStore((state) => state.theme)]; return (