import React from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from '@/components/ui/command'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Icon } from "@/components/icon/Icon"; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; type OperationType = 'merge' | 'rebase'; export interface OperationLogEntry { message: string; status: 'pending' | 'running' | 'done' | 'error'; timestamp: number; } interface BranchIntegrationSectionProps { currentBranch: string | null | undefined; localBranches: string[]; remoteBranches: string[]; onMerge: (branch: string) => void; onRebase: (branch: string) => void; disabled?: boolean; isOperating?: boolean; operationLogs?: OperationLogEntry[]; onOperationComplete?: () => void; mode?: 'dialog' | 'inline'; defaultTargetBranch?: string; } export const BranchIntegrationSection: React.FC = ({ currentBranch, localBranches, remoteBranches, onMerge, onRebase, disabled = false, isOperating = false, operationLogs = [], onOperationComplete, mode = 'dialog', defaultTargetBranch, }) => { const { t } = useI18n(); const [dialogOpen, setDialogOpen] = React.useState(false); const [operation, setOperation] = React.useState('merge'); const [selectedBranch, setSelectedBranch] = React.useState(null); const [branchDropdownOpen, setBranchDropdownOpen] = React.useState(false); const [branchSearch, setBranchSearch] = React.useState(''); const searchInputRef = React.useRef(null); const logContainerRef = React.useRef(null); const isDisabled = disabled || isOperating; const targetBranchLabel = currentBranch || t('gitView.branch.currentBranchFallback'); // Check if operation completed (all logs are done or error) const operationCompleted = operationLogs.length > 0 && operationLogs.every(log => log.status === 'done' || log.status === 'error'); const hasError = operationLogs.some(log => log.status === 'error'); // Auto-scroll log container when new entries are added React.useEffect(() => { if (logContainerRef.current) { logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; } }, [operationLogs]); // Filter branches based on search const filteredLocal = React.useMemo(() => { const term = branchSearch.toLowerCase(); const remoteBranchNames = new Set( remoteBranches .map((branch) => branch.slice(branch.indexOf('/') + 1)) .filter(Boolean) ); const filtered = localBranches.filter((branch) => branch !== currentBranch && !remoteBranchNames.has(branch)); if (!term) return filtered; return filtered.filter((b) => b.toLowerCase().includes(term)); }, [branchSearch, localBranches, currentBranch, remoteBranches]); const filteredRemote = React.useMemo(() => { const term = branchSearch.toLowerCase(); if (!term) return remoteBranches; return remoteBranches.filter((b) => b.toLowerCase().includes(term)); }, [branchSearch, remoteBranches]); const resolveDefaultBranch = React.useCallback(() => { if (!defaultTargetBranch) return null; if (remoteBranches.includes(defaultTargetBranch)) return defaultTargetBranch; if (localBranches.includes(defaultTargetBranch)) return defaultTargetBranch; return null; }, [defaultTargetBranch, localBranches, remoteBranches]); const handleOpenDialog = () => { setDialogOpen(true); setSelectedBranch(resolveDefaultBranch()); setOperation('merge'); setBranchSearch(''); }; const handleSelectBranch = (branch: string) => { setSelectedBranch(branch); setBranchDropdownOpen(false); setBranchSearch(''); }; const handleConfirm = () => { if (!selectedBranch) return; // Don't close dialog - keep it open to show progress if (operation === 'merge') { onMerge(selectedBranch); } else { onRebase(selectedBranch); } }; const handleCancel = () => { // Don't allow cancel during operation if (isOperating) return; setSelectedBranch(null); setOperation('merge'); setBranchSearch(''); setDialogOpen(false); }; const handleClose = () => { // Only allow closing when operation is complete or not started if (isOperating && !operationCompleted) return; if (operationCompleted) { onOperationComplete?.(); } setSelectedBranch(null); setOperation('merge'); setBranchSearch(''); setDialogOpen(false); }; React.useEffect(() => { if (!branchDropdownOpen) { setBranchSearch(''); } }, [branchDropdownOpen]); React.useEffect(() => { if (mode !== 'inline' || selectedBranch) return; setSelectedBranch(resolveDefaultBranch()); }, [mode, resolveDefaultBranch, selectedBranch]); const renderOperating = () => (
{operationLogs.map((log, index) => (
{log.status === 'running' && ( )} {log.status === 'done' && ( )} {log.status === 'error' && ( )} {log.status === 'pending' && (
)}
{log.message}
))}
{operationCompleted ? ( mode === 'dialog' ? ( ) : (
) ) : null}
); const renderForm = () => (
{/* Operation Selection */}

{t('gitView.branch.operation')}

{/* Branch Selection */}

{operation === 'merge' ? t('gitView.branch.branchToMergeInto', { branch: targetBranchLabel }) : t('gitView.branch.branchToRebaseOnto')}

event.stopPropagation()} /> {t('gitView.branch.empty')} {filteredLocal.length > 0 && ( {filteredLocal.map((branch) => ( handleSelectBranch(branch)}> {branch} ))} )} {filteredLocal.length > 0 && filteredRemote.length > 0 ? : null} {filteredRemote.length > 0 && ( {filteredRemote.map((branch) => ( handleSelectBranch(branch)}> {branch} ))} )}
{/* Summary */} {selectedBranch ? (

{operation === 'merge' ? ( <> {t('gitView.branch.summaryMergePrefix')} {selectedBranch} {t('gitView.branch.summaryMergeInfix')} {targetBranchLabel} ) : ( <> {t('gitView.branch.summaryRebasePrefix')} {targetBranchLabel} {t('gitView.branch.summaryRebaseInfix')} {selectedBranch} )}

) : null} {mode === 'dialog' ? ( ) : (
)}
); const body = isOperating ? renderOperating() : renderForm(); if (mode === 'inline') { return (
{t('gitView.branch.updateTitle')}
{t('gitView.branch.updateDescriptionPrefix')}{' '} {targetBranchLabel}.
{body}
); } return ( <> {t('gitView.branch.mergeRebaseTooltip')} { if (!open) { handleClose(); } else { setDialogOpen(true); } }}> {t('gitView.branch.updateTitle')} {isOperating ? ( operationCompleted ? ( hasError ? t('gitView.branch.operationFailed') : t('gitView.branch.operationCompleted') ) : ( operation === 'merge' ? t('gitView.branch.mergingInProgress') : t('gitView.branch.rebasingInProgress') ) ) : ( <> {t('gitView.branch.dialogDescriptionPrefix')}{' '} {targetBranchLabel} . )} {body} ); };