diff --git a/crates/ely_app/src/shell/archive_labels.rs b/crates/ely_app/src/shell/archive_labels.rs new file mode 100644 index 0000000..16bec1c --- /dev/null +++ b/crates/ely_app/src/shell/archive_labels.rs @@ -0,0 +1,109 @@ +use std::time::SystemTime; + +use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab, Profile, Space}; + +pub(super) fn archive_detail_label( + archived_tab: &ArchivedTab, + spaces: &[Space], + profiles: &[Profile], +) -> String { + archive_detail_label_for(archived_tab, spaces, profiles, SystemTime::now()) +} + +fn archive_detail_label_for( + archived_tab: &ArchivedTab, + spaces: &[Space], + profiles: &[Profile], + now: SystemTime, +) -> String { + let tab = archived_tab.tab(); + format!( + "{} - {} - {} - {} - {}", + tab.display_url(), + archive_space_label(tab, spaces), + archive_profile_label(tab, profiles), + archive_source_label(archived_tab.source()), + archived_at_label_for(archived_tab.archived_at(), now) + ) +} + +fn archive_source_label(source: &ArchiveSource) -> &'static str { + match source { + ArchiveSource::ManualClose => "Closed", + ArchiveSource::AutoArchive => "Auto archived", + } +} + +fn archive_space_label(tab: &BrowserTab, spaces: &[Space]) -> String { + spaces + .iter() + .find(|space| space.id() == tab.space_id()) + .map(|space| format!("Space: {}", space.name())) + .unwrap_or_else(|| format!("Space: {}", tab.space_id().as_str())) +} + +fn archive_profile_label(tab: &BrowserTab, profiles: &[Profile]) -> String { + profiles + .iter() + .find(|profile| profile.id() == tab.profile_id()) + .map(|profile| format!("Profile: {}", profile.name())) + .unwrap_or_else(|| format!("Profile: {}", tab.profile_id().as_str())) +} + +fn archived_at_label_for(archived_at: SystemTime, now: SystemTime) -> String { + let Ok(elapsed) = now.duration_since(archived_at) else { + return "Just now".to_string(); + }; + + let seconds = elapsed.as_secs(); + if seconds < 60 { + return "Just now".to_string(); + } + if seconds < 3_600 { + return archive_elapsed_label(seconds / 60, "min"); + } + if seconds < 86_400 { + return archive_elapsed_label(seconds / 3_600, "hr"); + } + if seconds < 604_800 { + return archive_elapsed_label(seconds / 86_400, "day"); + } + "Earlier".to_string() +} + +fn archive_elapsed_label(value: u64, unit: &str) -> String { + match value { + 1 => format!("1 {unit} ago"), + count => format!("{count} {unit}s ago"), + } +} + +#[cfg(test)] +mod tests { + use std::{error::Error, time::Duration}; + + use ely_domain::{ProfileKind, TabId, UrlText}; + + use super::*; + + #[test] + fn archive_detail_label_includes_space_profile_source_and_time() -> Result<(), Box> { + let profile = Profile::new("Research", 0x9fc9a2, ProfileKind::Standard); + let space = Space::new("Work", "W", 0xf54e00, profile.id().clone(), 0); + let tab = BrowserTab::new( + TabId::new(), + space.id().clone(), + profile.id().clone(), + "Research", + UrlText::parse("https://example.com/research")?, + ); + let archived_tab = ArchivedTab::new(tab, ArchiveSource::ManualClose); + let now = archived_tab.archived_at() + Duration::from_secs(7_200); + + assert_eq!( + archive_detail_label_for(&archived_tab, &[space], &[profile], now), + "example.com - Space: Work - Profile: Research - Closed - 2 hrs ago" + ); + Ok(()) + } +} diff --git a/crates/ely_app/src/shell/internal_pages.rs b/crates/ely_app/src/shell/internal_pages.rs index 7c7b41a..7d9116d 100644 --- a/crates/ely_app/src/shell/internal_pages.rs +++ b/crates/ely_app/src/shell/internal_pages.rs @@ -26,7 +26,7 @@ mod task_manager; use ely_browser_core::BrowserSnapshot; use ely_design_system::{colors, spacing}; -use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab}; +use ely_domain::{ArchivedTab, BrowserTab}; use gpui::{ AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, StatefulInteractiveElement, Styled, div, px, rgb, @@ -34,6 +34,7 @@ use gpui::{ use gpui_component::{IconName, StyledExt, scroll::ScrollableElement}; use super::ElyShell; +use super::archive_labels::archive_detail_label; impl ElyShell { pub(super) fn render_web_canvas( @@ -147,14 +148,9 @@ impl ElyShell { .overflow_y_scrollbar() .border_t_1() .border_color(rgb(colors::HAIRLINE)) - .children( - snapshot - .archived_tabs - .iter() - .rev() - .enumerate() - .map(|(index, archived_tab)| self.render_archive_row(index, archived_tab, cx)), - ) + .children(snapshot.archived_tabs.iter().rev().enumerate().map( + |(index, archived_tab)| self.render_archive_row(index, archived_tab, snapshot, cx), + )) .into_any_element() } @@ -162,10 +158,12 @@ impl ElyShell { &mut self, index: usize, archived_tab: &ArchivedTab, + snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { let tab = archived_tab.tab(); let tab_id = tab.id().clone(); + let detail = archive_detail_label(archived_tab, &snapshot.spaces, &snapshot.profiles); div() .id(SharedString::from(format!("archive-{index}"))) @@ -196,13 +194,7 @@ impl ElyShell { .text_color(rgb(colors::INK)) .child(tab.title().to_string()), ) - .child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child( - format!( - "{} - {}", - tab.display_url(), - archive_source_label(archived_tab.source()) - ), - )), + .child(div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(detail)), ) .child(div().text_color(rgb(colors::MUTED_SOFT)).child(IconName::Undo2)) .into_any_element() @@ -251,10 +243,3 @@ fn render_tab_status(tab: &BrowserTab) -> String { url => url.to_string(), } } - -fn archive_source_label(source: &ArchiveSource) -> &'static str { - match source { - ArchiveSource::ManualClose => "Closed", - ArchiveSource::AutoArchive => "Auto archived", - } -} diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index eef5b3c..d001539 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -1,3 +1,4 @@ +mod archive_labels; mod bookmarks; mod downloads; mod history; diff --git a/crates/ely_app/src/shell/render.rs b/crates/ely_app/src/shell/render.rs index 38a5fee..9ff9653 100644 --- a/crates/ely_app/src/shell/render.rs +++ b/crates/ely_app/src/shell/render.rs @@ -1,6 +1,6 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::{ELY_THEME, colors, spacing}; -use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab, Profile, Space}; +use ely_domain::{ArchivedTab, BrowserTab, Profile, Space}; use gpui::{ AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window, div, px, rgb, @@ -12,7 +12,7 @@ use gpui_component::{ }; use super::sidebar::{collapsed_sidebar_active, render_command_bar_identity}; -use super::{ElyShell, ShellState}; +use super::{ElyShell, ShellState, archive_labels::archive_detail_label}; impl Render for ElyShell { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { @@ -195,7 +195,7 @@ impl ElyShell { .archived_tabs .iter() .rev() - .map(|archived_tab| self.render_archived_row(archived_tab, cx)), + .map(|archived_tab| self.render_archived_row(archived_tab, snapshot, cx)), ) .child(div().flex_1()) .child(section_label("Profile")) @@ -383,10 +383,12 @@ impl ElyShell { fn render_archived_row( &mut self, archived_tab: &ArchivedTab, + snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { let tab = archived_tab.tab(); let tab_id = tab.id().clone(); + let detail = archive_detail_label(archived_tab, &snapshot.spaces, &snapshot.profiles); div() .id(SharedString::from(format!("archived-{}", tab.id().as_str()))) @@ -419,11 +421,7 @@ impl ElyShell { .text_color(rgb(colors::INK)) .child(tab.title().to_string()), ) - .child(div().text_xs().text_color(rgb(colors::MUTED)).child(format!( - "{} - {}", - tab.display_url(), - archive_source_label(archived_tab.source()) - ))), + .child(div().text_xs().text_color(rgb(colors::MUTED)).child(detail)), ) .into_any_element() } @@ -455,13 +453,6 @@ fn active_sidebar_width(snapshot: &BrowserSnapshot) -> Result { Ok(f32::from(active_space.sidebar_width_px())) } -fn archive_source_label(source: &ArchiveSource) -> &'static str { - match source { - ArchiveSource::ManualClose => "Closed", - ArchiveSource::AutoArchive => "Auto archived", - } -} - fn sidebar_tab_detail(tab: &BrowserTab, profiles: &[Profile]) -> String { format!("{} - {}", tab.display_url(), tab_profile_label(tab, profiles)) } diff --git a/docs/ui-shell.md b/docs/ui-shell.md index 5a80efb..96a1187 100644 --- a/docs/ui-shell.md +++ b/docs/ui-shell.md @@ -16,6 +16,7 @@ │ ● New Tab │ │ │ Archive │ │ │ ↶ servo.org │ │ +│ Space: Work - Profile: Default - Closed - Just now │ │ │ │ │ Profile │ Ready │ │ Default │ │