Skip to main content

ask/
ask.rs

1//! `ask`: the "Which size?" form from the protocol book. In Tern a click on
2//! a radio picks a size and Create submits; the program prints the answer.
3//! Anywhere else it asks on the command line.
4
5use std::io::{self, BufRead as _, Write as _};
6
7use tern_sdk::{
8	Answer, AskOptions, Font,
9	ui::{
10		self, Actions, Bound, Extent, View,
11		html::{self, El},
12	},
13};
14
15/// The sizes: value and label.
16const SIZES: [(&str, &str); 3] = [("s", "Small"), ("m", "Medium"), ("l", "Large")];
17
18/// One radio with its label.
19fn choice(value: &str, label: &str) -> El<()> {
20	html::label()
21		.key(value)
22		.child(html::radio("size", value).key("r"))
23		.child(html::span(label).key("t"))
24}
25
26/// The form: a card of radios and a primary Create button, styled by the
27/// kinds alone.
28fn view() -> View<()> {
29	let sizes = SIZES.iter().map(|(value, label)| choice(value, label));
30	View::new().main([html::form().key("ask").child(
31		ui::card()
32			.key("card")
33			.head("Which size?")
34			.max(Bound::w(Extent::Ch(48.0)))
35			.child(ui::row().key("sizes").gap("lg").children(sizes))
36			.child(
37				ui::button("Create")
38					.key("go")
39					.variant("primary")
40					.actions(Actions::new().click("submit")),
41			),
42	)])
43}
44
45/// The label of size `value`.
46fn label(value: &str) -> &str {
47	SIZES
48		.iter()
49		.find(|(v, _)| *v == value)
50		.map_or(value, |(_, l)| l)
51}
52
53/// Asks on the command line when TSP isn't available.
54fn ask_plainly() -> io::Result<()> {
55	print!("Which size? [s/m/l] ");
56	io::stdout().flush()?;
57	let mut line = String::new();
58	if io::stdin().lock().read_line(&mut line)? == 0 {
59		println!("\nNo size picked.");
60		return Ok(());
61	}
62	match line.trim() {
63		v @ ("s" | "m" | "l") => println!("Size: {} ({v})", label(v)),
64		_ => println!("No size picked."),
65	}
66	Ok(())
67}
68
69fn main() -> Result<(), tern_sdk::Error> {
70	let answer = tern_sdk::ask(view(), AskOptions::new().font(Font::Sans))?;
71	match answer {
72		Answer::Submitted(submission) => {
73			let size = submission.values.get("size").and_then(|v| v.as_str());
74			match size {
75				Some(v) => println!("Size: {} ({v})", label(v)),
76				None => println!("No size picked."),
77			}
78		},
79		Answer::Cancelled => println!("Cancelled."),
80		Answer::Unsupported => ask_plainly()?,
81	}
82	Ok(())
83}