use ely_browser_core::{BrowserSnapshot, InstalledPlugin}; use ely_design_system::colors; use ely_domain::{BrowserTab, DownloadEntry, DownloadState, TabId, TabState}; use gpui::prelude::FluentBuilder; use gpui::{ AnyElement, InteractiveElement, IntoElement, ParentElement, SharedString, Styled, div, px, rgb, }; use gpui_component::{IconName, StyledExt, scroll::ScrollableElement}; use super::{ ElyShell, download_labels::{download_size_label, download_state_label}, render_canvas_surface, }; impl ElyShell { pub(super) fn render_task_manager_page(&mut self, snapshot: &BrowserSnapshot) -> AnyElement { render_canvas_surface( div() .size_full() .p_8() .flex() .flex_col() .gap_5() .child(render_task_manager_header(snapshot)) .child(render_task_summary(snapshot)) .child(render_task_table(snapshot)), ) } } fn render_task_manager_header(snapshot: &BrowserSnapshot) -> AnyElement { div() .flex() .items_end() .justify_between() .gap_4() .child( div() .flex() .flex_col() .gap_2() .child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child("Task Manager")) .child(div().text_sm().text_color(rgb(colors::MUTED)).child(format!( "{} / {}", snapshot.active_profile_name, snapshot.active_space_name ))), ) .child( div() .flex() .items_center() .gap_2() .text_xs() .font_semibold() .text_color(rgb(colors::MUTED)) .child(IconName::LayoutDashboard) .child(format!("{} local tasks", task_count(snapshot))), ) .into_any_element() } fn render_task_summary(snapshot: &BrowserSnapshot) -> AnyElement { div() .border_t_1() .border_b_1() .border_color(rgb(colors::HAIRLINE)) .py_3() .flex() .items_center() .justify_between() .gap_4() .children([ task_metric("Tabs", snapshot.tabs.len()), task_metric("Downloads", snapshot.download_entries.len()), task_metric("Plugins", snapshot.installed_plugins.len()), task_metric("Spaces", snapshot.spaces.len()), ]) .into_any_element() } fn task_metric(label: &'static str, value: usize) -> AnyElement { div() .min_w_0() .flex() .flex_col() .gap_1() .child(div().text_xs().text_color(rgb(colors::MUTED)).child(label)) .child( div().text_sm().font_semibold().text_color(rgb(colors::INK)).child(value.to_string()), ) .into_any_element() } fn render_task_table(snapshot: &BrowserSnapshot) -> AnyElement { div() .flex_1() .min_h_0() .flex() .flex_col() .overflow_y_scrollbar() .child(render_section_header("Tabs")) .children( snapshot .tabs .iter() .enumerate() .map(|(index, tab)| render_tab_task_row(index, tab, &snapshot.active_tab_id)), ) .when(!snapshot.download_entries.is_empty(), |this| { this.child(render_section_header("Downloads")).children( snapshot .download_entries .iter() .rev() .enumerate() .map(|(index, entry)| render_download_task_row(index, entry)), ) }) .when(!snapshot.installed_plugins.is_empty(), |this| { this.child(render_section_header("Plugins")).children( snapshot .installed_plugins .iter() .enumerate() .map(|(index, plugin)| render_plugin_task_row(index, plugin)), ) }) .into_any_element() } fn render_section_header(label: &'static str) -> AnyElement { div() .pt_4() .pb_2() .text_xs() .font_semibold() .text_color(rgb(colors::MUTED)) .child(label) .into_any_element() } fn render_tab_task_row(index: usize, tab: &BrowserTab, active_tab_id: &TabId) -> AnyElement { let status = tab_state_label(tab.state()); let status_color = tab_state_color(tab.state()); let scope = if tab.id() == active_tab_id { "Active tab" } else { "Tab" }; let icon = if tab.id() == active_tab_id { IconName::CircleCheck } else { IconName::Globe }; render_task_row( format!("tab-task-{index}"), icon, tab.title().to_string(), tab.display_url(), scope, status, status_color, ) } fn render_download_task_row(index: usize, entry: &DownloadEntry) -> AnyElement { render_task_row( format!("download-task-{index}"), IconName::File, entry.file_name().to_string(), download_size_label(entry), "Download", download_state_label(entry.state()), download_state_color(entry.state()), ) } fn render_plugin_task_row(index: usize, plugin: &InstalledPlugin) -> AnyElement { let status = if plugin.enabled() { "Enabled" } else { "Disabled" }; let status_color = if plugin.enabled() { colors::SUCCESS } else { colors::MUTED }; let detail = format!( "{} permissions - {} contributions", plugin.manifest().permissions().len(), plugin.manifest().contributes().len() ); render_task_row( format!("plugin-task-{index}"), IconName::Asterisk, plugin.manifest().name().to_string(), detail, "Plugin", status, status_color, ) } fn render_task_row( id: String, icon: IconName, title: String, detail: String, scope: &'static str, status: &'static str, status_color: u32, ) -> AnyElement { div() .id(SharedString::from(id)) .py_3() .border_b_1() .border_color(rgb(colors::HAIRLINE)) .flex() .items_center() .justify_between() .gap_4() .child( div() .min_w_0() .flex() .items_center() .gap_3() .child(div().text_color(rgb(colors::MUTED_SOFT)).child(icon)) .child( div() .min_w_0() .flex() .flex_col() .gap_1() .child( div() .text_sm() .font_semibold() .truncate() .text_color(rgb(colors::INK)) .child(title), ) .child( div().text_xs().truncate().text_color(rgb(colors::MUTED)).child(detail), ), ), ) .child( div() .flex() .items_center() .justify_end() .gap_3() .text_xs() .child(div().min_w(px(86.0)).text_color(rgb(colors::MUTED)).child(scope)) .child( div() .min_w(px(76.0)) .font_semibold() .text_color(rgb(status_color)) .child(status), ), ) .into_any_element() } fn task_count(snapshot: &BrowserSnapshot) -> usize { snapshot.tabs.len() + snapshot.download_entries.len() + snapshot.installed_plugins.len() } fn tab_state_label(state: &TabState) -> &'static str { match state { TabState::Loading => "Loading", TabState::Ready => "Ready", TabState::Crashed => "Crashed", TabState::Discarded => "Discarded", TabState::Archived => "Archived", } } fn tab_state_color(state: &TabState) -> u32 { match state { TabState::Loading => colors::PRIMARY, TabState::Ready => colors::SUCCESS, TabState::Crashed => colors::ERROR, TabState::Discarded | TabState::Archived => colors::MUTED, } } fn download_state_color(state: &DownloadState) -> u32 { match state { DownloadState::InProgress => colors::PRIMARY, DownloadState::Completed => colors::SUCCESS, DownloadState::Paused => colors::MUTED, DownloadState::Cancelled | DownloadState::Failed => colors::ERROR, } }