feat: work on windows resizing
This commit is contained in:
118
src-tauri/Cargo.lock
generated
118
src-tauri/Cargo.lock
generated
@ -26,6 +26,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
@ -766,6 +767,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be"
|
||||
dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.11.3",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.36.0"
|
||||
@ -1081,6 +1095,12 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "ego-tree"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@ -1568,6 +1588,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@ -1880,6 +1909,20 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.29.1"
|
||||
@ -2494,6 +2537,20 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.11.3",
|
||||
"phf_codegen 0.11.3",
|
||||
"string_cache 0.8.9",
|
||||
"string_cache_codegen 0.5.4",
|
||||
"tendril 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.14.1"
|
||||
@ -3092,6 +3149,16 @@ dependencies = [
|
||||
"phf_shared 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
@ -4093,6 +4160,22 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "scraper"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"cssparser 0.31.2",
|
||||
"ego-tree",
|
||||
"getopts",
|
||||
"html5ever 0.27.0",
|
||||
"once_cell",
|
||||
"selectors 0.25.0",
|
||||
"tendril 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
@ -4140,6 +4223,25 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"cssparser 0.31.2",
|
||||
"derive_more 0.99.20",
|
||||
"fxhash",
|
||||
"log",
|
||||
"new_debug_unreachable",
|
||||
"phf 0.10.1",
|
||||
"phf_codegen 0.10.0",
|
||||
"precomputed-hash",
|
||||
"servo_arc 0.3.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.36.1"
|
||||
@ -4339,6 +4441,15 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.4.3"
|
||||
@ -5592,6 +5703,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"reqwest 0.12.28",
|
||||
"rusqlite",
|
||||
"scraper",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@ -5806,6 +5918,12 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
|
||||
@ -26,6 +26,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
dirs-next = "2"
|
||||
scraper = "0.20"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
webkit2gtk = { version = "2.0", features = ["v2_38"] }
|
||||
|
||||
@ -173,6 +173,18 @@ pub fn has_guides(state: State<DbState>) -> Result<bool, String> {
|
||||
Ok(!guides.is_empty())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_resource_inventory(state: State<DbState>, profile_id: String) -> Result<Vec<(String, i64)>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::get_resource_inventory(&conn, &profile_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_resource_quantity(state: State<DbState>, profile_id: String, resource_name: String, quantity: i64) -> Result<(), String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::set_resource_quantity(&conn, &profile_id, &resource_name, quantity).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[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())?;
|
||||
@ -194,6 +206,294 @@ pub fn set_always_on_top(app: AppHandle, value: bool) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct QuestStep {
|
||||
pub index: usize,
|
||||
pub text: String,
|
||||
pub images: Vec<String>,
|
||||
pub launch_position: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fetch_quest_detail(url: String) -> Result<Vec<QuestStep>, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(20))
|
||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let html = client.get(&url)
|
||||
.send()
|
||||
.map_err(|e| format!("Erreur réseau : {}", e))?
|
||||
.text()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
parse_quest_steps(&html)
|
||||
}).await.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
fn parse_quest_steps(html: &str) -> Result<Vec<QuestStep>, String> {
|
||||
use scraper::{Html, Selector};
|
||||
|
||||
const BASE_URL: &str = "https://www.dofuspourlesnoobs.com";
|
||||
|
||||
let document = Html::parse_document(html);
|
||||
|
||||
let children_sel = Selector::parse("#wsite-content > div").unwrap();
|
||||
let para_sel = Selector::parse("div.paragraph, div.paragraphe").unwrap();
|
||||
let img_link_sel = Selector::parse("div.wsite-image a, div.wsite-image img").unwrap();
|
||||
// Blue = Position de lancement label; red = other info labels (Prérequis, Niveau…)
|
||||
let position_sel = Selector::parse("font[color='#3a96b8'], font[color='#3A96B8']").unwrap();
|
||||
let info_block_sel = Selector::parse(
|
||||
"font[color='#3a96b8'], font[color='#3A96B8'], font[color='#ff0000'], font[color='#FF0000']"
|
||||
).unwrap();
|
||||
|
||||
let mut steps: Vec<QuestStep> = Vec::new();
|
||||
let mut first = true;
|
||||
let mut header_done = false;
|
||||
|
||||
for child in document.select(&children_sel) {
|
||||
if first { first = false; continue; }
|
||||
|
||||
let v = child.value();
|
||||
|
||||
// ── Case A: the child IS itself a paragraph ──────────────────────────
|
||||
if v.classes().any(|c| c == "paragraph" || c == "paragraphe") {
|
||||
let text = element_to_text(&child).trim().to_string();
|
||||
if text.is_empty() || is_date_meta(&text) { continue; }
|
||||
|
||||
if !header_done {
|
||||
header_done = true;
|
||||
// Info block detected by colored font labels typical of DPLN info sections
|
||||
if child.select(&info_block_sel).next().is_some() {
|
||||
let pos = extract_launch_position(&child, &position_sel);
|
||||
if pos.is_some() {
|
||||
let images = collect_images_from(&child, &img_link_sel, BASE_URL);
|
||||
steps.push(QuestStep { index: steps.len(), text: String::new(), images, launch_position: pos });
|
||||
}
|
||||
continue; // always skip as a regular step
|
||||
}
|
||||
}
|
||||
|
||||
steps.push(QuestStep { index: steps.len(), text, images: vec![], launch_position: None });
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Cases B & C: the child is a wrapper div ───────────────────────
|
||||
let inner_paras: Vec<_> = child.select(¶_sel).collect();
|
||||
let images = collect_images_from(&child, &img_link_sel, BASE_URL);
|
||||
|
||||
if inner_paras.is_empty() {
|
||||
if !images.is_empty() {
|
||||
if let Some(last) = steps.last_mut() {
|
||||
last.images.extend(images);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut first_para = true;
|
||||
for para in &inner_paras {
|
||||
let text = element_to_text(para).trim().to_string();
|
||||
if text.is_empty() || is_date_meta(&text) { continue; }
|
||||
|
||||
if !header_done && first_para {
|
||||
header_done = true;
|
||||
if para.select(&info_block_sel).next().is_some() {
|
||||
let pos = extract_launch_position(para, &position_sel);
|
||||
if pos.is_some() {
|
||||
let imgs = images.clone();
|
||||
steps.push(QuestStep { index: steps.len(), text: String::new(), images: imgs, launch_position: pos });
|
||||
}
|
||||
first_para = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let imgs = if first_para { images.clone() } else { vec![] };
|
||||
first_para = false;
|
||||
steps.push(QuestStep { index: steps.len(), text, images: imgs, launch_position: None });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(steps)
|
||||
}
|
||||
|
||||
fn extract_launch_position(para: &scraper::ElementRef, position_sel: &scraper::Selector) -> Option<String> {
|
||||
for font_el in para.select(position_sel) {
|
||||
let label: String = font_el.text().collect();
|
||||
if !label.to_lowercase().contains("position") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tree = font_el.tree();
|
||||
let para_id = para.id();
|
||||
let mut current_id = font_el.id();
|
||||
|
||||
// Walk up from font_el toward para, collecting text from siblings after each ancestor.
|
||||
// Necessary because the value <span> is a sibling of <strong> (font's grandparent),
|
||||
// not a sibling of <font> itself.
|
||||
loop {
|
||||
let current_node = tree.get(current_id)?;
|
||||
let parent_node = current_node.parent()?;
|
||||
let parent_id = parent_node.id();
|
||||
|
||||
let mut after_current = false;
|
||||
let mut result = String::new();
|
||||
|
||||
for sibling in parent_node.children() {
|
||||
if sibling.id() == current_id {
|
||||
after_current = true;
|
||||
continue;
|
||||
}
|
||||
if !after_current { continue; }
|
||||
|
||||
// A new <strong> means a new field — stop collecting
|
||||
if let scraper::Node::Element(e) = sibling.value() {
|
||||
if e.name() == "strong" { break; }
|
||||
}
|
||||
|
||||
if let Some(elem) = scraper::ElementRef::wrap(sibling) {
|
||||
let t: String = elem.text().collect();
|
||||
let t = t.trim().to_string();
|
||||
if !t.is_empty() {
|
||||
if !result.is_empty() { result.push(' '); }
|
||||
result.push_str(&t);
|
||||
}
|
||||
} else if let scraper::Node::Text(t) = sibling.value() {
|
||||
let s = t.text.trim();
|
||||
if !s.is_empty() {
|
||||
if !result.is_empty() { result.push(' '); }
|
||||
result.push_str(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pos = result.trim().trim_end_matches('.').trim().to_string();
|
||||
if !pos.is_empty() {
|
||||
return Some(pos);
|
||||
}
|
||||
|
||||
if parent_id == para_id {
|
||||
break;
|
||||
}
|
||||
current_id = parent_id;
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn collect_images_from(
|
||||
el: &scraper::ElementRef,
|
||||
img_link_sel: &scraper::Selector,
|
||||
base_url: &str,
|
||||
) -> Vec<String> {
|
||||
let mut images = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for img_el in el.select(img_link_sel) {
|
||||
let url_opt = img_el.value().attr("href")
|
||||
.filter(|u| is_image_url(u))
|
||||
.or_else(|| img_el.value().attr("src").filter(|u| is_image_url(u)));
|
||||
if let Some(url) = url_opt {
|
||||
let absolute = if url.starts_with('/') {
|
||||
format!("{}{}", base_url, url)
|
||||
} else {
|
||||
url.to_string()
|
||||
};
|
||||
if seen.insert(absolute.clone()) {
|
||||
images.push(absolute);
|
||||
}
|
||||
}
|
||||
}
|
||||
images
|
||||
}
|
||||
|
||||
fn is_date_meta(text: &str) -> bool {
|
||||
let lower = text.to_lowercase();
|
||||
// Matches Weebly blog metadata lines like "Publié le 01/01/2024" or "Mis à jour le …"
|
||||
let is_meta_word = lower.starts_with("publi")
|
||||
|| lower.starts_with("mis à jour")
|
||||
|| lower.starts_with("mis en ligne")
|
||||
|| lower.starts_with("modifi")
|
||||
|| lower.starts_with("rédigé")
|
||||
|| lower.starts_with("redige");
|
||||
// Also catch bare date lines like "01/01/2024" or "2024-01-01"
|
||||
let digit_count = text.chars().filter(|c| c.is_ascii_digit()).count();
|
||||
let sep_count = text.chars().filter(|&c| c == '/' || c == '-').count();
|
||||
let is_bare_date = text.len() < 40 && digit_count >= 6 && sep_count >= 2;
|
||||
is_meta_word || is_bare_date
|
||||
}
|
||||
|
||||
fn is_image_url(url: &str) -> bool {
|
||||
url.contains("uploads")
|
||||
|| url.ends_with(".jpg")
|
||||
|| url.ends_with(".jpeg")
|
||||
|| url.ends_with(".png")
|
||||
|| url.ends_with(".webp")
|
||||
}
|
||||
|
||||
fn element_to_text(el: &scraper::ElementRef) -> String {
|
||||
let mut out = String::new();
|
||||
for node in el.descendants() {
|
||||
match node.value() {
|
||||
scraper::Node::Text(t) => {
|
||||
let s = t.text.trim();
|
||||
if !s.is_empty() {
|
||||
if !out.is_empty() && !out.ends_with('\n') && !out.ends_with(' ') {
|
||||
out.push(' ');
|
||||
}
|
||||
out.push_str(s);
|
||||
}
|
||||
}
|
||||
scraper::Node::Element(e) => {
|
||||
if matches!(e.name(), "script" | "style" | "noscript") { continue; }
|
||||
if matches!(e.name(), "p" | "br" | "h1" | "h2" | "h3" | "h4" | "li" | "div" | "tr") {
|
||||
if !out.is_empty() && !out.ends_with('\n') {
|
||||
out.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Collapse multiple blank lines
|
||||
let mut result = String::new();
|
||||
let mut prev_empty = false;
|
||||
for line in out.lines() {
|
||||
let t = line.trim();
|
||||
if t.is_empty() {
|
||||
if !prev_empty { result.push('\n'); }
|
||||
prev_empty = true;
|
||||
} else {
|
||||
result.push_str(t);
|
||||
result.push('\n');
|
||||
prev_empty = false;
|
||||
}
|
||||
}
|
||||
result.trim().to_string()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_completed_steps(
|
||||
state: State<DbState>,
|
||||
profile_id: String,
|
||||
quest_name: String,
|
||||
) -> Result<Vec<i64>, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::get_completed_steps(&conn, &profile_id, &quest_name).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn toggle_quest_step(
|
||||
state: State<DbState>,
|
||||
profile_id: String,
|
||||
quest_name: String,
|
||||
step_index: i64,
|
||||
) -> Result<bool, String> {
|
||||
let conn = state.0.lock().map_err(|e| e.to_string())?;
|
||||
db::toggle_quest_step(&conn, &profile_id, &quest_name, step_index).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn collect_quest_names(data: &parser::GuideData) -> Vec<String> {
|
||||
let mut names = Vec::new();
|
||||
for section in &data.sections {
|
||||
|
||||
@ -60,6 +60,22 @@ pub fn migrate(conn: &Connection) -> Result<()> {
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS quest_step_progress (
|
||||
profile_id TEXT NOT NULL,
|
||||
quest_name TEXT NOT NULL,
|
||||
step_index INTEGER NOT NULL,
|
||||
PRIMARY KEY (profile_id, quest_name, step_index),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_inventory (
|
||||
profile_id TEXT NOT NULL,
|
||||
resource_name TEXT NOT NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (profile_id, resource_name),
|
||||
FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
|
||||
);
|
||||
")?;
|
||||
Ok(())
|
||||
}
|
||||
@ -145,6 +161,62 @@ pub fn get_guides(conn: &Connection) -> Result<Vec<GuideRow>> {
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
pub fn get_completed_steps(conn: &Connection, profile_id: &str, quest_name: &str) -> Result<Vec<i64>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT step_index FROM quest_step_progress WHERE profile_id = ?1 AND quest_name = ?2"
|
||||
)?;
|
||||
let rows = stmt.query_map(params![profile_id, quest_name], |row| row.get(0))?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
pub fn toggle_quest_step(conn: &Connection, profile_id: &str, quest_name: &str, step_index: i64) -> Result<bool> {
|
||||
let exists: bool = conn.query_row(
|
||||
"SELECT COUNT(*) FROM quest_step_progress WHERE profile_id = ?1 AND quest_name = ?2 AND step_index = ?3",
|
||||
params![profile_id, quest_name, step_index],
|
||||
|row| row.get::<_, i64>(0),
|
||||
).map(|c| c > 0)?;
|
||||
|
||||
if exists {
|
||||
conn.execute(
|
||||
"DELETE FROM quest_step_progress WHERE profile_id = ?1 AND quest_name = ?2 AND step_index = ?3",
|
||||
params![profile_id, quest_name, step_index],
|
||||
)?;
|
||||
Ok(false)
|
||||
} else {
|
||||
conn.execute(
|
||||
"INSERT INTO quest_step_progress (profile_id, quest_name, step_index) VALUES (?1, ?2, ?3)",
|
||||
params![profile_id, quest_name, step_index],
|
||||
)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_resource_inventory(conn: &Connection, profile_id: &str) -> Result<Vec<(String, i64)>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT resource_name, quantity FROM resource_inventory WHERE profile_id = ?1"
|
||||
)?;
|
||||
let rows = stmt.query_map(params![profile_id], |row| {
|
||||
Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?))
|
||||
})?;
|
||||
rows.collect()
|
||||
}
|
||||
|
||||
pub fn set_resource_quantity(conn: &Connection, profile_id: &str, resource_name: &str, quantity: i64) -> Result<()> {
|
||||
if quantity <= 0 {
|
||||
conn.execute(
|
||||
"DELETE FROM resource_inventory WHERE profile_id = ?1 AND resource_name = ?2",
|
||||
params![profile_id, resource_name],
|
||||
)?;
|
||||
} else {
|
||||
conn.execute(
|
||||
"INSERT INTO resource_inventory (profile_id, resource_name, quantity) VALUES (?1, ?2, ?3)
|
||||
ON CONFLICT(profile_id, resource_name) DO UPDATE SET quantity=excluded.quantity",
|
||||
params![profile_id, resource_name, quantity],
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_setting(conn: &Connection, key: &str) -> Option<String> {
|
||||
conn.query_row(
|
||||
"SELECT value FROM settings WHERE key = ?1",
|
||||
|
||||
@ -48,6 +48,11 @@ pub fn run() {
|
||||
commands::get_setting,
|
||||
commands::set_setting,
|
||||
commands::set_always_on_top,
|
||||
commands::fetch_quest_detail,
|
||||
commands::get_completed_steps,
|
||||
commands::toggle_quest_step,
|
||||
commands::get_resource_inventory,
|
||||
commands::set_resource_quantity,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"title": "TougliGui",
|
||||
"width": 1100,
|
||||
"height": 720,
|
||||
"minWidth": 380,
|
||||
"minWidth": 300,
|
||||
"minHeight": 400,
|
||||
"decorations": false,
|
||||
"transparent": false,
|
||||
|
||||
Reference in New Issue
Block a user