max / quasi
6 files changed,
+134 insertions,
-21 deletions
| @@ -1730,6 +1730,10 @@ | |||
| 1730 | 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1731 | 1731 | checksum = "95b7ccafc965eb90f308b68d6fb00f6ff642a453b661f4c452cb20aeb60e3990" | |
| 1732 | 1732 | ||
| 1733 | + | [[package]] | |
| 1734 | + | name = "makeover-layout" | |
| 1735 | + | version = "0.10.0" | |
| 1736 | + | ||
| 1733 | 1737 | [[package]] | |
| 1734 | 1738 | name = "makeover-touch" | |
| 1735 | 1739 | version = "0.5.0" | |
| @@ -1737,17 +1741,15 @@ | |||
| 1737 | 1741 | checksum = "f3228b2d380252585ab2fadd6146229bb17ba51fab1db5d8e6001d7e0e40fbb4" | |
| 1738 | 1742 | dependencies = [ | |
| 1739 | 1743 | "makeover-geometry", | |
| 1740 | - | "makeover-layout", | |
| 1744 | + | "makeover-layout 0.9.0", | |
| 1741 | 1745 | ] | |
| 1742 | 1746 | ||
| 1743 | 1747 | [[package]] | |
| 1744 | 1748 | name = "makeover-webview" | |
| 1745 | - | version = "0.18.0" | |
| 1746 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1747 | - | checksum = "b0bec4e3395087aa3777bea4aa559d1b90d53e170ae2012c77095b29be16a1c2" | |
| 1749 | + | version = "0.19.0" | |
| 1748 | 1750 | dependencies = [ | |
| 1749 | 1751 | "makeover-geometry", | |
| 1750 | - | "makeover-layout", | |
| 1752 | + | "makeover-layout 0.10.0", | |
| 1751 | 1753 | "makeover-touch", | |
| 1752 | 1754 | ] | |
| 1753 | 1755 | ||
| @@ -2368,7 +2370,7 @@ | |||
| 2368 | 2370 | name = "quasi-router" | |
| 2369 | 2371 | version = "0.1.0" | |
| 2370 | 2372 | dependencies = [ | |
| 2371 | - | "makeover-layout", | |
| 2373 | + | "makeover-layout 0.10.0", | |
| 2372 | 2374 | ] | |
| 2373 | 2375 | ||
| 2374 | 2376 | [[package]] | |
| @@ -2386,7 +2388,7 @@ | |||
| 2386 | 2388 | name = "quasi-webview" | |
| 2387 | 2389 | version = "0.1.0" | |
| 2388 | 2390 | dependencies = [ | |
| 2389 | - | "makeover-layout", | |
| 2391 | + | "makeover-layout 0.10.0", | |
| 2390 | 2392 | "makeover-webview", | |
| 2391 | 2393 | "quasi-http", | |
| 2392 | 2394 | "quasi-router", | |
| @@ -4531,7 +4533,15 @@ | |||
| 4531 | 4533 | checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" | |
| 4532 | 4534 | ||
| 4533 | 4535 | [[patch.unused]] | |
| 4534 | - | name = "docengine" | |
| 4536 | + | name = "kberg" | |
| 4537 | + | version = "0.1.0" | |
| 4538 | + | ||
| 4539 | + | [[patch.unused]] | |
| 4540 | + | name = "painhours" | |
| 4541 | + | version = "0.1.0" | |
| 4542 | + | ||
| 4543 | + | [[patch.unused]] | |
| 4544 | + | name = "tagtree" | |
| 4535 | 4545 | version = "0.4.0" | |
| 4536 | 4546 | ||
| 4537 | 4547 | [[patch.unused]] | |
| @@ -4543,13 +4553,5 @@ | |||
| 4543 | 4553 | version = "0.2.0" | |
| 4544 | 4554 | ||
| 4545 | 4555 | [[patch.unused]] | |
| 4546 | - | name = "kberg" | |
| 4547 | - | version = "0.1.0" | |
| 4548 | - | ||
| 4549 | - | [[patch.unused]] | |
| 4550 | - | name = "painhours" | |
| 4551 | - | version = "0.1.0" | |
| 4552 | - | ||
| 4553 | - | [[patch.unused]] | |
| 4554 | - | name = "tagtree" | |
| 4556 | + | name = "docengine" | |
| 4555 | 4557 | version = "0.4.0" |
| @@ -13,4 +13,4 @@ | |||
| 13 | 13 | workspace = true | |
| 14 | 14 | ||
| 15 | 15 | [dependencies] | |
| 16 | - | makeover-layout = "0.9" | |
| 16 | + | makeover-layout = "0.10.0" |
| @@ -15,5 +15,5 @@ | |||
| 15 | 15 | [dependencies] | |
| 16 | 16 | quasi-router = { path = "../quasi-router", version = "0.1.0" } | |
| 17 | 17 | quasi-http = { path = "../quasi-http", version = "0.1.0" } | |
| 18 | - | makeover-layout = "0.9" | |
| 19 | - | makeover-webview = "0.18" | |
| 18 | + | makeover-layout = "0.10.0" | |
| 19 | + | makeover-webview = "0.19.0" |
| @@ -857,6 +857,34 @@ | |||
| 857 | 857 | /// [`Self::SELECTED`]. | |
| 858 | 858 | action: Option<Action>, | |
| 859 | 859 | }, | |
| 860 | + | /// How much of a set is done. | |
| 861 | + | /// | |
| 862 | + | /// Against `makeover-layout`'s [`layout::Meter`], which arrived at 0.10.0 | |
| 863 | + | /// for this. Before it, a screen with a progress bar concatenated the two | |
| 864 | + | /// numbers into its heading — "Subtasks 3/7" — which keeps both facts and | |
| 865 | + | /// loses the reading, the same way a toned status badge read as prose before | |
| 866 | + | /// [`Row::tokens`]. | |
| 867 | + | /// | |
| 868 | + | /// Owned rather than borrowed for the reason every node here is, and | |
| 869 | + | /// carrying the pair rather than a percentage for the reason | |
| 870 | + | /// [`layout::Meter`] gives: a bar that is full because it landed exactly and | |
| 871 | + | /// one that is full because it ran over are the same width and not the same | |
| 872 | + | /// fact. | |
| 873 | + | /// | |
| 874 | + | /// This is a proportion of a set and not the progress of an operation. A | |
| 875 | + | /// running timer or a fetch is imperative and live, and a screen is | |
| 876 | + | /// described once per answer; [`layout::Readiness::Pending`] and a | |
| 877 | + | /// [`layout::Notice::Toast`] are what those get. | |
| 878 | + | Meter { | |
| 879 | + | /// How much is done. May exceed [`total`](Self::Meter::total). | |
| 880 | + | done: u32, | |
| 881 | + | /// How much there is to do. | |
| 882 | + | total: u32, | |
| 883 | + | /// What the proportion means. No renderer can derive this. | |
| 884 | + | tone: layout::Tone, | |
| 885 | + | /// What is being counted: "subtasks", "tasks". The noun, not the ratio. | |
| 886 | + | label: Option<String>, | |
| 887 | + | }, | |
| 860 | 888 | /// A region inside a region. | |
| 861 | 889 | Region(Slot), | |
| 862 | 890 | } | |
| @@ -922,6 +950,40 @@ | |||
| 922 | 950 | rows: rows.into_iter().collect(), | |
| 923 | 951 | } | |
| 924 | 952 | } | |
| 953 | + | ||
| 954 | + | /// A proportion of a set, untoned and unlabelled. | |
| 955 | + | pub const fn meter(done: u32, total: u32) -> Self { | |
| 956 | + | Self::Meter { | |
| 957 | + | done, | |
| 958 | + | total, | |
| 959 | + | tone: layout::Tone::Neutral, | |
| 960 | + | label: None, | |
| 961 | + | } | |
| 962 | + | } | |
| 963 | + | ||
| 964 | + | /// Read a [`Self::Meter`] as the description layer's own type. | |
| 965 | + | /// | |
| 966 | + | /// A callback rather than a return, for the reason [`Field::with_layout`] | |
| 967 | + | /// is one: [`layout::Meter`] borrows its label and ours owns it. `None` for | |
| 968 | + | /// every other member, so a renderer matching on the node can ask without | |
| 969 | + | /// first proving what it has. | |
| 970 | + | pub fn with_meter<R>(&self, f: impl FnOnce(layout::Meter<'_>) -> R) -> Option<R> { | |
| 971 | + | let Self::Meter { | |
| 972 | + | done, | |
| 973 | + | total, | |
| 974 | + | tone, | |
| 975 | + | label, | |
| 976 | + | } = self | |
| 977 | + | else { | |
| 978 | + | return None; | |
| 979 | + | }; | |
| 980 | + | Some(f(layout::Meter { | |
| 981 | + | done: *done, | |
| 982 | + | total: *total, | |
| 983 | + | tone: *tone, | |
| 984 | + | label: label.as_deref(), | |
| 985 | + | })) | |
| 986 | + | } | |
| 925 | 987 | } | |
| 926 | 988 | ||
| 927 | 989 | /// A whole screen. |
| @@ -25,6 +25,7 @@ | |||
| 25 | 25 | use makeover_webview::Emit; | |
| 26 | 26 | use makeover_webview::form::{Filling, Markup, Value, escape, field_html}; | |
| 27 | 27 | use makeover_webview::list::{Cell, cells_html}; | |
| 28 | + | use makeover_webview::meter::meter_html; | |
| 28 | 29 | use quasi_router::screen::{Act, Cells, Destination, Node, Row, Slot, Tag}; | |
| 29 | 30 | use quasi_router::{Action, Method, Params}; | |
| 30 | 31 | ||
| @@ -488,6 +489,16 @@ | |||
| 488 | 489 | out, | |
| 489 | 490 | ), | |
| 490 | 491 | ||
| 492 | + | // The trough and its tones are makeover-webview's, unchanged, for the | |
| 493 | + | // same reason the field markup is: a second emitter here would be the | |
| 494 | + | // same anatomy with a different escaping story, and the CSS it has to | |
| 495 | + | // match is emitted there too. | |
| 496 | + | node @ Node::Meter { .. } => { | |
| 497 | + | if let Some(html) = node.with_meter(|m| meter_html(&m, opts)) { | |
| 498 | + | out.push_str(&html); | |
| 499 | + | } | |
| 500 | + | } | |
| 501 | + | ||
| 491 | 502 | Node::Region(slot) => slot_html(slot, morphs, opts, out), | |
| 492 | 503 | } | |
| 493 | 504 | } |
| @@ -12,7 +12,7 @@ | |||
| 12 | 12 | use quasi_router::screen::{Act, Cells, Choice, Column, Field, Row, Tag}; | |
| 13 | 13 | use quasi_router::{Action, Node, RegionKind, Screen, Slot}; | |
| 14 | 14 | ||
| 15 | - | use crate::{Shell, Webview}; | |
| 15 | + | use crate::{Emit, Shell, Webview}; | |
| 16 | 16 | ||
| 17 | 17 | fn render(screen: &Screen) -> String { | |
| 18 | 18 | Webview::new().screen(screen) | |
| @@ -261,6 +261,44 @@ | |||
| 261 | 261 | assert!(!html.contains("<script>")); | |
| 262 | 262 | } | |
| 263 | 263 | ||
| 264 | + | #[test] | |
| 265 | + | fn a_meter_renders_through_makeover_and_not_through_a_second_emitter() { | |
| 266 | + | // The rule this crate is held to: the markup and the CSS that has to match | |
| 267 | + | // it are both makeover-webview's. If this ever diverges, the trough and its | |
| 268 | + | // fill stop agreeing about which classes exist. | |
| 269 | + | let html = fragment(&Node::Meter { | |
| 270 | + | done: 3, | |
| 271 | + | total: 7, | |
| 272 | + | tone: layout::Tone::Success, | |
| 273 | + | label: Some("subtasks".into()), | |
| 274 | + | }); | |
| 275 | + | assert_eq!( | |
| 276 | + | html, | |
| 277 | + | makeover_webview::meter::meter_html( | |
| 278 | + | &layout::Meter::new(3, 7) | |
| 279 | + | .tone(layout::Tone::Success) | |
| 280 | + | .label("subtasks"), | |
| 281 | + | &Emit::default(), | |
| 282 | + | ) | |
| 283 | + | ); | |
| 284 | + | assert!(html.contains(r#"aria-label="3 of 7 subtasks""#)); | |
| 285 | + | } | |
| 286 | + | ||
| 287 | + | #[test] | |
| 288 | + | fn a_meter_that_ran_over_says_so_after_the_width_is_clamped() { | |
| 289 | + | // The finding this member closed. "45m tracked / 30m est" was heading text | |
| 290 | + | // because nothing named a bar; a bar alone would report it as exactly full. | |
| 291 | + | let html = fragment(&Node::Meter { | |
| 292 | + | done: 45, | |
| 293 | + | total: 30, | |
| 294 | + | tone: layout::Tone::Danger, | |
| 295 | + | label: Some("minutes".into()), | |
| 296 | + | }); | |
| 297 | + | assert!(html.contains("width: 100%")); | |
| 298 | + | assert!(html.contains(r#"data-over="true""#)); | |
| 299 | + | assert!(html.contains(r#"aria-label="45 of 30 minutes""#)); | |
| 300 | + | } | |
| 301 | + | ||
| 264 | 302 | #[test] | |
| 265 | 303 | fn a_badge_is_not_a_button_and_a_chip_is() { | |
| 266 | 304 | let badge = fragment(&Node::Token(Tag { |