maia_wasm/ui/
preferences.rs

1use super::input::InputElement;
2use serde::{Deserialize, Serialize};
3use wasm_bindgen::JsValue;
4use web_sys::{Storage, Window};
5
6const PREFERENCES_KEY: &str = "preferences";
7
8pub struct Preferences {
9    storage: Option<Storage>,
10    data: PreferenceData,
11}
12
13macro_rules! impl_preference_data {
14    {$($name:ident : $ty:ty = $default:expr,)*} => {
15        #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
16        struct PreferenceData {
17            $(
18                $name: $ty,
19            )*
20        }
21
22        impl Default for PreferenceData {
23            fn default() -> Self {
24                Self {
25                    $(
26                        $name: $default,
27                    )*
28                }
29            }
30        }
31
32        impl Preferences {
33            $(
34                paste::paste! {
35                    pub fn [<update_ $name>](&mut self, value: &$ty) -> Result<(), JsValue> {
36                        if (*value != self.data.$name) {
37                            self.data.$name.clone_from(value);
38                            self.store()
39                        } else {
40                            Ok(())
41                        }
42                    }
43                }
44            )*
45        }
46
47        impl Preferences {
48            // pub(super) is here to avoid complaints about leaking the private type
49            // Elements
50            pub(super) fn apply(&self, ui: &super::Ui) -> Result<(), JsValue> {
51                $(
52                    ui.elements.$name.set(&self.data.$name);
53                    if let Some(onchange) = ui.elements.$name.onchange() {
54                        onchange.call0(&JsValue::NULL)?;
55                    }
56                )*
57                Ok(())
58            }
59        }
60    }
61}
62
63impl_preference_data! {
64    colormap_select: super::colormap::Colormap = super::colormap::Colormap::Turbo,
65    waterfall_show_waterfall: bool = true,
66    waterfall_show_spectrum: bool = false,
67    waterfall_show_ddc: bool = true,
68    waterfall_min: f32 = 35.0,
69    waterfall_max: f32 = 85.0,
70    ad9361_rx_lo_frequency: u64 = 2_400_000_000,
71    ad9361_sampling_frequency: u32 = 61_440_000,
72    ad9361_rx_rf_bandwidth: u32 = 56_000_000,
73    ad9361_rx_gain_mode: maia_json::Ad9361GainMode = maia_json::Ad9361GainMode::SlowAttack,
74    ad9361_rx_gain: f64 = 70.0,
75    ddc_frequency: f64 = 0.0,
76    ddc_decimation: u32 = 20,
77    ddc_transition_bandwidth: f64 = 0.05,
78    ddc_passband_ripple: f64 = 0.01,
79    ddc_stopband_attenuation_db: f64 = 60.0,
80    ddc_stopband_one_over_f: bool = true,
81    spectrometer_input: maia_json::SpectrometerInput = maia_json::SpectrometerInput::AD9361,
82    spectrometer_output_sampling_frequency: f64 = 20.0,
83    spectrometer_mode: maia_json::SpectrometerMode = maia_json::SpectrometerMode::Average,
84    recording_metadata_filename: String = "recording".to_string(),
85    recorder_prepend_timestamp: bool = false,
86    recording_metadata_description: String = "".to_string(),
87    recording_metadata_author: String = "".to_string(),
88    recorder_mode: maia_json::RecorderMode = maia_json::RecorderMode::IQ12bit,
89    recorder_maximum_duration: f64 = 0.0,
90    geolocation_watch: bool = false,
91}
92
93impl Preferences {
94    pub fn new(window: &Window) -> Result<Preferences, JsValue> {
95        let storage = window.local_storage()?;
96        let data = match &storage {
97            Some(storage) => match storage.get_item(PREFERENCES_KEY)? {
98                Some(data) => match serde_json::from_str(&data) {
99                    Ok(x) => x,
100                    Err(_) => {
101                        web_sys::console::error_1(&"preferences corrupted; removing".into());
102                        storage.remove_item(PREFERENCES_KEY)?;
103                        PreferenceData::default()
104                    }
105                },
106                None => PreferenceData::default(),
107            },
108            None => PreferenceData::default(),
109        };
110        Ok(Preferences { storage, data })
111    }
112
113    fn store(&self) -> Result<(), JsValue> {
114        if let Some(storage) = self.storage.as_ref() {
115            let data = serde_json::to_string(&self.data).unwrap();
116            storage.set_item(PREFERENCES_KEY, &data)
117        } else {
118            Ok(())
119        }
120    }
121}
122
123/// UI preferences macro: implements dummy `update_` methods for `Preferences`.
124///
125/// This macro is used to generate dummy `update_` methods that do nothing for
126/// values that aren't stored in the preferences. This is needed because the
127/// `set_values_if_inactive` macro (which is is used by
128/// [`impl_update_elements`](crate::impl_update_elements)) always calls the
129/// `update_` method of the preferences.
130///
131/// # Example
132///
133/// ```
134/// use maia_wasm::impl_dummy_preferences;
135///
136/// struct Preferences {}
137///
138/// impl_dummy_preferences!(
139///     my_section_my_float: f64,
140///     my_section_my_string: String,
141///  );
142///
143///  // Now it is possible to call update_ methods
144///  let mut preferences = Preferences {};
145///  preferences.update_my_section_my_float(&0.5);
146///  preferences.update_my_section_my_string(&"hello".to_string());
147///  ```
148#[macro_export]
149macro_rules! impl_dummy_preferences {
150    {$($name:ident : $ty:ty,)*} => {
151        impl Preferences {
152            $(
153                paste::paste! {
154                    pub fn [<update_ $name>](&mut self, _value: &$ty) -> Result<(), wasm_bindgen::JsValue> {
155                        Ok(())
156                    }
157                }
158            )*
159        }
160    }
161}
162
163impl_dummy_preferences!(
164    ddc_output_sampling_frequency: f64,
165    ddc_max_input_sampling_frequency: f64,
166);