Compare commits

...

2 Commits

Author SHA1 Message Date
Nate Butler
58a7a277df wip 2024-10-21 10:20:26 -04:00
Nate Butler
5e9f084f12 Start on quick commit UI PoC 2024-10-21 09:33:15 -04:00
3 changed files with 250 additions and 1 deletions

2
Cargo.lock generated
View File

@@ -12604,9 +12604,11 @@ version = "0.1.0"
dependencies = [
"editor",
"gpui",
"menu",
"settings",
"theme",
"ui",
"workspace",
]
[[package]]

View File

@@ -17,6 +17,8 @@ gpui.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true
workspace.workspace = true
menu.workspace = true
[features]
default = []

View File

@@ -1,3 +1,5 @@
#![allow(unused, dead_code)]
//! # UI Text Field
//!
//! This crate provides a text field component that can be used to create text fields like search inputs, form fields, etc.
@@ -5,11 +7,14 @@
//! It can't be located in the `ui` crate because it depends on `editor`.
//!
use std::default;
use editor::*;
use gpui::*;
use settings::Settings;
use theme::ThemeSettings;
use ui::*;
use ui::{List, *};
use workspace::{ModalView, Workspace};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FieldLabelLayout {
@@ -187,3 +192,243 @@ impl Render for TextField {
)
}
}
// -------------------------------------------------------------------------------------------------
actions!(quick_commit, [ToggleStageAll]);
pub const MODAL_WIDTH: f32 = 700.0;
pub const MODAL_HEIGHT: f32 = 300.0;
fn test_files() -> Vec<ChangedFile> {
vec![
ChangedFile {
id: 0,
state: FileVCSState::Modified,
file_name: "file1.txt".into(),
file_path: "/path/to/file1.txt".into(),
},
ChangedFile {
id: 1,
state: FileVCSState::Deleted,
file_name: "file2.txt".into(),
file_path: "/path/to/file2.txt".into(),
},
ChangedFile {
id: 2,
state: FileVCSState::Created,
file_name: "file3.txt".into(),
file_path: "/path/to/file3.txt".into(),
},
]
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
enum FileVCSState {
Deleted,
Modified,
Created,
}
struct ChangedFileId(usize);
impl ChangedFileId {
fn new(id: usize) -> Self {
Self(id)
}
}
// placeholder for ui
#[derive(Debug, Clone)]
struct ChangedFile {
id: usize,
state: FileVCSState,
file_name: SharedString,
file_path: SharedString,
}
struct QuickCommitState {
placeholder_text: SharedString,
tracked_files: Vec<ChangedFile>,
staged_files: Vec<usize>,
active_participant_handles: Vec<SharedString>,
editor: View<Editor>,
workspace: WeakView<Workspace>,
}
impl QuickCommitState {
fn init(
editor: View<Editor>,
workspace: WeakView<Workspace>,
cx: &mut ModelContext<Self>,
) -> Self {
let workspace = workspace.clone();
Self {
placeholder_text: "Add a message".into(),
tracked_files: Default::default(),
staged_files: Default::default(),
active_participant_handles: Default::default(),
editor,
workspace,
}
}
fn stage_state(&self) -> Selection {
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
if staged_files.len() == tracked_files.len() {
Selection::Selected
} else if staged_files.is_empty() {
Selection::Unselected
} else {
Selection::Indeterminate
}
}
fn stage_all(&mut self) -> &mut Self {
let tracked_files = self.tracked_files.clone();
self.staged_files = tracked_files.iter().map(|file| file.id).collect();
self
}
fn toggle_stage_all(&mut self) {
let stage_state = self.stage_state();
let staged_files = self.staged_files.clone();
let tracked_files = self.tracked_files.clone();
match stage_state {
Selection::Selected => {
self.staged_files.clear();
}
Selection::Unselected | Selection::Indeterminate => {
self.stage_all();
}
}
}
fn toggle_file_staged(&mut self, file_id: usize) {
if let Some(pos) = self.staged_files.iter().position() {
self.staged_files.swap_remove(pos);
} else {
self.staged_files.push(file_id);
}
}
}
pub struct QuickCommit {
state: Model<QuickCommitState>,
}
impl QuickCommit {
pub fn init(workspace: WeakView<Workspace>, cx: &mut WindowContext) -> View<Self> {
let editor = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.set_show_gutter(false, cx);
editor
});
cx.new_view(|cx| {
let state = cx
.new_model(move |cx| QuickCommitState::init(editor.clone(), workspace.clone(), cx));
Self { state }
})
}
fn stage_state(&self, cx: &ViewContext<Self>) -> Selection {
self.state.read(cx).stage_state()
}
fn toggle_stage_all(&mut self, _: &ToggleStageAll, cx: &mut ViewContext<Self>) {
self.state.update(cx, |state, _| state.toggle_stage_all());
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent)
}
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.state.read(cx).editor.focus_handle(cx)
}
}
impl QuickCommit {
fn render_file_list(&mut self, cx: &mut ViewContext<Self>) -> List {
List::new().empty_message("No changes")
}
}
impl Render for QuickCommit {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let staged_files = self.state.read(cx).staged_files.clone();
let total_tracked_files = self.state.read(cx).tracked_files.clone();
let staged_state = self.stage_state(cx);
h_flex()
.id("quick_commit_modal")
.key_context("quick_commit")
.track_focus(&self.focus_handle(cx))
.on_action(cx.listener(Self::cancel))
.occlude()
.h(px(MODAL_HEIGHT))
.w(px(MODAL_WIDTH))
.child(
// commit editor
div()
.h_full()
.flex_1()
// .child(self.editor.clone())
.child(
div()
.absolute()
.bottom_2()
.right_2()
.child(Button::new("submit_commit", "Commit")),
),
)
.child(
// file list
div()
.w(relative(0.42))
.h_full()
.border_l_1()
.border_color(cx.theme().colors().border)
// sticky header
.child(
h_flex()
.h_10()
.w_full()
.child(Label::new(format!(
"Staged Files: {}/{}",
staged_files.len(),
total_tracked_files.len()
)))
.child(Checkbox::new("toggle-stage-all", staged_state).on_click(
|_, cx| {
cx.dispatch_action(ToggleStageAll.boxed_clone());
},
)),
)
// file list
.child(self.render_file_list(cx)),
)
}
}
impl EventEmitter<DismissEvent> for QuickCommit {}
impl FocusableView for QuickCommit {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
// TODO: Not sure this is right
self.focus_handle(cx)
}
}
impl ModalView for QuickCommit {
fn fade_out_background(&self) -> bool {
true
}
}