Add another attempt with python
This commit is contained in:
2
rust_warp_svelte/api/.gitignore
vendored
Normal file
2
rust_warp_svelte/api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target/
|
||||
/sqlite.db
|
||||
1189
rust_warp_svelte/api/Cargo.lock
generated
Normal file
1189
rust_warp_svelte/api/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
rust_warp_svelte/api/Cargo.toml
Normal file
40
rust_warp_svelte/api/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "packager-api"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Hannes Körber <hannes@hkoerber.de>"
|
||||
]
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/hkoerber/packager/"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
publish = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[lib]
|
||||
name = "packager"
|
||||
|
||||
[dependencies]
|
||||
warp = "0.3"
|
||||
serde_json = "1.0"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1"
|
||||
features = ["full"]
|
||||
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
features = ["derive", "rc"]
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "0.8"
|
||||
features = ["serde", "v4"]
|
||||
|
||||
[dependencies.rusqlite]
|
||||
version = "0.26.2"
|
||||
features = ["uuid"]
|
||||
|
||||
25
rust_warp_svelte/api/README.md
Normal file
25
rust_warp_svelte/api/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Setup
|
||||
|
||||
* Install `rustup`
|
||||
|
||||
On Arch Linux:
|
||||
|
||||
```
|
||||
$ sudo pacman -S rustup
|
||||
```
|
||||
|
||||
```
|
||||
$ rustup toolchain install nightly
|
||||
```
|
||||
|
||||
# Run Dev
|
||||
|
||||
```
|
||||
$ rustup run nightly cargo run
|
||||
```
|
||||
|
||||
# Build
|
||||
|
||||
```
|
||||
$ rustup run nightly cargo build --release
|
||||
```
|
||||
1278
rust_warp_svelte/api/src/db.rs
Normal file
1278
rust_warp_svelte/api/src/db.rs
Normal file
File diff suppressed because it is too large
Load Diff
63
rust_warp_svelte/api/src/lib.rs
Normal file
63
rust_warp_svelte/api/src/lib.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
pub mod packagelist;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use packagelist::Duration;
|
||||
pub use packagelist::ItemSize;
|
||||
pub use packagelist::ItemUsage;
|
||||
pub use packagelist::PackageItem;
|
||||
pub use packagelist::PackageList;
|
||||
pub use packagelist::Period;
|
||||
pub use packagelist::Preparation;
|
||||
pub use packagelist::PreparationStep;
|
||||
|
||||
pub mod router;
|
||||
|
||||
pub mod db;
|
||||
|
||||
pub mod trip;
|
||||
pub use trip::Trip;
|
||||
pub use trip::TripParameters;
|
||||
pub use trip::TripState;
|
||||
pub use trip::TripItem;
|
||||
pub use trip::TripItemStatus;
|
||||
|
||||
pub fn get_list(id: Uuid) -> Option<packagelist::PackageList> {
|
||||
self::db::get_list(id).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_lists() -> Vec<packagelist::PackageList> {
|
||||
self::db::get_lists().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_packagelist_items(id: Uuid) -> Vec<packagelist::PackageItem> {
|
||||
self::db::get_list_items(id).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_preparation(list_id: Uuid, item_id: Uuid) -> Vec<packagelist::PreparationStep> {
|
||||
self::db::get_preparation(list_id, item_id).unwrap()
|
||||
}
|
||||
|
||||
pub fn new_item(list_id: Uuid, item_name: String, item_count: i32) -> packagelist::PackageItem {
|
||||
let item = PackageItem::new(
|
||||
Uuid::new_v4(),
|
||||
item_name,
|
||||
ItemSize::None,
|
||||
item_count,
|
||||
ItemUsage::Singleton,
|
||||
Preparation::None,
|
||||
);
|
||||
self::db::save_item(list_id, item).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_trips() -> Vec<Trip> {
|
||||
self::db::get_trips().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_trip(trip_id: Uuid) -> Option<Trip> {
|
||||
self::db::get_trip(trip_id).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_trip_items(trip_id: Uuid) -> Option<Vec<TripItem>> {
|
||||
self::db::get_trip_items(trip_id).unwrap()
|
||||
}
|
||||
50
rust_warp_svelte/api/src/main.rs
Normal file
50
rust_warp_svelte/api/src/main.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// let lists = packager::get_lists();
|
||||
|
||||
// for list in &lists {
|
||||
// println!("Contents of package list {:?}:", list.name);
|
||||
// for item in &list.items {
|
||||
// println!("\t{:?}", item);
|
||||
// }
|
||||
// }
|
||||
|
||||
// println!("\nNow we're starting an actual trip!");
|
||||
|
||||
// let mut trip = packager::trip::Trip::from_package_list(
|
||||
// String::from("Campingtrip"),
|
||||
// String::from("2021-09-06"),
|
||||
// &lists[0],
|
||||
// );
|
||||
|
||||
// println!(
|
||||
// "\nPackage list for trip {:?} at {:?}:",
|
||||
// trip.name, trip.date
|
||||
// );
|
||||
// for item in &trip.list.items {
|
||||
// println!("{:?}", item);
|
||||
// }
|
||||
|
||||
// trip.list.items[0].set_status(packager::trip::TripItemStatus::Ready);
|
||||
// trip.list.items[1].set_status(packager::trip::TripItemStatus::Packed);
|
||||
// for item in &trip.list.items {
|
||||
// println!("{:?}", item);
|
||||
// }
|
||||
let args: Vec<String> = env::args().skip(1).collect();
|
||||
match args.get(0) {
|
||||
None => (),
|
||||
Some(cmd) => match cmd.as_ref() {
|
||||
"--load-example-data" => {
|
||||
packager::db::load().unwrap();
|
||||
}
|
||||
_ => panic!("Unknown argument: \"{}\"", cmd),
|
||||
},
|
||||
};
|
||||
|
||||
let router = packager::router::new();
|
||||
|
||||
println!("Initialization done, listening for connections");
|
||||
warp::serve(router).run(([127, 0, 0, 1], 9000)).await;
|
||||
}
|
||||
251
rust_warp_svelte/api/src/packagelist.rs
Normal file
251
rust_warp_svelte/api/src/packagelist.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use serde::ser::{SerializeStruct, Serializer};
|
||||
use serde::Serialize;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Duration {
|
||||
None,
|
||||
Days(i32),
|
||||
}
|
||||
|
||||
impl Duration {
|
||||
pub fn is_none(d: &Duration) -> bool {
|
||||
matches!(d, Duration::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Period {
|
||||
Daily(i32),
|
||||
Weekly(i32),
|
||||
Days(i32),
|
||||
}
|
||||
|
||||
impl Serialize for Period {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Period::Daily(i) => {
|
||||
let mut s = serializer.serialize_struct("period", 2)?;
|
||||
s.serialize_field("type", "daily")?;
|
||||
s.serialize_field("value", &i)?;
|
||||
s.end()
|
||||
}
|
||||
Period::Weekly(i) => {
|
||||
let mut s = serializer.serialize_struct("period", 2)?;
|
||||
s.serialize_field("type", "weekly")?;
|
||||
s.serialize_field("value", &i)?;
|
||||
s.end()
|
||||
}
|
||||
Period::Days(i) => {
|
||||
let mut s = serializer.serialize_struct("period", 2)?;
|
||||
s.serialize_field("type", "days")?;
|
||||
s.serialize_field("value", &i)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ItemUsage {
|
||||
Singleton,
|
||||
Periodic(Period),
|
||||
Infinite,
|
||||
}
|
||||
|
||||
impl Serialize for ItemUsage {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
ItemUsage::Singleton => {
|
||||
let mut s = serializer.serialize_struct("usage", 1)?;
|
||||
s.serialize_field("type", "singleton")?;
|
||||
s.end()
|
||||
}
|
||||
ItemUsage::Periodic(p) => {
|
||||
let mut s = serializer.serialize_struct("usage", 2)?;
|
||||
s.serialize_field("type", "peridoic")?;
|
||||
s.serialize_field("value", &p)?;
|
||||
s.end()
|
||||
}
|
||||
ItemUsage::Infinite => {
|
||||
let mut s = serializer.serialize_struct("size", 1)?;
|
||||
s.serialize_field("type", "infinite")?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ItemSize {
|
||||
None,
|
||||
Pack(i32),
|
||||
Name(String),
|
||||
Gram(i32),
|
||||
}
|
||||
|
||||
impl Serialize for ItemSize {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
ItemSize::None => {
|
||||
let mut s = serializer.serialize_struct("size", 1)?;
|
||||
s.serialize_field("type", "none")?;
|
||||
s.end()
|
||||
}
|
||||
ItemSize::Pack(i) => {
|
||||
let mut s = serializer.serialize_struct("size", 2)?;
|
||||
s.serialize_field("type", "pack")?;
|
||||
s.serialize_field("value", &i)?;
|
||||
s.end()
|
||||
}
|
||||
ItemSize::Name(n) => {
|
||||
let mut s = serializer.serialize_struct("size", 2)?;
|
||||
s.serialize_field("type", "name")?;
|
||||
s.serialize_field("value", &n)?;
|
||||
s.end()
|
||||
}
|
||||
ItemSize::Gram(i) => {
|
||||
let mut s = serializer.serialize_struct("size", 2)?;
|
||||
s.serialize_field("type", "gram")?;
|
||||
s.serialize_field("value", &i)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemSize {
|
||||
pub fn is_none(d: &ItemSize) -> bool {
|
||||
matches!(d, ItemSize::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PreparationStep {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Duration::is_none")]
|
||||
pub start: Duration,
|
||||
}
|
||||
|
||||
impl PreparationStep {
|
||||
pub fn new(id: Uuid, name: String, start: Duration) -> PreparationStep {
|
||||
PreparationStep { id, name, start }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Preparation {
|
||||
None,
|
||||
Steps(Vec<PreparationStep>),
|
||||
}
|
||||
|
||||
impl Preparation {
|
||||
pub fn is_none(d: &Preparation) -> bool {
|
||||
matches!(d, Preparation::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Preparation {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Preparation::None => {
|
||||
// serializer.serialize_unit_variant("Preparation", 0, "null")
|
||||
let steps: Vec<i32> = vec![];
|
||||
serializer.serialize_newtype_variant("Preparation", 1, "steps", &steps)
|
||||
// let mut s = serializer.serialize_seq(Some(0))?;
|
||||
// s.end()
|
||||
}
|
||||
Preparation::Steps(steps) => {
|
||||
serializer.serialize_newtype_variant("Preparation", 1, "steps", steps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PackageItem {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
|
||||
#[serde(skip_serializing_if = "ItemSize::is_none")]
|
||||
size: ItemSize,
|
||||
pub count: i32,
|
||||
usage: ItemUsage,
|
||||
|
||||
pub preparation: Preparation,
|
||||
}
|
||||
|
||||
impl PackageItem {
|
||||
pub fn new(
|
||||
id: Uuid,
|
||||
name: String,
|
||||
size: ItemSize,
|
||||
count: i32,
|
||||
usage: ItemUsage,
|
||||
preparation: Preparation,
|
||||
) -> PackageItem {
|
||||
PackageItem {
|
||||
id,
|
||||
name,
|
||||
size,
|
||||
count,
|
||||
usage,
|
||||
preparation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_simple(id: Uuid, name: String) -> PackageItem {
|
||||
PackageItem::new(
|
||||
id,
|
||||
name,
|
||||
ItemSize::None,
|
||||
1,
|
||||
ItemUsage::Singleton,
|
||||
Preparation::None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PackageList {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub items: Vec<PackageItem>,
|
||||
}
|
||||
|
||||
impl PackageList {
|
||||
pub fn new_from_items(id: Uuid, name: String, items: Vec<PackageItem>) -> PackageList {
|
||||
PackageList { id, name, items }
|
||||
}
|
||||
|
||||
pub fn new(id: Uuid, name: String) -> PackageList {
|
||||
PackageList {
|
||||
id,
|
||||
name,
|
||||
items: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_items(&mut self, items: Vec<PackageItem>) {
|
||||
self.items = items;
|
||||
}
|
||||
}
|
||||
215
rust_warp_svelte/api/src/router.rs
Normal file
215
rust_warp_svelte/api/src/router.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::Infallible;
|
||||
|
||||
use warp;
|
||||
use warp::http::StatusCode;
|
||||
use warp::Filter;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InvalidUuid;
|
||||
|
||||
impl warp::reject::Reject for InvalidUuid {}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ErrorMessage {
|
||||
code: u16,
|
||||
success: bool,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct JsonListItem {
|
||||
name: String,
|
||||
count: i32,
|
||||
}
|
||||
|
||||
pub fn new() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
|
||||
let accept_json = warp::header::exact("accept", "application/json");
|
||||
let content_json = warp::header::exact("content-type", "application/json");
|
||||
let cors = warp::cors()
|
||||
.allow_any_origin()
|
||||
.allow_methods(&[
|
||||
warp::http::Method::GET,
|
||||
warp::http::Method::POST,
|
||||
warp::http::Method::DELETE,
|
||||
])
|
||||
.allow_headers(vec!["accept", "content-type"]);
|
||||
|
||||
fn json_body() -> impl Filter<Extract = (JsonListItem,), Error = warp::Rejection> + Clone {
|
||||
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
|
||||
}
|
||||
|
||||
let root = warp::path::end().map(|| "Hi");
|
||||
|
||||
let v1 = warp::path!("v1")
|
||||
.and(warp::get())
|
||||
.and(warp::path::end())
|
||||
.map(warp::reply);
|
||||
|
||||
let lists = warp::path!("v1" / "lists")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.map(|| warp::reply::json(&super::get_lists()))
|
||||
.with(&cors);
|
||||
|
||||
let list = warp::path!("v1" / "lists" / String)
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.and_then(|id: String| async move {
|
||||
match Uuid::parse_str(&id) {
|
||||
Ok(uuid) => {
|
||||
let list = &super::get_list(uuid);
|
||||
match list {
|
||||
Some(l) => Ok(warp::reply::json(l)),
|
||||
None => Err(warp::reject::not_found()),
|
||||
}
|
||||
}
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
let list_items = warp::path!("v1" / "lists" / String / "items")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.and_then(|list_id: String| async move {
|
||||
match Uuid::parse_str(&list_id) {
|
||||
Ok(uuid) => {
|
||||
let items = &super::get_packagelist_items(uuid);
|
||||
Ok(warp::reply::json(items))
|
||||
}
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
let preparation = warp::path!("v1" / "lists" / String / "items" / String / "preparation")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.and_then(|list_id: String, item_id: String| async move {
|
||||
match Uuid::parse_str(&list_id) {
|
||||
Ok(list_id) => match Uuid::parse_str(&item_id) {
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
Ok(item_id) => {
|
||||
let items = &super::get_preparation(list_id, item_id);
|
||||
Ok(warp::reply::json(items))
|
||||
}
|
||||
},
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
let new_item = warp::path!("v1" / "lists" / String / "items")
|
||||
.and(warp::path::end())
|
||||
.and(warp::post())
|
||||
.and(accept_json)
|
||||
.and(content_json)
|
||||
.and(json_body())
|
||||
.and_then(|list_id: String, item: JsonListItem| async move {
|
||||
match Uuid::parse_str(&list_id) {
|
||||
Ok(list_id) => {
|
||||
let new_item = &super::new_item(list_id, item.name, item.count);
|
||||
Ok(warp::reply::json(new_item))
|
||||
}
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
let trips = warp::path!("v1" / "trips")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.map(|| warp::reply::json(&super::get_trips()))
|
||||
.with(&cors);
|
||||
|
||||
let trip = warp::path!("v1" / "trips" / String)
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.and_then(|trip_id: String| async move {
|
||||
match Uuid::parse_str(&trip_id) {
|
||||
Ok(trip_id) => {
|
||||
match &super::get_trip(trip_id) {
|
||||
Some(trip) => Ok(warp::reply::json(trip)),
|
||||
None => Err(warp::reject::not_found()),
|
||||
}
|
||||
},
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
let trip_items = warp::path!("v1" / "trips" / String / "items")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(accept_json)
|
||||
.and_then(|trip_id: String| async move {
|
||||
match Uuid::parse_str(&trip_id) {
|
||||
Ok(trip_id) => {
|
||||
match &super::get_trip_items(trip_id) {
|
||||
Some(trip) => Ok(warp::reply::json(trip)),
|
||||
None => Err(warp::reject::not_found()),
|
||||
}
|
||||
},
|
||||
Err(_) => Err(warp::reject::custom(InvalidUuid)),
|
||||
}
|
||||
})
|
||||
.with(&cors);
|
||||
|
||||
root.or(v1)
|
||||
.or(lists)
|
||||
.or(list)
|
||||
.or(new_item)
|
||||
.or(list_items)
|
||||
.or(preparation)
|
||||
.or(trips)
|
||||
.or(trip)
|
||||
.or(trip_items)
|
||||
.recover(handle_rejection)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// See https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs
|
||||
async fn handle_rejection(err: warp::Rejection) -> Result<impl warp::Reply, Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
|
||||
if err.is_not_found() {
|
||||
message = "NOT_FOUND";
|
||||
code = StatusCode::NOT_FOUND;
|
||||
} else if let Some(InvalidUuid) = err.find() {
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
message = "INVALID_UUID";
|
||||
} else if err
|
||||
.find::<warp::filters::body::BodyDeserializeError>()
|
||||
.is_some()
|
||||
{
|
||||
message = "BAD_REQUEST";
|
||||
code = StatusCode::BAD_REQUEST;
|
||||
} else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
|
||||
message = "METHOD_NOT_ALLOWED";
|
||||
code = StatusCode::METHOD_NOT_ALLOWED;
|
||||
} else {
|
||||
// We should have expected this... Just log and say its a 500
|
||||
eprintln!("unhandled rejection: {:?}", err);
|
||||
message = "UNHANDLED_REJECTION";
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
let json = warp::reply::json(&ErrorMessage {
|
||||
success: false,
|
||||
code: code.as_u16(),
|
||||
message: message.into(),
|
||||
});
|
||||
|
||||
Ok(warp::reply::with_status(json, code))
|
||||
}
|
||||
158
rust_warp_svelte/api/src/trip.rs
Normal file
158
rust_warp_svelte/api/src/trip.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::PackageItem;
|
||||
use crate::PackageList;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TripItemStatus {
|
||||
Pending,
|
||||
Ready,
|
||||
Packed,
|
||||
}
|
||||
|
||||
impl rusqlite::types::FromSql for TripItemStatus {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
match value.as_i64()? {
|
||||
1 => Ok(TripItemStatus::Pending),
|
||||
2 => Ok(TripItemStatus::Ready),
|
||||
3 => Ok(TripItemStatus::Packed),
|
||||
v => Err(rusqlite::types::FromSqlError::OutOfRange(v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::ToSql for TripItemStatus {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let v = rusqlite::types::Value::Integer(match self {
|
||||
TripItemStatus::Pending => 1,
|
||||
TripItemStatus::Ready => 2,
|
||||
TripItemStatus::Packed => 3,
|
||||
});
|
||||
rusqlite::Result::Ok(rusqlite::types::ToSqlOutput::Owned(v))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TripItem {
|
||||
pub status: TripItemStatus,
|
||||
pub package_item: PackageItem,
|
||||
pub package_list: Rc<PackageList>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TripParameters {
|
||||
pub days: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TripPackageList {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl TripPackageList {
|
||||
fn construct(item: (Uuid, String)) -> TripPackageList {
|
||||
TripPackageList {
|
||||
id: item.0,
|
||||
name: item.1,
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_vec(items: Vec<(Uuid, String)>) -> Vec<TripPackageList> {
|
||||
items.into_iter().map(TripPackageList::construct).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TripState {
|
||||
Planned,
|
||||
Packing,
|
||||
Active,
|
||||
Finished,
|
||||
}
|
||||
|
||||
impl rusqlite::types::FromSql for TripState {
|
||||
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
match value.as_i64()? {
|
||||
1 => Ok(TripState::Planned),
|
||||
2 => Ok(TripState::Packing),
|
||||
3 => Ok(TripState::Active),
|
||||
4 => Ok(TripState::Finished),
|
||||
v => Err(rusqlite::types::FromSqlError::OutOfRange(v)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl rusqlite::types::ToSql for TripState {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let v = rusqlite::types::Value::Integer(match self {
|
||||
TripState::Planned => 1,
|
||||
TripState::Packing => 2,
|
||||
TripState::Active => 3,
|
||||
TripState::Finished => 4,
|
||||
});
|
||||
rusqlite::Result::Ok(rusqlite::types::ToSqlOutput::Owned(v))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Trip {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub date: String,
|
||||
pub parameters: TripParameters,
|
||||
pub package_lists: Vec<TripPackageList>,
|
||||
pub state: TripState,
|
||||
}
|
||||
|
||||
impl Trip {
|
||||
pub fn new(
|
||||
id: Uuid,
|
||||
name: String,
|
||||
date: String,
|
||||
parameters: TripParameters,
|
||||
state: TripState,
|
||||
) -> Trip {
|
||||
Trip {
|
||||
id,
|
||||
name,
|
||||
date,
|
||||
parameters,
|
||||
package_lists: vec![],
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_package_list(
|
||||
id: Uuid,
|
||||
name: String,
|
||||
date: String,
|
||||
parameters: TripParameters,
|
||||
package_lists: Vec<(Uuid, String)>,
|
||||
state: TripState,
|
||||
) -> Trip {
|
||||
let lists = TripPackageList::construct_vec(package_lists);
|
||||
Trip {
|
||||
id,
|
||||
name,
|
||||
date,
|
||||
parameters,
|
||||
package_lists: lists,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_package_lists(&mut self, package_lists: Vec<(Uuid, String)>) {
|
||||
let v = TripPackageList::construct_vec(package_lists);
|
||||
self.package_lists = v;
|
||||
}
|
||||
}
|
||||
4
rust_warp_svelte/ui/.gitignore
vendored
Normal file
4
rust_warp_svelte/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules/
|
||||
/public/build/
|
||||
|
||||
.DS_Store
|
||||
3
rust_warp_svelte/ui/.vscode/extensions.json
vendored
Normal file
3
rust_warp_svelte/ui/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
||||
107
rust_warp_svelte/ui/README.md
Normal file
107
rust_warp_svelte/ui/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||
|
||||
---
|
||||
|
||||
# svelte app
|
||||
|
||||
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||
|
||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||
|
||||
```bash
|
||||
npx degit sveltejs/template svelte-app
|
||||
cd svelte-app
|
||||
```
|
||||
|
||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||
|
||||
|
||||
## Get started
|
||||
|
||||
Install the dependencies...
|
||||
|
||||
```bash
|
||||
cd svelte-app
|
||||
npm install
|
||||
```
|
||||
|
||||
...then start [Rollup](https://rollupjs.org):
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||
|
||||
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
||||
|
||||
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
|
||||
|
||||
## Building and running in production mode
|
||||
|
||||
To create an optimised version of the app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||
|
||||
|
||||
## Single-page app mode
|
||||
|
||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||
|
||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||
|
||||
```js
|
||||
"start": "sirv public --single"
|
||||
```
|
||||
|
||||
## Using TypeScript
|
||||
|
||||
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
|
||||
|
||||
```bash
|
||||
node scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
Or remove the script via:
|
||||
|
||||
```bash
|
||||
rm scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
|
||||
|
||||
## Deploying to the web
|
||||
|
||||
### With [Vercel](https://vercel.com)
|
||||
|
||||
Install `vercel` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g vercel
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
cd public
|
||||
vercel deploy --name my-project
|
||||
```
|
||||
|
||||
### With [surge](https://surge.sh/)
|
||||
|
||||
Install `surge` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g surge
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
surge public my-project.surge.sh
|
||||
```
|
||||
6580
rust_warp_svelte/ui/package-lock.json
generated
Normal file
6580
rust_warp_svelte/ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
rust_warp_svelte/ui/package.json
Normal file
31
rust_warp_svelte/ui/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public --no-clear",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-typescript": "^8.0.0",
|
||||
"@tsconfig/svelte": "^2.0.0",
|
||||
"npm": "^8.11.0",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.0.0",
|
||||
"svelte-check": "^2.0.0",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"sirv-cli": "^1.0.0"
|
||||
}
|
||||
}
|
||||
BIN
rust_warp_svelte/ui/public/favicon.png
Normal file
BIN
rust_warp_svelte/ui/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
63
rust_warp_svelte/ui/public/global.css
Normal file
63
rust_warp_svelte/ui/public/global.css
Normal file
@@ -0,0 +1,63 @@
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(0,100,200);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(0,80,160);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input, button, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
-webkit-padding: 0.4em 0;
|
||||
padding: 0.4em;
|
||||
margin: 0 0 0.5em 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #333;
|
||||
background-color: #f4f4f4;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
button:not(:disabled):active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
border-color: #666;
|
||||
}
|
||||
18
rust_warp_svelte/ui/public/index.html
Normal file
18
rust_warp_svelte/ui/public/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
|
||||
<title>Svelte app</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/build/bundle.css'>
|
||||
|
||||
<script defer src='/build/bundle.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
83
rust_warp_svelte/ui/rollup.config.js
Normal file
83
rust_warp_svelte/ui/rollup.config.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
function serve() {
|
||||
let server;
|
||||
|
||||
function toExit() {
|
||||
if (server) server.kill(0);
|
||||
}
|
||||
|
||||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev', '--single'], {
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
process.on('SIGTERM', toExit);
|
||||
process.on('exit', toExit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
input: 'src/main.ts',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'public/build/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
preprocess: sveltePreprocess({ sourceMap: !production }),
|
||||
compilerOptions: {
|
||||
// enable run-time checks when not in production
|
||||
dev: !production
|
||||
}
|
||||
}),
|
||||
// we'll extract any component CSS out into
|
||||
// a separate file - better for performance
|
||||
css({ output: 'bundle.css' }),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration -
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: !production,
|
||||
inlineSources: !production
|
||||
}),
|
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
!production && serve(),
|
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('public'),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: false
|
||||
}
|
||||
};
|
||||
79
rust_warp_svelte/ui/src/App.svelte
Normal file
79
rust_warp_svelte/ui/src/App.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<svelte:head>
|
||||
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||
</svelte:head>
|
||||
|
||||
<script lang="ts">
|
||||
import Home from "./routes/Home.svelte";
|
||||
import PackageLists from "./routes/PackageLists.svelte";
|
||||
import PackageList from "./routes/PackageList.svelte";
|
||||
import Preparation from "./routes/Preparation.svelte";
|
||||
import Trips from "./routes/Trips.svelte";
|
||||
import Trip from "./routes/Trip.svelte";
|
||||
import NotFound from "./routes/NotFound.svelte";
|
||||
|
||||
function normalize(path) {
|
||||
return path.replace(/\/+$/, '') + "/";
|
||||
}
|
||||
|
||||
let currentRoute;
|
||||
let data;
|
||||
|
||||
function route(path) {
|
||||
path = normalize(path);
|
||||
console.log(`Routing path "${path}"`);
|
||||
data = {}
|
||||
|
||||
let urlParts = path.split("/").slice(1, -1);
|
||||
|
||||
if (path === "/") {
|
||||
console.log("=> Home");
|
||||
currentRoute = Home;
|
||||
} else if (urlParts[0] == "lists" && urlParts.length == 1) {
|
||||
console.log("=> PackageLists");
|
||||
currentRoute = PackageLists;
|
||||
} else if (urlParts[0] == "trips" && urlParts.length == 1) {
|
||||
console.log("=> Trips");
|
||||
currentRoute = Trips;
|
||||
} else if (urlParts[0] == "trips" && urlParts.length == 2) {
|
||||
console.log("=> Trip");
|
||||
currentRoute = Trip;
|
||||
data = {id: urlParts[1]};
|
||||
} else if (urlParts[0] == "lists" && urlParts.length == 2) {
|
||||
console.log("=> PackageList");
|
||||
currentRoute = PackageList;
|
||||
data = {id: urlParts[1]};
|
||||
} else if (urlParts.length == 5
|
||||
&& urlParts[0] == "lists"
|
||||
&& urlParts[2] == "items"
|
||||
&& urlParts[4] == "preparation") {
|
||||
console.log("=> PackageList");
|
||||
currentRoute = Preparation;
|
||||
data = {list_id: urlParts[1], item_id: urlParts[3]};
|
||||
} else {
|
||||
console.log("No matching route found");
|
||||
currentRoute = NotFound;
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = e => {
|
||||
route(window.location.pathname);
|
||||
}
|
||||
|
||||
function redirect(path) {
|
||||
history.pushState({id: path}, "", path);
|
||||
route(path);
|
||||
}
|
||||
|
||||
window.addEventListener("locationchange", function() {
|
||||
route(window.location.pathname);
|
||||
});
|
||||
|
||||
window.addEventListener("popstate", event => {
|
||||
route(window.location.pathname);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<svelte:component this={currentRoute} redirect={redirect} data={data}/>
|
||||
</main>
|
||||
10
rust_warp_svelte/ui/src/components/PackageItem.svelte
Normal file
10
rust_warp_svelte/ui/src/components/PackageItem.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{data.name}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
51
rust_warp_svelte/ui/src/components/PackageList.svelte
Normal file
51
rust_warp_svelte/ui/src/components/PackageList.svelte
Normal file
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import PackageItem from "./PackageItem.svelte"
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let name;
|
||||
export let items;
|
||||
export let id;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let active = false;
|
||||
let shown_items = [];
|
||||
let ellipsed = false;
|
||||
|
||||
const enter = () => {
|
||||
active = true
|
||||
}
|
||||
const leave = () => {
|
||||
active = false
|
||||
}
|
||||
|
||||
$: if (items.length <= 4) {
|
||||
shown_items = items
|
||||
ellipsed = false
|
||||
} else {
|
||||
shown_items = items.slice(0, !active && 3 || items.length)
|
||||
ellipsed = true
|
||||
}
|
||||
|
||||
function onClick(id) {
|
||||
dispatch('select', {
|
||||
id: id
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<main on:mouseenter={enter} on:mouseleave={leave} on:click={onClick(id)}>
|
||||
<h2 class="text-lg font-semibold text-center mb-5 mt-3">{name}</h2>
|
||||
<ul class="list-disc ml-5">
|
||||
{#each shown_items as item}
|
||||
<li in:fade|local={{duration: 200}} out:fade|local={{duration: 100, delay: 100}}><PackageItem data={item} /></li>
|
||||
{/each}
|
||||
{#if !active && ellipsed}
|
||||
...
|
||||
{/if}
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
78
rust_warp_svelte/ui/src/components/PackageListTable.svelte
Normal file
78
rust_warp_svelte/ui/src/components/PackageListTable.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
export let id;
|
||||
export let items;
|
||||
export let redirect;
|
||||
|
||||
$: has_counts = items.some(l => l.count > 1);
|
||||
$: has_preparations = items.some(l => l.preparation.steps.length > 0);
|
||||
//has_sizes = items.some(l => l.size.type != "None");
|
||||
|
||||
let has_sizes = false;
|
||||
let has_preparations = false;
|
||||
let has_counts = false;
|
||||
|
||||
const navigateToPreparation = (item_id) => {
|
||||
redirect(`/lists/${id}/items/${item_id}/preparation`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr class="font-semibold tracking-wider text-left bg-gray-100 uppercase border-b border-gray-400">
|
||||
<th class="p-3">Name</th>
|
||||
{#if has_sizes }
|
||||
<th class="p-3">Size</th>
|
||||
{/if}
|
||||
{#if has_counts}
|
||||
<th class="p-3">Count</th>
|
||||
{/if}
|
||||
{#if has_preparations}
|
||||
<th class="p-3">Preparation Steps</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr class="border">
|
||||
<td class="p-3">{item.name}</td>
|
||||
{#if has_sizes }
|
||||
<td class="p-3">
|
||||
{#if item.size.type == "None"}
|
||||
{:else if item.size.type == "Gram"}
|
||||
{#if item.size.value == 1}
|
||||
{item.size.value} Gram
|
||||
{:else}
|
||||
{item.size.value} Grams
|
||||
{/if}
|
||||
{:else if item.size.type == "Pack"}
|
||||
{#if item.size.value == 1}
|
||||
{item.size.value} Pack
|
||||
{:else}
|
||||
{item.size.value} Packs
|
||||
{/if}
|
||||
{:else}
|
||||
{item.size.value} {item.size.type}
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
{#if has_counts}
|
||||
<td class="p-3">
|
||||
{#if item.count > 1}
|
||||
{item.count}
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
{#if has_preparations}
|
||||
<td class="p-3">
|
||||
{#if item.preparation.steps.length > 0}
|
||||
{item.preparation.steps.length} steps
|
||||
<button on:click={() => navigateToPreparation(item.id)}>Show preparation steps</button>
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
1
rust_warp_svelte/ui/src/global.d.ts
vendored
Normal file
1
rust_warp_svelte/ui/src/global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="svelte" />
|
||||
9
rust_warp_svelte/ui/src/main.ts
Normal file
9
rust_warp_svelte/ui/src/main.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.body,
|
||||
props: {
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
15
rust_warp_svelte/ui/src/routes/Home.svelte
Normal file
15
rust_warp_svelte/ui/src/routes/Home.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let redirect;
|
||||
|
||||
function navigateToPackageLists() {
|
||||
redirect("/lists/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
Welcome to Packager, your helper for your next trip!
|
||||
</div>
|
||||
<button on:click={navigateToPackageLists}>Lists</button>
|
||||
<button on:click={() => redirect("/trips/")}>Trips</button>
|
||||
</main>
|
||||
10
rust_warp_svelte/ui/src/routes/NotFound.svelte
Normal file
10
rust_warp_svelte/ui/src/routes/NotFound.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let redirect;
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
404 -- Not Found
|
||||
</div>
|
||||
<button on:click={() => redirect("/")}>Back to home</button>
|
||||
</main>
|
||||
134
rust_warp_svelte/ui/src/routes/PackageList.svelte
Normal file
134
rust_warp_svelte/ui/src/routes/PackageList.svelte
Normal file
@@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import PackageListTable from "../components/PackageListTable.svelte"
|
||||
|
||||
export let redirect;
|
||||
export let data;
|
||||
|
||||
const resetActiveElement = () => {
|
||||
activeElement = {
|
||||
name: "",
|
||||
count: 1,
|
||||
preparationsteps: [],
|
||||
};
|
||||
};
|
||||
|
||||
let activeElement;
|
||||
resetActiveElement();
|
||||
|
||||
export const url = `/lists/${data.id}`
|
||||
|
||||
let sidebarActive = true;
|
||||
|
||||
const toggleSidebar = () => {
|
||||
sidebarActive = !sidebarActive;
|
||||
}
|
||||
|
||||
const apply = async () => {
|
||||
const response = await fetch(`http://localhost:9000/v1/lists/${data.id}/items`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: activeElement.name,
|
||||
count: activeElement.count,
|
||||
}),
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const d = await response.json();
|
||||
|
||||
console.log(d);
|
||||
items = [...items, d];
|
||||
console.log(items[0]);
|
||||
console.log(d);
|
||||
|
||||
resetActiveElement();
|
||||
sidebarActive = false;
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
resetActiveElement();
|
||||
sidebarActive = false;
|
||||
}
|
||||
|
||||
async function getItems(id) {
|
||||
let response = await fetch(`http://localhost:9000/v1/lists/${id}/items`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
items = await response.json();
|
||||
}
|
||||
|
||||
async function getList() {
|
||||
const response = await fetch(`http://localhost:9000/v1/lists/${data.id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
})
|
||||
|
||||
list = await response.json();
|
||||
}
|
||||
|
||||
let list = {name: ""};
|
||||
let items = [];
|
||||
|
||||
onMount(async () => {
|
||||
await getList();
|
||||
await getItems(data.id);
|
||||
});
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<h2 class="text-3xl mt-12 mb-20 font-semibold text-center mb-5 mt-3">{list.name}</h2>
|
||||
<div class="container mx-auto grid grid-cols-12 gap-1 items-start justify-items-stretch">
|
||||
<div class="col-start-1 col-end-9">
|
||||
<PackageListTable items={items} id={list.id} {redirect}/>
|
||||
<button class="p-3 w-full mt-3 border border-gray-200 bg-indigo-300" on:click={toggleSidebar}>Add new item</button>
|
||||
</div>
|
||||
<div class="col-start-9 col-end-10"/>
|
||||
<div class="col-start-10 col-end-13">
|
||||
{#if sidebarActive}
|
||||
<div>
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
class="w-full"
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
bind:value={activeElement.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="count">Count</label>
|
||||
<input
|
||||
class="w-full"
|
||||
type="number"
|
||||
id="count"
|
||||
name="count"
|
||||
bind:value={activeElement.count}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{#each activeElement.preparationsteps as step}
|
||||
{step}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex flex-row mt-6 justify-between w-full">
|
||||
<button type="submit" class="p-3 border border-gray-200 bg-green-300" on:click={() => apply()}>Apply</button>
|
||||
<button class="p-3 border border-gray-200 bg-red-300" on:click={() => cancel()}>Cancel</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
47
rust_warp_svelte/ui/src/routes/PackageLists.svelte
Normal file
47
rust_warp_svelte/ui/src/routes/PackageLists.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import PackageList from "../components/PackageList.svelte"
|
||||
|
||||
export let redirect;
|
||||
export let data;
|
||||
|
||||
export const url = "/lists/"
|
||||
|
||||
async function getLists() {
|
||||
let response = await fetch("http://localhost:9000/v1/lists", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
let lists = await response.json();
|
||||
return lists;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="container mx-auto mt-12">
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr class="font-semibold tracking-wider text-left bg-gray-100 uppercase border-b border-gray-400">
|
||||
<th class="p-3">Name</th>
|
||||
<th class="p-3"># Items</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#await getLists()}
|
||||
<p>Loading</p>
|
||||
{:then lists}
|
||||
{#each lists as list}
|
||||
<tr class="border" on:click={e => redirect(url + list.id)}>
|
||||
<td class="p-3">{list.name}</td>
|
||||
<td class="p-3">{list.items.length}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<p>Error: {error}</p>
|
||||
{/await}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
41
rust_warp_svelte/ui/src/routes/Preparation.svelte
Normal file
41
rust_warp_svelte/ui/src/routes/Preparation.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
export let redirect;
|
||||
export let data;
|
||||
|
||||
async function getSteps() {
|
||||
let response = await fetch(`http://localhost:9000/v1/lists/${data.list_id}/items/${data.item_id}/preparation`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
let list = await response.json();
|
||||
return list;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr class="font-semibold tracking-wider text-left bg-gray-100 uppercase border-b border-gray-400">
|
||||
<th class="p-3">Name</th>
|
||||
<th class="p-3">Start</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#await getSteps()}
|
||||
<p>Loading</p>
|
||||
{:then steps}
|
||||
{#each steps as step}
|
||||
<tr class="border">
|
||||
<td class="p-3">{step.name}</td>
|
||||
<td class="p-3">{step.start.days} days before</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<p>Error: {error}</p>
|
||||
{/await}
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
78
rust_warp_svelte/ui/src/routes/Trip.svelte
Normal file
78
rust_warp_svelte/ui/src/routes/Trip.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
export let redirect;
|
||||
export let data;
|
||||
|
||||
export const url = "/trips/"
|
||||
|
||||
async function getTrip() {
|
||||
let response = await fetch(`http://localhost:9000/v1/trips/${data.id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
let trip = await response.json();
|
||||
return trip;
|
||||
}
|
||||
|
||||
async function getTripItems() {
|
||||
let response = await fetch(`http://localhost:9000/v1/trips/${data.id}/items`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
let items = await response.json();
|
||||
return items;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="container mx-auto mt-12">
|
||||
{#await getTrip()}
|
||||
<p>Loading</p>
|
||||
{:then trip}
|
||||
<h2 class="text-3xl mt-12 mb-20 font-semibold text-center mb-5 mt-3">{trip.name}</h2>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>{trip.date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Duration</td>
|
||||
<td>{trip.parameters.days} Days</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>{trip.state}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr class="font-semibold tracking-wider text-left bg-gray-100 uppercase border-b border-gray-400">
|
||||
<th class="p-3">Name</th>
|
||||
<th class="p-3">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#await getTripItems()}
|
||||
<p>Loading</p>
|
||||
{:then items}
|
||||
{#each items as item}
|
||||
<tr class="border">
|
||||
<td class="p-3">{item.packageItem.name}</td>
|
||||
<td class="p-3">{item.status}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:catch error}
|
||||
{error}
|
||||
{/await}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
</div>
|
||||
</main>
|
||||
64
rust_warp_svelte/ui/src/routes/Trips.svelte
Normal file
64
rust_warp_svelte/ui/src/routes/Trips.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
export let redirect;
|
||||
export let data;
|
||||
|
||||
export const url = "/trips/"
|
||||
|
||||
async function getTrips() {
|
||||
let response = await fetch("http://localhost:9000/v1/trips", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
cache: "no-store",
|
||||
});
|
||||
let trips = await response.json();
|
||||
return trips;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="container mx-auto mt-12">
|
||||
<h2 class="text-3xl mt-12 mb-20 font-semibold text-center mb-5 mt-3">Trips</h2>
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr class="font-semibold tracking-wider text-left bg-gray-100 uppercase border-b border-gray-400">
|
||||
<th class="p-3">Name</th>
|
||||
<th class="p-3">Date</th>
|
||||
<th class="p-3">Days</th>
|
||||
<th class="p-3">State</th>
|
||||
<th class="p-3">Package Lists</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#await getTrips()}
|
||||
<p>Loading</p>
|
||||
{:then trips}
|
||||
{#each trips as trip}
|
||||
<tr class="border" on:click={e => redirect(url + trip.id)}>
|
||||
<td class="p-3" on:click={() => redirect(`/trips/${trip.id}`)}>{trip.name}</td>
|
||||
<td class="p-3">{trip.date}</td>
|
||||
<td class="p-3">{trip.parameters.days}</td>
|
||||
{#if trip.state == "active"}
|
||||
<td class="p-3 bg-green-100">{trip.state}</td>
|
||||
{:else if trip.state == "planned"}
|
||||
<td class="p-3 bg-blue-100">{trip.state}</td>
|
||||
{:else}
|
||||
<td class="p-3">{trip.state}</td>
|
||||
{/if}
|
||||
<td class="p-3">
|
||||
<ul>
|
||||
{#each trip.packageLists as list}
|
||||
<li><button on:click={() => redirect(`/lists/${list.id}`)}>{list.name}</button></li>
|
||||
{/each}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:catch error}
|
||||
<p>Error: {error}</p>
|
||||
{/await}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
6
rust_warp_svelte/ui/tsconfig.json
Normal file
6
rust_warp_svelte/ui/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user