fix(file-manager): render transfer toasts without plugin hooks (#1546)

This commit is contained in:
ZacharyZcR authored and GitHub committed 2026-10-03 21:02:38 +08:00
1 parent 4785bf5dfa
commit 2c4de5f705
9 files changed
+82 -12

No files matched your search

@@ -1416,10 +1416,13 @@ function FileManagerContent({
const { downloadSSHFileStream } = const { downloadSSHFileStream } =
await import("./api/ssh-file-operations-api"); await import("./api/ssh-file-operations-api");
toast.loading(<DownloadProgressToast fileName={file.name} loaded={0} />, { toast.loading(
id: toastId, <DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
duration: Infinity, {
}); id: toastId,
duration: Infinity,
},
);
await downloadSSHFileStream( await downloadSSHFileStream(
sshSessionId, sshSessionId,
@@ -1438,6 +1441,7 @@ function FileManagerContent({
toast.loading( toast.loading(
<DownloadProgressToast <DownloadProgressToast
t={t}
fileName={file.name} fileName={file.name}
loaded={loaded} loaded={loaded}
total={total} total={total}
@@ -134,7 +134,7 @@ export function DiffViewer({
const handleDownloadFile = async (file: FileItem) => { const handleDownloadFile = async (file: FileItem) => {
const toastId = toast.loading( const toastId = toast.loading(
<DownloadProgressToast fileName={file.name} loaded={0} />, <DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
{ duration: Infinity }, { duration: Infinity },
); );
try { try {
@@ -145,6 +145,7 @@ export function DiffViewer({
({ loaded, total }) => { ({ loaded, total }) => {
toast.loading( toast.loading(
<DownloadProgressToast <DownloadProgressToast
t={t}
fileName={file.name} fileName={file.name}
loaded={loaded} loaded={loaded}
total={total} total={total}
@@ -1,9 +1,10 @@
import { formatTransferMbPerSec } from "../api/transfer-api"; import { formatTransferMbPerSec } from "../api/transfer-api";
import { useTranslation } from "@termix/plugin-sdk/frontend"; import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import { ArrowDownToLine } from "lucide-react"; import { ArrowDownToLine } from "lucide-react";
import { TransferProgressBar } from "./TransferProgressBar"; import { TransferProgressBar } from "./TransferProgressBar";
interface DownloadProgressToastProps { interface DownloadProgressToastProps {
t: TranslateFn;
fileName: string; fileName: string;
loaded: number; loaded: number;
total?: number; total?: number;
@@ -25,12 +26,12 @@ function formatBytes(bytes: number): string {
} }
export function DownloadProgressToast({ export function DownloadProgressToast({
t,
fileName, fileName,
loaded, loaded,
total, total,
mbPerSec, mbPerSec,
}: DownloadProgressToastProps) { }: DownloadProgressToastProps) {
const { t } = useTranslation();
const percent = const percent =
total !== undefined && total > 0 total !== undefined && total > 0
? Math.min(100, Math.round((loaded / total) * 100)) ? Math.min(100, Math.round((loaded / total) * 100))
@@ -483,7 +483,7 @@ export function FileWindow({
const handleDownload = async () => { const handleDownload = async () => {
const toastId = toast.loading( const toastId = toast.loading(
<DownloadProgressToast fileName={file.name} loaded={0} />, <DownloadProgressToast t={t} fileName={file.name} loaded={0} />,
{ duration: Infinity }, { duration: Infinity },
); );
try { try {
@@ -495,6 +495,7 @@ export function FileWindow({
({ loaded, total }) => { ({ loaded, total }) => {
toast.loading( toast.loading(
<DownloadProgressToast <DownloadProgressToast
t={t}
fileName={file.name} fileName={file.name}
loaded={loaded} loaded={loaded}
total={total} total={total}
@@ -1,5 +1,5 @@
import { Button } from "@termix/plugin-sdk/ui"; import { Button } from "@termix/plugin-sdk/ui";
import { useTranslation } from "@termix/plugin-sdk/frontend"; import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import { formatFileSize } from "../file-manager-utils.ts"; import { formatFileSize } from "../file-manager-utils.ts";
export interface LocalTransferBatchStatus { export interface LocalTransferBatchStatus {
@@ -16,15 +16,16 @@ export interface LocalTransferBatchStatus {
} }
interface LocalTransferProgressToastProps { interface LocalTransferProgressToastProps {
t: TranslateFn;
status: LocalTransferBatchStatus; status: LocalTransferBatchStatus;
onCancel?: () => void; onCancel?: () => void;
} }
export function LocalTransferProgressToast({ export function LocalTransferProgressToast({
t,
status, status,
onCancel, onCancel,
}: LocalTransferProgressToastProps) { }: LocalTransferProgressToastProps) {
const { t } = useTranslation();
const percent = const percent =
status.totalBytes > 0 status.totalBytes > 0
? Math.min(100, Math.round((status.bytesDone / status.totalBytes) * 100)) ? Math.min(100, Math.round((status.bytesDone / status.totalBytes) * 100))
@@ -4,7 +4,7 @@ import {
getTransferProgressPercent, getTransferProgressPercent,
type TransferProgressResponse, type TransferProgressResponse,
} from "../api/transfer-api"; } from "../api/transfer-api";
import { useTranslation } from "@termix/plugin-sdk/frontend"; import type { TranslateFn } from "@termix/plugin-sdk/frontend";
import { import {
Archive, Archive,
ArchiveRestore, ArchiveRestore,
@@ -16,6 +16,7 @@ import {
import { TransferProgressBar } from "./TransferProgressBar"; import { TransferProgressBar } from "./TransferProgressBar";
interface TransferProgressToastProps { interface TransferProgressToastProps {
t: TranslateFn;
status: TransferProgressResponse; status: TransferProgressResponse;
liveMbPerSec?: number; liveMbPerSec?: number;
stalled?: boolean; stalled?: boolean;
@@ -25,6 +26,7 @@ interface TransferProgressToastProps {
} }
export function TransferProgressToast({ export function TransferProgressToast({
t,
status, status,
liveMbPerSec, liveMbPerSec,
stalled = false, stalled = false,
@@ -32,7 +34,6 @@ export function TransferProgressToast({
onCancel, onCancel,
cancelling = false, cancelling = false,
}: TransferProgressToastProps) { }: TransferProgressToastProps) {
const { t } = useTranslation();
const percent = getTransferProgressPercent(status); const percent = getTransferProgressPercent(status);
let title = t("transfer.progressTransferring"); let title = t("transfer.progressTransferring");
@@ -193,6 +193,7 @@ export function useLocalTransfers({
const render = () => { const render = () => {
toast.loading( toast.loading(
createElement(LocalTransferProgressToast, { createElement(LocalTransferProgressToast, {
t,
status: { ...status, cancelling }, status: { ...status, cancelling },
onCancel: () => { onCancel: () => {
cancelled = true; cancelled = true;
@@ -38,6 +38,7 @@ function formatFileSize(bytes?: number): string {
} }
function renderTransferProgressToast( function renderTransferProgressToast(
t: TFunction,
toastId: string | number, toastId: string | number,
status: TransferProgressResponse, status: TransferProgressResponse,
liveMbPerSec?: number, liveMbPerSec?: number,
@@ -47,6 +48,7 @@ function renderTransferProgressToast(
): void { ): void {
toast.loading( toast.loading(
<TransferProgressToast <TransferProgressToast
t={t}
status={status} status={status}
liveMbPerSec={liveMbPerSec} liveMbPerSec={liveMbPerSec}
stalled={stalled} stalled={stalled}
@@ -265,6 +267,7 @@ export function beginTransferProgressMonitoring(
const progressToast = toast.loading( const progressToast = toast.loading(
<TransferProgressToast <TransferProgressToast
t={t}
status={initialStatus} status={initialStatus}
formatSize={formatFileSize} formatSize={formatFileSize}
/>, />,
@@ -279,6 +282,7 @@ export function beginTransferProgressMonitoring(
if (cancelling) return; if (cancelling) return;
cancelling = true; cancelling = true;
renderTransferProgressToast( renderTransferProgressToast(
t,
progressToast, progressToast,
{ ...initialStatus, transferId }, { ...initialStatus, transferId },
undefined, undefined,
@@ -294,6 +298,7 @@ export function beginTransferProgressMonitoring(
options.onProgress?.(status); options.onProgress?.(status);
const { rate, stalled } = progressTracker.update(status); const { rate, stalled } = progressTracker.update(status);
renderTransferProgressToast( renderTransferProgressToast(
t,
progressToast, progressToast,
status, status,
rate, rate,
@@ -333,6 +338,7 @@ export function beginTransferProgressMonitoring(
}); });
renderTransferProgressToast( renderTransferProgressToast(
t,
progressToast, progressToast,
initialStatus, initialStatus,
undefined, undefined,
@@ -0,0 +1,54 @@
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, expect, it, vi } from "vitest";
vi.mock("@termix/plugin-sdk/frontend", () => ({
useTranslation: () => {
throw Error("No plugin scope in the global toaster");
},
}));
import { DownloadProgressToast } from "../../src/frontend/components/DownloadProgressToast";
import { TransferProgressToast } from "../../src/frontend/components/TransferProgressToast";
import { LocalTransferProgressToast } from "../../src/frontend/components/LocalTransferProgressToast";
afterEach(cleanup);
const t = (key: string) => key;
it("renders download progress outside plugin scope", () => {
render(
<DownloadProgressToast t={t} fileName="test.txt" loaded={50} total={100} />,
);
expect(screen.getByRole("progressbar").getAttribute("aria-valuenow")).toBe(
"50",
);
});
it("renders host transfer progress outside plugin scope", () => {
render(
<TransferProgressToast
t={t}
status={{
transferId: "test",
status: "running",
phase: "transferring",
bytesTransferred: 50,
totalBytes: 100,
}}
formatSize={String}
/>,
);
expect(screen.getByRole("progressbar")).toBeTruthy();
});
it("preserves cancellation on local transfers outside plugin scope", () => {
const cancel = vi.fn();
render(
<LocalTransferProgressToast
t={t}
status={{
direction: "upload",
totalFiles: 2,
completedFiles: 1,
bytesDone: 50,
totalBytes: 100,
}}
onCancel={cancel}
/>,
);
fireEvent.click(screen.getByRole("button"));
expect(cancel).toHaveBeenCalledOnce();
});