refactor: improve drag-and-drop handling and cleanup in FileTreeItem and ClaudeOAuthFlow components
- Added useEffect in FileTreeItem to clean up custom drag image on component unmount, preventing memory leaks. - Enhanced drag image creation using safe DOM manipulation instead of innerHTML. - Updated ClaudeOAuthFlow to manage auto-advance timeout with cleanup on unmount, ensuring onSuccess is not called after component unmount. These changes enhance the reliability and performance of drag-and-drop functionality and OAuth flow handling.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useRef, type DragEvent } from 'react';
|
||||
import { useState, useRef, useEffect, type DragEvent } from 'react';
|
||||
import { ChevronRight, ChevronDown, Folder, File, FileCode, FileJson, FileText, FileImage, Loader2 } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
import type { FileNode } from '../../shared/types';
|
||||
@@ -73,6 +73,17 @@ export function FileTreeItem({
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragImageRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Cleanup drag image on unmount to prevent memory leaks
|
||||
// This handles cases where component unmounts mid-drag or dragend doesn't fire
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (dragImageRef.current && dragImageRef.current.parentNode) {
|
||||
dragImageRef.current.parentNode.removeChild(dragImageRef.current);
|
||||
dragImageRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (node.isDirectory) {
|
||||
@@ -102,10 +113,18 @@ export function FileTreeItem({
|
||||
e.dataTransfer.setData('text/plain', `@${node.name}`);
|
||||
e.dataTransfer.effectAllowed = 'copy';
|
||||
|
||||
// Create a custom drag image
|
||||
// Create a custom drag image using safe DOM manipulation (no innerHTML)
|
||||
const dragImage = document.createElement('div');
|
||||
dragImage.className = 'flex items-center gap-2 bg-card border border-primary rounded-md px-3 py-2 shadow-lg text-sm';
|
||||
dragImage.innerHTML = `<span>${node.isDirectory ? '📁' : '📄'}</span><span>${node.name}</span>`;
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.textContent = node.isDirectory ? '📁' : '📄';
|
||||
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.textContent = node.name;
|
||||
|
||||
dragImage.appendChild(iconSpan);
|
||||
dragImage.appendChild(nameSpan);
|
||||
dragImage.style.position = 'absolute';
|
||||
dragImage.style.top = '-1000px';
|
||||
dragImage.style.left = '-1000px';
|
||||
|
||||
@@ -26,12 +26,16 @@ export function ClaudeOAuthFlow({ onSuccess, onCancel }: ClaudeOAuthFlowProps) {
|
||||
|
||||
// Track if we've already started auth to prevent double-execution
|
||||
const hasStartedRef = useRef(false);
|
||||
const listenerSetupRef = useRef(false);
|
||||
// Track the auto-advance timeout so we can cancel it on unmount/re-render
|
||||
const successTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// Listen for OAuth token detection
|
||||
useEffect(() => {
|
||||
if (listenerSetupRef.current) return;
|
||||
listenerSetupRef.current = true;
|
||||
// Clear any pending timeout from previous effect run
|
||||
if (successTimeoutRef.current) {
|
||||
clearTimeout(successTimeoutRef.current);
|
||||
successTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
const unsubscribe = window.electronAPI.onTerminalOAuthToken((info) => {
|
||||
console.warn('[ClaudeOAuth] Token event received:', {
|
||||
@@ -44,7 +48,9 @@ export function ClaudeOAuthFlow({ onSuccess, onCancel }: ClaudeOAuthFlowProps) {
|
||||
setEmail(info.email);
|
||||
setStatus('success');
|
||||
// Auto-advance after a short delay to show success message
|
||||
setTimeout(() => {
|
||||
// Store the timeout ID so cleanup can cancel it if needed
|
||||
successTimeoutRef.current = setTimeout(() => {
|
||||
successTimeoutRef.current = null; // Clear ref since timeout fired
|
||||
onSuccess();
|
||||
}, 1500);
|
||||
} else {
|
||||
@@ -54,8 +60,11 @@ export function ClaudeOAuthFlow({ onSuccess, onCancel }: ClaudeOAuthFlowProps) {
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
unsubscribe?.();
|
||||
// Clear timeout on cleanup to prevent calling onSuccess after unmount
|
||||
if (successTimeoutRef.current) {
|
||||
clearTimeout(successTimeoutRef.current);
|
||||
successTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [onSuccess]);
|
||||
|
||||
Reference in New Issue
Block a user