From f3880d77d2124aea293a13b13dd1abbe7f9ce814 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 21 Jan 2025 16:39:47 +0100 Subject: [PATCH] update --- Cargo.toml | 1 - examples/basic.rs | 12 +-- examples/ui.rs | 28 +++--- src/{htmx.rs => asset.rs} | 12 ++- src/lib.rs | 3 +- src/ui/components/shell.rs | 71 ++++++++++++--- src/ui/mod.rs | 45 ---------- src/ui/primitives/image.rs | 178 ++++++++++++++++++++++++++++++++++++- 8 files changed, 265 insertions(+), 85 deletions(-) rename src/{htmx.rs => asset.rs} (52%) diff --git a/Cargo.toml b/Cargo.toml index b506a28..b627b77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,4 +31,3 @@ reqwest = { version = "0.11", features = ["blocking"] } [features] cache = [] -htmx = [] diff --git a/examples/basic.rs b/examples/basic.rs index 9524ee0..f7b46ad 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,7 +1,7 @@ use based::get_pg; use based::request::{RequestContext, StringResponse}; use based::ui::components::Shell; -use based::ui::render_page; +use based::ui::prelude::Nothing; use maud::html; use rocket::get; use rocket::routes; @@ -12,13 +12,9 @@ pub async fn index_page(ctx: RequestContext) -> StringResponse { h1 { "Hello World!" }; ); - render_page( - content, - "Hello World", - ctx, - &Shell::new(html! {}, html! {}, Some(String::new())), - ) - .await + Shell::new(Nothing(), Nothing(), Nothing()) + .render_page(content, "Hello World", ctx) + .await } #[rocket::launch] diff --git a/examples/ui.rs b/examples/ui.rs index e2f92d3..d084bdb 100644 --- a/examples/ui.rs +++ b/examples/ui.rs @@ -1,14 +1,15 @@ +use based::asset::AssetRoutes; use based::request::{RequestContext, StringResponse}; use based::ui::components::{AppBar, Shell}; use based::ui::htmx::{Event, HTMXAttributes}; -use based::ui::{prelude::*, render_page}; +use based::ui::prelude::*; use maud::Render; use maud::html; -use rocket::get; use rocket::routes; +use rocket::{State, get}; #[get("/")] -pub async fn index_page(ctx: RequestContext) -> StringResponse { +pub async fn index_page(ctx: RequestContext, shell: &State) -> StringResponse { let content = AppBar("MyApp", None).render(); let content = html!( @@ -36,19 +37,7 @@ pub async fn index_page(ctx: RequestContext) -> StringResponse { ); - render_page( - content, - "Hello World", - ctx, - &Shell::new( - html! { - script src="https://cdn.tailwindcss.com" {}; - }, - html! {}, - Some(String::new()), - ), - ) - .await + shell.render_page(content, "Hello World", ctx).await } #[rocket::launch] @@ -56,5 +45,10 @@ async fn launch() -> _ { // Logging env_logger::init(); - rocket::build().mount("/", routes![index_page]) + let shell = Shell::new(Nothing(), Nothing(), Nothing()).use_ui(); + + rocket::build() + .mount("/", routes![index_page]) + .mount_assets() // Mount included assets routes + .manage(shell) // Manage global shell reference } diff --git a/src/htmx.rs b/src/asset.rs similarity index 52% rename from src/htmx.rs rename to src/asset.rs index 950e3ca..659380e 100644 --- a/src/htmx.rs +++ b/src/asset.rs @@ -1,5 +1,5 @@ use crate::request::assets::DataResponse; -use rocket::get; +use rocket::{Build, get, routes}; #[get("/assets/htmx.min.js")] pub fn htmx_script_route() -> DataResponse { @@ -9,3 +9,13 @@ pub fn htmx_script_route() -> DataResponse { Some(60 * 60 * 24 * 3), ) } + +pub trait AssetRoutes { + fn mount_assets(self) -> Self; +} + +impl AssetRoutes for rocket::Rocket { + fn mount_assets(self) -> Self { + self.mount("/", routes![crate::asset::htmx_script_route]) + } +} diff --git a/src/lib.rs b/src/lib.rs index 809b519..a5eb06c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,9 @@ use tokio::sync::OnceCell; +pub mod asset; pub mod auth; pub mod format; -#[cfg(feature = "htmx")] -pub mod htmx; pub mod request; pub mod result; pub mod ui; diff --git a/src/ui/components/shell.rs b/src/ui/components/shell.rs index 2bc0396..351ba9d 100644 --- a/src/ui/components/shell.rs +++ b/src/ui/components/shell.rs @@ -1,5 +1,10 @@ use maud::{PreEscaped, html}; +use crate::{ + request::{RequestContext, StringResponse}, + ui::UIWidget, +}; + // TODO : refactor shell /// Represents the HTML structure of a page shell, including the head, body class, and body content. @@ -9,9 +14,10 @@ pub struct Shell { /// The HTML content for the `` section of the page. head: PreEscaped, /// An optional class attribute for the `` element. - body_class: Option, + body_class: String, /// The HTML content for the static body portion. body_content: PreEscaped, + ui: bool, } impl Shell { @@ -25,18 +31,24 @@ impl Shell { /// # Returns /// A `Shell` instance encapsulating the provided HTML content and attributes. #[must_use] - pub const fn new( - head: PreEscaped, - body_content: PreEscaped, - body_class: Option, + pub fn new( + head: T, + body_content: B, + body_class: C, ) -> Self { Self { - head, - body_class, - body_content, + head: head.render(), + body_class: body_class.extended_class().join(" "), + body_content: body_content.render(), + ui: false, } } + pub fn use_ui(mut self) -> Self { + self.ui = true; + self + } + /// Renders the full HTML page using the shell structure, with additional content and a title. /// /// # Arguments @@ -51,10 +63,15 @@ impl Shell { html { head { title { (title) }; + @if self.ui { + script src="https://cdn.tailwindcss.com" {}; + script src="/assets/htmx.min.js" {}; + meta name="viewport" content="width=device-width, initial-scale=1.0"; + }; (self.head) }; - @if self.body_class.is_some() { - body class=(self.body_class.as_ref().unwrap()) { + @if !self.body_class.is_empty() { + body class=(self.body_class) { (self.body_content); div id="main_content" { @@ -73,4 +90,38 @@ impl Shell { } } } + + /// Renders a full page or an HTMX-compatible fragment based on the request context. + /// + /// If the request is not an HTMX request, this function uses the provided shell to generate + /// a full HTML page. If it is an HTMX request, only the provided content is rendered. + /// + /// # Arguments + /// * `content` - The HTML content to render. + /// * `title` - The title of the page for full-page rendering. + /// * `ctx` - The `RequestContext` containing request metadata. + /// + /// # Returns + /// A `StringResponse` + pub async fn render_page( + &self, + content: PreEscaped, + title: &str, + ctx: RequestContext, + ) -> StringResponse { + if ctx.is_htmx { + ( + rocket::http::Status::Ok, + (rocket::http::ContentType::HTML, content.into_string()), + ) + } else { + ( + rocket::http::Status::Ok, + ( + rocket::http::ContentType::HTML, + self.render(content, title).into_string(), + ), + ) + } + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ab1717d..a314169 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,3 @@ -use components::Shell; use maud::{Markup, PreEscaped, Render, html}; // UI @@ -71,50 +70,6 @@ pub mod prelude { }; } -use crate::request::{RequestContext, StringResponse}; - -use rocket::http::{ContentType, Status}; - -/// Renders a full page or an HTMX-compatible fragment based on the request context. -/// -/// If the request is not an HTMX request, this function uses the provided shell to generate -/// a full HTML page. If it is an HTMX request, only the provided content is rendered. -/// -/// # Arguments -/// * `content` - The HTML content to render. -/// * `title` - The title of the page for full-page rendering. -/// * `ctx` - The `RequestContext` containing request metadata. -/// * `shell` - The `Shell` instance used for full-page rendering. -/// -/// # Returns -/// A `StringResponse` -pub async fn render_page( - content: PreEscaped, - title: &str, - ctx: RequestContext, - shell: &Shell, -) -> StringResponse { - if ctx.is_htmx { - (Status::Ok, (ContentType::HTML, content.into_string())) - } else { - ( - Status::Ok, - ( - ContentType::HTML, - shell.render(content, title).into_string(), - ), - ) - } -} - -// Grids - -// ListViews - -// ListTiles - -// Cards - /// Generic UI Widget pub trait UIWidget: Render { /// Indicating if the widget supports inheriting classes diff --git a/src/ui/primitives/image.rs b/src/ui/primitives/image.rs index 913c60e..7070a16 100644 --- a/src/ui/primitives/image.rs +++ b/src/ui/primitives/image.rs @@ -1,5 +1,5 @@ use crate::ui::UIWidget; -use maud::{Markup, Render, html}; +use maud::{Markup, PreEscaped, Render, html}; #[allow(non_snake_case)] #[must_use] @@ -48,3 +48,179 @@ impl UIWidget for ImageWidget { } } } + +#[allow(non_snake_case)] +#[must_use] +pub fn Video() -> VideoWidget { + VideoWidget { + src: Vec::new(), + controls: false, + autoplay: false, + looping: false, + muted: false, + poster: None, + width: None, + height: None, + } +} + +pub struct VideoWidget { + src: Vec, + controls: bool, + autoplay: bool, + looping: bool, + muted: bool, + poster: Option, + width: Option, + height: Option, +} + +impl Render for VideoWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl VideoWidget { + pub fn add_src(mut self, src: SourceWidget) -> Self { + self.src.push(src); + self + } + + pub fn controls(mut self) -> Self { + self.controls = true; + self + } + + pub fn autoplay(mut self) -> Self { + self.autoplay = true; + self + } + + pub fn looping(mut self) -> Self { + self.looping = true; + self + } + + pub fn muted(mut self) -> Self { + self.muted = true; + self + } + + pub fn poster(mut self, poster: &str) -> Self { + self.poster = Some(poster.to_string()); + self + } + + pub fn width(mut self, w: u32) -> Self { + self.width = Some(w); + self + } + + pub fn height(mut self, h: u32) -> Self { + self.height = Some(h); + self + } +} + +impl UIWidget for VideoWidget { + fn can_inherit(&self) -> bool { + true + } + + fn base_class(&self) -> Vec { + vec![] + } + + fn extended_class(&self) -> Vec { + self.base_class() + } + + fn render_with_class(&self, class: &str) -> Markup { + let mut ret = " "); + + for src in &self.src { + ret.push_str(&src.render().0); + } + + ret.push_str("\nYour browser does not support the video tag.\n"); + + PreEscaped(ret) + } +} + +#[allow(non_snake_case)] +#[must_use] +pub fn Source(src: &str, mime: Option) -> SourceWidget { + SourceWidget { + src: src.to_owned(), + mime, + } +} + +pub struct SourceWidget { + src: String, + mime: Option, +} + +impl Render for SourceWidget { + fn render(&self) -> Markup { + self.render_with_class("") + } +} + +impl UIWidget for SourceWidget { + fn can_inherit(&self) -> bool { + false + } + + fn base_class(&self) -> Vec { + vec![] + } + + fn extended_class(&self) -> Vec { + self.base_class() + } + + fn render_with_class(&self, _: &str) -> Markup { + html! { + @if let Some(mime) = &self.mime { + source src=(self.src) type=(mime); + } else { + source src=(self.src); + }; + } + } +}