152 lines
4.1 KiB
TypeScript
152 lines
4.1 KiB
TypeScript
import { FC, ReactElement, useEffect, useState } from "react";
|
|
import clsx from "clsx";
|
|
|
|
import { useAppSelector } from "@/app/store";
|
|
import useExpiredResMap from "@/hooks/useExpiredResMap";
|
|
import { formatBytes, fromNowTime, getFileIcon } from "@/utils";
|
|
import IconDownload from "@/assets/icons/download.svg";
|
|
import {
|
|
AudioPreview,
|
|
CodePreview,
|
|
DocPreview,
|
|
ImagePreview,
|
|
PdfPreview,
|
|
VideoPreview
|
|
} from "./preview";
|
|
import { shallowEqual } from "react-redux";
|
|
|
|
interface Data {
|
|
file_type: string;
|
|
name: string;
|
|
content: string;
|
|
}
|
|
|
|
const renderPreview = (data: Data) => {
|
|
const { file_type, name, content } = data;
|
|
let preview: null | ReactElement = null;
|
|
|
|
const checks = {
|
|
image: /^image/gi,
|
|
audio: /^audio/gi,
|
|
video: /^video/gi,
|
|
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
|
|
doc: /^text/gi,
|
|
pdf: /\/pdf$/gi
|
|
};
|
|
const _arr = name.split(".");
|
|
const _type = file_type || _arr[_arr.length - 1];
|
|
switch (true) {
|
|
case checks.image.test(_type):
|
|
{
|
|
preview = <ImagePreview url={content} />;
|
|
}
|
|
break;
|
|
case checks.pdf.test(_type):
|
|
preview = <PdfPreview url={content} />;
|
|
break;
|
|
case checks.code.test(_type):
|
|
preview = <CodePreview url={content} />;
|
|
break;
|
|
case checks.doc.test(_type):
|
|
preview = <DocPreview url={content} />;
|
|
break;
|
|
case checks.audio.test(_type):
|
|
preview = <AudioPreview url={content} />;
|
|
break;
|
|
case checks.video.test(_type):
|
|
preview = <VideoPreview url={content} />;
|
|
break;
|
|
}
|
|
return preview;
|
|
};
|
|
|
|
interface Props {
|
|
preview?: boolean;
|
|
flex: boolean;
|
|
file_type: string;
|
|
name: string;
|
|
size: number;
|
|
created_at: number;
|
|
from_uid: number;
|
|
content: string;
|
|
}
|
|
|
|
const FileBox: FC<Props> = ({
|
|
preview,
|
|
flex,
|
|
file_type,
|
|
name,
|
|
size,
|
|
created_at,
|
|
from_uid,
|
|
content
|
|
}) => {
|
|
const [fetchError, setFetchError] = useState(false);
|
|
const { isExpired, setExpired } = useExpiredResMap();
|
|
const fromUser = useAppSelector((store) => store.users.byId[from_uid], shallowEqual);
|
|
const icon = getFileIcon(file_type, name, "icon w-9 h-12");
|
|
const expired = isExpired(content);
|
|
useEffect(() => {
|
|
const tryFetch = async (url: string) => {
|
|
try {
|
|
let statusNum = (await fetch(url)).status;
|
|
if (statusNum >= 400) {
|
|
setFetchError(true);
|
|
}
|
|
} catch (e) {
|
|
setFetchError(true);
|
|
}
|
|
};
|
|
if (!expired && content) {
|
|
tryFetch(content);
|
|
}
|
|
}, [content, expired]);
|
|
useEffect(() => {
|
|
if (fetchError) {
|
|
setExpired(content);
|
|
}
|
|
}, [fetchError, content]);
|
|
|
|
if (!content || expired || fetchError) return null;
|
|
|
|
const previewContent = renderPreview({ file_type, content, name });
|
|
const withPreview = preview && previewContent && !fetchError && !expired;
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
`rounded-md border border-solid border-gray-300 dark:border-gray-500 bg-gray-100 dark:bg-gray-900`,
|
|
flex ? "w-full" : "w-72 md:w-[370px]",
|
|
withPreview ? "relative overflow-hidden h-[281px]" : "h-[66px] ",
|
|
file_type.startsWith("audio") && "h-[125px]"
|
|
)}
|
|
>
|
|
<div className="w-full p-2 flex items-center justify-between gap-2">
|
|
{icon}
|
|
<div className="flex flex-col gap-1 w-full overflow-hidden">
|
|
<span className="font-semibold text-sm text-gray-800 dark:text-gray-200 truncate">
|
|
{name}
|
|
</span>
|
|
<em className="text-xs text-gray-500 flex gap-4 not-italic">
|
|
<span className="size">{formatBytes(size)}</span>
|
|
<span className="hidden md:block time">{fromNowTime(created_at)}</span>
|
|
<span>
|
|
by <strong className="font-bold">{fromUser?.name || "Deleted User"}</strong>
|
|
</span>
|
|
</em>
|
|
</div>
|
|
<a
|
|
className="hidden md:block whitespace-nowrap"
|
|
download={name}
|
|
href={`${content}&download=true`}
|
|
>
|
|
<IconDownload className="fill-gray-500 dark:fill-gray-300" />
|
|
</a>
|
|
</div>
|
|
{withPreview && <div className="h-[calc(100%_-_64px)] overflow-hidden">{previewContent}</div>}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default FileBox;
|