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.
This commit is contained in:
parent
4e316d32e5
commit
b5c8f034e7
9 changed files with 156 additions and 8 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1101,6 +1101,7 @@ dependencies = [
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
"epi",
|
"epi",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"percent-encoding",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
@ -14,6 +14,7 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
|
||||||
* Added `NativeOptions::initial_window_pos`.
|
* Added `NativeOptions::initial_window_pos`.
|
||||||
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
|
* 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)).
|
* 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
|
## 0.16.0 - 2021-12-29
|
||||||
|
|
|
@ -172,6 +172,12 @@ impl BackendPanel {
|
||||||
|
|
||||||
show_integration_name(ui, &frame.info());
|
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
|
// For instance: `egui_web` sets `pixels_per_point` every frame to force
|
||||||
// egui to use the same scale as the web zoom factor.
|
// 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();
|
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
|
||||||
|
|
|
@ -69,7 +69,7 @@ impl epi::App for WrapApp {
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
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();
|
self.selected_anchor = anchor.to_owned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)).
|
* 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)).
|
* 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`.
|
* 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
|
## 0.16.0 - 2021-12-29
|
||||||
|
|
|
@ -57,6 +57,7 @@ epi = { version = "0.16.0", path = "../epi" }
|
||||||
|
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
|
percent-encoding = "2.1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
|
@ -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 struct AppRunner {
|
||||||
pub(crate) frame: epi::Frame,
|
pub(crate) frame: epi::Frame,
|
||||||
egui_ctx: egui::Context,
|
egui_ctx: egui::Context,
|
||||||
|
@ -108,7 +179,7 @@ impl AppRunner {
|
||||||
info: epi::IntegrationInfo {
|
info: epi::IntegrationInfo {
|
||||||
name: painter.name(),
|
name: painter.name(),
|
||||||
web_info: Some(epi::WebInfo {
|
web_info: Some(epi::WebInfo {
|
||||||
web_location_hash: location_hash().unwrap_or_default(),
|
location: web_location(),
|
||||||
}),
|
}),
|
||||||
prefer_dark_mode,
|
prefer_dark_mode,
|
||||||
cpu_usage: None,
|
cpu_usage: None,
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub use web_sys;
|
||||||
|
|
||||||
pub use painter::Painter;
|
pub use painter::Painter;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
@ -353,9 +354,23 @@ pub fn open_url(url: &str, new_tab: bool) -> Option<()> {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
|
/// e.g. "#fragment" part of "www.example.com/index.html#fragment",
|
||||||
pub fn location_hash() -> Option<String> {
|
///
|
||||||
web_sys::window()?.location().hash().ok()
|
/// 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
|
/// 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
|
// `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 {
|
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()>);
|
}) as Box<dyn FnMut()>);
|
||||||
window.add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref())?;
|
window.add_event_listener_with_callback("hashchange", closure.as_ref().unchecked_ref())?;
|
||||||
|
|
|
@ -361,10 +361,62 @@ impl Frame {
|
||||||
/// Information about the web environment (if applicable).
|
/// Information about the web environment (if applicable).
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct WebInfo {
|
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.
|
/// Note that the leading `#` is included in the string.
|
||||||
/// Also known as "hash-link" or "anchor".
|
/// 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.
|
/// Information about the integration passed to the use app each frame.
|
||||||
|
|
Loading…
Reference in a new issue