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:
2026-03-13 23:49:29 +02:00
commit 9dca2bedfa
69 changed files with 17462 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
use std::path::PathBuf;
use nomina_core::preset::NominaPreset;
#[tauri::command]
pub async fn save_preset(preset: NominaPreset) -> Result<String, String> {
let dir = get_presets_dir();
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
let filename = format!("{}.nomina", sanitize_filename(&preset.name));
let path = dir.join(&filename);
preset.save(&path).map_err(|e| e.to_string())?;
Ok(path.to_string_lossy().to_string())
}
#[tauri::command]
pub async fn load_preset(path: String) -> Result<NominaPreset, String> {
NominaPreset::load(&PathBuf::from(path)).map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn list_presets() -> Result<Vec<PresetInfo>, String> {
let dir = get_presets_dir();
if !dir.exists() {
return Ok(Vec::new());
}
let mut presets = Vec::new();
let entries = std::fs::read_dir(&dir).map_err(|e| e.to_string())?;
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let path = entry.path();
if path.extension().map(|e| e == "nomina").unwrap_or(false) {
if let Ok(preset) = NominaPreset::load(&path) {
presets.push(PresetInfo {
name: preset.name,
description: preset.description,
path: path.to_string_lossy().to_string(),
});
}
}
}
Ok(presets)
}
#[derive(Debug, serde::Serialize)]
pub struct PresetInfo {
pub name: String,
pub description: String,
pub path: String,
}
fn get_presets_dir() -> PathBuf {
let dirs = directories::ProjectDirs::from("com", "nomina", "Nomina")
.expect("failed to get app data directory");
dirs.data_dir().join("presets")
}
fn sanitize_filename(name: &str) -> String {
name.chars()
.map(|c| match c {
'<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' => '_',
_ => c,
})
.collect()
}