Compare commits

..

No commits in common. "d6d40247f6a56abd9f8c11ec4ae59f57e19feb17" and "23b63ebecdbb18576b4e4eb53cc9d89300c189b5" have entirely different histories.

12 changed files with 154 additions and 380 deletions

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Compware.iml" filepath="$PROJECT_DIR$/.idea/Compware.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -7,12 +7,7 @@
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" /> <cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="eb7658c0-6daa-4def-a5ad-3d491e9b76c8" name="Changes" comment=""> <list default="true" id="eb7658c0-6daa-4def-a5ad-3d491e9b76c8" name="Changes" comment="" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/app.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/editable_cell.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/editable_cell.rs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/items_list.rs" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/items_list.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@ -33,31 +28,26 @@
}</component> }</component>
<component name="ProjectId" id="2pngw94pMiCvlaySWR2RUofHfwc" /> <component name="ProjectId" id="2pngw94pMiCvlaySWR2RUofHfwc" />
<component name="ProjectViewState"> <component name="ProjectViewState">
<option name="autoscrollToSource" value="true" />
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="openDirectoriesWithSingleClick" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"><![CDATA[{ <component name="PropertiesComponent">{
"keyToString": { &quot;keyToString&quot;: {
"Cargo.Run compareware.executor": "Run", &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
"RunOnceActivity.ShowReadmeOnStart": "true", &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
"RunOnceActivity.git.unshallow": "true", &quot;RunOnceActivity.rust.reset.selective.auto.import&quot;: &quot;true&quot;,
"RunOnceActivity.rust.reset.selective.auto.import": "true", &quot;git-widget-placeholder&quot;: &quot;main&quot;,
"git-widget-placeholder": "editable-cells-wiki", &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
"last_opened_file_path": "/home/noah/Desktop/Internship_tasks/Compware", &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
"node.js.detected.package.eslint": "true", &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
"node.js.detected.package.tslint": "true", &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
"node.js.selected.package.eslint": "(autodetect)", &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
"node.js.selected.package.tslint": "(autodetect)", &quot;org.rust.cargo.project.model.PROJECT_DISCOVERY&quot;: &quot;true&quot;,
"nodejs_package_manager_path": "npm", &quot;org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon&quot;: &quot;&quot;,
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true", &quot;org.rust.first.attach.projects&quot;: &quot;true&quot;,
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "", &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
"org.rust.first.attach.projects": "true",
"settings.editor.selected.configurable": "settings.sync",
"vue.rearranger.settings.migration": "true"
} }
}]]></component> }</component>
<component name="RunManager" selected="Cargo.Run compareware"> <component name="RunManager" selected="Cargo.Run compareware">
<configuration name="Run compareware" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> <configuration name="Run compareware" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="buildProfileId" value="dev" /> <option name="buildProfileId" value="dev" />
@ -107,7 +97,6 @@
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1733407491778</updated> <updated>1733407491778</updated>
<workItem from="1733407498746" duration="5306000" /> <workItem from="1733407498746" duration="5306000" />
<workItem from="1734955316600" duration="1248000" />
</task> </task>
<servers /> <servers />
</component> </component>

58
Cargo.lock generated
View File

@ -593,7 +593,7 @@ dependencies = [
"hashbrown 0.13.2", "hashbrown 0.13.2",
"instant", "instant",
"once_cell", "once_cell",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -731,13 +731,11 @@ dependencies = [
"nostr-sdk", "nostr-sdk",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.9",
"tokio", "tokio",
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"zerofrom",
] ]
[[package]] [[package]]
@ -1144,7 +1142,7 @@ dependencies = [
"pin-project", "pin-project",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -1165,7 +1163,7 @@ dependencies = [
"pin-project", "pin-project",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.69", "thiserror",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@ -1623,7 +1621,7 @@ dependencies = [
"config", "config",
"regex", "regex",
"serde", "serde",
"thiserror 1.0.69", "thiserror",
"typed-builder", "typed-builder",
] ]
@ -1746,7 +1744,7 @@ dependencies = [
"serde-wasm-bindgen", "serde-wasm-bindgen",
"serde_json", "serde_json",
"slotmap", "slotmap",
"thiserror 1.0.69", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
"wasm-bindgen", "wasm-bindgen",
@ -1778,7 +1776,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_qs 0.13.0", "serde_qs 0.13.0",
"thiserror 1.0.69", "thiserror",
"tracing", "tracing",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
@ -1798,7 +1796,7 @@ dependencies = [
"leptos_reactive", "leptos_reactive",
"serde", "serde",
"server_fn", "server_fn",
"thiserror 1.0.69", "thiserror",
"tracing", "tracing",
] ]
@ -2004,7 +2002,7 @@ dependencies = [
"async-trait", "async-trait",
"lru 0.12.5", "lru 0.12.5",
"nostr", "nostr",
"thiserror 1.0.69", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -2022,7 +2020,7 @@ dependencies = [
"negentropy 0.4.3", "negentropy 0.4.3",
"nostr", "nostr",
"nostr-database", "nostr-database",
"thiserror 1.0.69", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
@ -2039,7 +2037,7 @@ dependencies = [
"nostr", "nostr",
"nostr-database", "nostr-database",
"nostr-relay-pool", "nostr-relay-pool",
"thiserror 1.0.69", "thiserror",
"tokio", "tokio",
"tracing", "tracing",
] ]
@ -2066,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -2442,7 +2440,7 @@ dependencies = [
"quote", "quote",
"syn 2.0.90", "syn 2.0.90",
"syn_derive", "syn_derive",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -2633,7 +2631,7 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
"serde", "serde",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -2644,7 +2642,7 @@ checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
"serde", "serde",
"thiserror 1.0.69", "thiserror",
] ]
[[package]] [[package]]
@ -2699,7 +2697,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_qs 0.12.0", "serde_qs 0.12.0",
"server_fn_macro_default", "server_fn_macro_default",
"thiserror 1.0.69", "thiserror",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -2879,16 +2877,7 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [ dependencies = [
"thiserror-impl 1.0.69", "thiserror-impl",
]
[[package]]
name = "thiserror"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
"thiserror-impl 2.0.9",
] ]
[[package]] [[package]]
@ -2902,17 +2891,6 @@ dependencies = [
"syn 2.0.90", "syn 2.0.90",
] ]
[[package]]
name = "thiserror-impl"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.37" version = "0.3.37"
@ -3016,7 +2994,7 @@ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
dependencies = [ dependencies = [
"either", "either",
"futures-util", "futures-util",
"thiserror 1.0.69", "thiserror",
"tokio", "tokio",
] ]
@ -3142,7 +3120,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"sha1", "sha1",
"thiserror 1.0.69", "thiserror",
"utf-8", "utf-8",
] ]

View File

@ -25,8 +25,6 @@ gloo-net = "0.5"
futures = "0.3" futures = "0.3"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
serde_json="1.0.133" serde_json="1.0.133"
thiserror = "2.0.9"
zerofrom = "0.1"
[features] [features]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"] csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]

View File

@ -28,25 +28,28 @@ pub fn App() -> impl IntoView {
} }
}); });
// Handle adding a new item // Function to add a new item from the grid
let add_item = move || { let add_item_from_grid = move || {
let new_item = Item { let new_id = Uuid::new_v4().to_string();
id: Uuid::new_v4().to_string(),
name: "New Item".to_string(), set_items.update(|items| {
description: String::new(), let item = Item {
tags: vec![], id: new_id.clone(),
reviews: vec![], name: String::new(),
wikidata_id: None, description: String::new(),
}; tags: vec![],
set_items.update(|items| items.push(new_item)); reviews: vec![],
wikidata_id: None,
};
items.push(item);
});
}; };
view! { view! {
<Stylesheet href="/assets/style.css" /> <Stylesheet href="/assets/style.css" />
<div> <div>
<h1>{ "CompareWare" }</h1> <h1>{ "CompareWare" }</h1>
<button on:click=move |_| add_item()>{ "Add New Item" }</button> <ItemsList items=items_signal set_items=set_items on_add_item=add_item_from_grid />
<ItemsList items=items_signal set_items=set_items />
</div> </div>
} }
} }

View File

@ -1,23 +0,0 @@
use leptos::*;
#[component]
pub fn EditableCell(
value: String,
on_input: impl Fn(String) + 'static,
) -> impl IntoView {
let (input_value, set_input_value) = create_signal(value.clone());
let handle_input = move |e: web_sys::Event| {
let new_value = event_target_value(&e);
set_input_value.set(new_value.clone());
on_input(new_value);
};
view! {
<input
type="text"
value={input_value.get()}
on:input=handle_input
/>
}
}

View File

@ -1,186 +1,144 @@
use crate::components::editable_cell::EditableCell; use crate::models::item::Item;
use crate::components::tag_editor::TagEditor; use gloo_net::http::Request;
use leptos::logging::log;
use leptos::*; use leptos::*;
use serde::Deserialize; use serde::Deserialize;
use uuid::Uuid; use std::collections::HashMap;
use leptos::logging::log; use wasm_bindgen_futures::spawn_local;
use crate::models::item::Item;
use std::sync::{Arc, Mutex};
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
struct WikidataSuggestion { struct WikidataResponse {
id: String, entities: HashMap<String, WikidataEntity>,
label: String,
description: Option<String>,
} }
#[derive(Deserialize, Clone, Debug)]
struct WikidataEntity {
labels: Option<HashMap<String, WikidataLabel>>,
descriptions: Option<HashMap<String, WikidataLabel>>,
}
#[derive(Deserialize, Clone, Debug)]
struct WikidataLabel {
value: String,
}
#[component] #[component]
pub fn ItemsList( pub fn ItemsList(items: ReadSignal<Vec<Item>>, set_items: WriteSignal<Vec<Item>>, on_add_item: impl Fn() + 'static, ) -> impl IntoView {
items: ReadSignal<Vec<Item>>, let (wikidata_data, set_wikidata_data) = create_signal(Vec::<Option<WikidataEntity>>::new());
set_items: WriteSignal<Vec<Item>>,
) -> impl IntoView {
let (wikidata_suggestions, set_wikidata_suggestions) =
create_signal(Vec::<WikidataSuggestion>::new());
// Fetch Wikidata suggestions // Fetch data from Wikidata for a given item name
let fetch_wikidata_suggestions = move |query: String| { let fetch_wikidata = move |item_name: String| {
spawn_local(async move { spawn_local(async move {
if query.is_empty() { let url = format!("https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=1&format=json", item_name);
set_wikidata_suggestions.set(Vec::new()); match Request::get(&url).send().await {
return; Ok(response) => match response.json::<WikidataResponse>().await {
} Ok(parsed_data) => {
if let Some(entity) = parsed_data.entities.values().next() {
let url = format!( set_wikidata_data.update(|current_data| {
"https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=5&format=json&origin=*", current_data.push(Some(entity.clone()));
query });
); }
match gloo_net::http::Request::get(&url).send().await {
Ok(response) => {
if let Ok(data) = response.json::<WikidataResponse>().await {
set_wikidata_suggestions.set(data.search);
} }
} Err(_) => log!("Failed to parse response from Wikidata"),
Err(_) => log!("Failed to fetch Wikidata suggestions"), },
Err(_) => log!("Failed to make request to Wikidata"),
} }
}); });
}; };
// Update item fields // Handle updating grid cells
let update_item = move |index: usize, field: &str, value: String| { let update_item = move |index: usize, column: &str, value: String| {
set_items.update(|items| { set_items.update(|items| {
if let Some(item) = items.get_mut(index) { if let Some(item) = items.get_mut(index) {
match field { match column {
"name" => { "name" => item.name = value,
item.name = value.clone();
fetch_wikidata_suggestions(value);
}
"description" => item.description = value, "description" => item.description = value,
"tags" => item.tags.push((value.clone(), value)), // For simplicity, adding the same value as key and value.
"review" => item.reviews.push(crate::models::item::ReviewWithRating {
content: value.clone(),
rating: 5, // Assuming a default rating of 5
}),
"rating" => {
if let Ok(rating) = value.parse::<u8>() {
item.reviews.last_mut().map(|r| r.rating = rating);
}
}
_ => (), _ => (),
} }
} }
}); });
}; };
// Add a new tag to an item // Trigger add item event
let add_tag = move |index: usize, key: String, value: String| { let add_item = move |_| {
set_items.update(|items| { on_add_item(); // Call the passed closure from App to add a new item
if let Some(item) = items.get_mut(index) {
item.tags.push((key, value));
}
});
};
// Remove a tag from an item
let remove_tag = move |item_index: usize, tag_index: usize| {
set_items.update(|items| {
if let Some(item) = items.get_mut(item_index) {
item.tags.remove(tag_index);
}
});
};
// Add a new item
let add_item = move |_: web_sys::MouseEvent|{
set_items.update(|items| {
items.push(Item {
id: Uuid::new_v4().to_string(),
name: String::new(),
description: String::new(),
tags: vec![],
reviews:vec![],
wikidata_id: None,
});
});
};
// Remove an item
let remove_item = move |index: usize| {
set_items.update(|items| {
items.remove(index);
});
}; };
view! { view! {
<div> <div>
<h1>{ "Items List" }</h1> <h1>{ "CompareWare" }</h1>
<button on:click=add_item>{ "Add New Item" }</button> <button on:click=add_item>{ "Add New Item" }</button>
<table> <table>
<thead> <thead>
<tr> <tr>
<th>{ "Name" }</th> <th>{ "Name" }</th>
<th>{ "Description" }</th> <th>{ "Description" }</th>
<th>{ "Tags" }</th> <th>{ "Tags" }</th>
<th>{ "Actions" }</th> <th>{ "Review" }</th>
<th>{ "Rating" }</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{move || items.get().iter().enumerate().map(|(index, item)| { {move || {
view! { items.get().iter().enumerate().map(|(index, item)| {
<tr> view! {
// Editable Name Field with Wikidata Integration <tr>
<td> <td>
<EditableCell <input
value=item.name.clone() type="text"
on_input=move |value| update_item(index, "name", value) value={item.name.clone()}
/> on:input=move |e| {
<ul> update_item(index, "name", event_target_value(&e));
{move || { fetch_wikidata(event_target_value(&e)); // Fetch Wikidata when name is entered
let suggestions = wikidata_suggestions.get().to_vec(); }
suggestions.into_iter().map(|suggestion| { />
// Clone all necessary fields upfront </td>
let label_for_click = suggestion.label.clone(); <td>
let label_for_display = suggestion.label.clone(); <input
let description = suggestion.description.clone().unwrap_or_default(); type="text"
let id = suggestion.id.clone(); value={item.description.clone()}
on:input=move |e| update_item(index, "description", event_target_value(&e))
view! { />
<li on:click=move |_| { </td>
set_items.update(|items| { <td>
if let Some(item) = items.get_mut(index) { <input
item.wikidata_id = Some(id.clone()); type="text"
item.name = label_for_click.clone(); // Use the cloned version for updating placeholder="Add tags"
} on:input=move |e| update_item(index, "tags", event_target_value(&e))
}); />
}> </td>
{ format!("{} - {}", label_for_display, description) } // Use the cloned version for display <td>
</li> <textarea
} value={item.reviews.iter().map(|review| format!("{}: {}", review.rating, review.content)).collect::<Vec<_>>().join("\n")}
on:input=move |e| update_item(index, "review", event_target_value(&e))
}).collect::<Vec<_>>() />
}} </td>
</ul> <td>
</td> <input
// Editable Description Field type="number"
<td> value={item.reviews.last().map(|r| r.rating).unwrap_or(5)}
<EditableCell min="1" max="5"
value=item.description.clone() on:input=move |e| update_item(index, "rating", event_target_value(&e))
on_input=move |value| update_item(index, "description", value) />
/> </td>
</td> </tr>
// Tag Editor }
<td> }).collect::<Vec<_>>()
<TagEditor }}
tags=item.tags.clone()
on_add=move |key, value| add_tag(index, key, value)
on_remove=Arc::new (Mutex:: new (move |tag_index:usize| remove_tag(index, tag_index)))
/>
</td>
// Actions
<td>
<button on:click=move |_| remove_item(index)>{ "Delete" }</button>
</td>
</tr>
}
}).collect::<Vec<_>>()}
</tbody> </tbody>
</table> </table>
</div> </div>
} }
} }
#[derive(Deserialize, Clone, Debug)]
struct WikidataResponse {
search: Vec<WikidataSuggestion>,
}

View File

@ -1,4 +1,2 @@
pub mod item_form; pub mod item_form;
pub mod items_list; pub mod items_list;
pub mod editable_cell;
pub mod tag_editor;

View File

@ -1,54 +0,0 @@
use leptos::*;
use std::sync::{Arc, Mutex};
#[component]
pub fn TagEditor(
tags: Vec<(String, String)>,
on_add: impl Fn(String, String) + 'static,
on_remove: Arc<Mutex<dyn FnMut(usize) + Send + Sync>>,
) -> impl IntoView {
let (key, set_key) = create_signal(String::new());
let (value, set_value) = create_signal(String::new());
let add_tag = move |_| {
if !key.get().is_empty() && !value.get().is_empty() {
on_add(key.get(), value.get());
set_key.set(String::new());
set_value.set(String::new());
}
};
view! {
<div>
<ul>
{tags.iter().enumerate().map(|(index, (k, v))| {
let on_remove = on_remove.clone();
view! {
<li>
{format!("{}: {}", k, v)}
<button on:click=move |_| {
let mut on_remove = on_remove.lock().unwrap();
on_remove(index);
}>
{ "Remove" }
</button>
</li>
}
}).collect::<Vec<_>>()}
</ul>
<input
placeholder="Key"
value={key.get()}
on:input=move |e| set_key.set(event_target_value(&e))
/>
<input
placeholder="Value"
value={value.get()}
on:input=move |e| set_value.set(event_target_value(&e))
/>
<button on:click=add_tag>{ "Add Tag" }</button>
</div>
}
}

View File

@ -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>
}
}