mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 15:02:59 +03:00
Refactor the support for making browsers download files, and use more widely
This commit is contained in:
parent
5e11e6254b
commit
ffef0279bf
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -23,12 +23,14 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"include_dir",
|
||||
"instant",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
@ -2101,12 +2103,10 @@ dependencies = [
|
||||
"anyhow",
|
||||
"contour",
|
||||
"flate2",
|
||||
"fs-err",
|
||||
"geo",
|
||||
"geojson",
|
||||
"geom",
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"map_gui",
|
||||
@ -2119,7 +2119,6 @@ dependencies = [
|
||||
"structopt",
|
||||
"synthpop",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"widgetry",
|
||||
]
|
||||
|
||||
@ -3508,7 +3507,6 @@ dependencies = [
|
||||
"ctrlc",
|
||||
"downcast-rs",
|
||||
"enum_dispatch",
|
||||
"fs-err",
|
||||
"geom",
|
||||
"instant",
|
||||
"libm 0.2.1",
|
||||
|
@ -23,4 +23,6 @@ tokio = "1.1.1"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
include_dir = { git = "https://github.com/dabreegster/include_dir", branch = "union" }
|
||||
web-sys = { version = "0.3.47", features=["Storage", "Window"] }
|
||||
js-sys = "0.3.47"
|
||||
wasm-bindgen = "0.2.70"
|
||||
web-sys = { version = "0.3.47", features=["HtmlElement", "Storage", "Window"] }
|
||||
|
@ -198,3 +198,10 @@ impl Read for FileWithProgress {
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns path on success
|
||||
pub fn write_file(path: String, contents: String) -> Result<String> {
|
||||
let mut file = File::create(&path)?;
|
||||
write!(file, "{}", contents)?;
|
||||
Ok(path)
|
||||
}
|
||||
|
@ -179,3 +179,29 @@ fn list_local_storage_keys() -> Vec<String> {
|
||||
}
|
||||
keys
|
||||
}
|
||||
|
||||
/// Returns path on success
|
||||
pub fn write_file(path: String, contents: String) -> Result<String> {
|
||||
// Make the browser prompt the user to save a local file with arbitrary contents.
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let data: String = js_sys::JsString::from("data:text/json;charset=utf-8,")
|
||||
.concat(&js_sys::encode_uri_component(&contents))
|
||||
.into();
|
||||
|
||||
// TODO Proper error handling
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let node = document
|
||||
.create_element("a")
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlElement>()
|
||||
.unwrap();
|
||||
node.set_attribute("href", &data).unwrap();
|
||||
node.set_attribute("download", &path).unwrap();
|
||||
document.body().unwrap().append_child(&node).unwrap();
|
||||
node.click();
|
||||
node.remove();
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
@ -234,11 +234,7 @@ impl Throughput {
|
||||
)
|
||||
.flex_wrap(ctx, Percent::int(20)),
|
||||
ColorLegend::gradient(ctx, &app.cs.good_to_bad_red, vec!["0", "highest"]),
|
||||
if cfg!(not(target_arch = "wasm32")) {
|
||||
ctx.style().btn_plain.text("Export to CSV").build_def(ctx)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
ctx.style().btn_plain.text("Export to CSV").build_def(ctx),
|
||||
]))
|
||||
.aligned_pair(PANEL_PLACEMENT)
|
||||
.build(ctx);
|
||||
@ -758,22 +754,28 @@ fn export_throughput(app: &App) -> Result<(String, String)> {
|
||||
app.primary.map.get_name().as_filename(),
|
||||
app.primary.sim.time().as_filename()
|
||||
);
|
||||
let path1 = abstio::write_file(
|
||||
path1,
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.road_thruput
|
||||
.export_csv(&path1, |id| id.0)?;
|
||||
.export_csv(|id| id.0),
|
||||
)?;
|
||||
|
||||
let path2 = format!(
|
||||
"intersection_throughput_{}_{}.csv",
|
||||
app.primary.map.get_name().as_filename(),
|
||||
app.primary.sim.time().as_filename()
|
||||
);
|
||||
let path2 = abstio::write_file(
|
||||
path2,
|
||||
app.primary
|
||||
.sim
|
||||
.get_analytics()
|
||||
.intersection_thruput
|
||||
.export_csv(&path2, |id| id.0)?;
|
||||
.export_csv(|id| id.0),
|
||||
)?;
|
||||
|
||||
Ok((path1, path2))
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::Write;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use fs_err::File;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use sim::{ProblemType, TripID};
|
||||
@ -207,9 +206,9 @@ fn export_problems(app: &App) -> Result<String> {
|
||||
app.primary.map.get_name().as_filename(),
|
||||
app.primary.sim.time().as_filename()
|
||||
);
|
||||
let mut f = File::create(&path)?;
|
||||
let mut out = String::new();
|
||||
writeln!(
|
||||
f,
|
||||
out,
|
||||
"id,mode,seconds_after,problem_type,problems_before,problems_after"
|
||||
)?;
|
||||
|
||||
@ -225,7 +224,7 @@ fn export_problems(app: &App) -> Result<String> {
|
||||
problem_type.count(after.problems_per_trip.get(&id).unwrap_or(&empty));
|
||||
if count_before != 0 || count_after != 0 {
|
||||
writeln!(
|
||||
f,
|
||||
out,
|
||||
"{},{:?},{},{:?},{},{}",
|
||||
id.0,
|
||||
mode,
|
||||
@ -238,5 +237,5 @@ fn export_problems(app: &App) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
abstio::write_file(path, out)
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::Write;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use fs_err::File;
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Distance, Duration, Polygon, Pt2D};
|
||||
@ -42,8 +41,6 @@ impl TravelTimes {
|
||||
));
|
||||
}
|
||||
|
||||
// TODO We can make file downloads of dynamically generated data work on the browser too...
|
||||
if cfg!(not(target_arch = "wasm32")) {
|
||||
filters.push(
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
@ -51,7 +48,6 @@ impl TravelTimes {
|
||||
.build_def(ctx)
|
||||
.align_bottom(),
|
||||
);
|
||||
}
|
||||
|
||||
Panel::new_builder(Widget::col(vec![
|
||||
DashTab::TravelTimes.picker(ctx, app),
|
||||
@ -645,8 +641,8 @@ fn export_times(app: &App) -> Result<String> {
|
||||
app.primary.map.get_name().as_filename(),
|
||||
app.primary.sim.time().as_filename()
|
||||
);
|
||||
let mut f = File::create(&path)?;
|
||||
writeln!(f, "id,mode,seconds_before,seconds_after")?;
|
||||
let mut out = String::new();
|
||||
writeln!(out, "id,mode,seconds_before,seconds_after")?;
|
||||
for (id, b, a, mode) in app
|
||||
.primary
|
||||
.sim
|
||||
@ -654,7 +650,7 @@ fn export_times(app: &App) -> Result<String> {
|
||||
.both_finished_trips(app.primary.sim.time(), app.prebaked())
|
||||
{
|
||||
writeln!(
|
||||
f,
|
||||
out,
|
||||
"{},{:?},{},{}",
|
||||
id.0,
|
||||
mode,
|
||||
@ -662,5 +658,5 @@ fn export_times(app: &App) -> Result<String> {
|
||||
a.inner_seconds()
|
||||
)?;
|
||||
}
|
||||
Ok(path)
|
||||
abstio::write_file(path, out)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ crate-type = ["cdylib", "lib"]
|
||||
|
||||
[features]
|
||||
default = ["map_gui/native", "widgetry/native-backend"]
|
||||
wasm = ["getrandom/js", "js-sys", "map_gui/wasm", "wasm-bindgen", "web-sys", "widgetry/wasm-backend"]
|
||||
wasm = ["getrandom/js", "map_gui/wasm", "wasm-bindgen", "widgetry/wasm-backend"]
|
||||
|
||||
[dependencies]
|
||||
abstio = { path = "../../abstio" }
|
||||
@ -17,12 +17,10 @@ abstutil = { path = "../../abstutil" }
|
||||
anyhow = "1.0.38"
|
||||
contour = "0.4.0"
|
||||
flate2 = "1.0.20"
|
||||
fs-err = "2.6.0"
|
||||
geo = "0.20.1"
|
||||
geojson = { version = "0.22.2", features = ["geo-types"] }
|
||||
geom = { path = "../../geom" }
|
||||
getrandom = { version = "0.2.3", optional = true }
|
||||
js-sys = { version = "0.3.47", optional = true }
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
maplit = "1.0.2"
|
||||
@ -36,8 +34,3 @@ synthpop = { path = "../../synthpop" }
|
||||
wasm-bindgen = { version = "0.2.70", optional = true }
|
||||
widgetry = { path = "../../widgetry" }
|
||||
structopt = "0.3.23"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.47"
|
||||
optional = true
|
||||
features = ["HtmlElement"]
|
||||
|
@ -9,39 +9,7 @@ use crate::{App, Neighborhood};
|
||||
pub fn write_geojson_file(ctx: &EventCtx, app: &App) -> Result<String> {
|
||||
let contents = geojson_string(ctx, app)?;
|
||||
let path = format!("ltn_{}.geojson", app.map.get_name().map);
|
||||
|
||||
// TODO Refactor into map_gui or abstio and handle errors better
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let data: String = js_sys::JsString::from("data:text/json;charset=utf-8,")
|
||||
.concat(&js_sys::encode_uri_component(&contents))
|
||||
.into();
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let node = document
|
||||
.create_element("a")
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::HtmlElement>()
|
||||
.unwrap();
|
||||
node.set_attribute("href", &data).unwrap();
|
||||
node.set_attribute("download", &path).unwrap();
|
||||
document.body().unwrap().append_child(&node).unwrap();
|
||||
node.click();
|
||||
node.remove();
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
use std::io::Write;
|
||||
|
||||
let mut file = fs_err::File::create(&path)?;
|
||||
write!(file, "{}", contents)?;
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
abstio::write_file(path, contents)
|
||||
}
|
||||
|
||||
fn geojson_string(ctx: &EventCtx, app: &App) -> Result<String> {
|
||||
|
@ -11,7 +11,6 @@ anyhow = "1.0.38"
|
||||
ctrlc = { version = "3.1.7", optional = true }
|
||||
downcast-rs = "1.2.0"
|
||||
enum_dispatch = "0.3.5"
|
||||
fs-err = "2.6.0"
|
||||
geom = { path = "../geom" }
|
||||
instant = "0.1.7"
|
||||
libm = "0.2.1"
|
||||
|
@ -1,8 +1,6 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::io::Write;
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use fs_err::File;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use abstutil::Counter;
|
||||
@ -834,13 +832,22 @@ impl<X: Ord + Clone> TimeSeriesCount<X> {
|
||||
pts_per_type.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn export_csv<F: Fn(&X) -> usize>(&self, path: &str, extract_id: F) -> Result<()> {
|
||||
let mut f = File::create(path)?;
|
||||
writeln!(f, "id,agent_type,hour,count")?;
|
||||
/// Returns the contents of a CSV file
|
||||
pub fn export_csv<F: Fn(&X) -> usize>(&self, extract_id: F) -> String {
|
||||
let mut out = String::new();
|
||||
writeln!(out, "id,agent_type,hour,count").unwrap();
|
||||
for ((id, agent_type, hour), count) in &self.counts {
|
||||
writeln!(f, "{},{:?},{},{}", extract_id(id), agent_type, hour, count)?;
|
||||
writeln!(
|
||||
out,
|
||||
"{},{:?},{},{}",
|
||||
extract_id(id),
|
||||
agent_type,
|
||||
hour,
|
||||
count
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Ok(())
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user