wip: pull request view & md rendering
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
use gpui::{IntoElement, ParentElement, Styled, div, list, prelude::FluentBuilder, px};
|
||||
use std::ops::Deref;
|
||||
|
||||
use gpui::{
|
||||
InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div, list,
|
||||
prelude::FluentBuilder, px,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
api::{self},
|
||||
@@ -15,21 +20,21 @@ pub(crate) struct IssueList {
|
||||
|
||||
list_state: gpui::ListState,
|
||||
list_items: Vec<IssueListItem>,
|
||||
selected_item: Option<(usize, gpui::SharedString)>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum IssueStatus {
|
||||
Draft,
|
||||
Open,
|
||||
Closed,
|
||||
pub(crate) enum Event {
|
||||
ItemSelected(api::issues::Id),
|
||||
}
|
||||
|
||||
#[derive(gpui::IntoElement, Clone)]
|
||||
struct IssueListItem {
|
||||
pub(crate) struct IssueListItem {
|
||||
id: gpui::SharedString,
|
||||
repo_name: Option<gpui::SharedString>,
|
||||
title: gpui::SharedString,
|
||||
description: Option<gpui::SharedString>,
|
||||
status: api::issues::IssueState,
|
||||
status: api::issues::PullRequestState,
|
||||
is_selected: bool,
|
||||
is_last: bool,
|
||||
is_draft: bool,
|
||||
}
|
||||
@@ -46,6 +51,7 @@ pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> IssueList {
|
||||
|
||||
list_state: gpui::ListState::new(0, gpui::ListAlignment::Top, px(100.)),
|
||||
list_items: Vec::new(),
|
||||
selected_item: None,
|
||||
};
|
||||
list.on_create(cx);
|
||||
list
|
||||
@@ -60,10 +66,16 @@ impl IssueList {
|
||||
let new_len = res.items.len();
|
||||
|
||||
let new_items = res.items.iter().enumerate().map(|(i, it)| IssueListItem {
|
||||
id: gpui::SharedString::from(it.id.deref()),
|
||||
repo_name: Some(gpui::SharedString::new(it.repo_slug.as_str())),
|
||||
title: gpui::SharedString::new(it.title.as_str()),
|
||||
description: None,
|
||||
status: it.state,
|
||||
is_selected: this
|
||||
.selected_item
|
||||
.as_ref()
|
||||
.map(|(_, id)| id.as_str() == it.id.as_str())
|
||||
.unwrap_or(false),
|
||||
is_last: i == new_len - 1,
|
||||
is_draft: it.is_draft,
|
||||
});
|
||||
@@ -74,6 +86,17 @@ impl IssueList {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_item_click(&mut self, i: usize, cx: &mut gpui::Context<Self>) {
|
||||
let Some(item_id) = self.list_items.get(i).map(|item| item.id.clone()) else {
|
||||
return;
|
||||
};
|
||||
for (j, item) in self.list_items.iter_mut().enumerate() {
|
||||
item.is_selected = i == j;
|
||||
}
|
||||
cx.notify();
|
||||
cx.emit(Event::ItemSelected(item_id.as_str().into()));
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for IssueList {
|
||||
@@ -82,15 +105,31 @@ impl gpui::Render for IssueList {
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let this = cx.entity();
|
||||
let weak = cx.entity();
|
||||
|
||||
list(self.list_state.clone(), move |i, _, cx| {
|
||||
let this = this.read(cx);
|
||||
this.list_items[i].clone().into_any_element()
|
||||
let item = {
|
||||
let this = weak.read(cx);
|
||||
this.list_items[i].clone()
|
||||
};
|
||||
let weak = weak.clone();
|
||||
|
||||
div()
|
||||
.id(item.id.clone())
|
||||
.on_click(move |_, _, cx| {
|
||||
_ = weak.update(cx, |this, cx| {
|
||||
this.on_item_click(i, cx);
|
||||
})
|
||||
})
|
||||
.child(item)
|
||||
.into_any_element()
|
||||
})
|
||||
.size_full()
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::EventEmitter<Event> for IssueList {}
|
||||
|
||||
impl gpui::RenderOnce for IssueListItem {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
@@ -108,10 +147,10 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
.opacity(0.5)
|
||||
} else {
|
||||
match self.status {
|
||||
api::issues::IssueState::Closed => {
|
||||
api::issues::PullRequestState::Closed => {
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.danger)
|
||||
}
|
||||
api::issues::IssueState::Merged => {
|
||||
api::issues::PullRequestState::Merged => {
|
||||
font_icon(FontIcon::PullRequestClosed).text_color(theme.colors.success)
|
||||
}
|
||||
_ => font_icon(FontIcon::PullRequestArrow).text_color(theme.colors.success),
|
||||
@@ -127,7 +166,8 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.p_2()
|
||||
.px_1p5()
|
||||
.py_1()
|
||||
.gap_2()
|
||||
.flex()
|
||||
.flex_row()
|
||||
@@ -155,5 +195,12 @@ impl gpui::RenderOnce for IssueListItem {
|
||||
.when(!self.is_last, |it| {
|
||||
it.border_b_1().border_color(theme.colors.border)
|
||||
})
|
||||
.when(self.is_selected, |it| {
|
||||
it.bg(theme.colors.surface_elevated)
|
||||
.border_r_1()
|
||||
.border_b_0()
|
||||
.border_color(theme.colors.accent)
|
||||
.pb(px(5.))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod issue_list;
|
||||
mod pull_request_view;
|
||||
mod screen;
|
||||
mod sidebar;
|
||||
mod titlebar;
|
||||
|
||||
156
src/screen/dashboard/pull_request_view.rs
Normal file
156
src/screen/dashboard/pull_request_view.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use gpui::{AppContext, IntoElement, ParentElement, Styled, div, prelude::FluentBuilder};
|
||||
|
||||
use crate::{
|
||||
api::{self, issues::PullRequest},
|
||||
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<gpui::Entity<MarkdownText>>,
|
||||
|
||||
pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>,
|
||||
}
|
||||
|
||||
pub fn new(cx: &mut gpui::Context<PullRequestView>) -> 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<Self>,
|
||||
) {
|
||||
let query = use_query(api::issues::FetchPullRequest { id }, cx);
|
||||
|
||||
self.pull_request_query = Some(query.clone());
|
||||
|
||||
_ = cx
|
||||
.observe(&query.clone(), move |this, _, cx| {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
this.markdown_viewer =
|
||||
maybe_content.map(|content| cx.new(|cx| markdown::new(content, cx)))
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pr_content(
|
||||
&self,
|
||||
pr: &api::issues::DetailedPullRequest,
|
||||
cx: &gpui::Context<Self>,
|
||||
) -> gpui::Div {
|
||||
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()
|
||||
.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),
|
||||
)
|
||||
.when_some(self.markdown_viewer.as_ref(), |it, viewer| {
|
||||
it.child(div().h_full().p_3p5().child(viewer.clone()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for PullRequestView {
|
||||
fn render(
|
||||
&mut self,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
match &self.pull_request_query {
|
||||
| Some(q) => match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => self.pr_content(pr, cx),
|
||||
| QueryStatus::Err(e) => div().child(format!("{:?}", e)),
|
||||
| QueryStatus::Loading => div().child("loading pr content"),
|
||||
},
|
||||
|
||||
| None => div().child("no pr selected"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use gpui::{AppContext, BorrowAppContext, ParentElement, Styled, div};
|
||||
use gpui::{AppContext, ParentElement, Styled, div};
|
||||
|
||||
use crate::{
|
||||
app,
|
||||
api, app,
|
||||
screen::dashboard::{
|
||||
issue_list::{self, IssueList},
|
||||
pull_request_view::{self, PullRequestView},
|
||||
sidebar::{self, Sidebar, SidebarItemValue},
|
||||
titlebar::{self, TitleBar},
|
||||
},
|
||||
@@ -13,6 +14,7 @@ pub(crate) struct Screen {
|
||||
titlebar: gpui::Entity<TitleBar>,
|
||||
issue_list: gpui::Entity<IssueList>,
|
||||
sidebar: gpui::Entity<Sidebar>,
|
||||
pull_request_view: gpui::Entity<PullRequestView>,
|
||||
|
||||
issue_filter: Option<&'static str>,
|
||||
}
|
||||
@@ -22,6 +24,8 @@ pub(crate) fn new(cx: &mut gpui::Context<Screen>) -> Screen {
|
||||
titlebar: cx.new(titlebar::new),
|
||||
issue_list: cx.new(issue_list::new),
|
||||
sidebar: cx.new(|_| sidebar::new()),
|
||||
pull_request_view: cx.new(pull_request_view::new),
|
||||
|
||||
issue_filter: None,
|
||||
};
|
||||
screen.on_create(cx);
|
||||
@@ -36,6 +40,14 @@ impl Screen {
|
||||
self.sidebar.update(cx, |sidebar, _| {
|
||||
sidebar.on_item_change(on_item_change);
|
||||
});
|
||||
|
||||
_ = cx
|
||||
.subscribe(&self.issue_list, |this, _, event, cx| match event {
|
||||
issue_list::Event::ItemSelected(pr_id) => {
|
||||
this.handle_issue_list_item_selected(pr_id, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn handle_sidebar_item_change(
|
||||
@@ -50,6 +62,19 @@ impl Screen {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_issue_list_item_selected(
|
||||
&mut self,
|
||||
id: &api::issues::Id,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
println!("handle issue list item selected: {:?}", id);
|
||||
self.pull_request_view.update(cx, |view, cx| {
|
||||
view.change_displayed_pull_request(id.clone(), cx);
|
||||
println!("change displayed pull request: {:?}", id);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::Render for Screen {
|
||||
@@ -71,10 +96,17 @@ impl gpui::Render for Screen {
|
||||
.flex_row()
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.child(div().w_40().h_full().child(self.sidebar.clone()))
|
||||
.child(
|
||||
div()
|
||||
.w_80()
|
||||
.w_40()
|
||||
.flex_shrink_0()
|
||||
.h_full()
|
||||
.child(self.sidebar.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_64()
|
||||
.flex_shrink_0()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface)
|
||||
.border_x_1()
|
||||
@@ -86,10 +118,12 @@ impl gpui::Render for Screen {
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.h_full()
|
||||
.bg(theme.colors.surface)
|
||||
.border_l_1()
|
||||
.border_color(theme.colors.border),
|
||||
.border_color(theme.colors.border)
|
||||
.child(self.pull_request_view.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user