diff --git a/Cargo.lock b/Cargo.lock index ffde5eef641e..a711ad56fc90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4840,7 +4840,6 @@ dependencies = [ "thiserror", "time", "wasm-bindgen-futures", - "web-sys", "web-time", "wgpu", ] diff --git a/crates/re_viewer/Cargo.toml b/crates/re_viewer/Cargo.toml index 497b1a7b3812..05515ab455c5 100644 --- a/crates/re_viewer/Cargo.toml +++ b/crates/re_viewer/Cargo.toml @@ -99,7 +99,6 @@ wgpu.workspace = true # web dependencies: [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures.workspace = true -web-sys = { workspace = true, features = ["Window"] } [build-dependencies] re_build_tools.workspace = true diff --git a/crates/re_viewer/data/quick_start_guides/python_native.md b/crates/re_viewer/data/quick_start_guides/python_native.md index 6c33bde8ba82..53d094d41050 100644 --- a/crates/re_viewer/data/quick_start_guides/python_native.md +++ b/crates/re_viewer/data/quick_start_guides/python_native.md @@ -1,5 +1,7 @@ ## Python Quick Start +${SAFARI_WARNING} + ### Installing the Rerun SDK The Rerun SDK is available on [PyPI](https://pypi.org/) under the @@ -31,3 +33,5 @@ Instead of a pre-packaged demo, you can log your own data. Copy and paste the fo ```python ${EXAMPLE_CODE} ``` + +${HOW_DOES_IT_WORK} diff --git a/crates/re_viewer/data/quick_start_guides/rust_native.md b/crates/re_viewer/data/quick_start_guides/rust_native.md index 6fc3b86d96a3..687f7726ffd0 100644 --- a/crates/re_viewer/data/quick_start_guides/rust_native.md +++ b/crates/re_viewer/data/quick_start_guides/rust_native.md @@ -1,5 +1,7 @@ ## Rust Quick Start +${SAFARI_WARNING} + ### Installing Rerun To use the Rerun SDK in your project, you need the [rerun crate](https://crates.io/crates/rerun) which you can add with `cargo add rerun`. @@ -29,3 +31,5 @@ cargo run Once everything finishes compiling, you will see the points in this viewer: ![Demo recording](https://static.rerun.io/intro_rust_result/cc780eb9bf014d8b1a68fac174b654931f92e14f/768w.png) + +${HOW_DOES_IT_WORK} diff --git a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs index cdab927e19a1..a6a4b6508270 100644 --- a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs @@ -1,6 +1,5 @@ use super::{large_text_button, status_strings, url_large_text_button, WelcomeScreenResponse}; use egui::{NumExt, Ui}; -use itertools::Itertools; use re_data_store::StoreDb; use re_log_types::{ DataRow, EntityPath, LogMsg, RowId, StoreId, StoreInfo, StoreKind, StoreSource, Time, TimePoint, @@ -8,9 +7,12 @@ use re_log_types::{ use re_smart_channel::ReceiveSet; use re_ui::UICommandSender; use re_viewer_context::{SystemCommand, SystemCommandSender}; +use std::collections::HashMap; const SPACE_VIEWS_HELP: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough"; +const HOW_DOES_IT_WORK: &str = include_str!("../../../data/quick_start_guides/how_does_it_work.md"); + /// Show the welcome page. /// /// Return `true` if the user wants to switch to the example page. @@ -66,15 +68,18 @@ fn onboarding_content_ui( if large_text_button(ui, "C++").clicked() { open_quick_start( command_sender, + include_str!("../../../data/quick_start_guides/cpp_native.md"), [ - include_str!("../../../data/quick_start_guides/cpp_native.md"), - include_str!( - "../../../data/quick_start_guides/how_does_it_work.md" + ( + "EXAMPLE_CODE", + include_str!( + "../../../data/quick_start_guides/quick_start_connect.cpp" + ), ), - ], - include_str!( - "../../../data/quick_start_guides/quick_start_connect.cpp" - ), + ("HOW_DOES_IT_WORK", HOW_DOES_IT_WORK), + ("SAFARI_WARNING", safari_warning()), + ] + .into(), "C++ Quick Start", "cpp_quick_start", ); @@ -83,11 +88,18 @@ fn onboarding_content_ui( if large_text_button(ui, "Python").clicked() { open_quick_start( command_sender, + include_str!("../../../data/quick_start_guides/python_native.md"), [ - include_str!("../../../data/quick_start_guides/python_native.md"), - include_str!("../../../data/quick_start_guides/how_does_it_work.md"), - ], - include_str!("../../../data/quick_start_guides/quick_start_connect.py"), + ( + "EXAMPLE_CODE", + include_str!( + "../../../data/quick_start_guides/quick_start_connect.py" + ), + ), + ("HOW_DOES_IT_WORK", HOW_DOES_IT_WORK), + ("SAFARI_WARNING", safari_warning()), + ] + .into(), "Python Quick Start", "python_quick_start", ); @@ -95,11 +107,18 @@ fn onboarding_content_ui( if large_text_button(ui, "Rust").clicked() { open_quick_start( command_sender, + include_str!("../../../data/quick_start_guides/rust_native.md"), [ - include_str!("../../../data/quick_start_guides/rust_native.md"), - include_str!("../../../data/quick_start_guides/how_does_it_work.md"), - ], - include_str!("../../../data/quick_start_guides/quick_start_connect.rs"), + ( + "EXAMPLE_CODE", + include_str!( + "../../../data/quick_start_guides/quick_start_connect.rs" + ), + ), + ("HOW_DOES_IT_WORK", HOW_DOES_IT_WORK), + ("SAFARI_WARNING", safari_warning()), + ] + .into(), "Rust Quick Start", "rust_quick_start", ); @@ -276,17 +295,20 @@ fn image_banner(ui: &mut egui::Ui, icon: &re_ui::Icon, column_width: f32, max_im /// Open a Quick Start recording /// -/// The `parts` are joined with newlines to form the markdown, and the spacial tag -/// `"${EXAMPLE_CODE}"` is replaced with the content of th `example_code` variable. -fn open_quick_start<'a>( +/// The markdown content may contain placeholders in the form of `${NAME}`. These will be replaced +/// with the corresponding value from the `placeholder_content` hash map. +fn open_quick_start( command_sender: &re_viewer_context::CommandSender, - parts: impl IntoIterator, - example_code: &str, + markdown: &str, + placeholder_content: HashMap<&'static str, &'static str>, app_id: &str, entity_path: &str, ) { - let mut markdown = parts.into_iter().join("\n"); - markdown = markdown.replace("${EXAMPLE_CODE}", example_code); + let mut markdown = markdown.to_owned(); + + for (key, value) in placeholder_content { + markdown = markdown.replace(format!("${{{key}}}").as_str(), value); + } let res = open_markdown_recording(command_sender, markdown.as_str(), app_id, entity_path); if let Err(err) = res { @@ -324,3 +346,35 @@ fn open_markdown_recording( Ok(()) } + +/// The User-Agent of the user's browser. +fn user_agent() -> Option { + #[cfg(target_arch = "wasm32")] + return eframe::web::user_agent(); + + #[cfg(not(target_arch = "wasm32"))] + None +} + +/// Are we running on Safari? +fn safari_warning() -> &'static str { + // Note that this implementation is very naive and might return false positives. This is ok for + // the purpose of displaying a "can't copy" warning in the Quick Start guide, but this detection + // is likely not suitable for pretty much anything else. + // + // See this page for more information on User Agent sniffing (and why/how to avoid it): + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent + + let is_safari = user_agent().is_some_and(|user_agent| { + user_agent.contains("Safari") + && !user_agent.contains("Chrome") + && !user_agent.contains("Chromium") + }); + + if is_safari { + "**Note**: This browser appears to be Safari. If you are unable to copy the code, please \ + try a different browser (see [this issue](https://github.com/emilk/egui/issues/3480))." + } else { + "" + } +}