feat: pr tab switching btwn body/diff
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
use gpui::{HighlightStyle, IntoElement, ParentElement, Styled, div, list, px};
|
||||
use gpui::{IntoElement, ParentElement, Styled, div, list, px};
|
||||
|
||||
use crate::{
|
||||
component::code_view::{self, CodeLine, code_line, code_line_with_highlights},
|
||||
|
||||
@@ -3,4 +3,5 @@ pub(crate) mod code_view;
|
||||
pub(crate) mod diff_view;
|
||||
pub(crate) mod font_icon;
|
||||
pub(crate) mod markdown;
|
||||
pub(crate) mod segmented_control;
|
||||
pub(crate) mod text;
|
||||
|
||||
119
src/component/segmented_control.rs
Normal file
119
src/component/segmented_control.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled, div,
|
||||
prelude::FluentBuilder,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app,
|
||||
component::font_icon::{self, font_icon},
|
||||
};
|
||||
|
||||
#[derive(gpui::IntoElement)]
|
||||
pub(crate) struct SegmentedControl<T>
|
||||
where
|
||||
T: Copy + Eq + 'static,
|
||||
{
|
||||
items: Vec<SegmentedControlItem<T>>,
|
||||
selected_value: Option<T>,
|
||||
on_select: Option<Rc<dyn Fn(&T, &mut gpui::Window, &mut gpui::App)>>,
|
||||
}
|
||||
|
||||
struct SegmentedControlItem<T>
|
||||
where
|
||||
T: Copy + Eq + 'static,
|
||||
{
|
||||
value: T,
|
||||
icon: font_icon::FontIcon,
|
||||
}
|
||||
|
||||
pub(crate) fn segmented_control<T>() -> SegmentedControl<T>
|
||||
where
|
||||
T: Copy + Eq + 'static,
|
||||
{
|
||||
SegmentedControl {
|
||||
items: Vec::new(),
|
||||
selected_value: None,
|
||||
on_select: None,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SegmentedControl<T>
|
||||
where
|
||||
T: Copy + Eq + 'static,
|
||||
{
|
||||
pub(crate) fn item(mut self, value: T, icon: font_icon::FontIcon) -> Self {
|
||||
self.items
|
||||
.extend(std::iter::once(SegmentedControlItem { value, icon }));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn on_select(
|
||||
mut self,
|
||||
f: impl Fn(&T, &mut gpui::Window, &mut gpui::App) + 'static,
|
||||
) -> Self {
|
||||
self.on_select = Some(Rc::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn selected_value(mut self, value: T) -> Self {
|
||||
self.selected_value = Some(value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> gpui::RenderOnce for SegmentedControl<T>
|
||||
where
|
||||
T: Copy + Eq + 'static,
|
||||
{
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
let mut children: Vec<gpui::AnyElement> = Vec::with_capacity(self.items.len() * 2);
|
||||
for (i, item) in self.items.into_iter().enumerate() {
|
||||
let is_selected = Some(item.value) == self.selected_value;
|
||||
let cb = self.on_select.as_ref().map(Rc::clone);
|
||||
|
||||
// the segmented tab button
|
||||
children.push(
|
||||
div()
|
||||
.id(i)
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(theme.colors.surface_button)
|
||||
.child(
|
||||
font_icon(item.icon)
|
||||
.size_3p5()
|
||||
.when(is_selected, |it| it.text_color(theme.colors.accent_solid)),
|
||||
)
|
||||
.on_click(move |_, window, cx| {
|
||||
if let Some(f) = &cb {
|
||||
f(&item.value, window, cx);
|
||||
}
|
||||
})
|
||||
.into_any_element(),
|
||||
);
|
||||
|
||||
// divider next to the segmented tab
|
||||
children.push(
|
||||
div()
|
||||
.h_full()
|
||||
.w_px()
|
||||
.bg(theme.colors.border_strong)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
children.truncate(children.len() - 1);
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.border_1()
|
||||
.border_color(theme.colors.border_strong)
|
||||
.rounded_sm()
|
||||
.children(children)
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn watch_query<E, F, H>(query: &Entity<F>, on_notify: H, cx: &mut gpui::Context<E>) -> gpui::Subscription
|
||||
pub fn watch_query<E, F, H>(
|
||||
query: &Entity<F>,
|
||||
on_notify: H,
|
||||
cx: &mut gpui::Context<E>,
|
||||
) -> gpui::Subscription
|
||||
where
|
||||
E: 'static,
|
||||
F: QueryFn,
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use gpui::{
|
||||
AppContext, InteractiveElement, IntoElement, ParentElement, StatefulInteractiveElement, Styled,
|
||||
div, img, linear_gradient, prelude::FluentBuilder,
|
||||
div, img, prelude::FluentBuilder,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
button::{self, Button, button},
|
||||
font_icon::{FontIcon, font_icon},
|
||||
markdown::{self, MarkdownText},
|
||||
segmented_control::segmented_control,
|
||||
text::text,
|
||||
},
|
||||
query::{self, QueryStatus, read_query, use_query, watch_query},
|
||||
@@ -19,17 +20,23 @@ use crate::{
|
||||
};
|
||||
|
||||
pub(crate) struct PullRequestView {
|
||||
current_tab: Tab,
|
||||
|
||||
markdown_viewer: Option<gpui::Entity<MarkdownText>>,
|
||||
diff_view: Option<gpui::Entity<PullRequestDiffView>>,
|
||||
|
||||
pull_request_query: Option<query::Entity<api::issues::FetchPullRequest>>,
|
||||
}
|
||||
|
||||
#[derive(gpui::IntoElement)]
|
||||
struct Toolbar {}
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Tab {
|
||||
PullRequestBody,
|
||||
DiffView,
|
||||
}
|
||||
|
||||
pub fn new(_cx: &mut gpui::Context<PullRequestView>) -> PullRequestView {
|
||||
PullRequestView {
|
||||
current_tab: Tab::PullRequestBody,
|
||||
markdown_viewer: None,
|
||||
diff_view: None,
|
||||
pull_request_query: None,
|
||||
@@ -45,6 +52,7 @@ impl PullRequestView {
|
||||
let query = use_query(api::issues::FetchPullRequest { id }, cx);
|
||||
|
||||
self.pull_request_query = Some(query.clone());
|
||||
self.current_tab = Tab::PullRequestBody;
|
||||
|
||||
_ = watch_query(&query, Self::sync_pull_request_query, cx).detach();
|
||||
|
||||
@@ -98,6 +106,65 @@ impl PullRequestView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toolbar(&self, cx: &gpui::Context<Self>) -> gpui::AnyElement {
|
||||
fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button {
|
||||
button(id)
|
||||
.px_2p5()
|
||||
.py_1()
|
||||
.variant(button::Variant::Secondary)
|
||||
.border_0()
|
||||
}
|
||||
|
||||
fn divider() -> gpui::Div {
|
||||
div().h_full().w_px()
|
||||
}
|
||||
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_start()
|
||||
.p_1()
|
||||
.bg(theme.colors.surface)
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(
|
||||
segmented_control()
|
||||
.selected_value(self.current_tab)
|
||||
.item(Tab::PullRequestBody, FontIcon::MessageCircleMore)
|
||||
.item(Tab::DiffView, FontIcon::FileBracesCorner)
|
||||
.on_select(cx.listener(|this, tab, _, cx| {
|
||||
this.current_tab = *tab;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
toolbar_button("pr-close-btn")
|
||||
.leading(font_icon(FontIcon::PullRequestClosed))
|
||||
.mr_1(),
|
||||
)
|
||||
.child(
|
||||
toolbar_button("pr-merge-btn")
|
||||
.variant(button::Variant::Primary)
|
||||
.leading(font_icon(FontIcon::GitMerge))
|
||||
.rounded_r_none(),
|
||||
)
|
||||
.child(divider())
|
||||
.child(
|
||||
toolbar_button("chevron")
|
||||
.py_1()
|
||||
.px_0p5()
|
||||
.variant(button::Variant::Primary)
|
||||
.leading(font_icon(FontIcon::ChevronDown))
|
||||
.rounded_l_none(),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn pr_content(
|
||||
&self,
|
||||
pr: &api::issues::DetailedPullRequest,
|
||||
@@ -228,7 +295,6 @@ impl PullRequestView {
|
||||
.flex_col()
|
||||
.bg(theme.colors.surface)
|
||||
.overflow_hidden()
|
||||
.child(Toolbar {})
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
@@ -282,9 +348,18 @@ impl gpui::Render for PullRequestView {
|
||||
_window: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> 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),
|
||||
div()
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.toolbar(cx))
|
||||
.child(match &self.pull_request_query {
|
||||
| Some(q) => {
|
||||
match read_query(q, cx) {
|
||||
| QueryStatus::Loaded(pr) => match (&self.diff_view, self.current_tab) {
|
||||
| (Some(diff_view), Tab::DiffView) => diff_view.clone().into_any_element(),
|
||||
| _ => self.pr_content(pr, cx),
|
||||
},
|
||||
|
||||
| QueryStatus::Err(e) => div()
|
||||
.size_full()
|
||||
@@ -294,83 +369,10 @@ impl gpui::Render for PullRequestView {
|
||||
.size_full()
|
||||
.child("loading pr content")
|
||||
.into_any_element(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
| None => div().size_full().child("no pr selected").into_any_element(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::RenderOnce for Toolbar {
|
||||
fn render(self, _window: &mut gpui::Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
fn toolbar_button(id: impl Into<gpui::ElementId>) -> Button {
|
||||
button(id)
|
||||
.px_2p5()
|
||||
.py_1()
|
||||
.variant(button::Variant::Secondary)
|
||||
.border_0()
|
||||
}
|
||||
|
||||
fn divider() -> gpui::Div {
|
||||
div().h_full().w_px()
|
||||
}
|
||||
|
||||
let theme = app::current_theme(cx);
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_start()
|
||||
.p_1()
|
||||
.bg(theme.colors.surface)
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border_muted)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.border_t_1()
|
||||
.border_color(theme.colors.border_strong)
|
||||
.rounded_sm()
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(theme.colors.surface_elevated)
|
||||
.child(font_icon(FontIcon::MessageCircleMore).size_3p5()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(theme.colors.surface_elevated)
|
||||
.child(font_icon(FontIcon::FileBracesCorner).size_3p5()),
|
||||
),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
toolbar_button("pr-close-btn")
|
||||
.leading(font_icon(FontIcon::PullRequestClosed))
|
||||
.mr_1(),
|
||||
)
|
||||
.child(
|
||||
toolbar_button("pr-merge-btn")
|
||||
.variant(button::Variant::Primary)
|
||||
.leading(font_icon(FontIcon::GitMerge))
|
||||
.rounded_r_none(),
|
||||
)
|
||||
.child(divider())
|
||||
.child(
|
||||
toolbar_button("chevron")
|
||||
.py_1()
|
||||
.px_0p5()
|
||||
.variant(button::Variant::Primary)
|
||||
.leading(font_icon(FontIcon::ChevronDown))
|
||||
.rounded_l_none(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod catppuccin;
|
||||
pub(crate) mod syntax;
|
||||
|
||||
use gpui::Rgba;
|
||||
use gpui::{Background, Rgba};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use syntax::{HIGHLIGHT_NAMES, ThemeSyntax, ThemeSyntaxHighlight};
|
||||
@@ -33,6 +33,7 @@ pub struct ThemeColors {
|
||||
pub background: Rgba,
|
||||
pub surface: Rgba,
|
||||
pub surface_elevated: Rgba,
|
||||
pub surface_button: Background,
|
||||
pub surface_chrome: Rgba,
|
||||
pub surface_hover: Rgba,
|
||||
pub surface_active: Rgba,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::colors::{hex, hex_alpha};
|
||||
use gpui::{linear_color_stop, linear_gradient};
|
||||
|
||||
use super::{
|
||||
Theme, ThemeColors, ThemeMode, ThemeSyntaxHighlight,
|
||||
@@ -44,6 +45,11 @@ pub(crate) fn latte() -> Theme {
|
||||
background: hex(0xeff1f5),
|
||||
surface: hex(0xe6e9ef),
|
||||
surface_elevated: hex(0xeff1f5),
|
||||
surface_button: linear_gradient(
|
||||
180.,
|
||||
linear_color_stop(hex(0xf6f7fb), 0.),
|
||||
linear_color_stop(hex(0xeff1f5), 1.),
|
||||
),
|
||||
surface_chrome: hex(0xdce0e8),
|
||||
surface_hover: hex(0xdce0e8),
|
||||
surface_active: hex(0xccd0da),
|
||||
@@ -153,6 +159,11 @@ pub(crate) fn mocha() -> Theme {
|
||||
background: hex(0x1e1e2e),
|
||||
surface: hex(0x181825),
|
||||
surface_elevated: hex(0x45475a),
|
||||
surface_button: linear_gradient(
|
||||
180.,
|
||||
linear_color_stop(hex(0x4f5068), 0.),
|
||||
linear_color_stop(hex(0x45475a), 1.),
|
||||
),
|
||||
surface_chrome: hex(0x11111b),
|
||||
surface_hover: hex(0x313244),
|
||||
surface_active: hex(0x45475a),
|
||||
|
||||
@@ -94,15 +94,21 @@ pub(crate) fn diff_content(
|
||||
let old_line = old_index + i;
|
||||
let new_line = new_index + i;
|
||||
|
||||
let diff_line = match (old_line_ranges.get(old_line), new_line_ranges.get(new_line))
|
||||
{
|
||||
let diff_line = match (
|
||||
old_line_ranges.get(old_line),
|
||||
new_line_ranges.get(new_line),
|
||||
) {
|
||||
| (Some(old_range), Some(new_range)) => DiffLine {
|
||||
op: Op::Replace,
|
||||
old_line: Some(old_line),
|
||||
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
||||
old_content: Some(Arc::from(
|
||||
old_content.slice(old_range.clone()).as_str()?,
|
||||
)),
|
||||
old_byte_range: old_range.clone(),
|
||||
new_line: Some(new_line),
|
||||
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
||||
new_content: Some(Arc::from(
|
||||
new_content.slice(new_range.clone()).as_str()?,
|
||||
)),
|
||||
new_byte_range: new_range.clone(),
|
||||
},
|
||||
|
||||
@@ -112,14 +118,18 @@ pub(crate) fn diff_content(
|
||||
old_content: None,
|
||||
old_byte_range: 0..0,
|
||||
new_line: Some(new_index + i),
|
||||
new_content: Some(Arc::from(new_content.slice(new_range.clone()).as_str()?)),
|
||||
new_content: Some(Arc::from(
|
||||
new_content.slice(new_range.clone()).as_str()?,
|
||||
)),
|
||||
new_byte_range: new_range.clone(),
|
||||
},
|
||||
|
||||
| (Some(old_range), None) => DiffLine {
|
||||
op: Op::Replace,
|
||||
old_line: Some(old_index + i),
|
||||
old_content: Some(Arc::from(old_content.slice(old_range.clone()).as_str()?)),
|
||||
old_content: Some(Arc::from(
|
||||
old_content.slice(old_range.clone()).as_str()?,
|
||||
)),
|
||||
old_byte_range: old_range.clone(),
|
||||
new_line: None,
|
||||
new_content: None,
|
||||
|
||||
Reference in New Issue
Block a user