Compare commits
4 Commits
23b63ebecd
...
d6d40247f6
Author | SHA1 | Date |
---|---|---|
ryanmwangi | d6d40247f6 | |
ryan | e9b4c12a6d | |
ryan | 22e6ae38d4 | |
Ryan Mwangi | 16b04fcc1e |
|
@ -0,0 +1,11 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -7,7 +7,12 @@
|
||||||
<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" />
|
||||||
|
@ -28,26 +33,31 @@
|
||||||
}</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">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"Cargo.Run compareware.executor": "Run",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"git-widget-placeholder": "main",
|
"RunOnceActivity.rust.reset.selective.auto.import": "true",
|
||||||
"node.js.detected.package.eslint": "true",
|
"git-widget-placeholder": "editable-cells-wiki",
|
||||||
"node.js.detected.package.tslint": "true",
|
"last_opened_file_path": "/home/noah/Desktop/Internship_tasks/Compware",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.detected.package.tslint": "true",
|
||||||
"nodejs_package_manager_path": "npm",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "",
|
"nodejs_package_manager_path": "npm",
|
||||||
"org.rust.first.attach.projects": "true",
|
"org.rust.cargo.project.model.PROJECT_DISCOVERY": "true",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"org.rust.cargo.project.model.impl.CargoExternalSystemProjectAware.subscribe.first.balloon": "",
|
||||||
|
"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" />
|
||||||
|
@ -97,6 +107,7 @@
|
||||||
<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>
|
||||||
|
|
|
@ -593,7 +593,7 @@ dependencies = [
|
||||||
"hashbrown 0.13.2",
|
"hashbrown 0.13.2",
|
||||||
"instant",
|
"instant",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -731,11 +731,13 @@ 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]]
|
||||||
|
@ -1142,7 +1144,7 @@ dependencies = [
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
@ -1163,7 +1165,7 @@ dependencies = [
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
@ -1621,7 +1623,7 @@ dependencies = [
|
||||||
"config",
|
"config",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1744,7 +1746,7 @@ dependencies = [
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -1776,7 +1778,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_qs 0.13.0",
|
"serde_qs 0.13.0",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
@ -1796,7 +1798,7 @@ dependencies = [
|
||||||
"leptos_reactive",
|
"leptos_reactive",
|
||||||
"serde",
|
"serde",
|
||||||
"server_fn",
|
"server_fn",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2002,7 +2004,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"lru 0.12.5",
|
"lru 0.12.5",
|
||||||
"nostr",
|
"nostr",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
@ -2020,7 +2022,7 @@ dependencies = [
|
||||||
"negentropy 0.4.3",
|
"negentropy 0.4.3",
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-database",
|
"nostr-database",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -2037,7 +2039,7 @@ dependencies = [
|
||||||
"nostr",
|
"nostr",
|
||||||
"nostr-database",
|
"nostr-database",
|
||||||
"nostr-relay-pool",
|
"nostr-relay-pool",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
@ -2064,7 +2066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708"
|
checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2440,7 +2442,7 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.90",
|
||||||
"syn_derive",
|
"syn_derive",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2631,7 +2633,7 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2642,7 +2644,7 @@ checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2697,7 +2699,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",
|
"thiserror 1.0.69",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
@ -2877,7 +2879,16 @@ 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",
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
||||||
|
@ -2891,6 +2902,17 @@ 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"
|
||||||
|
@ -2994,7 +3016,7 @@ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3120,7 +3142,7 @@ dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror 1.0.69",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ 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"]
|
||||||
|
|
31
src/app.rs
31
src/app.rs
|
@ -28,28 +28,25 @@ pub fn App() -> impl IntoView {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Function to add a new item from the grid
|
// Handle adding a new item
|
||||||
let add_item_from_grid = move || {
|
let add_item = move || {
|
||||||
let new_id = Uuid::new_v4().to_string();
|
let new_item = Item {
|
||||||
|
id: Uuid::new_v4().to_string(),
|
||||||
set_items.update(|items| {
|
name: "New Item".to_string(),
|
||||||
let item = Item {
|
description: String::new(),
|
||||||
id: new_id.clone(),
|
tags: vec![],
|
||||||
name: String::new(),
|
reviews: vec![],
|
||||||
description: String::new(),
|
wikidata_id: None,
|
||||||
tags: vec![],
|
};
|
||||||
reviews: vec![],
|
set_items.update(|items| items.push(new_item));
|
||||||
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>
|
||||||
<ItemsList items=items_signal set_items=set_items on_add_item=add_item_from_grid />
|
<button on:click=move |_| add_item()>{ "Add New Item" }</button>
|
||||||
|
<ItemsList items=items_signal set_items=set_items />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
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
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,144 +1,186 @@
|
||||||
use crate::models::item::Item;
|
use crate::components::editable_cell::EditableCell;
|
||||||
use gloo_net::http::Request;
|
use crate::components::tag_editor::TagEditor;
|
||||||
use leptos::logging::log;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use leptos::logging::log;
|
||||||
|
use crate::models::item::Item;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
struct WikidataResponse {
|
struct WikidataSuggestion {
|
||||||
entities: HashMap<String, WikidataEntity>,
|
id: String,
|
||||||
|
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(items: ReadSignal<Vec<Item>>, set_items: WriteSignal<Vec<Item>>, on_add_item: impl Fn() + 'static, ) -> impl IntoView {
|
pub fn ItemsList(
|
||||||
let (wikidata_data, set_wikidata_data) = create_signal(Vec::<Option<WikidataEntity>>::new());
|
items: ReadSignal<Vec<Item>>,
|
||||||
|
set_items: WriteSignal<Vec<Item>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let (wikidata_suggestions, set_wikidata_suggestions) =
|
||||||
|
create_signal(Vec::<WikidataSuggestion>::new());
|
||||||
|
|
||||||
// Fetch data from Wikidata for a given item name
|
// Fetch Wikidata suggestions
|
||||||
let fetch_wikidata = move |item_name: String| {
|
let fetch_wikidata_suggestions = move |query: String| {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let url = format!("https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=1&format=json", item_name);
|
if query.is_empty() {
|
||||||
match Request::get(&url).send().await {
|
set_wikidata_suggestions.set(Vec::new());
|
||||||
Ok(response) => match response.json::<WikidataResponse>().await {
|
return;
|
||||||
Ok(parsed_data) => {
|
}
|
||||||
if let Some(entity) = parsed_data.entities.values().next() {
|
|
||||||
set_wikidata_data.update(|current_data| {
|
let url = format!(
|
||||||
current_data.push(Some(entity.clone()));
|
"https://www.wikidata.org/w/api.php?action=wbsearchentities&search={}&language=en&limit=5&format=json&origin=*",
|
||||||
});
|
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"),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle updating grid cells
|
// Update item fields
|
||||||
let update_item = move |index: usize, column: &str, value: String| {
|
let update_item = move |index: usize, field: &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 column {
|
match field {
|
||||||
"name" => item.name = value,
|
"name" => {
|
||||||
"description" => item.description = value,
|
item.name = value.clone();
|
||||||
"tags" => item.tags.push((value.clone(), value)), // For simplicity, adding the same value as key and value.
|
fetch_wikidata_suggestions(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
"description" => item.description = value,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trigger add item event
|
// Add a new tag to an item
|
||||||
let add_item = move |_| {
|
let add_tag = move |index: usize, key: String, value: String| {
|
||||||
on_add_item(); // Call the passed closure from App to add a new item
|
set_items.update(|items| {
|
||||||
|
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>{ "CompareWare" }</h1>
|
<h1>{ "Items List" }</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>{ "Review" }</th>
|
<th>{ "Actions" }</th>
|
||||||
<th>{ "Rating" }</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{move || {
|
{move || items.get().iter().enumerate().map(|(index, item)| {
|
||||||
items.get().iter().enumerate().map(|(index, item)| {
|
view! {
|
||||||
view! {
|
<tr>
|
||||||
<tr>
|
// Editable Name Field with Wikidata Integration
|
||||||
<td>
|
<td>
|
||||||
<input
|
<EditableCell
|
||||||
type="text"
|
value=item.name.clone()
|
||||||
value={item.name.clone()}
|
on_input=move |value| update_item(index, "name", value)
|
||||||
on:input=move |e| {
|
/>
|
||||||
update_item(index, "name", event_target_value(&e));
|
<ul>
|
||||||
fetch_wikidata(event_target_value(&e)); // Fetch Wikidata when name is entered
|
{move || {
|
||||||
}
|
let suggestions = wikidata_suggestions.get().to_vec();
|
||||||
/>
|
suggestions.into_iter().map(|suggestion| {
|
||||||
</td>
|
// Clone all necessary fields upfront
|
||||||
<td>
|
let label_for_click = suggestion.label.clone();
|
||||||
<input
|
let label_for_display = suggestion.label.clone();
|
||||||
type="text"
|
let description = suggestion.description.clone().unwrap_or_default();
|
||||||
value={item.description.clone()}
|
let id = suggestion.id.clone();
|
||||||
on:input=move |e| update_item(index, "description", event_target_value(&e))
|
|
||||||
/>
|
view! {
|
||||||
</td>
|
<li on:click=move |_| {
|
||||||
<td>
|
set_items.update(|items| {
|
||||||
<input
|
if let Some(item) = items.get_mut(index) {
|
||||||
type="text"
|
item.wikidata_id = Some(id.clone());
|
||||||
placeholder="Add tags"
|
item.name = label_for_click.clone(); // Use the cloned version for updating
|
||||||
on:input=move |e| update_item(index, "tags", event_target_value(&e))
|
}
|
||||||
/>
|
});
|
||||||
</td>
|
}>
|
||||||
<td>
|
{ format!("{} - {}", label_for_display, description) } // Use the cloned version for display
|
||||||
<textarea
|
</li>
|
||||||
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>
|
}}
|
||||||
<td>
|
</ul>
|
||||||
<input
|
</td>
|
||||||
type="number"
|
// Editable Description Field
|
||||||
value={item.reviews.last().map(|r| r.rating).unwrap_or(5)}
|
<td>
|
||||||
min="1" max="5"
|
<EditableCell
|
||||||
on:input=move |e| update_item(index, "rating", event_target_value(&e))
|
value=item.description.clone()
|
||||||
/>
|
on_input=move |value| update_item(index, "description", value)
|
||||||
</td>
|
/>
|
||||||
</tr>
|
</td>
|
||||||
}
|
// Tag Editor
|
||||||
}).collect::<Vec<_>>()
|
<td>
|
||||||
}}
|
<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>,
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
pub mod item_form;
|
pub mod item_form;
|
||||||
pub mod items_list;
|
pub mod items_list;
|
||||||
|
pub mod editable_cell;
|
||||||
|
pub mod tag_editor;
|
|
@ -0,0 +1,54 @@
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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…
Reference in New Issue