Skip to content

Commit 1a133cf

Browse files
committed
fix: improve drag-and-drop reliability
1 parent a3ff72f commit 1a133cf

2 files changed

Lines changed: 50 additions & 16 deletions

File tree

src/components/ScreenshotPreview.tsx

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,48 @@ export function ScreenshotPreview({
2424
className
2525
}: ScreenshotPreviewProps) {
2626
const [showActions, setShowActions] = useState(false);
27+
const [isDragging, setIsDragging] = useState(false);
2728
const { isLoading } = useScreenshot();
2829

2930
const DRAG_THRESHOLD = 5;
3031
const dragStateRef = useRef<{ startX: number; startY: number; pending: boolean } | null>(null);
3132

3233
const handlePointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
33-
if (e.button !== 0 || !dragFilePath) return;
34+
if (e.button !== 0 || !dragFilePath || isDragging) return;
3435
if ((e.target as HTMLElement).closest('button')) return;
3536
dragStateRef.current = { startX: e.clientX, startY: e.clientY, pending: true };
36-
}, [dragFilePath]);
37+
}, [dragFilePath, isDragging]);
3738

3839
const handlePointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
3940
const state = dragStateRef.current;
40-
if (!state?.pending || !dragFilePath) return;
41+
if (!state?.pending || !dragFilePath || isDragging) return;
4142
const dx = e.clientX - state.startX;
4243
const dy = e.clientY - state.startY;
4344
if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
44-
dragStateRef.current = null;
45-
startDrag({ item: [dragFilePath], icon: dragFilePath }).catch((err) => {
46-
debugLog('Native drag failed:', err);
47-
});
45+
setIsDragging(true);
46+
startDrag({ item: [dragFilePath], icon: dragFilePath })
47+
.then(() => {
48+
debugLog('Drag completed successfully');
49+
})
50+
.catch((err) => {
51+
debugLog('Native drag failed:', err);
52+
})
53+
.finally(() => {
54+
setIsDragging(false);
55+
dragStateRef.current = null;
56+
});
4857
}
49-
}, [dragFilePath]);
58+
}, [dragFilePath, isDragging]);
5059

5160
const handlePointerUp = useCallback(() => {
5261
dragStateRef.current = null;
5362
}, []);
5463

64+
const handlePointerCancel = useCallback(() => {
65+
dragStateRef.current = null;
66+
setIsDragging(false);
67+
}, []);
68+
5569
return (
5670
<div className={className}>
5771
<div
@@ -60,13 +74,15 @@ export function ScreenshotPreview({
6074
onPointerDown={handlePointerDown}
6175
onPointerMove={handlePointerMove}
6276
onPointerUp={handlePointerUp}
63-
onMouseEnter={() => setShowActions(true)}
77+
onPointerCancel={handlePointerCancel}
78+
onMouseEnter={() => !isDragging && setShowActions(true)}
6479
onMouseLeave={() => setShowActions(false)}
6580
>
6681
{/* Delete button - top right */}
6782
<button
6883
onClick={onDelete}
69-
className="absolute top-2 right-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-red-50 hover:border-red-500 hover:scale-110 transition-all duration-200"
84+
disabled={isDragging}
85+
className="absolute top-2 right-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-red-50 hover:border-red-500 hover:scale-110 transition-all duration-200 disabled:opacity-50"
7086
aria-label="Delete screenshot"
7187
title="Delete"
7288
>
@@ -76,7 +92,7 @@ export function ScreenshotPreview({
7692
{/* Edit button - top left */}
7793
<button
7894
onClick={onEdit}
79-
disabled={isLoading}
95+
disabled={isLoading || isDragging}
8096
className="absolute top-2 left-2 z-20 w-7 h-7 flex items-center justify-center rounded-full bg-white border border-black hover:bg-blue-50 hover:border-blue-500 hover:scale-110 transition-all duration-200 disabled:opacity-50"
8197
aria-label="Edit screenshot"
8298
title="Edit"
@@ -87,7 +103,9 @@ export function ScreenshotPreview({
87103
{/* Image container with drag support */}
88104
<div className={cn(
89105
"relative w-full h-full select-none",
90-
dragFilePath ? "cursor-grab active:cursor-grabbing" : "cursor-default"
106+
dragFilePath && !isDragging ? "cursor-grab" : "",
107+
isDragging ? "cursor-grabbing" : "",
108+
!dragFilePath ? "cursor-default" : ""
91109
)}>
92110
<div
93111
className="w-full h-full bg-center bg-cover bg-no-repeat"
@@ -109,7 +127,7 @@ export function ScreenshotPreview({
109127
>
110128
<button
111129
onClick={onCopy}
112-
disabled={isLoading}
130+
disabled={isLoading || isDragging}
113131
className="flex items-center justify-center px-5 py-1.5 bg-white text-black rounded-full hover:bg-white/90 hover:scale-105 active:scale-95 transition-all duration-200 disabled:opacity-50"
114132
aria-label="Copy to clipboard"
115133
title="Copy"
@@ -119,7 +137,7 @@ export function ScreenshotPreview({
119137

120138
<button
121139
onClick={onSave}
122-
disabled={isLoading}
140+
disabled={isLoading || isDragging}
123141
className="flex items-center justify-center px-5 py-1.5 bg-white text-black rounded-full hover:bg-white/90 hover:scale-105 active:scale-95 transition-all duration-200 disabled:opacity-50"
124142
aria-label="Save screenshot"
125143
title="Save"
@@ -135,6 +153,15 @@ export function ScreenshotPreview({
135153
<div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin" />
136154
</div>
137155
)}
156+
157+
{/* Drag indicator overlay */}
158+
{isDragging && (
159+
<div className="absolute inset-0 bg-blue-500/20 flex items-center justify-center z-20 pointer-events-none">
160+
<div className="bg-white text-black px-3 py-1.5 rounded-full text-xs font-medium shadow-lg">
161+
Release to drag
162+
</div>
163+
</div>
164+
)}
138165
</div>
139166
</div>
140167
);

src/popup.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ function PopupApp() {
2424
useEffect(() => {
2525
if (!currentScreenshot) return;
2626
let cancelled = false;
27+
let filePrepared = false;
2728
const prepare = async () => {
2829
try {
2930
const path = await invoke('prepare_drag_file', { timestamp: currentScreenshot.timestamp });
30-
if (cancelled) return;
31+
if (cancelled) {
32+
invoke('cleanup_drag_file', { timestamp: currentScreenshot.timestamp }).catch(() => {});
33+
return;
34+
}
35+
filePrepared = true;
3136
setDragFilePath(path);
3237
} catch (err) {
3338
console.error('Failed to prepare drag file:', err);
@@ -36,7 +41,9 @@ function PopupApp() {
3641
prepare();
3742
return () => {
3843
cancelled = true;
39-
invoke('cleanup_drag_file', { timestamp: currentScreenshot.timestamp }).catch(() => {});
44+
if (filePrepared) {
45+
invoke('cleanup_drag_file', { timestamp: currentScreenshot.timestamp }).catch(() => {});
46+
}
4047
};
4148
}, [currentScreenshot]);
4249

0 commit comments

Comments
 (0)