use std::str::FromStr as _;
use std::sync::Arc;
use ahash::HashMap;
use egui::{AtomExt as _, Theme};
use re_log_types::{EntryId, EntryName};
use re_ui::{Icon, LinkButton, icons};
use crate::open_url::{INTRA_RECORDING_URL_SCHEME, ViewerOpenUrl};
#[derive(Clone, Copy)]
pub enum LinkKind {
Recording,
Dataset,
Table,
Folder,
Proxy,
}
impl LinkKind {
fn icon(self, theme: Theme) -> Icon {
match (self, theme) {
(Self::Recording, Theme::Light) => icons::LINK_RECORDING_LIGHT,
(Self::Recording, Theme::Dark) => icons::LINK_RECORDING_DARK,
(Self::Dataset, Theme::Light) => icons::LINK_DATASET_LIGHT,
(Self::Dataset, Theme::Dark) => icons::LINK_DATASET_DARK,
(Self::Table, Theme::Light) => icons::LINK_TABLE_LIGHT,
(Self::Table, Theme::Dark) => icons::LINK_TABLE_DARK,
(Self::Folder, Theme::Light) => icons::LINK_FOLDER_LIGHT,
(Self::Folder, Theme::Dark) => icons::LINK_FOLDER_DARK,
(Self::Proxy, Theme::Light) => icons::LINK_PROXY_LIGHT,
(Self::Proxy, Theme::Dark) => icons::LINK_PROXY_DARK,
}
}
}
fn icon_tint(theme: Theme) -> egui::Color32 {
match theme {
Theme::Light => egui::Color32::BLACK,
Theme::Dark => egui::Color32::WHITE,
}
}
#[derive(Clone)]
pub struct ResolvedEntry {
pub name: EntryName,
pub kind: LinkKind,
}
pub type UrlNameLookup = HashMap<(re_uri::Origin, EntryId), ResolvedEntry>;
pub fn make_url_decorator(
lookup: Arc<UrlNameLookup>,
theme: Theme,
) -> impl Fn(&str) -> Option<LinkButton> + Send + Sync + 'static {
move |url| url_atoms(url, &lookup, theme)
}
pub fn url_atoms(url: &str, lookup: &UrlNameLookup, theme: Theme) -> Option<LinkButton> {
let button = match ViewerOpenUrl::from_str(url).ok()? {
ViewerOpenUrl::RedapDatasetSegment(uri) => {
let (_, dataset_label) = resolve(lookup, &uri.origin, EntryId::from(uri.dataset_id));
let atoms = dataset_segment_button(&dataset_label, uri.segment_id.as_str(), theme);
Some(LinkButton::new(url, atoms))
}
ViewerOpenUrl::RedapEntry(uri) => {
let (kind, label) = resolve(lookup, &uri.origin, uri.entry_id);
Some(LinkButton::new(url, (kind.icon(theme), label)))
}
ViewerOpenUrl::RedapCatalog(uri) => {
let label = uri.origin.host.to_string();
Some(LinkButton::new(url, (LinkKind::Proxy.icon(theme), label)))
}
ViewerOpenUrl::RedapProxy(uri) => {
let label = uri.origin.host.to_string();
Some(LinkButton::new(url, (LinkKind::Proxy.icon(theme), label)))
}
ViewerOpenUrl::RedapFolder(uri) => {
let label = folder_leaf(&uri.path);
Some(LinkButton::new(url, (LinkKind::Folder.icon(theme), label)))
}
ViewerOpenUrl::IntraRecordingSelection(_) => {
let image = icons::ENTITY.as_image().tint(icon_tint(theme));
let label = url
.strip_prefix(INTRA_RECORDING_URL_SCHEME)
.unwrap_or(url)
.to_owned();
Some(LinkButton::new(url, (image, label)))
}
ViewerOpenUrl::HttpUrl(http_url) => {
let name = http_url
.path_segments()
.and_then(|mut segments| segments.rfind(|segment| !segment.is_empty()))
.map(str::to_owned)
.unwrap_or_else(|| http_url.host_str().unwrap_or(http_url.as_str()).to_owned());
Some(LinkButton::new(
url,
(LinkKind::Recording.icon(theme), name),
))
}
#[cfg(not(target_arch = "wasm32"))]
ViewerOpenUrl::FilePath(path) => {
let name = path
.file_name()
.map(|name| name.to_string_lossy().into_owned())
.unwrap_or_else(|| path.to_string_lossy().into_owned());
Some(LinkButton::new(
url,
(LinkKind::Recording.icon(theme), name),
))
}
ViewerOpenUrl::WebViewerUrl { url_parameters, .. } => {
let inner = url_atoms(
&url_parameters.first().sharable_url(None).ok()?,
lookup,
theme,
)?;
Some(LinkButton::new(url, inner.into_atoms()))
}
ViewerOpenUrl::WebEventListener
| ViewerOpenUrl::Settings
| ViewerOpenUrl::ChunkStoreBrowser { .. } => None,
};
button.map(|b| b.tint_icons(false))
}
pub fn segment_button_atoms(segment_id: &str, theme: Theme) -> egui::Atoms<'static> {
egui::Atoms::new((LinkKind::Recording.icon(theme), segment_id.to_owned()))
}
fn dataset_segment_button(dataset: &str, segment: &str, theme: Theme) -> egui::Atoms<'static> {
let tint = icon_tint(theme);
egui::Atoms::new((
icons::DATASET.as_image().tint(tint),
dataset.to_owned(),
icons::BREADCRUMBS_SEPARATOR.as_image().tint(tint),
LinkKind::Recording.icon(theme).as_image(),
segment.to_owned().atom_shrink(true),
))
}
fn resolve(
lookup: &UrlNameLookup,
origin: &re_uri::Origin,
entry_id: EntryId,
) -> (LinkKind, String) {
if let Some(resolved) = lookup.get(&(origin.clone(), entry_id)) {
(resolved.kind, resolved.name.to_string())
} else {
(LinkKind::Dataset, short_id(&entry_id.to_string()))
}
}
fn short_id(id: &str) -> String {
const N: usize = 8;
if id.len() > N {
format!("{}…", &id[..N])
} else {
id.to_owned()
}
}
fn folder_leaf(path: &str) -> String {
path.rsplit('.')
.next()
.filter(|leaf| !leaf.is_empty())
.unwrap_or(path)
.to_owned()
}