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,
9	ui::{
10		Actions, View,
11		html::{self, El},
12	},
13};
14
15/// The form's look, from the protocol book.
16const CSS: &str = ".ask{display:flex;flex-direction:column;gap:8px;max-width:460px;padding:10px \
17                   12px;border-radius:8px;background:var(--card);box-shadow:inset 0 0 0 1px \
18                   var(--l2)} .ask p{margin:0} .ask .sizes{display:flex;gap:14px} .ask \
19                   :checked+span{color:var(--accent)} .ask \
20                   button{align-self:flex-start;padding:3px \
21                   12px;border-radius:6px;background:var(--accent-fill);color:#fff}";
22
23/// The sizes: value and label.
24const SIZES: [(&str, &str); 3] = [("s", "Small"), ("m", "Medium"), ("l", "Large")];
25
26/// One radio with its label.
27fn choice(value: &str, label: &str) -> El<()> {
28	html::label()
29		.key(value)
30		.child(html::radio("size", value).key("r"))
31		.child(html::span(label).key("t"))
32}
33
34/// The form.
35fn view() -> View<()> {
36	let sizes = SIZES.iter().map(|(value, label)| choice(value, label));
37	View::new().main([html::form()
38		.key("ask")
39		.class("ask")
40		.child(html::p("Which size?").key("q"))
41		.child(html::div().key("sizes").class("sizes").children(sizes))
42		.child(
43			html::button("Create")
44				.key("go")
45				.actions(Actions::new().click("submit")),
46		)])
47}
48
49/// The label of size `value`.
50fn label(value: &str) -> &str {
51	SIZES
52		.iter()
53		.find(|(v, _)| *v == value)
54		.map_or(value, |(_, l)| l)
55}
56
57/// Asks on the command line when TSP isn't available.
58fn ask_plainly() -> io::Result<()> {
59	print!("Which size? [s/m/l] ");
60	io::stdout().flush()?;
61	let mut line = String::new();
62	if io::stdin().lock().read_line(&mut line)? == 0 {
63		println!("\nNo size picked.");
64		return Ok(());
65	}
66	match line.trim() {
67		v @ ("s" | "m" | "l") => println!("Size: {} ({v})", label(v)),
68		_ => println!("No size picked."),
69	}
70	Ok(())
71}
72
73fn main() -> Result<(), tern_sdk::Error> {
74	let answer = tern_sdk::ask(view(), AskOptions::new().css(CSS))?;
75	match answer {
76		Answer::Submitted(submission) => {
77			let size = submission.values.get("size").and_then(|v| v.as_str());
78			match size {
79				Some(v) => println!("Size: {} ({v})", label(v)),
80				None => println!("No size picked."),
81			}
82		},
83		Answer::Cancelled => println!("Cancelled."),
84		Answer::Unsupported => ask_plainly()?,
85	}
86	Ok(())
87}