Skip to content

Commit 436ea5c

Browse files
authored
Show loading screen when opening link (#11270)
1 parent 1178ae7 commit 436ea5c

File tree

15 files changed

+267
-135
lines changed

15 files changed

+267
-135
lines changed

crates/utils/re_smart_channel/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,12 @@ impl SmartChannelSource {
142142
///
143143
/// Returns `None` for any source that receives data dynamically through SDK calls or similar.
144144
/// For a status string that applies to all sources, see [`Self::status_string`].
145-
pub fn loading_string(&self) -> Option<String> {
145+
pub fn loading_name(&self) -> Option<String> {
146146
match self {
147147
// We only show things we know are very-soon-to-be recordings:
148-
Self::File(path) => Some(format!("Loading {}…", path.display())),
149-
Self::RrdHttpStream { url, .. } => Some(format!("Loading {url}…")),
150-
Self::RedapGrpcStream { uri, .. } => Some(format!("Loading {uri}…")),
148+
Self::File(path) => Some(path.to_string_lossy().into_owned()),
149+
Self::RrdHttpStream { url, .. } => Some(url.clone()),
150+
Self::RedapGrpcStream { uri, .. } => Some(uri.partition_id.clone()),
151151

152152
Self::RrdWebEventListener
153153
| Self::JsChannel { .. }

crates/utils/re_smart_channel/src/receive_set.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ impl<T: Send> ReceiveSet<T> {
3131
rx.push(r);
3232
}
3333

34+
/// Are we currently receiving this source?
35+
pub fn contains(&self, source: &SmartChannelSource) -> bool {
36+
self.receivers
37+
.lock()
38+
.iter()
39+
.any(|src| src.source.is_same_ignoring_uri_fragments(source))
40+
}
41+
3442
/// Disconnect from any channel with the given source.
3543
pub fn remove(&self, source: &SmartChannelSource) {
3644
self.receivers.lock().retain(|r| r.source() != source);

crates/viewer/re_data_ui/src/item_ui.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,11 @@ fn instance_path_button_to_ex(
280280
ui.selectable_label_with_icon(
281281
instance_path_icon(&query.timeline(), db, instance_path),
282282
text,
283-
ctx.selection().contains_item(&item),
283+
ctx.is_selected_or_loading(&item),
284284
re_ui::LabelStyle::Normal,
285285
)
286286
} else {
287-
ui.selectable_label(ctx.selection().contains_item(&item), text)
287+
ui.selectable_label(ctx.is_selected_or_loading(&item), text)
288288
};
289289

290290
let response = response.on_hover_ui(|ui| {
@@ -472,7 +472,7 @@ pub fn data_blueprint_button_to(
472472
) -> egui::Response {
473473
let item = Item::DataResult(view_id, InstancePath::entity_all(entity_path.clone()));
474474
let response = ui
475-
.selectable_label(ctx.selection().contains_item(&item), text)
475+
.selectable_label(ctx.is_selected_or_loading(&item), text)
476476
.on_hover_ui(|ui| {
477477
let include_subtree = false;
478478
entity_hover_card_ui(ui, ctx, query, db, entity_path, include_subtree);
@@ -624,7 +624,7 @@ pub fn app_id_button_ui(
624624
let response = ui.selectable_label_with_icon(
625625
&icons::APPLICATION,
626626
app_id.to_string(),
627-
ctx.selection().contains_item(&item),
627+
ctx.is_selected_or_loading(&item),
628628
re_ui::LabelStyle::Normal,
629629
);
630630

@@ -645,7 +645,7 @@ pub fn data_source_button_ui(
645645
let response = ui.selectable_label_with_icon(
646646
&icons::DATA_SOURCE,
647647
data_source.to_string(),
648-
ctx.selection().contains_item(&item),
648+
ctx.is_selected_or_loading(&item),
649649
re_ui::LabelStyle::Normal,
650650
);
651651

@@ -756,7 +756,7 @@ pub fn entity_db_button_ui(
756756
let mut list_item = ui
757757
.list_item()
758758
.active(ctx.store_context.is_active(&store_id))
759-
.selected(ctx.selection().contains_item(&item));
759+
.selected(ctx.is_selected_or_loading(&item));
760760

761761
if ctx.hovered().contains_item(&item) {
762762
list_item = list_item.force_hovered(true);
@@ -841,7 +841,7 @@ pub fn table_id_button_ui(
841841

842842
let mut list_item = ui
843843
.list_item()
844-
.selected(ctx.selection().contains_item(&item))
844+
.selected(ctx.is_selected_or_loading(&item))
845845
.active(ctx.active_table_id() == Some(table_id));
846846

847847
if ctx.hovered().contains_item(&item) {

crates/viewer/re_dataframe_ui/src/datafusion_table_widget.rs

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -201,18 +201,6 @@ impl<'a> DataFusionTableWidget<'a> {
201201
self
202202
}
203203

204-
fn loading_ui(ui: &mut egui::Ui) -> egui::Response {
205-
Frame::new()
206-
.inner_margin(16.0)
207-
.show(ui, |ui| {
208-
ui.horizontal(|ui| {
209-
ui.spinner();
210-
ui.label("Loading table…");
211-
});
212-
})
213-
.response
214-
}
215-
216204
/// Display the table.
217205
pub fn show(
218206
self,
@@ -232,13 +220,17 @@ impl<'a> DataFusionTableWidget<'a> {
232220
match session_ctx.table_exist(table_ref.clone()) {
233221
Ok(true) => {}
234222
Ok(false) => {
235-
Self::loading_ui(ui).on_hover_text("Waiting…");
223+
ui.loading_screen(
224+
"Loading table:",
225+
url.as_deref().or(title.as_deref()).unwrap_or(""),
226+
);
236227
return;
237228
}
238229
Err(err) => {
239-
Self::loading_ui(ui).on_hover_ui(|ui| {
240-
ui.label(err.to_string());
241-
});
230+
ui.loading_screen(
231+
"Error while loading table:",
232+
RichText::from(err.to_string()).color(ui.style().visuals.error_fg_color),
233+
);
242234
return;
243235
}
244236
}
@@ -291,7 +283,10 @@ impl<'a> DataFusionTableWidget<'a> {
291283
// still processing, nothing yet to show
292284
//TODO(ab): it can happen that we're stuck in the state. We should detect it and
293285
//produce an error
294-
Self::loading_ui(ui);
286+
ui.loading_screen(
287+
"Loading table:",
288+
url.as_deref().or(title.as_deref()).unwrap_or(""),
289+
);
295290
return;
296291
}
297292
};

crates/viewer/re_global_context/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ pub enum DisplayMode {
6767
/// The settings dialog for application-wide configuration.
6868
Settings,
6969

70+
// TODO(isse): It would be nice to only switch to newly loaded items if we
71+
// are on the loading screen for that specific item.
72+
/// A loading screen to some source.
73+
Loading(Box<re_smart_channel::SmartChannelSource>),
74+
7075
/// Regular view of the local recordings, including the current recording's viewport.
7176
LocalRecordings(StoreId),
7277

crates/viewer/re_recording_panel/src/data.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ impl<'a> AppIdData<'a> {
194194
});
195195

196196
let is_active = false;
197-
let is_selected = ctx.selection().contains_item(&Item::AppId(app_id.clone()));
197+
let is_selected = ctx.is_selected_or_loading(&Item::AppId(app_id.clone()));
198198

199199
let loaded_recordings = entity_dbs
200200
.into_iter()
@@ -252,7 +252,7 @@ impl<'a> ServerData<'a> {
252252
let origin = server.origin();
253253
let item = Item::RedapServer(origin.clone());
254254

255-
let is_selected = ctx.selection().contains_item(&item);
255+
let is_selected = ctx.is_selected_or_loading(&item);
256256
let is_active = matches!(
257257
ctx.display_mode(),
258258
DisplayMode::RedapServer(current_origin)
@@ -310,7 +310,7 @@ impl<'a> ServerEntriesData<'a> {
310310
entry_id: entry.id(),
311311
name: entry.name().to_owned(),
312312
icon: entry.icon(),
313-
is_selected: ctx.selection().contains_item(&Item::RedapEntry(
313+
is_selected: ctx.is_selected_or_loading(&Item::RedapEntry(
314314
re_uri::EntryUri {
315315
origin: origin.clone(),
316316
entry_id: entry.id(),

crates/viewer/re_recording_panel/src/recording_panel_ui.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ fn all_sections_ui(
167167

168168
if recording_panel_data.show_example_section {
169169
let item = Item::RedapServer(EXAMPLES_ORIGIN.clone());
170-
let selected = ctx.selection().contains_item(&item);
170+
let selected = ctx.is_selected_or_loading(&item);
171171
let active = matches!(
172172
ctx.display_mode(),
173173
DisplayMode::RedapServer(origin) if origin == &*EXAMPLES_ORIGIN
@@ -569,23 +569,33 @@ fn receiver_ui(
569569
receiver: &SmartChannelSource,
570570
show_hierarchal: bool,
571571
) {
572-
let Some(string) = receiver.loading_string() else {
572+
let Some(string) = receiver.loading_name() else {
573573
return;
574574
};
575575

576-
let label_content = re_ui::list_item::LabelContent::new(string).with_buttons(|ui| {
577-
let resp = ui
578-
.small_icon_button(&re_ui::icons::REMOVE, "Disconnect")
579-
.on_hover_text("Disconnect from this source");
576+
let selected = ctx.is_selected_or_loading(&Item::DataSource(receiver.clone()));
580577

581-
if resp.clicked() {
582-
ctx.connected_receivers.remove(receiver);
583-
}
584-
});
578+
let label_content = re_ui::list_item::LabelContent::new(string)
579+
.with_icon_fn(|ui, rect, _| {
580+
ui.put(rect, egui::Spinner::new());
581+
})
582+
.with_buttons(|ui| {
583+
let resp = ui
584+
.small_icon_button(&re_ui::icons::REMOVE, "Disconnect")
585+
.on_hover_text("Disconnect from this source");
586+
587+
if resp.clicked() {
588+
ctx.connected_receivers.remove(receiver);
589+
}
590+
});
585591

586592
if show_hierarchal {
587-
ui.list_item().show_hierarchical(ui, label_content);
593+
ui.list_item()
594+
.selected(selected)
595+
.show_hierarchical(ui, label_content);
588596
} else {
589-
ui.list_item().show_flat(ui, label_content);
597+
ui.list_item()
598+
.selected(selected)
599+
.show_flat(ui, label_content);
590600
}
591601
}

crates/viewer/re_ui/src/ui_ext.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,25 @@ pub trait UiExt {
784784
response
785785
}
786786

787+
fn loading_screen(
788+
&mut self,
789+
header: impl Into<egui::RichText>,
790+
source: impl Into<egui::RichText>,
791+
) {
792+
self.ui_mut().center("loading spinner", |ui| {
793+
ui.vertical_centered(|ui| {
794+
ui.spinner();
795+
ui.label(
796+
header
797+
.into()
798+
.heading()
799+
.color(ui.style().visuals.weak_text_color()),
800+
);
801+
ui.strong(source);
802+
});
803+
});
804+
}
805+
787806
/// Paints a time cursor for indicating the time on a time axis along x.
788807
fn paint_time_cursor(
789808
&self,

crates/viewer/re_viewer/src/app.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use re_viewer_context::{
1717
ComponentUiRegistry, DisplayMode, Item, PlayState, RecordingConfig, RecordingOrTable,
1818
StorageContext, StoreContext, SystemCommand, SystemCommandSender as _, TableStore, ViewClass,
1919
ViewClassRegistry, ViewClassRegistryError, command_channel,
20-
open_url::{ViewerOpenUrl, combine_with_base_url},
20+
open_url::{OpenUrlOptions, ViewerOpenUrl, combine_with_base_url},
2121
santitize_file_name,
2222
store_hub::{BlueprintPersistence, StoreHub, StoreHubStats},
2323
};
@@ -431,14 +431,14 @@ impl App {
431431

432432
/// Open a content URL in the viewer.
433433
pub fn open_url_or_file(&self, url: &str) {
434-
let follow_if_http = false;
435-
let select_redap_source_when_loaded = true;
436-
437434
if let Ok(url) = ViewerOpenUrl::from_str(url) {
438435
url.open(
439436
&self.egui_ctx,
440-
follow_if_http,
441-
select_redap_source_when_loaded,
437+
&OpenUrlOptions {
438+
follow_if_http: false,
439+
select_redap_source_when_loaded: true,
440+
show_loader: true,
441+
},
442442
&self.command_sender,
443443
);
444444
} else {
@@ -734,7 +734,14 @@ impl App {
734734
return;
735735
}
736736

737-
self.state.navigation.replace(display_mode);
737+
if matches!(display_mode, DisplayMode::Loading(_)) {
738+
self.state
739+
.selection_state
740+
.set_selection(re_viewer_context::ItemCollection::default());
741+
self.state.navigation.push(display_mode);
742+
} else {
743+
self.state.navigation.replace(display_mode);
744+
}
738745

739746
egui_ctx.request_repaint(); // Make sure we actually see the new mode.
740747
}
@@ -1393,7 +1400,7 @@ impl App {
13931400
self.state.navigation.pop();
13941401
}
13951402

1396-
DisplayMode::Settings | DisplayMode::LocalTable(_) => {
1403+
DisplayMode::Settings | DisplayMode::Loading(_) | DisplayMode::LocalTable(_) => {
13971404
re_log::debug!(
13981405
"Cannot toggle chunk store browser from current display mode: {:?}",
13991406
self.state.navigation.peek()
@@ -2747,7 +2754,11 @@ impl eframe::App for App {
27472754

27482755
// Make sure some app is active
27492756
// Must be called before `read_context` below.
2750-
if store_hub.active_app().is_none() {
2757+
if let DisplayMode::Loading(source) = self.state.navigation.peek() {
2758+
if !self.msg_receive_set().contains(source) {
2759+
self.state.navigation.pop();
2760+
}
2761+
} else if store_hub.active_app().is_none() {
27512762
let apps: std::collections::BTreeSet<&ApplicationId> = store_hub
27522763
.store_bundle()
27532764
.entity_dbs()
@@ -2821,15 +2832,15 @@ impl eframe::App for App {
28212832
self.command_sender.send_ui(cmd);
28222833
}
28232834
re_ui::CommandPaletteAction::OpenUrl(url_desc) => {
2824-
let follow_if_http = false;
2825-
let select_redap_source_when_loaded = true;
2826-
28272835
match url_desc.url.parse::<ViewerOpenUrl>() {
28282836
Ok(url) => {
28292837
url.open(
28302838
egui_ctx,
2831-
follow_if_http,
2832-
select_redap_source_when_loaded,
2839+
&OpenUrlOptions {
2840+
follow_if_http: false,
2841+
select_redap_source_when_loaded: true,
2842+
show_loader: true,
2843+
},
28332844
&self.command_sender,
28342845
);
28352846
}

0 commit comments

Comments
 (0)