html
This commit is contained in:
12
rust/.sqlx/query-bc01ec2128b21bbdfaab9fc42782a4b30ea2c88d120b4bf2c56cdf0f8d4c5d9d.json
generated
Normal file
12
rust/.sqlx/query-bc01ec2128b21bbdfaab9fc42782a4b30ea2c88d120b4bf2c56cdf0f8d4c5d9d.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE trips_items\n SET pick = ?\n WHERE trip_id = ?\n AND item_id = ?\n AND user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "bc01ec2128b21bbdfaab9fc42782a4b30ea2c88d120b4bf2c56cdf0f8d4c5d9d"
|
||||||
|
}
|
||||||
@@ -406,7 +406,7 @@ impl TripItem {
|
|||||||
},
|
},
|
||||||
pool,
|
pool,
|
||||||
"UPDATE trips_items
|
"UPDATE trips_items
|
||||||
SET " => "pick" => "= ?
|
SET pick = ?
|
||||||
WHERE trip_id = ?
|
WHERE trip_id = ?
|
||||||
AND item_id = ?
|
AND item_id = ?
|
||||||
AND user_id = ?",
|
AND user_id = ?",
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use axum::{
|
|||||||
Form,
|
Form,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::view::Component;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -98,12 +100,26 @@ pub struct TripTypeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn root(Extension(current_user): Extension<models::user::User>) -> impl IntoResponse {
|
pub async fn root(
|
||||||
view::Root::build(
|
Extension(current_user): Extension<models::user::User>,
|
||||||
&Context::build(current_user),
|
headers: HeaderMap,
|
||||||
&view::home::Home::build(),
|
) -> impl IntoResponse {
|
||||||
None,
|
if htmx::is_htmx(&headers) {
|
||||||
)
|
view::root::Body::init(
|
||||||
|
view::Parent::Root,
|
||||||
|
view::root::BodyArgs {
|
||||||
|
body: &view::home::Home::build(),
|
||||||
|
active_page: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build(&Context::build(current_user))
|
||||||
|
} else {
|
||||||
|
view::Root::build(
|
||||||
|
&Context::build(current_user),
|
||||||
|
&view::home::Home::build(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
@@ -166,22 +182,38 @@ pub async fn inventory_inactive(
|
|||||||
Extension(current_user): Extension<models::user::User>,
|
Extension(current_user): Extension<models::user::User>,
|
||||||
State(mut state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Query(inventory_query): Query<InventoryQuery>,
|
Query(inventory_query): Query<InventoryQuery>,
|
||||||
|
headers: HeaderMap,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
let ctx = Context::build(current_user);
|
let ctx = Context::build(current_user);
|
||||||
state.client_state.edit_item = inventory_query.edit_item;
|
state.client_state.edit_item = inventory_query.edit_item;
|
||||||
state.client_state.active_category_id = None;
|
state.client_state.active_category_id = None;
|
||||||
|
|
||||||
let inventory = models::inventory::Inventory::load(&ctx, &state.database_pool).await?;
|
let inventory = models::inventory::Inventory::load(&ctx, &state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
if htmx::is_htmx(&headers) {
|
||||||
&ctx,
|
Ok(view::root::Body::init(
|
||||||
&view::inventory::Inventory::build(
|
view::Parent::Root,
|
||||||
None,
|
view::root::BodyArgs {
|
||||||
&inventory.categories,
|
body: &view::inventory::Inventory::build(
|
||||||
state.client_state.edit_item,
|
None,
|
||||||
),
|
&inventory.categories,
|
||||||
Some(&TopLevelPage::Inventory),
|
state.client_state.edit_item,
|
||||||
))
|
),
|
||||||
|
|
||||||
|
active_page: Some(&TopLevelPage::Inventory),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build(&ctx))
|
||||||
|
} else {
|
||||||
|
Ok(view::Root::build(
|
||||||
|
&ctx,
|
||||||
|
&view::inventory::Inventory::build(
|
||||||
|
None,
|
||||||
|
&inventory.categories,
|
||||||
|
state.client_state.edit_item,
|
||||||
|
),
|
||||||
|
Some(&TopLevelPage::Inventory),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
@@ -345,15 +377,27 @@ pub async fn trip_create(
|
|||||||
pub async fn trips(
|
pub async fn trips(
|
||||||
Extension(current_user): Extension<models::user::User>,
|
Extension(current_user): Extension<models::user::User>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
) -> Result<impl IntoResponse, Error> {
|
) -> Result<impl IntoResponse, Error> {
|
||||||
let ctx = Context::build(current_user);
|
let ctx = Context::build(current_user);
|
||||||
let trips = models::trips::Trip::all(&ctx, &state.database_pool).await?;
|
let trips = models::trips::Trip::all(&ctx, &state.database_pool).await?;
|
||||||
|
|
||||||
Ok(view::Root::build(
|
if htmx::is_htmx(&headers) {
|
||||||
&ctx,
|
Ok(view::root::Body::init(
|
||||||
&view::trip::TripManager::build(trips),
|
view::Parent::Root,
|
||||||
Some(&TopLevelPage::Trips),
|
view::root::BodyArgs {
|
||||||
))
|
body: &view::trip::TripManager::build(trips),
|
||||||
|
active_page: Some(&TopLevelPage::Trips),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.build(&ctx))
|
||||||
|
} else {
|
||||||
|
Ok(view::Root::build(
|
||||||
|
&ctx,
|
||||||
|
&view::trip::TripManager::build(trips),
|
||||||
|
Some(&TopLevelPage::Trips),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
|
|||||||
@@ -261,31 +261,31 @@ macro_rules! execute {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
( $class:expr, $pool:expr, $( $query:expr )=>+, $( $args:tt )*) => {
|
// ( $class:expr, $pool:expr, $( $query:expr )=>+, $( $args:tt )*) => {
|
||||||
{
|
// {
|
||||||
use tracing::Instrument as _;
|
// use tracing::Instrument as _;
|
||||||
async {
|
// async {
|
||||||
// $crate::sqlite::sqlx_query($class, $( $query )+ , &[]);
|
// // $crate::sqlite::sqlx_query($class, $( $query )+ , &[]);
|
||||||
// println!("haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay: {}", $crate::strip_plus!($(+ $query )+));
|
// // println!("haaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaay: {}", $crate::strip_plus!($(+ $query )+));
|
||||||
let result: Result<sqlx::sqlite::SqliteQueryResult, Error> = sqlx::query!(
|
// let result: Result<sqlx::sqlite::SqliteQueryResult, Error> = sqlx::query!(
|
||||||
// "x" + "y",
|
// // "x" + "y",
|
||||||
$crate::strip_plus!($(+ $query )+),
|
// $crate::strip_plus!($(+ $query )+),
|
||||||
// "UPDATE trips_items
|
// // "UPDATE trips_items
|
||||||
// SET " + "pick" +
|
// // SET " + "pick" +
|
||||||
// "= ?
|
// // "= ?
|
||||||
// WHERE trip_id = ?
|
// // WHERE trip_id = ?
|
||||||
// AND item_id = ?
|
// // AND item_id = ?
|
||||||
// AND user_id = ?",
|
// // AND user_id = ?",
|
||||||
$( $args )*
|
// $( $args )*
|
||||||
)
|
// )
|
||||||
.execute($pool)
|
// .execute($pool)
|
||||||
.await
|
// .await
|
||||||
.map_err(|e| e.into());
|
// .map_err(|e| e.into());
|
||||||
|
|
||||||
result
|
// result
|
||||||
}.instrument(tracing::info_span!("packager::sql::query", "query"))
|
// }.instrument(tracing::info_span!("packager::sql::query", "query"))
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|||||||
21
rust/src/view/error.rs
Normal file
21
rust/src/view/error.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use maud::{html, Markup, DOCTYPE};
|
||||||
|
|
||||||
|
pub struct ErrorPage;
|
||||||
|
|
||||||
|
impl ErrorPage {
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub fn build(message: &str) -> Markup {
|
||||||
|
html!(
|
||||||
|
(DOCTYPE)
|
||||||
|
html {
|
||||||
|
head {
|
||||||
|
title { "Packager" }
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
h1 { "Error" }
|
||||||
|
p { (message) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,135 +1,130 @@
|
|||||||
use super::Context;
|
use std::fmt;
|
||||||
|
|
||||||
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
use base64::Engine as _;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
use crate::Context;
|
||||||
|
use maud::Markup;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
|
pub mod root;
|
||||||
pub mod trip;
|
pub mod trip;
|
||||||
|
|
||||||
pub struct Root;
|
pub use error::ErrorPage;
|
||||||
|
pub use root::Root;
|
||||||
|
|
||||||
use crate::TopLevelPage;
|
#[derive(Debug)]
|
||||||
|
pub enum HtmxAction {
|
||||||
|
Get(String),
|
||||||
|
}
|
||||||
|
|
||||||
impl Root {
|
impl fmt::Display for HtmxAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Get(path) => write!(f, "{}", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FallbackAction {
|
||||||
|
Get(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for FallbackAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Get(path) => write!(f, "{}", path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ComponentId(String);
|
||||||
|
|
||||||
|
impl ComponentId {
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn build(context: &Context, body: &Markup, active_page: Option<&TopLevelPage>) -> Markup {
|
// fn new() -> Self {
|
||||||
let menu_item = |item: TopLevelPage, active_page: Option<&TopLevelPage>| {
|
// NOTE: this could also use a static AtomicUsize incrementing integer, which might be faster
|
||||||
let active = active_page.map_or(false, |page| *page == item);
|
// Self(random::<u32>())
|
||||||
html!(
|
// }
|
||||||
a
|
#[tracing::instrument]
|
||||||
href=(item.path())
|
fn html_id(&self) -> String {
|
||||||
#{"header-link-" (item.id())}
|
let id = {
|
||||||
."px-5"
|
let mut hasher = Sha256::new();
|
||||||
."flex"
|
hasher.update(self.0.as_bytes());
|
||||||
."h-full"
|
hasher.finalize()
|
||||||
."text-lg"
|
|
||||||
."hover:bg-gray-300"
|
|
||||||
|
|
||||||
// invisible top border to fix alignment
|
|
||||||
."border-t-gray-200"[active]
|
|
||||||
."hover:border-t-gray-300"[active]
|
|
||||||
|
|
||||||
."border-b-gray-500"[active]
|
|
||||||
."border-y-4"[active]
|
|
||||||
."font-bold"[active]
|
|
||||||
{ span ."m-auto" ."font-semibold" { (item.name()) }}
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
html!(
|
// 9 bytes is enough to be unique
|
||||||
(DOCTYPE)
|
// If this is divisible by 3, it means that we can base64-encode it without
|
||||||
html {
|
// any "=" padding
|
||||||
head {
|
//
|
||||||
title { "Packager" }
|
// cannot panic, as the output for sha256 will always be bit
|
||||||
script src="https://unpkg.com/htmx.org@1.9.4" {}
|
let id = &id[..9];
|
||||||
script src="https://unpkg.com/alpinejs@3.12.3" defer {}
|
|
||||||
script src="https://cdn.tailwindcss.com/3.3.3" {}
|
// URL_SAFE because we cannot have slashes in the output
|
||||||
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.2.96/css/materialdesignicons.min.css" {}
|
let id = base64::engine::general_purpose::URL_SAFE.encode(id);
|
||||||
link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg" {}
|
|
||||||
script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) }
|
id
|
||||||
meta name="htmx-config" content=r#"{"useTemplateFragments":true}"# {}
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
header
|
|
||||||
#header
|
|
||||||
."h-16"
|
|
||||||
."bg-gray-200"
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."flex-nowrap"
|
|
||||||
."justify-between"
|
|
||||||
."items-stretch"
|
|
||||||
{
|
|
||||||
a
|
|
||||||
#home
|
|
||||||
href="/"
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."items-center"
|
|
||||||
."gap-3"
|
|
||||||
."px-5"
|
|
||||||
."hover:bg-gray-300"
|
|
||||||
{
|
|
||||||
img ."h-12" src="/assets/luggage.svg" {}
|
|
||||||
span
|
|
||||||
."text-xl"
|
|
||||||
."font-semibold"
|
|
||||||
{ "Packager" }
|
|
||||||
}
|
|
||||||
nav
|
|
||||||
."grow"
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."justify-center"
|
|
||||||
."gap-x-10"
|
|
||||||
."items-stretch"
|
|
||||||
{
|
|
||||||
(menu_item(TopLevelPage::Inventory, active_page))
|
|
||||||
(menu_item(TopLevelPage::Trips, active_page))
|
|
||||||
}
|
|
||||||
a
|
|
||||||
."flex"
|
|
||||||
."flex-row"
|
|
||||||
."items-center"
|
|
||||||
."gap-3"
|
|
||||||
."px-5"
|
|
||||||
."bg-gray-200"
|
|
||||||
."hover:bg-gray-300"
|
|
||||||
href=(format!("/user/{}", context.user.id))
|
|
||||||
{
|
|
||||||
span
|
|
||||||
."m-auto"
|
|
||||||
."mdi"
|
|
||||||
."mdi-account"
|
|
||||||
."text-3xl"
|
|
||||||
{}
|
|
||||||
p { (context.user.fullname)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ErrorPage;
|
impl fmt::Display for ComponentId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
impl ErrorPage {
|
write!(f, "{}", self.html_id())
|
||||||
#[tracing::instrument]
|
|
||||||
pub fn build(message: &str) -> Markup {
|
|
||||||
html!(
|
|
||||||
(DOCTYPE)
|
|
||||||
html {
|
|
||||||
head {
|
|
||||||
title { "Packager" }
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
h1 { "Error" }
|
|
||||||
p { (message) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HtmxTarget {
|
||||||
|
Myself,
|
||||||
|
Component(ComponentId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HtmxComponent {
|
||||||
|
id: ComponentId,
|
||||||
|
action: HtmxAction,
|
||||||
|
fallback_action: FallbackAction,
|
||||||
|
target: HtmxTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HtmxComponent {
|
||||||
|
fn target(&self) -> &ComponentId {
|
||||||
|
match self.target {
|
||||||
|
HtmxTarget::Myself => &self.id,
|
||||||
|
HtmxTarget::Component(ref id) => id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Parent {
|
||||||
|
Root,
|
||||||
|
Component(ComponentId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Parent> for ComponentId {
|
||||||
|
fn from(value: Parent) -> Self {
|
||||||
|
match value {
|
||||||
|
Parent::Root => ComponentId("/".into()),
|
||||||
|
Parent::Component(c) => c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ComponentId> for Parent {
|
||||||
|
fn from(value: ComponentId) -> Self {
|
||||||
|
Self::Component(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Component {
|
||||||
|
type Args;
|
||||||
|
|
||||||
|
fn init(parent: Parent, args: Self::Args) -> Self;
|
||||||
|
fn build(self, context: &Context) -> Markup;
|
||||||
|
}
|
||||||
|
|||||||
216
rust/src/view/root.rs
Normal file
216
rust/src/view/root.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
use crate::{Context, TopLevelPage};
|
||||||
|
|
||||||
|
use maud::{html, Markup, PreEscaped, DOCTYPE};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Component, ComponentId, FallbackAction, HtmxAction, HtmxComponent, HtmxTarget, Parent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Header;
|
||||||
|
|
||||||
|
impl Header {
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub fn build() -> Markup {
|
||||||
|
html!(
|
||||||
|
head {
|
||||||
|
title { "Packager" }
|
||||||
|
script src="https://unpkg.com/htmx.org@1.9.4" {}
|
||||||
|
script src="https://unpkg.com/alpinejs@3.12.3" defer {}
|
||||||
|
script src="https://cdn.tailwindcss.com/3.3.3" {}
|
||||||
|
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.2.96/css/materialdesignicons.min.css" {}
|
||||||
|
link rel="shortcut icon" type="image/svg+xml" href="/favicon.svg" {}
|
||||||
|
script { (PreEscaped(include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/js/app.js")))) }
|
||||||
|
meta name="htmx-config" content=r#"{"useTemplateFragments":true}"# {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HeaderLink<'a> {
|
||||||
|
htmx: HtmxComponent,
|
||||||
|
args: HeaderLinkArgs<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HeaderLinkArgs<'a> {
|
||||||
|
pub item: TopLevelPage,
|
||||||
|
pub active_page: Option<&'a TopLevelPage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Component for HeaderLink<'a> {
|
||||||
|
type Args = HeaderLinkArgs<'a>;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(args))]
|
||||||
|
fn init(parent: Parent, args: Self::Args) -> Self {
|
||||||
|
Self {
|
||||||
|
htmx: HtmxComponent {
|
||||||
|
id: ComponentId(format!("/header/component/{}", args.item.id())),
|
||||||
|
action: HtmxAction::Get(args.item.path().to_string()),
|
||||||
|
fallback_action: FallbackAction::Get(args.item.path().to_string()),
|
||||||
|
target: HtmxTarget::Component(parent.into()),
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
fn build(self, context: &Context) -> Markup {
|
||||||
|
let active = self
|
||||||
|
.args
|
||||||
|
.active_page
|
||||||
|
.map_or(false, |page| *page == self.args.item);
|
||||||
|
html!(
|
||||||
|
a
|
||||||
|
href=(self.args.item.path())
|
||||||
|
hx-get=(self.args.item.path())
|
||||||
|
hx-target={ "#" (self.htmx.target().html_id()) }
|
||||||
|
hx-swap="outerHtml"
|
||||||
|
#{"header-link-" (self.args.item.id())}
|
||||||
|
."px-5"
|
||||||
|
."flex"
|
||||||
|
."h-full"
|
||||||
|
."text-lg"
|
||||||
|
."hover:bg-gray-300"
|
||||||
|
|
||||||
|
// invisible top border to fix alignment
|
||||||
|
."border-t-gray-200"[active]
|
||||||
|
."hover:border-t-gray-300"[active]
|
||||||
|
|
||||||
|
."border-b-gray-500"[active]
|
||||||
|
."border-y-4"[active]
|
||||||
|
."font-bold"[active]
|
||||||
|
{ span ."m-auto" ."font-semibold" { (self.args.item.name()) }}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Body<'a> {
|
||||||
|
htmx: HtmxComponent,
|
||||||
|
args: BodyArgs<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BodyArgs<'a> {
|
||||||
|
pub body: &'a Markup,
|
||||||
|
pub active_page: Option<&'a TopLevelPage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Component for Body<'a> {
|
||||||
|
type Args = BodyArgs<'a>;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(args))]
|
||||||
|
fn init(parent: Parent, args: Self::Args) -> Self {
|
||||||
|
Self {
|
||||||
|
htmx: HtmxComponent {
|
||||||
|
id: ComponentId("/body/".into()),
|
||||||
|
action: HtmxAction::Get("/".into()),
|
||||||
|
fallback_action: FallbackAction::Get("/".into()),
|
||||||
|
target: HtmxTarget::Myself,
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
fn build(self, context: &Context) -> Markup {
|
||||||
|
html!(
|
||||||
|
body #(self.htmx.id.html_id())
|
||||||
|
{
|
||||||
|
header
|
||||||
|
#header
|
||||||
|
."h-16"
|
||||||
|
."bg-gray-200"
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."flex-nowrap"
|
||||||
|
."justify-between"
|
||||||
|
."items-stretch"
|
||||||
|
{
|
||||||
|
a
|
||||||
|
#home
|
||||||
|
href=(self.htmx.fallback_action)
|
||||||
|
hx-get=(self.htmx.action)
|
||||||
|
hx-target={ "#" (self.htmx.target()) }
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
."gap-3"
|
||||||
|
."px-5"
|
||||||
|
."hover:bg-gray-300"
|
||||||
|
{
|
||||||
|
img ."h-12" src="/assets/luggage.svg" {}
|
||||||
|
span
|
||||||
|
."text-xl"
|
||||||
|
."font-semibold"
|
||||||
|
{ "Packager" }
|
||||||
|
}
|
||||||
|
nav
|
||||||
|
."grow"
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."justify-center"
|
||||||
|
."gap-x-10"
|
||||||
|
."items-stretch"
|
||||||
|
{
|
||||||
|
(
|
||||||
|
// todo make clone() unnecessary
|
||||||
|
// make ComponentId take &str instead of owned string
|
||||||
|
HeaderLink::init(
|
||||||
|
self.htmx.id.clone().into(),
|
||||||
|
HeaderLinkArgs {
|
||||||
|
item: TopLevelPage::Inventory,
|
||||||
|
active_page: self.args.active_page
|
||||||
|
}
|
||||||
|
).build(&context)
|
||||||
|
)
|
||||||
|
(
|
||||||
|
HeaderLink::init(
|
||||||
|
self.htmx.id.clone().into(),
|
||||||
|
HeaderLinkArgs {
|
||||||
|
item: TopLevelPage::Trips,
|
||||||
|
active_page: self.args.active_page
|
||||||
|
}
|
||||||
|
).build(&context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
a
|
||||||
|
."flex"
|
||||||
|
."flex-row"
|
||||||
|
."items-center"
|
||||||
|
."gap-3"
|
||||||
|
."px-5"
|
||||||
|
."bg-gray-200"
|
||||||
|
."hover:bg-gray-300"
|
||||||
|
href=(format!("/user/{}", context.user.id))
|
||||||
|
{
|
||||||
|
span
|
||||||
|
."m-auto"
|
||||||
|
."mdi"
|
||||||
|
."mdi-account"
|
||||||
|
."text-3xl"
|
||||||
|
{}
|
||||||
|
p { (context.user.fullname)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(self.args.body)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Root;
|
||||||
|
|
||||||
|
impl Root {
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub fn build(context: &Context, body: &Markup, active_page: Option<&TopLevelPage>) -> Markup {
|
||||||
|
html!(
|
||||||
|
(DOCTYPE)
|
||||||
|
html {
|
||||||
|
(Header::build())
|
||||||
|
(Body::init(Parent::Root, BodyArgs {
|
||||||
|
body,
|
||||||
|
active_page
|
||||||
|
}).build(&context))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user