use chrono::{DateTime, Utc};
use serde::Serialize;
use wasm_bindgen::JsCast;
use web_sys::{Blob, HtmlAnchorElement, Url};
fn timestamp() -> String {
let now: DateTime<Utc> = Utc::now();
now.format("%Y%m%d-%H%M%S").to_string()
}
pub fn export_as_csv(headers: Vec<String>, rows: Vec<Vec<String>>, filename: &str) {
let mut csv = String::new();
csv.push_str("\u{FEFF}");
csv.push_str(&headers.join(","));
csv.push('\n');
for row in rows {
let escaped: Vec<_> = row
.iter()
.map(|cell| {
if cell.contains(',') || cell.contains('"') || cell.contains('\n') {
format!("\"{}\"", cell.replace("\"", "\"\""))
} else {
cell.clone()
}
})
.collect();
csv.push_str(&escaped.join(","));
csv.push('\n');
}
let filename_with_ts = format!("{}-{}.csv", filename, timestamp());
trigger_download(&csv, &filename_with_ts, "text/csv");
}
pub fn export_as_json<T: Serialize>(data: &T, filename: &str) {
let json = match serde_json::to_string_pretty(data) {
Ok(j) => j,
Err(e) => {
web_sys::console::error_1(
&format!("Failed to serialize JSON for export: {}", e).into(),
);
return;
}
};
let filename_with_ts = format!("{}-{}.json", filename, timestamp());
trigger_download(&json, &filename_with_ts, "application/json");
}
fn trigger_download(content: &str, filename: &str, mime_type: &str) {
let window = match web_sys::window() {
Some(w) => w,
None => {
web_sys::console::error_1(&"Failed to get window object".into());
return;
}
};
let document = match window.document() {
Some(d) => d,
None => {
web_sys::console::error_1(&"Failed to get document object".into());
return;
}
};
let blob_parts = js_sys::Array::new();
blob_parts.push(&wasm_bindgen::JsValue::from_str(content));
let blob_options = web_sys::BlobPropertyBag::new();
blob_options.set_type(mime_type);
let blob = match Blob::new_with_str_sequence_and_options(&blob_parts, &blob_options) {
Ok(b) => b,
Err(e) => {
web_sys::console::error_1(&format!("Failed to create Blob: {:?}", e).into());
return;
}
};
let url = match Url::create_object_url_with_blob(&blob) {
Ok(u) => u,
Err(e) => {
web_sys::console::error_1(&format!("Failed to create object URL: {:?}", e).into());
return;
}
};
let anchor = match document
.create_element("a")
.ok()
.and_then(|el| el.dyn_into::<HtmlAnchorElement>().ok())
{
Some(a) => a,
None => {
web_sys::console::error_1(&"Failed to create anchor element".into());
let _ = Url::revoke_object_url(&url);
return;
}
};
anchor.set_href(&url);
anchor.set_download(filename);
anchor.click();
if let Err(e) = Url::revoke_object_url(&url) {
web_sys::console::error_1(&format!("Failed to revoke object URL: {:?}", e).into());
}
}