diff --git a/apps/frontend/src/renderer/components/ProjectTabBar.tsx b/apps/frontend/src/renderer/components/ProjectTabBar.tsx index 1f476ce2..5c12e47b 100644 --- a/apps/frontend/src/renderer/components/ProjectTabBar.tsx +++ b/apps/frontend/src/renderer/components/ProjectTabBar.tsx @@ -30,7 +30,7 @@ export function ProjectTabBar({ onSettingsClick }: ProjectTabBarProps) { const { t } = useTranslation('common'); - const { isProjectPoppedOut, setWindowLoading, addPoppedOutProject } = useWindowStore(); + const { isProjectPoppedOut, setWindowLoading, addPoppedOutProject, isWindowLoading } = useWindowStore(); // Handler for popping out a project into a new window const handlePopOutProject = async (projectId: string) => { @@ -148,6 +148,7 @@ export function ProjectTabBar({ onSettingsClick={isActiveTab ? onSettingsClick : undefined} onPopOutClick={isActiveTab ? () => handlePopOutProject(project.id) : undefined} isPoppedOut={isProjectPoppedOut(project.id)} + isLoading={isWindowLoading(project.id)} /> ); })} diff --git a/apps/frontend/src/renderer/components/Sidebar.tsx b/apps/frontend/src/renderer/components/Sidebar.tsx index 505843ae..21fd7a28 100644 --- a/apps/frontend/src/renderer/components/Sidebar.tsx +++ b/apps/frontend/src/renderer/components/Sidebar.tsx @@ -23,7 +23,8 @@ import { Wrench, PanelLeft, PanelLeftClose, - ExternalLink + ExternalLink, + Loader2 } from 'lucide-react'; import { Button } from './ui/button'; import { ScrollArea } from './ui/scroll-area'; @@ -113,7 +114,7 @@ export function Sidebar({ const projects = useProjectStore((state) => state.projects); const selectedProjectId = useProjectStore((state) => state.selectedProjectId); const settings = useSettingsStore((state) => state.settings); - const { isViewPoppedOut, setWindowLoading, addPoppedOutView } = useWindowStore(); + const { isViewPoppedOut, setWindowLoading, addPoppedOutView, isWindowLoading, getViewKey } = useWindowStore(); const [showAddProjectModal, setShowAddProjectModal] = useState(false); const [showInitDialog, setShowInitDialog] = useState(false); @@ -338,6 +339,8 @@ export function Sidebar({ const isActive = activeView === item.id; const Icon = item.icon; const isPoppedOut = selectedProjectId ? isViewPoppedOut(selectedProjectId, item.id) : false; + const viewKey = selectedProjectId ? getViewKey(selectedProjectId, item.id) : ''; + const isLoading = viewKey ? isWindowLoading(viewKey) : false; const button = (
{ e.stopPropagation(); - if (!isPoppedOut) { + if (!isPoppedOut && !isLoading) { handlePopOutView(item.id, e); } }} - disabled={isPoppedOut} + disabled={isPoppedOut || isLoading} aria-label={isPoppedOut ? t('navigation:viewPoppedOut') : t('navigation:popOutView')} className={cn( 'h-5 w-5 p-0 rounded flex items-center justify-center', 'text-muted-foreground hover:text-foreground', 'hover:bg-muted/50 transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', - isPoppedOut ? 'opacity-100 text-primary' : 'opacity-0 group-hover:opacity-100', - isPoppedOut && 'cursor-not-allowed' + isPoppedOut || isLoading ? 'opacity-100' : 'opacity-0 group-hover:opacity-100', + isPoppedOut && 'text-primary', + (isPoppedOut || isLoading) && 'cursor-not-allowed' )} > - + {isLoading ? ( + + ) : ( + + )} - {isPoppedOut ? t('navigation:tooltips.viewPoppedOut') : t('navigation:tooltips.popOutView')} + {isLoading ? t('navigation:tooltips.popOutLoading') : isPoppedOut ? t('navigation:tooltips.viewPoppedOut') : t('navigation:tooltips.popOutView')} )} diff --git a/apps/frontend/src/renderer/components/SortableProjectTab.tsx b/apps/frontend/src/renderer/components/SortableProjectTab.tsx index 4111003d..ba4e0b15 100644 --- a/apps/frontend/src/renderer/components/SortableProjectTab.tsx +++ b/apps/frontend/src/renderer/components/SortableProjectTab.tsx @@ -1,7 +1,7 @@ import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { useTranslation } from 'react-i18next'; -import { Settings2, ExternalLink } from 'lucide-react'; +import { Settings2, ExternalLink, Loader2 } from 'lucide-react'; import { cn } from '../lib/utils'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; import type { Project } from '../../shared/types'; @@ -17,6 +17,7 @@ interface SortableProjectTabProps { onSettingsClick?: () => void; onPopOutClick?: () => void; isPoppedOut?: boolean; + isLoading?: boolean; } // Detect if running on macOS for keyboard shortcut display @@ -32,7 +33,8 @@ export function SortableProjectTab({ onClose, onSettingsClick, onPopOutClick, - isPoppedOut = false + isPoppedOut = false, + isLoading = false }: SortableProjectTabProps) { const { t } = useTranslation('common'); // Build tooltip with keyboard shortcut hint (only for tabs 1-9) @@ -138,22 +140,26 @@ export function SortableProjectTab({ 'text-muted-foreground hover:text-foreground', 'hover:bg-muted/50 transition-colors', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', - isPoppedOut && 'opacity-50 cursor-not-allowed' + (isPoppedOut || isLoading) && 'opacity-50 cursor-not-allowed' )} onClick={(e) => { e.stopPropagation(); - if (!isPoppedOut) { + if (!isPoppedOut && !isLoading) { onPopOutClick(); } }} - disabled={isPoppedOut} + disabled={isPoppedOut || isLoading} aria-label={t('projectTab.popOutAriaLabel')} > - + {isLoading ? ( + + ) : ( + + )} - {t('projectTab.popOut')} + {isLoading ? t('projectTab.popOutLoading') : t('projectTab.popOut')} )} diff --git a/apps/frontend/src/shared/i18n/locales/en/common.json b/apps/frontend/src/shared/i18n/locales/en/common.json index 48aeef89..67da5dd5 100644 --- a/apps/frontend/src/shared/i18n/locales/en/common.json +++ b/apps/frontend/src/shared/i18n/locales/en/common.json @@ -10,6 +10,7 @@ "addProjectAriaLabel": "Add project", "popOut": "Pop out project", "popOutAriaLabel": "Pop out project into separate window", + "popOutLoading": "Creating window...", "poppedOutBadge": "Popped Out" }, "accessibility": { diff --git a/apps/frontend/src/shared/i18n/locales/en/navigation.json b/apps/frontend/src/shared/i18n/locales/en/navigation.json index 055b4fdd..12c4037d 100644 --- a/apps/frontend/src/shared/i18n/locales/en/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/en/navigation.json @@ -32,7 +32,8 @@ "settings": "Application Settings", "help": "Help & Feedback", "popOutView": "Open this view in a separate window", - "viewPoppedOut": "This view is opened in a separate window" + "viewPoppedOut": "This view is opened in a separate window", + "popOutLoading": "Creating window..." }, "messages": { "initializeToCreateTasks": "Initialize Auto Claude to create tasks" diff --git a/apps/frontend/src/shared/i18n/locales/fr/common.json b/apps/frontend/src/shared/i18n/locales/fr/common.json index ec5b8a34..71d9ac37 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/common.json +++ b/apps/frontend/src/shared/i18n/locales/fr/common.json @@ -10,6 +10,7 @@ "addProjectAriaLabel": "Ajouter un projet", "popOut": "Ouvrir dans une fenêtre séparée", "popOutAriaLabel": "Ouvrir le projet dans une fenêtre séparée", + "popOutLoading": "Création de la fenêtre...", "poppedOutBadge": "Détaché" }, "accessibility": { diff --git a/apps/frontend/src/shared/i18n/locales/fr/navigation.json b/apps/frontend/src/shared/i18n/locales/fr/navigation.json index b1e08b4e..afc54c84 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/fr/navigation.json @@ -32,7 +32,8 @@ "settings": "Paramètres de l'application", "help": "Aide & Feedback", "popOutView": "Ouvrir cette vue dans une fenêtre séparée", - "viewPoppedOut": "Cette vue est ouverte dans une fenêtre séparée" + "viewPoppedOut": "Cette vue est ouverte dans une fenêtre séparée", + "popOutLoading": "Création de la fenêtre..." }, "messages": { "initializeToCreateTasks": "Initialisez Auto Claude pour créer des tâches"