add ssr rust
This commit is contained in:
25
rust/src/components/home.rs
Normal file
25
rust/src/components/home.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use super::Tree;
|
||||
use axohtml::html;
|
||||
|
||||
pub struct Home {
|
||||
doc: Tree,
|
||||
}
|
||||
|
||||
impl Home {
|
||||
pub fn build() -> Self {
|
||||
let doc = html!(
|
||||
<div id="home" class=["p-8", "max-w-xl"]>
|
||||
<p><a href="/inventory">"Inventory"</a></p>
|
||||
<p><a href="/trips">"Trips"</a></p>
|
||||
</div>
|
||||
);
|
||||
|
||||
Self { doc }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Tree> for Home {
|
||||
fn into(self) -> Tree {
|
||||
self.doc
|
||||
}
|
||||
}
|
||||
227
rust/src/components/inventory.rs
Normal file
227
rust/src/components/inventory.rs
Normal file
@@ -0,0 +1,227 @@
|
||||
use super::Tree;
|
||||
use axohtml::{
|
||||
html, text,
|
||||
types::{Class, SpacedSet},
|
||||
};
|
||||
|
||||
use crate::models::*;
|
||||
use crate::State;
|
||||
|
||||
pub struct Inventory {
|
||||
doc: Tree,
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
pub async fn build(state: State, categories: Vec<Category>) -> Result<Self, Error> {
|
||||
let doc = html!(
|
||||
<div id="pkglist-item-manager">
|
||||
<div class=["p-8", "grid", "grid-cols-4", "gap-3"]>
|
||||
<div class=["col-span-2"]>
|
||||
{<InventoryCategoryList as Into<Tree>>::into(InventoryCategoryList::build(&categories).await?)}
|
||||
</div>
|
||||
{if state.has_active_category { html!(
|
||||
<div class=["col-span-2"]>
|
||||
{<InventoryItemList as Into<Tree>>::into(InventoryItemList::build(categories.iter().find(|category| category.active).unwrap().items()).await?)}
|
||||
</div>
|
||||
)} else {
|
||||
html!(<div></div>)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Ok(Self { doc })
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Tree> for Inventory {
|
||||
fn into(self) -> Tree {
|
||||
self.doc
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryCategoryList {
|
||||
doc: Tree,
|
||||
}
|
||||
|
||||
impl InventoryCategoryList {
|
||||
pub async fn build(categories: &Vec<Category>) -> Result<Self, Error> {
|
||||
let biggest_category_weight: u32 = categories
|
||||
.iter()
|
||||
.map(|category| category.total_weight())
|
||||
.max()
|
||||
.unwrap_or(1);
|
||||
|
||||
let cls_td_active: SpacedSet<Class> =
|
||||
["border", "p-0", "m-0", "font-bold"].try_into().unwrap();
|
||||
let cls_td_inactive: SpacedSet<Class> = ["border", "p-0", "m-0"].try_into().unwrap();
|
||||
|
||||
let cls_tr_active: SpacedSet<Class> = [
|
||||
"h-10",
|
||||
"hover:bg-purple-100",
|
||||
"m-3",
|
||||
"h-full",
|
||||
"outline",
|
||||
"outline-2",
|
||||
"outline-indigo-600",
|
||||
]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let cls_tr_inactive: SpacedSet<Class> = ["h-10", "hover:bg-purple-100", "m-3", "h-full"]
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let doc = html!(
|
||||
<div>
|
||||
<h1 class=["text-2xl", "mb-5"]>"Categories"</h1>
|
||||
<table class=[
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
]>
|
||||
|
||||
<colgroup>
|
||||
<col style="width:50%"/>
|
||||
<col style="width:50%"/>
|
||||
</colgroup>
|
||||
<thead class=["bg-gray-200"]>
|
||||
<tr class=["h-10"]>
|
||||
<th class=["border", "p-2"]>"Name"</th>
|
||||
<th class=["border", "p-2"]>"Weight"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{categories.iter().map(|category| html!(
|
||||
<tr
|
||||
class={if category.active {
|
||||
cls_tr_active.clone()
|
||||
} else {
|
||||
cls_tr_inactive.clone()
|
||||
}}
|
||||
>
|
||||
<td
|
||||
class={if category.active {
|
||||
cls_td_active.clone()
|
||||
} else {
|
||||
cls_td_inactive.clone()
|
||||
}}
|
||||
>
|
||||
<a
|
||||
id="select-category"
|
||||
href={
|
||||
format!(
|
||||
"/inventory/category/{id}",
|
||||
id=category.id
|
||||
)
|
||||
}
|
||||
class=["inline-block", "p-2", "m-0", "w-full"]
|
||||
>
|
||||
{text!(category.name.clone())}
|
||||
</a>
|
||||
</td>
|
||||
<td class=["border", "p-0", "m-0"] style="position:relative;">
|
||||
<a
|
||||
id="select-category"
|
||||
href={
|
||||
format!(
|
||||
"/inventory/category/{id}",
|
||||
id=category.id
|
||||
)
|
||||
}
|
||||
class=["inline-block", "p-2", "m-0", "w-full"]
|
||||
>
|
||||
<p>
|
||||
{text!(category.total_weight().to_string())}
|
||||
</p>
|
||||
</a>
|
||||
<div
|
||||
class=["bg-blue-600", "h-1.5"]
|
||||
style = {
|
||||
format!(
|
||||
"width: {width}%;position:absolute;left:0;bottom:0;right:0;",
|
||||
width=(
|
||||
category.total_weight() as f32
|
||||
/ biggest_category_weight as f32
|
||||
* 100.0
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr class=["h-10", "hover:bg-purple-200", "bg-gray-300", "font-bold"]>
|
||||
<td class=["border", "p-0", "m-0"]>
|
||||
<p class=["p-2", "m-2"]>"Sum"</p>
|
||||
</td>
|
||||
<td class=["border", "p-0", "m-0"]>
|
||||
<p class=["p-2", "m-2"]>
|
||||
{text!(categories.iter().map(|category| category.total_weight()).sum::<u32>().to_string())}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
Ok(Self { doc })
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Tree> for InventoryCategoryList {
|
||||
fn into(self) -> Tree {
|
||||
self.doc
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InventoryItemList {
|
||||
doc: Tree,
|
||||
}
|
||||
|
||||
impl InventoryItemList {
|
||||
pub async fn build(items: &Vec<Item>) -> Result<Self, Error> {
|
||||
let doc = html!(
|
||||
<div>
|
||||
<h1 class=["text-2xl", "mb-5"]>"Categories"</h1>
|
||||
<table class=[
|
||||
"table",
|
||||
"table-auto",
|
||||
"border-collapse",
|
||||
"border-spacing-0",
|
||||
"border",
|
||||
"w-full",
|
||||
]>
|
||||
|
||||
<thead class=["bg-gray-200"]>
|
||||
<tr class=["h-10"]>
|
||||
<th class=["border", "p-2"]>"Name"</th>
|
||||
<th class=["border", "p-2"]>"Weight"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.iter().map(|item| html!(
|
||||
<tr>
|
||||
<td>
|
||||
{text!(item.name.clone())}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
Ok(Self { doc })
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Tree> for InventoryItemList {
|
||||
fn into(self) -> Tree {
|
||||
self.doc
|
||||
}
|
||||
}
|
||||
86
rust/src/components/mod.rs
Normal file
86
rust/src/components/mod.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use axohtml::{
|
||||
dom::DOMTree,
|
||||
elements::FlowContent,
|
||||
html,
|
||||
types::{Class, SpacedSet},
|
||||
unsafe_text,
|
||||
};
|
||||
|
||||
type Tree = Box<dyn FlowContent<String>>;
|
||||
|
||||
pub mod home;
|
||||
pub mod inventory;
|
||||
pub mod triplist;
|
||||
|
||||
pub use home::*;
|
||||
pub use inventory::*;
|
||||
pub use triplist::*;
|
||||
|
||||
pub struct Root {
|
||||
doc: DOMTree<String>,
|
||||
}
|
||||
|
||||
pub enum TopLevelPage {
|
||||
Inventory,
|
||||
Trips,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Root {
|
||||
pub fn build(body: Tree, active_page: TopLevelPage) -> Self {
|
||||
let active_classes: SpacedSet<Class> =
|
||||
["text-lg", "font-bold", "underline"].try_into().unwrap();
|
||||
let inactive_classes: SpacedSet<Class> = ["text-lg"].try_into().unwrap();
|
||||
|
||||
let doc = html!(
|
||||
<html>
|
||||
<head>
|
||||
<title>"Packager"</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.7.0"/>
|
||||
<script src="https://cdn.tailwindcss.com"/>
|
||||
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.js" defer="true"/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"/>
|
||||
<script>{unsafe_text!(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))}</script>
|
||||
</head>
|
||||
<body>
|
||||
<header class=[
|
||||
"bg-gray-200",
|
||||
"p-5",
|
||||
"flex",
|
||||
"flex-row",
|
||||
"flex-nowrap",
|
||||
"justify-between",
|
||||
"items-center",
|
||||
]>
|
||||
<span class=["text-xl", "font-semibold"]>
|
||||
<a href="/">"Packager"</a>
|
||||
</span>
|
||||
<nav class=["grow", "flex", "flex-row", "justify-center", "gap-x-6"]>
|
||||
<a href="/inventory/" class={
|
||||
match active_page {
|
||||
TopLevelPage::Inventory => active_classes.clone(),
|
||||
_ => inactive_classes.clone(),
|
||||
}
|
||||
}>"Inventory"</a>
|
||||
<a href="/trips/" class={
|
||||
match active_page {
|
||||
TopLevelPage::Trips => active_classes,
|
||||
_ => inactive_classes,
|
||||
}
|
||||
}>"Trips"</a>
|
||||
</nav>
|
||||
</header>
|
||||
{body}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
Self { doc }
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut doc = self.doc.to_string();
|
||||
doc.insert_str(0, "<!DOCTYPE html>\n");
|
||||
doc
|
||||
}
|
||||
}
|
||||
43
rust/src/components/triplist.rs
Normal file
43
rust/src/components/triplist.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use super::Tree;
|
||||
use crate::models::*;
|
||||
|
||||
use axohtml::{html, text};
|
||||
|
||||
pub struct TripList {
|
||||
doc: Tree,
|
||||
}
|
||||
|
||||
impl TripList {
|
||||
pub fn build(package_lists: Vec<Trip>) -> Self {
|
||||
let doc = html!(
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>"ID"</th>
|
||||
<th>"Name"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
package_lists.into_iter().map(|list| {
|
||||
html!(
|
||||
<tr>
|
||||
<td>{text!(list.id.to_string())}</td>
|
||||
<td>{text!(list.name)}</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
Self { doc }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Tree> for TripList {
|
||||
fn into(self) -> Tree {
|
||||
self.doc
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user