diff --git a/osm2lanes-web/Cargo.toml b/osm2lanes-web/Cargo.toml index 3ad4032f..a341a809 100644 --- a/osm2lanes-web/Cargo.toml +++ b/osm2lanes-web/Cargo.toml @@ -2,7 +2,6 @@ name = "osm2lanes-web" version = "0.1.0" description = "Website for osm2lanes" -readme = true repository = "https://github.com/a-b-street/osm2lanes" license = "Apache 2.0" keywords = ["osm", "lanes", "web"] @@ -15,9 +14,9 @@ edition = "2021" [dependencies] console_log = { version = "0.2", optional = true, features = ["color"] } +console_error_panic_hook = "0.1" geo = { version = "0.20" } gloo-utils = "0.1" -gloo-timers = "0.2" leaflet = { git = "https://github.com/droogmic/leaflet-rs", branch = "polyline_get_bounds" } log = "0.4" osm-tags = { path = "../osm-tags" } @@ -28,10 +27,11 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" syntect = { version = "5.0", default-features = false, features = [ "default-fancy", -] } # TODO: https://github.com/trishume/syntect/issues/135#issuecomment-700306068, webwroker + create json bundle +] } # TODO: https://github.com/trishume/syntect/issues/135#issuecomment-700306068, webworker + create json bundle wasm-bindgen = "0.2" wee_alloc = { version = "0.4", optional = true } yew = "0.19" +gloo-worker = { git = "https://github.com/futursolo/gloo", rev = "a2101e166260294292c8121fdb8ed883dae62ed8" } [dependencies.web-sys] version = "0.3" diff --git a/osm2lanes-web/index.html b/osm2lanes-web/index.html index c0be8b02..37798928 100644 --- a/osm2lanes-web/index.html +++ b/osm2lanes-web/index.html @@ -15,8 +15,12 @@ integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==" crossorigin> + type="text/css"> + + + + \ No newline at end of file diff --git a/osm2lanes-web/src/agent.rs b/osm2lanes-web/src/agent.rs new file mode 100644 index 00000000..5813a10b --- /dev/null +++ b/osm2lanes-web/src/agent.rs @@ -0,0 +1,56 @@ +use gloo_worker::{Codec, HandlerId, Worker, WorkerScope}; +use osm2lanes::test::{get_tests, TestCase}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsValue; + +pub struct JsonCodec; + +impl Codec for JsonCodec { + fn encode(input: I) -> JsValue + where + I: Serialize, + { + let data = serde_json::to_string(&input).expect("can't serialize worker message"); + log::trace!("message in: {data}"); + JsValue::from_str(&data) + } + + fn decode(input: JsValue) -> O + where + O: for<'de> Deserialize<'de>, + { + let data = input.as_string().expect("JsValue string"); + log::trace!("message out: {data}"); + serde_json::from_str(&data).expect("can't deserialize worker message") + } +} + +pub(crate) const NAME: &str = "worker.js"; + +pub struct ExampleLoader; + +#[derive(Serialize, Deserialize)] +pub struct ExampleLoaderOutput(pub Vec); + +impl Worker for ExampleLoader { + type Message = (); + type Input = (); + type Output = ExampleLoaderOutput; + + fn create(_scope: &WorkerScope) -> Self { + Self + } + + fn update(&mut self, _scope: &WorkerScope, _msg: Self::Message) { + // no messaging + } + + fn received(&mut self, scope: &WorkerScope, _msg: Self::Input, id: HandlerId) { + let tests = get_tests(); + let examples = tests + .into_iter() + .filter(|t| t.example().is_some()) + .collect(); + scope.respond(id, ExampleLoaderOutput(examples)); + } +} diff --git a/osm2lanes-web/src/bin/app.rs b/osm2lanes-web/src/bin/app.rs new file mode 100644 index 00000000..62e7c365 --- /dev/null +++ b/osm2lanes-web/src/bin/app.rs @@ -0,0 +1,8 @@ +use osm2lanes_web::App; + +fn main() { + console_error_panic_hook::set_once(); + console_log::init_with_level(log::Level::Debug).expect("logging failed"); + log::trace!("Initializing yew..."); + yew::start_app::(); +} diff --git a/osm2lanes-web/src/bin/worker.rs b/osm2lanes-web/src/bin/worker.rs new file mode 100644 index 00000000..42f7bd33 --- /dev/null +++ b/osm2lanes-web/src/bin/worker.rs @@ -0,0 +1,11 @@ +use gloo_worker::Registrable; +use osm2lanes_web::agent::{ExampleLoader, JsonCodec}; + +fn main() { + console_error_panic_hook::set_once(); + console_log::init_with_level(log::Level::Debug).expect("logging failed"); + log::trace!("Initializing worker..."); + ExampleLoader::registrar() + .encoding::() + .register(); +} diff --git a/osm2lanes-web/src/control.rs b/osm2lanes-web/src/control.rs index dc41b3b1..1ff26098 100644 --- a/osm2lanes-web/src/control.rs +++ b/osm2lanes-web/src/control.rs @@ -2,17 +2,21 @@ use std::cell::RefCell; use std::collections::BTreeMap; use std::rc::Rc; -use gloo_timers::callback::Timeout; +use gloo_worker::{WorkerBridge, WorkerSpawner}; use osm2lanes::locale::{Country, DrivingSide, Locale}; -use osm2lanes::test::{get_tests, TestCase}; +use osm2lanes::test::TestCase; use web_sys::{Event, FocusEvent, HtmlInputElement, HtmlSelectElement, KeyboardEvent, MouseEvent}; use yew::{html, Callback, Component, Context, Html, NodeRef, Properties, TargetCast}; +use crate::agent::{ExampleLoader, ExampleLoaderOutput, JsonCodec, NAME}; use crate::{Msg as AppMsg, State}; pub(crate) enum Msg { + /// Pass the message to the parent Up(Box), - FirstLazy, + /// Worker's response with examples + WorkerMsg(ExampleLoaderOutput), + /// Select example given name Example(String), } @@ -28,8 +32,8 @@ pub(crate) struct Props { pub(crate) state: Rc>, } -#[derive(Default)] pub(crate) struct Control { + _worker: WorkerBridge, textarea_input_ref: NodeRef, textarea_output_ref: NodeRef, example: Option, @@ -40,16 +44,32 @@ impl Component for Control { type Properties = Props; type Message = Msg; - fn create(_ctx: &Context) -> Self { - Self::default() + fn create(ctx: &Context) -> Self { + let link = ctx.link().clone(); + let cb = move |output| link.send_message(Self::Message::WorkerMsg(output)); + let worker = WorkerSpawner::::new() + .encoding::() + .callback(cb) + .spawn(NAME); + + // Trigger worker + worker.send(()); + + Self { + _worker: worker, + textarea_input_ref: NodeRef::default(), + textarea_output_ref: NodeRef::default(), + example: Option::default(), + examples: Option::default(), + } } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::Up(msg) => ctx.props().callback_msg.emit(*msg), - Msg::FirstLazy => { - let tests = get_tests(); - let examples: BTreeMap<_, _> = tests + Msg::WorkerMsg(examples) => { + let examples: BTreeMap = examples + .0 .into_iter() .filter_map(|t| { let example_name = t.example().map(std::borrow::ToOwned::to_owned); @@ -256,14 +276,4 @@ impl Component for Control { } } - - fn rendered(&mut self, ctx: &Context, first_render: bool) { - if first_render { - let handle = { - let link = ctx.link().clone(); - Timeout::new(1, move || link.send_message(Msg::FirstLazy)) - }; - handle.forget(); - } - } } diff --git a/osm2lanes-web/src/main.rs b/osm2lanes-web/src/lib.rs similarity index 98% rename from osm2lanes-web/src/main.rs rename to osm2lanes-web/src/lib.rs index f7f3932f..aa51e7f7 100644 --- a/osm2lanes-web/src/main.rs +++ b/osm2lanes-web/src/lib.rs @@ -5,7 +5,7 @@ #![warn(unreachable_pub)] #![deny(unsafe_code)] #![deny(unsafe_op_in_unsafe_fn)] -#![warn(unused_crate_dependencies)] +// #![warn(unused_crate_dependencies)] // https://github.com/rust-lang/rust/issues/57274 #![warn(unused_lifetimes)] #![warn(unused_qualifications)] // Clippy @@ -57,6 +57,8 @@ use syntect::parsing::SyntaxSet; use web_sys::HtmlInputElement; use yew::prelude::*; +pub mod agent; + mod control; use control::Control; @@ -361,9 +363,3 @@ impl App { } } } - -fn main() { - console_log::init_with_level(log::Level::Debug).expect("logging failed"); - log::trace!("Initializing yew..."); - yew::start_app::(); -} diff --git a/osm2lanes/src/lib.rs b/osm2lanes/src/lib.rs index a4c53c13..d62f7931 100644 --- a/osm2lanes/src/lib.rs +++ b/osm2lanes/src/lib.rs @@ -11,7 +11,7 @@ #![warn(unreachable_pub)] #![deny(unsafe_code)] #![deny(unsafe_op_in_unsafe_fn)] -// #![warn(unused_crate_dependencies)] // TODO: report upstream +// #![warn(unused_crate_dependencies)] // https://github.com/rust-lang/rust/issues/57274 #![warn(unused_lifetimes)] #![warn(unused_qualifications)] // Clippy diff --git a/osm2lanes/src/test.rs b/osm2lanes/src/test.rs index 56b28013..005af35c 100644 --- a/osm2lanes/src/test.rs +++ b/osm2lanes/src/test.rs @@ -5,7 +5,7 @@ use crate::locale::DrivingSide; use crate::road::{Lane, Road}; #[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))] pub enum RustTesting { Enabled(bool), @@ -16,7 +16,7 @@ pub enum RustTesting { } #[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum Expected { Road(Road), @@ -26,7 +26,7 @@ pub enum Expected { #[allow(clippy::module_name_repetitions)] #[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TestCase { // Metadata /// The OSM way unique identifier @@ -478,6 +478,31 @@ mod tests { }); } + #[test] + fn test_json() { + env_logger_init(); + let tests = get_tests(); + for test in &tests { + serde_json::to_string(&test.tags).expect("can't serialize tags"); + match &test.expected { + Expected::Road(expected_road) => { + serde_json::to_string(&expected_road.lanes) + .expect("can't serialize expected road lanes"); + serde_json::to_string(&expected_road.highway) + .expect("can't serialize expected road highway"); + serde_json::to_string(&expected_road).expect("can't serialize expected road"); + }, + Expected::Output(expected_output) => { + serde_json::to_string(expected_output) + .expect("can't serialize expected output"); + }, + } + serde_json::to_string(&test.expected).expect("can't serialize expected"); + serde_json::to_string(&test).expect("can't serialize test case"); + } + serde_json::to_string(&tests).expect("can't serialize test cases"); + } + #[test] fn test_from_data() { env_logger_init();