Browse Source

Add web location info to egui_web/epi (#1258)

This adds all parts of the web "location" (URL) to frame.info().web_info, included a HashMap of the query parameters, percent-decoded and ready to go.

This lets you easily pass key-value pairs to your eframe web app.
pull/1263/head
Emil Ernerfeldt 3 years ago
committed by GitHub
parent
commit
b5c8f034e7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Cargo.lock
  2. 1
      eframe/CHANGELOG.md
  3. 6
      egui_demo_lib/src/backend_panel.rs
  4. 2
      egui_demo_lib/src/wrap_app.rs
  5. 1
      egui_web/CHANGELOG.md
  6. 1
      egui_web/Cargo.toml
  7. 73
      egui_web/src/backend.rs
  8. 23
      egui_web/src/lib.rs
  9. 56
      epi/src/lib.rs

1
Cargo.lock

@ -1101,6 +1101,7 @@ dependencies = [
"egui_glow",
"epi",
"js-sys",
"percent-encoding",
"ron",
"serde",
"tracing",

1
eframe/CHANGELOG.md

@ -14,6 +14,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
* Added `NativeOptions::initial_window_pos`.
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
* Log using the `tracing` crate. Log to stdout by adding `tracing_subscriber::fmt::init();` to your `main` ([#1192](https://github.com/emilk/egui/pull/1192)).
* Expose all parts of the location/url in `frame.info().web_info` ([#1258](https://github.com/emilk/egui/pull/1258)).
## 0.16.0 - 2021-12-29

6
egui_demo_lib/src/backend_panel.rs

@ -172,6 +172,12 @@ impl BackendPanel {
show_integration_name(ui, &frame.info());
if let Some(web_info) = &frame.info().web_info {
ui.collapsing("Web info (location)", |ui| {
ui.monospace(format!("{:#?}", web_info.location));
});
}
// For instance: `egui_web` sets `pixels_per_point` every frame to force
// egui to use the same scale as the web zoom factor.
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();

2
egui_demo_lib/src/wrap_app.rs

@ -69,7 +69,7 @@ impl epi::App for WrapApp {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
if let Some(web_info) = frame.info().web_info.as_ref() {
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
if let Some(anchor) = web_info.location.hash.strip_prefix('#') {
self.selected_anchor = anchor.to_owned();
}
}

1
egui_web/CHANGELOG.md

@ -10,6 +10,7 @@ All notable changes to the `egui_web` integration will be noted in this file.
* Shift-scroll will now result in horizontal scrolling ([#1136](https://github.com/emilk/egui/pull/1136)).
* Updated `epi::IntegrationInfo::web_location_hash` on `hashchange` event ([#1140](https://github.com/emilk/egui/pull/1140)).
* Panics will now be logged using `console.error`.
* Parse and percent-decode the web location query string ([#1258](https://github.com/emilk/egui/pull/1258)).
## 0.16.0 - 2021-12-29

1
egui_web/Cargo.toml

@ -57,6 +57,7 @@ epi = { version = "0.16.0", path = "../epi" }
bytemuck = "1.7"
js-sys = "0.3"
percent-encoding = "2.1"
tracing = "0.1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"

73
egui_web/src/backend.rs

@ -81,6 +81,77 @@ impl epi::backend::RepaintSignal for NeedRepaint {
// ----------------------------------------------------------------------------
fn web_location() -> epi::Location {
let location = web_sys::window().unwrap().location();
let hash = percent_decode(&location.hash().unwrap_or_default());
let query = location
.search()
.unwrap_or_default()
.strip_prefix('?')
.map(percent_decode)
.unwrap_or_default();
let query_map = parse_query_map(&query)
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
epi::Location {
url: percent_decode(&location.href().unwrap_or_default()),
protocol: percent_decode(&location.protocol().unwrap_or_default()),
host: percent_decode(&location.host().unwrap_or_default()),
hostname: percent_decode(&location.hostname().unwrap_or_default()),
port: percent_decode(&location.port().unwrap_or_default()),
hash,
query,
query_map,
origin: percent_decode(&location.origin().unwrap_or_default()),
}
}
fn parse_query_map(query: &str) -> BTreeMap<&str, &str> {
query
.split('&')
.filter_map(|pair| {
if pair.is_empty() {
None
} else {
Some(if let Some((key, value)) = pair.split_once('=') {
(key, value)
} else {
(pair, "")
})
}
})
.collect()
}
#[test]
fn test_parse_query() {
assert_eq!(parse_query_map(""), BTreeMap::default());
assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")]));
assert_eq!(
parse_query_map("foo=bar"),
BTreeMap::from_iter([("foo", "bar")])
);
assert_eq!(
parse_query_map("foo=bar&baz=42"),
BTreeMap::from_iter([("foo", "bar"), ("baz", "42")])
);
assert_eq!(
parse_query_map("foo&baz=42"),
BTreeMap::from_iter([("foo", ""), ("baz", "42")])
);
assert_eq!(
parse_query_map("foo&baz&&"),
BTreeMap::from_iter([("foo", ""), ("baz", "")])
);
}
// ----------------------------------------------------------------------------
pub struct AppRunner {
pub(crate) frame: epi::Frame,
egui_ctx: egui::Context,
@ -108,7 +179,7 @@ impl AppRunner {
info: epi::IntegrationInfo {
name: painter.name(),
web_info: Some(epi::WebInfo {
web_location_hash: location_hash().unwrap_or_default(),
location: web_location(),
}),
prefer_dark_mode,
cpu_usage: None,

23
egui_web/src/lib.rs

@ -33,6 +33,7 @@ pub use web_sys;
pub use painter::Painter;
use std::cell::Cell;
use std::collections::BTreeMap;
use std::rc::Rc;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
@ -353,9 +354,23 @@ pub fn open_url(url: &str, new_tab: bool) -> Option<()> {
Some(())
}
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
pub fn location_hash() -> Option<String> {
web_sys::window()?.location().hash().ok()
/// e.g. "#fragment" part of "www.example.com/index.html#fragment",
///
/// Percent decoded
pub fn location_hash() -> String {
percent_decode(
&web_sys::window()
.unwrap()
.location()
.hash()
.unwrap_or_default(),
)
}
pub fn percent_decode(s: &str) -> String {
percent_encoding::percent_decode_str(s)
.decode_utf8_lossy()
.to_string()
}
/// Web sends all keys as strings, so it is up to us to figure out if it is
@ -661,7 +676,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
if let Some(web_info) = &mut frame_lock.info.web_info {
web_info.web_location_hash = location_hash().unwrap_or_default();
web_info.location.hash = location_hash();
}
}) as Box<dyn FnMut()>);
window.add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref())?;

56
epi/src/lib.rs

@ -361,10 +361,62 @@ impl Frame {
/// Information about the web environment (if applicable).
#[derive(Clone, Debug)]
pub struct WebInfo {
/// e.g. "#fragment" part of "www.example.com/index.html#fragment".
/// Information about the URL.
pub location: Location,
}
/// Information about the URL.
///
/// Everything has been percent decoded (`%20` -> ` ` etc).
#[derive(Clone, Debug)]
pub struct Location {
/// The full URL (`location.href`) without the hash.
///
/// Example: "http://www.example.com:80/index.html?foo=bar".
pub url: String,
/// `location.protocol`
///
/// Example: "http:".
pub protocol: String,
/// `location.host`
///
/// Example: "example.com:80".
pub host: String,
/// `location.hostname`
///
/// Example: "example.com".
pub hostname: String,
/// `location.port`
///
/// Example: "80".
pub port: String,
/// The "#fragment" part of "www.example.com/index.html?query#fragment".
///
/// Note that the leading `#` is included in the string.
/// Also known as "hash-link" or "anchor".
pub web_location_hash: String,
pub hash: String,
/// The "query" part of "www.example.com/index.html?query#fragment".
///
/// Note that the leading `?` is NOT included in the string.
///
/// Use [`Self::web_query_map]` to get the parsed version of it.
pub query: String,
/// The parsed "query" part of "www.example.com/index.html?query#fragment".
///
/// "foo=42&bar%20" is parsed as `{"foo": "42", "bar ": ""}`
pub query_map: std::collections::BTreeMap<String, String>,
/// `location.origin`
///
/// Example: "http://www.example.com:80"
pub origin: String,
}
/// Information about the integration passed to the use app each frame.

Loading…
Cancel
Save