166 lines
5.0 KiB
Rust
166 lines
5.0 KiB
Rust
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<api::issues::ListPullRequests>,
|
|
|
|
list_state: gpui::ListState,
|
|
list_items: Vec<IssueListItem>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum IssueStatus {
|
|
Draft,
|
|
Open,
|
|
Closed,
|
|
}
|
|
|
|
#[derive(gpui::IntoElement, Clone)]
|
|
struct IssueListItem {
|
|
repo_name: Option<gpui::SharedString>,
|
|
title: gpui::SharedString,
|
|
description: Option<gpui::SharedString>,
|
|
status: IssueStatus,
|
|
is_last: bool,
|
|
}
|
|
|
|
pub(crate) fn new(cx: &mut gpui::Context<IssueList>) -> 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<Self>) {
|
|
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::<Vec<_>>();
|
|
|
|
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<Self>,
|
|
) -> 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)
|
|
})
|
|
}
|
|
}
|