update
This commit is contained in:
parent
e02def6bc1
commit
f3880d77d2
8 changed files with 265 additions and 85 deletions
|
@ -31,4 +31,3 @@ reqwest = { version = "0.11", features = ["blocking"] }
|
|||
|
||||
[features]
|
||||
cache = []
|
||||
htmx = []
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<Shell>) -> 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
|
||||
}
|
||||
|
|
|
@ -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<Build> {
|
||||
fn mount_assets(self) -> Self {
|
||||
self.mount("/", routes![crate::asset::htmx_script_route])
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 `<head>` section of the page.
|
||||
head: PreEscaped<String>,
|
||||
/// An optional class attribute for the `<body>` element.
|
||||
body_class: Option<String>,
|
||||
body_class: String,
|
||||
/// The HTML content for the static body portion.
|
||||
body_content: PreEscaped<String>,
|
||||
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<String>,
|
||||
body_content: PreEscaped<String>,
|
||||
body_class: Option<String>,
|
||||
pub fn new<T: UIWidget + 'static, C: UIWidget + 'static, B: UIWidget + 'static>(
|
||||
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<String>,
|
||||
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(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
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
|
||||
|
|
|
@ -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<SourceWidget>,
|
||||
controls: bool,
|
||||
autoplay: bool,
|
||||
looping: bool,
|
||||
muted: bool,
|
||||
poster: Option<String>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn extended_class(&self) -> Vec<String> {
|
||||
self.base_class()
|
||||
}
|
||||
|
||||
fn render_with_class(&self, class: &str) -> Markup {
|
||||
let mut ret = "<video".to_string();
|
||||
|
||||
if self.controls {
|
||||
ret.push_str(" controls");
|
||||
}
|
||||
|
||||
if self.autoplay {
|
||||
ret.push_str(" autoplay");
|
||||
}
|
||||
|
||||
if self.looping {
|
||||
ret.push_str(" loop");
|
||||
}
|
||||
|
||||
if self.muted {
|
||||
ret.push_str(" muted");
|
||||
}
|
||||
|
||||
if let Some(poster) = &self.poster {
|
||||
ret.push_str(&format!(" poster=\"{}\"", poster.replace("\"", "\\\"")));
|
||||
}
|
||||
|
||||
if let Some(w) = &self.width {
|
||||
ret.push_str(&format!(" width=\"{}\"", w));
|
||||
}
|
||||
|
||||
if let Some(h) = &self.height {
|
||||
ret.push_str(&format!(" height=\"{}\"", h));
|
||||
}
|
||||
|
||||
ret.push_str(&format!(" class=\"{class}\""));
|
||||
|
||||
ret.push_str("> ");
|
||||
|
||||
for src in &self.src {
|
||||
ret.push_str(&src.render().0);
|
||||
}
|
||||
|
||||
ret.push_str("\nYour browser does not support the video tag.\n</video>");
|
||||
|
||||
PreEscaped(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[must_use]
|
||||
pub fn Source(src: &str, mime: Option<String>) -> SourceWidget {
|
||||
SourceWidget {
|
||||
src: src.to_owned(),
|
||||
mime,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SourceWidget {
|
||||
src: String,
|
||||
mime: Option<String>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn extended_class(&self) -> Vec<String> {
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue