From 6d1f17e99dd4bb8f927a5d9ec9f75278321f8129 Mon Sep 17 00:00:00 2001 From: jarvis2f <137974272+jarvis2f@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:54:04 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20style:=20Update=20file=20list=20?= =?UTF-8?q?style=20and=20add=20file=20name=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/file-card.tsx | 41 +++-- web/src/components/file-control.tsx | 220 +++++++++++++++++---------- web/src/components/file-extra.tsx | 88 +++++++---- web/src/components/file-list.tsx | 190 ++--------------------- web/src/components/file-progress.tsx | 48 +++--- web/src/components/file-row.tsx | 189 +++++++++++++++++++++++ web/src/components/file-status.tsx | 10 +- web/src/components/proxys.tsx | 2 +- web/src/components/ui/select.tsx | 2 +- web/src/lib/types.ts | 2 +- 10 files changed, 462 insertions(+), 330 deletions(-) create mode 100644 web/src/components/file-row.tsx diff --git a/web/src/components/file-card.tsx b/web/src/components/file-card.tsx index 4ca80b5..6b84f07 100644 --- a/web/src/components/file-card.tsx +++ b/web/src/components/file-card.tsx @@ -34,31 +34,39 @@ import { Separator } from "./ui/separator"; import { Badge } from "./ui/badge"; import { formatDistanceToNow } from "date-fns"; import FileStatus from "@/components/file-status"; +import { cn } from "@/lib/utils"; +import { useFileSpeed } from "@/hooks/use-file-speed"; interface FileCardProps { file: TelegramFile; } export function FileCard({ file }: FileCardProps) { + const { downloadProgress, downloadSpeed } = useFileSpeed(file.id); return ( - +
-

{file.name}

+

{file.fileName}

{prettyBytes(file.size)} • {file.type}
-
- +
+
- +
@@ -96,14 +104,14 @@ function FileAvatar({ {loadPreview ? ( ) : ( {file.name - + - {file.name} + {file.fileName}
{file.caption && ( - - {file.caption} - + "), + }} + > )} - +
@@ -204,7 +217,7 @@ function FileDrawer({
- {(file.downloadStatus !== 'idle' && file.startDate !== 0) && ( + {file.downloadStatus !== "idle" && file.startDate !== 0 && (
diff --git a/web/src/components/file-control.tsx b/web/src/components/file-control.tsx index 5431a36..6df4f72 100644 --- a/web/src/components/file-control.tsx +++ b/web/src/components/file-control.tsx @@ -1,4 +1,4 @@ -import { type TelegramFile } from "@/lib/types"; +import { type DownloadStatus, type TelegramFile } from "@/lib/types"; import { useFileControl } from "@/hooks/use-file-control"; import { Button } from "@/components/ui/button"; import { ArrowDown, Loader2, Pause, SquareX, StepForward } from "lucide-react"; @@ -8,8 +8,50 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; +import { type ReactNode } from "react"; +import prettyBytes from "pretty-bytes"; +import { AnimatePresence, motion } from "framer-motion"; + +interface ActionButtonProps { + tooltipText: string; + icon: ReactNode; + onClick: () => void; + loading: boolean; +} + +const ActionButton = ({ + tooltipText, + icon, + onClick, + loading, +}: ActionButtonProps) => ( + + + + + +

{tooltipText}

+
+
+); + +export default function FileControl({ + file, + downloadSpeed, + hovered, + isMobile, +}: { + file: TelegramFile; + downloadSpeed: number; + hovered?: boolean; + isMobile?: boolean; +}) { + const showDownloadInfo = + !hovered && + (file.downloadStatus === "downloading" || file.downloadStatus === "paused"); -export default function FileControl({ file }: { file: TelegramFile }) { const { start, starting, togglePause, togglingPause, cancel, cancelling } = useFileControl(file); @@ -17,87 +59,103 @@ export default function FileControl({ file }: { file: TelegramFile }) { return null; } + const statusMapping: Record = { + idle: [ + { + onClick: () => start(file.id), + tooltipText: "Start Download", + icon: , + loading: starting, + }, + ], + error: [ + { + onClick: () => start(file.id), + tooltipText: "Retry", + icon: , + loading: starting, + }, + ], + downloading: [ + { + onClick: () => togglePause(file.id), + tooltipText: "Pause", + icon: , + loading: togglingPause, + }, + { + onClick: () => cancel(file.id), + tooltipText: "Cancel", + icon: , + loading: cancelling, + }, + ], + paused: [ + { + onClick: () => togglePause(file.id), + tooltipText: "Resume", + icon: , + loading: togglingPause, + }, + { + onClick: () => cancel(file.id), + tooltipText: "Cancel", + icon: , + loading: cancelling, + }, + ], + completed: [], + }; + + const actionButtons = ( +
+
e.preventDefault()} + > + {statusMapping[file.downloadStatus].map((btnProps, index) => ( + + ))} +
+
+ ); + + if (isMobile) { + return {actionButtons}; + } + return ( -
- {(file.downloadStatus === "idle" || - file.downloadStatus === "error") && ( - - - - - -

- {file.downloadStatus === "idle" - ? "Start Download" - : file.downloadStatus === "error" - ? "Retry" - : "Downloading"} -

-
-
- )} - {(file.downloadStatus === "downloading" || - file.downloadStatus === "paused") && ( - <> - - - - - -

- {file.downloadStatus === "downloading" - ? "Pause" - : file.downloadStatus === "paused" - ? "Resume" - : "Paused"} -

-
-
- - - - - -

- {file.downloadStatus === "downloading" || - file.downloadStatus === "paused" || - file.downloadStatus === "error" - ? "Cancel" - : "Cancelled"} -

-
-
- - )} +
+ + {showDownloadInfo ? ( + + + {file.downloadStatus === "downloading" + ? `${prettyBytes(downloadSpeed, { bits: true })}/s` + : prettyBytes(file.downloadedSize)} + + + ) : ( + + {actionButtons} + + )} +
); diff --git a/web/src/components/file-extra.tsx b/web/src/components/file-extra.tsx index ecc18c8..2d8e064 100644 --- a/web/src/components/file-extra.tsx +++ b/web/src/components/file-extra.tsx @@ -1,4 +1,11 @@ -import { Captions, Clock, ClockArrowDown, Copy, FileCheck } from "lucide-react"; +import { + Captions, + Clock, + ClockArrowDown, + Copy, + FileCheck, + Mountain, +} from "lucide-react"; import SpoiledWrapper from "@/components/spoiled-wrapper"; import { Tooltip, @@ -11,6 +18,7 @@ import { type TelegramFile } from "@/lib/types"; import { type RowHeight } from "@/components/table-row-height-switch"; import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; import { formatDistanceToNow } from "date-fns"; +import { cn } from "@/lib/utils"; interface FileExtraProps { file: TelegramFile; @@ -23,21 +31,39 @@ export default function FileExtra({ file, rowHeight }: FileExtraProps) { return (
+ {file.fileName && ( + +

+ + + {file.fileName} + +

+
+ )} {rowHeight !== "s" && file.caption && (
-

+

{file.caption}

- -
- {file.caption} -
+ +

"), + }} + >

@@ -63,31 +89,14 @@ export default function FileExtra({ file, rowHeight }: FileExtraProps) { )} -
- - -

- - - {formatDistanceToNow(new Date(file.date * 1000), { - addSuffix: true, - })} - -

-
- -
- {`Message received at ${new Date(file.date * 1000).toLocaleString()}`} -
-
-
- {file.completionDate && ( + {rowHeight !== "s" && ( +

- + - {formatDistanceToNow(new Date(file.completionDate), { + {formatDistanceToNow(new Date(file.date * 1000), { addSuffix: true, })} @@ -95,12 +104,31 @@ export default function FileExtra({ file, rowHeight }: FileExtraProps) {

- {`File downloaded at ${new Date(file.completionDate).toLocaleString()}`} + {`Message received at ${new Date(file.date * 1000).toLocaleString()}`}
- )} -
+ {file.completionDate && ( + + +

+ + + {formatDistanceToNow(new Date(file.completionDate), { + addSuffix: true, + })} + +

+
+ +
+ {`File downloaded at ${new Date(file.completionDate).toLocaleString()}`} +
+
+
+ )} +
+ )}
); diff --git a/web/src/components/file-list.tsx b/web/src/components/file-list.tsx index 3257334..89a58d1 100644 --- a/web/src/components/file-list.tsx +++ b/web/src/components/file-list.tsx @@ -1,14 +1,6 @@ "use client"; -import { type TelegramFile } from "@/lib/types"; import { FileCard } from "./file-card"; -import React, { - memo, - type ReactNode, - useCallback, - useEffect, - useRef, - useState, -} from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Table, TableBody, @@ -19,41 +11,17 @@ import { } from "@/components/ui/table"; import { Checkbox } from "@/components/ui/checkbox"; import { Button } from "@/components/ui/button"; -import { - Download, - FileAudioIcon, - FileIcon, - ImageIcon, - LoaderCircle, - LoaderPinwheel, - VideoIcon, -} from "lucide-react"; +import { Download, LoaderCircle, LoaderPinwheel } from "lucide-react"; import { useFiles } from "@/hooks/use-files"; import { FileFilters } from "@/components/file-filters"; -import { - getRowHeightTailwindClass, - useRowHeightLocalStorage, -} from "@/components/table-row-height-switch"; +import { useRowHeightLocalStorage } from "@/components/table-row-height-switch"; import { type Column } from "@/components/table-column-filter"; import { cn } from "@/lib/utils"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import PhotoPreview from "@/components/photo-preview"; -import SpoiledWrapper from "@/components/spoiled-wrapper"; -import Image from "next/image"; -import FileControl from "@/components/file-control"; -import prettyBytes from "pretty-bytes"; -import FileProgress from "@/components/file-progress"; import FileNotFount from "@/components/file-not-found"; -import FileExtra from "@/components/file-extra"; import useIsMobile from "@/hooks/use-is-mobile"; -import FileStatus from "@/components/file-status"; import useSWRMutation from "swr/mutation"; import { POST } from "@/lib/api"; +import FileRow from "@/components/file-row"; interface FileListProps { accountId: string; @@ -84,26 +52,6 @@ const COLUMNS: Column[] = [ }, ]; -const PhotoColumnImage = memo(function PhotoColumnImage({ - thumbnail, - name, - wh, -}: { - thumbnail: string; - name: string; - wh: string; -}) { - return ( - {name - ); -}); - export function FileList({ accountId, chatId }: FileListProps) { const isMobile = useIsMobile(); const [selectedFiles, setSelectedFiles] = useState>(new Set()); @@ -155,12 +103,12 @@ export function FileList({ accountId, chatId }: FileListProps) { return () => observer.disconnect(); }, [handleLoadMore]); - const dynamicClass = useCallback(() => { + const dynamicClass = useMemo(() => { switch (rowHeight) { case "s": return { content: "h-6 w-6", - contentCell: "w-6", + contentCell: "w-4", }; case "m": return { @@ -234,84 +182,6 @@ export function FileList({ accountId, chatId }: FileListProps) { setSelectedFiles(newSelected); }; - const getFileIcon = (type: TelegramFile["type"]) => { - let icon; - switch (type) { - case "photo": - icon = ; - break; - case "video": - icon = ; - break; - case "audio": - icon = ; - break; - default: - icon = ; - } - return ( -
- {icon} -
- ); - }; - - const columnRenders: Record< - string, - (file: TelegramFile, index: number) => ReactNode - > = { - content: (file: TelegramFile) => ( -
- {file.type === "photo" || file.type === "video" ? ( - file.thumbnail ? ( - - - - - - - - - - - - - ) : ( - getFileIcon(file.type) - ) - ) : ( - getFileIcon(file.type) - )} -
- ), - type: (file: TelegramFile) => ( -
- {file.type} - {file.id} -
- ), - size: (file: TelegramFile) => {prettyBytes(file.size)}, - status: (file: TelegramFile) => , - extra: (file: TelegramFile) => ( - - ), - actions: (file: TelegramFile) => , - }; - return ( <> {col.label} @@ -392,47 +262,13 @@ export function FileList({ accountId, chatId }: FileListProps) { {files.map((file, index) => ( - handleSelectFile(file.id)} + properties={{ rowHeight, dynamicClass, columns }} key={`${file.messageId}-${file.uniqueId}-${index}`} - > - - - handleSelectFile(file.id)} - disabled={file.downloadStatus !== "idle"} - /> - - {columns.map((col) => - col.isVisible ? ( - - {columnRenders[col.id]!(file, index)} - - ) : null, - )} - - - - - - - + /> ))} {!isLoading && files.length === 0 && ( diff --git a/web/src/components/file-progress.tsx b/web/src/components/file-progress.tsx index 4ce57b2..3a62ec1 100644 --- a/web/src/components/file-progress.tsx +++ b/web/src/components/file-progress.tsx @@ -1,18 +1,14 @@ import { Progress } from "@/components/ui/progress"; -import prettyBytes from "pretty-bytes"; import { type TelegramFile } from "@/lib/types"; -import { useFileSpeed } from "@/hooks/use-file-speed"; import { useMemo } from "react"; -import { ClockArrowDown, Zap } from "lucide-react"; export default function FileProgress({ file, - showSize, + downloadProgress, }: { file: TelegramFile; - showSize: boolean; + downloadProgress: number; }) { - const { downloadProgress, downloadSpeed } = useFileSpeed(file.id); const progress = useMemo(() => { const fileDownloadProgress = file.size > 0 @@ -37,22 +33,30 @@ export default function FileProgress({ return (
- {showSize && file.downloadedSize > 0 && ( -
- - - {prettyBytes(file.downloadedSize)} - -
- )} -
- - - {file.downloadStatus === "downloading" - ? `${prettyBytes(downloadSpeed, { bits: true })}/s` - : "0/s"} - -
+ {/*
*/} + {/*
*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* {`${prettyBytes(downloadSpeed, { bits: true })}/s`}*/} + {/* */} + {/* */} + {/* {prettyBytes(file.downloadedSize)}*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/*
*/} + {/*
*/} +
); diff --git a/web/src/components/file-row.tsx b/web/src/components/file-row.tsx new file mode 100644 index 0000000..eec9a27 --- /dev/null +++ b/web/src/components/file-row.tsx @@ -0,0 +1,189 @@ +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 { env } from "@/env"; +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 = ; + break; + case "video": + icon = ; + break; + case "audio": + icon = ; + break; + default: + icon = ; + } + return ( +
+ {icon} +
+ ); + }; + + const columnRenders: Record = { + content: ( +
+ {file.type === "photo" || file.type === "video" ? ( + file.thumbnail ? ( + + + + + + + + + + + + + ) : ( + getFileIcon(file.type) + ) + ) : ( + getFileIcon(file.type) + )} +
+ ), + type: ( +
+ {file.type} + {env.NEXT_PUBLIC_MOCK_DATA === true && ( + {file.id} + )} +
+ ), + size: {prettyBytes(file.size)}, + status: , + extra: , + actions: ( + + ), + }; + + return ( + + setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + + + + {columns.map((col) => + col.isVisible ? ( + + {columnRenders[col.id]} + + ) : null, + )} + + + + + + + + ); +} + +const PhotoColumnImage = memo(function PhotoColumnImage({ + thumbnail, + name, + wh, +}: { + thumbnail: string; + name: string; + wh: string; +}) { + return ( + {name + ); +}); diff --git a/web/src/components/file-status.tsx b/web/src/components/file-status.tsx index 18fc480..fc245f9 100644 --- a/web/src/components/file-status.tsx +++ b/web/src/components/file-status.tsx @@ -27,7 +27,11 @@ export default function FileStatus({ status }: { status: DownloadStatus }) { }, }; - return - {STATUS[status].text} - ; + return ( + + {STATUS[status].text} + + ); } diff --git a/web/src/components/proxys.tsx b/web/src/components/proxys.tsx index a02e953..2b5bdd7 100644 --- a/web/src/components/proxys.tsx +++ b/web/src/components/proxys.tsx @@ -246,7 +246,7 @@ export default function Proxys({
- + HTTP diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx index 7504cca..6afb4bd 100644 --- a/web/src/components/ui/select.tsx +++ b/web/src/components/ui/select.tsx @@ -118,7 +118,7 @@ const SelectItem = React.forwardRef<