From 95613581060532c06a7868f6aa2615a31c145847 Mon Sep 17 00:00:00 2001 From: Riccardo Zaglia Date: Fri, 2 Jun 2023 14:44:55 +0800 Subject: [PATCH] Populate dashboard lazily; add wasm wrapper code --- .../src/dashboard/components/connections.rs | 194 ++++++++++-------- .../src/dashboard/components/installation.rs | 23 ++- .../dashboard/src/dashboard/components/mod.rs | 8 +- .../src/dashboard/components/settings.rs | 105 ++++++---- .../presets/builtin_schema.rs | 15 -- alvr/dashboard/src/dashboard/mod.rs | 39 ++-- alvr/dashboard/src/data_sources_wasm.rs | 63 ++++++ alvr/dashboard/src/main.rs | 27 ++- 8 files changed, 299 insertions(+), 175 deletions(-) create mode 100644 alvr/dashboard/src/data_sources_wasm.rs diff --git a/alvr/dashboard/src/dashboard/components/connections.rs b/alvr/dashboard/src/dashboard/components/connections.rs index 0f5d88f7a7..8b93106f98 100644 --- a/alvr/dashboard/src/dashboard/components/connections.rs +++ b/alvr/dashboard/src/dashboard/components/connections.rs @@ -3,7 +3,7 @@ use crate::{ theme::{self, log_colors}, }; use alvr_packets::ClientListAction; -use alvr_session::SessionDesc; +use alvr_session::{ClientConnectionDesc, SessionDesc}; use eframe::{ egui::{Frame, Grid, Layout, RichText, TextEdit, Ui, Window}, emath::{Align, Align2}, @@ -18,23 +18,38 @@ struct EditPopupState { } pub struct ConnectionsTab { + new_clients: Option>, + trusted_clients: Option>, edit_popup_state: Option, } impl ConnectionsTab { pub fn new() -> Self { Self { + new_clients: None, + trusted_clients: None, edit_popup_state: None, } } - pub fn ui( - &mut self, - ui: &mut Ui, - session: &SessionDesc, - connected_to_server: bool, - ) -> Option { - let mut response = None; + pub fn update_client_list(&mut self, session: &SessionDesc) { + let (trusted_clients, untrusted_clients) = + session + .client_connections + .clone() + .into_iter() + .partition::, _>(|(_, data)| data.trusted); + + self.trusted_clients = Some(trusted_clients); + self.new_clients = Some(untrusted_clients); + } + + pub fn ui(&mut self, ui: &mut Ui, connected_to_server: bool) -> Vec { + let mut requests = vec![]; + + if self.new_clients.is_none() { + requests.push(ServerRequest::GetSession); + } if !connected_to_server { Frame::group(ui.style()) @@ -61,91 +76,90 @@ impl ConnectionsTab { }); } - // Get the different types of clients from the session - let (trusted_clients, untrusted_clients) = session - .client_connections - .iter() - .partition::, _>(|(_, data)| data.trusted); - ui.vertical_centered_justified(|ui| { - Frame::group(ui.style()) - .fill(theme::SECTION_BG) - .show(ui, |ui| { - ui.vertical_centered_justified(|ui| { - ui.add_space(5.0); - ui.heading("New clients"); - }); + if let Some(clients) = &self.new_clients { + Frame::group(ui.style()) + .fill(theme::SECTION_BG) + .show(ui, |ui| { + ui.vertical_centered_justified(|ui| { + ui.add_space(5.0); + ui.heading("New clients"); + }); - Grid::new(1).num_columns(2).show(ui, |ui| { - for (hostname, _) in untrusted_clients { - ui.horizontal(|ui| { - ui.add_space(10.0); - ui.label(hostname); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Trust").clicked() { - response = Some(ServerRequest::UpdateClientList { - hostname: hostname.clone(), - action: ClientListAction::Trust, - }); - }; - }); - ui.end_row(); - } - }) - }); + Grid::new(1).num_columns(2).show(ui, |ui| { + for (hostname, _) in clients { + ui.horizontal(|ui| { + ui.add_space(10.0); + ui.label(hostname); + }); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.button("Trust").clicked() { + requests.push(ServerRequest::UpdateClientList { + hostname: hostname.clone(), + action: ClientListAction::Trust, + }); + }; + }); + ui.end_row(); + } + }) + }); + } ui.add_space(10.0); - Frame::group(ui.style()) - .fill(theme::SECTION_BG) - .show(ui, |ui| { - ui.vertical_centered_justified(|ui| { - ui.add_space(5.0); - ui.heading("Trusted clients"); - }); + if let Some(clients) = &self.trusted_clients { + Frame::group(ui.style()) + .fill(theme::SECTION_BG) + .show(ui, |ui| { + ui.vertical_centered_justified(|ui| { + ui.add_space(5.0); + ui.heading("Trusted clients"); + }); - Grid::new(2).num_columns(2).show(ui, |ui| { - for (hostname, data) in trusted_clients { - ui.horizontal(|ui| { - ui.add_space(10.0); - ui.label(format!( - "{hostname}: {} ({})", - data.current_ip.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), - data.display_name - )); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if ui.button("Remove").clicked() { - response = Some(ServerRequest::UpdateClientList { - hostname: hostname.clone(), - action: ClientListAction::RemoveEntry, - }); - } - if ui.button("Edit").clicked() { - self.edit_popup_state = Some(EditPopupState { - new_client: false, - hostname: hostname.to_owned(), - ips: data - .manual_ips - .iter() - .map(|addr| addr.to_string()) - .collect::>(), - }); - } + Grid::new(2).num_columns(2).show(ui, |ui| { + for (hostname, data) in clients { + ui.horizontal(|ui| { + ui.add_space(10.0); + ui.label(format!( + "{hostname}: {} ({})", + data.current_ip + .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + data.display_name + )); + }); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.button("Remove").clicked() { + requests.push(ServerRequest::UpdateClientList { + hostname: hostname.clone(), + action: ClientListAction::RemoveEntry, + }); + } + if ui.button("Edit").clicked() { + self.edit_popup_state = Some(EditPopupState { + new_client: false, + hostname: hostname.to_owned(), + ips: data + .manual_ips + .iter() + .map(|addr| addr.to_string()) + .collect::>(), + }); + } + }); + ui.end_row(); + } + }); + + if ui.button("Add client manually").clicked() { + self.edit_popup_state = Some(EditPopupState { + hostname: "XXXX.client.alvr".into(), + new_client: true, + ips: Vec::new(), }); - ui.end_row(); } }); - - if ui.button("Add client manually").clicked() { - self.edit_popup_state = Some(EditPopupState { - hostname: "XXXX.client.alvr".into(), - new_client: true, - ips: Vec::new(), - }); - } - }); + } }); if let Some(mut state) = self.edit_popup_state.take() { @@ -165,7 +179,7 @@ impl ConnectionsTab { ui[1].text_edit_singleline(address); } if ui[1].button("Add new").clicked() { - state.ips.push("192.168.1.2".to_string()); + state.ips.push("192.168.X.X".to_string()); } }); ui.columns(2, |ui| { @@ -174,16 +188,16 @@ impl ConnectionsTab { state.ips.iter().filter_map(|s| s.parse().ok()).collect(); if state.new_client { - response = Some(ServerRequest::UpdateClientList { - hostname: state.hostname.clone(), + requests.push(ServerRequest::UpdateClientList { + hostname: state.hostname, action: ClientListAction::AddIfMissing { trusted: true, manual_ips, }, }); } else { - response = Some(ServerRequest::UpdateClientList { - hostname: state.hostname.clone(), + requests.push(ServerRequest::UpdateClientList { + hostname: state.hostname, action: ClientListAction::SetManualIps(manual_ips), }); } @@ -194,6 +208,6 @@ impl ConnectionsTab { }); } - response + requests } } diff --git a/alvr/dashboard/src/dashboard/components/installation.rs b/alvr/dashboard/src/dashboard/components/installation.rs index 39dc82cfa3..32ea8e4d27 100644 --- a/alvr/dashboard/src/dashboard/components/installation.rs +++ b/alvr/dashboard/src/dashboard/components/installation.rs @@ -4,7 +4,12 @@ use eframe::{ egui::{Frame, Grid, Layout, RichText, Ui}, emath::Align, }; -use std::path::PathBuf; +use std::{ + path::PathBuf, + time::{Duration, Instant}, +}; + +const DRIVER_UPDATE_INTERVAL: Duration = Duration::from_secs(1); pub enum InstallationTabRequest { OpenSetupWizard, @@ -13,11 +18,15 @@ pub enum InstallationTabRequest { pub struct InstallationTab { drivers: Vec, + last_update_instant: Instant, } impl InstallationTab { pub fn new() -> Self { - Self { drivers: vec![] } + Self { + drivers: vec![], + last_update_instant: Instant::now(), + } } pub fn update_drivers(&mut self, list: Vec) { @@ -26,6 +35,16 @@ impl InstallationTab { pub fn ui(&mut self, ui: &mut Ui) -> Vec { let mut requests = vec![]; + + let now = Instant::now(); + if now > self.last_update_instant + DRIVER_UPDATE_INTERVAL { + requests.push(InstallationTabRequest::ServerRequest( + ServerRequest::GetDriverList, + )); + + self.last_update_instant = now; + } + ui.vertical_centered_justified(|ui| { if ui.button("Run setup wizard").clicked() { requests.push(InstallationTabRequest::OpenSetupWizard); diff --git a/alvr/dashboard/src/dashboard/components/mod.rs b/alvr/dashboard/src/dashboard/components/mod.rs index 55e1de3dcc..d2a9c05878 100644 --- a/alvr/dashboard/src/dashboard/components/mod.rs +++ b/alvr/dashboard/src/dashboard/components/mod.rs @@ -1,7 +1,6 @@ mod about; mod connections; mod debug; -mod installation; mod logs; mod notifications; mod settings; @@ -9,13 +8,18 @@ mod settings_controls; mod setup_wizard; mod statistics; +#[cfg(not(target_arch = "wasm32"))] +mod installation; + pub use about::*; pub use connections::*; pub use debug::*; -pub use installation::*; pub use logs::*; pub use notifications::*; pub use settings::*; pub use settings_controls::*; pub use setup_wizard::*; pub use statistics::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use installation::*; diff --git a/alvr/dashboard/src/dashboard/components/settings.rs b/alvr/dashboard/src/dashboard/components/settings.rs index bab23e5641..42344a51ec 100644 --- a/alvr/dashboard/src/dashboard/components/settings.rs +++ b/alvr/dashboard/src/dashboard/components/settings.rs @@ -14,71 +14,87 @@ pub struct SettingsTab { resolution_preset: PresetControl, framerate_preset: PresetControl, encoder_preset: PresetControl, - game_audio_preset: PresetControl, - microphone_preset: PresetControl, + game_audio_preset: Option, + microphone_preset: Option, eye_face_tracking_preset: PresetControl, advanced_grid_id: usize, - session_settings_json: json::Value, + session_settings_json: Option, root_control: SettingControl, } impl SettingsTab { pub fn new() -> Self { - let session_settings = alvr_session::session_settings_default(); - let nesting_info = NestingInfo { path: vec!["session_settings".into()], indentation_level: 0, }; - let schema = Settings::schema(session_settings.clone()); + let schema = Settings::schema(alvr_session::session_settings_default()); Self { presets_grid_id: get_id(), resolution_preset: PresetControl::new(builtin_schema::resolution_schema()), framerate_preset: PresetControl::new(builtin_schema::framerate_schema()), encoder_preset: PresetControl::new(builtin_schema::encoder_preset_schema()), - game_audio_preset: PresetControl::new(builtin_schema::null_preset_schema()), - microphone_preset: PresetControl::new(builtin_schema::null_preset_schema()), + game_audio_preset: None, + microphone_preset: None, eye_face_tracking_preset: PresetControl::new(builtin_schema::eye_face_tracking_schema()), advanced_grid_id: get_id(), - session_settings_json: json::to_value(session_settings).unwrap(), + session_settings_json: None, root_control: SettingControl::new(nesting_info, schema), } } pub fn update_session(&mut self, session_settings: &SessionSettings) { - self.session_settings_json = json::to_value(session_settings).unwrap(); + let settings_json = json::to_value(session_settings).unwrap(); self.resolution_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); self.framerate_preset - .update_session_settings(&self.session_settings_json); - self.encoder_preset - .update_session_settings(&self.session_settings_json); - self.game_audio_preset - .update_session_settings(&self.session_settings_json); - self.microphone_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); + self.encoder_preset.update_session_settings(&settings_json); + if let Some(preset) = self.game_audio_preset.as_mut() { + preset.update_session_settings(&settings_json) + } + if let Some(preset) = self.microphone_preset.as_mut() { + preset.update_session_settings(&settings_json) + } self.eye_face_tracking_preset - .update_session_settings(&self.session_settings_json); + .update_session_settings(&settings_json); + + self.session_settings_json = Some(settings_json); } pub fn update_audio_devices(&mut self, list: AudioDevicesList) { let mut all_devices = list.output.clone(); all_devices.extend(list.input); - self.game_audio_preset = PresetControl::new(builtin_schema::game_audio_schema(all_devices)); - self.game_audio_preset - .update_session_settings(&self.session_settings_json); + let settings_json = self + .session_settings_json + .clone() + .unwrap_or_else(|| json::to_value(alvr_session::session_settings_default()).unwrap()); - self.microphone_preset = PresetControl::new(builtin_schema::microphone_schema(list.output)); - self.microphone_preset - .update_session_settings(&self.session_settings_json); + let mut preset = PresetControl::new(builtin_schema::game_audio_schema(all_devices)); + preset.update_session_settings(&settings_json); + self.game_audio_preset = Some(preset); + + let mut preset = PresetControl::new(builtin_schema::microphone_schema(list.output)); + preset.update_session_settings(&settings_json); + self.microphone_preset = Some(preset); } - pub fn ui(&mut self, ui: &mut Ui) -> Option { + pub fn ui(&mut self, ui: &mut Ui) -> Vec { let mut requests = vec![]; + if self.session_settings_json.is_none() { + requests.push(ServerRequest::GetSession); + } + + if self.game_audio_preset.is_none() { + requests.push(ServerRequest::GetAudioDevices); + } + + let mut path_value_pairs = vec![]; + ui.heading("Presets"); ScrollArea::new([true, false]) .id_source(self.presets_grid_id) @@ -87,22 +103,26 @@ impl SettingsTab { .striped(true) .num_columns(2) .show(ui, |ui| { - requests.extend(self.resolution_preset.ui(ui)); + path_value_pairs.extend(self.resolution_preset.ui(ui)); ui.end_row(); - requests.extend(self.framerate_preset.ui(ui)); + path_value_pairs.extend(self.framerate_preset.ui(ui)); ui.end_row(); - requests.extend(self.encoder_preset.ui(ui)); + path_value_pairs.extend(self.encoder_preset.ui(ui)); ui.end_row(); - requests.extend(self.game_audio_preset.ui(ui)); - ui.end_row(); + if let Some(preset) = &mut self.game_audio_preset { + path_value_pairs.extend(preset.ui(ui)); + ui.end_row(); + } - requests.extend(self.microphone_preset.ui(ui)); - ui.end_row(); + if let Some(preset) = &mut self.microphone_preset { + path_value_pairs.extend(preset.ui(ui)); + ui.end_row(); + } - requests.extend(self.eye_face_tracking_preset.ui(ui)); + path_value_pairs.extend(self.eye_face_tracking_preset.ui(ui)); ui.end_row(); }) }); @@ -120,21 +140,20 @@ impl SettingsTab { .striped(true) .num_columns(2) .show(ui, |ui| { - if let Some(request) = - self.root_control - .ui(ui, &mut self.session_settings_json, false) - { - requests.push(request); + if let Some(json) = &mut self.session_settings_json { + if let Some(pair) = self.root_control.ui(ui, json, false) { + path_value_pairs.push(pair); + } } ui.end_row(); }) }); - if !requests.is_empty() { - Some(ServerRequest::SetValues(requests)) - } else { - None + if !path_value_pairs.is_empty() { + requests.push(ServerRequest::SetValues(path_value_pairs)); } + + requests } } diff --git a/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs b/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs index 97ea5f3edb..e512d96ba0 100644 --- a/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs +++ b/alvr/dashboard/src/dashboard/components/settings_controls/presets/builtin_schema.rs @@ -302,18 +302,3 @@ pub fn eye_face_tracking_schema() -> PresetSchemaNode { gui: ChoiceControlType::ButtonGroup, }) } - -pub fn null_preset_schema() -> PresetSchemaNode { - PresetSchemaNode::HigherOrderChoice(HigherOrderChoiceSchema { - name: "null".into(), - strings: HashMap::new(), - flags: HashSet::new(), - options: vec![HigherOrderChoiceOption { - display_name: "null".into(), - modifiers: vec![], - content: None, - }], - default_option_index: 0, - gui: ChoiceControlType::Dropdown, - }) -} diff --git a/alvr/dashboard/src/dashboard/mod.rs b/alvr/dashboard/src/dashboard/mod.rs index adb007a579..bb18c9bf47 100644 --- a/alvr/dashboard/src/dashboard/mod.rs +++ b/alvr/dashboard/src/dashboard/mod.rs @@ -2,8 +2,7 @@ mod basic_components; mod components; use self::components::{ - ConnectionsTab, InstallationTab, InstallationTabRequest, LogsTab, NotificationBar, SettingsTab, - SetupWizard, SetupWizardRequest, + ConnectionsTab, LogsTab, NotificationBar, SettingsTab, SetupWizard, SetupWizardRequest, }; use crate::{dashboard::components::StatisticsTab, theme, DataSources}; use alvr_common::parking_lot::{Condvar, Mutex}; @@ -51,6 +50,7 @@ enum Tab { Connections, Statistics, Settings, + #[cfg(not(target_arch = "wasm32"))] Installation, Logs, Debug, @@ -67,20 +67,17 @@ pub struct Dashboard { connections_tab: ConnectionsTab, statistics_tab: StatisticsTab, settings_tab: SettingsTab, - installation_tab: InstallationTab, + #[cfg(not(target_arch = "wasm32"))] + installation_tab: components::InstallationTab, logs_tab: LogsTab, notification_bar: NotificationBar, setup_wizard: SetupWizard, setup_wizard_open: bool, - session: SessionDesc, + session: Option, } impl Dashboard { pub fn new(creation_context: &eframe::CreationContext<'_>, data_sources: DataSources) -> Self { - data_sources.request(ServerRequest::GetSession); - data_sources.request(ServerRequest::GetAudioDevices); - data_sources.request(ServerRequest::GetDriverList); - theme::set_theme(&creation_context.egui_ctx); Self { @@ -93,6 +90,7 @@ impl Dashboard { (Tab::Connections, "🔌 Connections"), (Tab::Statistics, "📈 Statistics"), (Tab::Settings, "⚙ Settings"), + #[cfg(not(target_arch = "wasm32"))] (Tab::Installation, "💾 Installation"), (Tab::Logs, "📝 Logs"), (Tab::Debug, "🐞 Debug"), @@ -103,12 +101,13 @@ impl Dashboard { connections_tab: ConnectionsTab::new(), statistics_tab: StatisticsTab::new(), settings_tab: SettingsTab::new(), - installation_tab: InstallationTab::new(), + #[cfg(not(target_arch = "wasm32"))] + installation_tab: components::InstallationTab::new(), logs_tab: LogsTab::new(), notification_bar: NotificationBar::new(), setup_wizard: SetupWizard::new(), setup_wizard_open: false, - session: SessionDesc::default(), + session: None, } } @@ -161,6 +160,7 @@ impl eframe::App for Dashboard { EventType::Session(session) => { let settings = session.to_settings(); + self.connections_tab.update_client_list(&session); self.settings_tab.update_session(&session.session_settings); self.logs_tab.update_settings(&settings); self.notification_bar.update_settings(&settings); @@ -172,12 +172,13 @@ impl eframe::App for Dashboard { self.just_opened = false; } - self.session = *session; + self.session = Some(*session); } EventType::ServerRequestsSelfRestart => self.restart_steamvr(&mut requests), EventType::AudioDevices(list) => self.settings_tab.update_audio_devices(list), + #[cfg(not(target_arch = "wasm32"))] EventType::DriversList(list) => self.installation_tab.update_drivers(list), - EventType::Tracking(_) | EventType::Buttons(_) | EventType::Haptics(_) => (), + _ => (), } } @@ -284,12 +285,7 @@ impl eframe::App for Dashboard { ); ScrollArea::new([false, true]).show(ui, |ui| match self.selected_tab { Tab::Connections => { - if let Some(request) = - self.connections_tab - .ui(ui, &self.session, connected_to_server) - { - requests.push(request); - } + requests.extend(self.connections_tab.ui(ui, connected_to_server)); } Tab::Statistics => { if let Some(request) = self.statistics_tab.ui(ui) { @@ -299,13 +295,16 @@ impl eframe::App for Dashboard { Tab::Settings => { requests.extend(self.settings_tab.ui(ui)); } + #[cfg(not(target_arch = "wasm32"))] Tab::Installation => { for request in self.installation_tab.ui(ui) { match request { - InstallationTabRequest::OpenSetupWizard => { + components::InstallationTabRequest::OpenSetupWizard => { self.setup_wizard_open = true } - InstallationTabRequest::ServerRequest(request) => { + components::InstallationTabRequest::ServerRequest( + request, + ) => { requests.push(request); } } diff --git a/alvr/dashboard/src/data_sources_wasm.rs b/alvr/dashboard/src/data_sources_wasm.rs new file mode 100644 index 0000000000..9f0d0f8b79 --- /dev/null +++ b/alvr/dashboard/src/data_sources_wasm.rs @@ -0,0 +1,63 @@ +use alvr_events::Event; +use alvr_packets::ServerRequest; +use eframe::{egui, web_sys}; +use ewebsock::{WsEvent, WsMessage, WsReceiver}; +use gloo_net::http::Request; + +pub struct DataSources { + context: egui::Context, + ws_receiver: Option, +} + +impl DataSources { + pub fn new(context: egui::Context) -> Self { + Self { + context, + ws_receiver: None, + } + } + + pub fn request(&self, request: ServerRequest) { + let context = self.context.clone(); + wasm_bindgen_futures::spawn_local(async move { + Request::post("/api/dashboard-request") + .body(serde_json::to_string(&request).unwrap()) + .send() + .await + .ok(); + + context.request_repaint(); + }) + } + + pub fn poll_event(&mut self) -> Option { + if self.ws_receiver.is_none() { + let host = web_sys::window().unwrap().location().host().unwrap(); + let Ok((_, receiver)) = ewebsock::connect(format!("ws://{host}/api/events")) else { + return None; + }; + self.ws_receiver = Some(receiver); + } + + if let Some(event) = self.ws_receiver.as_ref().unwrap().try_recv() { + match event { + WsEvent::Message(WsMessage::Text(json_string)) => { + serde_json::from_str(&json_string).ok() + } + WsEvent::Error(_) | WsEvent::Closed => { + // recreate the ws connection next poll_event invocation + self.ws_receiver = None; + + None + } + _ => None, + } + } else { + None + } + } + + pub fn server_connected(&self) -> bool { + true + } +} diff --git a/alvr/dashboard/src/main.rs b/alvr/dashboard/src/main.rs index 653d66513d..ef3c21753e 100644 --- a/alvr/dashboard/src/main.rs +++ b/alvr/dashboard/src/main.rs @@ -1,16 +1,22 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +// hide console window on Windows in release +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod dashboard; -mod logging_backend; mod theme; #[cfg(not(target_arch = "wasm32"))] mod data_sources; +#[cfg(target_arch = "wasm32")] +mod data_sources_wasm; +#[cfg(not(target_arch = "wasm32"))] +mod logging_backend; #[cfg(not(target_arch = "wasm32"))] mod steamvr_launcher; #[cfg(not(target_arch = "wasm32"))] use data_sources::DataSources; +#[cfg(target_arch = "wasm32")] +use data_sources_wasm::DataSources; use dashboard::Dashboard; @@ -94,4 +100,19 @@ fn main() { } #[cfg(target_arch = "wasm32")] -fn main() {} +fn main() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::default()); + + wasm_bindgen_futures::spawn_local(async { + eframe::WebRunner::new() + .start("dashboard_canvas", eframe::WebOptions::default(), { + Box::new(move |creation_context| { + let context = creation_context.egui_ctx.clone(); + Box::new(Dashboard::new(creation_context, DataSources::new(context))) + }) + }) + .await + .ok(); + }); +}