Party mode

This commit is contained in:
2023-12-03 17:52:38 +01:00
parent f7ec98f8e3
commit d001ce4567
12 changed files with 697 additions and 393 deletions

View File

@ -1,13 +1,13 @@
use crate::components::color_picker::{ColorPicker, ColorPickerMsg};
use crate::css::C;
use chrono::{NaiveTime, Weekday};
use common::{BulbGroup, BulbGroupShape, BulbMap, ClientMessage, ServerMessage};
use common::{BulbGroup, BulbGroupShape, BulbMap, BulbPrefs, ClientMessage, Param, ServerMessage};
use lighter_lib::{BulbId, BulbMode};
use seed::{attrs, button, div, h2, input, table, td, tr, C};
use seed::{attrs, button, div, empty, h2, input, table, td, tr, C};
use seed::{prelude::*, IF};
use seed_router::Page;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::collections::{BTreeMap, HashSet};
use std::fmt::Write;
use std::iter::repeat;
/// /lights page
#[derive(Default)]
@ -28,7 +28,7 @@ pub struct Model {
#[derive(Default, Clone)]
struct BulbState {
mode: BulbMode,
wake_schedule: HashMap<Weekday, NaiveTime>,
prefs: BTreeMap<String, BulbPrefs>,
}
#[derive(Debug)]
@ -39,7 +39,13 @@ pub enum Msg {
DeselectGroups,
ColorPicker(ColorPickerMsg),
SetBulbPower(bool),
LightTime(String, Weekday),
/// Set a script parameter value for all selected bulbs.
SetParam {
script: String,
name: String,
value: Param,
},
}
impl Page for Model {
@ -58,11 +64,11 @@ impl Page for Model {
ServerMessage::BulbState {
id,
mode: new_mode,
wake_schedule,
prefs,
} => {
*self.bulb_states.entry(id).or_default() = BulbState {
mode: new_mode,
wake_schedule,
prefs,
};
//color_picker.set_color(mode.color);
@ -124,26 +130,25 @@ impl Page for Model {
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);
});
}
Msg::SetParam {
script,
name,
value,
} => {
self.for_selected_bulbs(|id, bulb| {
bulb.prefs
.get_mut(&script)
.unwrap() //TOD
.kvs
.insert(name.clone(), value.clone());
let message = ClientMessage::SetBulbPref {
bulb: id.clone(),
script: script.clone(),
name: name.clone(),
value: value.clone(),
};
orders.notify(message);
});
}
}
}
@ -230,20 +235,44 @@ impl Page for Model {
.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))
]],
]
let script_param = |script: &str, name: &str, value: &Param| {
let name = name.to_string();
let script = script.to_string();
match value {
Param::String(value) => tr![
C![C.pref_line],
td![&name],
td![input![
C![C.pref_input],
attrs! {At::Placeholder => &script},
attrs! {At::Value => value},
input_ev(Ev::Input, move |input| Msg::SetParam {
script,
name,
value: Param::String(input),
})
]]
],
&Param::Toggle(value) => {
tr![
C![C.pref_line],
button![
if value {
C![C.pref_button_enabled]
} else {
C![C.pref_button]
},
&name,
input_ev(Ev::Click, move |_| Msg::SetParam {
script,
name,
value: Param::Toggle(!value),
})
]
]
}
}
};
div![
@ -278,30 +307,35 @@ impl Page for Model {
],
],
div![
C![C.calendar_box],
C![C.prefs_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),
],
h2!["Settings"],
if let Some(selected_bulb) = selected_bulb {
table![selected_bulb
.prefs
.iter()
.flat_map(|(script, prefs)| repeat(script).zip(prefs.kvs.iter()))
.map(|(script, (name, value))| script_param(script, name, value))]
} else {
empty![]
},
],
]
}
}
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));
fn for_selected_bulbs(&mut self, mut f: impl FnMut(&BulbId, &mut BulbState)) {
for &index in &self.selected_groups {
let Some(group) = self.bulb_map.groups.get(index) else {
continue;
};
for id in group.bulbs.iter() {
if let Some(bulb) = self.bulb_states.get_mut(id) {
f(id, bulb);
}
}
}
}
}

View File

@ -304,14 +304,16 @@ body {
transition: margin 0.1s ease-out;
}
.calendar_day {
.pref_line {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: .3em;
}
.calendar_time_input {
.pref_input {
background: #453f4b;
border: solid 0.35em #5b3f63;
border-radius: .3em;
@ -323,17 +325,49 @@ body {
margin-left: .5em;
}
.calendar_box {
.pref_button {}
.pref_button, .pref_button_enabled {
position: relative;
width: 100%;
font-size: large;
font-weight: bold;
color: white;
text-shadow: 0.1rem 0.1rem 0.3rem black;
padding: 1rem;
border: solid 0.35em #5b3f63;
border-radius: 0.3em;
background: transparent;
overflow: hidden;
}
.pref_button_enabled::before {
content: "";
z-index: -1;
width: 20rem;
height: 20rem;
background-size: 100% 100%;
background-image: url(/images/hsb.png);
position: absolute;
transform: translate(-8.5rem, -2.5rem);
animation: infinite linear 3s button_rainbow;
}
@keyframes button_rainbow {
from { transform: translate(-8.5rem, -2.5rem) rotate( 0deg); }
to { transform: translate(-8.5rem, -2.5rem) rotate(360deg); }
}
.prefs_box {
display: flex;
justify-content: center;
}
.calendar_box > h2 {
.prefs_box > h2 {
writing-mode: sideways-lr;
margin-top: auto;
margin-bottom: auto;
}
.calendar_box > * {
.prefs_box > * {
flex-shrink: 1;
}