//! The Askama entry point for the two described clip controls, and the place //! Shape 8's ruling is visible in one file. //! //! `b279b9eb` decided that destructiveness is marked separately from asking //! first. Both acts here destroy something and take [`Tone::Danger`]; only one //! of them asks. Deleting a clip from the library removes it from every item //! using it, which is worth stopping for. Removing a clip from one item is the //! undo of the Add sitting three lines below it in the same form, so //! interrupting it would be asking about nothing. //! //! Written together for that reason. The asymmetry is the ruling's proof, and a //! pair described in one pass is deliberate where two passes look like an //! oversight. //! //! # Why each names its region //! //! `DELETE /api/insertions/{id}` and `DELETE /api/item-insertions/{id}` are //! plain API routes the description layer does not serve. Each answers its //! whole list rebuilt, so the control has to say where that lands. See //! [`Action::replaces`]. //! //! Ids arrive as `impl Display` rather than `&str`: the caller is Askama and //! the server's ids are typed newtypes over `Uuid`, so asking for a string //! would push a `to_string()` into every call site to say what `format!` //! already says here. use std::fmt; use makeover_layout::Tone; use quasi_router::screen::Act; use quasi_router::{Action, Node}; /// Delete a clip from the creator's library. /// /// Asks, because the clip is shared: items the reader is not looking at lose /// their pre-roll too, and the prompt is the only place that is said. #[must_use] pub fn delete_html(insertion_id: impl fmt::Display) -> String { fragment( Act::new( "Delete", Action::delete(format!("/api/insertions/{insertion_id}")) .replacing("insertion-library"), ) .tone(Tone::Danger) .confirm("Delete this clip? It will be removed from all items using it."), ) } /// Take one clip off one item. /// /// Danger with no confirm, which is the pair's point. The template already said /// the first half as `btn-danger` to the stylesheet alone; nothing said it to /// any other renderer, and nothing ever asked. #[must_use] pub fn remove_html(placement_id: impl fmt::Display) -> String { fragment( Act::new( "Remove", Action::delete(format!("/api/item-insertions/{placement_id}")) .replacing("placement-list"), ) .tone(Tone::Danger), ) } /// No shell and no region: this lands inside a document Askama has already /// built, which is what every glue module here does. fn fragment(act: Act) -> String { use quasi_axum::Serves as _; quasi_webview::Webview::new().fragment(&Node::Act(act)) } #[cfg(test)] mod tests { #[test] fn deleting_a_clip_names_its_route_its_region_and_its_prompt() { let html = super::delete_html("9"); assert!(html.contains(r#"hx-delete="/api/insertions/9""#), "{html}"); assert!( html.contains(r##"hx-target="#insertion-library""##), "{html}" ); assert!( html.contains( r#"hx-confirm="Delete this clip? It will be removed from all items using it.""# ), "{html}" ); } /// The ruling in one assertion: both destroy, one asks. #[test] fn both_are_dangerous_and_only_one_asks() { let delete = super::delete_html("9"); let remove = super::remove_html("4"); assert!(delete.contains(r#"data-tone="danger""#), "{delete}"); assert!(remove.contains(r#"data-tone="danger""#), "{remove}"); assert!(delete.contains("hx-confirm"), "{delete}"); assert!(!remove.contains("hx-confirm"), "{remove}"); } #[test] fn removing_a_placement_names_its_route_and_its_region() { let html = super::remove_html("4"); assert!( html.contains(r#"hx-delete="/api/item-insertions/4""#), "{html}" ); assert!(html.contains(r##"hx-target="#placement-list""##), "{html}"); } }