Compare commits
13 commits
Author | SHA1 | Date | |
---|---|---|---|
443c7a7e0c | |||
4bfd47d8c4 | |||
94ed4c46b9 | |||
25b3128181 | |||
23cd674e31 | |||
af921088f9 | |||
a40e9c98c4 | |||
2d072f3303 | |||
792b4daf04 | |||
9eb930da19 | |||
e0c49ffa86 | |||
4ff9928a94 | |||
29434dc37c |
6 changed files with 261 additions and 235 deletions
84
Cargo.lock
generated
84
Cargo.lock
generated
|
@ -287,6 +287,21 @@ version = "0.2.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.94"
|
version = "1.0.94"
|
||||||
|
@ -687,6 +702,20 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ciborium"
|
name = "ciborium"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -737,6 +766,7 @@ version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"futures",
|
"futures",
|
||||||
"gloo-net 0.5.0",
|
"gloo-net 0.5.0",
|
||||||
|
@ -755,6 +785,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.9",
|
"thiserror 2.0.9",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
@ -831,6 +862,12 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -1383,6 +1420,29 @@ version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -2124,6 +2184,15 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.5"
|
||||||
|
@ -3332,6 +3401,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -3507,6 +3582,15 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
|
@ -31,6 +31,8 @@ serde_json="1.0.133"
|
||||||
thiserror = "2.0.9"
|
thiserror = "2.0.9"
|
||||||
zerofrom = "0.1"
|
zerofrom = "0.1"
|
||||||
mio = "0.8"
|
mio = "0.8"
|
||||||
|
chrono = "0.4"
|
||||||
|
urlencoding = "2.1.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ssr"]
|
default = ["ssr"]
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
use leptos::*;
|
|
||||||
use leptos_dom::ev::SubmitEvent;
|
|
||||||
use leptos::logging::log;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn ItemForm(on_submit: Box<dyn Fn(String, String, Vec<(String, String)>, String, u8)>) -> impl IntoView {
|
|
||||||
let (name, set_name) = create_signal(String::new());
|
|
||||||
let (description, set_description) = create_signal(String::new());
|
|
||||||
let (tags, set_tags) = create_signal(Vec::<(String, String)>::new());
|
|
||||||
let (tag_key, set_tag_key) = create_signal(String::new());
|
|
||||||
let (tag_value, set_tag_value) = create_signal(String::new());
|
|
||||||
let (review, set_review) = create_signal(String::new());
|
|
||||||
let (rating, set_rating) = create_signal(5u8); // Default rating to 5
|
|
||||||
|
|
||||||
let add_tag = move |_| {
|
|
||||||
if !tag_key.get().is_empty() && !tag_value.get().is_empty() {
|
|
||||||
set_tags.update(|t| t.push((tag_key.get(), tag_value.get())));
|
|
||||||
set_tag_key.set(String::new());
|
|
||||||
set_tag_value.set(String::new());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle_submit = move |ev: SubmitEvent| {
|
|
||||||
ev.prevent_default();
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if name.get().is_empty() || description.get().is_empty() || rating.get() < 1 || rating.get() > 5 {
|
|
||||||
log!("Validation failed: Check required fields.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
on_submit(
|
|
||||||
name.get(),
|
|
||||||
description.get(),
|
|
||||||
tags.get().clone(),
|
|
||||||
review.get(),
|
|
||||||
rating.get(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset values
|
|
||||||
set_name.set(String::new());
|
|
||||||
set_description.set(String::new());
|
|
||||||
set_tags.set(vec![]);
|
|
||||||
set_review.set(String::new());
|
|
||||||
set_rating.set(5);
|
|
||||||
};
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<form on:submit=handle_submit>
|
|
||||||
<input type="text" placeholder="Name" on:input=move |e| set_name.set(event_target_value(&e)) />
|
|
||||||
<textarea placeholder="Description" on:input=move |e| set_description.set(event_target_value(&e)) />
|
|
||||||
<h3>{ "Add Tags" }</h3>
|
|
||||||
<input type="text" placeholder="Key" on:input=move |e| set_tag_key.set(event_target_value(&e)) />
|
|
||||||
<input type="text" placeholder="Value" on:input=move |e| set_tag_value.set(event_target_value(&e)) />
|
|
||||||
<button type="button" on:click=add_tag>{ "Add Tag" }</button>
|
|
||||||
<ul>
|
|
||||||
{tags.get().iter().map(|(key, value)| view! {
|
|
||||||
<li>{ format!("{}: {}", key, value) }</li>
|
|
||||||
}).collect::<Vec<_>>() }
|
|
||||||
</ul>
|
|
||||||
<h3>{ "Write a Review" }</h3>
|
|
||||||
<textarea placeholder="Review" on:input=move |e| set_review.set(event_target_value(&e)) />
|
|
||||||
<h3>{ "Rating (1-5)" }</h3>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="5"
|
|
||||||
value={rating.get()}
|
|
||||||
on:input=move |e| set_rating.set(event_target_value(&e).parse::<u8>().unwrap_or(5))
|
|
||||||
/>
|
|
||||||
<button type="submit">{ "Add Item" }</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,6 +8,9 @@ use crate::models::item::Item;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
use urlencoding::encode;
|
||||||
|
use gloo_net::http::Request;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
struct WikidataSuggestion {
|
struct WikidataSuggestion {
|
||||||
|
@ -40,11 +43,11 @@ pub fn ItemsList(
|
||||||
// State to manage dynamic property names
|
// State to manage dynamic property names
|
||||||
let (custom_properties, set_custom_properties) = create_signal(Vec::<String>::new());
|
let (custom_properties, set_custom_properties) = create_signal(Vec::<String>::new());
|
||||||
|
|
||||||
// state to manage suggestions visibility
|
// State to manage suggestions visibility
|
||||||
let (show_suggestions, set_show_suggestions) = create_signal(HashMap::<String, bool>::new());
|
let (show_suggestions, set_show_suggestions) = create_signal(HashMap::<String, bool>::new());
|
||||||
|
|
||||||
// cache to store fetched properties
|
// cache to store fetched properties
|
||||||
let (fetched_properties, set_fetched_properties) = create_signal(HashMap::<String, String>::new());
|
let (fetched_properties, set_fetched_properties) = create_signal(HashMap::<String, HashMap<String, String>>::new());
|
||||||
|
|
||||||
// Signal to store the fetched property labels
|
// Signal to store the fetched property labels
|
||||||
let (property_labels, set_property_labels) = create_signal(HashMap::<String, String>::new());
|
let (property_labels, set_property_labels) = create_signal(HashMap::<String, String>::new());
|
||||||
|
@ -98,7 +101,7 @@ pub fn ItemsList(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log!("Items after loading: {:?}", items.get());
|
// log!("Items after loading: {:?}", items.get());
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
log!("Error loading items: {}", err);
|
log!("Error loading items: {}", err);
|
||||||
|
@ -160,7 +163,7 @@ pub fn ItemsList(
|
||||||
match response {
|
match response {
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if resp.status() == 200 {
|
if resp.status() == 200 {
|
||||||
log!("Item saved to database: {:?}", item_to_send);
|
// log!("Item saved to database: {:?}", item_to_send);
|
||||||
} else {
|
} else {
|
||||||
log!("Failed to save item: {}", resp.status_text());
|
log!("Failed to save item: {}", resp.status_text());
|
||||||
}
|
}
|
||||||
|
@ -184,7 +187,7 @@ pub fn ItemsList(
|
||||||
.await
|
.await
|
||||||
.map_err(|err| format!("Failed to parse items: {:?}", err))?;
|
.map_err(|err| format!("Failed to parse items: {:?}", err))?;
|
||||||
|
|
||||||
log!("Deserialized DB items: {:?}", db_items);
|
// log!("Deserialized DB items: {:?}", db_items);
|
||||||
|
|
||||||
// Convert DbItem to Item
|
// Convert DbItem to Item
|
||||||
let items = db_items
|
let items = db_items
|
||||||
|
@ -207,14 +210,14 @@ pub fn ItemsList(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
log!("Converted items: {:?}", items);
|
// log!("Converted items: {:?}", items);
|
||||||
Ok(items)
|
Ok(items)
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Failed to fetch items: {}", response.status_text()))
|
Err(format!("Failed to fetch items: {}", response.status_text()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove an item
|
// Function to remove an item
|
||||||
let remove_item = move |index: usize| {
|
let remove_item = move |index: usize| {
|
||||||
let item_id = items.get()[index].id.clone();
|
let item_id = items.get()[index].id.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
|
@ -237,6 +240,7 @@ pub fn ItemsList(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function to remove a property
|
||||||
let remove_property = move |property: String| {
|
let remove_property = move |property: String| {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let response = gloo_net::http::Request::delete(&format!("/api/properties/{}", property))
|
let response = gloo_net::http::Request::delete(&format!("/api/properties/{}", property))
|
||||||
|
@ -266,9 +270,10 @@ pub fn ItemsList(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// State to store Wikidata suggestions
|
||||||
let (wikidata_suggestions, set_wikidata_suggestions) = create_signal(HashMap::<String, Vec<WikidataSuggestion>>::new());
|
let (wikidata_suggestions, set_wikidata_suggestions) = create_signal(HashMap::<String, Vec<WikidataSuggestion>>::new());
|
||||||
|
|
||||||
// Fetch Wikidata suggestions
|
// Function to fetch Wikidata suggestions
|
||||||
let fetch_wikidata_suggestions = move |key: String, query: String| {
|
let fetch_wikidata_suggestions = move |key: String, query: String| {
|
||||||
log!("Fetching suggestions for key: {}, query: {}", key, query);
|
log!("Fetching suggestions for key: {}, query: {}", key, query);
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
|
@ -287,9 +292,7 @@ pub fn ItemsList(
|
||||||
match gloo_net::http::Request::get(&url).send().await {
|
match gloo_net::http::Request::get(&url).send().await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if let Ok(data) = response.json::<WikidataResponse>().await {
|
if let Ok(data) = response.json::<WikidataResponse>().await {
|
||||||
log!("Fetching suggestions for key: {}, query: {}", key, query);
|
|
||||||
set_wikidata_suggestions.update(|suggestions| {
|
set_wikidata_suggestions.update(|suggestions| {
|
||||||
log!("Updated suggestions: {:?}", suggestions);
|
|
||||||
suggestions.insert(key, data.search);
|
suggestions.insert(key, data.search);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -300,99 +303,154 @@ pub fn ItemsList(
|
||||||
};
|
};
|
||||||
|
|
||||||
//function to fetch properties
|
//function to fetch properties
|
||||||
async fn fetch_item_properties(wikidata_id: &str, set_fetched_properties: WriteSignal<HashMap<String, String>>, set_property_labels: WriteSignal<HashMap<String, String>>,) -> HashMap<String, String> {
|
async fn fetch_item_properties(wikidata_id: &str) -> HashMap<String, String> {
|
||||||
let url = format!(
|
let sparql_query = format!(
|
||||||
"https://www.wikidata.org/wiki/Special:EntityData/{}.json",
|
r#"
|
||||||
|
SELECT ?propLabel ?value ?valueLabel WHERE {{
|
||||||
|
wd:{} ?prop ?statement.
|
||||||
|
?statement ?ps ?value.
|
||||||
|
?property wikibase:claim ?prop.
|
||||||
|
?property wikibase:statementProperty ?ps.
|
||||||
|
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
wikidata_id
|
wikidata_id
|
||||||
);
|
);
|
||||||
|
|
||||||
match gloo_net::http::Request::get(&url).send().await {
|
let url = format!(
|
||||||
|
"https://query.wikidata.org/sparql?query={}&format=json",
|
||||||
|
urlencoding::encode(&sparql_query)
|
||||||
|
);
|
||||||
|
|
||||||
|
match gloo_net::http::Request::get(&url)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if let Ok(data) = response.json::<serde_json::Value>().await {
|
if let Ok(data) = response.json::<serde_json::Value>().await {
|
||||||
if let Some(entities) = data["entities"].as_object() {
|
|
||||||
if let Some(entity) = entities.get(wikidata_id) {
|
|
||||||
if let Some(claims) = entity["claims"].as_object() {
|
|
||||||
let mut result = HashMap::new();
|
let mut result = HashMap::new();
|
||||||
for (property, values) in claims {
|
if let Some(bindings) = data["results"]["bindings"].as_array() {
|
||||||
if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_str() {
|
for binding in bindings {
|
||||||
result.insert(property.clone(), value.to_string());
|
let prop_label = binding["propLabel"]["value"].as_str().unwrap_or("").to_string();
|
||||||
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_object() {
|
let prop_label = prop_label.replace("http://www.wikidata.org/prop/", "");
|
||||||
result.insert(property.clone(), serde_json::to_string(value).unwrap());
|
let value_label = binding["valueLabel"]["value"].as_str().unwrap_or("").to_string();
|
||||||
} else if let Some(value) = values[0]["mainsnak"]["datavalue"]["value"].as_f64() {
|
result.insert(prop_label, value_label);
|
||||||
result.insert(property.clone(), value.to_string());
|
log!("result: {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
result.insert(property.clone(), "Unsupported data type".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fetch labels for the properties
|
|
||||||
let property_ids = result.keys().cloned().collect::<Vec<_>>();
|
|
||||||
let labels = fetch_property_labels(property_ids).await;
|
|
||||||
set_property_labels.update(|labels_map| {
|
|
||||||
for (key, value) in labels {
|
|
||||||
labels_map.insert(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update fetched properties
|
|
||||||
set_fetched_properties.update(|properties| {
|
|
||||||
for (key, val) in result.clone() {
|
|
||||||
properties.insert(key.clone(), val.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => log!("Error fetching item properties: {:?}", err),
|
|
||||||
}
|
|
||||||
|
|
||||||
HashMap::new()
|
HashMap::new()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Err(_) => HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_property_labels(property_ids: Vec<String>) -> HashMap<String, String> {
|
async fn fetch_property_labels(property_ids: Vec<String>) -> HashMap<String, String> {
|
||||||
let mut property_labels = HashMap::new();
|
log!("Fetching property labels for properties: {:?}", property_ids);
|
||||||
|
|
||||||
// Construct the API URL to fetch labels for multiple properties
|
// Remove the "http://www.wikidata.org/prop/" prefix from property IDs
|
||||||
let url = format!(
|
let property_ids: Vec<String> = property_ids
|
||||||
"https://www.wikidata.org/w/api.php?action=wbgetentities&ids={}&props=labels&format=json&languages=en&origin=*",
|
.into_iter()
|
||||||
property_ids.join("|")
|
.map(|id| id.replace("http://www.wikidata.org/prop/", ""))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let property_ids_str = property_ids.join(" wd:");
|
||||||
|
let sparql_query = format!(
|
||||||
|
r#"
|
||||||
|
SELECT ?prop ?propLabel WHERE {{
|
||||||
|
VALUES ?prop {{ wd:{} }}
|
||||||
|
SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }}
|
||||||
|
}}
|
||||||
|
"#,
|
||||||
|
property_ids_str
|
||||||
);
|
);
|
||||||
|
|
||||||
match gloo_net::http::Request::get(&url).send().await {
|
let url = format!(
|
||||||
|
"https://query.wikidata.org/sparql?query={}&format=json",
|
||||||
|
urlencoding::encode(&sparql_query)
|
||||||
|
);
|
||||||
|
log!("Sending request to URL: {}", url);
|
||||||
|
|
||||||
|
match gloo_net::http::Request::get(&url)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if let Ok(data) = response.json::<serde_json::Value>().await {
|
log!("Received response from Wikidata. Status: {}", response.status());
|
||||||
if let Some(entities) = data["entities"].as_object() {
|
if response.status() != 200 {
|
||||||
for (property_id, entity) in entities {
|
log!("Error: Unexpected status code {}", response.status());
|
||||||
if let Some(label) = entity["labels"]["en"]["value"].as_str() {
|
return HashMap::new();
|
||||||
property_labels.insert(property_id.clone(), label.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => log!("Error fetching property labels: {:?}", err),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property_labels
|
match response.text().await {
|
||||||
|
Ok(text) => {
|
||||||
|
log!("Response body: {}", text);
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&text) {
|
||||||
|
Ok(data) => {
|
||||||
|
log!("Successfully parsed response from Wikidata");
|
||||||
|
let mut result = HashMap::new();
|
||||||
|
if let Some(bindings) = data["results"]["bindings"].as_array() {
|
||||||
|
log!("Found {} bindings in response", bindings.len());
|
||||||
|
for (i, binding) in bindings.iter().enumerate() {
|
||||||
|
if let (Some(prop), Some(label)) = (
|
||||||
|
binding["prop"]["value"].as_str(),
|
||||||
|
binding["propLabel"]["value"].as_str()
|
||||||
|
) {
|
||||||
|
let prop_id = prop.split('/').last().unwrap_or("").to_string();
|
||||||
|
result.insert(prop_id.clone(), label.to_string());
|
||||||
|
log!("Processed binding {}: prop_id = {}, label = {}", i, prop_id, label);
|
||||||
|
} else {
|
||||||
|
log!("Warning: Binding {} is missing prop or propLabel", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log!("Warning: No bindings found in the response");
|
||||||
|
}
|
||||||
|
log!("Fetched {} property labels", result.len());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log!("Error parsing response from Wikidata: {:?}", e);
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log!("Error reading response body: {:?}", e);
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log!("Error fetching property labels from Wikidata: {:?}", e);
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new custom property
|
// Add a new custom property
|
||||||
let add_property = move |property: String| {
|
let add_property = move |property: String| {
|
||||||
|
// Normalize the property ID
|
||||||
|
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
||||||
|
|
||||||
set_custom_properties.update(|props| {
|
set_custom_properties.update(|props| {
|
||||||
if !props.contains(&property) && !property.is_empty() {
|
if !props.contains(&normalized_property) && !normalized_property.is_empty() {
|
||||||
props.push(property.clone());
|
props.push(normalized_property.clone());
|
||||||
|
|
||||||
//update the selected_properties state when a new property is added
|
//update the selected_properties state when a new property is added
|
||||||
set_selected_properties.update(|selected| {
|
set_selected_properties.update(|selected| {
|
||||||
selected.insert(property.clone(), true);
|
selected.insert(normalized_property.clone(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the grid updates reactively
|
// Ensure the grid updates reactively
|
||||||
set_items.update(|items| {
|
set_items.update(|items| {
|
||||||
for item in items {
|
for item in items {
|
||||||
item.custom_properties.entry(property.clone()).or_insert_with(|| "".to_string());
|
item.custom_properties.entry(normalized_property.clone()).or_insert_with(|| "".to_string());
|
||||||
|
|
||||||
// Save the updated item to the database
|
// Save the updated item to the database
|
||||||
let item_clone = item.clone();
|
let item_clone = item.clone();
|
||||||
|
@ -403,12 +461,14 @@ pub fn ItemsList(
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch the property label
|
// Fetch the property label
|
||||||
let property_id = property.clone();
|
let property_id = normalized_property.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let labels = fetch_property_labels(vec![property_id.clone()]).await;
|
let labels = fetch_property_labels(vec![property_id.clone()]).await;
|
||||||
|
log!("Fetched labels: {:?}", labels);
|
||||||
set_property_labels.update(|labels_map| {
|
set_property_labels.update(|labels_map| {
|
||||||
if let Some(label) = labels.get(&property_id) {
|
for (key, value) in labels {
|
||||||
labels_map.insert(property_id, label.clone());
|
log!("Inserting label: {} -> {}", key, value);
|
||||||
|
labels_map.insert(key, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -423,8 +483,16 @@ pub fn ItemsList(
|
||||||
let set_property_labels = set_property_labels.clone();
|
let set_property_labels = set_property_labels.clone();
|
||||||
let property_clone = property.clone();
|
let property_clone = property.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
let properties = fetch_item_properties(&wikidata_id).await;
|
||||||
// log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
// Update fetched properties and property labels
|
||||||
|
set_fetched_properties.update(|fp| {
|
||||||
|
fp.insert(wikidata_id.clone(), properties.clone());
|
||||||
|
});
|
||||||
|
set_property_labels.update(|pl| {
|
||||||
|
for (key, value) in properties.iter() {
|
||||||
|
pl.entry(key.clone()).or_insert_with(|| value.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
if let Some(value) = properties.get(&property_clone) {
|
if let Some(value) = properties.get(&property_clone) {
|
||||||
set_items.update(|items| {
|
set_items.update(|items| {
|
||||||
if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) {
|
if let Some(item) = items.iter_mut().find(|item| item.wikidata_id.as_ref().unwrap() == &wikidata_id) {
|
||||||
|
@ -454,7 +522,7 @@ pub fn ItemsList(
|
||||||
let set_fetched_properties = set_fetched_properties.clone();
|
let set_fetched_properties = set_fetched_properties.clone();
|
||||||
let set_property_labels = set_property_labels.clone();
|
let set_property_labels = set_property_labels.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
let properties = fetch_item_properties(&wikidata_id).await;
|
||||||
log!("Fetched properties for index {}: {:?}", index, properties);
|
log!("Fetched properties for index {}: {:?}", index, properties);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -481,6 +549,7 @@ pub fn ItemsList(
|
||||||
id: Uuid::new_v4().to_string(),
|
id: Uuid::new_v4().to_string(),
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
// reviews: vec![],
|
||||||
wikidata_id: None,
|
wikidata_id: None,
|
||||||
custom_properties: HashMap::new(),
|
custom_properties: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
@ -496,7 +565,7 @@ pub fn ItemsList(
|
||||||
};
|
};
|
||||||
|
|
||||||
// List of properties to display as rows
|
// List of properties to display as rows
|
||||||
let properties = vec!["Name", "Description", "Actions"];
|
let properties = vec!["Name", "Description"];
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
|
@ -505,9 +574,12 @@ pub fn ItemsList(
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{ "Property" }</th>
|
<th>{ "Property" }</th>
|
||||||
{move || items.get().iter().enumerate().map(|(index, _)| {
|
{move || items.get().iter().enumerate().map(|(index, item)| {
|
||||||
view! {
|
view! {
|
||||||
<th>{ format!("Item {}", index + 1) }</th>
|
<th>
|
||||||
|
{ item.name.clone() }
|
||||||
|
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
|
||||||
|
</th>
|
||||||
}
|
}
|
||||||
}).collect::<Vec<_>>()}
|
}).collect::<Vec<_>>()}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -592,7 +664,7 @@ pub fn ItemsList(
|
||||||
let set_fetched_properties = set_fetched_properties.clone();
|
let set_fetched_properties = set_fetched_properties.clone();
|
||||||
let set_property_labels = set_property_labels.clone();
|
let set_property_labels = set_property_labels.clone();
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let properties = fetch_item_properties(&wikidata_id, set_fetched_properties, set_property_labels).await;
|
let properties = fetch_item_properties(&wikidata_id).await;
|
||||||
// log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
// log!("Fetched properties for Wikidata ID {}: {:?}", wikidata_id, properties);
|
||||||
|
|
||||||
// Populate the custom properties for the new item
|
// Populate the custom properties for the new item
|
||||||
|
@ -643,9 +715,6 @@ pub fn ItemsList(
|
||||||
input_type=InputType::TextArea
|
input_type=InputType::TextArea
|
||||||
/>
|
/>
|
||||||
}.into_view(),
|
}.into_view(),
|
||||||
"Actions" => view! {
|
|
||||||
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
|
|
||||||
}.into_view(),
|
|
||||||
_ => view! {
|
_ => view! {
|
||||||
{ "" }
|
{ "" }
|
||||||
}.into_view(),
|
}.into_view(),
|
||||||
|
@ -659,12 +728,11 @@ pub fn ItemsList(
|
||||||
// Dynamically adding custom properties as columns
|
// Dynamically adding custom properties as columns
|
||||||
{move || {
|
{move || {
|
||||||
let custom_props = custom_properties.get().clone();
|
let custom_props = custom_properties.get().clone();
|
||||||
log!("Rendering custom properties: {:?}", custom_props);
|
|
||||||
custom_props.into_iter().map(move |property| {
|
custom_props.into_iter().map(move |property| {
|
||||||
let property_clone = property.clone();
|
let normalized_property = property.replace("http://www.wikidata.org/prop/", "");
|
||||||
let property_label = property_labels.get().get(&property_clone).cloned().unwrap_or_else(|| property_clone.clone());
|
let property_label = property_labels.get().get(&normalized_property).cloned().unwrap_or_else(|| normalized_property.clone());
|
||||||
let property_clone_for_button = property_clone.clone();
|
log!("Rendering property: {} -> {}", normalized_property, property_label);
|
||||||
let property_clone_for_cells = property_clone.clone();
|
let property_clone_for_button = normalized_property.clone();
|
||||||
view! {
|
view! {
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
@ -686,7 +754,7 @@ pub fn ItemsList(
|
||||||
}>{ "Delete" }</button>
|
}>{ "Delete" }</button>
|
||||||
</td>
|
</td>
|
||||||
{move || {
|
{move || {
|
||||||
let property_clone_for_cells = property_clone.clone();
|
let property_clone_for_cells = normalized_property.clone();
|
||||||
items.get().iter().enumerate().map(move |(index, item)| {
|
items.get().iter().enumerate().map(move |(index, item)| {
|
||||||
let property_clone_for_closure = property_clone_for_cells.clone();
|
let property_clone_for_closure = property_clone_for_cells.clone();
|
||||||
view! {
|
view! {
|
||||||
|
@ -698,10 +766,8 @@ pub fn ItemsList(
|
||||||
focused_cell=focused_cell
|
focused_cell=focused_cell
|
||||||
set_focused_cell=set_focused_cell.clone()
|
set_focused_cell=set_focused_cell.clone()
|
||||||
on_focus=Some(Callback::new(move |_| {
|
on_focus=Some(Callback::new(move |_| {
|
||||||
log!("Custom property input focused");
|
|
||||||
}))
|
}))
|
||||||
on_blur=Some(Callback::new(move |_| {
|
on_blur=Some(Callback::new(move |_| {
|
||||||
log!("Custom property input blurred");
|
|
||||||
}))
|
}))
|
||||||
input_type=InputType::TextArea
|
input_type=InputType::TextArea
|
||||||
/>
|
/>
|
||||||
|
@ -716,36 +782,33 @@ pub fn ItemsList(
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<input type="text" id="new-property" placeholder="Add New Property" list="properties"/>
|
<input type="text" id="new-property" placeholder="Add New Property" list="properties" on:keydown=move |event| {
|
||||||
<datalist id="properties">
|
if event.key() == "Enter"{
|
||||||
{move || {
|
let input_element = event.target().unwrap().dyn_into::<web_sys::HtmlInputElement>().unwrap();
|
||||||
let properties = fetched_properties.get().clone();
|
let property = input_element.value();
|
||||||
let property_labels = property_labels.get().clone();
|
if !property.is_empty() {
|
||||||
properties.into_iter().map(|(key, _)| {
|
|
||||||
let key_clone = key.clone();
|
|
||||||
let label = property_labels.get(&key_clone).cloned().unwrap_or_else(|| key_clone.clone());
|
|
||||||
view! {
|
|
||||||
<option value={format!("{} - {}", key, label)}>{ format!("{} - {}", key, label) }</option>
|
|
||||||
}
|
|
||||||
}).collect::<Vec<_>>()
|
|
||||||
}}
|
|
||||||
</datalist>
|
|
||||||
<button on:click=move |_| {
|
|
||||||
let property = web_sys::window()
|
|
||||||
.unwrap()
|
|
||||||
.document()
|
|
||||||
.unwrap()
|
|
||||||
.get_element_by_id("new-property")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<web_sys::HtmlInputElement>()
|
|
||||||
.unwrap()
|
|
||||||
.value();
|
|
||||||
// Extract the coded name from the selected value
|
// Extract the coded name from the selected value
|
||||||
let coded_name = property.split(" - ").next().unwrap_or(&property).to_string();
|
let coded_name = property.split(" - ").next().unwrap_or(&property).to_string();
|
||||||
|
|
||||||
// Add the property using the coded name
|
// Add the property using the coded name
|
||||||
add_property(coded_name);
|
add_property(coded_name);
|
||||||
}>{ "Add Property" }</button>
|
|
||||||
|
// Clear the input field
|
||||||
|
input_element.set_value("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} />
|
||||||
|
<datalist id="properties">
|
||||||
|
{move || {
|
||||||
|
let property_labels = property_labels.get().clone();
|
||||||
|
property_labels.into_iter().map(|(property, label)| {
|
||||||
|
let property_clone = property.clone();
|
||||||
|
view! {
|
||||||
|
<option value={property}>{ format!("{} - {}", property_clone, label) }</option>
|
||||||
|
}
|
||||||
|
}).collect::<Vec<_>>()
|
||||||
|
}}
|
||||||
|
</datalist>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
pub mod item_form;
|
|
||||||
pub mod items_list;
|
pub mod items_list;
|
||||||
pub mod editable_cell;
|
pub mod editable_cell;
|
|
@ -1,48 +0,0 @@
|
||||||
use leptos::*;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
|
||||||
struct WikidataResult {
|
|
||||||
id: String,
|
|
||||||
label: String,
|
|
||||||
description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn WikidataLookup(
|
|
||||||
query: String,
|
|
||||||
on_select: impl Fn(WikidataResult) + 'static,
|
|
||||||
) -> impl IntoView {
|
|
||||||
let (suggestions, set_suggestions) = create_signal(Vec::new());
|
|
||||||
|
|
||||||
let fetch_suggestions = move |query: String| {
|
|
||||||
spawn_local(async move {
|
|
||||||
if query.is_empty() {
|
|
||||||
set_suggestions(Vec::new());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let url = format!("https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=5&format=json&origin=*", query);
|
|
||||||
if let Ok(response) = reqwest::get(&url).await {
|
|
||||||
if let Ok(data) = response.json::<WikidataResponse>().await {
|
|
||||||
set_suggestions(data.search);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
create_effect(move || {
|
|
||||||
fetch_suggestions(query.clone());
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<ul>
|
|
||||||
{suggestions.get().iter().map(|suggestion| {
|
|
||||||
view! {
|
|
||||||
<li on:click=move |_| on_select(suggestion.clone())>
|
|
||||||
{format!("{} - {}", suggestion.label, suggestion.description.clone().unwrap_or_default())}
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
}).collect::<Vec<_>>()}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue