fix: simplify image annotator toolbar
This commit is contained in:
parent
593a00cebc
commit
3f1bc41ae9
@ -4,7 +4,7 @@ import React, { useRef, useState, useEffect, useCallback } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { X, Pencil, Circle, ArrowRight, Type, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2, ZoomIn, ZoomOut, Loader2, Move3D, Layers, Upload as UploadIcon, Image as ImageIcon, Droplet, Redo, MousePointer2 } from "lucide-react";
|
||||
import { X, Check, Undo, Trash2, Scissors, Sparkles, Download, Expand, Move, Maximize2, ZoomIn, ZoomOut, Loader2, Move3D, Layers, Upload as UploadIcon, Image as ImageIcon, Droplet, Redo, MousePointer2 } from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Input } from "./ui/input";
|
||||
import { Slider } from "./ui/slider";
|
||||
@ -56,20 +56,6 @@ interface ImageAnnotatorProps {
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const COLORS = [
|
||||
"#FF0000", // 红色
|
||||
"#FF6B00", // 橙色
|
||||
"#FFCC00", // 黄色
|
||||
"#00CC00", // 绿色
|
||||
"#0099FF", // 蓝色
|
||||
"#9933FF", // 紫色
|
||||
"#FF0099", // 粉色
|
||||
"#FFFFFF", // 白色
|
||||
"#000000", // 黑色
|
||||
];
|
||||
|
||||
const LINE_WIDTHS = [2, 4, 6, 8];
|
||||
|
||||
export function ImageAnnotator({
|
||||
imageUrl,
|
||||
onConfirm,
|
||||
@ -85,9 +71,9 @@ export function ImageAnnotator({
|
||||
const [ctx, setCtx] = useState<CanvasRenderingContext2D | null>(null);
|
||||
const [image, setImage] = useState<HTMLImageElement | null>(null);
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
const [tool, setTool] = useState<Tool>("pen");
|
||||
const [color, setColor] = useState("#FF0000");
|
||||
const [lineWidth, setLineWidth] = useState(4);
|
||||
const tool: Tool = "pen";
|
||||
const color = "#FF0000";
|
||||
const lineWidth = 4;
|
||||
const [actions, setActions] = useState<DrawingAction[]>([]);
|
||||
const [currentAction, setCurrentAction] = useState<DrawingAction | null>(null);
|
||||
const [textInput, setTextInput] = useState("");
|
||||
@ -1463,285 +1449,6 @@ export function ImageAnnotator({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating Toolbar - Top (Tools & Actions) */}
|
||||
{!showSceneFusion && !isResizeMode && (
|
||||
<div className="absolute left-1/2 top-[72px] z-20 -translate-x-1/2 pointer-events-none w-full max-w-[90vw] flex justify-center">
|
||||
<div className="pointer-events-auto flex items-center gap-1 rounded-[30px] border border-black/10 bg-white/95 p-2 shadow-xl backdrop-blur-xl dark:bg-zinc-900/95 dark:border-white/10 overflow-x-auto max-w-full scrollbar-hide">
|
||||
|
||||
{!isResizeMode && (
|
||||
<>
|
||||
{/* Drawing Tools */}
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
||||
tool === "pen" && "bg-black text-white hover:bg-black dark:bg-white dark:text-black dark:hover:bg-white"
|
||||
)}
|
||||
onClick={() => setTool("pen")}
|
||||
>
|
||||
<Pencil className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>画笔</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
||||
tool === "arrow" && "bg-black text-white hover:bg-black dark:bg-white dark:text-black dark:hover:bg-white"
|
||||
)}
|
||||
onClick={() => setTool("arrow")}
|
||||
>
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>箭头</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
||||
tool === "circle" && "bg-black text-white hover:bg-black dark:bg-white dark:text-black dark:hover:bg-white"
|
||||
)}
|
||||
onClick={() => setTool("circle")}
|
||||
>
|
||||
<Circle className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>圆圈</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
||||
tool === "text" && "bg-black text-white hover:bg-black dark:bg-white dark:text-black dark:hover:bg-white"
|
||||
)}
|
||||
onClick={() => setTool("text")}
|
||||
>
|
||||
<Type className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>文字</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<div className="mx-1 h-7 w-px bg-black/10 dark:bg-white/10" />
|
||||
|
||||
{/* Colors */}
|
||||
<div className="flex items-center gap-1 px-1">
|
||||
{COLORS.slice(0, 5).map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
className={cn(
|
||||
"h-5 w-5 rounded-full border border-black/15 transition-all",
|
||||
color === c && "scale-110 ring-2 ring-black/25 dark:ring-white/25"
|
||||
)}
|
||||
style={{ backgroundColor: c }}
|
||||
onClick={() => setColor(c)}
|
||||
title={c}
|
||||
/>
|
||||
))}
|
||||
<input
|
||||
type="color"
|
||||
value={color}
|
||||
onChange={(event) => setColor(event.target.value)}
|
||||
className="h-5 w-5 cursor-pointer rounded-full border border-black/15 bg-transparent p-0"
|
||||
title="自定义颜色"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx-1 h-7 w-px bg-black/10 dark:bg-white/10" />
|
||||
|
||||
{/* Line Width */}
|
||||
<div className="flex items-center gap-1">
|
||||
{LINE_WIDTHS.map((w) => (
|
||||
<button
|
||||
key={w}
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
||||
lineWidth === w && "bg-zinc-200 dark:bg-zinc-700"
|
||||
)}
|
||||
onClick={() => setLineWidth(w)}
|
||||
title={`线宽 ${w}px`}
|
||||
>
|
||||
<div className="rounded-full bg-zinc-800 dark:bg-zinc-200" style={{ width: w + 2, height: w + 2 }} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mx-1 h-7 w-px bg-black/10 dark:bg-white/10" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* AI Actions */}
|
||||
<TooltipProvider delayDuration={0}>
|
||||
{!isResizeMode && (
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleRemoveBackground}
|
||||
disabled={isRemovingBg}
|
||||
>
|
||||
<Scissors className={cn("h-5 w-5", isRemovingBg && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>移除背景</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleEnhance}
|
||||
disabled={isEnhancing}
|
||||
>
|
||||
<Sparkles className={cn("h-5 w-5", isEnhancing && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>变清晰</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleExtend}
|
||||
disabled={isExtending}
|
||||
>
|
||||
<Expand className={cn("h-5 w-5", isExtending && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>图像拓展</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleWatermarkRemove}
|
||||
disabled={isRemovingWatermark}
|
||||
>
|
||||
<Droplet className={cn("h-5 w-5", isRemovingWatermark && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>去水印</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="mx-1 h-7 w-px bg-black/10 dark:bg-white/10" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleResize}
|
||||
>
|
||||
<Maximize2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>尺寸调整</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleAngleEdit}
|
||||
>
|
||||
<Move3D className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>角度调整</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={() => setShowSceneFusion(true)}
|
||||
>
|
||||
<Layers className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>批量处理</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="mx-1 h-7 w-px bg-black/10 dark:bg-white/10" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* History */}
|
||||
{!isResizeMode && (
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleUndo}
|
||||
disabled={actions.length === 0}
|
||||
>
|
||||
<Undo className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>撤销</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleClear}
|
||||
disabled={actions.length === 0}
|
||||
>
|
||||
<Trash2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>清空</TooltipContent>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="relative flex h-full w-full bg-[#f4f4f5] dark:bg-[#09090b]">
|
||||
{showSceneFusion ? (
|
||||
@ -1758,7 +1465,7 @@ export function ImageAnnotator({
|
||||
{/* Main Canvas / Image Area */}
|
||||
<div className={cn(
|
||||
"relative z-10 flex items-center justify-center w-full h-full p-8",
|
||||
isResizeMode ? "pb-36" : "pb-24"
|
||||
isResizeMode ? "pb-36" : "pb-32"
|
||||
)}>
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center gap-3">
|
||||
@ -2015,23 +1722,164 @@ export function ImageAnnotator({
|
||||
)}
|
||||
|
||||
{!showSceneFusion && !isResizeMode && (
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-20 pointer-events-none">
|
||||
<div className="pointer-events-auto flex items-center gap-2 p-1.5 bg-background/95 backdrop-blur-xl border border-border/50 shadow-2xl rounded-[24px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="rounded-xl px-4 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<div className="h-4 w-px bg-border/50" />
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
className="rounded-xl px-6 bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
{confirmButtonText}
|
||||
</Button>
|
||||
<div className="absolute bottom-8 left-1/2 z-20 w-[min(94vw,1120px)] -translate-x-1/2 pointer-events-none">
|
||||
<div className="pointer-events-auto flex items-center justify-between gap-3 overflow-x-auto rounded-[28px] border border-border/60 bg-background px-3 py-2 shadow-2xl scrollbar-hide">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<div className="flex min-w-max items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleRemoveBackground}
|
||||
disabled={isRemovingBg}
|
||||
>
|
||||
<Scissors className={cn("h-5 w-5", isRemovingBg && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>移除背景</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleEnhance}
|
||||
disabled={isEnhancing}
|
||||
>
|
||||
<Sparkles className={cn("h-5 w-5", isEnhancing && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>变清晰</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleExtend}
|
||||
disabled={isExtending}
|
||||
>
|
||||
<Expand className={cn("h-5 w-5", isExtending && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>图像拓展</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleWatermarkRemove}
|
||||
disabled={isRemovingWatermark}
|
||||
>
|
||||
<Droplet className={cn("h-5 w-5", isRemovingWatermark && "animate-spin")} />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>去水印</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="mx-1 h-7 w-px shrink-0 bg-border/60" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleResize}
|
||||
>
|
||||
<Maximize2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>尺寸调整</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleAngleEdit}
|
||||
>
|
||||
<Move3D className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>角度调整</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={() => setShowSceneFusion(true)}
|
||||
>
|
||||
<Layers className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>批量处理</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="mx-1 h-7 w-px shrink-0 bg-border/60" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleUndo}
|
||||
disabled={actions.length === 0}
|
||||
>
|
||||
<Undo className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>撤销</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"
|
||||
onClick={handleClear}
|
||||
disabled={actions.length === 0}
|
||||
>
|
||||
<Trash2 className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>清空</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
className="rounded-[18px] px-4 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
className="rounded-[18px] px-6 bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
{confirmButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user