Add Align Up/Down list reordering and naming-scheme Presets
Align Up/Down now swaps the selected file with its neighbor in the shared item list (Original Files and New Names stay positionally paired, matching the original FileBot's "align" buttons for manually lining up mismatched lists). Naming-scheme Presets: up to 9 saved expressions, one per digit slot, persisted as presets.json (src-tauri/src/presets.rs, same load/save pattern as settings.rs/history.rs). A new Presets panel lists all 9 slots with Save Current / Apply / Clear, and a global 1-9 keydown handler applies a slot instantly from anywhere in the app (ignored while a text field is focused or a modal is open).
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
mod files;
|
||||
mod history;
|
||||
mod metadata;
|
||||
mod presets;
|
||||
mod settings;
|
||||
|
||||
use renamr_core::format::ExpressionFormat;
|
||||
@@ -87,6 +88,9 @@ pub fn run() {
|
||||
settings::save_tmdb_token,
|
||||
history::list_rename_history,
|
||||
history::undo_rename_batch,
|
||||
presets::list_presets,
|
||||
presets::save_preset,
|
||||
presets::delete_preset,
|
||||
files::pick_files,
|
||||
files::pick_folder,
|
||||
files::expand_dropped_paths,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
//! Naming-scheme presets: up to 9 saved expressions, one per digit slot ("1".."9"),
|
||||
//! recalled with a single keypress in the UI (mirrors the original FileBot's
|
||||
//! `PresetEditor`/1-9 hotkey behavior in `RenamePanel.java`). Stored as plain JSON in
|
||||
//! Tauri's per-OS app config directory, same load/save pattern as `settings.rs` and
|
||||
//! `history.rs` - `presets.json` alongside `settings.json`/`history.json`.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
struct StoredPresets {
|
||||
#[serde(default)]
|
||||
slots: HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// A slot key is exactly one ASCII digit `1`-`9` ("0" and multi-character keys are
|
||||
/// rejected - there are only 9 digit keys to bind hotkeys to, matching the original's
|
||||
/// 1-9 preset shortcuts).
|
||||
fn is_valid_slot(slot: &str) -> bool {
|
||||
matches!(slot.as_bytes(), [b] if (b'1'..=b'9').contains(b))
|
||||
}
|
||||
|
||||
fn presets_path(app: &AppHandle) -> Result<PathBuf, String> {
|
||||
let dir = app
|
||||
.path()
|
||||
.app_config_dir()
|
||||
.map_err(|e| format!("couldn't resolve the app config directory: {e}"))?;
|
||||
fs::create_dir_all(&dir).map_err(|e| format!("couldn't create {}: {e}", dir.display()))?;
|
||||
Ok(dir.join("presets.json"))
|
||||
}
|
||||
|
||||
fn load(app: &AppHandle) -> StoredPresets {
|
||||
let Ok(path) = presets_path(app) else {
|
||||
return StoredPresets::default();
|
||||
};
|
||||
fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|contents| serde_json::from_str(&contents).ok())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn save(app: &AppHandle, presets: &StoredPresets) -> Result<(), String> {
|
||||
let path = presets_path(app)?;
|
||||
let json = serde_json::to_string_pretty(presets).map_err(|e| e.to_string())?;
|
||||
fs::write(&path, json).map_err(|e| format!("couldn't write {}: {e}", path.display()))
|
||||
}
|
||||
|
||||
/// Lists every saved preset slot -> naming-scheme expression. Slots with nothing saved
|
||||
/// simply aren't present in the map - the frontend renders those as empty.
|
||||
#[tauri::command]
|
||||
pub fn list_presets(app: AppHandle) -> HashMap<String, String> {
|
||||
load(&app).slots
|
||||
}
|
||||
|
||||
/// Saves (or overwrites) the naming-scheme expression at one digit slot.
|
||||
#[tauri::command]
|
||||
pub fn save_preset(app: AppHandle, slot: String, expression: String) -> Result<(), String> {
|
||||
if !is_valid_slot(&slot) {
|
||||
return Err("Preset slots are the digits 1-9.".to_string());
|
||||
}
|
||||
let trimmed = expression.trim();
|
||||
if trimmed.is_empty() {
|
||||
return Err("Can't save an empty naming scheme as a preset.".to_string());
|
||||
}
|
||||
|
||||
let mut presets = load(&app);
|
||||
presets.slots.insert(slot, trimmed.to_string());
|
||||
save(&app, &presets)
|
||||
}
|
||||
|
||||
/// Clears one digit slot, if anything was saved there.
|
||||
#[tauri::command]
|
||||
pub fn delete_preset(app: AppHandle, slot: String) -> Result<(), String> {
|
||||
if !is_valid_slot(&slot) {
|
||||
return Err("Preset slots are the digits 1-9.".to_string());
|
||||
}
|
||||
|
||||
let mut presets = load(&app);
|
||||
presets.slots.remove(&slot);
|
||||
save(&app, &presets)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepts_every_digit_one_through_nine() {
|
||||
for d in '1'..='9' {
|
||||
assert!(is_valid_slot(&d.to_string()), "slot {d} should be valid");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_multi_digit_and_non_digit_slots() {
|
||||
assert!(!is_valid_slot("0"));
|
||||
assert!(!is_valid_slot("10"));
|
||||
assert!(!is_valid_slot(""));
|
||||
assert!(!is_valid_slot("a"));
|
||||
assert!(!is_valid_slot("99"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user