Make a reaction small, and the picker a panel rather than a page
Two things were too big. The pills under a message were caption-sized, which made a reaction read as a second thing said rather than a footnote on what was said; they are 14pt glyphs now, on a pill laid out from that. And the picker took the whole window, so choosing a reaction hid the message being reacted to — which is the one thing that has to stay in sight. It is a panel over the compose bar now: the message list gives up exactly the room the panel needs and keeps the rest. That room is measured rather than fixed, since the popular row is two rows deep and a group is as deep as the grid allows, and a fixed reserve would leave a hole above the short one. The groups go by their first word to keep them to two rows at panel width, the grid is nine across, and while the picker is open the jump-to-present row stands down: the panel is holding the compose bar's place, and a gap under it would just be a gap. Opening another buffer closes the picker — it belongs to the message it was opened on, and carrying it across would offer to react to something no longer on screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ad3a77d parent: 18c5ccd modified
src/frq/app.jolt +77 -35 | @@ -254,6 +254,11 @@ | ||
| 254 | 254 | ;; The message it answers is older than this buffer goes. |
| 255 | 255 | [:dim-label {:label "↩ replying to an earlier message"}])]) |
| 256 | 256 | |
| 257 | +(def ^:private pill-size | |
| 258 | + "How big a reaction is under a message. Small: it is a footnote on what was | |
| 259 | + said, not a second thing said." | |
| 260 | + 14) | |
| 261 | + | |
| 257 | 262 | (defn- react-button |
| 258 | 263 | "The way to put an emoji on a message, beside the way to answer it. |
| 259 | 264 | |
| @@ -275,6 +280,7 @@ | ||
| 275 | 280 | (for [emoji (sort (keys reactions))] |
| 276 | 281 | [:reaction {:key emoji |
| 277 | 282 | :emoji emoji |
| 283 | + :size pill-size | |
| 278 | 284 | :count (count (get reactions emoji)) |
| 279 | 285 | :mine (s/my-reaction? m emoji) |
| 280 | 286 | :on-click #(s/toggle-reaction! channel m emoji)}])])) |
| @@ -414,51 +420,77 @@ | ||
| 414 | 420 | [:image {:src path :max-height 20000 :on-click #(reset! s/lightbox nil)}]]])) |
| 415 | 421 | |
| 416 | 422 | (def ^:private picker-columns |
| 417 | - "Emoji to a row. Narrow enough that the grid still fits a phone-width | |
| 418 | - window, which is the width this app is laid out for." | |
| 419 | - 8) | |
| 423 | + "Emoji to a row, at the picker's own size. Narrow enough that the grid fits a | |
| 424 | + phone-width window, which is the width this app is laid out for." | |
| 425 | + 9) | |
| 426 | + | |
| 427 | +(def ^:private picker-size 20) | |
| 428 | + | |
| 429 | +(def ^:private picker-row-height 48) | |
| 430 | +(def ^:private picker-grid-max 170) | |
| 420 | 431 | |
| 421 | -(defn emoji-picker-screen | |
| 422 | - "The whole set to choose from, searchable, for one message. | |
| 432 | +(defn- picker-reserve | |
| 433 | + "How much room the picker panel wants under the message list. | |
| 423 | 434 | |
| 424 | - A screen rather than something floating over the conversation: the tree | |
| 425 | - backend paints in one layer and has no z-order to put a panel on top with — | |
| 426 | - the same reason the lightbox is a screen. It opens on the emoji people | |
| 427 | - actually react with; the groups and the search box are for the rest." | |
| 435 | + Measured rather than fixed: a panel is as tall as the grid it is showing, and | |
| 436 | + a reserve that assumed the tallest would leave a hole above a short one — the | |
| 437 | + popular row is two rows deep, a group is as deep as the grid allows." | |
| 438 | + [] | |
| 439 | + (let [n (min s/picker-limit (count (s/picker-emoji))) | |
| 440 | + rows (quot (+ n picker-columns -1) picker-columns) | |
| 441 | + searching? (seq (str/trim @s/emoji-search))] | |
| 442 | + (+ 82 ; the separator and compose bar | |
| 443 | + 44 ; the search box | |
| 444 | + (if searching? 0 88) ; the two rows of groups | |
| 445 | + (min picker-grid-max (* picker-row-height rows)) | |
| 446 | + ;; The spacing between the panel's own rows, which is small and adds up | |
| 447 | + ;; — a reserve that came up short would put the compose bar under the | |
| 448 | + ;; bottom of the window. | |
| 449 | + 60))) | |
| 450 | + | |
| 451 | +(defn emoji-picker | |
| 452 | + "The whole set to choose from, over the compose bar, for one message. | |
| 453 | + | |
| 454 | + A panel under the conversation rather than a screen of its own: choosing a | |
| 455 | + reaction is something done *to* a message, and a picker that hid the message | |
| 456 | + would be asking about something no longer on screen. It takes the bottom of | |
| 457 | + the window; the list above it gives up that much room and keeps the rest. | |
| 458 | + | |
| 459 | + It opens on the emoji people actually react with; the groups and the search | |
| 460 | + box are for the rest." | |
| 428 | 461 | [] |
| 429 | 462 | (let [shown (s/picker-emoji) |
| 430 | 463 | over (max 0 (- (count shown) s/picker-limit)) |
| 431 | - shown (vec (take s/picker-limit shown)) | |
| 432 | - rows (partition-all picker-columns shown) | |
| 464 | + rows (partition-all picker-columns (take s/picker-limit shown)) | |
| 433 | 465 | searching? (seq (str/trim @s/emoji-search))] |
| 434 | - [:vbox {:spacing 8 :margin 12} | |
| 435 | - [:hbox {:spacing 8} | |
| 436 | - [:button {:label "← Back" :on-click s/close-picker!}] | |
| 437 | - [:title {:label "React"}]] | |
| 438 | - [:entry {:text @s/emoji-search | |
| 439 | - :width-request 300 | |
| 440 | - :placeholder "Search emoji" | |
| 441 | - :on-change #(reset! s/emoji-search %)}] | |
| 442 | - ;; The groups are what the search box is not: a way in for someone who | |
| 443 | - ;; does not have a word for what they want. Dimmed out while a search is | |
| 444 | - ;; running, since what is on screen then is the search's answer. | |
| 466 | + [:vbox {:key :picker :spacing 4} | |
| 467 | + [:hbox {:spacing 6} | |
| 468 | + [:entry {:text @s/emoji-search | |
| 469 | + :width-request 240 | |
| 470 | + :placeholder "Search emoji" | |
| 471 | + :on-change #(reset! s/emoji-search %)}] | |
| 472 | + [:button {:label "✕" :on-click s/close-picker!}]] | |
| 473 | + ;; The groups are what the search box is not: a way in for someone who has | |
| 474 | + ;; no word for what they want. Their first word is enough to tell them | |
| 475 | + ;; apart, and is what keeps them to two rows. They give way to the | |
| 476 | + ;; search's own answer while something is typed. | |
| 445 | 477 | [:vbox {:key :groups :spacing 4} |
| 446 | 478 | (when-not searching? |
| 447 | - (for [[i row] (map-indexed vector (partition-all 3 (cons nil s/emoji-groups)))] | |
| 479 | + (for [[i row] (map-indexed vector (partition-all 5 (cons nil s/emoji-groups)))] | |
| 448 | 480 | [:hbox {:key i :spacing 4} |
| 449 | 481 | (for [g row] |
| 450 | 482 | [:button {:key (or g "popular") |
| 451 | - :label (or g "Popular") | |
| 483 | + :label (if g (first (str/split g #" ")) "Popular") | |
| 452 | 484 | :kind (if (= g @s/emoji-group) :primary :normal) |
| 453 | 485 | :on-click #(reset! s/emoji-group g)}])]))] |
| 454 | - [:separator {}] | |
| 455 | - [:scroll {:orientation :vertical :reserve 40} | |
| 486 | + [:scroll {:orientation :vertical :max-height picker-grid-max} | |
| 456 | 487 | (if (seq rows) |
| 457 | 488 | (for [[i row] (map-indexed vector rows)] |
| 458 | - [:hbox {:key i :spacing 4} | |
| 489 | + [:hbox {:key i :spacing 2} | |
| 459 | 490 | (for [[glyph] row] |
| 460 | 491 | [:reaction {:key glyph |
| 461 | 492 | :emoji glyph |
| 493 | + :size picker-size | |
| 462 | 494 | :count 0 |
| 463 | 495 | :on-click #(s/react-from-picker! glyph)}])]) |
| 464 | 496 | [:dim-label {:label "No emoji by that name."}])] |
| @@ -490,7 +522,12 @@ | ||
| 490 | 522 | ;; move the compose bar, and should: it appears because the |
| 491 | 523 | ;; reader asked to answer something, unlike the jump button, |
| 492 | 524 | ;; which appears on its own and must not shift what is under it. |
| 493 | - :reserve (if @s/replying-to 150 116) | |
| 525 | + ;; And the picker's, which takes the bottom of the window | |
| 526 | + ;; while it is open — the list gives up that room rather than | |
| 527 | + ;; being painted under it. | |
| 528 | + :reserve (cond @s/reacting (picker-reserve) | |
| 529 | + @s/replying-to 150 | |
| 530 | + :else 116) | |
| 494 | 531 | :stick-to-bottom true |
| 495 | 532 | :scroll-to-bottom @s/jump-tick |
| 496 | 533 | :on-change #(reset! s/at-present? (= "end" %))} |
| @@ -501,10 +538,17 @@ | ||
| 501 | 538 | ;; back to the present belongs next to the thing that puts you there. |
| 502 | 539 | ;; The row keeps its height whether or not the button is in it, so the |
| 503 | 540 | ;; compose bar below stays where the reader last saw it. |
| 541 | + ;; Choosing an emoji is the only thing being done while the picker is | |
| 542 | + ;; open, so it takes the row the way back to the present would have had. | |
| 543 | + [:vbox {:key :picker-panel} | |
| 544 | + (when @s/reacting [emoji-picker])] | |
| 504 | 545 | [:vbox {:key :jump} |
| 505 | - (if @s/at-present? | |
| 506 | - [:spacer {:size 34}] | |
| 507 | - [:button {:label "↓ Jump to present" :on-click s/jump-to-present!}])] | |
| 546 | + (cond | |
| 547 | + ;; Not even the spacer while the picker is open: the panel is what is | |
| 548 | + ;; holding the compose bar's place, and a gap under it is just a gap. | |
| 549 | + @s/reacting nil | |
| 550 | + @s/at-present? [:spacer {:size 34}] | |
| 551 | + :else [:button {:label "↓ Jump to present" :on-click s/jump-to-present!}])] | |
| 508 | 552 | [:separator {}] |
| 509 | 553 | ;; What the draft is answering, directly above where it is being typed. |
| 510 | 554 | [:vbox {:key :replying} |
| @@ -577,10 +621,8 @@ | ||
| 577 | 621 | ;; ---------------------------------------------------------------- shell |
| 578 | 622 | |
| 579 | 623 | (defn app [] |
| 580 | - (cond | |
| 581 | - @s/lightbox [lightbox-screen] | |
| 582 | - @s/reacting [emoji-picker-screen] | |
| 583 | - :else | |
| 624 | + (if @s/lightbox | |
| 625 | + [lightbox-screen] | |
| 584 | 626 | (case @s/screen |
| 585 | 627 | :connect [connect-screen] |
| 586 | 628 | :chat [chat-screen] |
| @@ -254,6 +254,11 @@ | |||
| 254 | ;; The message it answers is older than this buffer goes. | 254 | ;; The message it answers is older than this buffer goes. |
| 255 | [:dim-label {:label "↩ replying to an earlier message"}])]) | 255 | [:dim-label {:label "↩ replying to an earlier message"}])]) |
| 256 | 256 | ||
| 257 | +(def ^:private pill-size | ||
| 258 | + "How big a reaction is under a message. Small: it is a footnote on what was | ||
| 259 | + said, not a second thing said." | ||
| 260 | + 14) | ||
| 261 | + | ||
| 257 | (defn- react-button | 262 | (defn- react-button |
| 258 | "The way to put an emoji on a message, beside the way to answer it. | 263 | "The way to put an emoji on a message, beside the way to answer it. |
| 259 | 264 | ||
| @@ -275,6 +280,7 @@ | |||
| 275 | (for [emoji (sort (keys reactions))] | 280 | (for [emoji (sort (keys reactions))] |
| 276 | [:reaction {:key emoji | 281 | [:reaction {:key emoji |
| 277 | :emoji emoji | 282 | :emoji emoji |
| 283 | + :size pill-size | ||
| 278 | :count (count (get reactions emoji)) | 284 | :count (count (get reactions emoji)) |
| 279 | :mine (s/my-reaction? m emoji) | 285 | :mine (s/my-reaction? m emoji) |
| 280 | :on-click #(s/toggle-reaction! channel m emoji)}])])) | 286 | :on-click #(s/toggle-reaction! channel m emoji)}])])) |
| @@ -414,51 +420,77 @@ | |||
| 414 | [:image {:src path :max-height 20000 :on-click #(reset! s/lightbox nil)}]]])) | 420 | [:image {:src path :max-height 20000 :on-click #(reset! s/lightbox nil)}]]])) |
| 415 | 421 | ||
| 416 | (def ^:private picker-columns | 422 | (def ^:private picker-columns |
| 417 | - "Emoji to a row. Narrow enough that the grid still fits a phone-width | 423 | + "Emoji to a row, at the picker's own size. Narrow enough that the grid fits a |
| 418 | - window, which is the width this app is laid out for." | 424 | + phone-width window, which is the width this app is laid out for." |
| 419 | - 8) | 425 | + 9) |
| 426 | + | ||
| 427 | +(def ^:private picker-size 20) | ||
| 428 | + | ||
| 429 | +(def ^:private picker-row-height 48) | ||
| 430 | +(def ^:private picker-grid-max 170) | ||
| 420 | 431 | ||
| 421 | -(defn emoji-picker-screen | 432 | +(defn- picker-reserve |
| 422 | - "The whole set to choose from, searchable, for one message. | 433 | + "How much room the picker panel wants under the message list. |
| 423 | 434 | ||
| 424 | - A screen rather than something floating over the conversation: the tree | 435 | + Measured rather than fixed: a panel is as tall as the grid it is showing, and |
| 425 | - backend paints in one layer and has no z-order to put a panel on top with — | 436 | + a reserve that assumed the tallest would leave a hole above a short one — the |
| 426 | - the same reason the lightbox is a screen. It opens on the emoji people | 437 | + popular row is two rows deep, a group is as deep as the grid allows." |
| 427 | - actually react with; the groups and the search box are for the rest." | 438 | + [] |
| 439 | + (let [n (min s/picker-limit (count (s/picker-emoji))) | ||
| 440 | + rows (quot (+ n picker-columns -1) picker-columns) | ||
| 441 | + searching? (seq (str/trim @s/emoji-search))] | ||
| 442 | + (+ 82 ; the separator and compose bar | ||
| 443 | + 44 ; the search box | ||
| 444 | + (if searching? 0 88) ; the two rows of groups | ||
| 445 | + (min picker-grid-max (* picker-row-height rows)) | ||
| 446 | + ;; The spacing between the panel's own rows, which is small and adds up | ||
| 447 | + ;; — a reserve that came up short would put the compose bar under the | ||
| 448 | + ;; bottom of the window. | ||
| 449 | + 60))) | ||
| 450 | + | ||
| 451 | +(defn emoji-picker | ||
| 452 | + "The whole set to choose from, over the compose bar, for one message. | ||
| 453 | + | ||
| 454 | + A panel under the conversation rather than a screen of its own: choosing a | ||
| 455 | + reaction is something done *to* a message, and a picker that hid the message | ||
| 456 | + would be asking about something no longer on screen. It takes the bottom of | ||
| 457 | + the window; the list above it gives up that much room and keeps the rest. | ||
| 458 | + | ||
| 459 | + It opens on the emoji people actually react with; the groups and the search | ||
| 460 | + box are for the rest." | ||
| 428 | [] | 461 | [] |
| 429 | (let [shown (s/picker-emoji) | 462 | (let [shown (s/picker-emoji) |
| 430 | over (max 0 (- (count shown) s/picker-limit)) | 463 | over (max 0 (- (count shown) s/picker-limit)) |
| 431 | - shown (vec (take s/picker-limit shown)) | 464 | + rows (partition-all picker-columns (take s/picker-limit shown)) |
| 432 | - rows (partition-all picker-columns shown) | ||
| 433 | searching? (seq (str/trim @s/emoji-search))] | 465 | searching? (seq (str/trim @s/emoji-search))] |
| 434 | - [:vbox {:spacing 8 :margin 12} | 466 | + [:vbox {:key :picker :spacing 4} |
| 435 | - [:hbox {:spacing 8} | 467 | + [:hbox {:spacing 6} |
| 436 | - [:button {:label "← Back" :on-click s/close-picker!}] | 468 | + [:entry {:text @s/emoji-search |
| 437 | - [:title {:label "React"}]] | 469 | + :width-request 240 |
| 438 | - [:entry {:text @s/emoji-search | 470 | + :placeholder "Search emoji" |
| 439 | - :width-request 300 | 471 | + :on-change #(reset! s/emoji-search %)}] |
| 440 | - :placeholder "Search emoji" | 472 | + [:button {:label "✕" :on-click s/close-picker!}]] |
| 441 | - :on-change #(reset! s/emoji-search %)}] | 473 | + ;; The groups are what the search box is not: a way in for someone who has |
| 442 | - ;; The groups are what the search box is not: a way in for someone who | 474 | + ;; no word for what they want. Their first word is enough to tell them |
| 443 | - ;; does not have a word for what they want. Dimmed out while a search is | 475 | + ;; apart, and is what keeps them to two rows. They give way to the |
| 444 | - ;; running, since what is on screen then is the search's answer. | 476 | + ;; search's own answer while something is typed. |
| 445 | [:vbox {:key :groups :spacing 4} | 477 | [:vbox {:key :groups :spacing 4} |
| 446 | (when-not searching? | 478 | (when-not searching? |
| 447 | - (for [[i row] (map-indexed vector (partition-all 3 (cons nil s/emoji-groups)))] | 479 | + (for [[i row] (map-indexed vector (partition-all 5 (cons nil s/emoji-groups)))] |
| 448 | [:hbox {:key i :spacing 4} | 480 | [:hbox {:key i :spacing 4} |
| 449 | (for [g row] | 481 | (for [g row] |
| 450 | [:button {:key (or g "popular") | 482 | [:button {:key (or g "popular") |
| 451 | - :label (or g "Popular") | 483 | + :label (if g (first (str/split g #" ")) "Popular") |
| 452 | :kind (if (= g @s/emoji-group) :primary :normal) | 484 | :kind (if (= g @s/emoji-group) :primary :normal) |
| 453 | :on-click #(reset! s/emoji-group g)}])]))] | 485 | :on-click #(reset! s/emoji-group g)}])]))] |
| 454 | - [:separator {}] | 486 | + [:scroll {:orientation :vertical :max-height picker-grid-max} |
| 455 | - [:scroll {:orientation :vertical :reserve 40} | ||
| 456 | (if (seq rows) | 487 | (if (seq rows) |
| 457 | (for [[i row] (map-indexed vector rows)] | 488 | (for [[i row] (map-indexed vector rows)] |
| 458 | - [:hbox {:key i :spacing 4} | 489 | + [:hbox {:key i :spacing 2} |
| 459 | (for [[glyph] row] | 490 | (for [[glyph] row] |
| 460 | [:reaction {:key glyph | 491 | [:reaction {:key glyph |
| 461 | :emoji glyph | 492 | :emoji glyph |
| 493 | + :size picker-size | ||
| 462 | :count 0 | 494 | :count 0 |
| 463 | :on-click #(s/react-from-picker! glyph)}])]) | 495 | :on-click #(s/react-from-picker! glyph)}])]) |
| 464 | [:dim-label {:label "No emoji by that name."}])] | 496 | [:dim-label {:label "No emoji by that name."}])] |
| @@ -490,7 +522,12 @@ | |||
| 490 | ;; move the compose bar, and should: it appears because the | 522 | ;; move the compose bar, and should: it appears because the |
| 491 | ;; reader asked to answer something, unlike the jump button, | 523 | ;; reader asked to answer something, unlike the jump button, |
| 492 | ;; which appears on its own and must not shift what is under it. | 524 | ;; which appears on its own and must not shift what is under it. |
| 493 | - :reserve (if @s/replying-to 150 116) | 525 | + ;; And the picker's, which takes the bottom of the window |
| 526 | + ;; while it is open — the list gives up that room rather than | ||
| 527 | + ;; being painted under it. | ||
| 528 | + :reserve (cond @s/reacting (picker-reserve) | ||
| 529 | + @s/replying-to 150 | ||
| 530 | + :else 116) | ||
| 494 | :stick-to-bottom true | 531 | :stick-to-bottom true |
| 495 | :scroll-to-bottom @s/jump-tick | 532 | :scroll-to-bottom @s/jump-tick |
| 496 | :on-change #(reset! s/at-present? (= "end" %))} | 533 | :on-change #(reset! s/at-present? (= "end" %))} |
| @@ -501,10 +538,17 @@ | |||
| 501 | ;; back to the present belongs next to the thing that puts you there. | 538 | ;; back to the present belongs next to the thing that puts you there. |
| 502 | ;; The row keeps its height whether or not the button is in it, so the | 539 | ;; The row keeps its height whether or not the button is in it, so the |
| 503 | ;; compose bar below stays where the reader last saw it. | 540 | ;; compose bar below stays where the reader last saw it. |
| 541 | + ;; Choosing an emoji is the only thing being done while the picker is | ||
| 542 | + ;; open, so it takes the row the way back to the present would have had. | ||
| 543 | + [:vbox {:key :picker-panel} | ||
| 544 | + (when @s/reacting [emoji-picker])] | ||
| 504 | [:vbox {:key :jump} | 545 | [:vbox {:key :jump} |
| 505 | - (if @s/at-present? | 546 | + (cond |
| 506 | - [:spacer {:size 34}] | 547 | + ;; Not even the spacer while the picker is open: the panel is what is |
| 507 | - [:button {:label "↓ Jump to present" :on-click s/jump-to-present!}])] | 548 | + ;; holding the compose bar's place, and a gap under it is just a gap. |
| 549 | + @s/reacting nil | ||
| 550 | + @s/at-present? [:spacer {:size 34}] | ||
| 551 | + :else [:button {:label "↓ Jump to present" :on-click s/jump-to-present!}])] | ||
| 508 | [:separator {}] | 552 | [:separator {}] |
| 509 | ;; What the draft is answering, directly above where it is being typed. | 553 | ;; What the draft is answering, directly above where it is being typed. |
| 510 | [:vbox {:key :replying} | 554 | [:vbox {:key :replying} |
| @@ -577,10 +621,8 @@ | |||
| 577 | ;; ---------------------------------------------------------------- shell | 621 | ;; ---------------------------------------------------------------- shell |
| 578 | 622 | ||
| 579 | (defn app [] | 623 | (defn app [] |
| 580 | - (cond | 624 | + (if @s/lightbox |
| 581 | - @s/lightbox [lightbox-screen] | 625 | + [lightbox-screen] |
| 582 | - @s/reacting [emoji-picker-screen] | ||
| 583 | - :else | ||
| 584 | (case @s/screen | 626 | (case @s/screen |
| 585 | :connect [connect-screen] | 627 | :connect [connect-screen] |
| 586 | :chat [chat-screen] | 628 | :chat [chat-screen] |
modified
src/frq/state.jolt +16 -11 | @@ -70,6 +70,16 @@ | ||
| 70 | 70 | (defn reply-to! [m] (reset! replying-to (select-keys m [:id :from :text]))) |
| 71 | 71 | (defn cancel-reply! [] (reset! replying-to nil)) |
| 72 | 72 | |
| 73 | +;; The message the emoji picker is choosing for, as `{:channel :id}`, or nil | |
| 74 | +;; when it is closed. The picker is a panel over the compose bar rather than a | |
| 75 | +;; screen: what is being reacted to has to stay in sight. | |
| 76 | +(defonce reacting (atom nil)) | |
| 77 | + | |
| 78 | +;; What the picker is showing: the search box, and which group is on screen | |
| 79 | +;; when nothing has been typed. `nil` is the popular row it opens on. | |
| 80 | +(defonce emoji-search (atom "")) | |
| 81 | +(defonce emoji-group (atom nil)) | |
| 82 | + | |
| 73 | 83 | ;; The picture being looked at full size, or nil. Vidya's tree has no overlay, |
| 74 | 84 | ;; so this is a screen of its own rather than a layer over the chat. |
| 75 | 85 | (defonce lightbox (atom nil)) ; {:path :url} |
| @@ -158,6 +168,9 @@ | ||
| 158 | 168 | [name] |
| 159 | 169 | (reset! current name) |
| 160 | 170 | (reset! screen :chat) |
| 171 | + ;; A picker belongs to the message it was opened on; carrying it into another | |
| 172 | + ;; buffer would offer to react to something that is no longer on screen. | |
| 173 | + (reset! reacting nil) | |
| 161 | 174 | (swap! channels #(-> (ensure-channel % name) |
| 162 | 175 | (assoc-in [name :unread] 0) |
| 163 | 176 | (assoc-in [name :accessed] (swap! access-tick inc)))) |
| @@ -513,16 +526,6 @@ | ||
| 513 | 526 | (reset! replying-to nil) |
| 514 | 527 | (reset! draft "")))) |
| 515 | 528 | |
| 516 | -;; The message the emoji picker is choosing for, as `{:channel :id}`, or nil | |
| 517 | -;; when it is closed. The picker is a screen of its own — the tree backend | |
| 518 | -;; paints in one layer, so there is no floating it over the conversation. | |
| 519 | -(defonce reacting (atom nil)) | |
| 520 | - | |
| 521 | -;; What the picker is showing: the search box, and which group is on screen | |
| 522 | -;; when nothing has been typed. `nil` is the popular row it opens on. | |
| 523 | -(defonce emoji-search (atom "")) | |
| 524 | -(defonce emoji-group (atom nil)) | |
| 525 | - | |
| 526 | 529 | (defn open-picker! |
| 527 | 530 | "Choose an emoji for this message. Opening it fresh — no leftover search from |
| 528 | 531 | the last time, which would be a screen of somebody else's question." |
| @@ -549,7 +552,9 @@ | ||
| 549 | 552 | face, and by the emoji itself, so pasting one finds it." |
| 550 | 553 | [] |
| 551 | 554 | (let [q (str/lower-case (str/trim @emoji-search)) |
| 552 | - group @emoji-group] | |
| 555 | + ;; A blank group is no group: the popular row is what nothing selected | |
| 556 | + ;; means, and an empty string would filter the catalog down to nothing. | |
| 557 | + group (when (seq (or @emoji-group "")) @emoji-group)] | |
| 553 | 558 | (cond |
| 554 | 559 | (seq q) (->> emoji/catalog |
| 555 | 560 | (filter (fn [[glyph name _]] |
| @@ -70,6 +70,16 @@ | |||
| 70 | (defn reply-to! [m] (reset! replying-to (select-keys m [:id :from :text]))) | 70 | (defn reply-to! [m] (reset! replying-to (select-keys m [:id :from :text]))) |
| 71 | (defn cancel-reply! [] (reset! replying-to nil)) | 71 | (defn cancel-reply! [] (reset! replying-to nil)) |
| 72 | 72 | ||
| 73 | +;; The message the emoji picker is choosing for, as `{:channel :id}`, or nil | ||
| 74 | +;; when it is closed. The picker is a panel over the compose bar rather than a | ||
| 75 | +;; screen: what is being reacted to has to stay in sight. | ||
| 76 | +(defonce reacting (atom nil)) | ||
| 77 | + | ||
| 78 | +;; What the picker is showing: the search box, and which group is on screen | ||
| 79 | +;; when nothing has been typed. `nil` is the popular row it opens on. | ||
| 80 | +(defonce emoji-search (atom "")) | ||
| 81 | +(defonce emoji-group (atom nil)) | ||
| 82 | + | ||
| 73 | ;; The picture being looked at full size, or nil. Vidya's tree has no overlay, | 83 | ;; The picture being looked at full size, or nil. Vidya's tree has no overlay, |
| 74 | ;; so this is a screen of its own rather than a layer over the chat. | 84 | ;; so this is a screen of its own rather than a layer over the chat. |
| 75 | (defonce lightbox (atom nil)) ; {:path :url} | 85 | (defonce lightbox (atom nil)) ; {:path :url} |
| @@ -158,6 +168,9 @@ | |||
| 158 | [name] | 168 | [name] |
| 159 | (reset! current name) | 169 | (reset! current name) |
| 160 | (reset! screen :chat) | 170 | (reset! screen :chat) |
| 171 | + ;; A picker belongs to the message it was opened on; carrying it into another | ||
| 172 | + ;; buffer would offer to react to something that is no longer on screen. | ||
| 173 | + (reset! reacting nil) | ||
| 161 | (swap! channels #(-> (ensure-channel % name) | 174 | (swap! channels #(-> (ensure-channel % name) |
| 162 | (assoc-in [name :unread] 0) | 175 | (assoc-in [name :unread] 0) |
| 163 | (assoc-in [name :accessed] (swap! access-tick inc)))) | 176 | (assoc-in [name :accessed] (swap! access-tick inc)))) |
| @@ -513,16 +526,6 @@ | |||
| 513 | (reset! replying-to nil) | 526 | (reset! replying-to nil) |
| 514 | (reset! draft "")))) | 527 | (reset! draft "")))) |
| 515 | 528 | ||
| 516 | -;; The message the emoji picker is choosing for, as `{:channel :id}`, or nil | ||
| 517 | -;; when it is closed. The picker is a screen of its own — the tree backend | ||
| 518 | -;; paints in one layer, so there is no floating it over the conversation. | ||
| 519 | -(defonce reacting (atom nil)) | ||
| 520 | - | ||
| 521 | -;; What the picker is showing: the search box, and which group is on screen | ||
| 522 | -;; when nothing has been typed. `nil` is the popular row it opens on. | ||
| 523 | -(defonce emoji-search (atom "")) | ||
| 524 | -(defonce emoji-group (atom nil)) | ||
| 525 | - | ||
| 526 | (defn open-picker! | 529 | (defn open-picker! |
| 527 | "Choose an emoji for this message. Opening it fresh — no leftover search from | 530 | "Choose an emoji for this message. Opening it fresh — no leftover search from |
| 528 | the last time, which would be a screen of somebody else's question." | 531 | the last time, which would be a screen of somebody else's question." |
| @@ -549,7 +552,9 @@ | |||
| 549 | face, and by the emoji itself, so pasting one finds it." | 552 | face, and by the emoji itself, so pasting one finds it." |
| 550 | [] | 553 | [] |
| 551 | (let [q (str/lower-case (str/trim @emoji-search)) | 554 | (let [q (str/lower-case (str/trim @emoji-search)) |
| 552 | - group @emoji-group] | 555 | + ;; A blank group is no group: the popular row is what nothing selected |
| 556 | + ;; means, and an empty string would filter the catalog down to nothing. | ||
| 557 | + group (when (seq (or @emoji-group "")) @emoji-group)] | ||
| 553 | (cond | 558 | (cond |
| 554 | (seq q) (->> emoji/catalog | 559 | (seq q) (->> emoji/catalog |
| 555 | (filter (fn [[glyph name _]] | 560 | (filter (fn [[glyph name _]] |