| use serde::{Deserialize, Serialize};
|
| use tauri::AppHandle;
|
| use uuid::Uuid;
|
|
|
| #[derive(Debug, Clone, Serialize, Deserialize)]
|
| pub struct ProjectEntry {
|
| pub id: String,
|
| pub title: String,
|
| pub element_count: usize,
|
| pub saved_at: i64,
|
| }
|
|
|
| #[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
| pub struct ProjectIndex {
|
| pub projects: Vec<ProjectEntry>,
|
| pub active_id: Option<String>,
|
| }
|
|
|
| const INDEX_FILE: &str = "projects_index.json";
|
|
|
| fn project_file(id: &str) -> String { format!("project_{id}.json") }
|
|
|
| #[tauri::command]
|
| pub fn projects_list(app: AppHandle) -> Result<Vec<ProjectEntry>, String> {
|
| let index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| Ok(index.projects)
|
| }
|
|
|
| #[tauri::command]
|
| pub fn projects_get_active_id(app: AppHandle) -> Result<Option<String>, String> {
|
| let index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| Ok(index.active_id)
|
| }
|
|
|
|
|
| #[tauri::command]
|
| pub fn project_create(app: AppHandle, title: Option<String>) -> Result<ProjectEntry, String> {
|
| let mut index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
|
|
|
|
| let base_title = title.unwrap_or_else(|| "Untitled Board".to_string());
|
| let mut final_title = base_title.clone();
|
| let mut counter = 2;
|
| while index.projects.iter().any(|p| p.title == final_title) {
|
| final_title = format!("{base_title} {counter}");
|
| counter += 1;
|
| }
|
|
|
| let id = Uuid::new_v4().to_string();
|
| let now = chrono::Utc::now().timestamp();
|
| let entry = ProjectEntry { id: id.clone(), title: final_title, element_count: 0, saved_at: now };
|
|
|
|
|
| crate::persistence::save_json(&app, &project_file(&id), &serde_json::json!({ "title": &entry.title }))?;
|
|
|
|
|
| index.projects.insert(0, entry.clone());
|
| index.active_id = Some(id);
|
| crate::persistence::save_json(&app, INDEX_FILE, &index)?;
|
|
|
| Ok(entry)
|
| }
|
|
|
|
|
| #[tauri::command]
|
| pub fn project_save(app: AppHandle, id: String, state: String, title: Option<String>) -> Result<(), String> {
|
|
|
| let parsed: serde_json::Value = serde_json::from_str(&state).unwrap_or(serde_json::json!({}));
|
| let images_count = parsed.get("images").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
| let notes_count = parsed.get("textNotes").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
| let annotations_count = parsed.get("annotations").and_then(|v| v.as_array()).map(|a| a.len()).unwrap_or(0);
|
| let element_count = images_count + notes_count + annotations_count;
|
|
|
|
|
| crate::persistence::save_json(&app, &project_file(&id), &parsed)?;
|
|
|
|
|
| let mut index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| let now = chrono::Utc::now().timestamp();
|
| if let Some(entry) = index.projects.iter_mut().find(|p| p.id == id) {
|
| entry.element_count = element_count;
|
| entry.saved_at = now;
|
| if let Some(t) = title { entry.title = t; }
|
| }
|
| index.active_id = Some(id);
|
| crate::persistence::save_json(&app, INDEX_FILE, &index)?;
|
| Ok(())
|
| }
|
|
|
|
|
| #[tauri::command]
|
| pub fn project_load(app: AppHandle, id: String) -> Result<String, String> {
|
| let path = crate::persistence::app_file_path(&app, &project_file(&id))?;
|
| if !path.exists() { return Ok("{}".to_string()); }
|
| let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;
|
|
|
|
|
| let mut index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| index.active_id = Some(id);
|
| crate::persistence::save_json(&app, INDEX_FILE, &index)?;
|
|
|
| Ok(content)
|
| }
|
|
|
|
|
| #[tauri::command]
|
| pub fn project_delete(app: AppHandle, id: String) -> Result<(), String> {
|
| let mut index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| index.projects.retain(|p| p.id != id);
|
| if index.active_id.as_deref() == Some(&id) { index.active_id = index.projects.first().map(|p| p.id.clone()); }
|
| crate::persistence::save_json(&app, INDEX_FILE, &index)?;
|
|
|
|
|
| let path = crate::persistence::app_file_path(&app, &project_file(&id))?;
|
| let _ = std::fs::remove_file(path);
|
| Ok(())
|
| }
|
|
|
|
|
| #[tauri::command]
|
| pub fn project_rename(app: AppHandle, id: String, title: String) -> Result<(), String> {
|
| let mut index: ProjectIndex = crate::persistence::load_json(&app, INDEX_FILE)?;
|
| if let Some(entry) = index.projects.iter_mut().find(|p| p.id == id) { entry.title = title; }
|
| crate::persistence::save_json(&app, INDEX_FILE, &index)
|
| }
|
|
|