mirror of
https://github.com/hulthe/inkopslista.git
synced 2026-08-03 23:11:28 +02:00
sync list updates with server
This commit is contained in:
Generated
+11
-11
@@ -2360,6 +2360,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkopslista"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"inkopslista-lib",
|
||||
"reqwest",
|
||||
"slint",
|
||||
"slint-build",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inkopslista-lib"
|
||||
version = "0.1.0"
|
||||
@@ -2377,17 +2388,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inköpslista"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"inkopslista-lib",
|
||||
"reqwest",
|
||||
"slint",
|
||||
"slint-build",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inlinable_string"
|
||||
version = "0.1.15"
|
||||
|
||||
@@ -10,4 +10,5 @@ pub struct Item {
|
||||
|
||||
pub struct ItemData {
|
||||
pub checked: bool,
|
||||
// TODO: add number
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "inköpslista"
|
||||
name = "inkopslista"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::error::Error;
|
||||
use std::{error::Error, rc::Rc};
|
||||
|
||||
use inkopslista_lib::Item;
|
||||
use slint::{ModelRc, ToSharedString, VecModel};
|
||||
use inkopslista_lib::{Item, ItemData};
|
||||
use reqwest::Url;
|
||||
use slint::{Model, ModelRc, ToSharedString, VecModel};
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
@@ -21,29 +22,119 @@ async fn get_list() -> Result<Vec<Item>, MagicAnyError> {
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
/// Add or update an item in the list
|
||||
async fn put_list(item: &str, data: &ItemData) -> Result<(), MagicAnyError> {
|
||||
let client = reqwest::Client::new();
|
||||
let base = Url::parse("http://localhost:8000/api/list/")?;
|
||||
let url = base.join(item)?;
|
||||
let _response = client
|
||||
.put(url)
|
||||
.json(data)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_item(item: &str) -> Result<(), MagicAnyError> {
|
||||
let client = reqwest::Client::new();
|
||||
let base = Url::parse("http://localhost:8000/api/list/")?;
|
||||
let url = base.join(item)?;
|
||||
let _response = client.delete(url).send().await?.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert shopping list to slint types
|
||||
fn list_slint_convert(list: Vec<Item>) -> ModelRc<SItem> {
|
||||
let list = list
|
||||
fn update_slint_list(new_list: Vec<Item>, list: &VecModel<SItem>) {
|
||||
list.clear();
|
||||
new_list
|
||||
.iter()
|
||||
.map(|item| SItem {
|
||||
name: item.name.to_shared_string(),
|
||||
checked: item.data.checked,
|
||||
})
|
||||
.collect::<VecModel<_>>();
|
||||
ModelRc::new(list)
|
||||
.for_each(|item| list.push(item));
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), MagicAnyError> {
|
||||
let ui = App::new()?;
|
||||
let state = ui.global::<State>();
|
||||
let list = Rc::new(VecModel::default());
|
||||
state.set_list(ModelRc::new(list.clone()));
|
||||
|
||||
// HACK: load shopping list from server on start
|
||||
match get_list().await {
|
||||
Ok(list) => ui.set_list(list_slint_convert(list)),
|
||||
Ok(new_list) => update_slint_list(new_list, &list),
|
||||
Err(e) => {
|
||||
eprintln!("{e:?}");
|
||||
}
|
||||
}
|
||||
let list2 = list.clone();
|
||||
state.on_add_to_list(move |item| {
|
||||
if list2.iter().any(|item2| item2.name == item) {
|
||||
return;
|
||||
// TODO: implement increment when number is added
|
||||
}
|
||||
list2.push(SItem {
|
||||
checked: false,
|
||||
name: item.clone(),
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
// TODO: add number to increment
|
||||
let res = put_list(&item, &ItemData { checked: false }).await;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let list2 = list.clone();
|
||||
state.on_check_item(move |item| {
|
||||
let positem = list2
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, item2)| item2.name == item);
|
||||
let (pos, mut item) = match positem {
|
||||
Some(positem) => positem,
|
||||
None => return,
|
||||
};
|
||||
item.checked = !item.checked;
|
||||
list2.set_row_data(pos, item.clone());
|
||||
|
||||
tokio::spawn(async move {
|
||||
let res = put_list(
|
||||
&item.name,
|
||||
&ItemData {
|
||||
checked: item.checked,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
});
|
||||
});
|
||||
let list2 = list.clone();
|
||||
state.on_delete_checked(move || {
|
||||
let poss: Vec<_> = list2
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, item)| item.checked)
|
||||
.map(|(pos, item)| (pos, item.name))
|
||||
.collect();
|
||||
for &(pos, _) in poss.iter().rev() {
|
||||
list2.remove(pos);
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
for (_, name) in poss {
|
||||
let res = delete_item(&name).await;
|
||||
if let Err(err) = res {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.run()?;
|
||||
|
||||
|
||||
@@ -16,6 +16,17 @@ export struct SItem {
|
||||
checked: bool,
|
||||
}
|
||||
|
||||
export global State {
|
||||
in-out property <[SItem]> list: [
|
||||
{ name: "Test 1", checked: true },
|
||||
{ name: "Test 2", checked: false },
|
||||
{ name: "Test 3", checked: false },
|
||||
];
|
||||
callback add-to-list(string);
|
||||
callback check-item(string);
|
||||
callback delete-checked();
|
||||
}
|
||||
|
||||
component GoodListItem inherits Rectangle {
|
||||
in-out property <SItem> item;
|
||||
background: #ffffff10;
|
||||
@@ -24,8 +35,9 @@ component GoodListItem inherits Rectangle {
|
||||
HorizontalBox {
|
||||
check := CheckBox {
|
||||
checked: root.item.checked;
|
||||
toggled => {
|
||||
// TODO
|
||||
toggled() => {
|
||||
State.check-item(item.name);
|
||||
debug("CheckBox checked: ", self.checked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +50,8 @@ component GoodListItem inherits Rectangle {
|
||||
|
||||
input := TouchArea {
|
||||
clicked => {
|
||||
root.item.checked = !root.item.checked;
|
||||
//root.item.checked = !root.item.checked;
|
||||
State.check-item(item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +89,9 @@ component GoodButtons inherits ScrollView {
|
||||
width: root.button-width * 1pt;
|
||||
height: root.button-height * 1pt;
|
||||
text: item;
|
||||
clicked => {
|
||||
State.add-to-list(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +112,9 @@ component AddView inherits VerticalBox {
|
||||
Button {
|
||||
width: self.height;
|
||||
text: "+";
|
||||
// clicked => {
|
||||
// goods.items[goods.items.length] = input.text;
|
||||
// }
|
||||
clicked => {
|
||||
State.add-to-list(input.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,27 +130,28 @@ component ShopView inherits VerticalBox {
|
||||
|
||||
Button {
|
||||
text: "I am done hehe";
|
||||
clicked => {
|
||||
State.delete-checked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component App inherits Window {
|
||||
in property <[SItem]> list: [
|
||||
{ name: "Test 1", checked: true },
|
||||
{ name: "Test 2", checked: false },
|
||||
{ name: "Test 3", checked: false },
|
||||
];
|
||||
|
||||
preferred-height: 700px;
|
||||
preferred-width: 480px;
|
||||
TabWidget {
|
||||
Tab {
|
||||
title: "Add";
|
||||
AddView {
|
||||
list <=> list;
|
||||
list <=> State.list;
|
||||
}
|
||||
}
|
||||
|
||||
Tab {
|
||||
title: "Shop";
|
||||
ShopView {
|
||||
list <=> list;
|
||||
list <=> State.list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ fn put_list(item: &str, data: Json<ItemData>, mock_db: &State<Mutex<MockDb>>) {
|
||||
}
|
||||
|
||||
#[delete("/api/list/<item>")]
|
||||
fn delete_trip(item: &str, mock_db: &State<Mutex<MockDb>>) {
|
||||
fn delete_item(item: &str, mock_db: &State<Mutex<MockDb>>) {
|
||||
let mut mock_db = mock_db.lock().unwrap();
|
||||
if let Some(index) = mock_db.list.iter().position(|x| x.name == item) {
|
||||
mock_db.list.remove(index);
|
||||
@@ -53,7 +53,7 @@ fn delete_trip(item: &str, mock_db: &State<Mutex<MockDb>>) {
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.manage(Mutex::new(MockDb::default()))
|
||||
.mount("/", routes![index, get_list, put_list, delete_trip])
|
||||
.mount("/", routes![index, get_list, put_list, delete_item])
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
|
||||
Reference in New Issue
Block a user