musealpha / src-tauri /src /projects.rs
asdf98's picture
Upload 112 files
3d7d9b5 verified
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)
}
/// Create a new project. Returns its id. Generates unique title if needed.
#[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)?;
// Generate unique title
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 };
// Save empty project file
crate::persistence::save_json(&app, &project_file(&id), &serde_json::json!({ "title": &entry.title }))?;
// Update index
index.projects.insert(0, entry.clone());
index.active_id = Some(id);
crate::persistence::save_json(&app, INDEX_FILE, &index)?;
Ok(entry)
}
/// Save project state. Updates the project file and index metadata.
#[tauri::command]
pub fn project_save(app: AppHandle, id: String, state: String, title: Option<String>) -> Result<(), String> {
// Parse to count elements
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;
// Save project file
crate::persistence::save_json(&app, &project_file(&id), &parsed)?;
// Update index
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(())
}
/// Load a project's full state JSON.
#[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())?;
// Set as active
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)
}
/// Delete a project.
#[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)?;
// Delete file (ignore if missing)
let path = crate::persistence::app_file_path(&app, &project_file(&id))?;
let _ = std::fs::remove_file(path);
Ok(())
}
/// Rename a project.
#[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)
}