auto-claude: subtask-1-1 - Remove Popover wrapper and related functionality from ClaudeCodeStatusBadge (#1566)
- Remove Popover, PopoverContent, PopoverTrigger wrappers - Remove AlertDialog components for update/rollback/path change warnings - Remove version selector (Select component) and installation selector - Remove all popover-related state (isOpen, showUpdateWarning, showRollbackWarning, etc.) - Remove unused imports (Button, Download, RefreshCw, ExternalLink, FolderOpen, Select, AlertDialog) - Remove install/update functions and related state (isInstalling, availableVersions, etc.) - Keep only the status badge display with colored indicator - Keep Tooltip for hover information - Keep version checking for status indicator - Add new i18n key upToDateWithVersion for showing version in tooltip Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,33 +6,10 @@ import {
|
||||
AlertTriangle,
|
||||
X,
|
||||
Loader2,
|
||||
Download,
|
||||
RefreshCw,
|
||||
ExternalLink,
|
||||
FolderOpen,
|
||||
} from "lucide-react";
|
||||
import { Button } from "./ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./ui/select";
|
||||
import { cn } from "../lib/utils";
|
||||
import type { ClaudeCodeVersionInfo, ClaudeInstallationInfo } from "../../shared/types/cli";
|
||||
import type { ClaudeCodeVersionInfo } from "../../shared/types/cli";
|
||||
|
||||
interface ClaudeCodeStatusBadgeProps {
|
||||
className?: string;
|
||||
@@ -42,36 +19,15 @@ type StatusType = "loading" | "installed" | "outdated" | "not-found" | "error";
|
||||
|
||||
// Check every 24 hours
|
||||
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
// Delay before re-checking version after install/update
|
||||
const VERSION_RECHECK_DELAY_MS = 5000;
|
||||
|
||||
/**
|
||||
* Claude Code CLI status badge for the sidebar.
|
||||
* Shows installation status and provides quick access to install/update.
|
||||
* Shows installation status with a tooltip on hover.
|
||||
*/
|
||||
export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {
|
||||
const { t } = useTranslation(["common", "navigation"]);
|
||||
const [status, setStatus] = useState<StatusType>("loading");
|
||||
const [versionInfo, setVersionInfo] = useState<ClaudeCodeVersionInfo | null>(null);
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const [lastChecked, setLastChecked] = useState<Date | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showUpdateWarning, setShowUpdateWarning] = useState(false);
|
||||
|
||||
// Version rollback state
|
||||
const [availableVersions, setAvailableVersions] = useState<string[]>([]);
|
||||
const [isLoadingVersions, setIsLoadingVersions] = useState(false);
|
||||
const [versionsError, setVersionsError] = useState<string | null>(null);
|
||||
const [selectedVersion, setSelectedVersion] = useState<string | null>(null);
|
||||
const [showRollbackWarning, setShowRollbackWarning] = useState(false);
|
||||
const [installError, setInstallError] = useState<string | null>(null);
|
||||
|
||||
// CLI path selection state
|
||||
const [installations, setInstallations] = useState<ClaudeInstallationInfo[]>([]);
|
||||
const [isLoadingInstallations, setIsLoadingInstallations] = useState(false);
|
||||
const [installationsError, setInstallationsError] = useState<string | null>(null);
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<string | null>(null);
|
||||
const [showPathChangeWarning, setShowPathChangeWarning] = useState(false);
|
||||
|
||||
// Check Claude Code version
|
||||
const checkVersion = useCallback(async () => {
|
||||
@@ -85,7 +41,6 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
|
||||
if (result.success && result.data) {
|
||||
setVersionInfo(result.data);
|
||||
setLastChecked(new Date());
|
||||
|
||||
if (!result.data.installed) {
|
||||
setStatus("not-found");
|
||||
@@ -97,60 +52,11 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
} else {
|
||||
setStatus("error");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to check Claude Code version:", err);
|
||||
} catch {
|
||||
setStatus("error");
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fetch available versions
|
||||
const fetchVersions = useCallback(async () => {
|
||||
if (!window.electronAPI?.getClaudeCodeVersions) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingVersions(true);
|
||||
setVersionsError(null);
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.getClaudeCodeVersions();
|
||||
if (result.success && result.data) {
|
||||
setAvailableVersions(result.data.versions);
|
||||
} else {
|
||||
setVersionsError(result.error || "Failed to load versions");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch versions:", err);
|
||||
setVersionsError("Failed to load versions");
|
||||
} finally {
|
||||
setIsLoadingVersions(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fetch CLI installations
|
||||
const fetchInstallations = useCallback(async () => {
|
||||
if (!window.electronAPI?.getClaudeCodeInstallations) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingInstallations(true);
|
||||
setInstallationsError(null);
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.getClaudeCodeInstallations();
|
||||
if (result.success && result.data) {
|
||||
setInstallations(result.data.installations);
|
||||
} else {
|
||||
setInstallationsError(result.error || "Failed to load installations");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch installations:", err);
|
||||
setInstallationsError("Failed to load installations");
|
||||
} finally {
|
||||
setIsLoadingInstallations(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Initial check and periodic re-check
|
||||
useEffect(() => {
|
||||
checkVersion();
|
||||
@@ -163,155 +69,6 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
return () => clearInterval(interval);
|
||||
}, [checkVersion]);
|
||||
|
||||
// Fetch versions when popover opens and Claude is installed
|
||||
useEffect(() => {
|
||||
if (isOpen && versionInfo?.installed && availableVersions.length === 0) {
|
||||
fetchVersions();
|
||||
}
|
||||
}, [isOpen, versionInfo?.installed, availableVersions.length, fetchVersions]);
|
||||
|
||||
// Fetch installations when popover opens
|
||||
useEffect(() => {
|
||||
if (isOpen && installations.length === 0) {
|
||||
fetchInstallations();
|
||||
}
|
||||
}, [isOpen, installations.length, fetchInstallations]);
|
||||
|
||||
// Perform the actual install/update
|
||||
const performInstall = async () => {
|
||||
setIsInstalling(true);
|
||||
setShowUpdateWarning(false);
|
||||
setInstallError(null);
|
||||
try {
|
||||
if (!window.electronAPI?.installClaudeCode) {
|
||||
setInstallError("Installation not available");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.installClaudeCode();
|
||||
|
||||
if (result.success) {
|
||||
// Re-check after a delay
|
||||
setTimeout(() => {
|
||||
checkVersion();
|
||||
}, VERSION_RECHECK_DELAY_MS);
|
||||
} else {
|
||||
setInstallError(result.error || "Installation failed");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to install Claude Code:", err);
|
||||
setInstallError(err instanceof Error ? err.message : "Installation failed");
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Perform version rollback/switch
|
||||
const performVersionSwitch = async () => {
|
||||
if (!selectedVersion) return;
|
||||
|
||||
setIsInstalling(true);
|
||||
setShowRollbackWarning(false);
|
||||
setInstallError(null);
|
||||
|
||||
try {
|
||||
if (!window.electronAPI?.installClaudeCodeVersion) {
|
||||
setInstallError("Version switching not available");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.installClaudeCodeVersion(selectedVersion);
|
||||
|
||||
if (result.success) {
|
||||
// Re-check after a delay
|
||||
setTimeout(() => {
|
||||
checkVersion();
|
||||
}, VERSION_RECHECK_DELAY_MS);
|
||||
} else {
|
||||
setInstallError(result.error || "Failed to switch version");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to switch Claude Code version:", err);
|
||||
setInstallError(err instanceof Error ? err.message : "Failed to switch version");
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
setSelectedVersion(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Perform CLI path switch
|
||||
const performPathSwitch = async () => {
|
||||
if (!selectedInstallation) return;
|
||||
|
||||
setIsInstalling(true);
|
||||
setShowPathChangeWarning(false);
|
||||
setInstallError(null);
|
||||
|
||||
try {
|
||||
if (!window.electronAPI?.setClaudeCodeActivePath) {
|
||||
setInstallError("Path switching not available");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await window.electronAPI.setClaudeCodeActivePath(selectedInstallation);
|
||||
|
||||
if (result.success) {
|
||||
// Re-check version and refresh installations
|
||||
setTimeout(() => {
|
||||
checkVersion();
|
||||
fetchInstallations();
|
||||
}, VERSION_RECHECK_DELAY_MS);
|
||||
} else {
|
||||
setInstallError(result.error || "Failed to switch CLI path");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to switch Claude CLI path:", err);
|
||||
setInstallError(err instanceof Error ? err.message : "Failed to switch CLI path");
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
setSelectedInstallation(null);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle install/update button click
|
||||
const handleInstall = () => {
|
||||
if (status === "outdated") {
|
||||
// Show warning for updates since it will close running Claude sessions
|
||||
setShowUpdateWarning(true);
|
||||
} else {
|
||||
// Fresh install - no warning needed
|
||||
performInstall();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle installation selection
|
||||
const handleInstallationSelect = (cliPath: string) => {
|
||||
// Don't do anything if it's the currently active installation
|
||||
const installation = installations.find(i => i.path === cliPath);
|
||||
if (installation?.isActive) {
|
||||
return;
|
||||
}
|
||||
setInstallError(null);
|
||||
setSelectedInstallation(cliPath);
|
||||
setShowPathChangeWarning(true);
|
||||
};
|
||||
|
||||
// Normalize version string by removing 'v' prefix for comparison
|
||||
const normalizeVersion = (v: string) => v.replace(/^v/, '');
|
||||
|
||||
// Handle version selection
|
||||
const handleVersionSelect = (version: string) => {
|
||||
// Don't do anything if it's the currently installed version (normalize both for comparison)
|
||||
const normalizedSelected = normalizeVersion(version);
|
||||
const normalizedInstalled = versionInfo?.installed ? normalizeVersion(versionInfo.installed) : '';
|
||||
if (normalizedSelected === normalizedInstalled) {
|
||||
return;
|
||||
}
|
||||
setInstallError(null);
|
||||
setSelectedVersion(version);
|
||||
setShowRollbackWarning(true);
|
||||
};
|
||||
|
||||
// Get status indicator color
|
||||
const getStatusColor = () => {
|
||||
switch (status) {
|
||||
@@ -327,29 +84,17 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
}
|
||||
};
|
||||
|
||||
// Get status icon
|
||||
const getStatusIcon = () => {
|
||||
switch (status) {
|
||||
case "loading":
|
||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||
case "installed":
|
||||
return <Check className="h-3 w-3" />;
|
||||
case "outdated":
|
||||
return <AlertTriangle className="h-3 w-3" />;
|
||||
case "not-found":
|
||||
return <X className="h-3 w-3" />;
|
||||
case "error":
|
||||
return <AlertTriangle className="h-3 w-3" />;
|
||||
}
|
||||
};
|
||||
|
||||
// Get tooltip text
|
||||
const getTooltipText = () => {
|
||||
switch (status) {
|
||||
case "loading":
|
||||
return t("navigation:claudeCode.checking", "Checking Claude Code...");
|
||||
case "installed":
|
||||
return t("navigation:claudeCode.upToDate", "Claude Code is up to date");
|
||||
return versionInfo?.installed
|
||||
? t("navigation:claudeCode.upToDateWithVersion", "Claude Code {{version}} installed", {
|
||||
version: versionInfo.installed,
|
||||
})
|
||||
: t("navigation:claudeCode.upToDate", "Claude Code is up to date");
|
||||
case "outdated":
|
||||
return t("navigation:claudeCode.updateAvailable", "Claude Code update available");
|
||||
case "not-found":
|
||||
@@ -359,366 +104,62 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)
|
||||
}
|
||||
};
|
||||
|
||||
// Get status icon for the badge
|
||||
const getStatusIcon = () => {
|
||||
switch (status) {
|
||||
case "loading":
|
||||
return <Loader2 className="h-3 w-3 animate-spin" />;
|
||||
case "installed":
|
||||
return <Check className="h-3 w-3" />;
|
||||
case "outdated":
|
||||
return <AlertTriangle className="h-3 w-3" />;
|
||||
case "not-found":
|
||||
case "error":
|
||||
return <X className="h-3 w-3" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-2 py-1.5 text-xs rounded-md",
|
||||
status === "not-found" || status === "error" ? "text-destructive" : "",
|
||||
status === "outdated" ? "text-yellow-600 dark:text-yellow-500" : "",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="relative">
|
||||
<Terminal className="h-4 w-4" />
|
||||
<span
|
||||
className={cn(
|
||||
"w-full justify-start gap-2 text-xs",
|
||||
status === "not-found" || status === "error" ? "text-destructive" : "",
|
||||
status === "outdated" ? "text-yellow-600 dark:text-yellow-500" : "",
|
||||
className
|
||||
"absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full",
|
||||
getStatusColor()
|
||||
)}
|
||||
>
|
||||
<div className="relative">
|
||||
<Terminal className="h-4 w-4" />
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -bottom-0.5 -right-0.5 h-2 w-2 rounded-full",
|
||||
getStatusColor()
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<span className="truncate">Claude Code</span>
|
||||
{status === "outdated" && (
|
||||
<span className="ml-auto text-[10px] bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">
|
||||
{t("common:update", "Update")}
|
||||
</span>
|
||||
)}
|
||||
{status === "not-found" && (
|
||||
<span className="ml-auto text-[10px] bg-destructive/20 text-destructive px-1.5 py-0.5 rounded">
|
||||
{t("common:install", "Install")}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{getTooltipText()}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<PopoverContent side="right" align="end" className="w-72">
|
||||
<div className="space-y-3">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10">
|
||||
<Terminal className="h-4 w-4 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-medium">Claude Code CLI</h4>
|
||||
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
||||
{getStatusIcon()}
|
||||
{status === "installed" && t("navigation:claudeCode.installed", "Installed")}
|
||||
{status === "outdated" && t("navigation:claudeCode.outdated", "Update available")}
|
||||
{status === "not-found" && t("navigation:claudeCode.missing", "Not installed")}
|
||||
{status === "loading" && t("navigation:claudeCode.checking", "Checking...")}
|
||||
{status === "error" && t("navigation:claudeCode.error", "Error")}
|
||||
</p>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Version info */}
|
||||
{versionInfo && status !== "loading" && (
|
||||
<div className="text-xs space-y-1 p-2 bg-muted rounded-md">
|
||||
{versionInfo.installed && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">
|
||||
{t("navigation:claudeCode.current", "Current")}:
|
||||
</span>
|
||||
<span className="font-mono">{versionInfo.installed}</span>
|
||||
</div>
|
||||
)}
|
||||
{versionInfo.latest && versionInfo.latest !== "unknown" && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">
|
||||
{t("navigation:claudeCode.latest", "Latest")}:
|
||||
</span>
|
||||
<span className="font-mono">{versionInfo.latest}</span>
|
||||
</div>
|
||||
)}
|
||||
{versionInfo.path && (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<span className="text-muted-foreground flex items-center gap-1">
|
||||
<FolderOpen className="h-3 w-3" />
|
||||
{t("navigation:claudeCode.path", "Path")}:
|
||||
</span>
|
||||
<span
|
||||
className="font-mono text-[10px] truncate max-w-[140px]"
|
||||
title={versionInfo.path}
|
||||
>
|
||||
{versionInfo.path}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{lastChecked && (
|
||||
<div className="flex justify-between text-muted-foreground">
|
||||
<span>{t("navigation:claudeCode.lastChecked", "Last checked")}:</span>
|
||||
<span>{lastChecked.toLocaleTimeString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="truncate">Claude Code</span>
|
||||
{status === "outdated" && (
|
||||
<span className="ml-auto text-[10px] bg-yellow-500/20 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded flex items-center gap-1">
|
||||
{getStatusIcon()}
|
||||
{t("common:update", "Update")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
{(status === "not-found" || status === "outdated") && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 gap-1"
|
||||
onClick={handleInstall}
|
||||
disabled={isInstalling}
|
||||
>
|
||||
{isInstalling ? (
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-3 w-3" />
|
||||
)}
|
||||
{status === "outdated"
|
||||
? t("common:update", "Update")
|
||||
: t("common:install", "Install")}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-1"
|
||||
onClick={() => checkVersion()}
|
||||
disabled={status === "loading"}
|
||||
>
|
||||
<RefreshCw className={cn("h-3 w-3", status === "loading" && "animate-spin")} />
|
||||
{t("common:refresh", "Refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Install/Update error display */}
|
||||
{installError && (
|
||||
<div className="text-xs p-2 bg-destructive/10 text-destructive rounded-md flex items-center gap-2">
|
||||
<AlertTriangle className="h-3 w-3 shrink-0" />
|
||||
<span>{installError}</span>
|
||||
</div>
|
||||
{status === "not-found" && (
|
||||
<span className="ml-auto text-[10px] bg-destructive/20 text-destructive px-1.5 py-0.5 rounded flex items-center gap-1">
|
||||
{getStatusIcon()}
|
||||
{t("common:install", "Install")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Version selector - only show when Claude is installed */}
|
||||
{versionInfo?.installed && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
{t("navigation:claudeCode.switchVersion", "Switch Version")}
|
||||
</label>
|
||||
<Select
|
||||
value={selectedVersion || ""}
|
||||
onValueChange={handleVersionSelect}
|
||||
disabled={isLoadingVersions || isInstalling}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingVersions
|
||||
? t("navigation:claudeCode.loadingVersions", "Loading versions...")
|
||||
: versionsError
|
||||
? t("navigation:claudeCode.failedToLoadVersions", "Failed to load versions")
|
||||
: t("navigation:claudeCode.selectVersion", "Select version")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableVersions.map((version) => {
|
||||
const isCurrentVersion = normalizeVersion(version) === normalizeVersion(versionInfo.installed || '');
|
||||
return (
|
||||
<SelectItem
|
||||
key={version}
|
||||
value={version}
|
||||
className="text-xs"
|
||||
disabled={isCurrentVersion}
|
||||
>
|
||||
<span className="font-mono">{version}</span>
|
||||
{isCurrentVersion && (
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
({t("navigation:claudeCode.currentVersion", "Current")})
|
||||
</span>
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{status === "installed" && (
|
||||
<span className="ml-auto text-[10px] text-muted-foreground flex items-center gap-1">
|
||||
{getStatusIcon()}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* CLI Installation selector - show when multiple installations are found */}
|
||||
{installations.length > 1 && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs text-muted-foreground">
|
||||
{t("navigation:claudeCode.switchInstallation", "Switch Installation")}
|
||||
</label>
|
||||
<Select
|
||||
value={selectedInstallation || ""}
|
||||
onValueChange={handleInstallationSelect}
|
||||
disabled={isLoadingInstallations || isInstalling}
|
||||
>
|
||||
<SelectTrigger className="h-8 text-xs">
|
||||
<SelectValue
|
||||
placeholder={
|
||||
isLoadingInstallations
|
||||
? t("navigation:claudeCode.loadingInstallations", "Loading installations...")
|
||||
: installationsError
|
||||
? t("navigation:claudeCode.failedToLoadInstallations", "Failed to load installations")
|
||||
: t("navigation:claudeCode.selectInstallation", "Select installation")
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{installations.map((installation) => (
|
||||
<SelectItem
|
||||
key={installation.path}
|
||||
value={installation.path}
|
||||
className="text-xs"
|
||||
disabled={installation.isActive}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-mono text-[10px] truncate max-w-[180px]" title={installation.path}>
|
||||
{/* Split on both path separators for cross-platform compatibility */}
|
||||
{installation.path.split(/[/\\]/).slice(-2).join('/') || installation.path}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-[9px]">
|
||||
{installation.version ? `v${installation.version}` : t("navigation:claudeCode.versionUnknown", "version unknown")} ({installation.source})
|
||||
{installation.isActive && ` - ${t("navigation:claudeCode.activeInstallation", "Active")}`}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Learn more link */}
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() => window.electronAPI?.openExternal?.("https://claude.ai/code")}
|
||||
aria-label={t(
|
||||
"navigation:claudeCode.learnMoreAriaLabel",
|
||||
"Learn more about Claude Code (opens in new window)"
|
||||
)}
|
||||
>
|
||||
{t("navigation:claudeCode.learnMore", "Learn more about Claude Code")}
|
||||
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||
</Button>
|
||||
|
||||
{/* Changelog link */}
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="w-full text-xs text-muted-foreground gap-1"
|
||||
onClick={() =>
|
||||
window.electronAPI?.openExternal?.(
|
||||
"https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md"
|
||||
)
|
||||
}
|
||||
aria-label={t(
|
||||
"navigation:claudeCode.viewChangelogAriaLabel",
|
||||
"View Claude Code Changelog (opens in new window)"
|
||||
)}
|
||||
>
|
||||
{t("navigation:claudeCode.viewChangelog", "View Claude Code Changelog")}
|
||||
<ExternalLink className="h-3 w-3" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
||||
{/* Update warning dialog */}
|
||||
<AlertDialog open={showUpdateWarning} onOpenChange={setShowUpdateWarning}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("navigation:claudeCode.updateWarningTitle", "Update Claude Code?")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t(
|
||||
"navigation:claudeCode.updateWarningDescription",
|
||||
"Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding."
|
||||
)}
|
||||
<span className="block mt-2 font-semibold text-foreground">
|
||||
{t(
|
||||
"navigation:claudeCode.updateWarningTerminalNote",
|
||||
"A terminal window will open to run the installation command. Please wait for the installation to complete before continuing."
|
||||
)}
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("common:cancel", "Cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={performInstall}>
|
||||
{t("navigation:claudeCode.updateAnyway", "Open Terminal & Update")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Version rollback warning dialog */}
|
||||
<AlertDialog open={showRollbackWarning} onOpenChange={setShowRollbackWarning}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("navigation:claudeCode.rollbackWarningTitle", "Switch to version {{version}}?", {
|
||||
version: selectedVersion,
|
||||
})}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t(
|
||||
"navigation:claudeCode.rollbackWarningDescription",
|
||||
"Switching versions will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding."
|
||||
)}
|
||||
<span className="block mt-2 font-semibold text-foreground">
|
||||
{t(
|
||||
"navigation:claudeCode.rollbackWarningTerminalNote",
|
||||
"A terminal window will open to run the installation command. Please wait for the installation to complete before continuing."
|
||||
)}
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setSelectedVersion(null)}>
|
||||
{t("common:cancel", "Cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={performVersionSwitch}>
|
||||
{t("navigation:claudeCode.switchAnyway", "Open Terminal & Switch")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Path change warning dialog */}
|
||||
<AlertDialog open={showPathChangeWarning} onOpenChange={setShowPathChangeWarning}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{t("navigation:claudeCode.pathChangeWarningTitle", "Switch CLI installation?")}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t(
|
||||
"navigation:claudeCode.pathChangeWarningDescription",
|
||||
"Switching CLI installations will use a different Claude Code binary. Any running sessions will continue using the previous installation until restarted."
|
||||
)}
|
||||
<span className="block mt-2 font-mono text-xs break-all">
|
||||
{selectedInstallation}
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setSelectedInstallation(null)}>
|
||||
{t("common:cancel", "Cancel")}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={performPathSwitch}>
|
||||
{t("navigation:claudeCode.switchInstallationConfirm", "Switch")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</Popover>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">{getTooltipText()}</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"claudeCode": {
|
||||
"checking": "Checking Claude Code...",
|
||||
"upToDate": "Claude Code is up to date",
|
||||
"upToDateWithVersion": "Claude Code {{version}} installed",
|
||||
"updateAvailable": "Claude Code update available",
|
||||
"notInstalled": "Claude Code not installed",
|
||||
"error": "Error checking Claude Code",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"claudeCode": {
|
||||
"checking": "Vérification de Claude Code...",
|
||||
"upToDate": "Claude Code est à jour",
|
||||
"upToDateWithVersion": "Claude Code {{version}} installé",
|
||||
"updateAvailable": "Mise à jour Claude Code disponible",
|
||||
"notInstalled": "Claude Code non installé",
|
||||
"error": "Erreur de vérification de Claude Code",
|
||||
|
||||
Reference in New Issue
Block a user