initial project scaffold
Rust workspace with nomina-core (rename engine) and nomina-app (Tauri v2 shell). React/TypeScript frontend with tabbed rule panels, virtual-scrolled file list, and Zustand state management. All 9 rule types implemented with 25 passing tests.
This commit is contained in:
88
crates/nomina-core/src/undo.rs
Normal file
88
crates/nomina-core/src/undo.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::NominaError;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UndoLog {
|
||||
pub entries: Vec<UndoBatch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UndoBatch {
|
||||
pub id: Uuid,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub description: String,
|
||||
pub operations: Vec<UndoEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UndoEntry {
|
||||
pub original_path: PathBuf,
|
||||
pub renamed_path: PathBuf,
|
||||
}
|
||||
|
||||
const MAX_UNDO_BATCHES: usize = 50;
|
||||
|
||||
impl UndoLog {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(path: &std::path::Path) -> crate::Result<Self> {
|
||||
if !path.exists() {
|
||||
return Ok(Self::new());
|
||||
}
|
||||
let data = std::fs::read_to_string(path).map_err(|e| NominaError::Filesystem {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
serde_json::from_str(&data).map_err(|e| NominaError::PresetError {
|
||||
reason: e.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save(&self, path: &std::path::Path) -> crate::Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| NominaError::Filesystem {
|
||||
path: parent.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
}
|
||||
let json = serde_json::to_string_pretty(self).map_err(|e| NominaError::PresetError {
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
std::fs::write(path, json).map_err(|e| NominaError::Filesystem {
|
||||
path: path.to_path_buf(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_batch(&mut self, batch: UndoBatch) {
|
||||
self.entries.push(batch);
|
||||
while self.entries.len() > MAX_UNDO_BATCHES {
|
||||
self.entries.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo_last(&mut self) -> Option<UndoBatch> {
|
||||
self.entries.pop()
|
||||
}
|
||||
|
||||
pub fn undo_by_id(&mut self, id: Uuid) -> Option<UndoBatch> {
|
||||
if let Some(pos) = self.entries.iter().position(|b| b.id == id) {
|
||||
Some(self.entries.remove(pos))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user