import { Text } from "@methanium/ui/markdown"; import { useStorage } from "@tensamin/storage/context"; import { Avatar, AvatarFallback, AvatarImage, Button, Dialog, DialogClose, DialogContent, DialogTrigger, Tooltip, TooltipContent, TooltipTrigger, } from "@methanium/ui"; import { toast } from "@tensamin/shared/log"; import { Check, Copy, ExternalLink, TriangleAlert, X, ZoomIn, } from "lucide-react"; import { useState, useMemo, useEffect, useRef, type WheelEvent } from "react"; import MediaSaveButton from "./mediaSaveButton"; import { useUserFields } from "@tensamin/identity/context"; const zoomLevels = [1, 1.5, 2, 3]; export default function Media({ link, senderId, time, }: { link: string; senderId: number; time: string; }) { const { load } = useStorage(); const [trustedDomains, setTrustedDomains] = useState([]); const [hidden, setHidden] = useState(true); const [dialogOpen, setDialogOpen] = useState(false); const [zoomLevel, setZoomLevel] = useState(0); const wheelDelta = useRef(0); const hostname = useMemo(() => new URL(link).hostname, [link]); const { data: user } = useUserFields(senderId, ["Avatar", "Display"]); const avatar = user?.Avatar ? `data:image/webp;base64,${user.Avatar}` : undefined; useEffect(() => { load("chat_trusted_domains").then(setTrustedDomains); }, [load]); const changeZoom = (change: number) => { setZoomLevel((level) => Math.max(0, Math.min(zoomLevels.length - 1, level + change)), ); }; const handleWheel = (event: WheelEvent) => { wheelDelta.current += event.deltaY; if (Math.abs(wheelDelta.current) < 50) return; changeZoom(wheelDelta.current < 0 ? 1 : -1); wheelDelta.current = 0; }; const [copied, setCopied] = useState(false); useEffect(() => { let cancelled = false; if (copied) { setTimeout(() => { if (cancelled) return; setCopied(false); }, 1000); } return () => { cancelled = true; }; }, [copied]); const copyImage = async () => { try { if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") { throw new Error("Image clipboard access is not supported."); } const image = (async () => { const response = await fetch(link); if (!response.ok) { throw new Error(`Failed to download image (${response.status}).`); } const blob = await response.blob(); if (blob.type === "image/png") return blob; const bitmap = await createImageBitmap(blob); try { const canvas = document.createElement("canvas"); canvas.width = bitmap.width; canvas.height = bitmap.height; const context = canvas.getContext("2d"); if (!context) { throw new Error("Failed to prepare the image for copying."); } context.drawImage(bitmap, 0, 0); return await new Promise((resolve, reject) => { canvas.toBlob( (convertedBlob) => convertedBlob ? resolve(convertedBlob) : reject(new Error("Failed to convert the image to PNG.")), "image/png", ); }); } finally { bitmap.close(); } })(); await navigator.clipboard.write([ new ClipboardItem({ "image/png": image }), ]); setCopied(true); } catch (error) { toast("error", "Failed to copy image", String(error)); } }; return trustedDomains.includes(hostname) ? (
{hidden ? ( ) : ( )} { setDialogOpen(open); if (!open) { setZoomLevel(0); wheelDelta.current = 0; } }} > ( setHidden(false)} className="max-h-70 max-w-70 py-1 rounded-lg cursor-pointer" /> )} />
{user?.Display.slice(0, 2).toUpperCase()}

{user?.Display}

{time}

( )} /> Zoom {zoomLevel + 1}/4
( )} /> {copied ? "Copied!" : "Copy image"} ( )} /> Open in browser
( ( )} /> Close )} />
setHidden(false)} style={{ transform: `translate(-50%, -50%) scale(${zoomLevels[zoomLevel]})`, }} className="h-[80vh] rounded-lg object-cover absolute top-1/2 left-1/2 transition-transform duration-200" />
) : (
} /> Link embeds can get your IP-Address! You can configure trusted domains in the settings.
); }