From 94478e469ffd6aea69bcd1c9e8b3676fad06ae63 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 6 Apr 2026 01:50:06 +0800 Subject: [PATCH] refactor: polish realtime enhance workspace layout --- components/canvas/EnhanceProWorkspace.tsx | 240 +++++++++++++-------- components/canvas/RealtimeCanvasDialog.tsx | 2 +- 2 files changed, 152 insertions(+), 90 deletions(-) diff --git a/components/canvas/EnhanceProWorkspace.tsx b/components/canvas/EnhanceProWorkspace.tsx index 75c3fd61c..3b029195f 100644 --- a/components/canvas/EnhanceProWorkspace.tsx +++ b/components/canvas/EnhanceProWorkspace.tsx @@ -7,7 +7,13 @@ import { Send, Sparkles, } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { + type PointerEvent as ReactPointerEvent, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { toast } from "sonner"; import useSWR from "swr"; import { Button } from "@/components/ui/button"; @@ -93,14 +99,18 @@ const SOLID_PANEL_FIELD_CLASS = "border-[#e7e1d4] bg-[#fffdfa] text-foreground shadow-[0_8px_20px_rgba(15,23,42,0.05)]"; const SOLID_PANEL_CARD_CLASS = "rounded-[22px] border border-[#e7e1d4] bg-[#fffdfa] shadow-[0_8px_20px_rgba(15,23,42,0.05)]"; -const PANEL_SECTION_TITLE_CLASS = "font-semibold text-[13px] text-slate-700"; -const PANEL_SECTION_NOTE_CLASS = "text-[11px] leading-5 text-slate-500"; +const PANEL_SECTION_TITLE_CLASS = "font-semibold text-[12px] text-slate-700"; +const PANEL_SECTION_NOTE_CLASS = "text-[10.5px] leading-4 text-slate-500"; const SOLID_PANEL_SELECT_CONTENT_CLASS = "border-[#e7e1d4] bg-[#fffdfa] shadow-[0_16px_34px_rgba(15,23,42,0.08)]"; const PRIMARY_ACTION_BUTTON_CLASS = "border border-[#2563eb] bg-[#2563eb] text-white shadow-[0_10px_22px_rgba(37,99,235,0.18)] transition hover:border-[#1d4ed8] hover:bg-[#1d4ed8] disabled:border-[#e7e1d4] disabled:bg-slate-200 disabled:text-slate-500 disabled:shadow-none"; const SECONDARY_ACTION_BUTTON_CLASS = "border border-[#d8e4ff] bg-[#eef4ff] text-[#1d4ed8] shadow-[0_8px_20px_rgba(59,130,246,0.10)] transition hover:bg-[#e4eeff] hover:text-[#1e40af] disabled:border-[#e7e1d4] disabled:bg-slate-100 disabled:text-slate-400 disabled:shadow-none"; +const COMPACT_FIELD_CLASS = `${SOLID_PANEL_FIELD_CLASS} h-10 rounded-[16px] text-sm`; +const COMPACT_LABEL_CLASS = "text-slate-600 text-[11px] leading-4"; +const COMPACT_TOGGLE_CARD_CLASS = + "flex items-center justify-between gap-2.5 px-3 py-2.5"; function clampNumber(value: number, min: number, max: number) { return Math.min(max, Math.max(min, value)); @@ -278,6 +288,15 @@ export function EnhanceProWorkspace({ EnhanceProImageResult[] >([]); const [lastErrorMessage, setLastErrorMessage] = useState(""); + const [previewOffset, setPreviewOffset] = useState({ x: 0, y: 0 }); + const [isPreviewDragging, setIsPreviewDragging] = useState(false); + const previewDragStateRef = useRef<{ + pointerId: number; + startX: number; + startY: number; + originX: number; + originY: number; + } | null>(null); const { data: configPayload, isLoading } = useSWR<{ success?: boolean; @@ -352,6 +371,12 @@ export function EnhanceProWorkspace({ setLastErrorMessage(""); }, [sourceImageUrl]); + useEffect(() => { + setPreviewOffset({ x: 0, y: 0 }); + setIsPreviewDragging(false); + previewDragStateRef.current = null; + }, [activePreviewUrl]); + const handleNumericField = ( key: K, nextValue: number @@ -474,7 +499,7 @@ export function EnhanceProWorkspace({ isSource?: boolean; }> = []; - if (sourceImageUrl) { + if (generatedImages.length > 0 && sourceImageUrl) { items.push({ key: "source", label: "原图", @@ -494,21 +519,83 @@ export function EnhanceProWorkspace({ return items; }, [generatedImages, sourceImageUrl]); + const handlePreviewPointerDown = ( + event: ReactPointerEvent + ) => { + if (!activePreviewUrl || event.button !== 0) { + return; + } + + previewDragStateRef.current = { + pointerId: event.pointerId, + startX: event.clientX, + startY: event.clientY, + originX: previewOffset.x, + originY: previewOffset.y, + }; + setIsPreviewDragging(true); + event.currentTarget.setPointerCapture(event.pointerId); + }; + + const handlePreviewPointerMove = ( + event: ReactPointerEvent + ) => { + const dragState = previewDragStateRef.current; + if (!dragState || dragState.pointerId !== event.pointerId) { + return; + } + + setPreviewOffset({ + x: dragState.originX + event.clientX - dragState.startX, + y: dragState.originY + event.clientY - dragState.startY, + }); + }; + + const finishPreviewDrag = (event?: ReactPointerEvent) => { + const dragState = previewDragStateRef.current; + if (!dragState) { + return; + } + + if ( + event && + event.currentTarget.hasPointerCapture(dragState.pointerId) + ) { + event.currentTarget.releasePointerCapture(dragState.pointerId); + } + + previewDragStateRef.current = null; + setIsPreviewDragging(false); + }; + return (
{activePreviewUrl ? ( - <> +
setPreviewOffset({ x: 0, y: 0 })} + onPointerCancel={finishPreviewDrag} + onPointerDown={handlePreviewPointerDown} + onPointerMove={handlePreviewPointerMove} + onPointerUp={finishPreviewDrag} + > {activePreviewLabel}
{activePreviewLabel}
- +
) : (
@@ -522,7 +609,7 @@ export function EnhanceProWorkspace({
)} - {sidePreviewItems.length > 0 ? ( + {generatedImages.length > 0 && sidePreviewItems.length > 0 ? (
{sidePreviewItems.map((item, index) => { @@ -557,13 +644,13 @@ export function EnhanceProWorkspace({ ) : null}
-
-
+
+
{isLoading ? (
@@ -584,13 +671,11 @@ export function EnhanceProWorkspace({
) : null} -
+