Skip to content

Commit b9ba856

Browse files
authored
Universal copy direct link command that works on native & web (#11035)
1 parent df274b1 commit b9ba856

File tree

3 files changed

+54
-30
lines changed

3 files changed

+54
-30
lines changed

crates/viewer/re_ui/src/command.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ pub enum UICommand {
8888
#[cfg(debug_assertions)]
8989
ResetEguiMemory,
9090

91-
#[cfg(target_arch = "wasm32")]
9291
CopyDirectLink,
9392

9493
CopyTimeRangeLink,
@@ -284,10 +283,9 @@ impl UICommand {
284283
"Reset egui memory, useful for debugging UI code.",
285284
),
286285

287-
#[cfg(target_arch = "wasm32")]
288286
Self::CopyDirectLink => (
289287
"Copy direct link",
290-
"Copy a link to the viewer with the URL parameter set to the current .rrd data source.",
288+
"Try to copy a shareable link to the current screen. This is not supported for all data sources & viewer states.",
291289
),
292290

293291
Self::CopyTimeRangeLink => (
@@ -429,7 +427,6 @@ impl UICommand {
429427
#[cfg(debug_assertions)]
430428
Self::ResetEguiMemory => smallvec![],
431429

432-
#[cfg(target_arch = "wasm32")]
433430
Self::CopyDirectLink => smallvec![],
434431

435432
Self::CopyTimeRangeLink => smallvec![],

crates/viewer/re_viewer/src/app.rs

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,17 @@ impl App {
507507
app_blueprint: &AppBlueprint<'_>,
508508
storage_context: &StorageContext<'_>,
509509
store_context: Option<&StoreContext<'_>>,
510+
display_mode: &DisplayMode,
510511
) {
511512
while let Some(cmd) = self.command_receiver.recv_ui() {
512-
self.run_ui_command(egui_ctx, app_blueprint, storage_context, store_context, cmd);
513+
self.run_ui_command(
514+
egui_ctx,
515+
app_blueprint,
516+
storage_context,
517+
store_context,
518+
display_mode,
519+
cmd,
520+
);
513521
}
514522
}
515523

@@ -1030,8 +1038,9 @@ impl App {
10301038
&mut self,
10311039
egui_ctx: &egui::Context,
10321040
app_blueprint: &AppBlueprint<'_>,
1033-
_storage_context: &StorageContext<'_>,
1041+
storage_context: &StorageContext<'_>,
10341042
store_context: Option<&StoreContext<'_>>,
1043+
display_mode: &DisplayMode,
10351044
cmd: UICommand,
10361045
) {
10371046
let mut force_store_info = false;
@@ -1070,7 +1079,7 @@ impl App {
10701079
for item in self.state.selection_state.selected_items().iter_items() {
10711080
match item {
10721081
Item::AppId(selected_app_id) => {
1073-
for recording in _storage_context.bundle.recordings() {
1082+
for recording in storage_context.bundle.recordings() {
10741083
if recording.application_id() == selected_app_id {
10751084
selected_stores.push(recording.store_id().clone());
10761085
}
@@ -1085,7 +1094,7 @@ impl App {
10851094

10861095
let selected_stores = selected_stores
10871096
.iter()
1088-
.filter_map(|store_id| _storage_context.bundle.get(store_id))
1097+
.filter_map(|store_id| storage_context.bundle.get(store_id))
10891098
.collect_vec();
10901099

10911100
if selected_stores.is_empty() {
@@ -1391,13 +1400,8 @@ impl App {
13911400
re_ui::apply_style_and_install_loaders(egui_ctx);
13921401
}
13931402

1394-
#[cfg(target_arch = "wasm32")]
13951403
UICommand::CopyDirectLink => {
1396-
if self.run_copy_direct_link_command(store_context).is_none() {
1397-
re_log::error!(
1398-
"Failed to copy direct link to clipboard. Is this not running in a browser?"
1399-
);
1400-
}
1404+
self.run_copy_direct_link_command(storage_context, display_mode);
14011405
}
14021406

14031407
UICommand::CopyTimeRangeLink => {
@@ -1552,27 +1556,38 @@ impl App {
15521556
Ok(url)
15531557
}
15541558

1555-
#[cfg(target_arch = "wasm32")]
15561559
fn run_copy_direct_link_command(
15571560
&mut self,
1558-
store_context: Option<&StoreContext<'_>>,
1559-
) -> Option<()> {
1560-
use crate::web_tools::JsResultExt as _;
1561-
let href = self.get_viewer_url().ok_or_log_js_error()?;
1562-
1563-
let direct_link = match store_context
1564-
.map(|ctx| ctx.recording)
1565-
.and_then(|rec| rec.data_source.as_ref())
1561+
storage_context: &StorageContext<'_>,
1562+
display_mode: &DisplayMode,
1563+
) {
1564+
// TODO(rerun-io/dataplatform#2663): Should take into account dataplatform URLs if any are provided.
1565+
let base_url;
1566+
#[cfg(target_arch = "wasm32")]
15661567
{
1567-
Some(SmartChannelSource::RrdHttpStream { url, .. }) => format!("{href}?url={url}"),
1568-
_ => href,
1568+
use crate::web_tools::JsResultExt as _;
1569+
base_url = crate::web_tools::current_base_url().ok_or_log_js_error();
1570+
};
1571+
#[cfg(not(target_arch = "wasm32"))]
1572+
{
1573+
base_url = None;
15691574
};
15701575

1571-
self.egui_ctx.copy_text(direct_link.clone());
1572-
self.notifications
1573-
.success(format!("Copied {direct_link:?} to clipboard"));
1574-
1575-
Some(())
1576+
match crate::open_url::ViewerImportUrl::from_display_mode(
1577+
storage_context.hub,
1578+
display_mode.clone(),
1579+
)
1580+
.and_then(|content_url| content_url.sharable_url(base_url.as_ref()))
1581+
{
1582+
Ok(url) => {
1583+
self.egui_ctx.copy_text(url);
1584+
self.notifications
1585+
.success("Copied link to clipboard".to_owned());
1586+
}
1587+
Err(err) => {
1588+
re_log::error!("{err}");
1589+
}
1590+
}
15761591
}
15771592

15781593
fn run_copy_time_range_link_command(&mut self, store_context: Option<&StoreContext<'_>>) {
@@ -2767,11 +2782,13 @@ impl eframe::App for App {
27672782
Self::handle_dropping_files(egui_ctx, &storage_context, &self.command_sender);
27682783

27692784
// Run pending commands last (so we don't have to wait for a repaint before they are run):
2785+
let display_mode = self.state.navigation.peek().clone();
27702786
self.run_pending_ui_commands(
27712787
egui_ctx,
27722788
&app_blueprint,
27732789
&storage_context,
27742790
store_context.as_ref(),
2791+
&display_mode,
27752792
);
27762793
}
27772794
self.run_pending_system_commands(&mut store_hub, egui_ctx);

crates/viewer/re_viewer/src/web_tools.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ pub fn window() -> Result<Window, JsValue> {
7676
web_sys::window().ok_or_else(|| js_error("failed to get window object"))
7777
}
7878

79+
/// Returns the base URL of the current page.
80+
///
81+
/// E.g. if the current URL is `https://rerun.io/viewer?url=https://example.com/recording.rrd`,
82+
/// this will return `https://rerun.io/viewer`.
83+
pub fn current_base_url() -> Result<url::Url, JsValue> {
84+
let location = window()?.location().href()?;
85+
let location = url::Url::parse(&location).map_err(JsError::from)?;
86+
Ok(crate::open_url::base_url(&location))
87+
}
88+
7989
// Can't deserialize `Option<js_sys::Function>` directly, so newtype it is.
8090
#[derive(Clone, Deserialize)]
8191
#[repr(transparent)]

0 commit comments

Comments
 (0)