max / audiofiles
1 file changed,
+115 insertions,
-84 deletions
| @@ -76,10 +76,8 @@ | |||
| 76 | 76 | //! take. Untidy rather than broken, and it is what shipped: the binding | |
| 77 | 77 | //! predates this flip, which only made it the sole path to the overlay. | |
| 78 | 78 | ||
| 79 | - | use quasi_router::{ | |
| 80 | - | Action, Cell, Cells, Chrome, Column, Node, RegionKind, Request, Response, RouteError, Router, | |
| 81 | - | Screen, Slot, Table, | |
| 82 | - | }; | |
| 79 | + | use quasi_declare::declare; | |
| 80 | + | use quasi_router::{Action, Chrome, Request, Response, RouteError, Router}; | |
| 83 | 81 | ||
| 84 | 82 | use super::{Panel, Panels}; | |
| 85 | 83 | ||
| @@ -220,95 +218,128 @@ | |||
| 220 | 218 | ||
| 221 | 219 | /// `GET /help` | |
| 222 | 220 | fn index(_state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> { | |
| 223 | - | Ok(Response::over(screen())) | |
| 221 | + | let chrome = chrome(); | |
| 222 | + | Ok(Response::over(screen(&bound(&chrome)))) | |
| 224 | 223 | } | |
| 225 | 224 | ||
| 226 | - | /// The overlay. | |
| 225 | + | /// One heading's worth of the shortcuts table. | |
| 227 | 226 | /// | |
| 228 | - | /// # Two tabs as one region, not as a control beside one | |
| 229 | - | /// | |
| 230 | - | /// This was a `Node::Select` over a `Slot` the chosen tab's contents were | |
| 231 | - | /// posted into, and it was the last tenant of that member anywhere in the tree | |
| 232 | - | /// (`e9f61b46`, Max: one way to describe a tab strip). A region showing one | |
| 233 | - | /// child at a time says the same thing with nothing beside it: the strip is | |
| 234 | - | /// derived from the children's labels, and which one is up is | |
| 235 | - | /// [`Slot::showing_one`]. | |
| 236 | - | /// | |
| 237 | - | /// Both panels are built here rather than fetched, so no child carries | |
| 238 | - | /// [`Slot::fed_by`] and pressing a tab calls no route. They are two static | |
| 239 | - | /// documents a few kilobytes long; a round trip to reveal bytes already in hand | |
| 240 | - | /// is what every renderer's derivation refuses. That is what retired | |
| 241 | - | /// `POST /help/tab` along with the member. | |
| 242 | - | fn screen() -> Screen { | |
| 243 | - | let body = Slot::new(BODY, RegionKind::Pane) | |
| 244 | - | .with(Node::page("audiofiles")) | |
| 245 | - | .with(Node::Region( | |
| 246 | - | Slot::new(TAB, RegionKind::TabGroup) | |
| 247 | - | .showing_one(0) | |
| 248 | - | .with(Node::Region(shortcuts().into_iter().fold( | |
| 249 | - | Slot::new(SHORTCUTS, RegionKind::Group).label("Shortcuts"), | |
| 250 | - | Slot::with, | |
| 251 | - | ))) | |
| 252 | - | .with(Node::Region( | |
| 253 | - | Slot::new(FEATURES, RegionKind::Group) | |
| 254 | - | .label("Features") | |
| 255 | - | .with(features()), | |
| 256 | - | )), | |
| 257 | - | )); | |
| 258 | - | ||
| 259 | - | Screen::sidebar_content("Help").with(body) | |
| 227 | + | /// [`Chrome::grouped`] answers with pairs, and a description names what it | |
| 228 | + | /// draws: `.0` is not a name. Same reshaping the two MNW consts took when the | |
| 229 | + | /// form met them. | |
| 230 | + | struct Bound<'a> { | |
| 231 | + | /// The heading, absent for the ungrouped run. | |
| 232 | + | /// | |
| 233 | + | /// This table has none today and would have one again the moment somebody | |
| 234 | + | /// added a `bind`. Drawn without a heading rather than under an invented | |
| 235 | + | /// one, which is the loop over the `Option` in [`shortcuts`]. | |
| 236 | + | name: Option<&'a str>, | |
| 237 | + | /// The keys under it, in the order the app bound them. | |
| 238 | + | keys: Vec<&'a quasi_router::chrome::Binding>, | |
| 260 | 239 | } | |
| 261 | 240 | ||
| 262 | - | /// Every key that works, read off the one table. | |
| 263 | - | /// | |
| 264 | - | /// No filter box. The shipped tab has one because twenty-six rows in a | |
| 265 | - | /// fixed-height scroll area need it; narrowing a list a screen was handed is | |
| 266 | - | /// what a host does, which is the rule the bulk port's tag completions and | |
| 267 | - | /// folder filter both follow. | |
| 268 | - | fn shortcuts() -> Vec<Node> { | |
| 269 | - | // A heading and a table per group, in the order the app bound them, which | |
| 270 | - | // is what `Chrome::grouped` answers. Not sorted here: the reading order is | |
| 271 | - | // the shipped tab's and belongs to whoever wrote the table. | |
| 272 | - | // | |
| 273 | - | // Several tables rather than one with a group column, because a group is a | |
| 274 | - | // heading and not a value: a column repeating "Toggles" six times says the | |
| 275 | - | // same thing six times and is still one wall. | |
| 276 | - | let mut sections = Vec::new(); | |
| 277 | - | for (group, bindings) in chrome().grouped() { | |
| 278 | - | // `None` is the ungrouped run, which this table has none of today and | |
| 279 | - | // would have again the moment somebody added a `bind`. Drawn without a | |
| 280 | - | // heading rather than under an invented one. | |
| 281 | - | if let Some(name) = group { | |
| 282 | - | sections.push(Node::section(name)); | |
| 241 | + | /// The one table, grouped and named. | |
| 242 | + | fn bound(chrome: &Chrome) -> Vec<Bound<'_>> { | |
| 243 | + | chrome | |
| 244 | + | .grouped() | |
| 245 | + | .into_iter() | |
| 246 | + | .map(|(name, keys)| Bound { name, keys }) | |
| 247 | + | .collect() | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | declare! { | |
| 251 | + | /// The overlay. | |
| 252 | + | /// | |
| 253 | + | /// # Two tabs as one region, not as a control beside one | |
| 254 | + | /// | |
| 255 | + | /// This was a `Node::Select` over a `Slot` the chosen tab's contents were | |
| 256 | + | /// posted into, and it was the last tenant of that member anywhere in the | |
| 257 | + | /// tree (`e9f61b46`, Max: one way to describe a tab strip). A region | |
| 258 | + | /// showing one child at a time says the same thing with nothing beside it: | |
| 259 | + | /// the strip is derived from the children's labels, and which one is up is | |
| 260 | + | /// [`Slot::showing_one`]. | |
| 261 | + | /// | |
| 262 | + | /// Both panels are built here rather than fetched, so no child carries | |
| 263 | + | /// [`Slot::fed_by`] and pressing a tab calls no route. They are two static | |
| 264 | + | /// documents a few kilobytes long; a round trip to reveal bytes already in | |
| 265 | + | /// hand is what every renderer's derivation refuses. That is what retired | |
| 266 | + | /// `POST /help/tab` along with the member. | |
| 267 | + | shape screen(bound: &[Bound<'_>]) -> Screen; | |
| 268 | + | ||
| 269 | + | screen sidebar_content "Help" { | |
| 270 | + | region BODY as Pane { | |
| 271 | + | page "audiofiles"; | |
| 272 | + | ||
| 273 | + | region TAB as TabGroup { | |
| 274 | + | showing_one 0; | |
| 275 | + | ||
| 276 | + | region SHORTCUTS as Group { | |
| 277 | + | label "Shortcuts"; | |
| 278 | + | extend shortcuts(bound); | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | region FEATURES as Group { | |
| 282 | + | label "Features"; | |
| 283 | + | include features(); | |
| 284 | + | } | |
| 285 | + | } | |
| 283 | 286 | } | |
| 284 | - | // Positional cells: the two columns and the two cells are written a | |
| 285 | - | // line apart in the one expression, and a binding is always a key and | |
| 286 | - | // a label, so there is no cell here that appears only sometimes. | |
| 287 | - | // | |
| 288 | - | // No `more`: every key that is bound is listed, which is the whole | |
| 289 | - | // claim of this screen. A shortcuts table with something withheld | |
| 290 | - | // would be the drift it exists to end. | |
| 291 | - | sections.push(Node::from( | |
| 292 | - | Table::new(vec![Column::new("Key"), Column::new("Does")]).rows(bindings.iter().map( | |
| 293 | - | |binding| { | |
| 294 | - | Cells::new(vec![Cell::new(&binding.key), Cell::new(&binding.label)]) | |
| 295 | - | .activate(binding.action.clone()) | |
| 296 | - | }, | |
| 297 | - | )), | |
| 298 | - | )); | |
| 299 | 287 | } | |
| 300 | - | sections | |
| 301 | 288 | } | |
| 302 | 289 | ||
| 303 | - | /// What the app does, in prose. | |
| 304 | - | /// | |
| 305 | - | /// One `Node::Rich` rather than nine headings and nine paragraphs, because it is | |
| 306 | - | /// a document: markdown source is what `Rich` carries and every renderer turns | |
| 307 | - | /// it into its own markup, which is the member's whole argument. The shipped tab | |
| 308 | - | /// builds the same thing out of `ui.heading` and `ui.label` calls, so the | |
| 309 | - | /// structure is there and is not written down anywhere a renderer can read. | |
| 310 | - | fn features() -> Node { | |
| 311 | - | Node::rich(FEATURES_MD) | |
| 290 | + | declare! { | |
| 291 | + | /// Every key that works, read off the one table. | |
| 292 | + | /// | |
| 293 | + | /// No filter box. The shipped tab has one because twenty-six rows in a | |
| 294 | + | /// fixed-height scroll area need it; narrowing a list a screen was handed | |
| 295 | + | /// is what a host does, which is the rule the bulk port's tag completions | |
| 296 | + | /// and folder filter both follow. | |
| 297 | + | /// | |
| 298 | + | /// A heading and a table per group, in the order the app bound them. Not | |
| 299 | + | /// sorted here: the reading order is the shipped tab's and belongs to | |
| 300 | + | /// whoever wrote the table. Several tables rather than one with a group | |
| 301 | + | /// column, because a group is a heading and not a value: a column repeating | |
| 302 | + | /// "Toggles" six times says the same thing six times and is still one wall. | |
| 303 | + | /// | |
| 304 | + | /// Positional cells, because a binding is always a key and a label and | |
| 305 | + | /// there is no cell here that appears only sometimes. No `more`: every key | |
| 306 | + | /// that is bound is listed, which is the whole claim of this screen. A | |
| 307 | + | /// shortcuts table with something withheld would be the drift it exists to | |
| 308 | + | /// end. | |
| 309 | + | shape shortcuts(bound: &[Bound<'_>]) -> Vec<Node>; | |
| 310 | + | ||
| 311 | + | for group in bound { | |
| 312 | + | for &name in group.name.iter() { | |
| 313 | + | section name; | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | table { | |
| 317 | + | column "Key"; | |
| 318 | + | column "Does"; | |
| 319 | + | ||
| 320 | + | for binding in group.keys.iter() { | |
| 321 | + | cells { | |
| 322 | + | cell &binding.key; | |
| 323 | + | cell &binding.label; | |
| 324 | + | activate to doing binding.action.clone(); | |
| 325 | + | } | |
| 326 | + | } | |
| 327 | + | } | |
| 328 | + | } | |
| 329 | + | } | |
| 330 | + | ||
| 331 | + | declare! { | |
| 332 | + | /// What the app does, in prose. | |
| 333 | + | /// | |
| 334 | + | /// One `Node::Rich` rather than nine headings and nine paragraphs, because | |
| 335 | + | /// it is a document: markdown source is what `Rich` carries and every | |
| 336 | + | /// renderer turns it into its own markup, which is the member's whole | |
| 337 | + | /// argument. The shipped tab builds the same thing out of `ui.heading` and | |
| 338 | + | /// `ui.label` calls, so the structure is there and is not written down | |
| 339 | + | /// anywhere a renderer can read. | |
| 340 | + | shape features() -> Node; | |
| 341 | + | ||
| 342 | + | rich FEATURES_MD; | |
| 312 | 343 | } | |
| 313 | 344 | ||
| 314 | 345 | /// The features tab, as the document it is. |