Render new tab dashboard

This commit is contained in:
2026-05-08 16:42:33 -04:00
parent e6eda3bf1f
commit cd3acf671d
2 changed files with 248 additions and 0 deletions
@@ -7,6 +7,7 @@ mod download_settings;
mod downloads; mod downloads;
mod general; mod general;
mod history; mod history;
mod new_tab;
mod notes; mod notes;
mod notes_markdown; mod notes_markdown;
mod plugin_catalog; mod plugin_catalog;
@@ -54,6 +55,7 @@ impl ElyShell {
} }
match tab.url().as_str() { match tab.url().as_str() {
"ely://new-tab" => self.render_new_tab_page(snapshot, cx),
"ely://bookmarks" => self.render_bookmarks_page(snapshot, cx), "ely://bookmarks" => self.render_bookmarks_page(snapshot, cx),
"ely://notes" => self.render_notes_page(snapshot, cx), "ely://notes" => self.render_notes_page(snapshot, cx),
"ely://reading-list" => self.render_reading_list_page(snapshot, cx), "ely://reading-list" => self.render_reading_list_page(snapshot, cx),
@@ -0,0 +1,246 @@
use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors;
use ely_domain::{BrowserTab, TabId};
use gpui::{
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString,
StatefulInteractiveElement, Styled, div, px, rgb,
};
use gpui_component::{
IconName, Sizable, StyledExt,
button::{Button, ButtonVariants},
scroll::ScrollableElement,
};
use super::{ElyShell, render_canvas_surface};
struct NewTabAction {
label: &'static str,
route: &'static str,
icon: IconName,
}
const NEW_TAB_ACTIONS: &[NewTabAction] = &[
NewTabAction { label: "History", route: "ely://history", icon: IconName::Undo2 },
NewTabAction { label: "Bookmarks", route: "ely://bookmarks", icon: IconName::BookOpen },
NewTabAction { label: "Downloads", route: "ely://downloads", icon: IconName::Folder },
NewTabAction { label: "Plugins", route: "ely://plugins", icon: IconName::Asterisk },
NewTabAction { label: "Settings", route: "ely://settings", icon: IconName::Settings2 },
];
impl ElyShell {
pub(super) fn render_new_tab_page(
&mut self,
snapshot: &BrowserSnapshot,
cx: &mut Context<Self>,
) -> AnyElement {
render_canvas_surface(
div()
.size_full()
.p_8()
.flex()
.flex_col()
.gap_6()
.child(render_new_tab_header(snapshot))
.child(render_new_tab_actions(cx))
.child(render_new_tab_metrics(snapshot))
.child(render_new_tab_tab_list(snapshot, cx)),
)
}
}
fn render_new_tab_header(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.flex()
.items_start()
.justify_between()
.gap_5()
.child(
div()
.min_w_0()
.flex()
.flex_col()
.gap_2()
.child(div().text_size(px(30.0)).text_color(rgb(colors::INK)).child("New Tab"))
.child(
div()
.text_sm()
.text_color(rgb(colors::MUTED))
.child(format!("{} space", snapshot.active_space_name)),
),
)
.child(
div()
.flex()
.items_center()
.gap_2()
.rounded_md()
.border_1()
.border_color(rgb(colors::HAIRLINE))
.px_3()
.py_2()
.text_xs()
.text_color(rgb(colors::MUTED))
.child(IconName::CircleUser)
.child(snapshot.active_profile_name.clone()),
)
.into_any_element()
}
fn render_new_tab_actions(cx: &mut Context<ElyShell>) -> AnyElement {
div()
.grid()
.grid_cols(5)
.gap_3()
.children(
NEW_TAB_ACTIONS
.iter()
.enumerate()
.map(|(index, action)| render_new_tab_action(index, action, cx)),
)
.into_any_element()
}
fn render_new_tab_action(
index: usize,
action: &'static NewTabAction,
cx: &mut Context<ElyShell>,
) -> AnyElement {
Button::new(("new-tab-action", index))
.ghost()
.small()
.icon(action.icon.clone())
.label(action.label)
.tooltip(action.label)
.on_click(cx.listener(move |shell, _, window, cx| {
shell.open_internal_tab(action.route, window, cx);
}))
.into_any_element()
}
fn render_new_tab_metrics(snapshot: &BrowserSnapshot) -> AnyElement {
div()
.border_t_1()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.py_3()
.grid()
.grid_cols(4)
.gap_4()
.child(new_tab_metric("Open Tabs", snapshot.tabs.len()))
.child(new_tab_metric("Favorites", snapshot.favorites.len()))
.child(new_tab_metric("History", snapshot.active_profile_history_entry_count))
.child(new_tab_metric("Downloads", snapshot.download_entries.len()))
.into_any_element()
}
fn new_tab_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_new_tab_tab_list(snapshot: &BrowserSnapshot, cx: &mut Context<ElyShell>) -> AnyElement {
div()
.flex_1()
.min_h_0()
.flex()
.flex_col()
.gap_3()
.child(
div()
.flex()
.items_center()
.justify_between()
.gap_3()
.child(div().text_sm().font_semibold().text_color(rgb(colors::INK)).child("Tabs"))
.child(
div()
.text_xs()
.text_color(rgb(colors::MUTED))
.child(format!("{} visible", snapshot.tabs.len())),
),
)
.child(
div()
.flex_1()
.min_h_0()
.overflow_y_scrollbar()
.border_t_1()
.border_color(rgb(colors::HAIRLINE))
.children(snapshot.tabs.iter().enumerate().map(|(index, tab)| {
render_new_tab_tab_row(index, tab, &snapshot.active_tab_id, cx)
})),
)
.into_any_element()
}
fn render_new_tab_tab_row(
index: usize,
tab: &BrowserTab,
active_tab_id: &TabId,
cx: &mut Context<ElyShell>,
) -> AnyElement {
let tab_id = tab.id().clone();
let active = tab.id() == active_tab_id;
let icon = if active { IconName::CircleCheck } else { IconName::Globe };
let icon_color = if active { colors::PRIMARY } else { colors::MUTED_SOFT };
div()
.id(SharedString::from(format!("new-tab-row-{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.select_tab(&tab_id, window, cx);
}))
.child(
div()
.min_w_0()
.flex()
.items_center()
.gap_3()
.child(div().text_color(rgb(icon_color)).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(tab.title().to_string()),
)
.child(
div()
.text_xs()
.truncate()
.text_color(rgb(colors::MUTED))
.child(tab.display_url()),
),
),
)
.child(div().text_xs().text_color(rgb(colors::MUTED)).child(if active {
"Active"
} else {
"Open"
}))
.into_any_element()
}