feat: add first version of TougliGui with same features as on google sheet
This commit is contained in:
211
src-tauri/src/commands.rs
Normal file
211
src-tauri/src/commands.rs
Normal file
@ -0,0 +1,211 @@
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Mutex;
|
||||
use rusqlite::Connection;
|
||||
|
||||
use crate::{db, parser};
|
||||
|
||||
pub struct DbState(pub Mutex<Connection>);
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct GuideListItem {
|
||||
pub gid: String,
|
||||
pub name: String,
|
||||
pub last_synced_at: Option<String>,
|
||||
pub total_quests: usize,
|
||||
pub completed_quests: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SyncResult {
|
||||
pub synced: usize,
|
||||
pub errors: Vec<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_profiles(state: State<DbState>) -> Result<Vec<db::Profile>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::get_profiles(&conn).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_profile(state: State<DbState>, name: String) -> Result<db::Profile, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::create_profile(&conn, &name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_profile(state: State<DbState>, profile_id: String) -> Result<(), String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::delete_profile(&conn, &profile_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_completed_quests(state: State<DbState>, profile_id: String) -> Result<Vec<String>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::get_completed_quests(&conn, &profile_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn toggle_quest(state: State<DbState>, profile_id: String, quest_name: String) -> Result<bool, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::toggle_quest(&conn, &profile_id, &quest_name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_guides_list(state: State<DbState>, profile_id: String) -> Result<Vec<GuideListItem>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
|
||||
let completed = db::get_completed_quests(&conn, &profile_id).map_err(|e| e.to_string())?;
|
||||
let completed_set: std::collections::HashSet<String> = completed.into_iter().collect();
|
||||
|
||||
let items = guides.into_iter().map(|g| {
|
||||
let data: parser::GuideData = serde_json::from_str(&g.data).unwrap_or_else(|_| parser::GuideData {
|
||||
name: g.name.clone(),
|
||||
gid: g.gid.clone(),
|
||||
effect: String::new(),
|
||||
recommended_level: None,
|
||||
combat_legend: vec![],
|
||||
resources: vec![],
|
||||
sections: vec![],
|
||||
});
|
||||
|
||||
let all_quests = collect_quest_names(&data);
|
||||
let total = all_quests.len();
|
||||
let completed_count = all_quests.iter().filter(|q| completed_set.contains(*q)).count();
|
||||
|
||||
GuideListItem {
|
||||
gid: g.gid,
|
||||
name: g.name,
|
||||
last_synced_at: g.last_synced_at,
|
||||
total_quests: total,
|
||||
completed_quests: completed_count,
|
||||
}
|
||||
}).collect();
|
||||
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_guide(state: State<DbState>, gid: String) -> Result<parser::GuideData, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
|
||||
if let Some(g) = guides.into_iter().find(|g| g.gid == gid) {
|
||||
serde_json::from_str(&g.data).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Guide {} not found", gid))
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sync_guides(state: State<'_, DbState>) -> Result<SyncResult, String> {
|
||||
let mut synced = 0;
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for (gid, name) in parser::TABS {
|
||||
let gid = gid.to_string();
|
||||
let name = name.to_string();
|
||||
|
||||
// Run blocking HTTP+parse in a dedicated thread to avoid runtime conflict
|
||||
let result = tokio::task::spawn_blocking({
|
||||
let gid = gid.clone();
|
||||
let name = name.clone();
|
||||
move || -> Result<String, String> {
|
||||
let csv = parser::fetch_csv(&gid)?;
|
||||
let links = parser::fetch_quest_links(&gid);
|
||||
let data = parser::parse_guide_with_links(&gid, &name, &csv, &links);
|
||||
serde_json::to_string(&data).map_err(|e| e.to_string())
|
||||
}
|
||||
}).await.map_err(|e| e.to_string())?;
|
||||
|
||||
match result {
|
||||
Ok(json) => {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::upsert_guide(&conn, &gid, &name, &json).map_err(|e| e.to_string())?;
|
||||
synced += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
errors.push(format!("{}: {}", name, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SyncResult { synced, errors })
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TabInfo {
|
||||
pub gid: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_tabs_list() -> Vec<TabInfo> {
|
||||
parser::TABS.iter().map(|(gid, name)| TabInfo {
|
||||
gid: gid.to_string(),
|
||||
name: name.to_string(),
|
||||
}).collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sync_single_guide(state: State<'_, DbState>, gid: String, name: String) -> Result<(), String> {
|
||||
let json = tokio::task::spawn_blocking({
|
||||
let gid = gid.clone();
|
||||
let name = name.clone();
|
||||
move || -> Result<String, String> {
|
||||
let csv = parser::fetch_csv(&gid)?;
|
||||
let links = parser::fetch_quest_links(&gid);
|
||||
let data = parser::parse_guide_with_links(&gid, &name, &csv, &links);
|
||||
serde_json::to_string(&data).map_err(|e| e.to_string())
|
||||
}
|
||||
}).await.map_err(|e| e.to_string())??;
|
||||
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::upsert_guide(&conn, &gid, &name, &json).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This remains for backwards compat but delegates to per-guide approach
|
||||
#[tauri::command]
|
||||
pub fn has_guides(state: State<DbState>) -> Result<bool, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
let guides = db::get_guides(&conn).map_err(|e| e.to_string())?;
|
||||
Ok(!guides.is_empty())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_setting(state: State<DbState>, key: String) -> Result<Option<String>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
Ok(db::get_setting(&conn, &key))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_setting(state: State<DbState>, key: String, value: String) -> Result<(), String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::set_setting(&conn, &key, &value).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_always_on_top(app: AppHandle, value: bool) -> Result<(), String> {
|
||||
if let Some(win) = app.get_webview_window("main") {
|
||||
win.set_always_on_top(value).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err("Window not found".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_quest_names(data: &parser::GuideData) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
for section in &data.sections {
|
||||
for item in §ion.items {
|
||||
match item {
|
||||
parser::SectionItem::Quest(q) => names.push(q.name.clone()),
|
||||
parser::SectionItem::Group(g) => {
|
||||
for q in &g.quests { names.push(q.name.clone()); }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
Reference in New Issue
Block a user