diff options
Diffstat (limited to 'server/src/routes/ui/layout.rs')
-rw-r--r-- | server/src/routes/ui/layout.rs | 184 |
1 files changed, 0 insertions, 184 deletions
diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs deleted file mode 100644 index 0a0d036..0000000 --- a/server/src/routes/ui/layout.rs +++ /dev/null @@ -1,184 +0,0 @@ -/* - This file is part of jellything (https://codeberg.org/metamuffin/jellything) - which is licensed under the GNU Affero General Public License (version 3); see /COPYING. - Copyright (C) 2025 metamuffin <metamuffin.org> -*/ -use crate::{ - routes::{ - locale::lang_from_request, - ui::{ - account::{ - rocket_uri_macro_r_account_login, rocket_uri_macro_r_account_logout, - rocket_uri_macro_r_account_register, session::Session, - settings::rocket_uri_macro_r_account_settings, - }, - admin::rocket_uri_macro_r_admin_dashboard, - browser::rocket_uri_macro_r_all_items, - node::rocket_uri_macro_r_library_node, - search::rocket_uri_macro_r_search, - stats::rocket_uri_macro_r_stats, - }, - }, - uri, -}; -use futures::executor::block_on; -use jellybase::{ - locale::{tr, Language}, - CONF, -}; -use jellycommon::user::Theme; -use jellycommon::NodeID; -use jellyimport::is_importing; -use markup::{raw, DynRender, Render, RenderAttributeValue}; -use rocket::{ - http::ContentType, - response::{self, Responder}, - Request, Response, -}; -use std::{borrow::Cow, io::Cursor, sync::LazyLock}; - -static LOGO_ENABLED: LazyLock<bool> = LazyLock::new(|| CONF.asset_path.join("logo.svg").exists()); - -pub struct TrString<'a>(Cow<'a, str>); -impl Render for TrString<'_> { - fn render(&self, writer: &mut impl std::fmt::Write) -> std::fmt::Result { - self.0.as_str().render(writer) - } -} -impl RenderAttributeValue for TrString<'_> { - fn is_none(&self) -> bool { - false - } - fn is_true(&self) -> bool { - false - } - fn is_false(&self) -> bool { - false - } -} - -pub fn escape(str: &str) -> String { - let mut o = String::with_capacity(str.len()); - let mut last = 0; - for (index, byte) in str.bytes().enumerate() { - if let Some(esc) = match byte { - b'<' => Some("<"), - b'>' => Some(">"), - b'&' => Some("&"), - b'"' => Some("""), - _ => None, - } { - o += &str[last..index]; - o += esc; - last = index + 1; - } - } - o += &str[last..]; - o -} - -pub fn trs<'a>(lang: &Language, key: &str) -> TrString<'a> { - TrString(tr(*lang, key)) -} - -markup::define! { - Layout<'a, Main: Render>(title: String, main: Main, class: &'a str, session: Option<Session>, lang: Language) { - @markup::doctype() - html { - head { - title { @title " - " @CONF.brand } - meta[name="viewport", content="width=device-width, initial-scale=1.0"]; - link[rel="stylesheet", href="/assets/style.css"]; - script[src="/assets/bundle.js"] {} - } - body[class=class] { - nav { - h1 { a[href=if session.is_some() {"/home"} else {"/"}] { @if *LOGO_ENABLED { img.logo[src="/assets/logo.svg"]; } else { @CONF.brand } } } " " - @if let Some(_) = session { - a.library[href=uri!(r_library_node("library"))] { @trs(lang, "nav.root") } " " - a.library[href=uri!(r_all_items())] { @trs(lang, "nav.all") } " " - a.library[href=uri!(r_search(None::<&'static str>, None::<usize>))] { @trs(lang, "nav.search") } " " - a.library[href=uri!(r_stats())] { @trs(lang, "nav.stats") } " " - } - @if is_importing() { span.warn { "Library database is updating..." } } - div.account { - @if let Some(session) = session { - span { @raw(tr(*lang, "nav.username").replace("{name}", &format!("<b class=\"username\">{}</b>", escape(&session.user.display_name)))) } " " - @if session.user.admin { - a.admin.hybrid_button[href=uri!(r_admin_dashboard())] { p {@trs(lang, "nav.admin")} } " " - } - a.settings.hybrid_button[href=uri!(r_account_settings())] { p {@trs(lang, "nav.settings")} } " " - a.logout.hybrid_button[href=uri!(r_account_logout())] { p {@trs(lang, "nav.logout")} } - } else { - a.register.hybrid_button[href=uri!(r_account_register())] { p {@trs(lang, "nav.register")} } " " - a.login.hybrid_button[href=uri!(r_account_login())] { p {@trs(lang, "nav.login")} } - } - } - } - #main { @main } - footer { - p { @CONF.brand " - " @CONF.slogan " | powered by " a[href="https://codeberg.org/metamuffin/jellything"]{"Jellything"} } - } - } - } - } - - FlashDisplay(flash: Option<Result<String, String>>) { - @if let Some(flash) = &flash { - @match flash { - Ok(mesg) => { section.message { p.success { @mesg } } } - Err(err) => { section.message { p.error { @err } } } - } - } - } -} - -pub type DynLayoutPage<'a> = LayoutPage<markup::DynRender<'a>>; - -pub struct LayoutPage<T> { - pub title: String, - pub class: Option<&'static str>, - pub content: T, -} - -impl Default for LayoutPage<DynRender<'_>> { - fn default() -> Self { - Self { - class: None, - content: markup::new!(), - title: String::new(), - } - } -} - -impl<'r, Main: Render> Responder<'r, 'static> for LayoutPage<Main> { - fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { - // TODO blocking the event loop here. it seems like there is no other way to - // TODO offload this, since the guard references `req` which has a lifetime. - // TODO therefore we just block. that is fine since the database is somewhat fast. - let lang = lang_from_request(&req); - let session = block_on(req.guard::<Option<Session>>()).unwrap(); - let mut out = String::new(); - Layout { - main: self.content, - title: self.title, - class: &format!( - "{} theme-{:?}", - self.class.unwrap_or(""), - session - .as_ref() - .map(|s| s.user.theme) - .unwrap_or(Theme::Dark) - ), - session, - lang, - } - .render(&mut out) - .unwrap(); - - Response::build() - .header(ContentType::HTML) - .streamed_body(Cursor::new(out)) - .ok() - } -} |