//! The keyboard shortcuts this site offers, and the overlay that lists them. //! //! The only other `Chrome` in the tree is `Chrome::new()`, the empty default, //! used twice in [`super::embeds`] for embeds that deliberately have no shell. //! Every converted screen inherits the shortcut with no further work. //! //! # What quasi does, and what this owns //! //! quasi wires the key and does not draw the listing. //! `quasi-webview`'s `binding_html` emits a hidden button whose action targets //! the overlay container, and that is all of it; `Binding::group` names "the //! heading a listing shows it beneath", which says plainly that the listing is //! a screen the app describes. So the plumbing is quasi's and the screen is //! ours. There is no renderer-drawn help overlay to go looking for. //! //! # Its own mount, not a route inside the pricing nest //! //! An overlay reachable from every screen is not one screen's route. The //! binding's address is absolute in the emitted markup, so registering it under //! `/pricing` would give the site-wide shortcut a pricing-shaped address and //! move it the first time a second screen converted. use quasi_router::screen::{Cell, Column, Row, Table}; use quasi_router::{ Action, Chrome, Node, Outcome, RegionKind, Request, Response, RouteError, Router, Screen, Slot, }; /// Where the listing answers. pub const PATH: &str = "/shortcuts"; /// The key that opens it. const OPENS: &str = "?"; /// What this site binds. /// /// One binding, and the listing below says why the other three are not here: /// a `Binding` carries an [`Action`], and Escape, Cmd+S and Cmd+K perform no /// route. They are `static/dist/core/keyboard.js`'s, and they move into this /// function on the day the description layer can say what they do. /// /// Hung on the shell rather than built per screen, so every described screen /// this server serves offers it and none of them has to remember to. #[must_use] pub fn chrome() -> Chrome { Chrome::new().bind(OPENS, "Keyboard shortcuts", Action::get(PATH)) } /// One key and what it does, for the listing. /// /// The described binding is read off [`chrome`] rather than written again here, /// which is the point `Binding::label` exists to make: a help overlay that /// lists the bindings is otherwise a second, hand-written copy that drifts. /// The host keys have no `Binding` to read, so they are spelled -- once, beside /// the declaration, rather than in a template. const HOST_KEYS: &[(&str, &str)] = &[ ("Cmd+K", "Search"), ("Esc", "Close modal or overlay"), ("Cmd+S", "Save the current form"), ]; /// The listing, drawn over whatever the reader was looking at. /// /// A table because that is what it is: two columns, one row per key. The /// shipped overlay was a hand-written `` inside a string of markup in /// `keyboard.js`, and this is the same thing said once. pub fn screen(_state: &(), _request: Request) -> Result { // `Row::cells`, not `Row::new`: this row answers the two columns above // positionally, and `new` names the primary column of the default set. let mut rows: Vec = chrome() .bindings .iter() .map(|binding| Row::cells([Cell::new(&binding.key), Cell::new(&binding.label)])) .collect(); rows.extend( HOST_KEYS .iter() .map(|(key, what)| Row::cells([Cell::new(*key), Cell::new(*what)])), ); Ok(Outcome::Over( Screen::list_detail("Keyboard shortcuts", false).with( // `named` and not `label`. A modal is a screen's own slot, so // nothing reveals it and nothing would have drawn a label: this // said "Keyboard shortcuts" to no one until `2cdc6761` made that // unspellable. A dialog does want an accessible name, and `named` // is the field that writes one. Slot::new("shortcuts", RegionKind::Modal) .named("Keyboard shortcuts") .with(Node::from( Table::new([Column::new("Key"), Column::new("Does")]).rows(rows), )), ), ) .into()) } /// The listing's own nest. #[must_use] pub fn router() -> Router<()> { Router::<()>::new().get("/", screen) }