feat: add media storage
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { useWAClient } from "@/app/context";
|
||||
import { storage } from "@/lib/storage";
|
||||
import type { MessageDTO } from "@shared/dto";
|
||||
import { DownloadIcon, Loader2Icon, PaperclipIcon } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
type Props = {
|
||||
data: Omit<MessageDTO, "id" | "from"> & { id: string };
|
||||
};
|
||||
|
||||
export default function MessageMedia({ data }: Props) {
|
||||
const { type } = data;
|
||||
const { client } = useWAClient();
|
||||
const loadingRef = useRef(false);
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const [isDownloaded, setDownloaded] = useState(false);
|
||||
const [body, setBody] = useState(
|
||||
type === "image" ? "data:image/webp;base64," + data.body : data.body,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingRef.current) return;
|
||||
|
||||
(async function () {
|
||||
try {
|
||||
loadingRef.current = true;
|
||||
|
||||
if (type === "image" || type === "document") {
|
||||
const res = await storage.get<string>(data.id);
|
||||
if (res) {
|
||||
setBody(res);
|
||||
setDownloaded(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "sticker" && !body) {
|
||||
const res = await storage.get<string>(data.id);
|
||||
if (res) {
|
||||
setBody(res);
|
||||
setDownloaded(true);
|
||||
} else {
|
||||
await fetchMedia();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
loadingRef.current = false;
|
||||
}
|
||||
})();
|
||||
}, [data.id]);
|
||||
|
||||
async function fetchMedia() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await client.getMedia(data.id);
|
||||
setBody(res);
|
||||
setDownloaded(true);
|
||||
storage.set(data.id, res);
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function openMedia() {
|
||||
if (isLoading) return;
|
||||
|
||||
if (isDownloaded) {
|
||||
// download image
|
||||
const blob = await fetch(body!).then((res) => res.blob());
|
||||
const url = URL.createObjectURL(blob);
|
||||
window.open(url);
|
||||
return;
|
||||
} else {
|
||||
fetchMedia();
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "image") {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => openMedia()}
|
||||
className="w-full max-w-40 block relative overflow-hidden"
|
||||
>
|
||||
<img src={body || ""} alt="" className="w-full" />
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
||||
<Loader2Icon className="animate-spin size-8" />
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "sticker" && body) {
|
||||
return (
|
||||
<img
|
||||
src={body}
|
||||
alt=""
|
||||
className="w-full max-w-40 block relative overflow-hidden"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "document") {
|
||||
return (
|
||||
<button
|
||||
onClick={() => openMedia()}
|
||||
className="flex gap-2 items-center rounded-md bg-accent px-3 py-2 mb-2 truncate w-full cursor-pointer"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2Icon className="animate-spin size-4" />
|
||||
) : isDownloaded ? (
|
||||
<PaperclipIcon className="size-4" />
|
||||
) : (
|
||||
<DownloadIcon className="size-4" />
|
||||
)}
|
||||
<span className="text-xs truncate">{data.filename}</span>
|
||||
<span className="text-xs">{readableBytes(data.size || 0)}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function readableBytes(bytes: number) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
||||
if (bytes < 1024 * 1024 * 1024)
|
||||
return (bytes / 1024 / 1024).toFixed(1) + " MB";
|
||||
return (bytes / 1024 / 1024 / 1024).toFixed(1) + " GB";
|
||||
}
|
||||
Reference in New Issue
Block a user