2025-10-14 22:53:26 +08:00
|
|
|
// Prevents additional console window on Windows in release
|
|
|
|
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
|
|
|
|
|
|
|
|
use tauri::Manager;
|
2025-10-15 00:23:19 +08:00
|
|
|
use tauri::AppHandle;
|
2025-10-15 17:15:05 +08:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
use std::collections::HashMap;
|
2025-10-14 22:53:26 +08:00
|
|
|
|
|
|
|
|
// IPC Commands
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn greet(name: &str) -> String {
|
|
|
|
|
format!("Hello, {}! Welcome to ECS Framework Editor.", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn open_project(path: String) -> Result<String, String> {
|
|
|
|
|
// 项目打开逻辑
|
|
|
|
|
Ok(format!("Project opened: {}", path))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn save_project(path: String, data: String) -> Result<(), String> {
|
|
|
|
|
// 项目保存逻辑
|
|
|
|
|
std::fs::write(&path, data)
|
|
|
|
|
.map_err(|e| format!("Failed to save project: {}", e))?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn export_binary(data: Vec<u8>, output_path: String) -> Result<(), String> {
|
|
|
|
|
std::fs::write(&output_path, data)
|
|
|
|
|
.map_err(|e| format!("Failed to export binary: {}", e))?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 00:23:19 +08:00
|
|
|
#[tauri::command]
|
|
|
|
|
async fn open_project_dialog(app: AppHandle) -> Result<Option<String>, String> {
|
2025-10-15 00:40:27 +08:00
|
|
|
use tauri_plugin_dialog::DialogExt;
|
2025-10-15 00:23:19 +08:00
|
|
|
|
2025-10-15 00:40:27 +08:00
|
|
|
let folder = app.dialog()
|
|
|
|
|
.file()
|
2025-10-15 00:23:19 +08:00
|
|
|
.set_title("Select Project Directory")
|
2025-10-15 00:40:27 +08:00
|
|
|
.blocking_pick_folder();
|
2025-10-15 00:23:19 +08:00
|
|
|
|
2025-10-15 00:40:27 +08:00
|
|
|
Ok(folder.map(|path| path.to_string()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn scan_directory(path: String, pattern: String) -> Result<Vec<String>, String> {
|
|
|
|
|
use glob::glob;
|
2025-10-15 09:58:45 +08:00
|
|
|
use std::path::Path;
|
2025-10-15 00:40:27 +08:00
|
|
|
|
|
|
|
|
let base_path = Path::new(&path);
|
|
|
|
|
if !base_path.exists() {
|
|
|
|
|
return Err(format!("Directory does not exist: {}", path));
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 09:19:30 +08:00
|
|
|
let separator = if path.contains('\\') { '\\' } else { '/' };
|
|
|
|
|
let glob_pattern = format!("{}{}{}", path.trim_end_matches(&['/', '\\'][..]), separator, pattern);
|
|
|
|
|
let normalized_pattern = if cfg!(windows) {
|
|
|
|
|
glob_pattern.replace('/', "\\")
|
|
|
|
|
} else {
|
|
|
|
|
glob_pattern.replace('\\', "/")
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-15 00:40:27 +08:00
|
|
|
let mut files = Vec::new();
|
|
|
|
|
|
2025-10-15 09:19:30 +08:00
|
|
|
match glob(&normalized_pattern) {
|
2025-10-15 00:40:27 +08:00
|
|
|
Ok(entries) => {
|
|
|
|
|
for entry in entries {
|
|
|
|
|
match entry {
|
|
|
|
|
Ok(path) => {
|
|
|
|
|
if path.is_file() {
|
|
|
|
|
files.push(path.to_string_lossy().to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => eprintln!("Error reading entry: {}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => return Err(format!("Failed to scan directory: {}", e)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(files)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn read_file_content(path: String) -> Result<String, String> {
|
|
|
|
|
std::fs::read_to_string(&path)
|
|
|
|
|
.map_err(|e| format!("Failed to read file {}: {}", path, e))
|
2025-10-15 00:23:19 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-15 09:43:48 +08:00
|
|
|
#[derive(serde::Serialize)]
|
|
|
|
|
struct DirectoryEntry {
|
|
|
|
|
name: String,
|
|
|
|
|
path: String,
|
|
|
|
|
is_dir: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
fn list_directory(path: String) -> Result<Vec<DirectoryEntry>, String> {
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
let dir_path = Path::new(&path);
|
|
|
|
|
if !dir_path.exists() {
|
|
|
|
|
return Err(format!("Directory does not exist: {}", path));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !dir_path.is_dir() {
|
|
|
|
|
return Err(format!("Path is not a directory: {}", path));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut entries = Vec::new();
|
|
|
|
|
|
|
|
|
|
match fs::read_dir(dir_path) {
|
|
|
|
|
Ok(read_dir) => {
|
|
|
|
|
for entry in read_dir {
|
|
|
|
|
match entry {
|
|
|
|
|
Ok(entry) => {
|
|
|
|
|
let entry_path = entry.path();
|
|
|
|
|
if let Some(name) = entry_path.file_name() {
|
|
|
|
|
entries.push(DirectoryEntry {
|
|
|
|
|
name: name.to_string_lossy().to_string(),
|
|
|
|
|
path: entry_path.to_string_lossy().to_string(),
|
|
|
|
|
is_dir: entry_path.is_dir(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => eprintln!("Error reading directory entry: {}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => return Err(format!("Failed to read directory: {}", e)),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entries.sort_by(|a, b| {
|
|
|
|
|
match (a.is_dir, b.is_dir) {
|
|
|
|
|
(true, false) => std::cmp::Ordering::Less,
|
|
|
|
|
(false, true) => std::cmp::Ordering::Greater,
|
|
|
|
|
_ => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Ok(entries)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 17:15:05 +08:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn set_project_base_path(
|
|
|
|
|
path: String,
|
|
|
|
|
state: tauri::State<Arc<Mutex<HashMap<String, String>>>>
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let mut paths = state.lock().map_err(|e| format!("Failed to lock state: {}", e))?;
|
|
|
|
|
paths.insert("current".to_string(), path);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-15 20:23:55 +08:00
|
|
|
#[tauri::command]
|
|
|
|
|
fn toggle_devtools(app: AppHandle) -> Result<(), String> {
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
|
{
|
|
|
|
|
if let Some(window) = app.get_webview_window("main") {
|
|
|
|
|
if window.is_devtools_open() {
|
|
|
|
|
window.close_devtools();
|
|
|
|
|
} else {
|
|
|
|
|
window.open_devtools();
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err("Window not found".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
|
|
|
{
|
|
|
|
|
Err("DevTools are only available in debug mode".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 22:53:26 +08:00
|
|
|
fn main() {
|
2025-10-15 17:15:05 +08:00
|
|
|
let project_paths: Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
|
|
|
|
|
let project_paths_clone = Arc::clone(&project_paths);
|
|
|
|
|
|
2025-10-14 22:53:26 +08:00
|
|
|
tauri::Builder::default()
|
|
|
|
|
.plugin(tauri_plugin_shell::init())
|
2025-10-15 00:40:27 +08:00
|
|
|
.plugin(tauri_plugin_dialog::init())
|
2025-10-15 17:15:05 +08:00
|
|
|
.register_uri_scheme_protocol("project", move |_app, request| {
|
|
|
|
|
let project_paths = Arc::clone(&project_paths_clone);
|
|
|
|
|
|
|
|
|
|
let uri = request.uri();
|
|
|
|
|
let path = uri.path();
|
|
|
|
|
|
|
|
|
|
let file_path = {
|
|
|
|
|
let paths = project_paths.lock().unwrap();
|
|
|
|
|
if let Some(base_path) = paths.get("current") {
|
|
|
|
|
format!("{}{}", base_path, path)
|
|
|
|
|
} else {
|
|
|
|
|
return tauri::http::Response::builder()
|
|
|
|
|
.status(404)
|
|
|
|
|
.body(Vec::new())
|
|
|
|
|
.unwrap();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match std::fs::read(&file_path) {
|
|
|
|
|
Ok(content) => {
|
|
|
|
|
let mime_type = if file_path.ends_with(".ts") || file_path.ends_with(".tsx") {
|
|
|
|
|
"application/javascript"
|
|
|
|
|
} else if file_path.ends_with(".js") {
|
|
|
|
|
"application/javascript"
|
|
|
|
|
} else if file_path.ends_with(".json") {
|
|
|
|
|
"application/json"
|
|
|
|
|
} else {
|
|
|
|
|
"text/plain"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tauri::http::Response::builder()
|
|
|
|
|
.status(200)
|
|
|
|
|
.header("Content-Type", mime_type)
|
|
|
|
|
.header("Access-Control-Allow-Origin", "*")
|
|
|
|
|
.body(content)
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
eprintln!("Failed to read file {}: {}", file_path, e);
|
|
|
|
|
tauri::http::Response::builder()
|
|
|
|
|
.status(404)
|
|
|
|
|
.body(Vec::new())
|
|
|
|
|
.unwrap()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.setup(move |app| {
|
|
|
|
|
app.manage(project_paths);
|
2025-10-14 22:53:26 +08:00
|
|
|
Ok(())
|
|
|
|
|
})
|
|
|
|
|
.invoke_handler(tauri::generate_handler![
|
|
|
|
|
greet,
|
|
|
|
|
open_project,
|
|
|
|
|
save_project,
|
2025-10-15 00:23:19 +08:00
|
|
|
export_binary,
|
2025-10-15 00:40:27 +08:00
|
|
|
open_project_dialog,
|
|
|
|
|
scan_directory,
|
2025-10-15 09:43:48 +08:00
|
|
|
read_file_content,
|
2025-10-15 17:15:05 +08:00
|
|
|
list_directory,
|
2025-10-15 20:23:55 +08:00
|
|
|
set_project_base_path,
|
|
|
|
|
toggle_devtools
|
2025-10-14 22:53:26 +08:00
|
|
|
])
|
|
|
|
|
.run(tauri::generate_context!())
|
|
|
|
|
.expect("error while running tauri application");
|
|
|
|
|
}
|