Attachment.tsx
Language TypeScript1.8 KB60 lines
import { useEffect, useState } from 'react'import type { AttachmentMeta } from '../ipc/gen/AttachmentMeta'import { useStore } from '../state/store'/** An image's bytes come from Rust as an ArrayBuffer and become a Blob URL. */export function useAttachmentUrl(a: AttachmentMeta) {const client = useStore((s) => s.client)const [url, setUrl] = useState<string | null>(null)useEffect(() => {if (a.kind !== 'image' || !client) returnlet revoked = falselet made: string | null = nullclient.attachmentBytes(a.id).then((buf) => {if (revoked) returnmade = URL.createObjectURL(new Blob([buf], { type: a.mime }))setUrl(made)})return () => {revoked = trueif (made) URL.revokeObjectURL(made)}}, [a.id, a.kind, a.mime, client])return url}export function AttachmentChip({ a, onRemove }: { a: AttachmentMeta; onRemove?: () => void }) {const url = useAttachmentUrl(a)return (<div className={`chip ${a.kind}`} title={a.filename}>{a.kind === 'image' ? (url ? (<img src={url} alt="" />) : (<span className="chip-thumb" />)) : (<span className="chip-icon" aria-hidden>TXT</span>)}<span className="chip-name">{a.filename}</span>{onRemove && (<button className="chip-x" onClick={onRemove} aria-label={`Remove ${a.filename}`}>×</button>)}</div>)}export function AttachmentImage({ a }: { a: AttachmentMeta }) {const url = useAttachmentUrl(a)const ratio = a.width && a.height ? `${a.width} / ${a.height}` : '4 / 3'return (<figure className="msg-image" style={{ aspectRatio: ratio }}>{url && <img src={url} alt={a.filename} />}</figure>)}