Files
ColdBreeze-chat-web/src/common/component/FileMessage/index.tsx
T
2023-02-13 07:36:11 +08:00

192 lines
5.6 KiB
TypeScript

import { FC, useEffect, useState } from "react";
import ImageMessage from "./ImageMessage";
import useRemoveLocalMessage from "../../hook/useRemoveLocalMessage";
import useUploadFile from "../../hook/useUploadFile";
import useSendMessage from "../../hook/useSendMessage";
import Progress from "./Progress";
import { getFileIcon, formatBytes, isImage, getImageSize, fromNowTime } from "../../utils";
import { useAppSelector } from "../../../app/store";
import IconDownload from "../../../assets/icons/download.svg";
import IconClose from "../../../assets/icons/close.circle.svg";
import VideoMessage from "./VideoMessage";
import AudioMessage from "./AudioMessage";
import clsx from "clsx";
const isLocalFile = (content: string) => {
return content.startsWith("blob:");
};
interface Props {
context: "user" | "channel";
to: number;
created_at: number;
from_uid: number;
content: string;
download: string;
thumbnail: string;
properties: {
local_id: number;
name: string;
size: number;
content_type: string;
};
}
const FileMessage: FC<Props> = ({
context,
to,
created_at,
from_uid,
content = "",
download = "",
thumbnail = "",
properties = { local_id: 0, name: "", size: 0, content_type: "" }
}) => {
const [imageSize, setImageSize] = useState(null);
const [uploadingFile, setUploadingFile] = useState(false);
const removeLocalMessage = useRemoveLocalMessage({ context, id: to });
const {
sendMessage,
isSuccess: sendMessageSuccess,
isSending
} = useSendMessage({
context,
from: from_uid,
to
});
const { stopUploading, data, uploadFile, progress, isSuccess: uploadSuccess } = useUploadFile();
const fromUser = useAppSelector((store) => store.users.byId[from_uid]);
const { size = 0, name, content_type } = properties ?? {};
useEffect(() => {
const handleUpSend = async ({
url,
name,
type
}: {
url: string;
name: string;
type: string;
}) => {
try {
setUploadingFile(true);
if (type.startsWith("image")) {
const size = await getImageSize(url);
setImageSize(size);
}
let file = await fetch(url)
.then((r) => r.blob())
.then((blobFile) => new File([blobFile], name, { type }));
await uploadFile(file);
setUploadingFile(false);
} catch (error) {
setUploadingFile(false);
console.error("fetch local file error", error);
}
};
// local file
if (isLocalFile(content)) {
handleUpSend({ url: content, name, type: content_type });
}
}, [content, name, content_type]);
useEffect(() => {
const props = properties ?? {};
const propsV2 = imageSize ? { ...props, ...imageSize } : props;
// 本地文件 并且上传成功
if (uploadSuccess && isLocalFile(content)) {
// 把已经上传的东西当做消息发出去
const { path } = data;
sendMessage({
ignoreLocal: true,
type: "file",
content: { path },
properties: propsV2
});
}
}, [uploadSuccess, data, properties, content]);
useEffect(() => {
if (sendMessageSuccess) {
// 回收本地资源
URL.revokeObjectURL(content);
}
}, [sendMessageSuccess, content]);
const handleCancel = () => {
stopUploading();
URL.revokeObjectURL(content);
removeLocalMessage(properties.local_id);
};
if (!properties) return null;
const icon = getFileIcon(content_type, name, "w-9 h-auto");
if (!content || !name) return null;
const sending = uploadingFile || isSending;
// image
if (isImage(content_type, size))
return (
<ImageMessage
key={properties?.local_id}
uploading={sending}
progress={progress}
properties={{ ...imageSize, ...properties }}
content={content}
download={download}
thumbnail={thumbnail}
/>
);
// video
if (content_type.startsWith("video") && !sending)
return (
<VideoMessage
size={size}
url={content}
name={name}
download={download}
/>
);
// audio
if (content_type.startsWith("audio") && !sending)
return (
<AudioMessage
size={size}
url={content}
name={name}
download={download}
/>
);
return (
<div className={clsx(`file_message bg-slate-50 dark:bg-slate-900 border border-solid border-gray-300 dark:border-gray-500 box-border w-[370px] rounded-md`, sending && "opacity-90")}>
<div className="px-2 py-3 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-100 truncate">{name}</span>
<span className="whitespace-nowrap text-xs text-gray-500 dark:text-gray-300 flex gap-4">
{sending ? (
<Progress value={progress} width={"80%"} />
) : (
<>
<strong>{formatBytes(size)}</strong>
<strong>{fromNowTime(created_at)}</strong>
{fromUser && (
<strong>
by <strong className="font-bold">{fromUser.name}</strong>
</strong>
)}
</>
)}
</span>
</div>
{sending ? (
<IconClose className="cursor-pointer" onClick={handleCancel} />
) : (
<a className="whitespace-nowrap" download={name} href={`${content}&download=true`}>
<IconDownload className="fill-gray-500 dark:fill-gray-400" />
</a>
)}
</div>
</div>
);
};
export default FileMessage;