fix(canvas): unify title, improve multi-select drag and immediate delete save
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-02-17 13:00:39 +08:00
parent 30e943d787
commit aa2554e4ed
4 changed files with 35 additions and 4 deletions

View File

@ -2,7 +2,6 @@ import { Metadata } from "next";
import { Suspense } from "react";
export const metadata: Metadata = {
title: "无限画布",
description: "AI 驱动的无限画布创作工具",
};

View File

@ -1,6 +1,6 @@
"use client";
import { useEffect, useState, useCallback, use } from "react";
import { useEffect, useState, useCallback, use, useRef } from "react";
import { useRouter, useParams } from "next/navigation";
import useSWR from "swr";
import {
@ -133,6 +133,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
const [negativePrompt, setNegativePrompt] = useState("");
const [aspectRatio, setAspectRatio] = useState("1:1");
const [imageCount, setImageCount] = useState(1);
const previousNodeCountRef = useRef(0);
// 获取选中的图片节点
const selectedImageNode = selectedNodeIds.length === 1
@ -191,6 +192,31 @@ export default function CanvasEditorPage({ params }: PageProps) {
return () => clearTimeout(saveTimer);
}, [nodes, id, isLoading, getCanvasState]);
// 删除节点时立即保存,避免用户立刻刷新后看到被删除节点又出现
useEffect(() => {
if (isLoading) {
previousNodeCountRef.current = nodes.length;
return;
}
const previousCount = previousNodeCountRef.current;
const currentCount = nodes.length;
previousNodeCountRef.current = currentCount;
if (previousCount > currentCount) {
const persistAfterDelete = async () => {
setIsSaving(true);
try {
await canvasService.saveCanvasState(id, getCanvasState());
} catch (error) {
console.error("Delete immediate save failed:", error);
}
setIsSaving(false);
};
void persistAfterDelete();
}
}, [nodes.length, id, isLoading, getCanvasState]);
// 智能命名与缩略图逻辑
useEffect(() => {
const autoTitleAndThumbnail = async () => {
@ -558,7 +584,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
{/* 4. Floating Sidebar (Right) */}
<div className={cn(
"absolute top-4 right-4 bottom-4 z-10 w-[400px] transition-all duration-300 ease-in-out",
"absolute top-4 right-4 bottom-4 z-10 w-[460px] max-w-[calc(100vw-1rem)] transition-all duration-300 ease-in-out",
isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none"
)}>
<div className="h-full w-full rounded-2xl border bg-background/95 backdrop-blur-md shadow-2xl overflow-hidden flex flex-col">

View File

@ -3705,7 +3705,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
{/* Bottom Toolbar */}
<div className="absolute left-2 bottom-2 right-2 flex items-center gap-2">
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto overscroll-x-contain touch-pan-x pr-1 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
<div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-x-visible pr-0.5">
<input
ref={fileInputRef}
type="file"

View File

@ -47,6 +47,7 @@ export function CanvasWorkspace() {
const [containerSize, setContainerSize] = useState({ width: 1920, height: 1080 });
const dragCounterRef = useRef(0);
const dragPointerRef = useRef<Position>({ x: 0, y: 0 });
const suppressCanvasClickRef = useRef(false);
const groupDragRef = useRef<{
nodeId: string;
startX: number;
@ -179,6 +180,10 @@ export function CanvasWorkspace() {
// 处理画布点击(取消选择)
const handleCanvasClick = useCallback((e: React.MouseEvent) => {
if (!isEventFromCanvasContainer(e.target)) return;
if (suppressCanvasClickRef.current) {
suppressCanvasClickRef.current = false;
return;
}
if (!isPointOnNodeElement(e.target) && !isMarqueeSelecting) {
clearSelection();
}
@ -247,6 +252,7 @@ export function CanvasWorkspace() {
.map((node) => node.id);
if (selectedIds.length > 0) {
selectNodes(selectedIds);
suppressCanvasClickRef.current = true;
} else {
clearSelection();
}