mattrguerra/cortexmainPrivate on GitHub, published here in full

Attachment.tsx

Language TypeScript1.8 KB60 lines

src/components/Attachment.tsx

  1. import { useEffect, useState } from 'react'
  2. import type { AttachmentMeta } from '../ipc/gen/AttachmentMeta'
  3. import { useStore } from '../state/store'
  4. /** An image's bytes come from Rust as an ArrayBuffer and become a Blob URL. */
  5. export function useAttachmentUrl(a: AttachmentMeta) {
  6. const client = useStore((s) => s.client)
  7. const [url, setUrl] = useState<string | null>(null)
  8. useEffect(() => {
  9. if (a.kind !== 'image' || !client) return
  10. let revoked = false
  11. let made: string | null = null
  12. client.attachmentBytes(a.id).then((buf) => {
  13. if (revoked) return
  14. made = URL.createObjectURL(new Blob([buf], { type: a.mime }))
  15. setUrl(made)
  16. })
  17. return () => {
  18. revoked = true
  19. if (made) URL.revokeObjectURL(made)
  20. }
  21. }, [a.id, a.kind, a.mime, client])
  22. return url
  23. }
  24. export function AttachmentChip({ a, onRemove }: { a: AttachmentMeta; onRemove?: () => void }) {
  25. const url = useAttachmentUrl(a)
  26. return (
  27. <div className={`chip ${a.kind}`} title={a.filename}>
  28. {a.kind === 'image' ? (
  29. url ? (
  30. <img src={url} alt="" />
  31. ) : (
  32. <span className="chip-thumb" />
  33. )
  34. ) : (
  35. <span className="chip-icon" aria-hidden>
  36. TXT
  37. </span>
  38. )}
  39. <span className="chip-name">{a.filename}</span>
  40. {onRemove && (
  41. <button className="chip-x" onClick={onRemove} aria-label={`Remove ${a.filename}`}>
  42. ×
  43. </button>
  44. )}
  45. </div>
  46. )
  47. }
  48. export function AttachmentImage({ a }: { a: AttachmentMeta }) {
  49. const url = useAttachmentUrl(a)
  50. const ratio = a.width && a.height ? `${a.width} / ${a.height}` : '4 / 3'
  51. return (
  52. <figure className="msg-image" style={{ aspectRatio: ratio }}>
  53. {url && <img src={url} alt={a.filename} />}
  54. </figure>
  55. )
  56. }