//! 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}");
}
}