work
This commit is contained in:
parent
b3a96ed3e3
commit
ca24591d9d
12 changed files with 177 additions and 105 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -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
10
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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
81
src/api.rs
81
src/api.rs
|
@ -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,
|
||||||
|
|
26
src/main.rs
26
src/main.rs
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -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() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue