telegram-files/web/src/components/file-row.tsx
2025-01-23 18:21:17 +08:00

188 lines
5.2 KiB
TypeScript

import { TableCell, TableRow } from "@/components/ui/table";
import { cn } from "@/lib/utils";
import {
getRowHeightTailwindClass,
type RowHeight,
} from "@/components/table-row-height-switch";
import { Checkbox } from "@/components/ui/checkbox";
import FileProgress from "@/components/file-progress";
import React, { memo, type ReactNode, useState } from "react";
import { type TelegramFile } from "@/lib/types";
import SpoiledWrapper from "@/components/spoiled-wrapper";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import PhotoPreview from "@/components/photo-preview";
import prettyBytes from "pretty-bytes";
import FileStatus from "@/components/file-status";
import FileExtra from "@/components/file-extra";
import FileControl from "@/components/file-control";
import { FileAudioIcon, FileIcon, ImageIcon, VideoIcon } from "lucide-react";
import Image from "next/image";
import { type Column } from "./table-column-filter";
import { useFileSpeed } from "@/hooks/use-file-speed";
interface FileRowProps {
file: TelegramFile;
checked: boolean;
properties: {
rowHeight: RowHeight;
dynamicClass: {
content: string;
contentCell: string;
};
columns: Column[];
};
onCheckedChange: (checked: boolean) => void;
}
export default function FileRow({
file,
checked,
properties,
onCheckedChange,
}: FileRowProps) {
const { rowHeight, dynamicClass, columns } = properties;
const { downloadProgress, downloadSpeed } = useFileSpeed(file.id);
const [hovered, setHovered] = useState(false);
const getFileIcon = (type: TelegramFile["type"]) => {
let icon;
switch (type) {
case "photo":
icon = <ImageIcon className="h-4 w-4" />;
break;
case "video":
icon = <VideoIcon className="h-4 w-4" />;
break;
case "audio":
icon = <FileAudioIcon className="h-4 w-4" />;
break;
default:
icon = <FileIcon className="h-4 w-4" />;
}
return (
<div
className={cn(
dynamicClass.content,
"flex items-center justify-center rounded border",
)}
>
{icon}
</div>
);
};
const columnRenders: Record<string, ReactNode> = {
content: (
<div className="flex items-center justify-center gap-2">
{file.type === "photo" || file.type === "video" ? (
file.thumbnail ? (
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<PhotoColumnImage
thumbnail={file.thumbnail}
name={file.fileName}
wh={dynamicClass.content}
/>
</TooltipTrigger>
<TooltipContent asChild>
<PhotoPreview
thumbnail={file.thumbnail}
name={file.fileName}
chatId={file.chatId}
messageId={file.messageId}
/>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</SpoiledWrapper>
) : (
getFileIcon(file.type)
)
) : (
getFileIcon(file.type)
)}
</div>
),
type: (
<div className="flex flex-col items-center">
<span className="capitalize">{file.type}</span>
{process.env.NODE_ENV === "development" && (
<span className="text-xs">{file.id}</span>
)}
</div>
),
size: <span>{prettyBytes(file.size)}</span>,
status: <FileStatus file={file} />,
extra: <FileExtra file={file} rowHeight={rowHeight} />,
actions: (
<FileControl
file={file}
downloadSpeed={downloadSpeed}
hovered={hovered}
/>
),
};
return (
<React.Fragment>
<TableRow
className={cn(getRowHeightTailwindClass(rowHeight), "border-b-0")}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<TableCell className="text-center">
<Checkbox
checked={checked}
onCheckedChange={onCheckedChange}
disabled={file.downloadStatus !== "idle"}
/>
</TableCell>
{columns.map((col) =>
col.isVisible ? (
<TableCell
key={col.id}
className={cn(
col.className ?? "",
col.id === "content" ? dynamicClass.contentCell : "",
)}
>
{columnRenders[col.id]}
</TableCell>
) : null,
)}
</TableRow>
<TableRow>
<TableCell colSpan={columns.length + 1} className="h-px p-0">
<FileProgress file={file} downloadProgress={downloadProgress} />
</TableCell>
</TableRow>
</React.Fragment>
);
}
const PhotoColumnImage = memo(function PhotoColumnImage({
thumbnail,
name,
wh,
}: {
thumbnail: string;
name: string;
wh: string;
}) {
return (
<Image
src={`data:image/jpeg;base64,${thumbnail}`}
alt={name ?? "File thumbnail"}
width={32}
height={32}
className={cn(wh, "rounded object-cover")}
/>
);
});