Skip to content

Commit

Permalink
On the web, modify the current URL as we change maps and scenarios. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster authored Feb 12, 2021
1 parent 03538fa commit 8d0718d
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ crate-type = ["cdylib", "lib"]

[features]
default = ["built", "map_gui/native", "widgetry/native-backend"]
wasm = ["getrandom/js", "map_gui/wasm", "wasm-bindgen", "widgetry/wasm-backend"]
wasm = ["getrandom/js", "map_gui/wasm", "wasm-bindgen", "web-sys", "widgetry/wasm-backend"]

[dependencies]
aabb-quadtree = "0.1.0"
Expand Down Expand Up @@ -45,6 +45,7 @@ serde_json = "1.0.61"
svg_face = "0.1.3"
sim = { path = "../sim" }
wasm-bindgen = { version = "0.2.70", optional = true }
web-sys = { version = "0.3.47", optional = true, features=["History", "Location", "Window"] }
widgetry = { path = "../widgetry" }

[build-dependencies]
Expand Down
102 changes: 102 additions & 0 deletions game/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::BTreeSet;

use anyhow::Result;

use geom::{Duration, Polygon};
use map_gui::ID;
use map_model::{IntersectionID, Map, RoadID};
Expand Down Expand Up @@ -407,3 +409,103 @@ pub fn checkbox_per_mode(
}
Widget::custom_row(filters)
}

/// This does nothing on native. On web, it modifies the current URL to change the first free
/// parameter in the HTTP GET params to the specified value, adding it if needed.
#[allow(unused_variables)]
pub fn update_url(free_param: &str) -> Result<()> {
#[cfg(target_arch = "wasm32")]
{
let window = web_sys::window().ok_or(anyhow!("no window?"))?;
let url = window.location().href().map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.location.href failed".to_string()))
})?;
let new_url = change_url_free_query_param(url, free_param);

// Setting window.location.href may seem like the obvious thing to do, but that actually
// refreshes the page. This method just changes the URL and doesn't mess up history. See
// https://developer.mozilla.org/en-US/docs/Web/API/History_API/Working_with_the_History_API.
let history = window.history().map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.history failed".to_string()))
})?;
history
.replace_state_with_url(&wasm_bindgen::JsValue::NULL, "", Some(&new_url))
.map_err(|err| {
anyhow!(err
.as_string()
.unwrap_or("window.history.replace_state failed".to_string()))
})?;
}
Ok(())
}

#[allow(unused)]
fn change_url_free_query_param(url: String, free_param: &str) -> String {
// The URL parsing crates I checked had lots of dependencies and didn't even expose such a nice
// API for doing this anyway.
let url_parts = url.split("?").collect::<Vec<_>>();
if url_parts.len() == 1 {
return format!("{}?{}", url, free_param);
}
let mut query_params = String::new();
let mut found_free = false;
let mut first = true;
for x in url_parts[1].split("&") {
if !first {
query_params.push('&');
}
first = false;

if x.starts_with("--") {
query_params.push_str(x);
} else if !found_free {
// Replace the first free parameter
query_params.push_str(free_param);
found_free = true;
} else {
query_params.push_str(x);
}
}
if !found_free {
if !first {
query_params.push('&');
}
query_params.push_str(free_param);
}

format!("{}?{}", url_parts[0], query_params)
}

#[cfg(test)]
mod tests {
#[test]
fn test_change_url() {
use super::change_url_free_query_param;

assert_eq!(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin",
change_url_free_query_param(
"http://0.0.0.0:8000/?--dev".to_string(),
"seattle/maps/montlake.bin"
)
);
assert_eq!(
"http://0.0.0.0:8000/?--dev&seattle/maps/qa.bin",
change_url_free_query_param(
"http://0.0.0.0:8000/?--dev&seattle/maps/montlake.bin".to_string(),
"seattle/maps/qa.bin"
)
);
assert_eq!(
"http://0.0.0.0:8000?seattle/maps/montlake.bin",
change_url_free_query_param(
"http://0.0.0.0:8000".to_string(),
"seattle/maps/montlake.bin"
)
);
}
}
15 changes: 13 additions & 2 deletions game/src/sandbox/gameplay/freeform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use widgetry::{
};

use crate::app::{App, Transition};
use crate::common::CommonState;
use crate::common::{update_url, CommonState};
use crate::edit::EditMode;
use crate::sandbox::gameplay::{GameplayMode, GameplayState};
use crate::sandbox::{Actions, SandboxControls, SandboxMode};
Expand All @@ -26,7 +26,18 @@ pub struct Freeform {
}

impl Freeform {
pub fn new(ctx: &mut EventCtx) -> Box<dyn GameplayState> {
pub fn new(ctx: &mut EventCtx, app: &App) -> Box<dyn GameplayState> {
if let Err(err) = update_url(
app.primary
.map
.get_name()
.path()
.strip_prefix(&abstio::path(""))
.unwrap(),
) {
warn!("Couldn't update URL: {}", err);
}

Box::new(Freeform {
top_center: Panel::empty(ctx),
})
Expand Down
4 changes: 2 additions & 2 deletions game/src/sandbox/gameplay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ impl GameplayMode {
/// after this, so each constructor doesn't need to.
pub fn initialize(&self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn GameplayState> {
match self {
GameplayMode::Freeform(_) => freeform::Freeform::new(ctx),
GameplayMode::Freeform(_) => freeform::Freeform::new(ctx, app),
GameplayMode::PlayScenario(_, ref scenario, ref modifiers) => {
play_scenario::PlayScenario::new(ctx, scenario, modifiers.clone())
play_scenario::PlayScenario::new(ctx, app, scenario, modifiers.clone())
}
GameplayMode::FixTrafficSignals => {
fix_traffic_signals::FixTrafficSignals::new(ctx, app)
Expand Down
14 changes: 13 additions & 1 deletion game/src/sandbox/gameplay/play_scenario.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use widgetry::{
};

use crate::app::{App, Transition};
use crate::common::checkbox_per_mode;
use crate::common::{checkbox_per_mode, update_url};
use crate::edit::EditMode;
use crate::sandbox::gameplay::freeform::ChangeScenario;
use crate::sandbox::gameplay::{GameplayMode, GameplayState};
Expand All @@ -25,9 +25,21 @@ pub struct PlayScenario {
impl PlayScenario {
pub fn new(
ctx: &mut EventCtx,
app: &App,
name: &String,
modifiers: Vec<ScenarioModifier>,
) -> Box<dyn GameplayState> {
if let Err(err) = update_url(
// For dynamiclly generated scenarios like "random" and "home_to_work", this winds up
// making up a filename that doesn't actually exist. But if you pass that in, it winds
// up working, because we call abstio::parse_scenario_path() on the other side.
abstio::path_scenario(app.primary.map.get_name(), name)
.strip_prefix(&abstio::path(""))
.unwrap(),
) {
warn!("Couldn't update URL: {}", err);
}

Box::new(PlayScenario {
top_center: Panel::empty(ctx),
scenario_name: name.to_string(),
Expand Down

0 comments on commit 8d0718d

Please sign in to comment.