import { useState } from 'react'; import { ImageOff, Pencil, Trash2, Upload } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { MediaPicker } from './MediaPicker'; import { posterUrl, thumbUrl } from './thumbnail'; /** * One picture or video on the page, shown at the shape the page will show it. * * ── Why not a path in a text box ── * * It was one, and a path is not what anybody is choosing. Somebody picking the header photograph is * choosing a PHOTOGRAPH; `ABOUT US/HEADER.jpg` is how the machine refers to it, and reading a string to * find out whether you picked the right image is doing the computer's job for it. * * So the field is the picture, at the aspect ratio the page uses. An empty one is the same box, empty — * which also means the panel stops changing height as things are chosen, and a page with three slots * looks like the page rather than like a form. * * ── The aspect ratio is a prop, and has to be ── * * The header is 21:9, the product video is 16:9, a gallery slot is square. This component cannot know * that and must not guess: it is the SITE that decides what shape a slot is, and a component that * guessed would be a second opinion to keep in step with the stylesheet. * * ── Dropping ── * * It accepts a drag from Officer's own file browser, which carries paths under * `application/x-officer-files`. That is the same payload the file browser uses to move files between * windows, so dragging a photograph out of Media and onto a slot is one gesture with no upload. * * It does NOT accept a file dragged from the desktop. That would be an upload into `originals/`, which * is a different operation with its own failure modes, and the Media section already does it properly. */ const DRAG_TYPE = 'application/x-officer-files'; export const MediaField = ({ value, onChange, aspect, kind = 'photo', label, }: { /** A path under `originals/`, or null. */ value: string | null; onChange: (path: string | null) => void; /** Tailwind's ratio, as the page spells it — `21/9`, `16/9`, `1/1`. */ aspect: string; /** A video is shown by its poster; there is no width rung to ask for. */ kind?: 'photo' | 'video'; /** What the picker's title says, so it names the thing rather than saying "media". */ label?: string; }) => { const [picking, setPicking] = useState(false); const [over, setOver] = useState(false); const [broken, setBroken] = useState(false); const preview = value ? (kind === 'video' ? posterUrl(value) : thumbUrl(value, 640)) : null; const accept = (event: React.DragEvent): string | null => { if (!event.dataTransfer.types.includes(DRAG_TYPE)) return null; try { const paths = JSON.parse(event.dataTransfer.getData(DRAG_TYPE)); return Array.isArray(paths) && typeof paths[0] === 'string' ? paths[0] : null; } catch { return null; } }; return ( <>
{value}
}