fix: unify canvas scale edit entry

This commit is contained in:
134 2026-06-22 21:38:29 +08:00
parent d64d3c9deb
commit 12b21d6f3b
2 changed files with 91 additions and 97 deletions

View File

@ -1463,16 +1463,20 @@ function DetailResultLocalEditDialog({
draggable={false}
src={buildImageProxyUrl(target.imageUrl, { width: 1200, quality: 88 })}
/>
{regions.map((region) => {
const isActive = region.id === activeRegionId;
return (
<button
className={cn(
"absolute rounded-sm border-2 bg-black/0 text-left transition",
regionInteraction?.regionId === region.id
? "cursor-grabbing"
: "cursor-grab"
)}
{regions.map((region) => {
const isActive = region.id === activeRegionId;
const isAwaitingScaleTarget =
isScaleAdjustMode && !region.targetRect;
return (
<button
className={cn(
"absolute rounded-sm border-2 bg-black/0 text-left transition",
isAwaitingScaleTarget
? "pointer-events-none cursor-crosshair"
: regionInteraction?.regionId === region.id
? "cursor-grabbing"
: "cursor-grab"
)}
key={region.id}
onClick={(event) => {
event.preventDefault();
@ -1510,8 +1514,8 @@ function DetailResultLocalEditDialog({
>
{region.index}
</span>
{isActive
? (
{isActive && !isAwaitingScaleTarget
? (
[
{
handle: "nw",
@ -1705,7 +1709,7 @@ function DetailResultLocalEditDialog({
? "在左侧图片上先框主体,再框青色目标大小和位置。"
: "在左侧图片上拖拽框选后,这里会显示区域提示词和参考图。"}
</div>
) : (
) : isScaleAdjustMode ? null : (
<div className="max-h-[52vh] space-y-3 overflow-y-auto pr-1">
{regions.map((region) => {
const isActive = region.id === activeRegionId;

View File

@ -22,7 +22,6 @@ import {
Link2,
Maximize2,
MessageSquarePlus,
Minimize2,
MoreHorizontal,
MoreVertical,
Pencil,
@ -1401,10 +1400,11 @@ export function ImageNode({
[node.size.height, node.size.width]
);
const viewportZoom = useCanvasStore((state) => state.viewport.zoom);
const nodeContentScale = contentScale * viewportZoom;
const scaledContentStyle = useMemo(
() => getScaledNodeContentStyle(nodeContentScale, { allowUpscale: true }),
[nodeContentScale]
const nodeContentScale = contentScale * viewportZoom;
const localEditPromptVisualScale = clampNumber(nodeContentScale, 0.5, 1);
const scaledContentStyle = useMemo(
() => getScaledNodeContentStyle(nodeContentScale, { allowUpscale: true }),
[nodeContentScale]
);
const selectedTitleStyle = useMemo<React.CSSProperties>(
() => ({
@ -2387,20 +2387,35 @@ export function ImageNode({
localEditMode,
localEditRegionOverlays,
]);
const localEditPromptViewportOverlays = useMemo(
() =>
localEditPromptInlineOverlays.map(({ region, isEditing, style }) => ({
region,
isEditing,
style: {
...style,
left: Number(style.left || 0) * nodeContentScale,
top: Number(style.top || 0) * nodeContentScale,
width: Number(style.width || 0) * nodeContentScale,
} satisfies React.CSSProperties,
})),
[localEditPromptInlineOverlays, nodeContentScale]
);
const localEditPromptViewportOverlays = useMemo(
() =>
localEditPromptInlineOverlays.map(({ region, isEditing, style }) => {
const viewportWidth = Number(style.width || 0) * nodeContentScale;
return {
region,
isEditing,
isAwaitingScaleTarget:
localEditOperationMode === "scale_adjust" && !region.targetRect,
style: {
...style,
left: Number(style.left || 0) * nodeContentScale,
top: Number(style.top || 0) * nodeContentScale,
transform: `scale(${localEditPromptVisualScale})`,
transformOrigin: "top left",
width:
localEditPromptVisualScale > 0
? viewportWidth / localEditPromptVisualScale
: viewportWidth,
} satisfies React.CSSProperties,
};
}),
[
localEditOperationMode,
localEditPromptInlineOverlays,
localEditPromptVisualScale,
nodeContentScale,
]
);
useEffect(() => {
if (
!localEditMode ||
@ -8396,21 +8411,26 @@ export function ImageNode({
);
}
)}
{localEditRegionOverlays.map(({ region, style }) => {
const left = Number(style.left || 0);
const top = Number(style.top || 0);
const width = Number(style.width || 0);
const height = Number(style.height || 0);
const active =
localEditRegionInteraction?.regionId === region.id &&
localEditRegionInteraction.target === "source";
{localEditRegionOverlays.map(({ region, style }) => {
const left = Number(style.left || 0);
const top = Number(style.top || 0);
const width = Number(style.width || 0);
const height = Number(style.height || 0);
const isAwaitingScaleTarget =
localEditOperationMode === "scale_adjust" && !region.targetRect;
const active =
localEditRegionInteraction?.regionId === region.id &&
localEditRegionInteraction.target === "source";
return (
<div
className={cn(
"nodrag nopan nowheel pointer-events-auto absolute z-[160] rounded-none border-[1.5px] bg-transparent shadow-none",
active ? "cursor-grabbing" : "cursor-grab"
)}
return (
<div
className={cn(
"nodrag nopan nowheel absolute z-[160] rounded-none border-[1.5px] bg-transparent shadow-none",
isAwaitingScaleTarget
? "pointer-events-none cursor-crosshair"
: "pointer-events-auto",
active ? "cursor-grabbing" : "cursor-grab"
)}
data-canvas-no-drag="true"
data-local-edit-control="true"
key={region.id}
@ -8438,10 +8458,10 @@ export function ImageNode({
borderColor: region.color,
}}
>
{(
[
{
handle: "nw",
{!isAwaitingScaleTarget ? (
[
{
handle: "nw",
className: "-left-[5px] -top-[5px] cursor-nwse-resize",
},
{
@ -8457,8 +8477,8 @@ export function ImageNode({
className: "-right-[5px] -bottom-[5px] cursor-nwse-resize",
},
] as const
).map((item) => (
<button
).map((item) => (
<button
aria-label={`调整区域 ${region.index}`}
className={cn(
"nodrag nopan nowheel absolute h-[10px] w-[10px] rounded-none border-[1.5px] bg-white shadow-none",
@ -8469,12 +8489,12 @@ export function ImageNode({
data-local-edit-region-handle={item.handle}
key={item.handle}
style={{ borderColor: region.color }}
type="button"
/>
))}
</div>
);
})}
type="button"
/>
)) : null}
</div>
);
})}
{typeof document !== "undefined" && localEditPromptPortalFrame
? createPortal(
<div
@ -8487,10 +8507,13 @@ export function ImageNode({
height: localEditPromptPortalFrame.height,
}}
>
{localEditPromptViewportOverlays.map(
({ region, isEditing, style }) => (
<div
className="nodrag nopan nowheel pointer-events-auto absolute z-[170] flex flex-col items-stretch gap-1.5"
{localEditPromptViewportOverlays.map(
({ region, isEditing, isAwaitingScaleTarget, style }) => (
<div
className={cn(
"nodrag nopan nowheel absolute z-[170] flex flex-col items-stretch gap-1.5",
isAwaitingScaleTarget ? "pointer-events-none" : "pointer-events-auto"
)}
data-canvas-no-drag="true"
data-local-edit-control="true"
key={`local-edit-prompt-${region.id}`}
@ -9298,17 +9321,8 @@ export function ImageNode({
<div className="mx-1 h-6 w-px bg-slate-200 dark:bg-slate-700" />
{localEditOperationMode === "scale_adjust" ? (
<div className="inline-flex h-10 shrink-0 items-center gap-2 rounded-xl border border-cyan-300/35 bg-cyan-50 px-3 text-[12px] font-semibold text-cyan-800 dark:border-cyan-300/25 dark:bg-cyan-400/10 dark:text-cyan-100">
<Maximize2 className="h-3.5 w-3.5 shrink-0" />
<span className="whitespace-nowrap">
先框主体,再框目标大小和位置
</span>
</div>
) : null}
<Button
className="mr-1 inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-xl bg-slate-950 px-3 font-semibold text-[13px] text-white hover:bg-slate-800 hover:text-white disabled:bg-slate-300 disabled:text-slate-500 dark:border dark:border-white/14 dark:bg-black dark:text-white dark:disabled:border-transparent dark:disabled:bg-slate-700 dark:disabled:text-slate-400 dark:hover:bg-zinc-900"
<Button
className="mr-1 inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-xl bg-slate-950 px-3 font-semibold text-[13px] text-white hover:bg-slate-800 hover:text-white disabled:bg-slate-300 disabled:text-slate-500 dark:border dark:border-white/14 dark:bg-black dark:text-white dark:disabled:border-transparent dark:disabled:bg-slate-700 dark:disabled:text-slate-400 dark:hover:bg-zinc-900"
disabled={
isSubmittingLocalEdit ||
localEditRegions.length === 0 ||
@ -9789,23 +9803,6 @@ export function ImageNode({
</Button>
) : null}
{isSelectedImageActionInline("localEdit") ? (
<Button
className="group relative h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
disabled={isProcessing || isSubmittingLocalEdit}
onClick={() => handleAction("scale-adjust")}
title="比例调整"
variant="ghost"
>
<Minimize2 className="mr-1.5 h-4 w-4" />
比例调整
<ImageActionCreditBadge
compact
label={localEditCostShortLabel}
/>
</Button>
) : null}
{isSelectedImageActionInline("removeWatermark") ? (
<Button
className="group relative h-10 rounded-xl px-3 font-medium text-[13px] text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-100 dark:hover:bg-zinc-800"
@ -10077,13 +10074,6 @@ export function ImageNode({
item={imageLocalEditItem}
/>
<span>{imageLocalEditItem.label}</span>
<ImageActionCreditBadge label={localEditCostShortLabel} />
</DropdownMenuItem>
) : null}
{isSelectedImageActionInMore("localEdit") ? (
<DropdownMenuItem onClick={() => handleAction("scale-adjust")}>
<Minimize2 className="mr-2 h-4 w-4" />
<span>比例调整</span>
<ImageActionCreditBadge label={localEditCostShortLabel} />
</DropdownMenuItem>
) : null}