Collapse a project's sessions by clicking its name
This commit is contained in:
1
migrations/20260726100000_add_project_collapsed/down.sql
Normal file
1
migrations/20260726100000_add_project_collapsed/down.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE projects DROP COLUMN collapsed;
|
||||||
2
migrations/20260726100000_add_project_collapsed/up.sql
Normal file
2
migrations/20260726100000_add_project_collapsed/up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE projects ADD COLUMN collapsed BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
|
CHECK (collapsed IN (FALSE, TRUE));
|
||||||
28
src/app.rs
28
src/app.rs
@@ -178,7 +178,7 @@ pub(crate) enum Message {
|
|||||||
ProjectNameChanged(String),
|
ProjectNameChanged(String),
|
||||||
ConfirmProject,
|
ConfirmProject,
|
||||||
CancelProject,
|
CancelProject,
|
||||||
SelectProject(i32),
|
ToggleProject(i32),
|
||||||
DeleteProject(i32),
|
DeleteProject(i32),
|
||||||
CreateSession(i32),
|
CreateSession(i32),
|
||||||
DiscardSession(i32),
|
DiscardSession(i32),
|
||||||
@@ -718,20 +718,22 @@ impl App {
|
|||||||
self.project_name_input.clear();
|
self.project_name_input.clear();
|
||||||
self.error = None;
|
self.error = None;
|
||||||
}
|
}
|
||||||
Message::SelectProject(project_id) => {
|
// Sidebar-only: collapsing a project leaves the open chat alone.
|
||||||
if self.generating {
|
Message::ToggleProject(project_id) => {
|
||||||
self.error =
|
let Some(item) = self
|
||||||
Some("Stop the active generation before changing sessions.".into());
|
.projects
|
||||||
|
.iter_mut()
|
||||||
|
.find(|item| item.project.id == project_id)
|
||||||
|
else {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
};
|
||||||
|
let collapsed = !item.project.collapsed;
|
||||||
|
item.project.collapsed = collapsed;
|
||||||
|
if let Some(database) = &mut self.database
|
||||||
|
&& let Err(error) = database.set_project_collapsed(project_id, collapsed)
|
||||||
|
{
|
||||||
|
self.error = Some(error);
|
||||||
}
|
}
|
||||||
self.remember_project(project_id);
|
|
||||||
self.selected_session = None;
|
|
||||||
self.conversation.clear();
|
|
||||||
self.composer.clear();
|
|
||||||
self.context_used = 0;
|
|
||||||
self.context_limit = self.preferences.context_tokens.max(0) as u32;
|
|
||||||
self.tokens_per_second = None;
|
|
||||||
self.error = None;
|
|
||||||
}
|
}
|
||||||
Message::DeleteProject(project_id) => {
|
Message::DeleteProject(project_id) => {
|
||||||
if self.generating && self.selected_project == Some(project_id) {
|
if self.generating && self.selected_project == Some(project_id) {
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ mod tests {
|
|||||||
id,
|
id,
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
path: format!("/tmp/{name}"),
|
path: format!("/tmp/{name}"),
|
||||||
|
collapsed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,12 +155,16 @@ impl App {
|
|||||||
projects = projects.push(
|
projects = projects.push(
|
||||||
row![
|
row![
|
||||||
button(
|
button(
|
||||||
row![icon(ICON_FOLDER, 17), text(&project.name).size(14)]
|
row![
|
||||||
|
text(if project.collapsed { "›" } else { "▾" }).size(12),
|
||||||
|
icon(ICON_FOLDER, 17),
|
||||||
|
text(&project.name).size(14)
|
||||||
|
]
|
||||||
.spacing(9)
|
.spacing(9)
|
||||||
.align_y(Alignment::Center),
|
.align_y(Alignment::Center),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.on_press(Message::SelectProject(project.id))
|
.on_press(Message::ToggleProject(project.id))
|
||||||
.style(button::text),
|
.style(button::text),
|
||||||
button(icon(ICON_NEW_SESSION, 15))
|
button(icon(ICON_NEW_SESSION, 15))
|
||||||
.on_press(Message::CreateSession(project.id))
|
.on_press(Message::CreateSession(project.id))
|
||||||
@@ -173,6 +177,10 @@ impl App {
|
|||||||
.align_y(Alignment::Center),
|
.align_y(Alignment::Center),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if project.collapsed {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for session in item
|
for session in item
|
||||||
.sessions
|
.sessions
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -257,6 +257,8 @@ pub struct Project {
|
|||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
/// Sidebar state: hides this project's session rows.
|
||||||
|
pub collapsed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
@@ -536,6 +538,18 @@ impl Database {
|
|||||||
.map_err(|error| error.to_string())
|
.map_err(|error| error.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_project_collapsed(
|
||||||
|
&mut self,
|
||||||
|
project_id: i32,
|
||||||
|
collapsed: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
diesel::update(projects::table.find(project_id))
|
||||||
|
.set(projects::collapsed.eq(collapsed))
|
||||||
|
.execute(&mut self.connection)
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete_project(&mut self, project_id: i32) -> Result<(), String> {
|
pub fn delete_project(&mut self, project_id: i32) -> Result<(), String> {
|
||||||
self.connection
|
self.connection
|
||||||
.transaction(|connection| {
|
.transaction(|connection| {
|
||||||
@@ -863,6 +877,9 @@ mod tests {
|
|||||||
|
|
||||||
database.set_sidebar_collapsed(true).unwrap();
|
database.set_sidebar_collapsed(true).unwrap();
|
||||||
database.set_last_project(Some(project.id)).unwrap();
|
database.set_last_project(Some(project.id)).unwrap();
|
||||||
|
assert!(!loaded[0].project.collapsed);
|
||||||
|
database.set_project_collapsed(project.id, true).unwrap();
|
||||||
|
assert!(database.load_projects().unwrap()[0].project.collapsed);
|
||||||
|
|
||||||
database.delete_project(project.id).unwrap();
|
database.delete_project(project.id).unwrap();
|
||||||
assert!(database.load_projects().unwrap().is_empty());
|
assert!(database.load_projects().unwrap().is_empty());
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ diesel::table! {
|
|||||||
id -> Integer,
|
id -> Integer,
|
||||||
name -> Text,
|
name -> Text,
|
||||||
path -> Text,
|
path -> Text,
|
||||||
|
collapsed -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user