//! The Askama entry point for `quasi_basics::carousel`. //! //! The widget left for `quasi-basics` once three real pages had proved it, and //! this is the part that stayed: glue between a template's own frame type and //! the widget's. Called from `partials/carousel.html`, so all three call sites //! keep the macro they already have. //! //! Why the island wraps the region rather than being it: a custom element is //! what the browser re-upgrades after an htmx swap, which is the whole reason //! islands are custom elements, and the description emits a plain `
` //! because it has no idea this host prefixes its tags with `mnw-`. The template //! puts one around the other and both halves stay honest. use quasi_basics::{Frame, Gallery}; use quasi_declare::declare; use crate::templates::CarouselFrame; /// One template frame as the widget's own. /// /// The conversion, and the whole of what stayed in MNW. Written as two matches /// rather than a `let mut` and an `if let` because a description reads a value /// and a builder that reassigns itself is not one, which is the same reason /// [`gallery`] is a supplier and not part of the declaration below. fn frame(source: &CarouselFrame) -> Frame { let sized = match source.intrinsic { Some((width, height)) => Frame::new(&source.image, &source.alt).intrinsic(width, height), None => Frame::new(&source.image, &source.alt), }; match &source.caption { Some(caption) => sized.caption(caption), None => sized, } } /// The template's frames as the widget's read. /// /// [`Gallery::new`] is where the first frame becomes the one on screen, so this /// is the one place the order is looked at. Everything after it reads a fact. fn gallery(items: &[CarouselFrame]) -> Gallery { items.iter().map(frame).collect() } declare! { /// The widget, for a screen that owns its whole document. /// /// [`html`] is the Askama half and this is the described half. Both come out /// of `quasi_basics::carousel`, so the project page's gallery and the three /// templates that still call the macro cannot be two different carousels. #[must_use] pub shape region(id: &str, items: &[CarouselFrame]) -> Node; include quasi_basics::carousel(id, &gallery(items)); } /// The markup, for an Askama template to drop in. #[must_use] pub fn html(id: &str, items: &[CarouselFrame]) -> String { use quasi_axum::Serves as _; let node = region(id, items); // No shell: this is a fragment landing inside a document Askama already // built, which is exactly what `fragment` is for. quasi_webview::Webview::new().fragment(&node) } #[cfg(test)] mod tests { use crate::templates::CarouselFrame; /// What the widget guarantees is `quasi-basics`' to test, and it does. /// What is MNW's is that this conversion loses nothing on the way through, /// because it is the one place a template's frame becomes a widget's. #[test] fn a_template_frame_arrives_whole() { let html = super::html( "landing-shots", &[ CarouselFrame { image: "/a.png".into(), alt: "The library, mid-import".into(), caption: Some("Library".into()), intrinsic: Some((5120, 3412)), }, CarouselFrame { image: "/b.png".into(), alt: "A project page".into(), caption: None, intrinsic: None, }, ], ); assert!(html.contains(r#"data-widget="carousel""#), "{html}"); assert!(html.contains(r#"id="landing-shots""#), "{html}"); assert!(html.contains(r#"alt="The library, mid-import""#), "{html}"); assert!(html.contains("Library"), "{html}"); assert!(html.contains(r#"width="5120" height="3412""#), "{html}"); assert!(html.contains("/b.png"), "{html}"); } }