use crate::components::color_picker::{ColorPicker, ColorPickerMsg}; use crate::css::C; use chrono::{NaiveTime, Weekday}; use common::{BulbGroup, BulbGroupShape, BulbMap, ClientMessage, ServerMessage}; use lighter_lib::{BulbId, BulbMode}; use seed::{attrs, button, div, h2, input, table, td, tr, C}; use seed::{prelude::*, IF}; use seed_router::Page; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fmt::Write; /// /lights page #[derive(Default)] pub struct Model { bulb_states: BTreeMap, bulb_map: BulbMap, /// the currently selected bulb map groups selected_groups: HashSet, /// whether the currently selected map groups have been interacted with groups_interacted: bool, color_picker: ColorPicker, } #[derive(Default, Clone)] struct BulbState { mode: BulbMode, wake_schedule: HashMap, } #[derive(Debug)] pub enum Msg { ServerMessage(ServerMessage), SelectGroup(usize), DeselectGroups, ColorPicker(ColorPickerMsg), SetBulbPower(bool), LightTime(String, Weekday), } impl Page for Model { type Msg = Msg; fn new(orders: &mut impl Orders) -> Self { orders.subscribe(Msg::ServerMessage); orders.notify(ClientMessage::GetBulbs); Model::default() } fn update(&mut self, msg: Self::Msg, orders: &mut impl Orders) { match msg { Msg::ServerMessage(msg) => match msg { ServerMessage::BulbState { id, mode: new_mode, wake_schedule, } => { *self.bulb_states.entry(id).or_default() = BulbState { mode: new_mode, wake_schedule, }; //color_picker.set_color(mode.color); } ServerMessage::BulbMap(bulb_map) => { self.bulb_map = bulb_map; self.selected_groups.clear(); } _ => {} }, Msg::DeselectGroups => { self.selected_groups.clear(); } Msg::SelectGroup(index) => { if self.groups_interacted { self.groups_interacted = false; // If this group is the only selected group, don't clear it if self.selected_groups.len() != 1 || !self.selected_groups.contains(&index) { self.selected_groups.clear(); } } if !self.selected_groups.insert(index) { self.selected_groups.remove(&index); } else { let bulb = self .bulb_map .groups .get(index) .and_then(|group| group.bulbs.first()) .and_then(|id| self.bulb_states.get(id)); if let Some(bulb) = bulb { self.color_picker.set_color(bulb.mode.color); } } } Msg::ColorPicker(ColorPickerMsg::SetColor(color)) => { self.groups_interacted = true; self.for_selected_bulbs(|id, _| { let message = ClientMessage::SetBulbColor { id: id.clone(), color, }; orders.notify(message); }); } Msg::ColorPicker(msg) => { self.color_picker .update(msg, &mut orders.proxy(Msg::ColorPicker)); } Msg::SetBulbPower(power) => { self.groups_interacted = true; self.for_selected_bulbs(|id, _| { let message = ClientMessage::SetBulbPower { id: id.clone(), power, }; orders.notify(message); }); } Msg::LightTime(time, day) => { if time.is_empty() { self.for_selected_bulbs(|id, _| { let message = ClientMessage::SetBulbWakeTime { id: id.clone(), day, time: None, }; orders.notify(message); }); } else if let Ok(time) = NaiveTime::parse_from_str(&time, "%H:%M") { self.for_selected_bulbs(|id, _| { let message = ClientMessage::SetBulbWakeTime { id: id.clone(), day, time: Some(time), }; orders.notify(message); }); } } } } fn view(&self) -> Node { //let view_bulb = |(id, (mode, color_picker)): (&BulbId, &(BulbMode, ColorPicker))| { // div![ // C![C.bulb_box], // h1![id], // div![ // C![C.bulb_controls], // { // let id = id.clone(); // color_picker.view().map_msg(|msg| Msg::ColorPicker(id, msg)) // }, // button![ // if mode.power { // C![C.bulb_power_button, C.bulb_power_button_on] // } else { // C![C.bulb_power_button] // }, // { // let id = id.clone(); // let power = !mode.power; // ev(Ev::Click, move |_| Msg::SetBulbPower(id, power)) // }, // ], // ], // ] //}; let bulb_map_width = self .bulb_map .groups .iter() .map(|group| group.x + group.shape.width()) .max() .unwrap_or(0); let bulb_map_height = self .bulb_map .groups .iter() .map(|group| group.y + group.shape.height()) .max() .unwrap_or(0); let view_bulb_group = |(i, group): (usize, &BulbGroup)| { let (w, h) = (group.shape.width(), group.shape.height()); let mut style = String::new(); write!( &mut style, "margin-left: {}rem; margin-top: {}rem; width: {}rem; height: {}rem;", group.x, group.y, w, h ) .ok(); if let BulbGroupShape::Circle { r } = group.shape { write!(&mut style, " border-radius: {r}rem;").ok(); } div![ &group.name[..1], if self.selected_groups.contains(&i) { C![C.bulb_group, C.bulb_group_selected] } else { C![C.bulb_group] }, attrs! { At::Style => style, }, ev(Ev::Click, move |event| { event.stop_propagation(); Msg::SelectGroup(i) }), ] }; let selected_bulb = self .selected_groups .iter() .next() .and_then(|&index| self.bulb_map.groups.get(index)) .and_then(|group| group.bulbs.first()) .and_then(|id| self.bulb_states.get(id)); let calendar_day = |day: Weekday| { let time = selected_bulb .and_then(|b| b.wake_schedule.get(&day)) .map(|t| t.to_string()) .unwrap_or_default(); tr![ C![C.calendar_day], td![day.to_string()], td![input![ C![C.calendar_time_input], attrs! {At::Placeholder => time}, input_ev(Ev::Input, move |input| Msg::LightTime(input, day)) ]], ] }; div![ C![C.bulb_box], div![ C![C.bulb_map], attrs! { At::Style => format!("min-width: {}rem; height: {}rem;", bulb_map_width, bulb_map_height), }, ev(Ev::Click, |_| Msg::DeselectGroups), self.bulb_map.groups.iter().enumerate().map(view_bulb_group), ], div![ C![C.bulb_controls], IF!(selected_bulb.is_none() => C![C.cross_out]), self.color_picker.view().map_msg(Msg::ColorPicker), button![ if selected_bulb.map(|b| b.mode.power).unwrap_or(false) { C![C.bulb_power_button, C.bulb_power_button_on] } else { C![C.bulb_power_button] }, { let target_power = selected_bulb.map(|b| !b.mode.power); ev(Ev::Click, move |_| target_power.map(Msg::SetBulbPower)) }, div![attrs! { At::Id => "switch_socket" }], div![attrs! { At::Id => "off_label" }, "Off"], div![attrs! { At::Id => "on_label" }, "On"], div![attrs! { At::Id => "lever_stem" }], div![attrs! { At::Id => "lever_face" }], ], ], div![ C![C.calendar_box], IF!(selected_bulb.is_none() => C![C.cross_out]), h2!["Wake Schedule"], table![ calendar_day(Weekday::Mon), calendar_day(Weekday::Tue), calendar_day(Weekday::Wed), calendar_day(Weekday::Thu), calendar_day(Weekday::Fri), calendar_day(Weekday::Sat), calendar_day(Weekday::Sun), ], ], ] } } impl Model { fn for_selected_bulbs(&self, mut f: impl FnMut(&BulbId, &BulbState)) { self.selected_groups .iter() .filter_map(|&index| self.bulb_map.groups.get(index)) .flat_map(|group| group.bulbs.iter()) .filter_map(|id| self.bulb_states.get(id).map(|bulb| (id, bulb))) .for_each(|(id, bulb)| f(id, bulb)); } }