Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/icons/triangle-warning-fill.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion desktop/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ pub mod datagrid {
use gpui::actions;
actions!(
grid,
[CopyCell, ConfirmEdit, CancelEdit, StartEdit, CommitChanges]
[
CopyCell,
ConfirmEdit,
CancelEdit,
StartEdit,
CommitChanges,
AddRow,
DeleteRow,
DiscardChanges,
]
);
}

Expand Down
156 changes: 70 additions & 86 deletions desktop/src/component/tab.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,15 @@
use std::rc::Rc;

use assets::AppIcon;
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, ClickEvent, Div, Edges, Hsla, InteractiveElement, IntoElement, ParentElement,
Pixels, RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window, div, px,
relative,
};
use gpui_component::{ActiveTheme, Icon, IconName, Selectable, Sizable, Size, StyledExt, h_flex};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TabVariant {
Tab,
}

impl TabVariant {
fn height(self, size: Size) -> Pixels {
match size {
Size::XSmall => px(20.),
Size::Small => px(24.),
Size::Large => px(36.),
_ => px(32.),
}
}

pub(super) fn inner_height(self, size: Size) -> Pixels {
match size {
Size::XSmall => px(18.),
Size::Small => px(22.),
Size::Large => px(36.),
_ => px(30.),
}
}

fn inner_paddings(self, size: Size) -> Edges<Pixels> {
let padding_x = match size {
Size::XSmall => px(8.),
Size::Small => px(10.),
Size::Large => px(16.),
_ => px(12.),
};
Edges {
left: padding_x,
right: padding_x,
..Default::default()
}
}
}
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::spinner::Spinner;
use gpui_component::{ActiveTheme, Icon, IconName, Selectable, Sizable};

#[allow(dead_code)]
struct TabStyle {
Expand Down Expand Up @@ -132,12 +96,14 @@ pub struct Tab {
non_border_l: Option<bool>,
suffix: Option<AnyElement>,
children: Vec<AnyElement>,
size: Size,
disabled: bool,
selected: bool,
dirtied: bool,
loading: bool,
close_button: bool,
indicator_active: bool,
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
on_close: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
}

impl From<&'static str> for Tab {
Expand Down Expand Up @@ -181,12 +147,14 @@ impl Default for Tab {
non_border_l: None,
suffix: None,
children: Vec::new(),
size: Size::default(),
disabled: false,
selected: false,
dirtied: false,
loading: false,
close_button: false,
indicator_active: false,
on_click: None,
on_close: None,
}
}
}
Expand Down Expand Up @@ -226,6 +194,24 @@ impl Tab {
self
}

pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
}

pub fn close_button(mut self, close_button: bool) -> Self {
self.close_button = close_button;
self
}

pub fn on_close(
mut self,
on_close: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
) -> Self {
self.on_close = Some(Rc::new(on_close));
self
}

pub fn on_click(
mut self,
on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
Expand Down Expand Up @@ -281,13 +267,6 @@ impl Styled for Tab {
}
}

impl Sizable for Tab {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}

impl RenderOnce for Tab {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let tab_style = if self.disabled {
Expand Down Expand Up @@ -321,25 +300,29 @@ impl RenderOnce for Tab {
)
};

let inner_height = TabVariant::Tab.inner_height(self.size);
let inner_paddings = TabVariant::Tab.inner_paddings(self.size);
let height = TabVariant::Tab.height(self.size);
let icon_element = if self.loading {
Some(Spinner::new().with_size(px(14.)).into_any_element())
} else {
self.icon
.map(|icon| icon.with_size(px(14.)).into_any_element())
};

self.base
.id(self.ix)
.relative()
.flex()
.flex_wrap()
.gap_1()
.items_center()
.justify_center()
.flex_shrink_0()
.h(height)
.h_8()
.pl_5()
.when_else(self.close_button, |this| this.pr_7(), |this| this.pr_5())
.line_height(relative(1.))
.whitespace_nowrap()
.overflow_hidden()
.text_color(tab_style.fg)
.map(|this| match self.size {
Size::XSmall => this.text_xs(),
Size::Large => this.text_base(),
_ => this.text_sm(),
})
.text_sm()
.bg(tab_style.bg)
.border_l(borders_left)
.border_r(tab_style.borders.right)
Expand All @@ -355,34 +338,35 @@ impl RenderOnce for Tab {
})
})
.when_some(self.prefix, |this, prefix| this.child(prefix))
.child(
h_flex()
.flex_1()
.ml_2()
.relative()
.h(inner_height)
.line_height(relative(1.))
.whitespace_nowrap()
.items_center()
.justify_center()
.overflow_hidden()
.gap_1()
.flex_shrink_0()
.paddings(inner_paddings)
.when(self.dirtied, |this| {
this.child(
div()
.size_1p5()
.rounded_full()
.bg(cx.theme().blue)
.absolute()
.left_0(),
)
})
.when_some(self.icon, |this, icon| this.child(icon).mb_neg_1())
.when_some(self.label, |this, label| this.child(label)),
)
.when(self.dirtied, |this| {
this.child(
div()
.absolute()
.left_2()
.size_1p5()
.rounded_full()
.bg(cx.theme().blue),
)
})
.when_some(icon_element, |this, icon| this.child(icon))
.when_some(self.label, |this, label| {
this.child(div().child(label).pb_0p5())
})
.when_some(self.suffix, |this, suffix| this.child(suffix))
.when(self.close_button, |this| {
this.child(
Button::new(format!("close-tab-{}", self.ix))
.absolute()
.right_1()
.ghost()
.xsmall()
.cursor_pointer()
.icon(AppIcon::X)
.when_some(self.on_close.clone(), |this, on_close| {
this.on_click(move |event, window, cx| on_close(event, window, cx))
}),
)
})
.when(!self.disabled, |this| {
this.when_some(self.on_click.clone(), |this, on_click| {
this.on_click(move |event, window, cx| on_click(event, window, cx))
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/connection/database_node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use engine::{DatabaseBrief, DatabaseConfig, EngineError, SqlClient};
use engine::{DatabaseBrief, DatabaseConfig, SqlClient};
use gpui::*;

use crate::{connection::SchemaNode, shared::LoadState};
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async fn main() {
height: px(480.0),
}),
titlebar: Some(TitlebarOptions {
title: None,
title: Some("TruyVanSQL".into()),
appears_transparent: true,
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
Expand Down
28 changes: 9 additions & 19 deletions desktop/src/panel/tab/tab_bar.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use gpui::prelude::*;
use gpui::*;
use gpui_component::ActiveTheme;
use gpui_component::Sizable;
use gpui_component::button::{Button, ButtonVariants};
use gpui_component::h_flex;

use crate::component::tab::Tab;
use assets::AppIcon;

use crate::panel::tab::TabManager;

Expand Down Expand Up @@ -57,6 +54,7 @@ impl Render for TabBar {
let info = tab.info(cx);
let tab_title = info.title;
let is_dirty = info.is_dirty;
let is_loading = info.is_loading;
let icon = info.icon;
let is_selected = active_index == Some(i);

Expand All @@ -80,23 +78,15 @@ impl Render for TabBar {
})
.icon(icon)
.dirtied(is_dirty)
.suffix(
h_flex().child(
Button::new(format!("close-tab-{}", i))
.ghost()
.xsmall()
.mr_1()
.cursor_pointer()
.icon(AppIcon::X)
.on_click(move |_e, _window, cx| {
cx.stop_propagation();
tab_manager_for_close
.update(cx, |service, cx| service.close_tab(i, cx));
}),
),
)
.loading(is_loading)
.close_button(true)
.on_close(move |_, _, cx| {
cx.stop_propagation();
tab_manager_for_close
.update(cx, |service, cx| service.close_tab(i, cx));
})
.into_any_element()
})),
)
}
}
}
5 changes: 4 additions & 1 deletion desktop/src/panel/tab/tab_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub struct TabInfo {
/// Kiểm tra tab có dữ liệu chưa lưu hay không (hiển thị dấu chấm tròn)
pub is_dirty: bool,

/// Tab đang tải dữ liệu (hiển thị spinner loading)
pub is_loading: bool,

/// Icon hiển thị trên thanh Tab (ví dụ: biểu tượng database, biểu tượng file)
pub icon: AppIcon,
}
Expand All @@ -21,4 +24,4 @@ pub trait TabItem: Render {

/// Trả về tham chiếu Any để TabManager có thể quản lý đa hình (Type Erasure)
fn as_any(&self) -> &dyn Any;
}
}
3 changes: 2 additions & 1 deletion desktop/src/panel/tab_content/sql_editor/sql_editor_tab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl TabItem for SqlEditorTab {
TabInfo {
title: format!("SQL: {}", conn_name).into(),
is_dirty: false,
is_loading: false,
icon: AppIcon::FileSql,
}
}
Expand All @@ -73,4 +74,4 @@ impl Render for SqlEditorTab {
.child(self.toolbar.clone())
.child(self.results.clone())
}
}
}
Loading