diff --git a/dev-proxy/index.ts b/dev-proxy/index.ts index 9f37cfa..841cd51 100644 --- a/dev-proxy/index.ts +++ b/dev-proxy/index.ts @@ -246,7 +246,7 @@ function handleApiMessage(e: MessageEvent) { return; } - console.log("API recv", message.type); + console.log("API recv", message); if (message.type !== "device_event") return; const socket = sockets.get(message.deviceId); if (!socket) { @@ -274,6 +274,7 @@ function handleApiMessage(e: MessageEvent) { if (event.type === "party_status") { const quizData = event.party?.data ?? null; + console.log("API quizData", quizData); if (!quizData) return; if (quizData.status === "results") { writeProxyOutput(socket, "Results"); diff --git a/device-state/src/lib.rs b/device-state/src/lib.rs index 4fd66a8..c828671 100644 --- a/device-state/src/lib.rs +++ b/device-state/src/lib.rs @@ -38,26 +38,26 @@ pub struct QuestionData { } #[derive(Deserialize)] -pub struct QuestionDataNet<'a> { - pub text: &'a str, +pub struct QuestionDataNet { + pub text: String, pub q_type: QuestionType, pub points: i32, pub index: usize, } #[derive(Deserialize)] -pub enum ProxyOutput<'a> { - ConnectPrompt(&'a str), - WaitingForParty(&'a str), - Question(QuestionDataNet<'a>), +pub enum ProxyOutput { + ConnectPrompt(String), + WaitingForParty(String), + Question(QuestionDataNet), Results, - Error(&'a str), + Error(String), } -impl<'a> From> for QuestionData { - fn from(value: QuestionDataNet<'a>) -> Self { +impl From for QuestionData { + fn from(value: QuestionDataNet) -> Self { QuestionData { - text: OwnedStr::from_str(value.text).unwrap(), + text: OwnedStr::from_str(value.text.as_str()).unwrap(), q_type: value.q_type, points: value.points, index: value.index, @@ -135,7 +135,7 @@ impl DeviceState { &mut self.wheel } - pub fn apply_proxy_output(&mut self, data: ProxyOutput<'_>) { + pub fn apply_proxy_output(&mut self, data: ProxyOutput) { match data { ProxyOutput::ConnectPrompt(device_id) => { let mut owned_device_id = OwnedStr::new(); @@ -161,11 +161,32 @@ impl DeviceState { } ProxyOutput::Question(data) => { let data: QuestionData = data.into(); - let mut future_wheel = WheelData::empty(); - if let QuestionType::Numeric { min, max } = data.q_type { - future_wheel.max = max; - future_wheel.min = min; - future_wheel.value = (min + max) / 2; + let same_question = self + .question + .as_ref() + .is_some_and(|question| question.index == data.index); + let mut future_wheel = if same_question { + self.wheel + } else { + WheelData::empty() + }; + match data.q_type { + QuestionType::Numeric { min, max } => { + future_wheel.max = max; + future_wheel.min = min; + if same_question { + future_wheel.value = future_wheel.value.clamp(min, max); + } else { + future_wheel.value = (min + max) / 2; + } + } + QuestionType::Choice => { + future_wheel = WheelData::empty(); + } + } + if !same_question { + self.last_index = data.index; + self.title_offset = 0; } self.question = Some(data); self.view = ViewState::Question; @@ -235,10 +256,7 @@ impl DeviceState { break; } } - return Some(( - display_name, - OwnedStr::from_str("Awaiting party").unwrap(), - )); + return Some((display_name, OwnedStr::from_str("Awaiting party").unwrap())); } if self.view == ViewState::Results { @@ -255,7 +273,10 @@ impl DeviceState { let question = self.question.as_ref()?; let title_line = if question.text.len() > 16 { // overscroll, should show spaces after the end - self.title_offset %= question.text.len() - 31; + let scroll_len = question.text.len().saturating_sub(15); + if scroll_len > 0 { + self.title_offset %= scroll_len; + } let end = usize::min(self.title_offset + 16, question.text.len()); OwnedStr::from_str(&question.text[self.title_offset..end]).unwrap() } else { @@ -306,8 +327,8 @@ pub enum WriteType<'a> { DeviceId(&'a str), } -pub fn parse_proxy_output<'a>(input: &'a str) -> Result, serde_json::Error> { - serde_json::from_str::>(input) +pub fn parse_proxy_output(input: &str) -> Result { + serde_json::from_str::(input) } pub fn serialize_write(data: &WriteType<'_>) -> Result { @@ -453,6 +474,67 @@ mod tests { assert!(state.question().is_none()); } + #[test] + fn parses_numeric_question_with_escaped_quotes() { + let data = parse_proxy_output( + r#"{"Question":{"text":"How many players have \"Porter Robinson\" as a favourite artist?","points":10,"index":2,"q_type":{"Numeric":{"min":0,"max":2}}}}"#, + ) + .unwrap(); + let mut state = DeviceState::new(); + + state.apply_proxy_output(data); + + let question = state.question().unwrap(); + assert_eq!(question.index, 2); + assert_eq!( + question.text.as_str(), + "How many players have \"Porter Robinson\" as a favourite artist?", + ); + assert_eq!(state.wheel().value, 1); + } + + #[test] + fn preserves_numeric_wheel_for_same_question_updates() { + let mut state = DeviceState::new(); + state.apply_proxy_output( + parse_proxy_output( + r#"{"Question":{"text":"Q","points":10,"index":2,"q_type":{"Numeric":{"min":0,"max":10}}}}"#, + ) + .unwrap(), + ); + state.wheel_mut().value = 7; + + state.apply_proxy_output( + parse_proxy_output( + r#"{"Question":{"text":"Q updated","points":10,"index":2,"q_type":{"Numeric":{"min":0,"max":10}}}}"#, + ) + .unwrap(), + ); + + assert_eq!(state.wheel().value, 7); + } + + #[test] + fn resets_numeric_wheel_for_new_questions() { + let mut state = DeviceState::new(); + state.apply_proxy_output( + parse_proxy_output( + r#"{"Question":{"text":"Q","points":10,"index":2,"q_type":{"Numeric":{"min":0,"max":10}}}}"#, + ) + .unwrap(), + ); + state.wheel_mut().value = 7; + + state.apply_proxy_output( + parse_proxy_output( + r#"{"Question":{"text":"Next","points":10,"index":3,"q_type":{"Numeric":{"min":0,"max":10}}}}"#, + ) + .unwrap(), + ); + + assert_eq!(state.wheel().value, 5); + } + #[test] fn parses_and_renders_waiting_for_party() { let data = parse_proxy_output(r#"{"WaitingForParty":"User"}"#).unwrap(); diff --git a/esp32/src/main.rs b/esp32/src/main.rs index a2025a8..1a8f15b 100644 --- a/esp32/src/main.rs +++ b/esp32/src/main.rs @@ -89,6 +89,7 @@ pub async fn tcp_read_loop( }; if let Some(last) = str.lines().last() { let Ok(data) = device_state::parse_proxy_output(last) else { + println!("parse proxy output failed: {}", last); continue; }; let mut state = STATE.lock().await;