Skip to main content

max / makenotwork

3.9 KB · 103 lines History Blame Raw
1 //! The Askama entry point for `quasi_basics::carousel`.
2 //!
3 //! The widget left for `quasi-basics` once three real pages had proved it, and
4 //! this is the part that stayed: glue between a template's own frame type and
5 //! the widget's. Called from `partials/carousel.html`, so all three call sites
6 //! keep the macro they already have.
7 //!
8 //! Why the island wraps the region rather than being it: a custom element is
9 //! what the browser re-upgrades after an htmx swap, which is the whole reason
10 //! islands are custom elements, and the description emits a plain `<div>`
11 //! because it has no idea this host prefixes its tags with `mnw-`. The template
12 //! puts one around the other and both halves stay honest.
13
14 use quasi_basics::{Frame, Gallery};
15 use quasi_declare::declare;
16
17 use crate::templates::CarouselFrame;
18
19 /// One template frame as the widget's own.
20 ///
21 /// The conversion, and the whole of what stayed in MNW. Written as two matches
22 /// rather than a `let mut` and an `if let` because a description reads a value
23 /// and a builder that reassigns itself is not one, which is the same reason
24 /// [`gallery`] is a supplier and not part of the declaration below.
25 fn frame(source: &CarouselFrame) -> Frame {
26 let sized = match source.intrinsic {
27 Some((width, height)) => Frame::new(&source.image, &source.alt).intrinsic(width, height),
28 None => Frame::new(&source.image, &source.alt),
29 };
30 match &source.caption {
31 Some(caption) => sized.caption(caption),
32 None => sized,
33 }
34 }
35
36 /// The template's frames as the widget's read.
37 ///
38 /// [`Gallery::new`] is where the first frame becomes the one on screen, so this
39 /// is the one place the order is looked at. Everything after it reads a fact.
40 fn gallery(items: &[CarouselFrame]) -> Gallery {
41 items.iter().map(frame).collect()
42 }
43
44 declare! {
45 /// The widget, for a screen that owns its whole document.
46 ///
47 /// [`html`] is the Askama half and this is the described half. Both come out
48 /// of `quasi_basics::carousel`, so the project page's gallery and the three
49 /// templates that still call the macro cannot be two different carousels.
50 #[must_use]
51 pub shape region(id: &str, items: &[CarouselFrame]) -> Node;
52
53 include quasi_basics::carousel(id, &gallery(items));
54 }
55
56 /// The markup, for an Askama template to drop in.
57 #[must_use]
58 pub fn html(id: &str, items: &[CarouselFrame]) -> String {
59 use quasi_axum::Serves as _;
60
61 let node = region(id, items);
62
63 // No shell: this is a fragment landing inside a document Askama already
64 // built, which is exactly what `fragment` is for.
65 quasi_webview::Webview::new().fragment(&node)
66 }
67
68 #[cfg(test)]
69 mod tests {
70 use crate::templates::CarouselFrame;
71
72 /// What the widget guarantees is `quasi-basics`' to test, and it does.
73 /// What is MNW's is that this conversion loses nothing on the way through,
74 /// because it is the one place a template's frame becomes a widget's.
75 #[test]
76 fn a_template_frame_arrives_whole() {
77 let html = super::html(
78 "landing-shots",
79 &[
80 CarouselFrame {
81 image: "/a.png".into(),
82 alt: "The library, mid-import".into(),
83 caption: Some("Library".into()),
84 intrinsic: Some((5120, 3412)),
85 },
86 CarouselFrame {
87 image: "/b.png".into(),
88 alt: "A project page".into(),
89 caption: None,
90 intrinsic: None,
91 },
92 ],
93 );
94
95 assert!(html.contains(r#"data-widget="carousel""#), "{html}");
96 assert!(html.contains(r#"id="landing-shots""#), "{html}");
97 assert!(html.contains(r#"alt="The library, mid-import""#), "{html}");
98 assert!(html.contains("Library</figcaption>"), "{html}");
99 assert!(html.contains(r#"width="5120" height="3412""#), "{html}");
100 assert!(html.contains("/b.png"), "{html}");
101 }
102 }
103