web based infinite canvas
at main 130 lines 3.8 kB view raw
1use std::fs; 2use std::path::{Path, PathBuf}; 3use tauri::AppHandle; 4 5#[derive(serde::Serialize, serde::Deserialize)] 6pub struct FileEntry { 7 pub path: String, 8 pub name: String, 9 pub is_dir: bool, 10} 11 12/// Read directory contents and return matching files 13#[tauri::command] 14fn read_directory(directory: String, pattern: Option<String>) -> Result<Vec<FileEntry>, String> { 15 let path = Path::new(&directory); 16 if !path.exists() { 17 return Err(format!("Directory does not exist: {}", directory)); 18 } 19 if !path.is_dir() { 20 return Err(format!("Path is not a directory: {}", directory)); 21 } 22 23 let entries = fs::read_dir(path).map_err(|e| format!("Failed to read directory: {}", e))?; 24 25 let mut results = Vec::new(); 26 let pattern = pattern.unwrap_or_else(|| "*.inkfinite.json".to_string()); 27 28 for entry in entries { 29 let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?; 30 let entry_path = entry.path(); 31 let metadata = entry 32 .metadata() 33 .map_err(|e| format!("Failed to read metadata: {}", e))?; 34 35 let name = entry.file_name().to_string_lossy().to_string(); 36 37 if metadata.is_file() { 38 if pattern.contains('*') { 39 let pattern_without_star = pattern.replace('*', ""); 40 if !name.contains(&pattern_without_star) { 41 continue; 42 } 43 } else if !name.ends_with(&pattern) { 44 continue; 45 } 46 } 47 48 results.push(FileEntry { 49 path: entry_path.to_string_lossy().to_string(), 50 name, 51 is_dir: metadata.is_dir(), 52 }); 53 } 54 55 // Sort: directories first, then files, alphabetically 56 results.sort_by(|a, b| { 57 if a.is_dir == b.is_dir { 58 a.name.to_lowercase().cmp(&b.name.to_lowercase()) 59 } else if a.is_dir { 60 std::cmp::Ordering::Less 61 } else { 62 std::cmp::Ordering::Greater 63 } 64 }); 65 66 Ok(results) 67} 68 69/// Rename a file 70#[tauri::command] 71fn rename_file(old_path: String, new_path: String) -> Result<(), String> { 72 let old = Path::new(&old_path); 73 let new = Path::new(&new_path); 74 75 if !old.exists() { 76 return Err(format!("Source file does not exist: {}", old_path)); 77 } 78 79 fs::rename(old, new).map_err(|e| format!("Failed to rename file: {}", e))?; 80 81 Ok(()) 82} 83 84/// Delete a file 85#[tauri::command] 86fn delete_file(file_path: String) -> Result<(), String> { 87 let path = Path::new(&file_path); 88 89 if !path.exists() { 90 return Err(format!("File does not exist: {}", file_path)); 91 } 92 93 if path.is_dir() { 94 return Err(format!("Path is a directory, not a file: {}", file_path)); 95 } 96 97 fs::remove_file(path).map_err(|e| format!("Failed to delete file: {}", e))?; 98 99 Ok(()) 100} 101 102/// Pick a workspace directory using the system folder picker 103#[tauri::command] 104async fn pick_workspace_directory(app: AppHandle) -> Result<Option<String>, String> { 105 use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; 106 107 let result = app.dialog().file().blocking_pick_folder(); 108 109 match result { 110 Some(path) => Ok(Some(path.to_string_lossy().to_string())), 111 None => Ok(None), 112 } 113} 114 115#[cfg_attr(mobile, tauri::mobile_entry_point)] 116pub fn run() { 117 tauri::Builder::default() 118 .plugin(tauri_plugin_opener::init()) 119 .plugin(tauri_plugin_dialog::init()) 120 .plugin(tauri_plugin_fs::init()) 121 .plugin(tauri_plugin_store::Builder::default().build()) 122 .invoke_handler(tauri::generate_handler![ 123 read_directory, 124 rename_file, 125 delete_file, 126 pick_workspace_directory 127 ]) 128 .run(tauri::generate_context!()) 129 .expect("error while running tauri application"); 130}