mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
fix: Prevent race condition in project removal dialog cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useState, memo } from 'react';
|
import { useEffect, useRef, useState, memo, useCallback } from 'react';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
import type { LucideIcon } from 'lucide-react';
|
||||||
import { Edit2, Trash2, Palette, ChevronRight, Moon, Sun, Monitor } from 'lucide-react';
|
import { Edit2, Trash2, Palette, ChevronRight, Moon, Sun, Monitor } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@@ -9,6 +9,9 @@ import type { Project } from '@/lib/electron';
|
|||||||
import { PROJECT_DARK_THEMES, PROJECT_LIGHT_THEMES } from '@/components/layout/sidebar/constants';
|
import { PROJECT_DARK_THEMES, PROJECT_LIGHT_THEMES } from '@/components/layout/sidebar/constants';
|
||||||
import { useThemePreview } from '@/components/layout/sidebar/hooks';
|
import { useThemePreview } from '@/components/layout/sidebar/hooks';
|
||||||
|
|
||||||
|
// Constant for "use global theme" option
|
||||||
|
const USE_GLOBAL_THEME = '' as const;
|
||||||
|
|
||||||
// Constants for z-index values
|
// Constants for z-index values
|
||||||
const Z_INDEX = {
|
const Z_INDEX = {
|
||||||
CONTEXT_MENU: 100,
|
CONTEXT_MENU: 100,
|
||||||
@@ -125,6 +128,7 @@ export function ProjectContextMenu({
|
|||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
const [showRemoveDialog, setShowRemoveDialog] = useState(false);
|
const [showRemoveDialog, setShowRemoveDialog] = useState(false);
|
||||||
const [showThemeSubmenu, setShowThemeSubmenu] = useState(false);
|
const [showThemeSubmenu, setShowThemeSubmenu] = useState(false);
|
||||||
|
const [removeConfirmed, setRemoveConfirmed] = useState(false);
|
||||||
const themeSubmenuRef = useRef<HTMLDivElement>(null);
|
const themeSubmenuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { handlePreviewEnter, handlePreviewLeave } = useThemePreview({ setPreviewTheme });
|
const { handlePreviewEnter, handlePreviewLeave } = useThemePreview({ setPreviewTheme });
|
||||||
@@ -167,24 +171,41 @@ export function ProjectContextMenu({
|
|||||||
setShowRemoveDialog(true);
|
setShowRemoveDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleThemeSelect = (value: ThemeMode | '') => {
|
const handleThemeSelect = useCallback(
|
||||||
setPreviewTheme(null);
|
(value: ThemeMode | typeof USE_GLOBAL_THEME) => {
|
||||||
if (value !== '') {
|
setPreviewTheme(null);
|
||||||
setTheme(value);
|
const isUsingGlobal = value === USE_GLOBAL_THEME;
|
||||||
} else {
|
setTheme(isUsingGlobal ? globalTheme : value);
|
||||||
setTheme(globalTheme);
|
setProjectTheme(project.id, isUsingGlobal ? null : value);
|
||||||
}
|
setShowThemeSubmenu(false);
|
||||||
setProjectTheme(project.id, value === '' ? null : value);
|
},
|
||||||
setShowThemeSubmenu(false);
|
[globalTheme, project.id, setPreviewTheme, setProjectTheme, setTheme]
|
||||||
};
|
);
|
||||||
|
|
||||||
const handleConfirmRemove = () => {
|
const handleConfirmRemove = useCallback(() => {
|
||||||
moveProjectToTrash(project.id);
|
moveProjectToTrash(project.id);
|
||||||
toast.success('Project removed', {
|
toast.success('Project removed', {
|
||||||
description: `${project.name} has been removed from your projects list`,
|
description: `${project.name} has been removed from your projects list`,
|
||||||
});
|
});
|
||||||
onClose();
|
setRemoveConfirmed(true);
|
||||||
};
|
}, [moveProjectToTrash, project.id, project.name]);
|
||||||
|
|
||||||
|
const handleDialogClose = useCallback(
|
||||||
|
(isOpen: boolean) => {
|
||||||
|
setShowRemoveDialog(isOpen);
|
||||||
|
// Only close the context menu after dialog closes if removal was confirmed
|
||||||
|
// This prevents race condition where onClose unmounts the component
|
||||||
|
// before ConfirmDialog finishes its internal state cleanup
|
||||||
|
if (!isOpen && removeConfirmed) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
// Reset confirmation state when dialog closes (for potential reopen)
|
||||||
|
if (!isOpen) {
|
||||||
|
setRemoveConfirmed(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onClose, removeConfirmed]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -267,7 +288,7 @@ export function ProjectContextMenu({
|
|||||||
<button
|
<button
|
||||||
onPointerEnter={() => handlePreviewEnter(globalTheme)}
|
onPointerEnter={() => handlePreviewEnter(globalTheme)}
|
||||||
onPointerLeave={handlePreviewLeave}
|
onPointerLeave={handlePreviewLeave}
|
||||||
onClick={() => handleThemeSelect('')}
|
onClick={() => handleThemeSelect(USE_GLOBAL_THEME)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
'w-full flex items-center gap-2 px-3 py-2 rounded-md',
|
||||||
'text-sm font-medium text-left',
|
'text-sm font-medium text-left',
|
||||||
@@ -332,7 +353,7 @@ export function ProjectContextMenu({
|
|||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
open={showRemoveDialog}
|
open={showRemoveDialog}
|
||||||
onOpenChange={setShowRemoveDialog}
|
onOpenChange={handleDialogClose}
|
||||||
onConfirm={handleConfirmRemove}
|
onConfirm={handleConfirmRemove}
|
||||||
title="Remove Project"
|
title="Remove Project"
|
||||||
description={`Are you sure you want to remove "${project.name}" from the project list? This won't delete any files on disk.`}
|
description={`Are you sure you want to remove "${project.name}" from the project list? This won't delete any files on disk.`}
|
||||||
|
|||||||
Reference in New Issue
Block a user