'use client';
import React from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Progress } from '@/components/ui/progress';
import { Button } from '@/components/ui/button';
import {
Download,
Package,
Settings,
Rocket,
CheckCircle2,
XCircle,
Loader2,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import {
usePluginInstallTasks,
InstallStage,
PluginInstallTask,
} from './PluginInstallTaskContext';
import { cn } from '@/lib/utils';
const STAGES: {
key: InstallStage;
icon: React.ElementType;
i18nKey: string;
}[] = [
{
key: InstallStage.DOWNLOADING,
icon: Download,
i18nKey: 'plugins.installProgress.downloading',
},
{
key: InstallStage.INSTALLING_DEPS,
icon: Package,
i18nKey: 'plugins.installProgress.installingDeps',
},
{
key: InstallStage.INITIALIZING,
icon: Settings,
i18nKey: 'plugins.installProgress.initializing',
},
{
key: InstallStage.LAUNCHING,
icon: Rocket,
i18nKey: 'plugins.installProgress.launching',
},
];
function getStageIndex(stage: InstallStage): number {
const idx = STAGES.findIndex((s) => s.key === stage);
return idx >= 0 ? idx : -1;
}
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i];
}
/**
* A single stage row — used in both active (single) and completed (all) views.
*/
function StageRow({
icon: Icon,
label,
isActive,
isCompleted,
isError,
detail,
}: {
icon: React.ElementType;
label: string;
isActive: boolean;
isCompleted: boolean;
isError: boolean;
detail?: React.ReactNode;
}) {
return (
{/* Left: status indicator */}
{isCompleted ? (
) : isError && isActive ? (
) : isActive ? (
) : (
)}
{/* Middle: label + detail */}
{label}
{/* Small icon after text */}
{detail && (
{detail}
)}
);
}
function formatSpeed(bytesPerSec: number): string {
if (bytesPerSec === 0) return '0 B/s';
const k = 1024;
const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s'];
const i = Math.floor(Math.log(bytesPerSec) / Math.log(k));
return (bytesPerSec / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i];
}
function TaskProgressContent({ task }: { task: PluginInstallTask }) {
const { t } = useTranslation();
const currentStageIndex = getStageIndex(task.stage);
const isDone = task.stage === InstallStage.DONE;
const isError = task.stage === InstallStage.ERROR;
/** Build detail node for a stage */
const getStageDetail = (
stageKey: InstallStage,
isCompletedView: boolean,
): React.ReactNode | undefined => {
if (stageKey === InstallStage.DOWNLOADING) {
// Show download progress: current / total + speed
const dlTotal = task.downloadTotal || task.fileSize;
const dlCurrent = task.downloadCurrent;
const dlSpeed = task.downloadSpeed;
if (isCompletedView && dlTotal) {
// Done view: just show total size
return t('plugins.installProgress.downloadSize', {
size: formatFileSize(dlTotal),
});
}
if (dlTotal && dlCurrent != null) {
const parts: string[] = [];
parts.push(`${formatFileSize(dlCurrent)} / ${formatFileSize(dlTotal)}`);
if (dlSpeed && dlSpeed > 0) {
parts.push(formatSpeed(dlSpeed));
}
return parts.join(' · ');
}
if (dlTotal) {
return t('plugins.installProgress.downloadSize', {
size: formatFileSize(dlTotal),
});
}
return undefined;
}
if (stageKey === InstallStage.INSTALLING_DEPS) {
const total = task.depsTotal;
const installed = task.depsInstalled;
const remaining = task.depsRemaining;
const currentDep = task.currentDep;
const dlSize = task.depsDownloadedSize;
const speed = task.depsSpeed;
if (isCompletedView && total != null) {
const parts: string[] = [];
parts.push(t('plugins.installProgress.depsInfo', { count: total }));
if (dlSize && dlSize > 0) {
parts.push(formatFileSize(dlSize));
}
return parts.join(' · ');
}
if (total != null && installed != null) {
const parts: string[] = [];
parts.push(
t('plugins.installProgress.depsProgress', {
installed,
total,
remaining: remaining ?? total - installed,
}),
);
if (dlSize && dlSize > 0) {
parts.push(formatFileSize(dlSize));
}
if (speed && speed > 0) {
parts.push(formatSpeed(speed));
}
if (currentDep) {
return (
<>
{parts.join(' · ')}
{currentDep}
>
);
}
return parts.join(' · ');
}
if (total != null) {
return t('plugins.installProgress.depsInfo', { count: total });
}
return undefined;
}
return undefined;
};
return (
{/* Overall progress bar — always blue */}
{isDone
? t('plugins.installProgress.completed')
: isError
? t('plugins.installProgress.failed')
: t('plugins.installProgress.overallProgress')}
{isDone ? '100%' : `${task.overallProgress}%`}
div]:bg-blue-500 dark:[&>div]:bg-blue-400',
'bg-blue-100 dark:bg-blue-900/30',
isDone &&
'[&>div]:bg-green-500 dark:[&>div]:bg-green-400 bg-green-100 dark:bg-green-900/30',
isError &&
'[&>div]:bg-red-500 dark:[&>div]:bg-red-400 bg-red-100 dark:bg-red-900/30',
)}
/>
{/* Stage display */}
{isDone
? /* When done: show all stages with completed style */
STAGES.map((stageConfig) => (
))
: isError
? /* Error: show the failed stage */
currentStageIndex >= 0 && (
)
: /* In progress: only show the current active stage */
currentStageIndex >= 0 && (
)}
{/* Done banner */}
{isDone && (
{t('plugins.installProgress.installComplete')}
)}
{/* Error detail */}
{isError && task.error && (
)}
);
}
export default function PluginInstallProgressDialog() {
const { t } = useTranslation();
const { tasks, selectedTaskId, setSelectedTaskId, removeTask } =
usePluginInstallTasks();
const selectedTask = tasks.find((t) => t.id === selectedTaskId) || null;
const open = !!selectedTask;
const handleClose = () => {
setSelectedTaskId(null);
};
const handleDismiss = () => {
if (selectedTask) {
if (
selectedTask.stage === InstallStage.DONE ||
selectedTask.stage === InstallStage.ERROR
) {
removeTask(selectedTask.id);
}
}
setSelectedTaskId(null);
};
return (
!o && handleClose()}>
{selectedTask
? t('plugins.installProgress.title', {
name: selectedTask.pluginName,
})
: t('plugins.installProgress.titleGeneric')}
{selectedTask && }
{selectedTask &&
(selectedTask.stage === InstallStage.DONE ||
selectedTask.stage === InstallStage.ERROR) && (
{t('plugins.installProgress.dismiss')}
)}
{selectedTask?.stage === InstallStage.DONE ||
selectedTask?.stage === InstallStage.ERROR
? t('common.close')
: t('plugins.installProgress.background')}
);
}