dynamic/static embeds

This commit is contained in:
Robin Appelman 2023-04-12 21:08:09 +02:00
commit cb56c80555
22 changed files with 9068 additions and 665 deletions

894
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,4 @@ async-session = "3.0.0"
quick-xml = { version = "0.28.1", features = ["serialize"] } quick-xml = { version = "0.28.1", features = ["serialize"] }
reqwest = "0.11.16" reqwest = "0.11.16"
rand = "0.8.5" rand = "0.8.5"
[build-dependencies]
demostf-build = { path = "./build", version = "*" } demostf-build = { path = "./build", version = "*" }

View file

@ -1,12 +0,0 @@
use demostf_build::{bundle_script, bundle_style, save_asset};
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=style");
println!("cargo:rerun-if-changed=images");
println!("cargo:rerun-if-changed=script");
save_asset!("style.css", bundle_style("style/style.css"));
save_asset!("upload.js", bundle_script("script/upload.ts"));
save_asset!("demo_list.js", bundle_script("script/demo_list.js"));
}

828
build/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -10,25 +10,8 @@ path = "src/bundle_script.rs"
name = "script" name = "script"
[dependencies] [dependencies]
demostf-build-derive = {version = "0.1", path = "./derive"}
demostf-build-bundlers = {version = "0.1", path = "./bundlers"}
rand = "0.8.5"
tracing-subscriber = "0.3.16" tracing-subscriber = "0.3.16"
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
base64 = "0.21.0"
urlencoding = "2.1.2"
const-fnv1a-hash = "1.1.0"
swc = "0.259.6"
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], path = "../../../rust/swc/crates/swc_common" }
#swc_bundler = "0.212.5"
swc_bundler = { version = "0.212.5", path = "../../../rust/swc/crates/swc_bundler" }
#swc_ecma_loader = "0.42.5"
swc_ecma_loader = { version = "0.42.5", path = "../../../rust/swc/crates/swc_ecma_loader", features = ["node", "cache"] }
swc_ecma_ast = { version = "0.102.5", path = "../../../rust/swc/crates/swc_ecma_ast" }
swc_atoms = { version = "0.4.43", path = "../../../rust/swc/crates/swc_atoms" }
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], path = "../../../rust/swc/crates/swc_ecma_parser" }
swc_ecma_codegen = { version = "0.137.6", path = "../../../rust/swc/crates/swc_ecma_codegen" }
swc_ecma_transforms_base = { version = "0.125.1", path = "../../../rust/swc/crates/swc_ecma_transforms_base" }
swc_ecma_transforms_typescript = { version = "0.175.4", path = "../../../rust/swc/crates/swc_ecma_transforms_typescript" }
swc_ecma_visit = { version = "0.88.5", path = "../../../rust/swc/crates/swc_ecma_visit" }
anyhow = "1.0.70"
#jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions" }
jsx-dom-expressions = { version = "0.1", path = "../../../rust/swc-plugin-jsx-dom-expressions" }

3943
build/bundlers/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

29
build/bundlers/Cargo.toml Normal file
View file

@ -0,0 +1,29 @@
[package]
name = "demostf-build-bundlers"
version = "0.1.0"
edition = "2021"
[dependencies]
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
base64 = "0.21.0"
urlencoding = "2.1.2"
swc = "0.259.6"
swc_common = { version = "0.30.5", features = ["tty-emitter", "concurrent"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_core = { version = "0.74.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
#swc_bundler = "0.212.5"
swc_bundler = { version = "0.212.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
#swc_ecma_loader = "0.42.5"
swc_ecma_loader = { version = "0.42.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old", features = ["node", "cache"] }
swc_ecma_ast = { version = "0.102.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_atoms = { version = "0.4.43", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_ecma_parser = { version = "0.132.6", features = ["typescript"], git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_ecma_codegen = { version = "0.137.6", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_ecma_transforms_base = { version = "0.125.1", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_ecma_transforms_typescript = { version = "0.175.4", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
swc_ecma_visit = { version = "0.88.5", git = "https://github.com/icewind1991/swc", branch = "loader-browser-overwrite-old" }
anyhow = "1.0.70"
jsx-dom-expressions = { version = "0.1", git = "https://github.com/icewind1991/swc-plugin-jsx-dom-expressions", branch = "loader-browser-overwrite-old" }
#jsx-dom-expressions = { version = "0.1", path = "../../../../rust/swc-plugin-jsx-dom-expressions" }
rand = "0.8.5"
fnv = "1.0.7"

39
build/bundlers/src/lib.rs Normal file
View file

@ -0,0 +1,39 @@
mod script;
mod style;
use fnv::FnvHasher;
pub use script::bundle_script;
use std::fs::read;
use std::hash::{Hash, Hasher};
pub use style::bundle_style;
pub fn bundle_raw(input: &str) -> Vec<u8> {
read(input).expect("failed to read raw")
}
fn guess_embed(path: &str) -> (&'static str, bool) {
match path.split('.').last().unwrap() {
"svg" => ("image/svg+xml", false),
"png" => ("image/png", true),
ext => panic!("no mimetype known for {ext}"),
}
}
pub fn hash(input: &[u8]) -> String {
let mut hasher = FnvHasher::default();
input.hash(&mut hasher);
format!("{:x}", hasher.finish())
}
pub fn guess_mime(path: &str) -> &'static str {
if path.ends_with("svg") {
return "image/svg+xml";
} else if path.ends_with("png") {
return "image/png";
} else if path.ends_with("css") {
return "text/css";
} else if path.ends_with("js") {
return "text/javascript";
}
return "text/plain";
}

View file

@ -23,13 +23,13 @@ use swc_ecma_transforms_base::hygiene::hygiene;
use swc_ecma_transforms_typescript::strip; use swc_ecma_transforms_typescript::strip;
use swc_ecma_visit::{as_folder, FoldWith}; use swc_ecma_visit::{as_folder, FoldWith};
pub fn bundle_script(script: &str) -> String { pub fn bundle_script(script: &str) -> Vec<u8> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let minify = false; let minify = false;
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let minify = true; let minify = true;
let output = GLOBALS.set(&Default::default(), || { GLOBALS.set(&Default::default(), || {
let cm = Arc::<SourceMap>::default(); let cm = Arc::<SourceMap>::default();
let globals = &Box::default(); let globals = &Box::default();
@ -62,8 +62,7 @@ pub fn bundle_script(script: &str) -> String {
write(minify, cm.clone(), &module.module, &mut buf); write(minify, cm.clone(), &module.module, &mut buf);
} }
buf buf
}); })
String::from_utf8(output).expect("invalid utf8 bundle")
} }
fn write<W: Write>(minify: bool, cm: Arc<SourceMap>, module: &Module, out: W) { fn write<W: Write>(minify: bool, cm: Arc<SourceMap>, module: &Module, out: W) {

View file

@ -1,4 +1,4 @@
use crate::guess_mime; use crate::guess_embed;
use base64::engine::general_purpose::STANDARD; use base64::engine::general_purpose::STANDARD;
use base64::Engine; use base64::Engine;
use lightningcss::bundler::{Bundler, FileProvider}; use lightningcss::bundler::{Bundler, FileProvider};
@ -11,7 +11,7 @@ use std::convert::Infallible;
use std::fs::read; use std::fs::read;
use std::path::Path; use std::path::Path;
pub fn bundle_style(style: &str) -> String { pub fn bundle_style(style: &str) -> Vec<u8> {
// todo build time? // todo build time?
let fs = FileProvider::new(); let fs = FileProvider::new();
let mut bundler = Bundler::new( let mut bundler = Bundler::new(
@ -49,6 +49,7 @@ pub fn bundle_style(style: &str) -> String {
}) })
.expect("failed to output css") .expect("failed to output css")
.code .code
.into_bytes()
} }
struct InlineUrlVisitor; struct InlineUrlVisitor;
@ -64,7 +65,7 @@ impl<'i> Visitor<'i> for InlineUrlVisitor {
eprintln!("Failed to write inline file {path}: {e}"); eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline"); panic!("Failed to inline");
}); });
let (mime, encode) = guess_mime(path); let (mime, encode) = guess_embed(path);
if encode { if encode {
let encoded = STANDARD.encode(content); let encoded = STANDARD.encode(content);

3994
build/derive/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
build/derive/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "demostf-build-derive"
version = "0.1.0"
edition = "2021"
[lib]
name = "demostf_build_derive"
proc-macro = true
[dependencies]
syn = "2.0.12"
quote = "1.0.26"
proc-macro2 = "1.0.54"
structmeta = "0.2.0"
merge = "0.1.0"
demostf-build-bundlers = {version = "0.1", path = "../bundlers"}

203
build/derive/src/asset.rs Normal file
View file

@ -0,0 +1,203 @@
use crate::{err, Derivable, DeriveParams};
use demostf_build_bundlers::guess_mime;
use merge::Merge;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned};
use structmeta::StructMeta;
use syn::parse::Parse;
use syn::{Attribute, DeriveInput, LitStr, Result};
pub struct Asset;
impl Derivable for Asset {
type Params = AssetParams;
fn derive(params: AssetParams) -> Result<TokenStream> {
let struct_ident = params.name;
let span = struct_ident.span();
let input_path = params.source;
let asset = if params.debug {
AssetContent::Runtime {
url: input_path.clone(),
ty: params.ty,
}
} else {
let content = params.ty.bundle(&input_path);
AssetContent::Compiled {
route: params.url.clone(),
content,
}
};
let content = asset.content();
let buster = asset.buster();
let etag = asset.etag();
let url = asset.url();
let mime = guess_mime(&input_path);
let mime = quote_spanned!(span => #mime);
let route = params.url;
let route = quote_spanned!(span => #route);
Ok(
quote_spanned!(span => impl demostf_build::Asset for #struct_ident {
fn mime() -> &'static str {
#mime
}
fn etag() -> &'static str {
#etag
}
fn cache_buster() -> std::borrow::Cow<'static, str> {
#buster
}
fn content() -> std::borrow::Cow<'static, [u8]> {
#content
}
fn route() -> &'static str {
#route
}
fn url() -> std::borrow::Cow<'static, str> {
#url
}
}),
)
}
}
pub struct AssetParams {
name: Ident,
debug: bool,
source: String,
url: String,
ty: AssetType,
}
enum AssetType {
Js,
Css,
Raw,
}
impl AssetType {
fn from_url(url: &str) -> Self {
if url.ends_with("css") {
return AssetType::Css;
} else if url.ends_with("js") {
return AssetType::Js;
}
return AssetType::Raw;
}
fn bundle_fn(&self) -> TokenStream {
match self {
AssetType::Css => quote!(bundle_style),
AssetType::Js => quote!(bundle_script),
AssetType::Raw => quote!(bundle_raw),
}
}
fn bundle(self, url: &str) -> Vec<u8> {
match self {
AssetType::Css => demostf_build_bundlers::bundle_style(url),
AssetType::Js => demostf_build_bundlers::bundle_script(url),
AssetType::Raw => demostf_build_bundlers::bundle_raw(url),
}
}
}
enum AssetContent {
Runtime { url: String, ty: AssetType },
Compiled { route: String, content: Vec<u8> },
}
impl AssetContent {
pub fn content(&self) -> TokenStream {
match self {
AssetContent::Runtime { url, ty } => {
let bundle_fn = ty.bundle_fn();
quote!(demostf_build::#bundle_fn(#url).into())
}
AssetContent::Compiled { content, .. } => {
quote!(std::borrow::Cow::Borrowed(&[
#(#content,)*
]))
}
}
}
pub fn buster(&self) -> TokenStream {
match self {
AssetContent::Runtime { .. } => {
quote!(demostf_build::random_cache_buster().into())
}
AssetContent::Compiled { content, .. } => {
let hash = demostf_build_bundlers::hash(&content);
quote!(std::borrow::Cow::Borrowed(#hash))
}
}
}
pub fn etag(&self) -> TokenStream {
match self {
AssetContent::Runtime { .. } => {
quote!("")
}
AssetContent::Compiled { content, .. } => {
let hash = demostf_build_bundlers::hash(&content);
quote!(#hash)
}
}
}
pub fn url(&self) -> TokenStream {
match self {
AssetContent::Runtime { .. } => {
quote!(format!("{}?v={}", Self::route(), Self::cache_buster()).into())
}
AssetContent::Compiled { content, route } => {
let hash = demostf_build_bundlers::hash(&content);
let url = format!("{}?v={}", route, hash);
quote!(std::borrow::Cow::Borrowed(#url))
}
}
}
}
impl DeriveParams for AssetParams {
fn parse(input: &DeriveInput) -> Result<AssetParams> {
let attrs: AssetAttrs = parse_attrs(&input.attrs);
#[cfg(debug_assertions)]
let debug = true;
#[cfg(not(debug_assertions))]
let debug = false;
let Some(source) = attrs.source else {
return err("missing require attribute #[asset(source)]", input);
};
let source = source.value();
let Some(url) = attrs.url else {
return err("missing require attribute #[asset(url)]", input);
};
let url = url.value();
let ty = AssetType::from_url(&url);
Ok(AssetParams {
name: input.ident.clone(),
debug,
source,
url,
ty,
})
}
}
fn parse_attrs<T: Parse + Default + Merge>(attrs: &[Attribute]) -> T {
let mut result = T::default();
for attr in attrs {
if let Ok(parsed) = attr.parse_args() {
result.merge(parsed);
}
}
result
}
#[derive(Default, StructMeta, Merge)]
struct AssetAttrs {
source: Option<LitStr>,
url: Option<LitStr>,
}

43
build/derive/src/lib.rs Normal file
View file

@ -0,0 +1,43 @@
//! Derive macros for tf-log-parser
extern crate proc_macro;
mod asset;
use crate::asset::Asset;
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::fmt::Display;
use syn::{parse_macro_input, DeriveInput, Error, Result};
/// Derive the `Asset` trait for a struct
#[proc_macro_derive(Asset, attributes(asset))]
pub fn derive_asset(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let expanded = derive_trait::<Asset>(parse_macro_input!(input as DeriveInput));
proc_macro::TokenStream::from(expanded)
}
/// Basic wrapper for error handling
fn derive_trait<Trait: Derivable>(input: DeriveInput) -> TokenStream {
derive_trait_inner::<Trait>(input).unwrap_or_else(|err| err.into_compile_error())
}
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
let params = Trait::Params::parse(&input)?;
Trait::derive(params)
}
trait Derivable {
type Params: DeriveParams;
fn derive(params: Self::Params) -> Result<TokenStream>;
}
trait DeriveParams: Sized {
fn parse(input: &DeriveInput) -> Result<Self>;
}
fn err<R, T: ToTokens, U: Display>(msg: U, span: T) -> Result<R> {
return Err(Error::new_spanned(&span, msg));
}

View file

@ -1,11 +1,9 @@
use std::env::args; use std::env::args;
mod script;
fn main() { fn main() {
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let path = args().skip(1).next().unwrap(); let path = args().skip(1).next().unwrap();
let output = script::bundle_script(&path); let output = demostf_build::bundle_script(&path);
// println!("{output}") // println!("{output}")
} }

View file

@ -1,30 +1,23 @@
mod script; pub use demostf_build_bundlers::bundle_raw;
mod style; pub use demostf_build_bundlers::bundle_script;
pub use demostf_build_bundlers::bundle_style;
pub use demostf_build_derive::Asset;
use rand::{distributions::Alphanumeric, Rng};
use std::borrow::Cow;
use const_fnv1a_hash::fnv1a_hash_str_32; pub trait Asset {
pub use script::bundle_script; fn mime() -> &'static str;
pub use style::bundle_style; fn cache_buster() -> Cow<'static, str>;
fn etag() -> &'static str;
#[macro_export] fn content() -> Cow<'static, [u8]>;
macro_rules! save_asset { fn url() -> Cow<'static, str>;
($name:expr, $val:expr) => { fn route() -> &'static str;
let val = $val;
let out_dir = std::env::var("OUT_DIR").unwrap();
std::fs::write(format!("{out_dir}/{}", $name), &val).expect("failed to write asset");
let hash = demostf_build::hash(&val);
std::fs::write(format!("{out_dir}/{}.hash", $name), hash)
.expect("failed to write asset hash");
};
} }
pub fn hash(data: &str) -> String { pub fn random_cache_buster() -> String {
format!("{:x}", fnv1a_hash_str_32(data)) rand::thread_rng()
} .sample_iter(&Alphanumeric)
.take(8)
fn guess_mime(path: &str) -> (&'static str, bool) { .map(char::from)
match path.split('.').last().unwrap() { .collect()
"svg" => ("image/svg+xml", false),
"png" => ("image/png", true),
ext => panic!("no mimetype known for {ext}"),
}
} }

View file

@ -1,26 +1,8 @@
use axum::response::IntoResponse;
use demostf_build::Asset;
use hyper::header::{CACHE_CONTROL, CONTENT_TYPE, ETAG}; use hyper::header::{CACHE_CONTROL, CONTENT_TYPE, ETAG};
use hyper::http::{HeaderName, HeaderValue}; use hyper::http::{HeaderName, HeaderValue};
macro_rules! saved_asset {
($name:expr) => {
include_str!(concat!(env!("OUT_DIR"), "/", $name))
};
}
macro_rules! saved_asset_url {
($name:expr) => {
concat!("/", $name, "?v=", crate::asset::saved_asset_hash!($name))
};
}
macro_rules! saved_asset_hash {
($name:expr) => {
include_str!(concat!(env!("OUT_DIR"), "/", $name, ".hash"))
};
($name:expr, quoted) => {
concat!(r#"""#, crate::asset::saved_asset_hash!($name), r#"""#)
};
}
pub const fn cache_headers( pub const fn cache_headers(
content_type: &'static str, content_type: &'static str,
etag: &'static str, etag: &'static str,
@ -35,51 +17,9 @@ pub const fn cache_headers(
] ]
} }
pub const fn guess_mime(path: &'static str) -> &'static str { pub async fn serve_asset<A: Asset>() -> impl IntoResponse {
use const_str::ends_with; let mime = A::mime();
if ends_with!(path, "svg") { let style = A::content();
return "image/svg+xml"; let etag = A::etag();
} else if ends_with!(path, "png") { (cache_headers(mime, etag), style.into_owned())
return "image/png";
} else if ends_with!(path, "css") {
return "text/css";
} else if ends_with!(path, "js") {
return "text/javascript";
}
return "text/plain";
} }
macro_rules! serve_static {
($name:expr) => {
|| async {
const CONTENT: &[u8] = include_bytes!($name);
const HASH: u32 = const_fnv1a_hash::fnv1a_hash_32(&CONTENT, None);
const HASH_S: const_base::ArrayStr<8> = const_base::WrongOutputLength::unwrap(
const_base::encode(&HASH.to_le_bytes(), const_base::Config::HEX),
);
(
crate::asset::cache_headers(crate::asset::guess_mime($name), HASH_S.as_str()),
CONTENT,
)
}
};
}
macro_rules! serve_compiled {
($name:expr) => {
|| async {
let style = crate::asset::saved_asset!($name);
let etag = crate::asset::saved_asset_hash!($name, quoted);
(
crate::asset::cache_headers(crate::asset::guess_mime($name), etag),
style,
)
}
};
}
pub(crate) use saved_asset;
pub(crate) use saved_asset_hash;
pub(crate) use saved_asset_url;
pub(crate) use serve_compiled;
pub(crate) use serve_static;

View file

@ -5,6 +5,7 @@ mod error;
mod pages; mod pages;
mod session; mod session;
use crate::asset::serve_asset;
pub use crate::config::Config; pub use crate::config::Config;
use crate::config::Listen; use crate::config::Listen;
use crate::data::demo::{Demo, ListDemo}; use crate::data::demo::{Demo, ListDemo};
@ -13,11 +14,10 @@ use crate::data::steam_id::SteamId;
use crate::data::user::User; use crate::data::user::User;
use crate::pages::about::AboutPage; use crate::pages::about::AboutPage;
use crate::pages::demo::DemoPage; use crate::pages::demo::DemoPage;
use crate::pages::index::Index; use crate::pages::index::{DemoListScript, Index};
use crate::pages::render; use crate::pages::upload::{UploadPage, UploadScript};
use crate::pages::upload::UploadPage; use crate::pages::{render, GlobalStyle};
use crate::session::{SessionData, COOKIE_NAME}; use crate::session::{SessionData, COOKIE_NAME};
use asset::{serve_compiled, serve_static};
use async_session::{MemoryStore, Session, SessionStore}; use async_session::{MemoryStore, Session, SessionStore};
use axum::extract::{MatchedPath, Path, RawQuery}; use axum::extract::{MatchedPath, Path, RawQuery};
use axum::headers::Cookie; use axum::headers::Cookie;
@ -25,6 +25,7 @@ use axum::http::header::{LOCATION, SET_COOKIE};
use axum::http::{HeaderValue, Request, StatusCode}; use axum::http::{HeaderValue, Request, StatusCode};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{extract::State, routing::get, Router, Server, TypedHeader}; use axum::{extract::State, routing::get, Router, Server, TypedHeader};
use demostf_build::Asset;
pub use error::Error; pub use error::Error;
use hyperlocal::UnixServerExt; use hyperlocal::UnixServerExt;
use maud::Markup; use maud::Markup;
@ -50,6 +51,13 @@ struct App {
pub session_store: MemoryStore, pub session_store: MemoryStore,
} }
#[derive(Asset)]
#[asset(source = "images/logo.png", url = "/images/logo.png")]
struct LogoPng;
#[derive(Asset)]
#[asset(source = "images/logo.svg", url = "/images/logo.svg")]
struct LogoSvg;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
tracing_subscriber::registry() tracing_subscriber::registry()
@ -75,11 +83,11 @@ async fn main() -> Result<()> {
let app = Router::new() let app = Router::new()
.route("/", get(index)) .route("/", get(index))
.route("/style.css", get(serve_compiled!("style.css"))) .route(GlobalStyle::route(), get(serve_asset::<GlobalStyle>))
.route("/upload.js", get(serve_compiled!("upload.js"))) .route(UploadScript::route(), get(serve_asset::<UploadScript>))
.route("/demo_list.js", get(serve_compiled!("demo_list.js"))) .route(DemoListScript::route(), get(serve_asset::<DemoListScript>))
.route("/images/logo.png", get(serve_static!("../images/logo.png"))) .route(LogoPng::route(), get(serve_asset::<LogoPng>))
.route("/images/logo.svg", get(serve_static!("../images/logo.svg"))) .route(LogoSvg::route(), get(serve_asset::<LogoSvg>))
.route("/about", get(about)) .route("/about", get(about))
.route("/login/callback", get(login_callback)) .route("/login/callback", get(login_callback))
.route("/login", get(login)) .route("/login", get(login))

View file

@ -1,6 +1,6 @@
use crate::asset::saved_asset_url;
use crate::data::demo::ListDemo; use crate::data::demo::ListDemo;
use crate::pages::Page; use crate::pages::Page;
use demostf_build::Asset;
use maud::{html, Markup, Render}; use maud::{html, Markup, Render};
use std::borrow::Cow; use std::borrow::Cow;
@ -10,13 +10,17 @@ pub struct Index<'a> {
pub api: &'a str, pub api: &'a str,
} }
#[derive(Asset)]
#[asset(source = "script/demo_list.js", url = "/demo_list.js")]
pub struct DemoListScript;
impl Page for Index<'_> { impl Page for Index<'_> {
fn title(&self) -> Cow<'static, str> { fn title(&self) -> Cow<'static, str> {
"Demos - demos.tf".into() "Demos - demos.tf".into()
} }
fn render(&self) -> Markup { fn render(&self) -> Markup {
let script = saved_asset_url!("demo_list.js"); let script = DemoListScript::url();
html! { html! {
h1 { "Demos" } h1 { "Demos" }
#filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {} #filter-bar data-maps = (MapList(&self.maps)) data-api-base = (self.api) {}

View file

@ -4,8 +4,8 @@ pub mod index;
mod plugin_section; mod plugin_section;
pub mod upload; pub mod upload;
use crate::asset::saved_asset_url;
use crate::session::SessionData; use crate::session::SessionData;
use demostf_build::Asset;
use maud::{html, Markup, DOCTYPE}; use maud::{html, Markup, DOCTYPE};
use std::borrow::Cow; use std::borrow::Cow;
@ -14,8 +14,12 @@ pub trait Page {
fn render(&self) -> Markup; fn render(&self) -> Markup;
} }
#[derive(Asset)]
#[asset(source = "style/style.css", url = "/style.css")]
pub struct GlobalStyle;
pub fn render<T: Page>(page: T, session: SessionData) -> Markup { pub fn render<T: Page>(page: T, session: SessionData) -> Markup {
let style_url = saved_asset_url!("style.css"); let style_url = GlobalStyle::url();
html! { html! {
(DOCTYPE) (DOCTYPE)
html { html {

View file

@ -1,6 +1,6 @@
use crate::asset::saved_asset_url;
use crate::pages::plugin_section::PluginSection; use crate::pages::plugin_section::PluginSection;
use crate::pages::Page; use crate::pages::Page;
use demostf_build::Asset;
use maud::{html, Markup}; use maud::{html, Markup};
use std::borrow::Cow; use std::borrow::Cow;
@ -17,13 +17,17 @@ impl<'a> UploadPage<'a> {
} }
} }
#[derive(Asset)]
#[asset(source = "script/upload.ts", url = "/upload.js")]
pub struct UploadScript;
impl Page for UploadPage<'_> { impl Page for UploadPage<'_> {
fn title(&self) -> Cow<'static, str> { fn title(&self) -> Cow<'static, str> {
"Upload - demos.tf".into() "Upload - demos.tf".into()
} }
fn render(&self) -> Markup { fn render(&self) -> Markup {
let script = saved_asset_url!("upload.js"); let script = UploadScript::url();
html! { html! {
.upload-page { .upload-page {
section.upload { section.upload {

View file

@ -1,5 +1,9 @@
@import '../node_modules/@thisbeyond/solid-select/dist/esm/style.css'; @import '../node_modules/@thisbeyond/solid-select/dist/esm/style.css';
#filter-bar {
height: 35px;
}
.filter-bar { .filter-bar {
display: flex; display: flex;
flex-direction: row; flex-direction: row;