(ns frq.pictures "Choosing a picture and sending it, named once for both Flutter targets. The fifth seam, and it exists because a browser has no file. dart:io picks one with the system chooser, copies it into the app's own storage and uploads it by path; a browser is handed BYTES — from the chooser, or from the clipboard — and there is no path to copy to. `File.copy` on the web throws `Unsupported operation: _Namespace`, which is dart:io's filesystem saying it is not there, and what the reader saw was `Could not read that picture: _Namespace`. So a picture here is a map, and either half of it may be missing: `:path` where there is a filesystem, `:bytes` where there is not, `:filename` always. `frq.upload.core` already builds the multipart body out of bytes, so only the transport and the chooser differ." (:refer-clojure :exclude [name])) (defonce ^:private impl (atom {})) (defn install! [m] (swap! impl merge m) nil) (defn ^:async pick! "Ask the reader for a picture. Nil when they chose none. `dir` is where a copy belongs on the targets that have one — it is the caller's, because it comes from `frq.io/config-dir`, and the web backend ignores it." [dir] (if-let [f (:pick! @impl)] (await (f dir)) (throw (ex-info "frq.pictures: no backend installed" {:op :pick!})))) (defn ^:async upload! "Send `picture` and answer the URL freeq serves it back at." [host did channel picture] (if-let [f (:upload! @impl)] (await (f host did channel picture)) (throw (ex-info "frq.pictures: no backend installed" {:op :upload!})))) (defn preview-url "What the renderer should draw for the picture at `url`, or nil while there is nothing to draw yet. Two different answers, and the browser's is the simpler one. dart:io downloads the picture into a cache and hands back a path, because a phone wants the bytes on disk and `frq.media` is the thing that puts them there. A browser already has an image loader with a cache in front of it, and no disk to put anything on — so the answer is the URL, which `frq.hiccup/src-url` then sends through the same proxy the avatars use. Synchronous, because the screens call it while they build a row and read nil as \"not here yet\" — see `frq.media/path-when-ready`, which is the answer on the other two targets." [url] (if-let [f (:preview-url @impl)] (f url) nil))