use gpui::{ AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div, prelude::FluentBuilder, }; use crate::{ api::{self}, app, component::{ font_icon::{FontIcon, font_icon}, markdown::{self, MarkdownText}, text::text, }, query::{self, QueryStatus, read_query, use_query}, }; pub(crate) struct PullRequestView { markdown_viewer: Option>, pull_request_query: Option>, } pub fn new(cx: &mut gpui::Context) -> PullRequestView { PullRequestView { markdown_viewer: None, pull_request_query: None, } } impl PullRequestView { pub(crate) fn change_displayed_pull_request( &mut self, id: api::issues::Id, cx: &mut gpui::Context, ) { let query = use_query(api::issues::FetchPullRequest { id }, cx); self.pull_request_query = Some(query.clone()); _ = cx .observe(&query.clone(), move |this, _, cx| { this.load_markdown_content(cx); }) .detach(); // cached query will not trigger observe callback // this is required so that content is loaded immediately for cached query self.load_markdown_content(cx); cx.notify(); } fn load_markdown_content(&mut self, cx: &mut gpui::Context) { let Some(query) = &self.pull_request_query else { return; }; let maybe_content = { let data = read_query(&query, cx); if let QueryStatus::Loaded(pr) = data { Some(gpui::SharedString::new(pr.body.as_str())) } else { None } }; self.markdown_viewer = maybe_content.map(|content| cx.new(|cx| markdown::new(content, cx))); cx.notify(); } fn pr_content( &self, pr: &api::issues::DetailedPullRequest, cx: &gpui::Context, ) -> gpui::AnyElement { let theme = app::current_theme(cx); let mut status_pill = div() .flex() .flex_row() .items_center() .gap_1() .px_2() .rounded_full(); match pr.state { | api::issues::PullRequestState::Open => { status_pill = status_pill .bg(theme.colors.success) .child( font_icon(FontIcon::PullRequestArrow) .size_3() .text_color(theme.colors.accent_text), ) .child(text("Open").text_color(theme.colors.accent_text).text_xs()); } | api::issues::PullRequestState::Closed => { status_pill = status_pill .bg(theme.colors.danger) .child( font_icon(FontIcon::PullRequestClosed) .size_3() .text_color(theme.colors.accent_text), ) .child( text("Closed") .text_color(theme.colors.accent_text) .text_xs(), ); } | api::issues::PullRequestState::Merged => { status_pill = status_pill.bg(theme.colors.accent).child( text("Merged") .text_color(theme.colors.accent_text) .text_xs(), ); } } let author_pill = div() .px_2() .border_1() .border_color(theme.colors.border) .rounded_full() .bg(theme.colors.surface_elevated) .child(text("kennethnym").text_xs()); let row = div() .flex() .flex_row() .gap_2() .child(status_pill) .child(author_pill); div() .size_full() .flex() .flex_col() .overflow_hidden() .child( div() .w_full() .px_3p5() .py_3() .border_b_1() .border_color(theme.colors.border) .child(text(pr.title.clone()).w_full().text_xl().mb_2()) .child(row), ) .child( div().flex_1().min_h_0().w_full().child( div() .id("pr-body-content") .size_full() .overflow_y_scroll() .when_some(self.markdown_viewer.as_ref(), |it, viewer| { it.child(div().w_full().p_3p5().child(viewer.clone())) }), ), ) .into_any_element() } } impl gpui::Render for PullRequestView { fn render( &mut self, _window: &mut gpui::Window, cx: &mut gpui::Context, ) -> impl gpui::IntoElement { div().size_full().child(match &self.pull_request_query { | Some(q) => match read_query(q, cx) { | QueryStatus::Loaded(pr) => self.pr_content(pr, cx), | QueryStatus::Err(e) => div() .size_full() .child(format!("{:?}", e)) .into_any_element(), | QueryStatus::Loading => div() .size_full() .child("loading pr content") .into_any_element(), }, | None => div().size_full().child("no pr selected").into_any_element(), }) } }