use gpui::prelude::*; use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Subscription, Window, div, px, uniform_list, }; use gpui_component::dock::{Panel, PanelEvent}; use gpui_component::{ActiveTheme, StyledExt}; use signed_core::Announcement; use signed_state::{ProfileStore, RepoListStore}; /// Browse all announced repositories (works anonymously). pub struct RepoListView { store: Entity, focus_handle: FocusHandle, _subscription: Subscription, } impl RepoListView { pub fn new(_window: &mut Window, cx: &mut Context) -> Self { let store = cx.new(|cx| RepoListStore::new(None, cx)); let subscription = cx.observe(&store, |_this, _store, cx| cx.notify()); Self { store, focus_handle: cx.focus_handle(), _subscription: subscription, } } } impl Panel for RepoListView { fn panel_name(&self) -> &'static str { "repo_list" } fn title(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { "Explore" } } impl EventEmitter for RepoListView {} impl Focusable for RepoListView { fn focus_handle(&self, _cx: &App) -> FocusHandle { self.focus_handle.clone() } } fn render_card(announcement: &Announcement, cx: &mut App) -> AnyElement { let name = announcement .name .clone() .unwrap_or_else(|| announcement.id.clone()); let owner = ProfileStore::global(cx) .update(cx, |store, cx| store.get(announcement.owner, cx)) .name(); let description = announcement.description.clone().unwrap_or_default(); div() .v_flex() .h(px(60.)) .justify_center() .gap_1() .px_4() .border_b(px(1.)) .border_color(cx.theme().border) .child( div() .h_flex() .gap_2() .items_center() .child( div() .text_sm() .font_semibold() .whitespace_nowrap() .text_ellipsis() .child(name), ) .child( div() .text_xs() .text_color(cx.theme().muted_foreground) .whitespace_nowrap() .child(owner), ), ) .child( div() .text_xs() .text_color(cx.theme().muted_foreground) .whitespace_nowrap() .text_ellipsis() .child(description), ) .into_any_element() } impl Render for RepoListView { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let announcements = self.store.read(cx).announcements.clone(); let count = announcements.len(); let body = if announcements.is_empty() { div() .size_full() .v_flex() .items_center() .justify_center() .child( div() .text_sm() .text_color(cx.theme().muted_foreground) .child("No repositories found. Waiting for relays..."), ) .into_any_element() } else { uniform_list("repo-list", count, move |range, _window, cx| { range .map(|index| render_card(&announcements[index], cx)) .collect() }) .size_full() .into_any_element() }; div() .v_flex() .size_full() .child( div() .h_flex() .px_4() .py_2() .items_center() .child(div().text_sm().font_semibold().child("Repositories")) .child( div() .text_xs() .text_color(cx.theme().muted_foreground) .child(SharedString::from(format!(" ({count})"))), ), ) .child(body) } }