| @@ -303,13 +303,31 @@ |
| 303 | said, not a second thing said." | 303 | said, not a second thing said." |
| 304 | 14) | 304 | 14) |
| 305 | | 305 | |
| 306 | -(defn- react-button | 306 | +(defn- action-chips |
| 307 | - "The way to put an emoji on a message, beside the way to answer it. | 307 | + "Answering and reacting, on the sender's row above the message. |
| 308 | | 308 | |
| 309 | - Both are things done *to* a message rather than parts of it, so they share | 309 | + Both are things done *to* a message rather than parts of it, so they ride |
| 310 | - the sender's line and the right edge. It opens the picker on this message." | 310 | + the sender's row against its right edge, at the size a reaction is. In the |
| | 311 | + line with the text they took width off every line under them and wrapped a |
| | 312 | + message that had the room to sit on one. |
| | 313 | + |
| | 314 | + Chips rather than buttons: a button is sized for a label and these carry a |
| | 315 | + glyph, which made two lozenges taller than the line they belonged to. |
| | 316 | + `:reaction` with no count is the same pill the picker offers, so what you |
| | 317 | + press to react and what appears once you have look like one family. |
| | 318 | + |
| | 319 | + A row laid out from the right lays its first child furthest right, so |
| | 320 | + reacting comes first in the source and this reads ↩️ then 🙂 on screen." |
| 311 | [channel m] | 321 | [channel m] |
| 312 | - [:button {:label "☺" :on-click #(s/open-picker! channel m)}]) | 322 | + [:hbox {:key :actions :align :end :spacing 4} |
| | 323 | + [:reaction {:key :react |
| | 324 | + :emoji "🙂" |
| | 325 | + :size pill-size |
| | 326 | + :on-click #(s/open-picker! channel m)}] |
| | 327 | + [:reaction {:key :reply |
| | 328 | + :emoji "↩️" |
| | 329 | + :size pill-size |
| | 330 | + :on-click #(s/reply-to! m)}]]) |
| 313 | | 331 | |
| 314 | (defn- reaction-row | 332 | (defn- reaction-row |
| 315 | "What people have put on a message, under it. | 333 | "What people have put on a message, under it. |
| @@ -437,18 +455,18 @@ |
| 437 | [:dim-label {:label "Click for the full profile"}]])) | 455 | [:dim-label {:label "Click for the full profile"}]])) |
| 438 | | 456 | |
| 439 | (defn message-row | 457 | (defn message-row |
| 440 | - "One message. `prev` is the message above it, which decides whether this one | 458 | + "One message: who said it, when, what you can do to it, and the words. |
| 441 | - repeats the sender. | 459 | + |
| | 460 | + Every line names its sender, rather than the first of a run only. A run |
| | 461 | + collapsed to one heading reads well until you answer the fourth line of it, |
| | 462 | + and then the line quoted back has no name on it; and the actions live on the |
| | 463 | + sender's row, which a headerless line has nowhere to put. |
| 442 | | 464 | |
| 443 | Sender above the text, not beside it: a wrapping label in a horizontal row | 465 | Sender above the text, not beside it: a wrapping label in a horizontal row |
| 444 | lays out against the row's width rather than the column's, so one long URL | 466 | lays out against the row's width rather than the column's, so one long URL |
| 445 | drags every line that follows it off the left edge." | 467 | drags every line that follows it off the left edge." |
| 446 | - [i prev m] | 468 | + [i m] |
| 447 | - (let [same-sender? (and prev | 469 | + (let [;; What a jump landed on wears a surface of its own for a moment, so |
| 448 | - (not (:system? m)) | | |
| 449 | - (not (:system? prev)) | | |
| 450 | - (= (:from prev) (:from m))) | | |
| 451 | - ;; What a jump landed on wears a surface of its own for a moment, so | | |
| 452 | ;; the answer to "which one was I sent to" is on the screen rather | 470 | ;; the answer to "which one was I sent to" is on the screen rather |
| 453 | ;; than in the reader's count of rows. | 471 | ;; than in the reader's count of rows. |
| 454 | highlit? (boolean (and (:id m) (= (:id m) @s/highlight)))] | 472 | highlit? (boolean (and (:id m) (= (:id m) @s/highlight)))] |
| @@ -457,22 +475,21 @@ |
| 457 | ;; list's scrollbar rides it too — without this the ↩ is what the | 475 | ;; list's scrollbar rides it too — without this the ↩ is what the |
| 458 | ;; scrollbar is drawn over. | 476 | ;; scrollbar is drawn over. |
| 459 | :margin-right 10 | 477 | :margin-right 10 |
| 460 | - ;; Space above a new speaker, none inside their run: the gap is | 478 | + ;; One gap between messages: every line carries its own heading |
| 461 | - ;; what separates one person's block from the next, and evenly | 479 | + ;; now, so there are no runs to hold together and nothing for a |
| 462 | - ;; spaced lines are what made the column read as one dense list. | 480 | + ;; closed-up line to belong to. |
| 463 | - :margin-top (if same-sender? 0 10) | 481 | + :margin-top 10 |
| 464 | ;; The jump target is what a "go to message" click scrolls to. | 482 | ;; The jump target is what a "go to message" click scrolls to. |
| 465 | :scroll-here (boolean (and (:id m) (= (:id m) @s/jump-to)))} | 483 | :scroll-here (boolean (and (:id m) (= (:id m) @s/jump-to)))} |
| 466 | ;; Likewise a key each: the surface a jump leaves behind is a different | 484 | ;; Likewise a key each: the surface a jump leaves behind is a different |
| 467 | ;; node from the plain column, not the same node wearing another tag. | 485 | ;; node from the plain column, not the same node wearing another tag. |
| 468 | [(if highlit? :card :vbox) {:key (if highlit? :body-card :body-plain) | 486 | [(if highlit? :card :vbox) {:key (if highlit? :body-card :body-plain) |
| 469 | :spacing 2 :margin 0} | 487 | :spacing 2 :margin 0} |
| 470 | - ;; A run from one person reads as one block; repeating the nick on every | 488 | + ;; Who, when, and what can be done about it, on one line above the |
| 471 | - ;; line is what made a busy channel look like a list of headers. The time | 489 | + ;; words: the name and the time say where the message came from, and the |
| 472 | - ;; rides on that same line, for the same reason: once per run, not once | 490 | + ;; chips at the far end are the two answers to it. |
| 473 | - ;; per line. | | |
| 474 | [:vbox {:key :who} | 491 | [:vbox {:key :who} |
| 475 | - (when-not (or same-sender? (:system? m)) | 492 | + (when-not (:system? m) |
| 476 | ;; Everything that is about the person rather than the line: their | 493 | ;; Everything that is about the person rather than the line: their |
| 477 | ;; picture, their name, and when they started saying this. | 494 | ;; picture, their name, and when they started saying this. |
| 478 | [:hbox {:spacing 6} | 495 | [:hbox {:spacing 6} |
| @@ -505,52 +522,32 @@ |
| 505 | [:label {:label (:from m)}] | 522 | [:label {:label (:from m)}] |
| 506 | (when-let [at (:at m)] | 523 | (when-let [at (:at m)] |
| 507 | [:dim-label {:label (clock/clock-time at)}]) | 524 | [:dim-label {:label (clock/clock-time at)}]) |
| 508 | - ])] | 525 | + ;; And the two things you can do to the message, at the far end of |
| 509 | - ;; Every message can be answered, not only the one that opened a run — | 526 | + ;; its heading: a nested row laid out from the right takes what is |
| 510 | - ;; laid out from the right so the action sits at the edge and the text | 527 | + ;; left of the width and puts the chips against the edge of it, so |
| 511 | - ;; takes the width that is left. The text keeps a column of its own: | 528 | + ;; the words below keep the whole column. |
| 512 | - ;; a wrapping label directly in a row wraps against the row. | 529 | + ;; |
| | 530 | + ;; A key of its own for each branch, rather than one key whose node |
| | 531 | + ;; changes tag under it: a line this client sent has no msgid until |
| | 532 | + ;; the server echoes it back, and swapping a `:spacer` for a chip in |
| | 533 | + ;; place is what left the row rebuilt in the wrong order. |
| | 534 | + (if (:id m) |
| | 535 | + [action-chips {:key :actions} @s/current m] |
| | 536 | + [:spacer {:key :actions-gap :size 0}])])] |
| 513 | ;; A little air under the sender's row: the name and face are a heading | 537 | ;; A little air under the sender's row: the name and face are a heading |
| 514 | ;; for what follows, and a heading that touches its text is not one. | 538 | ;; for what follows, and a heading that touches its text is not one. |
| 515 | - [:hbox {:key :line :align :end :spacing 6 :margin-top 10} | 539 | + ;; The text keeps a column of its own: a wrapping label directly in a |
| 516 | - ;; Answering and reacting sit together because they are the same kind | 540 | + ;; row wraps against the row. |
| 517 | - ;; of thing: what you do to a message rather than what it says. Both | 541 | + [:vbox {:key :text :spacing 2 :margin-top 4} |
| 518 | - ;; are in the row itself rather than in a box of their own — a box with | 542 | + ;; What this answers, in the column the answer itself is in: the chip |
| 519 | - ;; no width takes the rest of the row and puts the text on a line below | 543 | + ;; is a quote of a line, so it wraps to the width a line has. Outside |
| 520 | - ;; it, and one with a width takes the column's whole height. Reacting | 544 | + ;; this column it wrapped to the whole row instead — wider than any |
| 521 | - ;; comes first in the source because a row laid out from the right lays | 545 | + ;; message, and out to the window's edge on a narrow screen. |
| 522 | - ;; its first child furthest right: on screen this reads ↩ then ☺. | 546 | + [:vbox {:key :reply-chip} |
| 523 | - ;; A key of its own for each branch, rather than one key whose node | 547 | + (when-let [reply-to (:reply-to m)] |
| 524 | - ;; changes tag under it: a line this client sent has no msgid until the | 548 | + [reply-chip @s/current reply-to])] |
| 525 | - ;; server echoes it back, and swapping a `:spacer` for a `:button` in | 549 | + (map-indexed (fn [j run] (run-node j run (:system? m))) |
| 526 | - ;; place is what left the row rebuilt in the wrong order — the actions | 550 | + (text-runs (:text m)))] |
| 527 | - ;; ended up after the text, and every row beside it laid out from an | | |
| 528 | - ;; edge that was no longer the row's. Two keys make it a removal and an | | |
| 529 | - ;; addition, which is what it is. | | |
| 530 | - (if (:id m) | | |
| 531 | - [react-button {:key :react-button} @s/current m] | | |
| 532 | - [:spacer {:key :react-gap :size 0}]) | | |
| 533 | - (if (:id m) | | |
| 534 | - [:button {:key :reply-button :label "↩" :on-click #(s/reply-to! m)}] | | |
| 535 | - [:spacer {:key :reply-gap :size 0}]) | | |
| 536 | - ;; The text column carries the pills as well as the words: put them | | |
| 537 | - ;; outside this row and they sit below it, and the row is as tall as | | |
| 538 | - ;; the buttons at its right edge — which is half a line of air between | | |
| 539 | - ;; a message and the reactions on it, coming from nothing anyone asked | | |
| 540 | - ;; for. | | |
| 541 | - [:vbox {:key :text :spacing 2} | | |
| 542 | - ;; What this answers, in the column the answer itself is in: the chip | | |
| 543 | - ;; is a quote of a line, so it wraps to the width a line has. Outside | | |
| 544 | - ;; this column it wrapped to the whole row instead — wider than any | | |
| 545 | - ;; message, and out to the window's edge on a narrow screen. | | |
| 546 | - [:vbox {:key :reply-chip} | | |
| 547 | - (when-let [reply-to (:reply-to m)] | | |
| 548 | - [reply-chip @s/current reply-to])] | | |
| 549 | - (map-indexed (fn [j run] (run-node j run (:system? m))) | | |
| 550 | - (text-runs (:text m))) | | |
| 551 | - [:vbox {:key :reactions-row :margin-top 6} | | |
| 552 | - (when (and (:id m) (not (:system? m)) (seq (:reactions m))) | | |
| 553 | - [reaction-row @s/current m])]]] | | |
| 554 | ;; And the picker, when this is the message it was opened on: under the | 551 | ;; And the picker, when this is the message it was opened on: under the |
| 555 | ;; line it is about, where the reader is already looking. | 552 | ;; line it is about, where the reader is already looking. |
| 556 | ;; | 553 | ;; |
| @@ -571,7 +568,15 @@ |
| 571 | [:image {:key url | 568 | [:image {:key url |
| 572 | :src path | 569 | :src path |
| 573 | :max-height 260 | 570 | :max-height 260 |
| 574 | - :on-click #(reset! s/lightbox {:path path :url url})}]))))]]])) | 571 | + :on-click #(reset! s/lightbox {:path path :url url})}]))))] |
| | 572 | + ;; Reactions go last, under whatever the message turned out to be: a |
| | 573 | + ;; line with a picture on it is the picture, and pills between the words |
| | 574 | + ;; and the image they introduce read as reactions to the words alone. |
| | 575 | + ;; Nothing moves for a message with no picture — the box above it is |
| | 576 | + ;; empty, and the pills still sit under the last line of text. |
| | 577 | + [:vbox {:key :reactions-row :margin-top 6} |
| | 578 | + (when (and (:id m) (not (:system? m)) (seq (:reactions m))) |
| | 579 | + [reaction-row @s/current m])]]])) |
| 575 | | 580 | |
| 576 | (defn- day-separator [day-key label] | 581 | (defn- day-separator [day-key label] |
| 577 | [:vbox {:key day-key :spacing 4 :margin 0} | 582 | [:vbox {:key day-key :spacing 4 :margin 0} |
| @@ -598,7 +603,7 @@ |
| 598 | ;; replay, an echo taking the place of what was sent — shifts | 603 | ;; replay, an echo taking the place of what was sent — shifts |
| 599 | ;; every row after it onto the widgets of its neighbour. | 604 | ;; every row after it onto the widgets of its neighbour. |
| 600 | true (conj ^{:key (or (:id m) (str "row-" i))} | 605 | true (conj ^{:key (or (:id m) (str "row-" i))} |
| 601 | - [message-row i (when-not new-day? prev) m])))) | 606 | + [message-row i m])))) |
| 602 | (range (count messages)) | 607 | (range (count messages)) |
| 603 | messages)) | 608 | messages)) |
| 604 | | 609 | |