Add task manager internal page

This commit is contained in:
2026-05-08 01:03:59 -04:00
parent b8d2b291b2
commit a6077fa00a
8 changed files with 332 additions and 3 deletions
@@ -0,0 +1,285 @@
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,
}
}