修复快速设置学生丢失并新增重置入口

This commit is contained in:
Linkong
2026-03-21 00:03:14 +08:00
parent 1fe9dbe66a
commit 12d6be331c
7 changed files with 4916 additions and 4693 deletions
+161 -40
View File
@@ -1,62 +1,183 @@
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
use tauri::State; use tauri::{AppHandle, Emitter, State};
use crate::db::connection::{create_sqlite_connection, DatabaseType};
use crate::db::migration::Migration;
use crate::services::settings::{SettingsKey, SettingsValue, SettingsService};
use crate::services::{AutoScoreService, ThemeService};
use crate::services::permission::PermissionLevel; use crate::services::permission::PermissionLevel;
use crate::state::AppState; use crate::state::AppState;
use super::response::IpcResponse; use super::response::IpcResponse;
fn check_admin_permission(state: &Arc<RwLock<AppState>>) -> Result<(), String> { fn check_admin_permission(state: &Arc<RwLock<AppState>>) -> Result<(), String> {
let state_guard = state.read(); let state_guard = state.read();
let mut permissions = state_guard.permissions.write(); let mut permissions = state_guard.permissions.write();
let sender_id = 0; let sender_id = 0;
if !permissions.require_permission(sender_id, PermissionLevel::Admin) { if !permissions.require_permission(sender_id, PermissionLevel::Admin) {
return Err("Permission denied: Admin required".to_string()); return Err("Permission denied: Admin required".to_string());
} }
Ok(()) Ok(())
} }
#[tauri::command] fn sqlite_db_path(app_handle: &AppHandle) -> Result<String, String> {
pub async fn data_export_json( if cfg!(all(debug_assertions, desktop)) {
state: State<'_, Arc<RwLock<AppState>>>, return Ok("data.sql".to_string());
) -> Result<IpcResponse<String>, String> { }
check_admin_permission(&state)?;
let state_guard = state.read(); let app_data_dir = app_handle
let settings = state_guard.settings.read(); .path()
let settings_value = settings.get_all(); .app_data_dir()
let settings_json = serde_json::to_value(settings_value) .map_err(|e| format!("Failed to get app data directory: {}", e))?;
.map_err(|e| format!("Failed to serialize settings: {}", e))?; let data_dir = app_data_dir.join("data");
std::fs::create_dir_all(&data_dir)
let data_service = state_guard.data.read(); .map_err(|e| format!("Failed to create data directory: {}", e))?;
let students: Vec<crate::services::data::StudentExport> = Vec::new(); let db_path = data_dir.join("data.sql");
let events: Vec<crate::services::data::EventExport> = Vec::new(); db_path
.to_str()
let export_data = data_service.export_json(settings_json, students, events); .map(|s| s.to_string())
.ok_or_else(|| "Invalid sqlite database path".to_string())
let json_string = serde_json::to_string_pretty(&export_data)
.map_err(|e| format!("Failed to serialize export data: {}", e))?;
Ok(IpcResponse::success(json_string))
} }
#[tauri::command] #[tauri::command]
pub async fn data_export_json(
state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<String>, String> {
check_admin_permission(&state)?;
let state_guard = state.read();
let settings = state_guard.settings.read();
let settings_value = settings.get_all();
let settings_json = serde_json::to_value(settings_value)
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
let data_service = state_guard.data.read();
let students: Vec<crate::services::data::StudentExport> = Vec::new();
let events: Vec<crate::services::data::EventExport> = Vec::new();
let export_data = data_service.export_json(settings_json, students, events);
let json_string = serde_json::to_string_pretty(&export_data)
.map_err(|e| format!("Failed to serialize export data: {}", e))?;
Ok(IpcResponse::success(json_string))
}
#[tauri::command]
pub async fn data_import_json( pub async fn data_import_json(
json_text: String, json_text: String,
state: State<'_, Arc<RwLock<AppState>>>, state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<()>, String> { ) -> Result<IpcResponse<()>, String> {
check_admin_permission(&state)?; check_admin_permission(&state)?;
let state_guard = state.read(); let state_guard = state.read();
let mut data_service = state_guard.data.write(); let mut data_service = state_guard.data.write();
let result = data_service.import_json(&json_text); let result = data_service.import_json(&json_text);
if result.success { if result.success {
Ok(IpcResponse::success_empty()) Ok(IpcResponse::success_empty())
} else { } else {
Ok(IpcResponse::error( Ok(IpcResponse::error(
result.message.as_deref().unwrap_or("Import failed"), result.message.as_deref().unwrap_or("Import failed"),
)) ))
} }
} }
#[tauri::command]
pub async fn data_reset_all(
app_handle: AppHandle,
state: State<'_, Arc<RwLock<AppState>>>,
) -> Result<IpcResponse<()>, String> {
check_admin_permission(&state)?;
let sqlite_path = sqlite_db_path(&app_handle)?;
let sqlite_conn = create_sqlite_connection(&sqlite_path)
.await
.map_err(|e| format!("Failed to open sqlite database: {}", e))?;
let (active_conn, active_db_type) = {
let state_guard = state.read();
let active_conn = state_guard
.db
.read()
.clone()
.ok_or_else(|| "Database not connected".to_string())?;
let db_type = {
let settings = state_guard.settings.read();
match settings.get_value(SettingsKey::PgConnectionStatus) {
SettingsValue::Json(json) => match json.get("type").and_then(|value| value.as_str()) {
Some("postgresql") => DatabaseType::PostgreSQL,
_ => DatabaseType::SQLite,
},
_ => DatabaseType::SQLite,
}
};
(active_conn, db_type)
};
if active_db_type == DatabaseType::PostgreSQL {
Migration::reset_database(&active_conn, false)
.await
.map_err(|e| format!("Failed to reset PostgreSQL database: {}", e))?;
}
Migration::reset_database(&sqlite_conn, true)
.await
.map_err(|e| format!("Failed to reset SQLite database: {}", e))?;
{
let state_guard = state.read();
{
let mut db = state_guard.db.write();
*db = Some(sqlite_conn.clone());
}
{
let mut settings = state_guard.settings.write();
*settings = SettingsService::new();
settings.attach_db(Some(sqlite_conn.clone()));
settings
.initialize()
.await
.map_err(|e| format!("Failed to reinitialize settings: {}", e))?;
settings
.set_value(
SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({
"connected": true,
"type": "sqlite"
})),
)
.await
.map_err(|e| format!("Failed to restore sqlite status: {}", e))?;
}
{
let mut permissions = state_guard.permissions.write();
permissions.clear_all_permissions();
permissions.update_password_status(false, false);
}
{
let mut theme = state_guard.theme.write();
*theme = ThemeService::new();
}
{
let mut auto_score = state_guard.auto_score.write();
*auto_score = AutoScoreService::new();
}
}
let _ = app_handle.emit(
"settings:changed",
serde_json::json!({
"key": "is_wizard_completed",
"value": false
}),
);
Ok(IpcResponse::success_empty())
}
+386 -385
View File
@@ -1,388 +1,389 @@
pub mod commands; pub mod commands;
pub mod db; pub mod db;
pub mod models; pub mod models;
pub mod services; pub mod services;
pub mod state; pub mod state;
pub mod utils; pub mod utils;
use crate::db::connection::DatabaseType; use crate::db::connection::DatabaseType;
use crate::db::connection::{create_postgres_connection, create_sqlite_connection}; use crate::db::connection::{create_postgres_connection, create_sqlite_connection};
use crate::db::migration::run_migration; use crate::db::migration::run_migration;
use crate::services::settings::{SettingsKey, SettingsValue}; use crate::services::settings::{SettingsKey, SettingsValue};
use crate::{commands::*, state::AppState}; use crate::{commands::*, state::AppState};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::Arc; use std::sync::Arc;
#[cfg(desktop)] #[cfg(desktop)]
use tauri::{ use tauri::{
image::Image, image::Image,
menu::{Menu, MenuItem}, menu::{Menu, MenuItem},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
WindowEvent, WindowEvent,
}; };
use tauri::{App, Manager}; use tauri::{App, Manager};
use tokio::time::{timeout, Duration}; use tokio::time::{timeout, Duration};
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.setup(|app| { .setup(|app| {
let state = AppState::new(app.handle().clone()); let state = AppState::new(app.handle().clone());
app.manage(Arc::new(RwLock::new(state))); app.manage(Arc::new(RwLock::new(state)));
setup_app(app)?; setup_app(app)?;
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
student_query, student_query,
student_create, student_create,
student_update, student_update,
student_delete, student_delete,
student_import_from_xlsx, student_import_from_xlsx,
tags_get_all, tags_get_all,
tags_get_by_student, tags_get_by_student,
tags_create, tags_create,
tags_delete, tags_delete,
tags_update_student_tags, tags_update_student_tags,
reason_query, reason_query,
reason_create, reason_create,
reason_update, reason_update,
reason_delete, reason_delete,
reward_setting_query, reward_setting_query,
reward_setting_create, reward_setting_create,
reward_setting_update, reward_setting_update,
reward_setting_delete, reward_setting_delete,
reward_redeem, reward_redeem,
reward_redemption_query, reward_redemption_query,
event_query, event_query,
event_create, event_create,
event_delete, event_delete,
event_query_by_student, event_query_by_student,
leaderboard_query, leaderboard_query,
db_settlement_query, db_settlement_query,
db_settlement_create, db_settlement_create,
db_settlement_leaderboard, db_settlement_leaderboard,
settings_get_all, settings_get_all,
settings_get, settings_get,
settings_set, settings_set,
auth_get_status, auth_get_status,
auth_login, auth_login,
auth_logout, auth_logout,
auth_set_passwords, auth_set_passwords,
auth_generate_recovery, auth_generate_recovery,
auth_reset_by_recovery, auth_reset_by_recovery,
auth_clear_all, auth_clear_all,
theme_list, theme_list,
theme_current, theme_current,
theme_set, theme_set,
theme_save, theme_save,
theme_delete, theme_delete,
auto_score_get_rules, auto_score_get_rules,
auto_score_add_rule, auto_score_add_rule,
auto_score_update_rule, auto_score_update_rule,
auto_score_delete_rule, auto_score_delete_rule,
auto_score_toggle_rule, auto_score_toggle_rule,
auto_score_get_status, auto_score_get_status,
auto_score_sort_rules, auto_score_sort_rules,
board_query_sql, board_query_sql,
log_query, log_query,
log_clear, log_clear,
log_set_level, log_set_level,
log_write, log_write,
data_export_json, data_export_json,
data_import_json, data_import_json,
data_reset_all,
window_minimize, window_minimize,
window_maximize, window_maximize,
window_close, window_close,
window_is_maximized, window_is_maximized,
toggle_devtools, toggle_devtools,
window_resize, window_resize,
window_set_resizable, window_set_resizable,
db_test_connection, db_test_connection,
db_switch_connection, db_switch_connection,
db_get_status, db_get_status,
db_sync, db_sync,
db_sync_preview, db_sync_preview,
db_sync_apply, db_sync_apply,
fs_get_config_structure, fs_get_config_structure,
fs_read_json, fs_read_json,
fs_write_json, fs_write_json,
fs_read_text, fs_read_text,
fs_write_text, fs_write_text,
fs_delete_file, fs_delete_file,
fs_list_files, fs_list_files,
fs_file_exists, fs_file_exists,
http_server_start, http_server_start,
http_server_stop, http_server_stop,
http_server_status, http_server_status,
mcp_server_start, mcp_server_start,
mcp_server_stop, mcp_server_stop,
mcp_server_status, mcp_server_status,
register_url_protocol, register_url_protocol,
app_quit, app_quit,
app_restart, app_restart,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> { pub fn setup_app(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
setup_database(app)?; setup_database(app)?;
setup_tray(app)?; setup_tray(app)?;
setup_window_events(app)?; setup_window_events(app)?;
Ok(()) Ok(())
} }
fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
const DB_CONNECT_TIMEOUT_SECS: u64 = 15; const DB_CONNECT_TIMEOUT_SECS: u64 = 15;
const DB_MIGRATION_TIMEOUT_SECS: u64 = 20; const DB_MIGRATION_TIMEOUT_SECS: u64 = 20;
let handle = app.handle().clone(); let handle = app.handle().clone();
let db_path = if cfg!(all(debug_assertions, desktop)) { let db_path = if cfg!(all(debug_assertions, desktop)) {
std::path::PathBuf::from("data.sql") std::path::PathBuf::from("data.sql")
} else { } else {
let app_data_dir = handle let app_data_dir = handle
.path() .path()
.app_data_dir() .app_data_dir()
.map_err(|e| format!("Failed to get app data directory: {}", e))?; .map_err(|e| format!("Failed to get app data directory: {}", e))?;
let data_dir = app_data_dir.join("data"); let data_dir = app_data_dir.join("data");
std::fs::create_dir_all(&data_dir) std::fs::create_dir_all(&data_dir)
.map_err(|e| format!("Failed to create data directory: {}", e))?; .map_err(|e| format!("Failed to create data directory: {}", e))?;
data_dir.join("data.sql") data_dir.join("data.sql")
}; };
let db_path_str = db_path.to_str().ok_or("Invalid database path")?.to_string(); let db_path_str = db_path.to_str().ok_or("Invalid database path")?.to_string();
let db_result = tauri::async_runtime::block_on(async { let db_result = tauri::async_runtime::block_on(async {
let sqlite_conn = create_sqlite_connection(&db_path_str).await?; let sqlite_conn = create_sqlite_connection(&db_path_str).await?;
run_migration(&sqlite_conn, DatabaseType::SQLite).await?; run_migration(&sqlite_conn, DatabaseType::SQLite).await?;
let state = handle.state::<crate::state::SafeAppState>(); let state = handle.state::<crate::state::SafeAppState>();
let state_guard = state.write(); let state_guard = state.write();
let mut active_conn = sqlite_conn.clone(); let mut active_conn = sqlite_conn.clone();
{ {
let mut settings = state_guard.settings.write(); let mut settings = state_guard.settings.write();
settings.attach_db(Some(sqlite_conn.clone())); settings.attach_db(Some(sqlite_conn.clone()));
settings settings
.initialize() .initialize()
.await .await
.map_err(|e| format!("Failed to initialize settings from sqlite: {}", e))?; .map_err(|e| format!("Failed to initialize settings from sqlite: {}", e))?;
let pg_connection_string = match settings.get_value(SettingsKey::PgConnectionString) { let pg_connection_string = match settings.get_value(SettingsKey::PgConnectionString) {
SettingsValue::String(s) => s, SettingsValue::String(s) => s,
_ => String::new(), _ => String::new(),
}; };
if !pg_connection_string.trim().is_empty() { if !pg_connection_string.trim().is_empty() {
match timeout( match timeout(
Duration::from_secs(DB_CONNECT_TIMEOUT_SECS), Duration::from_secs(DB_CONNECT_TIMEOUT_SECS),
create_postgres_connection(&pg_connection_string), create_postgres_connection(&pg_connection_string),
) )
.await .await
{ {
Err(_) => { Err(_) => {
let timeout_message = "PostgreSQL auto-connect timeout".to_string(); let timeout_message = "PostgreSQL auto-connect timeout".to_string();
eprintln!( eprintln!(
"PostgreSQL auto-connect timed out on startup, fallback to sqlite" "PostgreSQL auto-connect timed out on startup, fallback to sqlite"
); );
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": false, "connected": false,
"type": "sqlite", "type": "sqlite",
"error": timeout_message "error": timeout_message
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save pg status: {}", err))?; .map_err(|err| format!("Failed to save pg status: {}", err))?;
} }
Ok(Err(e)) => { Ok(Err(e)) => {
eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e); eprintln!("PostgreSQL auto-connect failed, fallback to sqlite: {}", e);
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": false, "connected": false,
"type": "sqlite", "type": "sqlite",
"error": e.to_string() "error": e.to_string()
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save pg status: {}", err))?; .map_err(|err| format!("Failed to save pg status: {}", err))?;
} }
Ok(Ok(pg_conn)) => { Ok(Ok(pg_conn)) => {
let migration_result = timeout( let migration_result = timeout(
Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS), Duration::from_secs(DB_MIGRATION_TIMEOUT_SECS),
run_migration(&pg_conn, DatabaseType::PostgreSQL), run_migration(&pg_conn, DatabaseType::PostgreSQL),
) )
.await; .await;
if let Err(_) = migration_result { if let Err(_) = migration_result {
let timeout_message = "PostgreSQL migration timeout".to_string(); let timeout_message = "PostgreSQL migration timeout".to_string();
eprintln!( eprintln!(
"PostgreSQL migration timed out on startup, fallback to sqlite" "PostgreSQL migration timed out on startup, fallback to sqlite"
); );
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": false, "connected": false,
"type": "sqlite", "type": "sqlite",
"error": timeout_message "error": timeout_message
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save pg status: {}", err))?; .map_err(|err| format!("Failed to save pg status: {}", err))?;
} else if let Ok(Err(e)) = migration_result { } else if let Ok(Err(e)) = migration_result {
eprintln!( eprintln!(
"PostgreSQL migration failed on startup, fallback to sqlite: {}", "PostgreSQL migration failed on startup, fallback to sqlite: {}",
e e
); );
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": false, "connected": false,
"type": "sqlite", "type": "sqlite",
"error": e.to_string() "error": e.to_string()
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save pg status: {}", err))?; .map_err(|err| format!("Failed to save pg status: {}", err))?;
} else { } else {
active_conn = pg_conn; active_conn = pg_conn;
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": true, "connected": true,
"type": "postgresql" "type": "postgresql"
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save pg status: {}", err))?; .map_err(|err| format!("Failed to save pg status: {}", err))?;
eprintln!("Auto connected to PostgreSQL from saved connection string"); eprintln!("Auto connected to PostgreSQL from saved connection string");
} }
} }
} }
} else { } else {
settings settings
.set_value( .set_value(
SettingsKey::PgConnectionStatus, SettingsKey::PgConnectionStatus,
SettingsValue::Json(serde_json::json!({ SettingsValue::Json(serde_json::json!({
"connected": true, "connected": true,
"type": "sqlite" "type": "sqlite"
})), })),
) )
.await .await
.map_err(|err| format!("Failed to save sqlite status: {}", err))?; .map_err(|err| format!("Failed to save sqlite status: {}", err))?;
} }
} }
{ {
let mut db_guard = state_guard.db.write(); let mut db_guard = state_guard.db.write();
*db_guard = Some(active_conn); *db_guard = Some(active_conn);
} }
Ok::<_, Box<dyn std::error::Error>>(()) Ok::<_, Box<dyn std::error::Error>>(())
}); });
if let Err(e) = db_result { if let Err(e) = db_result {
eprintln!("Failed to connect to database: {}", e); eprintln!("Failed to connect to database: {}", e);
} else { } else {
eprintln!( eprintln!(
"Database bootstrap completed, sqlite settings db: {}", "Database bootstrap completed, sqlite settings db: {}",
db_path_str db_path_str
); );
} }
Ok(()) Ok(())
} }
#[cfg(desktop)] #[cfg(desktop)]
fn setup_tray(app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_tray(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
let show_item = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?; let show_item = MenuItem::with_id(app, "show", "显示窗口", true, None::<&str>)?;
let hide_item = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?; let hide_item = MenuItem::with_id(app, "hide", "隐藏窗口", true, None::<&str>)?;
let quit_item = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?; let quit_item = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?; let menu = Menu::with_items(app, &[&show_item, &hide_item, &quit_item])?;
let _tray = TrayIconBuilder::new() let _tray = TrayIconBuilder::new()
.icon(Image::from_bytes(include_bytes!("../icons/icon.png"))?) .icon(Image::from_bytes(include_bytes!("../icons/icon.png"))?)
.menu(&menu) .menu(&menu)
.show_menu_on_left_click(false) .show_menu_on_left_click(false)
.on_menu_event(|app, event| match event.id.as_ref() { .on_menu_event(|app, event| match event.id.as_ref() {
"show" => { "show" => {
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
let _ = window.show(); let _ = window.show();
let _ = window.set_focus(); let _ = window.set_focus();
} }
} }
"hide" => { "hide" => {
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
let _ = window.hide(); let _ = window.hide();
} }
} }
"quit" => { "quit" => {
app.exit(0); app.exit(0);
} }
_ => {} _ => {}
}) })
.on_tray_icon_event(|tray, event| { .on_tray_icon_event(|tray, event| {
if let TrayIconEvent::Click { if let TrayIconEvent::Click {
button: MouseButton::Left, button: MouseButton::Left,
button_state: MouseButtonState::Up, button_state: MouseButtonState::Up,
.. ..
} = event } = event
{ {
let app = tray.app_handle(); let app = tray.app_handle();
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
let _ = window.show(); let _ = window.show();
let _ = window.set_focus(); let _ = window.set_focus();
} }
} }
}) })
.build(app)?; .build(app)?;
Ok(()) Ok(())
} }
#[cfg(not(desktop))] #[cfg(not(desktop))]
fn setup_tray(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_tray(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
#[cfg(desktop)] #[cfg(desktop)]
fn setup_window_events(app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_window_events(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize { let _ = window.set_size(tauri::Size::Logical(tauri::LogicalSize {
width: 1180.0, width: 1180.0,
height: 680.0, height: 680.0,
})); }));
let _ = window.center(); let _ = window.center();
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let _ = window.set_shadow(true); let _ = window.set_shadow(true);
} }
let window_clone = window.clone(); let window_clone = window.clone();
window.on_window_event(move |event| { window.on_window_event(move |event| {
if let WindowEvent::CloseRequested { api, .. } = event { if let WindowEvent::CloseRequested { api, .. } = event {
api.prevent_close(); api.prevent_close();
let _ = window_clone.hide(); let _ = window_clone.hide();
} }
}); });
} }
Ok(()) Ok(())
} }
#[cfg(not(desktop))] #[cfg(not(desktop))]
fn setup_window_events(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> { fn setup_window_events(_app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
File diff suppressed because it is too large Load Diff
+1383 -1321
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+401 -400
View File
@@ -1,404 +1,405 @@
import { invoke } from "@tauri-apps/api/core" import { invoke } from "@tauri-apps/api/core"
import { listen, UnlistenFn } from "@tauri-apps/api/event" import { listen, UnlistenFn } from "@tauri-apps/api/event"
export interface themeConfig { export interface themeConfig {
name: string name: string
id: string id: string
mode: "light" | "dark" mode: "light" | "dark"
config: { config: {
tdesign: Record<string, string> tdesign: Record<string, string>
custom: Record<string, string> custom: Record<string, string>
} }
} }
export interface settingChange { export interface settingChange {
key: string key: string
value: any value: any
oldValue: any oldValue: any
} }
export type settingsKey = export type settingsKey =
| "is_wizard_completed" | "is_wizard_completed"
| "log_level" | "log_level"
| "window_zoom" | "window_zoom"
| "search_keyboard_layout" | "search_keyboard_layout"
| "disable_search_keyboard" | "disable_search_keyboard"
| "themes_custom" | "themes_custom"
| "auto_score_enabled" | "auto_score_enabled"
| "auto_score_rules" | "auto_score_rules"
| "current_theme_id" | "current_theme_id"
| "dashboards_config" | "dashboards_config"
| "pg_connection_string" | "pg_connection_string"
| "pg_connection_status" | "pg_connection_status"
export interface settingsSpec { export interface settingsSpec {
is_wizard_completed: boolean is_wizard_completed: boolean
log_level: string log_level: string
window_zoom: number window_zoom: number
search_keyboard_layout: "t9" | "qwerty26" search_keyboard_layout: "t9" | "qwerty26"
disable_search_keyboard: boolean disable_search_keyboard: boolean
themes_custom: themeConfig[] themes_custom: themeConfig[]
auto_score_enabled: boolean auto_score_enabled: boolean
auto_score_rules: any[] auto_score_rules: any[]
current_theme_id: string current_theme_id: string
dashboards_config: any[] dashboards_config: any[]
pg_connection_string: string pg_connection_string: string
pg_connection_status: { pg_connection_status: {
connected: boolean connected: boolean
type: "sqlite" | "postgresql" type: "sqlite" | "postgresql"
error?: string error?: string
} }
} }
const api = { const api = {
// Theme // Theme
getThemes: (): Promise<{ success: boolean; data: themeConfig[] }> => invoke("theme_list"), getThemes: (): Promise<{ success: boolean; data: themeConfig[] }> => invoke("theme_list"),
getCurrentTheme: (): Promise<{ success: boolean; data: themeConfig }> => invoke("theme_current"), getCurrentTheme: (): Promise<{ success: boolean; data: themeConfig }> => invoke("theme_current"),
setTheme: (themeId: string): Promise<{ success: boolean }> => invoke("theme_set", { themeId }), setTheme: (themeId: string): Promise<{ success: boolean }> => invoke("theme_set", { themeId }),
saveTheme: (theme: themeConfig): Promise<{ success: boolean }> => invoke("theme_save", { theme }), saveTheme: (theme: themeConfig): Promise<{ success: boolean }> => invoke("theme_save", { theme }),
deleteTheme: (themeId: string): Promise<{ success: boolean }> => deleteTheme: (themeId: string): Promise<{ success: boolean }> =>
invoke("theme_delete", { themeId }), invoke("theme_delete", { themeId }),
onThemeChanged: (callback: (theme: themeConfig) => void): Promise<UnlistenFn> => { onThemeChanged: (callback: (theme: themeConfig) => void): Promise<UnlistenFn> => {
return listen<themeConfig | { theme?: themeConfig }>("theme:updated", (event) => { return listen<themeConfig | { theme?: themeConfig }>("theme:updated", (event) => {
const payload = event.payload as themeConfig | { theme?: themeConfig } | undefined const payload = event.payload as themeConfig | { theme?: themeConfig } | undefined
const theme = const theme =
payload && typeof payload === "object" && "theme" in payload payload && typeof payload === "object" && "theme" in payload
? payload.theme ? payload.theme
: (payload as themeConfig | undefined) : (payload as themeConfig | undefined)
if (theme) callback(theme) if (theme) callback(theme)
}) })
}, },
// DB - Student // DB - Student
queryStudents: (params?: any): Promise<{ success: boolean; data: any[] }> => queryStudents: (params?: any): Promise<{ success: boolean; data: any[] }> =>
invoke("student_query", { params }), invoke("student_query", { params }),
createStudent: (data: { createStudent: (data: {
name: string name: string
}): Promise<{ success: boolean; data?: number; message?: string }> => }): Promise<{ success: boolean; data?: number; message?: string }> =>
invoke("student_create", { data }), invoke("student_create", { data }),
updateStudent: (id: number, data: any): Promise<{ success: boolean }> => updateStudent: (id: number, data: any): Promise<{ success: boolean }> =>
invoke("student_update", { id, data }), invoke("student_update", { id, data }),
deleteStudent: (id: number): Promise<{ success: boolean }> => invoke("student_delete", { id }), deleteStudent: (id: number): Promise<{ success: boolean }> => invoke("student_delete", { id }),
importStudentsFromXlsx: (params: { importStudentsFromXlsx: (params: {
names: string[] names: string[]
}): Promise<{ success: boolean; data: { inserted: number; skipped: number; total: number } }> => }): Promise<{ success: boolean; data: { inserted: number; skipped: number; total: number } }> =>
invoke("student_import_from_xlsx", { params }), invoke("student_import_from_xlsx", { params }),
// DB - Tags // DB - Tags
tagsGetAll: (): Promise<{ success: boolean; data: any[] }> => invoke("tags_get_all"), tagsGetAll: (): Promise<{ success: boolean; data: any[] }> => invoke("tags_get_all"),
tagsGetByStudent: (studentId: number): Promise<{ success: boolean; data: any[] }> => tagsGetByStudent: (studentId: number): Promise<{ success: boolean; data: any[] }> =>
invoke("tags_get_by_student", { studentId }), invoke("tags_get_by_student", { studentId }),
tagsCreate: (name: string): Promise<{ success: boolean; data: any }> => tagsCreate: (name: string): Promise<{ success: boolean; data: any }> =>
invoke("tags_create", { name }), invoke("tags_create", { name }),
tagsDelete: (id: number): Promise<{ success: boolean }> => invoke("tags_delete", { id }), tagsDelete: (id: number): Promise<{ success: boolean }> => invoke("tags_delete", { id }),
tagsUpdateStudentTags: (studentId: number, tagIds: number[]): Promise<{ success: boolean }> => tagsUpdateStudentTags: (studentId: number, tagIds: number[]): Promise<{ success: boolean }> =>
invoke("tags_update_student_tags", { studentId, tagIds }), invoke("tags_update_student_tags", { studentId, tagIds }),
// DB - Reason // DB - Reason
queryReasons: (): Promise<{ success: boolean; data: any[] }> => invoke("reason_query"), queryReasons: (): Promise<{ success: boolean; data: any[] }> => invoke("reason_query"),
createReason: (data: any): Promise<{ success: boolean; data?: number; message?: string }> => createReason: (data: any): Promise<{ success: boolean; data?: number; message?: string }> =>
invoke("reason_create", { data }), invoke("reason_create", { data }),
updateReason: (id: number, data: any): Promise<{ success: boolean }> => updateReason: (id: number, data: any): Promise<{ success: boolean }> =>
invoke("reason_update", { id, data }), invoke("reason_update", { id, data }),
deleteReason: (id: number): Promise<{ success: boolean }> => invoke("reason_delete", { id }), deleteReason: (id: number): Promise<{ success: boolean }> => invoke("reason_delete", { id }),
// DB - Reward // DB - Reward
rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> => invoke("reward_setting_query"), rewardSettingQuery: (): Promise<{ success: boolean; data: any[] }> => invoke("reward_setting_query"),
rewardSettingCreate: (data: { rewardSettingCreate: (data: {
name: string name: string
cost_points: number cost_points: number
}): Promise<{ success: boolean; data?: number; message?: string }> => }): Promise<{ success: boolean; data?: number; message?: string }> =>
invoke("reward_setting_create", { data }), invoke("reward_setting_create", { data }),
rewardSettingUpdate: (id: number, data: any): Promise<{ success: boolean; message?: string }> => rewardSettingUpdate: (id: number, data: any): Promise<{ success: boolean; message?: string }> =>
invoke("reward_setting_update", { id, data }), invoke("reward_setting_update", { id, data }),
rewardSettingDelete: (id: number): Promise<{ success: boolean; message?: string }> => rewardSettingDelete: (id: number): Promise<{ success: boolean; message?: string }> =>
invoke("reward_setting_delete", { id }), invoke("reward_setting_delete", { id }),
rewardRedeem: (data: { rewardRedeem: (data: {
student_name: string student_name: string
reward_id: number reward_id: number
}): Promise<{ }): Promise<{
success: boolean success: boolean
data?: { redemption_id: number; remaining_reward_points: number } data?: { redemption_id: number; remaining_reward_points: number }
message?: string message?: string
}> => invoke("reward_redeem", { data }), }> => invoke("reward_redeem", { data }),
rewardRedemptionQuery: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> => rewardRedemptionQuery: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
invoke("reward_redemption_query", { params }), invoke("reward_redemption_query", { params }),
// DB - Event // DB - Event
queryEvents: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> => queryEvents: (params?: { limit?: number }): Promise<{ success: boolean; data: any[] }> =>
invoke("event_query", { params }), invoke("event_query", { params }),
createEvent: (data: { createEvent: (data: {
studentName: string studentName: string
reasonContent: string reasonContent: string
delta: number delta: number
}): Promise<{ success: boolean; data?: number; message?: string }> => }): Promise<{ success: boolean; data?: number; message?: string }> =>
invoke("event_create", { data }), invoke("event_create", { data }),
deleteEvent: (uuid: string): Promise<{ success: boolean }> => invoke("event_delete", { uuid }), deleteEvent: (uuid: string): Promise<{ success: boolean }> => invoke("event_delete", { uuid }),
queryEventsByStudent: (params: { queryEventsByStudent: (params: {
studentName: string studentName: string
limit?: number limit?: number
startTime?: string startTime?: string
}): Promise<{ success: boolean; data: any[] }> => invoke("event_query_by_student", { params }), }): Promise<{ success: boolean; data: any[] }> => invoke("event_query_by_student", { params }),
queryLeaderboard: (params: { queryLeaderboard: (params: {
range: "today" | "week" | "month" range: "today" | "week" | "month"
}): Promise<{ success: boolean; data: { startTime: string; rows: any[] } }> => }): Promise<{ success: boolean; data: { startTime: string; rows: any[] } }> =>
invoke("leaderboard_query", { params }), invoke("leaderboard_query", { params }),
boardQuerySql: (params: { boardQuerySql: (params: {
sql: string sql: string
limit?: number limit?: number
}): Promise<{ success: boolean; data: any[]; message?: string }> => }): Promise<{ success: boolean; data: any[]; message?: string }> =>
invoke("board_query_sql", { params }), invoke("board_query_sql", { params }),
// Settlement // Settlement
querySettlements: (): Promise<{ success: boolean; data: any[] }> => invoke("db_settlement_query"), querySettlements: (): Promise<{ success: boolean; data: any[] }> => invoke("db_settlement_query"),
createSettlement: (): Promise<{ success: boolean; data: any }> => invoke("db_settlement_create"), createSettlement: (): Promise<{ success: boolean; data: any }> => invoke("db_settlement_create"),
querySettlementLeaderboard: (params: { querySettlementLeaderboard: (params: {
settlementId: number settlementId: number
}): Promise<{ success: boolean; data: any }> => invoke("db_settlement_leaderboard", { params }), }): Promise<{ success: boolean; data: any }> => invoke("db_settlement_leaderboard", { params }),
// Settings & Sync // Settings & Sync
getAllSettings: (): Promise<{ success: boolean; data: settingsSpec }> => getAllSettings: (): Promise<{ success: boolean; data: settingsSpec }> =>
invoke("settings_get_all"), invoke("settings_get_all"),
getSetting: <K extends settingsKey>( getSetting: <K extends settingsKey>(
key: K key: K
): Promise<{ success: boolean; data: settingsSpec[K] }> => invoke("settings_get", { key }), ): Promise<{ success: boolean; data: settingsSpec[K] }> => invoke("settings_get", { key }),
setSetting: <K extends settingsKey>( setSetting: <K extends settingsKey>(
key: K, key: K,
value: settingsSpec[K] value: settingsSpec[K]
): Promise<{ success: boolean }> => invoke("settings_set", { key, value }), ): Promise<{ success: boolean }> => invoke("settings_set", { key, value }),
onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => { onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => {
return listen<settingChange>("settings:changed", (event) => { return listen<settingChange>("settings:changed", (event) => {
callback(event.payload) callback(event.payload)
}) })
}, },
// Auth & Security // Auth & Security
authGetStatus: (): Promise<{ authGetStatus: (): Promise<{
success: boolean success: boolean
data: { data: {
permission: string permission: string
hasAdminPassword: boolean hasAdminPassword: boolean
hasPointsPassword: boolean hasPointsPassword: boolean
hasRecoveryString: boolean hasRecoveryString: boolean
} }
}> => invoke("auth_get_status"), }> => invoke("auth_get_status"),
authLogin: ( authLogin: (
password: string password: string
): Promise<{ success: boolean; data?: { permission: string }; message?: string }> => ): Promise<{ success: boolean; data?: { permission: string }; message?: string }> =>
invoke("auth_login", { password }), invoke("auth_login", { password }),
authLogout: (): Promise<{ success: boolean; data: { permission: string } }> => authLogout: (): Promise<{ success: boolean; data: { permission: string } }> =>
invoke("auth_logout"), invoke("auth_logout"),
authSetPasswords: (payload: { authSetPasswords: (payload: {
adminPassword?: string | null adminPassword?: string | null
pointsPassword?: string | null pointsPassword?: string | null
}): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> => }): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
invoke("auth_set_passwords", payload), invoke("auth_set_passwords", payload),
authGenerateRecovery: (): Promise<{ success: boolean; data: { recoveryString: string } }> => authGenerateRecovery: (): Promise<{ success: boolean; data: { recoveryString: string } }> =>
invoke("auth_generate_recovery"), invoke("auth_generate_recovery"),
authResetByRecovery: ( authResetByRecovery: (
recoveryString: string recoveryString: string
): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> => ): Promise<{ success: boolean; data?: { recoveryString: string }; message?: string }> =>
invoke("auth_reset_by_recovery", { recoveryString }), invoke("auth_reset_by_recovery", { recoveryString }),
authClearAll: (): Promise<{ success: boolean }> => invoke("auth_clear_all"), authClearAll: (): Promise<{ success: boolean }> => invoke("auth_clear_all"),
// Data import/export // Data import/export
exportDataJson: (): Promise<{ success: boolean; data: string }> => invoke("data_export_json"), exportDataJson: (): Promise<{ success: boolean; data: string }> => invoke("data_export_json"),
importDataJson: (jsonText: string): Promise<{ success: boolean }> => importDataJson: (jsonText: string): Promise<{ success: boolean }> =>
invoke("data_import_json", { jsonText }), invoke("data_import_json", { jsonText }),
dataResetAll: (): Promise<{ success: boolean }> => invoke("data_reset_all"),
// Window
windowMinimize: (): Promise<void> => invoke("window_minimize"), // Window
windowMaximize: (): Promise<boolean> => invoke("window_maximize"), windowMinimize: (): Promise<void> => invoke("window_minimize"),
windowClose: (): Promise<void> => invoke("window_close"), windowMaximize: (): Promise<boolean> => invoke("window_maximize"),
windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"), windowClose: (): Promise<void> => invoke("window_close"),
toggleDevTools: (): Promise<void> => invoke("toggle_devtools"), windowIsMaximized: (): Promise<boolean> => invoke("window_is_maximized"),
windowResize: (width: number, height: number): Promise<void> => toggleDevTools: (): Promise<void> => invoke("toggle_devtools"),
invoke("window_resize", { width, height }), windowResize: (width: number, height: number): Promise<void> =>
windowSetResizable: (resizable: boolean): Promise<void> => invoke("window_resize", { width, height }),
invoke("window_set_resizable", { resizable }), windowSetResizable: (resizable: boolean): Promise<void> =>
onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => { invoke("window_set_resizable", { resizable }),
return listen<boolean>("window:maximized-changed", (event) => { onWindowMaximizedChanged: (callback: (maximized: boolean) => void): Promise<UnlistenFn> => {
callback(event.payload) return listen<boolean>("window:maximized-changed", (event) => {
}) callback(event.payload)
}, })
onNavigate: (callback: (route: string) => void): Promise<UnlistenFn> => { },
return listen<string>("app:navigate", (event) => { onNavigate: (callback: (route: string) => void): Promise<UnlistenFn> => {
callback(event.payload) return listen<string>("app:navigate", (event) => {
}) callback(event.payload)
}, })
},
// Logger
queryLogs: ( // Logger
input?: number | { lines?: number } queryLogs: (
): Promise<{ success: boolean; data: string[] }> => { input?: number | { lines?: number }
const lines = typeof input === "number" ? input : input?.lines ): Promise<{ success: boolean; data: string[] }> => {
return invoke("log_query", { lines }) const lines = typeof input === "number" ? input : input?.lines
}, return invoke("log_query", { lines })
clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"), },
setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }), clearLogs: (): Promise<{ success: boolean }> => invoke("log_clear"),
writeLog: (payload: { setLogLevel: (level: string): Promise<{ success: boolean }> => invoke("log_set_level", { level }),
level: string writeLog: (payload: {
message: string level: string
meta?: any message: string
}): Promise<{ success: boolean }> => invoke("log_write", { payload }), meta?: any
}): Promise<{ success: boolean }> => invoke("log_write", { payload }),
// Database Connection
dbTestConnection: ( // Database Connection
connectionString: string dbTestConnection: (
): Promise<{ success: boolean; data: { success: boolean; error?: string } }> => connectionString: string
invoke("db_test_connection", { connectionString }), ): Promise<{ success: boolean; data: { success: boolean; error?: string } }> =>
dbSwitchConnection: ( invoke("db_test_connection", { connectionString }),
connectionString: string dbSwitchConnection: (
): Promise<{ success: boolean; data: { type: "sqlite" | "postgresql" } }> => connectionString: string
invoke("db_switch_connection", { connectionString }), ): Promise<{ success: boolean; data: { type: "sqlite" | "postgresql" } }> =>
dbGetStatus: (): Promise<{ invoke("db_switch_connection", { connectionString }),
success: boolean dbGetStatus: (): Promise<{
data: { type: string; connected: boolean; error?: string } success: boolean
}> => invoke("db_get_status"), data: { type: string; connected: boolean; error?: string }
dbSync: (): Promise<{ success: boolean; data: { success: boolean; message?: string } }> => }> => invoke("db_get_status"),
invoke("db_sync"), dbSync: (): Promise<{ success: boolean; data: { success: boolean; message?: string } }> =>
dbSyncPreview: (): Promise<{ invoke("db_sync"),
success: boolean dbSyncPreview: (): Promise<{
data: { success: boolean
can_sync: boolean data: {
need_sync: boolean can_sync: boolean
local_only: number need_sync: boolean
remote_only: number local_only: number
conflicts: Array<{ remote_only: number
table: string conflicts: Array<{
key: string table: string
local_summary: string key: string
remote_summary: string local_summary: string
}> remote_summary: string
message?: string }>
} message?: string
}> => invoke("db_sync_preview"), }
dbSyncApply: (strategy: "keep_local" | "keep_remote"): Promise<{ }> => invoke("db_sync_preview"),
success: boolean dbSyncApply: (strategy: "keep_local" | "keep_remote"): Promise<{
data: { success: boolean; synced_records: number; resolved_conflicts: number; message?: string } success: boolean
}> => invoke("db_sync_apply", { strategy }), data: { success: boolean; synced_records: number; resolved_conflicts: number; message?: string }
}> => invoke("db_sync_apply", { strategy }),
// HTTP Server
httpServerStart: (config?: { // HTTP Server
port?: number httpServerStart: (config?: {
host?: string port?: number
corsOrigin?: string host?: string
}): Promise<{ success: boolean; data: { url: string; config: any } }> => corsOrigin?: string
invoke("http_server_start", { config }), }): Promise<{ success: boolean; data: { url: string; config: any } }> =>
httpServerStop: (): Promise<{ success: boolean }> => invoke("http_server_stop"), invoke("http_server_start", { config }),
httpServerStatus: (): Promise<{ httpServerStop: (): Promise<{ success: boolean }> => invoke("http_server_stop"),
success: boolean httpServerStatus: (): Promise<{
data: { isRunning: boolean; config?: any; url?: string } success: boolean
}> => invoke("http_server_status"), data: { isRunning: boolean; config?: any; url?: string }
}> => invoke("http_server_status"),
// MCP Server
mcpServerStart: (config?: { // MCP Server
port?: number mcpServerStart: (config?: {
host?: string port?: number
}): Promise<{ success: boolean; data?: { url: string; config: { port: number; host: string } } }> => host?: string
invoke("mcp_server_start", { config }), }): Promise<{ success: boolean; data?: { url: string; config: { port: number; host: string } } }> =>
mcpServerStop: (): Promise<{ success: boolean }> => invoke("mcp_server_stop"), invoke("mcp_server_start", { config }),
mcpServerStatus: (): Promise<{ mcpServerStop: (): Promise<{ success: boolean }> => invoke("mcp_server_stop"),
success: boolean mcpServerStatus: (): Promise<{
data?: { success: boolean
is_running: boolean data?: {
config: { port: number; host: string } is_running: boolean
url?: string | null config: { port: number; host: string }
} url?: string | null
}> => invoke("mcp_server_status"), }
}> => invoke("mcp_server_status"),
// File System
fsGetConfigStructure: (): Promise<{ // File System
success: boolean fsGetConfigStructure: (): Promise<{
data: { configRoot: string; automatic: string; script: string } success: boolean
}> => invoke("fs_get_config_structure"), data: { configRoot: string; automatic: string; script: string }
fsReadJson: ( }> => invoke("fs_get_config_structure"),
relativePath: string, fsReadJson: (
folder?: "automatic" | "script" relativePath: string,
): Promise<{ success: boolean; data: any }> => invoke("fs_read_json", { relativePath, folder }), folder?: "automatic" | "script"
fsWriteJson: ( ): Promise<{ success: boolean; data: any }> => invoke("fs_read_json", { relativePath, folder }),
relativePath: string, fsWriteJson: (
data: any, relativePath: string,
folder?: "automatic" | "script" data: any,
): Promise<{ success: boolean }> => invoke("fs_write_json", { relativePath, data, folder }), folder?: "automatic" | "script"
fsReadText: ( ): Promise<{ success: boolean }> => invoke("fs_write_json", { relativePath, data, folder }),
relativePath: string, fsReadText: (
folder?: "automatic" | "script" relativePath: string,
): Promise<{ success: boolean; data: string }> => folder?: "automatic" | "script"
invoke("fs_read_text", { relativePath, folder }), ): Promise<{ success: boolean; data: string }> =>
fsWriteText: ( invoke("fs_read_text", { relativePath, folder }),
content: string, fsWriteText: (
relativePath: string, content: string,
folder?: "automatic" | "script" relativePath: string,
): Promise<{ success: boolean }> => invoke("fs_write_text", { content, relativePath, folder }), folder?: "automatic" | "script"
fsDeleteFile: ( ): Promise<{ success: boolean }> => invoke("fs_write_text", { content, relativePath, folder }),
relativePath: string, fsDeleteFile: (
folder?: "automatic" | "script" relativePath: string,
): Promise<{ success: boolean }> => invoke("fs_delete_file", { relativePath, folder }), folder?: "automatic" | "script"
fsListFiles: (folder?: "automatic" | "script"): Promise<{ success: boolean; data: any[] }> => ): Promise<{ success: boolean }> => invoke("fs_delete_file", { relativePath, folder }),
invoke("fs_list_files", { folder }), fsListFiles: (folder?: "automatic" | "script"): Promise<{ success: boolean; data: any[] }> =>
fsFileExists: ( invoke("fs_list_files", { folder }),
relativePath: string, fsFileExists: (
folder?: "automatic" | "script" relativePath: string,
): Promise<{ success: boolean; data: boolean }> => folder?: "automatic" | "script"
invoke("fs_file_exists", { relativePath, folder }), ): Promise<{ success: boolean; data: boolean }> =>
invoke("fs_file_exists", { relativePath, folder }),
// App
registerUrlProtocol: (): Promise<{ // App
success: boolean registerUrlProtocol: (): Promise<{
data?: { registered: boolean } success: boolean
message?: string data?: { registered: boolean }
}> => invoke("register_url_protocol"), message?: string
appQuit: (): Promise<void> => invoke("app_quit"), }> => invoke("register_url_protocol"),
appRestart: (): Promise<void> => invoke("app_restart"), appQuit: (): Promise<void> => invoke("app_quit"),
appRestart: (): Promise<void> => invoke("app_restart"),
// Auto Score
autoScoreGetRules: (): Promise<{ success: boolean; data: any[] }> => // Auto Score
invoke("auto_score_get_rules"), autoScoreGetRules: (): Promise<{ success: boolean; data: any[] }> =>
autoScoreAddRule: (rule: any): Promise<{ success: boolean; data?: number; message?: string }> => invoke("auto_score_get_rules"),
invoke("auto_score_add_rule", { rule }), autoScoreAddRule: (rule: any): Promise<{ success: boolean; data?: number; message?: string }> =>
autoScoreUpdateRule: ( invoke("auto_score_add_rule", { rule }),
rule: any autoScoreUpdateRule: (
): Promise<{ success: boolean; data?: boolean; message?: string }> => rule: any
invoke("auto_score_update_rule", { rule }), ): Promise<{ success: boolean; data?: boolean; message?: string }> =>
autoScoreDeleteRule: ( invoke("auto_score_update_rule", { rule }),
ruleId: number autoScoreDeleteRule: (
): Promise<{ success: boolean; data?: boolean; message?: string }> => ruleId: number
invoke("auto_score_delete_rule", { ruleId }), ): Promise<{ success: boolean; data?: boolean; message?: string }> =>
autoScoreToggleRule: (params: { invoke("auto_score_delete_rule", { ruleId }),
ruleId: number autoScoreToggleRule: (params: {
enabled: boolean ruleId: number
}): Promise<{ success: boolean; data?: boolean; message?: string }> => enabled: boolean
invoke("auto_score_toggle_rule", params), }): Promise<{ success: boolean; data?: boolean; message?: string }> =>
autoScoreGetStatus: (): Promise<{ success: boolean; data: { enabled: boolean } }> => invoke("auto_score_toggle_rule", params),
invoke("auto_score_get_status"), autoScoreGetStatus: (): Promise<{ success: boolean; data: { enabled: boolean } }> =>
autoScoreSortRules: ( invoke("auto_score_get_status"),
ruleIds: number[] autoScoreSortRules: (
): Promise<{ success: boolean; data?: boolean; message?: string }> => ruleIds: number[]
invoke("auto_score_sort_rules", { ruleIds }), ): Promise<{ success: boolean; data?: boolean; message?: string }> =>
invoke("auto_score_sort_rules", { ruleIds }),
// Generic invoke wrapper for backward compatibility with callers using `api.invoke`
invoke: async (channel: string, ...args: any[]): Promise<any> => { // Generic invoke wrapper for backward compatibility with callers using `api.invoke`
switch (channel) { invoke: async (channel: string, ...args: any[]): Promise<any> => {
case "auto-score:getRules": switch (channel) {
return api.autoScoreGetRules() case "auto-score:getRules":
case "auto-score:addRule": return api.autoScoreGetRules()
return api.autoScoreAddRule(args[0]) case "auto-score:addRule":
case "auto-score:updateRule": return api.autoScoreAddRule(args[0])
return api.autoScoreUpdateRule(args[0]) case "auto-score:updateRule":
case "auto-score:deleteRule": return api.autoScoreUpdateRule(args[0])
return api.autoScoreDeleteRule(args[0]) case "auto-score:deleteRule":
case "auto-score:toggleRule": return api.autoScoreDeleteRule(args[0])
return api.autoScoreToggleRule(args[0]) case "auto-score:toggleRule":
case "auto-score:sortRules": return api.autoScoreToggleRule(args[0])
return api.autoScoreSortRules(args[0]) case "auto-score:sortRules":
case "auto-score:getStatus": return api.autoScoreSortRules(args[0])
return api.autoScoreGetStatus() case "auto-score:getStatus":
default: return api.autoScoreGetStatus()
throw new Error(`Unsupported legacy invoke channel: ${channel}`) default:
} throw new Error(`Unsupported legacy invoke channel: ${channel}`)
}, }
} },
}
export default api
export { api } export default api
export { api }