This commit is contained in:
JMARyA 2025-05-28 15:43:50 +02:00
parent b3a96ed3e3
commit ca24591d9d
12 changed files with 177 additions and 105 deletions

2
.gitignore vendored
View file

@ -5,3 +5,5 @@
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk
/assets/tailwind.css

10
Cargo.lock generated
View file

@ -378,6 +378,7 @@ dependencies = [
"bardecoder", "bardecoder",
"base64", "base64",
"dioxus", "dioxus",
"dioxus-material-icons",
"dioxus-sdk", "dioxus-sdk",
"gloo-timers", "gloo-timers",
"image", "image",
@ -1194,6 +1195,15 @@ dependencies = [
"tracing-wasm", "tracing-wasm",
] ]
[[package]]
name = "dioxus-material-icons"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf38d89d3100bb3dbe38c7c08eef9d87fb81fcec49a4e33ead63fdcf4e67f01"
dependencies = [
"dioxus",
]
[[package]] [[package]]
name = "dioxus-mobile" name = "dioxus-mobile"
version = "0.6.2" version = "0.6.2"

View file

@ -21,6 +21,7 @@ serde_json = "1.0.140"
dioxus-sdk = { version = "0.6.0", features = ["storage"] } dioxus-sdk = { version = "0.6.0", features = ["storage"] }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
reqwest = { version = "0.12.15", features = ["json"] } reqwest = { version = "0.12.15", features = ["json"] }
dioxus-material-icons = "3.0.0"
[features] [features]
default = ["web"] default = ["web"]

View file

@ -2,8 +2,6 @@ FROM git.hydrar.de/navos/navos:latest as builder
RUN pacman-key --init && pacman-key --populate archlinux && pacman-key --populate navos && pacman -Sy --noconfirm RUN pacman-key --init && pacman-key --populate archlinux && pacman-key --populate navos && pacman -Sy --noconfirm
RUN pacman -S --noconfirm tailwindcss-bin rustup
RUN pacman -S --noconfirm --needed \ RUN pacman -S --noconfirm --needed \
webkit2gtk-4.1 \ webkit2gtk-4.1 \
base-devel \ base-devel \
@ -14,9 +12,9 @@ RUN pacman -S --noconfirm --needed \
appmenu-gtk-module \ appmenu-gtk-module \
libappindicator-gtk3 \ libappindicator-gtk3 \
librsvg \ librsvg \
xdotool xdotool tailwindcss-bin rustup && pacman -Scc --noconfirm
RUN rustup default nightly RUN rustup default nightly && rustup target add wasm32-unknown-unknown
RUN cargo install dioxus-cli RUN cargo install dioxus-cli

File diff suppressed because one or more lines are too long

View file

@ -1,76 +1,3 @@
/*
import 'dart:convert';
import 'package:cdb_ui/pages/supply.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
// todo : api errors
class API {
SharedPreferences? pref;
static final API _instance = API._internal();
// cache
List<Item>? items;
Map<String, Location>? locations;
Map<String, FlowInfo>? flowInfos;
factory API() {
return _instance;
}
API._internal();
Future<void> init(Function refresh) async {
pref = await SharedPreferences.getInstance();
instance = pref!.getString("instance") ?? "";
refresh();
}
bool isInit() {
if (pref == null) {
return false;
}
return pref!.containsKey("token") && pref!.containsKey("instance");
}
bool isPrefetched() {
return items != null && locations != null && flowInfos != null;
}
void save(String instance, String token) {
pref!.setString("instance", instance);
pref!.setString("token", token);
this.instance = instance;
}
String instance = "";
Future<String> getRequest(String url) async {
var resp = await http.get(Uri.parse(url), headers: <String, String>{
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=UTF-8',
'Token': pref!.getString("token")!
});
return utf8.decode(resp.bodyBytes);
}
Future<String> postRequest(String url, Map<String, dynamic> data) async {
var resp = await http.post(Uri.parse(url),
headers: <String, String>{
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json; charset=UTF-8',
'Token': pref!.getString("token")!
},
body: jsonEncode(data));
return utf8.decode(resp.bodyBytes);
}
}
*/
use std::collections::HashMap; use std::collections::HashMap;
use dioxus::signals::Readable; use dioxus::signals::Readable;
@ -84,6 +11,14 @@ use reqwest::Client;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub fn get_item(item: &str) -> Option<Item> {
crate::API
.read()
.as_ref()
.unwrap()
.get_item(item.to_string())
}
pub async fn api_get_auth<T>(path: String) -> Result<T, reqwest::Error> pub async fn api_get_auth<T>(path: String) -> Result<T, reqwest::Error>
where where
T: DeserializeOwned, T: DeserializeOwned,

View file

@ -1,5 +1,6 @@
use api::{Item, Transaction}; use api::{Item, Transaction};
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_material_icons::{MaterialIcon, MaterialIconStylesheet};
use dioxus_sdk::storage::use_persistent; use dioxus_sdk::storage::use_persistent;
use qrscan::QRCodeScanPage; use qrscan::QRCodeScanPage;
use setup::{Credentials, SetupPage}; use setup::{Credentials, SetupPage};
@ -69,6 +70,7 @@ fn App() -> Element {
document::Link { rel: "icon", href: FAVICON } document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "stylesheet", href: MAIN_CSS } document::Link { rel: "stylesheet", href: MAIN_CSS }
document::Link { rel: "stylesheet", href: TAILWIND_CSS } document::Link { rel: "stylesheet", href: TAILWIND_CSS }
MaterialIconStylesheet { }
if creds.read().empty() { if creds.read().empty() {
SetupPage { } SetupPage { }
@ -144,19 +146,35 @@ fn Navbar() -> Element {
id: "navbar", id: "navbar",
Link { Link {
to: Route::Home {}, to: Route::Home {},
"Home" div {
class: "flex flex-row gap-2 p-2",
MaterialIcon { name: "home", size: 24 },
"Home"
}
} }
Link { Link {
to: Route::ItemPage { }, to: Route::ItemPage { },
"Items" div {
class: "flex flex-row gap-2 p-2",
MaterialIcon { name: "category", size: 24 },
"Items"
}
} }
Link { Link {
to: Route::FlowPage { }, to: Route::FlowPage { },
"Flows" div {
class: "flex flex-row gap-2 p-2",
MaterialIcon { name: "assignment", size: 24 },
"Flows"
}
} }
Link { Link {
to: Route::LocationsPage { }, to: Route::LocationsPage { },
"Locations" div {
class: "flex flex-row gap-2 p-2",
MaterialIcon { name: "map", size: 24 },
"Locations"
}
} }
} }

View file

@ -16,7 +16,7 @@ pub fn ItemDetailPage(id: String) -> Element {
class: "flex flex-col h-screen", class: "flex flex-col h-screen",
header { header {
class: "p-4 bg-blue-500 text-white text-lg font-bold", class: "p-4 text-white text-lg font-bold",
{item.name.as_str()} {item.name.as_str()}
} }
div { div {
@ -79,9 +79,9 @@ pub fn ItemDetailPage(id: String) -> Element {
Route::SupplyPage { Route::SupplyPage {
item: item.uuid.clone(), item: item.uuid.clone(),
param: SupplyPageParam { param: SupplyPageParam {
onlyVariants: None, only_variants: None,
forcePrice: None, force_price: None,
forceOrigin: None force_origin: None
} }
} }
); );

View file

@ -19,8 +19,11 @@ pub fn ItemPage() -> Element {
match &*items.read_unchecked() { match &*items.read_unchecked() {
Some(items) => { Some(items) => {
rsx! { rsx! {
for item in items { div {
ItemTile { item: item.clone() } class: "flex flex-col",
for item in items {
ItemTile { item: item.clone() }
}
} }
} }
}, },

View file

@ -1,19 +1,99 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
fn build_tree(root: &str, locs: &HashMap<String, Location>) -> Element {
rsx! {}
}
#[component] #[component]
pub fn LocationsPage() -> Element { pub fn LocationsPage() -> Element {
let api = crate::API.read(); let api = crate::API.read();
let locations = use_signal(|| api.as_ref().unwrap().get_locations().clone()); let locations = use_signal(|| api.as_ref().unwrap().get_locations().clone());
// TODO : find roots let roots: Vec<String> = get_root_locations(&*locations.read());
// build them
rsx! { rsx! {
h1 { "Locations" }, h1 { "Locations" },
for l in &roots {
LocationTreeTile { location: l.clone(), locations }
}
}
}
pub fn get_root_locations(locations: &HashMap<String, crate::api::Location>) -> Vec<String> {
let mut ret = Vec::new();
for (_, v) in locations {
if v.parent.is_none() {
ret.push(v.id.clone());
}
}
ret
}
pub fn get_children_of_loc(
loc: String,
locations: &HashMap<String, crate::api::Location>,
) -> Vec<String> {
let mut ret = Vec::new();
for (_, v) in locations {
if let Some(parent) = &v.parent {
if *parent == loc {
ret.push(v.id.clone());
}
}
}
ret
}
#[component]
pub fn LocationTreeTile(
location: String,
locations: Signal<HashMap<String, crate::api::Location>>,
) -> Element {
let mut open = use_signal(|| false);
// TODO
let children = get_children_of_loc(location.clone(), &*locations.read());
rsx! {
p { {location} }
button {
onclick: move |_| { open.set(true); },
"Expand"
}
if *open.read() {
for loc in &children {
LocationTreeTile { location: loc.clone(), locations }
}
}
}
}
#[component]
pub fn LocationPage(location: String) -> Element {
let mut recursive = use_signal(|| true);
rsx! {
h1 { {location} }
div {
id: "location-info",
p { "Info" }
}
input {
r#type: "checkbox",
value: !*recursive.read(),
onclick: move |_| {
let val = *recursive.read();
recursive.set(!val);
},
"Show only entries matching this location"
}
// Transaction List
} }
} }

View file

@ -41,7 +41,7 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
let origin = use_signal(|| String::new()); let origin = use_signal(|| String::new());
let mut price = use_signal(|| 0.0); let mut price = use_signal(|| param.force_price.unwrap_or(0.0));
let location = use_signal(|| String::new()); let location = use_signal(|| String::new());
let mut note = use_signal(|| String::new()); let mut note = use_signal(|| String::new());
@ -72,7 +72,14 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
input { input {
r#type: "number", r#type: "number",
value: "{price}", value: "{price}",
oninput: move |e| price.set(e.value().parse().unwrap()) disabled: param.force_price.is_some(),
oninput: move |e| {
if let Some(fprice) = param.force_price {
price.set(fprice);
} else {
price.set(e.value().parse().unwrap())
}
}
} }
}, },
// Location Dropdown // Location Dropdown
@ -107,9 +114,9 @@ pub fn SupplyPage(item: String, param: SupplyPageParam) -> Element {
#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)] #[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
pub struct SupplyPageParam { pub struct SupplyPageParam {
pub onlyVariants: Option<Vec<String>>, pub only_variants: Option<Vec<String>>,
pub forcePrice: Option<f64>, pub force_price: Option<f64>,
pub forceOrigin: Option<String>, pub force_origin: Option<String>,
} }
impl std::fmt::Display for SupplyPageParam { impl std::fmt::Display for SupplyPageParam {

View file

@ -1,6 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_material_icons::MaterialIcon;
use crate::Route; use crate::{api::get_item, Route};
#[component] #[component]
pub fn TransactionPage(id: String) -> Element { pub fn TransactionPage(id: String) -> Element {
@ -10,7 +11,6 @@ pub fn TransactionPage(id: String) -> Element {
}); });
rsx! { rsx! {
match transaction.value()() { match transaction.value()() {
Some(transaction) => rsx! { Some(transaction) => rsx! {
@ -32,9 +32,9 @@ pub fn TransactionPage(id: String) -> Element {
div { div {
id: "col-1", id: "col-1",
p { {crate::API.read().as_ref().unwrap().get_item(transaction.item.clone()).unwrap().name.as_str()} } p { {get_item(&transaction.item).unwrap().name.as_str()} }
// TODO : variant name p { {get_item(&transaction.item).unwrap().variants.get(&transaction.variant).as_ref().unwrap().name.clone()} }
// timestamp p { {transaction.timestamp.to_string()} }
} }
}, },
@ -45,6 +45,9 @@ pub fn TransactionPage(id: String) -> Element {
// price // price
p { {transaction.price.to_string()} }, p { {transaction.price.to_string()} },
// origin // origin
if let Some(origin) = &transaction.origin {
IconLabel { icon: "home".to_string(), label: "Origin".to_owned(), value: origin.clone() }
}
// location // location
// note // note
@ -58,3 +61,20 @@ pub fn TransactionPage(id: String) -> Element {
} }
} }
#[component]
pub fn IconLabel(icon: String, label: String, value: String) -> Element {
rsx! {
div { class: "flex items-center space-x-3",
MaterialIcon {
name: icon,
size: 12,
},
div { class: "flex flex-col",
span { class: "text-sm font-medium text-gray-700", "{label}" }
span { class: "text-lg font-semibold text-gray-900", "{value}" }
}
}
}
}