Click a picture to see it full size
A screen rather than a layer over the chat: the tree backend paints in one pass with no z-order, so there is nothing to put an overlay on top of. It takes the whole window, names the URL it came from, and closes on the same gesture that opened it — clicking the picture — or on Back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
563ada3 parent: 56cdac3 modified
README.md +1 -1 | @@ -104,7 +104,7 @@ surface — that surface does not work on Android either, while the syscalls do. | ||
| 104 | 104 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` |
| 105 | 105 | * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead |
| 106 | 106 | * Inline previews for PNG links, fetched once and cached under |
| 107 | - `$XDG_CACHE_HOME/frq/media` | |
| 107 | + `$XDG_CACHE_HOME/frq/media`; click one to see it full size | |
| 108 | 108 | * Join/part notices, DMs bucketed under the sender's nick |
| 109 | 109 | * Discover list, search over buffers, disconnect |
| 110 | 110 | * Conversations listed most recently opened first |
| @@ -104,7 +104,7 @@ surface — that surface does not work on Android either, while the syscalls do. | |||
| 104 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` | 104 | * Join channels, channel buffers with unread counts, send and receive `PRIVMSG` |
| 105 | * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead | 105 | * Backlog on join, and `CHATHISTORY` for the channels freeq restores instead |
| 106 | * Inline previews for PNG links, fetched once and cached under | 106 | * Inline previews for PNG links, fetched once and cached under |
| 107 | - `$XDG_CACHE_HOME/frq/media` | 107 | + `$XDG_CACHE_HOME/frq/media`; click one to see it full size |
| 108 | * Join/part notices, DMs bucketed under the sender's nick | 108 | * Join/part notices, DMs bucketed under the sender's nick |
| 109 | * Discover list, search over buffers, disconnect | 109 | * Discover list, search over buffers, disconnect |
| 110 | * Conversations listed most recently opened first | 110 | * Conversations listed most recently opened first |
modified
src/frq/app.jolt +26 -7 | @@ -200,12 +200,29 @@ | ||
| 200 | 200 | (let [_ @s/media-tick] |
| 201 | 201 | (for [url (:images m)] |
| 202 | 202 | (when-let [path (media/path-when-ready url)] |
| 203 | - [:image {:key url :src path :max-height 260}]))))]])) | |
| 203 | + [:image {:key url | |
| 204 | + :src path | |
| 205 | + :max-height 260 | |
| 206 | + :on-click #(reset! s/lightbox {:path path :url url})}]))))]])) | |
| 204 | 207 | |
| 205 | 208 | (defn- message-rows [messages] |
| 206 | 209 | (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m]) |
| 207 | 210 | messages)) |
| 208 | 211 | |
| 212 | +(defn lightbox-screen | |
| 213 | + "One picture, as big as the window allows. | |
| 214 | + | |
| 215 | + A screen rather than an overlay: the tree backend paints in one layer and | |
| 216 | + has no z-order to put something on top of everything else with." | |
| 217 | + [] | |
| 218 | + (let [{:keys [path url]} @s/lightbox] | |
| 219 | + [:vbox {:spacing 8 :margin 12} | |
| 220 | + [:hbox {:spacing 8} | |
| 221 | + [:button {:label "← Back" :on-click #(reset! s/lightbox nil)}] | |
| 222 | + [:dim-label {:label url}]] | |
| 223 | + ;; Clicking the picture closes it too — the same gesture that opened it. | |
| 224 | + [:image {:src path :max-height 2000 :on-click #(reset! s/lightbox nil)}]])) | |
| 225 | + | |
| 209 | 226 | (defn chat-screen [] |
| 210 | 227 | (let [name @s/current |
| 211 | 228 | buffer (get @s/channels name)] |
| @@ -289,12 +306,14 @@ | ||
| 289 | 306 | ;; ---------------------------------------------------------------- shell |
| 290 | 307 | |
| 291 | 308 | (defn app [] |
| 292 | - (case @s/screen | |
| 293 | - :connect [connect-screen] | |
| 294 | - :chat [chat-screen] | |
| 295 | - :discover [discover-screen] | |
| 296 | - :settings [settings-screen] | |
| 297 | - [chats-screen])) | |
| 309 | + (if @s/lightbox | |
| 310 | + [lightbox-screen] | |
| 311 | + (case @s/screen | |
| 312 | + :connect [connect-screen] | |
| 313 | + :chat [chat-screen] | |
| 314 | + :discover [discover-screen] | |
| 315 | + :settings [settings-screen] | |
| 316 | + [chats-screen]))) | |
| 298 | 317 | |
| 299 | 318 | (defn -main [& _] |
| 300 | 319 | ;; Before the window: a saved sign-in decides which mode the connect screen |
| @@ -200,12 +200,29 @@ | |||
| 200 | (let [_ @s/media-tick] | 200 | (let [_ @s/media-tick] |
| 201 | (for [url (:images m)] | 201 | (for [url (:images m)] |
| 202 | (when-let [path (media/path-when-ready url)] | 202 | (when-let [path (media/path-when-ready url)] |
| 203 | - [:image {:key url :src path :max-height 260}]))))]])) | 203 | + [:image {:key url |
| 204 | + :src path | ||
| 205 | + :max-height 260 | ||
| 206 | + :on-click #(reset! s/lightbox {:path path :url url})}]))))]])) | ||
| 204 | 207 | ||
| 205 | (defn- message-rows [messages] | 208 | (defn- message-rows [messages] |
| 206 | (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m]) | 209 | (map-indexed (fn [i m] [message-row i (when (pos? i) (nth messages (dec i))) m]) |
| 207 | messages)) | 210 | messages)) |
| 208 | 211 | ||
| 212 | +(defn lightbox-screen | ||
| 213 | + "One picture, as big as the window allows. | ||
| 214 | + | ||
| 215 | + A screen rather than an overlay: the tree backend paints in one layer and | ||
| 216 | + has no z-order to put something on top of everything else with." | ||
| 217 | + [] | ||
| 218 | + (let [{:keys [path url]} @s/lightbox] | ||
| 219 | + [:vbox {:spacing 8 :margin 12} | ||
| 220 | + [:hbox {:spacing 8} | ||
| 221 | + [:button {:label "← Back" :on-click #(reset! s/lightbox nil)}] | ||
| 222 | + [:dim-label {:label url}]] | ||
| 223 | + ;; Clicking the picture closes it too — the same gesture that opened it. | ||
| 224 | + [:image {:src path :max-height 2000 :on-click #(reset! s/lightbox nil)}]])) | ||
| 225 | + | ||
| 209 | (defn chat-screen [] | 226 | (defn chat-screen [] |
| 210 | (let [name @s/current | 227 | (let [name @s/current |
| 211 | buffer (get @s/channels name)] | 228 | buffer (get @s/channels name)] |
| @@ -289,12 +306,14 @@ | |||
| 289 | ;; ---------------------------------------------------------------- shell | 306 | ;; ---------------------------------------------------------------- shell |
| 290 | 307 | ||
| 291 | (defn app [] | 308 | (defn app [] |
| 292 | - (case @s/screen | 309 | + (if @s/lightbox |
| 293 | - :connect [connect-screen] | 310 | + [lightbox-screen] |
| 294 | - :chat [chat-screen] | 311 | + (case @s/screen |
| 295 | - :discover [discover-screen] | 312 | + :connect [connect-screen] |
| 296 | - :settings [settings-screen] | 313 | + :chat [chat-screen] |
| 297 | - [chats-screen])) | 314 | + :discover [discover-screen] |
| 315 | + :settings [settings-screen] | ||
| 316 | + [chats-screen]))) | ||
| 298 | 317 | ||
| 299 | (defn -main [& _] | 318 | (defn -main [& _] |
| 300 | ;; Before the window: a saved sign-in decides which mode the connect screen | 319 | ;; Before the window: a saved sign-in decides which mode the connect screen |
modified
src/frq/oauth.jolt +2 -3 | @@ -12,6 +12,7 @@ | ||
| 12 | 12 | from the broker token on every later connection." |
| 13 | 13 | (:require [clojure.string :as str] |
| 14 | 14 | [frq.atproto :as atproto] |
| 15 | + [frq.platform :as platform] | |
| 15 | 16 | [jolt.ffi :as ffi] |
| 16 | 17 | [jolt.host :as host] |
| 17 | 18 | [jolt.socket :as socket])) |
| @@ -46,9 +47,7 @@ | ||
| 46 | 47 | "Hand the URL to the desktop. A failure here is not fatal — the caller shows |
| 47 | 48 | the URL so it can be opened by hand." |
| 48 | 49 | [url] |
| 49 | - (try | |
| 50 | - (zero? (host/sh (str "xdg-open '" (str/replace url "'" "%27") "' >/dev/null 2>&1 &"))) | |
| 51 | - (catch Exception _ false))) | |
| 50 | + (platform/open-url! url)) | |
| 52 | 51 | |
| 53 | 52 | ;; ------------------------------------------------------------------ capture |
| 54 | 53 | |
| @@ -12,6 +12,7 @@ | |||
| 12 | from the broker token on every later connection." | 12 | from the broker token on every later connection." |
| 13 | (:require [clojure.string :as str] | 13 | (:require [clojure.string :as str] |
| 14 | [frq.atproto :as atproto] | 14 | [frq.atproto :as atproto] |
| 15 | + [frq.platform :as platform] | ||
| 15 | [jolt.ffi :as ffi] | 16 | [jolt.ffi :as ffi] |
| 16 | [jolt.host :as host] | 17 | [jolt.host :as host] |
| 17 | [jolt.socket :as socket])) | 18 | [jolt.socket :as socket])) |
| @@ -46,9 +47,7 @@ | |||
| 46 | "Hand the URL to the desktop. A failure here is not fatal — the caller shows | 47 | "Hand the URL to the desktop. A failure here is not fatal — the caller shows |
| 47 | the URL so it can be opened by hand." | 48 | the URL so it can be opened by hand." |
| 48 | [url] | 49 | [url] |
| 49 | - (try | 50 | + (platform/open-url! url)) |
| 50 | - (zero? (host/sh (str "xdg-open '" (str/replace url "'" "%27") "' >/dev/null 2>&1 &"))) | ||
| 51 | - (catch Exception _ false))) | ||
| 52 | 51 | ||
| 53 | ;; ------------------------------------------------------------------ capture | 52 | ;; ------------------------------------------------------------------ capture |
| 54 | 53 | ||
added
src/frq/platform.jolt +12 -0 | new file mode 100644 | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +(ns frq.platform | |
| 2 | + "The few things that are the desktop's job rather than the app's." | |
| 3 | + (:require [clojure.string :as str] | |
| 4 | + [jolt.host :as host])) | |
| 5 | + | |
| 6 | +(defn open-url! | |
| 7 | + "Hand a URL to the desktop. Android has no xdg-open, so this is a no-op | |
| 8 | + there — the same reason sign-in is desktop-only." | |
| 9 | + [url] | |
| 10 | + (try | |
| 11 | + (zero? (host/sh (str "xdg-open '" (str/replace (or url "") "'" "%27") "' >/dev/null 2>&1 &"))) | |
| 12 | + (catch Exception _ false))) | |
| new file mode 100644 | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | +(ns frq.platform | ||
| 2 | + "The few things that are the desktop's job rather than the app's." | ||
| 3 | + (:require [clojure.string :as str] | ||
| 4 | + [jolt.host :as host])) | ||
| 5 | + | ||
| 6 | +(defn open-url! | ||
| 7 | + "Hand a URL to the desktop. Android has no xdg-open, so this is a no-op | ||
| 8 | + there — the same reason sign-in is desktop-only." | ||
| 9 | + [url] | ||
| 10 | + (try | ||
| 11 | + (zero? (host/sh (str "xdg-open '" (str/replace (or url "") "'" "%27") "' >/dev/null 2>&1 &"))) | ||
| 12 | + (catch Exception _ false))) | ||
modified
src/frq/state.jolt +4 -0 | @@ -59,6 +59,10 @@ | ||
| 59 | 59 | (defonce channels (atom {})) |
| 60 | 60 | (defonce current (atom nil)) |
| 61 | 61 | (defonce draft (atom "")) |
| 62 | + | |
| 63 | +;; The picture being looked at full size, or nil. Vidya's tree has no overlay, | |
| 64 | +;; so this is a screen of its own rather than a layer over the chat. | |
| 65 | +(defonce lightbox (atom nil)) ; {:path :url} | |
| 62 | 66 | (defonce join-input (atom "")) |
| 63 | 67 | ;; A counter rather than a clock: the list only needs their order, and a |
| 64 | 68 | ;; monotonic tick cannot be surprised by the system time moving. |
| @@ -59,6 +59,10 @@ | |||
| 59 | (defonce channels (atom {})) | 59 | (defonce channels (atom {})) |
| 60 | (defonce current (atom nil)) | 60 | (defonce current (atom nil)) |
| 61 | (defonce draft (atom "")) | 61 | (defonce draft (atom "")) |
| 62 | + | ||
| 63 | +;; The picture being looked at full size, or nil. Vidya's tree has no overlay, | ||
| 64 | +;; so this is a screen of its own rather than a layer over the chat. | ||
| 65 | +(defonce lightbox (atom nil)) ; {:path :url} | ||
| 62 | (defonce join-input (atom "")) | 66 | (defonce join-input (atom "")) |
| 63 | ;; A counter rather than a clock: the list only needs their order, and a | 67 | ;; A counter rather than a clock: the list only needs their order, and a |
| 64 | ;; monotonic tick cannot be surprised by the system time moving. | 68 | ;; monotonic tick cannot be surprised by the system time moving. |