"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import { type z } from "zod"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { getFileType } from "~/lib/previewHelper"; import { bytesToReadable, durationToReadable, formatDate } from "~/lib/utils"; import { type Schema_File } from "~/types/schema"; import { CreateDownloadToken } from "actions"; import config from "config"; import { Button, LoadingButton } from "../ui/button"; import { ResponsiveDialog, ResponsiveDialogBody, ResponsiveDialogClose, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, } from "../ui/dialog.responsive"; import { ResponsiveDropdownMenu, ResponsiveDropdownMenuContent, ResponsiveDropdownMenuItem, ResponsiveDropdownMenuSeparator, ResponsiveDropdownMenuTrigger, } from "../ui/dropdown-menu.responsive"; import Icon from "../ui/icon"; import { Separator } from "../ui/separator"; type Props = { file: z.infer; }; export default function PreviewInformation({ file }: Props) { const pathname = usePathname(); const fileType = useMemo>(() => { return getFileType(file.fileExtension ?? "", file.mimeType) ?? "unknown"; }, [file]); const showVirusTotal = useMemo( () => fileType === "executable" || fileType === "manga" || fileType === "unknown", [fileType], ); const showRawUrl = useMemo( () => fileType === "video" || fileType === "audio" || fileType === "image", [fileType], ); const showViewDoc = useMemo(() => fileType === "document", [fileType]); const fileInfo = useMemo<{ label: string; value: string }[]>(() => { const value = [ { label: "File Name", value: file.name, }, { label: "Mime Type", value: file.mimeType, }, { label: "Size", value: `${bytesToReadable(file.size ?? 0)} (${file.size ?? 0} bytes)`, }, { label: "Last Modified", value: formatDate(file.modifiedTime), }, ]; if (file.imageMediaMetadata) { value.push({ label: "Dimension", value: `${file.imageMediaMetadata.width}px x ${file.imageMediaMetadata.height}px (${ Math.round((file.imageMediaMetadata.width / file.imageMediaMetadata.height) * 100) / 100 })`, }); } if (file.videoMediaMetadata) { value.push({ label: "Dimension", value: `${file.videoMediaMetadata.width}px x ${file.videoMediaMetadata.height}px (${ Math.round((file.videoMediaMetadata.width / file.videoMediaMetadata.height) * 100) / 100 })`, }); value.push({ label: "Duration", value: durationToReadable(file.videoMediaMetadata.durationMillis), }); } return value; }, [file]); const [viewerBtnLoading, setViewerBtnLoading] = useState(false); const [copyBtnLoading, setCopyBtnLoading] = useState(false); const [copyDownloadBtnLoading, setCopyDownloadBtnLoading] = useState(false); const [downloadBtnLoading, setDownloadBtnLoading] = useState(false); const [isRawExplanationOpen, setIsRawExplanationOpen] = useState(false); const onCopyRawLink = useCallback(async () => { try { const rawUrl = new URL(`/api/raw?url=${encodeURIComponent(pathname)}`, config.basePath); } catch (error) {} }, []); const onCopyRaw = async () => { setCopyBtnLoading(true); try { const rawURL = new URL(`/api/raw/${pathname}`.replace(/\/+/g, "/"), config.basePath); rawURL.searchParams.append("token", file.encryptedId); toast.promise(navigator.clipboard.writeText(rawURL.toString()), { loading: "Copying link...", success: "Raw link copied!", error: "Failed to copy link", }); } catch (error) { const e = error as Error; console.error(e.message); } finally { setCopyBtnLoading(false); } }; const onCopy = async (e: React.MouseEvent) => { e.stopPropagation(); setCopyDownloadBtnLoading(true); toast.loading("Creating download token...", { id: `download-${file.encryptedId}`, }); try { const token = await CreateDownloadToken(); if (!token) throw new Error("Failed to create download token"); await navigator.clipboard.writeText( new URL(`/api/download/${file.encryptedId}?token=${token}`, config.basePath).toString(), ); toast.success("Download link copied!", { id: `download-${file.encryptedId}`, }); } catch (error) { const e = error as Error; console.error(e.message); toast.error(e.message, { id: `download-${file.encryptedId}`, }); } finally { setCopyDownloadBtnLoading(false); } }; const onDownload = async (e: React.MouseEvent) => { e.stopPropagation(); setDownloadBtnLoading(true); toast.loading("Creating download token...", { id: `download-${file.encryptedId}`, }); try { const token = await CreateDownloadToken(); if (!token) throw new Error("Failed to create download token"); toast.success("Opening download link...", { id: `download-${file.encryptedId}`, }); const timeout = setTimeout(() => { clearTimeout(timeout); window.open(`/api/download/${file.encryptedId}?token=${token}`); }, 250); } catch (error) { const e = error as Error; console.error(e.message); toast.error(e.message, { id: `download-${file.encryptedId}`, }); } finally { setDownloadBtnLoading(false); } }; const onOpenViewer = async (e: React.MouseEvent) => { e.stopPropagation(); toast.loading("Creating view token...", { id: `view-${file.encryptedId}`, }); setViewerBtnLoading(true); try { const token = await CreateDownloadToken(); if (!token) throw new Error("Failed to create view token"); const streamURL = new URL(`/api/stream/${file.encryptedId}`, config.basePath); streamURL.searchParams.set("token", token); toast.success("Opening viewer link...", { id: `view-${file.encryptedId}`, }); const timeout = setTimeout(() => { clearTimeout(timeout); const viewerUrl = new URL(`/gview`, "https://docs.google.com"); viewerUrl.searchParams.set("url", streamURL.toString()); viewerUrl.searchParams.set("embedded", "true"); window.open(viewerUrl.toString(), "_blank"); }, 250); } catch (error) { const e = error as Error; console.error(e.message); toast.error(e.message, { id: `view-${file.encryptedId}`, }); } finally { setViewerBtnLoading(false); } }; return ( <> Difference between download link and raw link? Learn the reason why it's 2 different links.

Some services need the file extension to be present in the URL to properly embed the file.
The download link only have encrypted file id in the URL, meanwhile raw link will have the whole path included in the URL.

So it's recommended to use the raw link for embedding media file like video, audio, and image.

Note: This is only applicable for video, audio, and image files.

This information is based on the{" "} onedrive-vercel-index documentation.

Customise Direct Link - onedrive-vercel-index
Raw Link Direct Download Link { setIsRawExplanationOpen(true); }} > Learn the difference {showViewDoc && ( Open in Viewer )} {showVirusTotal && ( )}
File information {fileInfo.map((info) => (
{info.label} {info.value}
))}
); // return ( //
// //
//
// {isDesktop ? ( // // // // Difference between download link and raw link? // // // //
//

Why?

//

// Some services need the file extension to be present in the URL to properly embed the file. //
// - The download link only have encrypted file id. //
// - The raw link will have the whole path includes the file name and extension. //
//
// So if you want to embed the file, it's recommended to use the raw link instead of the // download link. //

//

Note: This is only applicable for video, audio, and image files.

//

Ref

//

// This information is based on the onedrive-vercel-index project documentation. //
// // Customise Direct Link - onedrive-vercel-index // //

//
//
//
// ) : ( // // // // Difference between download link and raw link? // // // // //
//

Why?

//

// Some services need the file extension to be present in the URL to properly embed the file. //
// - The download link only have encrypted file id. //
// - The raw link will have the whole path includes the file name and extension. //
//
// So if you want to embed the file, it's recommended to use the raw link instead of the // download link. //

//

Ref

//

// This information is based on the onedrive-vercel-index project documentation. //
// // Customise Direct Link - onedrive-vercel-index // //

//
// // // // // //
//
// )} //
//
// {showRawUrl ? ( // // // Raw Link // // ) : null} // {showViewDoc ? ( // // // Open in Viewer // // ) : null} // // // Download Link // // // // Download // //
//
//
// // // File Information // // //
// {fileInfo.map((info) => ( //
// {info.label}: // {info.value} // //
// ))} //
//
//
//
// ); }