use gpui::{IntoElement, ParentElement, Styled, div, list, prelude::FluentBuilder, px}; use crate::{ api::{self}, app, component::{ font_icon::{FontIcon, font_icon}, text::text, }, query::{self, QueryStatus, read_query, use_query}, }; pub(crate) struct IssueList { pr_query: query::Entity, list_state: gpui::ListState, list_items: Vec, } #[derive(Clone)] enum IssueStatus { Draft, Open, Closed, } #[derive(gpui::IntoElement, Clone)] struct IssueListItem { repo_name: Option, title: gpui::SharedString, description: Option, status: IssueStatus, is_last: bool, } pub(crate) fn new(cx: &mut gpui::Context) -> IssueList { let mut list = IssueList { pr_query: use_query( api::issues::ListPullRequests { filter: Some(api::issues::Issue::FILTER_ALL), page: 1, }, cx, ), list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)), list_items: Vec::new(), }; list.on_create(cx); list } impl IssueList { fn on_create(&mut self, cx: &mut gpui::Context) { cx.observe(&self.pr_query, |this, _, cx| { let data = read_query(&this.pr_query, cx); if let QueryStatus::Loaded(issues) = data { let old_len = this.list_state.item_count(); let new_len = issues.len(); this.list_items = issues .iter() .enumerate() .map(|(i, it)| IssueListItem { repo_name: it.repository.as_ref().map(|it| { gpui::SharedString::new(format!("{}/{}", it.owner.login, it.name)) }), title: gpui::SharedString::new(it.title.as_str()), description: it .body_text .as_ref() .map(|it| gpui::SharedString::new(it.as_str())), status: if it.state == "open" { IssueStatus::Open } else if it.state == "closed" { IssueStatus::Closed } else { IssueStatus::Draft }, is_last: i == new_len - 1, }) .collect::>(); this.list_state.splice(old_len..old_len, new_len); } }) .detach(); } } impl gpui::Render for IssueList { fn render( &mut self, _window: &mut gpui::Window, cx: &mut gpui::Context, ) -> impl gpui::IntoElement { let this = cx.entity(); list(self.list_state.clone(), move |i, _, cx| { let this = this.read(cx); this.list_items[i].clone().into_any_element() }) .size_full() } } impl gpui::RenderOnce for IssueListItem { fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement { let theme = app::current_theme(cx); let repo_name_text = match self.repo_name { Some(name) => text(name), None => text("Unknown repo"), } .text_xs() .opacity(0.5); let icon = match self.status { IssueStatus::Draft => font_icon(FontIcon::PullRequestDraft) .text_color(theme.colors.text) .opacity(0.5), IssueStatus::Open => { font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success) } IssueStatus::Closed => { font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger) } } .flex_shrink_0() .size_4(); let description_text = match self.description { Some(description) => text(description).text_xs(), None => text("No description provided").opacity(0.5).text_xs(), }; div() .w_full() .p_2() .gap_2() .flex() .flex_row() .items_center() .child(icon) .child( div() .flex_1() .flex() .flex_col() .w_full() .pr_2() .child(repo_name_text) .child( text(self.title) .text_sm() .leading_tight() .medium() .styled(|it| it.w_full().min_w_0().line_clamp(2)), ) .child(description_text), ) .when(!self.is_last, |it| { it.border_b_1().border_color(theme.colors.border) }) } }