mod downloads; use ely_browser_core::BrowserSnapshot; use ely_design_system::{colors, spacing}; use ely_domain::{ArchiveSource, ArchivedTab, BrowserTab, HistoryEntry}; use gpui::{ AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, StatefulInteractiveElement, Styled, div, px, rgb, }; use gpui_component::{IconName, StyledExt, scroll::ScrollableElement}; use super::ElyShell; impl ElyShell { pub(super) fn render_web_canvas( &mut self, tab: &BrowserTab, snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { match tab.url().as_str() { "ely://downloads" => self.render_downloads_page(snapshot, cx), "ely://history" => self.render_history_page(snapshot, cx), "ely://archive" => self.render_archive_page(snapshot, cx), _ => render_default_page(tab), } } fn render_history_page( &mut self, snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { render_canvas_surface( div() .size_full() .p_8() .flex() .flex_col() .gap_5() .child( div() .flex() .items_end() .justify_between() .child( div() .flex() .flex_col() .gap_2() .child( div() .text_size(px(26.0)) .text_color(rgb(colors::INK)) .child("History"), ) .child(div().text_sm().text_color(rgb(colors::MUTED)).child( format!( "{} / {}", snapshot.active_profile_name, snapshot.active_space_name ), )), ) .child( div() .text_xs() .text_color(rgb(colors::MUTED)) .child(format!("{} entries", snapshot.history_entries.len())), ), ) .child(self.render_history_list(snapshot, cx)), ) } fn render_history_list( &mut self, snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { if snapshot.history_entries.is_empty() { return div() .flex_1() .border_t_1() .border_color(rgb(colors::HAIRLINE)) .pt_5() .text_sm() .text_color(rgb(colors::MUTED)) .child("History is empty for this Space and Profile.") .into_any_element(); } div() .flex_1() .flex() .flex_col() .overflow_y_scrollbar() .border_t_1() .border_color(rgb(colors::HAIRLINE)) .children( snapshot .history_entries .iter() .rev() .enumerate() .map(|(index, entry)| self.render_history_row(index, entry, cx)), ) .into_any_element() } fn render_history_row( &mut self, index: usize, entry: &HistoryEntry, cx: &mut Context, ) -> AnyElement { let url = entry.url().clone(); div() .id(SharedString::from(format!("history-{index}"))) .py_3() .border_b_1() .border_color(rgb(colors::HAIRLINE)) .flex() .items_center() .justify_between() .gap_4() .cursor_pointer() .hover(|style| style.bg(rgb(colors::CANVAS_SOFT))) .active(|style| style.opacity(0.82)) .on_click(cx.listener(move |shell, _, window, cx| { shell.open_url(url.clone(), window, cx); })) .child( div() .min_w_0() .flex() .flex_col() .gap_1() .child( div() .text_sm() .font_semibold() .truncate() .text_color(rgb(colors::INK)) .child(entry.title().to_string()), ) .child( div() .text_xs() .truncate() .text_color(rgb(colors::MUTED)) .child(entry.url().display_url()), ), ) .child(div().text_color(rgb(colors::MUTED_SOFT)).child(IconName::ExternalLink)) .into_any_element() } fn render_archive_page( &mut self, snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { render_canvas_surface( div() .size_full() .p_8() .flex() .flex_col() .gap_5() .child( div() .flex() .items_end() .justify_between() .child( div() .flex() .flex_col() .gap_2() .child( div() .text_size(px(26.0)) .text_color(rgb(colors::INK)) .child("Archived Tabs"), ) .child( div() .text_sm() .text_color(rgb(colors::MUTED)) .child("All Spaces"), ), ) .child( div() .text_xs() .text_color(rgb(colors::MUTED)) .child(format!("{} archived", snapshot.archived_tabs.len())), ), ) .child(self.render_archive_list(snapshot, cx)), ) } fn render_archive_list( &mut self, snapshot: &BrowserSnapshot, cx: &mut Context, ) -> AnyElement { if snapshot.archived_tabs.is_empty() { return div() .flex_1() .border_t_1() .border_color(rgb(colors::HAIRLINE)) .pt_5() .text_sm() .text_color(rgb(colors::MUTED)) .child("Archive is empty.") .into_any_element(); } div() .flex_1() .flex() .flex_col() .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)), ) .into_any_element() } fn render_archive_row( &mut self, index: usize, archived_tab: &ArchivedTab, cx: &mut Context, ) -> AnyElement { let tab = archived_tab.tab(); let tab_id = tab.id().clone(); div() .id(SharedString::from(format!("archive-{index}"))) .py_3() .border_b_1() .border_color(rgb(colors::HAIRLINE)) .flex() .items_center() .justify_between() .gap_4() .cursor_pointer() .hover(|style| style.bg(rgb(colors::CANVAS_SOFT))) .active(|style| style.opacity(0.82)) .on_click(cx.listener(move |shell, _, window, cx| { shell.restore_archived_tab(&tab_id, window, cx); })) .child( div() .min_w_0() .flex() .flex_col() .gap_1() .child( div() .text_sm() .font_semibold() .truncate() .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_color(rgb(colors::MUTED_SOFT)).child(IconName::Undo2)) .into_any_element() } } fn render_default_page(tab: &BrowserTab) -> AnyElement { render_canvas_surface( div() .size_full() .p_8() .flex() .flex_col() .gap_4() .child( div() .text_size(px(26.0)) .text_color(rgb(colors::INK)) .child(tab.title().to_string()), ) .child(div().text_sm().text_color(rgb(colors::MUTED)).child(render_tab_status(tab))), ) } fn render_canvas_surface(content: impl IntoElement) -> AnyElement { div() .flex_1() .h_full() .p(px(spacing::LG)) .bg(rgb(colors::CANVAS_SOFT)) .child( div() .size_full() .rounded_lg() .border_1() .border_color(rgb(colors::HAIRLINE)) .bg(rgb(colors::SURFACE_CARD)) .child(content), ) .into_any_element() } fn render_tab_status(tab: &BrowserTab) -> String { match tab.url().as_str() { "ely://new-tab" => "Ready".to_string(), url => url.to_string(), } } fn archive_source_label(source: &ArchiveSource) -> &'static str { match source { ArchiveSource::ManualClose => "Closed", ArchiveSource::AutoArchive => "Auto archived", } }