games / reflections on Minecraft
This commit is contained in:
parent
161fb6a442
commit
93c24859d0
51 changed files with 743 additions and 55 deletions
|
@ -32,4 +32,5 @@ tracing.workspace = true
|
|||
tracing-chrome = "0.7.2"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
ulid = "1.0.0"
|
||||
webp = "0.3.0"
|
||||
xmlparser = "0.13.6"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::sync::Arc;
|
||||
use std::{io::Cursor, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
debug_handler,
|
||||
extract::{Query, State},
|
||||
extract::{DefaultBodyLimit, Query, State},
|
||||
response::IntoResponse,
|
||||
routing::post,
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
dirs::Dirs,
|
||||
|
@ -18,10 +19,11 @@ use crate::{
|
|||
pub fn router<S>(dirs: Arc<Dirs>) -> Router<S> {
|
||||
Router::new()
|
||||
.route("/", post(picture_upload))
|
||||
.layer(DefaultBodyLimit::disable())
|
||||
.with_state(dirs)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PictureUpload {
|
||||
label: String,
|
||||
format: String,
|
||||
|
@ -31,6 +33,16 @@ struct PictureUpload {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
||||
enum Compression {
|
||||
Lossless,
|
||||
GameScreenshot,
|
||||
}
|
||||
|
||||
impl Compression {
|
||||
pub fn output_format(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Compression::Lossless => None,
|
||||
Compression::GameScreenshot => Some("image/webp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -40,12 +52,48 @@ enum Response {
|
|||
Error(String),
|
||||
}
|
||||
|
||||
fn compress(image_data: &[u8], compression: Compression) -> anyhow::Result<Vec<u8>> {
|
||||
match compression {
|
||||
Compression::Lossless => Ok(image_data.to_vec()),
|
||||
Compression::GameScreenshot => {
|
||||
info!("decompressing original image");
|
||||
let decompressed = image::ImageReader::new(Cursor::new(image_data))
|
||||
.with_guessed_format()?
|
||||
.decode()?
|
||||
.to_rgba8();
|
||||
|
||||
info!("compressing to webp");
|
||||
let compressed = webp::Encoder::new(
|
||||
&decompressed,
|
||||
webp::PixelLayout::Rgba,
|
||||
decompressed.width(),
|
||||
decompressed.height(),
|
||||
)
|
||||
.encode(85.0)
|
||||
.to_vec();
|
||||
Ok(compressed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_compressed(
|
||||
image_data: &[u8],
|
||||
compression: Compression,
|
||||
edit_path: EditPath,
|
||||
) -> anyhow::Result<()> {
|
||||
let compressed = compress(image_data, compression)?;
|
||||
Edit::Write(edit_path, compressed).apply().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
async fn picture_upload(
|
||||
State(dirs): State<Arc<Dirs>>,
|
||||
Query(mut params): Query<PictureUpload>,
|
||||
image: Bytes,
|
||||
) -> impl IntoResponse {
|
||||
info!(?params, "uploading picture");
|
||||
|
||||
let ulid = ulid::Generator::new()
|
||||
.generate_with_source(&mut rand::thread_rng())
|
||||
.expect("failed to generate ulid");
|
||||
|
@ -57,16 +105,15 @@ async fn picture_upload(
|
|||
let file_name = VPathBuf::new(format!(
|
||||
"{ulid}-{}.{}",
|
||||
params.label,
|
||||
get_extension(¶ms.format).unwrap_or("unknown")
|
||||
get_extension(params.compression.output_format().unwrap_or(¶ms.format))
|
||||
.unwrap_or("unknown")
|
||||
));
|
||||
let Some(edit_path) = vfs::query::<EditPath>(&dirs.pic, &file_name) else {
|
||||
return Json(Response::Error(format!("{file_name} is not editable")));
|
||||
};
|
||||
|
||||
let result = match params.compression {
|
||||
Compression::Lossless => Edit::Write(edit_path, image.to_vec()).apply().await,
|
||||
};
|
||||
|
||||
let result = write_compressed(&image, params.compression, edit_path).await;
|
||||
info!(?result, "done processing");
|
||||
Json(match result {
|
||||
Ok(()) => Response::Ulid(ulid.to_string()),
|
||||
Err(error) => Response::Error(error.to_string()),
|
||||
|
|
|
@ -105,12 +105,6 @@ pub struct JavaScript {
|
|||
pub import_roots: Vec<ImportRoot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub enum Markup {
|
||||
Markdown,
|
||||
Djot,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[instrument(name = "Config::autopopulate_emoji", skip(self))]
|
||||
pub fn autopopulate_emoji(&mut self, dir: &dyn Dir) -> anyhow::Result<()> {
|
||||
|
|
|
@ -247,7 +247,7 @@ impl<'a> Writer<'a> {
|
|||
Container::Image(..) => {
|
||||
self.img_alt_text += 1;
|
||||
if self.img_alt_text == 1 {
|
||||
out.push_str(r#"<img class="pic""#);
|
||||
out.push_str(r#"<img class="pic" loading="lazy""#);
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue