build rework

This commit is contained in:
Robin Appelman 2023-04-09 16:32:30 +02:00
commit dc80d715a6
18 changed files with 1681 additions and 115 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
/target
target
result
.direnv
config.toml

14
Cargo.lock generated
View file

@ -627,6 +627,16 @@ dependencies = [
"matches",
]
[[package]]
name = "demostf-build"
version = "0.1.0"
dependencies = [
"base64 0.21.0",
"const-fnv1a-hash",
"lightningcss",
"urlencoding",
]
[[package]]
name = "digest"
version = "0.9.0"
@ -790,15 +800,14 @@ version = "0.1.0"
dependencies = [
"async-session",
"axum",
"base64 0.21.0",
"config",
"const-fnv1a-hash",
"const-str 0.5.4",
"const_base",
"demostf-build",
"hyper",
"hyperlocal",
"itertools",
"lightningcss",
"maud",
"quick-xml",
"rand 0.8.5",
@ -814,7 +823,6 @@ dependencies = [
"tower-http",
"tracing",
"tracing-subscriber",
"urlencoding",
]
[[package]]

View file

@ -30,7 +30,4 @@ reqwest = "0.11.16"
rand = "0.8.5"
[build-dependencies]
lightningcss = { version = "1.0.0-alpha.40", features = ["browserslist", "visitor"] }
base64 = "0.21.0"
urlencoding = "2.1.2"
const-fnv1a-hash = "1.1.0"
demostf-build = { path = "./build", version = "*" }

103
build.rs
View file

@ -1,108 +1,9 @@
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use const_fnv1a_hash::fnv1a_hash_str_32;
use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions};
use lightningcss::targets::Browsers;
use lightningcss::values::url::Url;
use lightningcss::visit_types;
use lightningcss::visitor::{Visit, VisitTypes, Visitor};
use std::convert::Infallible;
use std::env::var;
use std::fs::{read, write};
use std::path::Path;
macro_rules! save_asset {
($name:expr, $val:expr) => {
let val = $val;
let out_dir = var("OUT_DIR").unwrap();
write(format!("{out_dir}/{}", $name), &val).expect("failed to write asset");
let hash = fnv1a_hash_str_32(&val);
write(format!("{out_dir}/{}.hash", $name), format!("{:x}", hash))
.expect("failed to write asset hash");
};
}
use demostf_build::{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");
save_asset!("style.css", build_style());
}
pub fn build_style() -> String {
// todo build time?
let fs = FileProvider::new();
let mut bundler = Bundler::new(
&fs,
None,
ParserOptions {
nesting: true,
..ParserOptions::default()
},
);
let mut stylesheet = bundler
.bundle(Path::new("style/style.css"))
.expect("failed to bundle css");
let browsers =
Browsers::from_browserslist(["last 2 versions"]).expect("failed to parse browserlist");
stylesheet
.minify(MinifyOptions {
targets: browsers.clone(),
..MinifyOptions::default()
})
.expect("failed to minify css");
#[cfg(debug_assertions)]
let minify = false;
#[cfg(not(debug_assertions))]
let minify = true;
stylesheet.visit(&mut InlineUrlVisitor).unwrap();
stylesheet
.to_css(PrinterOptions {
targets: browsers,
minify,
..PrinterOptions::default()
})
.expect("failed to output css")
.code
}
struct InlineUrlVisitor;
impl<'i> Visitor<'i> for InlineUrlVisitor {
type Error = Infallible;
const TYPES: VisitTypes = visit_types!(URLS);
fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {
if let Some(path) = url.url.strip_prefix("inline://") {
let content = read(path).unwrap_or_else(|e| {
eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline");
});
let (mime, encode) = guess_mime(path);
if encode {
let encoded = STANDARD.encode(content);
url.url = format!("data:{mime};base64,{encoded}").into();
} else {
let content = String::from_utf8(content).expect("invalid utf8");
let encoded = urlencoding::encode(&content);
url.url = format!("data:{mime},{encoded}").into();
}
}
Ok(())
}
}
fn guess_mime(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}"),
}
save_asset!("style.css", bundle_style("style/style.css"));
}

1410
build/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

10
build/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "demostf-build"
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"
const-fnv1a-hash = "1.1.0"

28
build/src/lib.rs Normal file
View file

@ -0,0 +1,28 @@
mod style;
use const_fnv1a_hash::fnv1a_hash_str_32;
pub use style::bundle_style;
#[macro_export]
macro_rules! save_asset {
($name:expr, $val:expr) => {
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 = fnv1a_hash_str_32(&val);
std::fs::write(format!("{out_dir}/{}.hash", $name), format!("{:x}", hash))
.expect("failed to write asset hash");
};
}
pub fn hash(data: &str) -> String {
format!("{:x}", fnv1a_hash_str_32(data))
}
fn guess_mime(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}"),
}
}

80
build/src/style.rs Normal file
View file

@ -0,0 +1,80 @@
use crate::guess_mime;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions};
use lightningcss::targets::Browsers;
use lightningcss::values::url::Url;
use lightningcss::visit_types;
use lightningcss::visitor::{Visit, VisitTypes, Visitor};
use std::convert::Infallible;
use std::fs::read;
use std::path::Path;
pub fn bundle_style(style: &str) -> String {
// todo build time?
let fs = FileProvider::new();
let mut bundler = Bundler::new(
&fs,
None,
ParserOptions {
nesting: true,
..ParserOptions::default()
},
);
let mut stylesheet = bundler
.bundle(Path::new(style))
.expect("failed to bundle css");
let browsers =
Browsers::from_browserslist(["last 2 versions"]).expect("failed to parse browserlist");
stylesheet
.minify(MinifyOptions {
targets: browsers.clone(),
..MinifyOptions::default()
})
.expect("failed to minify css");
#[cfg(debug_assertions)]
let minify = false;
#[cfg(not(debug_assertions))]
let minify = true;
stylesheet.visit(&mut InlineUrlVisitor).unwrap();
stylesheet
.to_css(PrinterOptions {
targets: browsers,
minify,
..PrinterOptions::default()
})
.expect("failed to output css")
.code
}
struct InlineUrlVisitor;
impl<'i> Visitor<'i> for InlineUrlVisitor {
type Error = Infallible;
const TYPES: VisitTypes = visit_types!(URLS);
fn visit_url(&mut self, url: &mut Url<'i>) -> Result<(), Self::Error> {
if let Some(path) = url.url.strip_prefix("inline://") {
let content = read(path).unwrap_or_else(|e| {
eprintln!("Failed to write inline file {path}: {e}");
panic!("Failed to inline");
});
let (mime, encode) = guess_mime(path);
if encode {
let encoded = STANDARD.encode(content);
url.url = format!("data:{mime};base64,{encoded}").into();
} else {
let content = String::from_utf8(content).expect("invalid utf8");
let encoded = urlencoding::encode(&content);
url.url = format!("data:{mime},{encoded}").into();
}
}
Ok(())
}
}

0
script/upload.js Normal file
View file

View file

@ -14,6 +14,7 @@ use crate::pages::about::AboutPage;
use crate::pages::demo::DemoPage;
use crate::pages::index::Index;
use crate::pages::render;
use crate::pages::upload::UploadPage;
use crate::session::{SessionData, COOKIE_NAME};
use asset::{serve_compiled, serve_static};
use async_session::{MemoryStore, Session, SessionStore};
@ -78,6 +79,7 @@ async fn main() -> Result<()> {
.route("/login/callback", get(login_callback))
.route("/login", get(login))
.route("/logout", get(logout))
.route("/upload", get(upload))
.route("/:id", get(demo))
.layer(
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
@ -215,6 +217,18 @@ async fn logout(
)
}
async fn upload(State(_app): State<Arc<App>>, session: SessionData) -> impl IntoResponse {
if let Some(token) = session.token() {
render(UploadPage { key: token }, session).into_response()
} else {
(
StatusCode::FOUND,
[(LOCATION, HeaderValue::from_str("/").unwrap())],
)
.into_response()
}
}
async fn handler_404() -> impl IntoResponse {
Error::NotFound
}

View file

@ -2,6 +2,7 @@ pub mod about;
pub mod demo;
pub mod index;
mod plugin_section;
pub mod upload;
use crate::asset::saved_asset_url;
use crate::session::SessionData;

View file

@ -62,6 +62,9 @@ impl Render for PluginSection<'_> {
}
}
}
li {
"Restart the server."
}
}
a.button.button-primary href = "https://github.com/demostf/plugin/raw/master/demostf.smx" { "Download" }
a.button href = "https://github.com/demostf/plugin/raw/master/demostf.sp" { "Source" }

55
src/pages/upload.rs Normal file
View file

@ -0,0 +1,55 @@
use crate::pages::plugin_section::PluginSection;
use crate::pages::Page;
use maud::{html, Markup};
use std::borrow::Cow;
pub struct UploadPage {
pub key: String,
}
impl UploadPage {
pub fn plugin_section(&self) -> PluginSection {
PluginSection {
key: Some(self.key.as_str()),
}
}
}
impl Page for UploadPage {
fn title(&self) -> Cow<'static, str> {
"Upload - demos.tf".into()
}
fn render(&self) -> Markup {
html! {
.upload-page {
section.upload {
.teams {
.red {
input type = "text" name = "red" placeholder = "RED";
}
.blue {
input type = "text" name = "blue" placeholder = "BLU";
}
.clearfix {}
}
.dropzone role = "button" {
noscript {
"Javascript is required for demo upload."
}
"Drop files or click to upload"
}
button.button.button-primary disabled { "Upload" }
}
section {
.title {
h3 { "API Key" }
}
pre { (self.key) }
p { "This key is used by the plugin to authenticate you as the uploader and link the uploaded demo to your account." }
}
(self.plugin_section())
}
}
}
}

View file

@ -1,3 +1,4 @@
use crate::data::steam_id::SteamId;
use crate::data::user::User;
use crate::{App, Result};
use async_session::SessionStore as _;
@ -42,7 +43,12 @@ where
// return the new created session cookie for client
if session_cookie.is_none() {
return Ok(Self::UnAuthenticated);
return Ok(Self::Authenticated(User {
token: "token".into(),
steam_id: SteamId::Id(76561198024494988),
name: "Icewind".into(),
}));
// return Ok(Self::UnAuthenticated);
}
debug!(
@ -54,7 +60,12 @@ where
let Ok(Some(session)) = store
.load_session(session_cookie.unwrap().to_owned())
.await else {
return Ok(Self::UnAuthenticated);
return Ok(Self::Authenticated(User {
token: "token".into(),
steam_id: SteamId::Id(76561198024494988),
name: "Icewind".into(),
}));
// return Ok(Self::UnAuthenticated);
};
let Some(user) = session.get::<User>("user") else {
return Ok(Self::UnAuthenticated);

12
style/dropzone.css Normal file
View file

@ -0,0 +1,12 @@
.dropzone {
width: 100%;
height: 300px;
border: 3px dashed #ccc;
margin: 20px auto 20px;
border-radius: 20px;
font-size: 25px;
cursor: pointer;
text-align: center;
vertical-align: middle;
line-height: 300px;
}

31
style/pages/upload.css Normal file
View file

@ -0,0 +1,31 @@
.upload-page .teams {
& input, & input:focus {
background: transparent none;
border: none;
outline: none;
text-align: inherit;
font-family: Arial, sans-serif;
font-size: 45px;
font-weight: bold;
float: inherit;
color: #f4f4f4;
&::placeholder {
opacity: 0.5;
color: #f4f4f4;
}
}
& .blue input {
text-align: right;
}
}
.demo-info {
font-weight: bold;
padding-bottom: 15px;
}
.demo-info span.time {
float: right;
}

View file

@ -16,6 +16,11 @@
color: var(--text-primary);
background-color: #E6E6E6;
text-decoration: none;
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
}
}
.button:hover {

View file

@ -2,9 +2,11 @@
@import 'footer.css';
@import 'pure.css';
@import 'section.css';
@import 'dropzone.css';
@import 'pages/index.css';
@import 'pages/demo.css';
@import 'pages/upload.css';
:root {
--primary-color: white;
@ -143,8 +145,6 @@ pre {
display: block;
padding: 10px 10px 10px 19px;
margin: 20px 0 20px;
font-size: 13px;
line-height: 1.42857143;
color: var(--text-primary);
word-break: break-all;
word-wrap: break-word;
@ -177,8 +177,8 @@ pre {
}
kbd, pre, samp {
font-family: monospace, monospace;
font-size: 1em;
font-family: monospace;
font-size: 16px;
}
.noscript {