use crate::ui::layout::SplitNode;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppState {
pub split_tree: SplitNode,
pub selected_columns: HashMap<usize, usize>,
pub selected_task_indices: HashMap<usize, usize>,
pub focused_pane: usize,
}
impl Default for AppState {
fn default() -> Self {
Self {
split_tree: SplitNode::new_leaf(0),
selected_columns: HashMap::new(),
selected_task_indices: HashMap::new(),
focused_pane: 0,
}
}
}
fn get_state_file_path() -> PathBuf {
let home_dir = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.expect("Failed to get home directory");
PathBuf::from(home_dir).join(".kanban").join("state.toml")
}
fn reload_all_pane_projects(app: &mut crate::app::App) {
fn reload_node_projects(node: &mut SplitNode, app: &mut crate::app::App) {
match node {
SplitNode::Leaf { project_id, .. } => {
if let Some(project_name) = project_id {
if let Some(project) = app.projects.iter().find(|p| &p.name == project_name) {
let project_path = project.path.clone();
let project_type = project.project_type;
if let Ok(reloaded_project) =
crate::fs::load_project_with_type(&project_path, project_type)
{
if let Some(idx) =
app.projects.iter().position(|p| &p.name == project_name)
{
app.projects[idx] = reloaded_project;
}
}
}
}
}
SplitNode::Horizontal { left, right, .. } => {
reload_node_projects(left, app);
reload_node_projects(right, app);
}
SplitNode::Vertical { top, bottom, .. } => {
reload_node_projects(top, app);
reload_node_projects(bottom, app);
}
}
}
let mut tree = app.split_tree.clone();
reload_node_projects(&mut tree, app);
app.split_tree = tree;
}
pub fn extract_state(app: &crate::app::App) -> AppState {
AppState {
split_tree: app.split_tree.clone(),
focused_pane: app.focused_pane,
selected_columns: app.selected_column.clone(),
selected_task_indices: app.selected_task_index.clone(),
}
}
pub fn save_state(state: &AppState) -> Result<()> {
let state_path = get_state_file_path();
if let Some(parent) = state_path.parent() {
std::fs::create_dir_all(parent)?;
}
let toml = toml::to_string_pretty(state)
.map_err(|e| anyhow::anyhow!("Failed to serialize state: {}", e))?;
std::fs::write(state_path, toml)?;
Ok(())
}
pub fn load_state() -> Result<AppState> {
let state_path = get_state_file_path();
if !state_path.exists() {
return Ok(AppState::default());
}
let content = std::fs::read_to_string(state_path)?;
let state: AppState = toml::from_str(&content)
.map_err(|e| anyhow::anyhow!("Failed to parse state: {}", e))?;
Ok(state)
}
pub fn apply_state(app: &mut crate::app::App, state: AppState) {
app.split_tree = state.split_tree;
reload_all_pane_projects(app);
app.selected_column = state.selected_columns;
app.selected_task_index = state.selected_task_indices;
let all_panes = app.split_tree.collect_pane_ids();
if all_panes.contains(&state.focused_pane) {
app.focused_pane = state.focused_pane;
} else if let Some(&first_pane) = all_panes.first() {
app.focused_pane = first_pane;
}
if let Some(&max_id) = all_panes.iter().max() {
app.next_pane_id = max_id + 1;
}
}