/** Compact edit-reference thumbnails; the generation form owns the images. */ import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Plus, X, ChevronLeft, ChevronRight, ImagePlus } from 'lucide-react' import { tt } from './helpers.ts' import { clipboardImageFiles, EDIT_REFERENCE_LIMIT, type EditReference } from './reference-images.ts' import css from './reference-images.module.css' export function ReferenceImages(props: { images: EditReference[] reading: boolean onAdd: (files: File[]) => void onRemove: (id: string) => void onClear: () => void notice?: string | null }) { const input = useRef(null) const dialog = useRef(null) const previousFocus = useRef(null) const [previewId, setPreviewId] = useState(null) const index = props.images.findIndex(image => image.id === previewId) const preview = props.images[index] const close = (): void => { setPreviewId(null) } const move = (delta: number): void => { const next = props.images[(index + delta + props.images.length) % props.images.length]; if (next) setPreviewId(next.id) } useEffect(() => { if (!preview) return dialog.current?.focus() const keyboard = (event: KeyboardEvent): void => { if (event.key === 'Escape' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') { event.preventDefault(); event.stopImmediatePropagation() if (event.key === 'Escape') close() else move(event.key === 'ArrowLeft' ? -1 : 1) } if (event.key === 'Tab') { const buttons = [...(dialog.current?.querySelectorAll('button:not(:disabled)') ?? [])] const first = buttons[0], last = buttons[buttons.length - 1] if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog.current)) { event.preventDefault(); last?.focus() } else if (!event.shiftKey && (document.activeElement === last || document.activeElement === dialog.current)) { event.preventDefault(); first?.focus() } } } window.addEventListener('keydown', keyboard, true) return () => window.removeEventListener('keydown', keyboard, true) }, [previewId, props.images]) const previewOpen = preview !== undefined useEffect(() => { if (!previewOpen) return return () => { if (previousFocus.current?.isConnected) previousFocus.current.focus() } }, [previewOpen]) return <>
{ event.preventDefault(); event.stopPropagation() }} onDrop={event => { event.preventDefault(); event.stopPropagation(); props.onAdd(Array.from(event.dataTransfer.files)) }} onPaste={event => { const files = clipboardImageFiles(event.clipboardData); if (files.length > 0) { event.preventDefault(); event.stopPropagation(); props.onAdd(files) } }}>
{tt('edit.references')}{tt('edit.addedCount', { count: props.images.length })}
{props.images.length === 0 ? :
{props.images.map((image, index) =>
)} {props.images.length < EDIT_REFERENCE_LIMIT ? : null}
}

{props.reading ? tt('edit.reading') : tt('edit.multiHint')}

{tt('edit.limits', { count: EDIT_REFERENCE_LIMIT })}

{props.notice ?

{props.notice}

: null} { props.onAdd(Array.from(event.target.files ?? [])); event.target.value = '' }} />
{preview ? createPortal(
event.stopPropagation()}>
event.stopPropagation()}>
{index + 1} / {props.images.length} ยท {preview.name}
{preview.name} {props.images.length > 1 ? : null}
, document.body) : null} }