fix(canvas): restore compare drag and ctrl+a image multi-select
This commit is contained in:
parent
d0a214d309
commit
eba188116a
@ -112,6 +112,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
const clearCanvas = useCanvasStore((state) => state.clearCanvas);
|
const clearCanvas = useCanvasStore((state) => state.clearCanvas);
|
||||||
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
|
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
|
||||||
const addNode = useCanvasStore((state) => state.addNode);
|
const addNode = useCanvasStore((state) => state.addNode);
|
||||||
|
const selectNodes = useCanvasStore((state) => state.selectNodes);
|
||||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||||
const setGenerating = useCanvasStore((state) => state.setGenerating);
|
const setGenerating = useCanvasStore((state) => state.setGenerating);
|
||||||
|
|
||||||
@ -487,6 +488,22 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleSave();
|
handleSave();
|
||||||
}
|
}
|
||||||
|
// Ctrl/Cmd + A:当已选中图片时,选中画布内全部图片
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'a') {
|
||||||
|
const { nodes: currentNodes, selectedNodeIds: currentSelectedNodeIds } = useCanvasStore.getState();
|
||||||
|
const selectedHasImage = currentSelectedNodeIds.some((selectedId) =>
|
||||||
|
currentNodes.some((node) => node.id === selectedId && node.type === "image")
|
||||||
|
);
|
||||||
|
if (selectedHasImage) {
|
||||||
|
const allImageNodeIds = currentNodes
|
||||||
|
.filter((node) => node.type === "image" && Boolean((node.data as any)?.src))
|
||||||
|
.map((node) => node.id);
|
||||||
|
if (allImageNodeIds.length > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
selectNodes(allImageNodeIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Ctrl/Cmd + Z 撤销
|
// Ctrl/Cmd + Z 撤销
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -513,7 +530,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [handleSave, undo, redo, setTool]);
|
}, [handleSave, nodes, redo, selectNodes, selectedNodeIds, setTool, undo]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -264,7 +264,6 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
// 文件输入的 ref
|
// 文件输入的 ref
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const compareFrameRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const updateNode = useCanvasStore((state) => state.updateNode);
|
const updateNode = useCanvasStore((state) => state.updateNode);
|
||||||
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
const deleteNode = useCanvasStore((state) => state.deleteNode);
|
||||||
@ -338,44 +337,6 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
}
|
}
|
||||||
}, [hasCompareSource, isThisNodePicking]);
|
}, [hasCompareSource, isThisNodePicking]);
|
||||||
|
|
||||||
const updateCompareSplitByClientX = useCallback((clientX: number) => {
|
|
||||||
const frame = compareFrameRef.current;
|
|
||||||
if (!frame) return;
|
|
||||||
|
|
||||||
const rect = frame.getBoundingClientRect();
|
|
||||||
if (rect.width <= 0) return;
|
|
||||||
|
|
||||||
const rawPercent = ((clientX - rect.left) / rect.width) * 100;
|
|
||||||
const clampedPercent = Math.max(0, Math.min(100, rawPercent));
|
|
||||||
setCompareSplitPercent(clampedPercent);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const startCompareDrag = useCallback((clientX: number) => {
|
|
||||||
setIsCompareDragging(true);
|
|
||||||
updateCompareSplitByClientX(clientX);
|
|
||||||
}, [updateCompareSplitByClientX]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isCompareDragging) return;
|
|
||||||
|
|
||||||
const handlePointerMove = (event: PointerEvent) => {
|
|
||||||
updateCompareSplitByClientX(event.clientX);
|
|
||||||
};
|
|
||||||
const handlePointerEnd = () => {
|
|
||||||
setIsCompareDragging(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("pointermove", handlePointerMove);
|
|
||||||
window.addEventListener("pointerup", handlePointerEnd);
|
|
||||||
window.addEventListener("pointercancel", handlePointerEnd);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("pointermove", handlePointerMove);
|
|
||||||
window.removeEventListener("pointerup", handlePointerEnd);
|
|
||||||
window.removeEventListener("pointercancel", handlePointerEnd);
|
|
||||||
};
|
|
||||||
}, [isCompareDragging, updateCompareSplitByClientX]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isEditingTitle) {
|
if (!isEditingTitle) {
|
||||||
setDraftTitle(getImageNodeTitle(data, node.id));
|
setDraftTitle(getImageNodeTitle(data, node.id));
|
||||||
@ -1561,35 +1522,12 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
{isCompareMode && hasCompareSource ? (
|
{isCompareMode && hasCompareSource ? (
|
||||||
<div
|
<div
|
||||||
ref={compareFrameRef}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative h-full w-full overflow-hidden bg-muted/30 select-none",
|
"relative h-full w-full overflow-hidden bg-muted/30 select-none",
|
||||||
isCompareDragging ? "cursor-col-resize" : "cursor-default"
|
isCompareDragging ? "cursor-col-resize" : "cursor-default"
|
||||||
)}
|
)}
|
||||||
style={{ touchAction: "none" }}
|
style={{ touchAction: "none" }}
|
||||||
data-canvas-no-drag="true"
|
data-canvas-no-drag="true"
|
||||||
onPointerDown={(event) => {
|
|
||||||
if (event.button !== 0) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.currentTarget.setPointerCapture(event.pointerId);
|
|
||||||
startCompareDrag(event.clientX);
|
|
||||||
}}
|
|
||||||
onPointerMove={(event) => {
|
|
||||||
if (!isCompareDragging) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
updateCompareSplitByClientX(event.clientX);
|
|
||||||
}}
|
|
||||||
onPointerUp={(event) => {
|
|
||||||
if (!isCompareDragging) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
setIsCompareDragging(false);
|
|
||||||
}}
|
|
||||||
onPointerCancel={() => {
|
|
||||||
setIsCompareDragging(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={compareSource}
|
src={compareSource}
|
||||||
@ -1617,19 +1555,50 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute top-1/2 z-40 h-7 w-7 -translate-x-1/2 -translate-y-1/2 cursor-col-resize rounded-full border border-white/85 bg-black/70 text-white"
|
className="absolute top-1/2 z-40 h-7 w-7 -translate-x-1/2 -translate-y-1/2 cursor-col-resize rounded-full border border-white/85 bg-black/70 text-white"
|
||||||
style={{ left: `${compareSplitPercent}%`, touchAction: "none" }}
|
style={{ left: `${compareSplitPercent}%` }}
|
||||||
data-canvas-no-drag="true"
|
data-canvas-no-drag="true"
|
||||||
onPointerDown={(event) => {
|
tabIndex={-1}
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
event.currentTarget.setPointerCapture(event.pointerId);
|
|
||||||
startCompareDrag(event.clientX);
|
|
||||||
}}
|
|
||||||
title="拖动中线查看对比"
|
title="拖动中线查看对比"
|
||||||
aria-label="拖动中线查看对比"
|
aria-label="拖动中线查看对比"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
</button>
|
</button>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={0.1}
|
||||||
|
value={compareSplitPercent}
|
||||||
|
data-canvas-no-drag="true"
|
||||||
|
aria-label="新旧图对比滑块"
|
||||||
|
className="absolute inset-0 z-50 h-full w-full cursor-col-resize appearance-none bg-transparent opacity-0"
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsCompareDragging(true);
|
||||||
|
}}
|
||||||
|
onPointerUp={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsCompareDragging(false);
|
||||||
|
}}
|
||||||
|
onPointerCancel={() => {
|
||||||
|
setIsCompareDragging(false);
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
|
onInput={(event) => {
|
||||||
|
const value = Number((event.target as HTMLInputElement).value);
|
||||||
|
if (!Number.isNaN(value)) {
|
||||||
|
setCompareSplitPercent(Math.max(0, Math.min(100, value)));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onChange={(event) => {
|
||||||
|
const value = Number(event.target.value);
|
||||||
|
if (!Number.isNaN(value)) {
|
||||||
|
setCompareSplitPercent(Math.max(0, Math.min(100, value)));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className="pointer-events-none absolute left-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white">
|
<div className="pointer-events-none absolute left-2 top-2 rounded bg-black/55 px-1.5 py-0.5 text-[10px] text-white">
|
||||||
原图
|
原图
|
||||||
</div>
|
</div>
|
||||||
@ -1695,6 +1664,9 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute top-2 right-2 z-40 inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/70 bg-black/70 text-white transition-colors hover:bg-black/80"
|
className="absolute top-2 right-2 z-40 inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/70 bg-black/70 text-white transition-colors hover:bg-black/80"
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setIsCompareMode((prev) => !prev);
|
setIsCompareMode((prev) => !prev);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user