Files
packager/src/view/mod.rs

165 lines
3.9 KiB
Rust
Raw Normal View History

2023-09-13 23:20:57 +02:00
/*!
Each component is a struct that can be rendered
They form a tree, with a "build of" relationship
So each component can contain multiple other components
Each component has a parent, except the `root` element
Each component can send a single request (called action) which is
either done with or without Htmx.
Each component implements the [`Component`] trait. It has two
functions:
* `init()` to pass a reference to the parent and component-specific
arguments (implemented as an associated type of the trait)
* `build()` that receives [`Context`] and emits Markup to render.
## Actions
Actions can either be done using Htmx or the standard way. To do this,
each component is supposed to contain a `HtmxComponent` struct. A `HtmxComponent`
contains the following information:
* `ComponentId` that uniquely (and stably) identifies the component (used for the
HTML target ID)
* `action`: The action to take (HTTP method & URL)
* `fallback_action`: The action to take when Htmx is not available (HTTP method & URL)
* `target`: What to target for Htmx swap (either itself or a reference to another component)
*/
2023-08-29 21:34:01 +02:00
use std::fmt;
2023-08-29 21:34:00 +02:00
2023-08-29 21:34:01 +02:00
use base64::Engine as _;
use sha2::{Digest, Sha256};
2023-05-08 00:05:45 +02:00
2023-08-29 21:34:01 +02:00
use crate::Context;
use maud::Markup;
pub mod error;
2023-05-08 00:05:45 +02:00
pub mod home;
pub mod inventory;
2023-08-29 21:34:01 +02:00
pub mod root;
2023-05-18 00:11:52 +02:00
pub mod trip;
2023-05-17 17:47:26 +02:00
2023-08-29 21:34:01 +02:00
pub use error::ErrorPage;
pub use root::Root;
#[derive(Debug)]
pub enum HtmxAction {
Get(String),
}
impl fmt::Display for HtmxAction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
2023-09-11 19:47:51 +02:00
Self::Get(path) => write!(f, "{path}"),
2023-08-29 21:34:01 +02:00
}
}
}
#[derive(Debug)]
pub enum FallbackAction {
Get(String),
}
impl fmt::Display for FallbackAction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
2023-09-11 19:47:51 +02:00
Self::Get(path) => write!(f, "{path}"),
2023-08-29 21:34:01 +02:00
}
}
}
2023-05-08 00:05:45 +02:00
2023-08-29 21:34:01 +02:00
#[derive(Debug, Clone)]
pub struct ComponentId(String);
2023-05-08 00:05:45 +02:00
2023-08-29 21:34:01 +02:00
impl ComponentId {
#[tracing::instrument]
// fn new() -> Self {
// NOTE: this could also use a static AtomicUsize incrementing integer, which might be faster
// Self(random::<u32>())
// }
#[tracing::instrument]
2023-08-29 21:34:01 +02:00
fn html_id(&self) -> String {
let id = {
let mut hasher = Sha256::new();
hasher.update(self.0.as_bytes());
hasher.finalize()
2023-08-29 21:34:00 +02:00
};
2023-08-29 21:34:01 +02:00
// 9 bytes is enough to be unique
// If this is divisible by 3, it means that we can base64-encode it without
// any "=" padding
//
// cannot panic, as the output for sha256 will always be bit
let id = &id[..9];
// URL_SAFE because we cannot have slashes in the output
2023-09-11 19:47:51 +02:00
base64::engine::general_purpose::URL_SAFE.encode(id)
2023-08-29 21:34:01 +02:00
}
2023-09-13 23:36:56 +02:00
#[tracing::instrument]
fn selector(&self) -> String {
format!("#{}", self.html_id())
}
2023-08-29 21:34:01 +02:00
}
impl fmt::Display for ComponentId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.html_id())
2023-05-08 00:05:45 +02:00
}
2023-08-29 21:33:59 +02:00
}
2023-08-29 21:34:01 +02:00
#[derive(Debug)]
pub enum HtmxTarget {
Myself,
Component(ComponentId),
}
2023-05-08 00:05:45 +02:00
2023-08-29 21:34:01 +02:00
#[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)
2023-05-08 00:05:45 +02:00
}
}
2023-08-29 21:34:01 +02:00
pub trait Component {
type Args;
fn init(parent: Parent, args: Self::Args) -> Self;
fn build(self, context: &Context) -> Markup;
}