/ web / src / pages / panels / csv.rs
csv.rs
  1  use super::sensor_types::*;
  2  use wasm_bindgen::JsCast;
  3  
  4  pub fn download_csv(filename: &str, csv_content: &str) {
  5      let array = js_sys::Array::new();
  6      array.push(&wasm_bindgen::JsValue::from_str(csv_content));
  7      let mut options = web_sys::BlobPropertyBag::new();
  8      options.type_("text/csv");
  9  
 10      let Ok(blob) = web_sys::Blob::new_with_str_sequence_and_options(&array, &options) else {
 11          return;
 12      };
 13      let Ok(url) = web_sys::Url::create_object_url_with_blob(&blob) else {
 14          return;
 15      };
 16  
 17      if let Some(document) = web_sys::window().and_then(|w| w.document()) {
 18          if let Ok(anchor) = document.create_element("a") {
 19              let _ = anchor.set_attribute("href", &url);
 20              let _ = anchor.set_attribute("download", filename);
 21              let anchor: web_sys::HtmlElement = anchor.unchecked_into();
 22              anchor.click();
 23          }
 24      }
 25  
 26      let _ = web_sys::Url::revoke_object_url(&url);
 27  }
 28  
 29  pub trait CsvRow {
 30      fn header(sample: &[Self]) -> String
 31      where
 32          Self: Sized;
 33      fn to_row(&self) -> String;
 34  }
 35  
 36  impl CsvRow for Co2Row {
 37      fn header(_: &[Self]) -> String {
 38          "#,CO2_PPM,TEMP_C,HUMIDITY_PCT,TIME".to_string()
 39      }
 40  
 41      fn to_row(&self) -> String {
 42          format!(
 43              "{},{},{},{},{}",
 44              self.row, self.co2_ppm, self.temperature, self.humidity, self.time
 45          )
 46      }
 47  }
 48  
 49  impl CsvRow for TemperatureHumidityRow {
 50      fn header(sample: &[Self]) -> String {
 51          let sensor_count = sample.first().map(|row| row.sensors.len()).unwrap_or(0);
 52          let mut header = String::from("#,");
 53          for i in 0..sensor_count {
 54              header.push_str(&format!("TEMP_{i}_C,RH_{i}_PCT,"));
 55          }
 56          header.push_str("TIME");
 57          header
 58      }
 59  
 60      fn to_row(&self) -> String {
 61          let mut row = format!("{},", self.row);
 62          for sensor in &self.sensors {
 63              if sensor.read_ok {
 64                  row.push_str(&format!(
 65                      "{},{},",
 66                      sensor.temperature_celsius, sensor.relative_humidity_percent
 67                  ));
 68              } else {
 69                  row.push_str(",,");
 70              }
 71          }
 72          row.push_str(&self.time);
 73          row
 74      }
 75  }
 76  
 77  impl CsvRow for VoltageRow {
 78      fn header(_: &[Self]) -> String {
 79          "#,CH0_V,CH1_V,CH2_V,CH3_V,TIME".to_string()
 80      }
 81  
 82      fn to_row(&self) -> String {
 83          let mut row = format!("{},", self.row);
 84          for (i, voltage) in self.channels.iter().enumerate() {
 85              row.push_str(&format!("{voltage:.4}"));
 86              if i < self.channels.len() - 1 {
 87                  row.push(',');
 88              }
 89          }
 90          row.push_str(&format!(",{}", self.time));
 91          row
 92      }
 93  }
 94  
 95  impl CsvRow for PressureRow {
 96      fn header(_: &[Self]) -> String {
 97          "#,MODEL,PRESSURE_HPA,TEMP_C,TIME".to_string()
 98      }
 99  
100      fn to_row(&self) -> String {
101          format!(
102              "{},{},{:.2},{:.1},{}",
103              self.row, self.model, self.pressure_hpa, self.temperature_celsius, self.time
104          )
105      }
106  }
107  
108  pub fn build_csv<T: CsvRow>(readings: &[T]) -> String {
109      let mut csv = T::header(readings);
110      csv.push('\n');
111      for row in readings {
112          csv.push_str(&row.to_row());
113          csv.push('\n');
114      }
115      csv
116  }