Skip to main content

max / makenotwork

4.2 KB · 102 lines History Blame Raw
1 //! The keyboard shortcuts this site offers, and the overlay that lists them.
2 //!
3 //! The only other `Chrome` in the tree is `Chrome::new()`, the empty default,
4 //! used twice in [`super::embeds`] for embeds that deliberately have no shell.
5 //! Every converted screen inherits the shortcut with no further work.
6 //!
7 //! # What quasi does, and what this owns
8 //!
9 //! quasi wires the key and does not draw the listing.
10 //! `quasi-webview`'s `binding_html` emits a hidden button whose action targets
11 //! the overlay container, and that is all of it; `Binding::group` names "the
12 //! heading a listing shows it beneath", which says plainly that the listing is
13 //! a screen the app describes. So the plumbing is quasi's and the screen is
14 //! ours. There is no renderer-drawn help overlay to go looking for.
15 //!
16 //! # Its own mount, not a route inside the pricing nest
17 //!
18 //! An overlay reachable from every screen is not one screen's route. The
19 //! binding's address is absolute in the emitted markup, so registering it under
20 //! `/pricing` would give the site-wide shortcut a pricing-shaped address and
21 //! move it the first time a second screen converted.
22
23 use quasi_router::screen::{Cell, Column, Row, Table};
24 use quasi_router::{
25 Action, Chrome, Node, Outcome, RegionKind, Request, Response, RouteError, Router, Screen, Slot,
26 };
27
28 /// Where the listing answers.
29 pub const PATH: &str = "/shortcuts";
30
31 /// The key that opens it.
32 const OPENS: &str = "?";
33
34 /// What this site binds.
35 ///
36 /// One binding, and the listing below says why the other three are not here:
37 /// a `Binding` carries an [`Action`], and Escape, Cmd+S and Cmd+K perform no
38 /// route. They are `static/dist/core/keyboard.js`'s, and they move into this
39 /// function on the day the description layer can say what they do.
40 ///
41 /// Hung on the shell rather than built per screen, so every described screen
42 /// this server serves offers it and none of them has to remember to.
43 #[must_use]
44 pub fn chrome() -> Chrome {
45 Chrome::new().bind(OPENS, "Keyboard shortcuts", Action::get(PATH))
46 }
47
48 /// One key and what it does, for the listing.
49 ///
50 /// The described binding is read off [`chrome`] rather than written again here,
51 /// which is the point `Binding::label` exists to make: a help overlay that
52 /// lists the bindings is otherwise a second, hand-written copy that drifts.
53 /// The host keys have no `Binding` to read, so they are spelled -- once, beside
54 /// the declaration, rather than in a template.
55 const HOST_KEYS: &[(&str, &str)] = &[
56 ("Cmd+K", "Search"),
57 ("Esc", "Close modal or overlay"),
58 ("Cmd+S", "Save the current form"),
59 ];
60
61 /// The listing, drawn over whatever the reader was looking at.
62 ///
63 /// A table because that is what it is: two columns, one row per key. The
64 /// shipped overlay was a hand-written `<table>` inside a string of markup in
65 /// `keyboard.js`, and this is the same thing said once.
66 pub fn screen(_state: &(), _request: Request) -> Result<Response, RouteError> {
67 // `Row::cells`, not `Row::new`: this row answers the two columns above
68 // positionally, and `new` names the primary column of the default set.
69 let mut rows: Vec<Row> = chrome()
70 .bindings
71 .iter()
72 .map(|binding| Row::cells([Cell::new(&binding.key), Cell::new(&binding.label)]))
73 .collect();
74 rows.extend(
75 HOST_KEYS
76 .iter()
77 .map(|(key, what)| Row::cells([Cell::new(*key), Cell::new(*what)])),
78 );
79
80 Ok(Outcome::Over(
81 Screen::list_detail("Keyboard shortcuts", false).with(
82 // `named` and not `label`. A modal is a screen's own slot, so
83 // nothing reveals it and nothing would have drawn a label: this
84 // said "Keyboard shortcuts" to no one until `2cdc6761` made that
85 // unspellable. A dialog does want an accessible name, and `named`
86 // is the field that writes one.
87 Slot::new("shortcuts", RegionKind::Modal)
88 .named("Keyboard shortcuts")
89 .with(Node::from(
90 Table::new([Column::new("Key"), Column::new("Does")]).rows(rows),
91 )),
92 ),
93 )
94 .into())
95 }
96
97 /// The listing's own nest.
98 #[must_use]
99 pub fn router() -> Router<()> {
100 Router::<()>::new().get("/", screen)
101 }
102