wip
This commit is contained in:
@@ -22,6 +22,7 @@ derive_more = { version = "1", features = [
|
||||
"from_str",
|
||||
] }
|
||||
http = "1"
|
||||
image = { version = "0.25", optional = true }
|
||||
leptos = { version = "0.7.4", features = ["tracing"] }
|
||||
leptos_axum = { version = "0.7", optional = true }
|
||||
leptos_meta = { version = "0.7" }
|
||||
@@ -58,6 +59,7 @@ hydrate = ["leptos/hydrate", "getrandom/wasm_js", "uuid/js"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:redb",
|
||||
"dep:image",
|
||||
"dep:bincode",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! Shared models between server and client code.
|
||||
|
||||
pub use v1::Hold;
|
||||
pub use v1::HoldPosition;
|
||||
pub use v1::HoldRole;
|
||||
pub use v1::Image;
|
||||
pub use v2::Hold;
|
||||
pub use v2::Image;
|
||||
pub use v2::ImageFilename;
|
||||
pub use v2::ImageResolution;
|
||||
pub use v2::ImageUid;
|
||||
pub use v2::Method;
|
||||
pub use v2::Problem;
|
||||
pub use v2::ProblemUid;
|
||||
@@ -32,7 +35,7 @@ pub mod v2 {
|
||||
pub rows: u64,
|
||||
pub cols: u64,
|
||||
|
||||
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
||||
pub holds: BTreeMap<v1::HoldPosition, Hold>,
|
||||
pub problems: BTreeSet<ProblemUid>,
|
||||
}
|
||||
impl Wall {
|
||||
@@ -81,6 +84,46 @@ pub mod v2 {
|
||||
Footless,
|
||||
FootlessPlusKickboard,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Hold {
|
||||
pub position: v1::HoldPosition,
|
||||
pub image: Option<Image>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Image {
|
||||
pub uid: ImageUid,
|
||||
pub resolutions: BTreeMap<ImageResolution, ImageFilename>,
|
||||
}
|
||||
impl Image {
|
||||
pub(crate) fn srcset(&self) -> String {
|
||||
self.resolutions
|
||||
.iter()
|
||||
.map(|(res, filename)| format!("/files/holds/{} {}w", filename.filename, res.width))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ImageResolution {
|
||||
pub width: u64,
|
||||
pub height: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ImageFilename {
|
||||
pub filename: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||
pub struct ImageUid(pub uuid::Uuid);
|
||||
impl ImageUid {
|
||||
pub fn create() -> Self {
|
||||
Self(uuid::Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::components::header::HeaderItem;
|
||||
use crate::components::header::HeaderItems;
|
||||
use crate::models;
|
||||
use crate::models::HoldPosition;
|
||||
use crate::models::WallUid;
|
||||
use leptos::Params;
|
||||
use leptos::ev::Event;
|
||||
use leptos::html::Input;
|
||||
@@ -11,6 +12,9 @@ use leptos_router::params::Params;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use server_fn::codec::Cbor;
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::FileList;
|
||||
@@ -78,7 +82,7 @@ fn Ready(wall: models::Wall) -> impl IntoView {
|
||||
|
||||
let mut holds = vec![];
|
||||
for hold in wall.holds.values().cloned() {
|
||||
holds.push(view! { <Hold hold /> });
|
||||
holds.push(view! { <Hold wall_uid=wall.uid hold /> });
|
||||
}
|
||||
|
||||
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", wall.rows, wall.cols);
|
||||
@@ -92,7 +96,7 @@ fn Ready(wall: models::Wall) -> impl IntoView {
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Hold(hold: models::Hold) -> impl IntoView {
|
||||
fn Hold(wall_uid: models::WallUid, hold: models::Hold) -> impl IntoView {
|
||||
let hold_position = hold.position;
|
||||
let file_input_ref = NodeRef::<Input>::new();
|
||||
|
||||
@@ -132,7 +136,11 @@ fn Hold(hold: models::Hold) -> impl IntoView {
|
||||
file_contents,
|
||||
};
|
||||
|
||||
upload.dispatch(SetImage { hold_position, image });
|
||||
upload.dispatch(SetImage {
|
||||
wall_uid,
|
||||
hold_position,
|
||||
image,
|
||||
});
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
file_reader.set_onload(Some(on_load.as_ref().unchecked_ref()));
|
||||
@@ -141,8 +149,8 @@ fn Hold(hold: models::Hold) -> impl IntoView {
|
||||
|
||||
let img = move || {
|
||||
hold.read().image.as_ref().map(|img| {
|
||||
let src = format!("/files/holds/{}", img.filename);
|
||||
view! { <img class="object-cover w-full h-full" src=src /> }
|
||||
let srcset = img.srcset();
|
||||
view! { <img class="object-cover w-full h-full" srcset=srcset /> }
|
||||
})
|
||||
};
|
||||
|
||||
@@ -170,15 +178,59 @@ pub struct Image {
|
||||
|
||||
#[server(name = SetImage, input = Cbor)]
|
||||
#[tracing::instrument(skip(image), err)]
|
||||
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
|
||||
async fn set_image(wall_uid: WallUid, hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
|
||||
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());
|
||||
|
||||
// TODO: Fix file extension presumption, and possibly use uuid
|
||||
let filename = format!("row{}_col{}.jpg", hold_position.row, hold_position.col);
|
||||
tokio::fs::create_dir_all("datastore/public/holds").await?;
|
||||
tokio::fs::write(format!("datastore/public/holds/{filename}"), image.file_contents).await?;
|
||||
let db = expect_context::<crate::server::db::Database>();
|
||||
|
||||
let image = tokio::task::spawn_blocking(move || -> Result<models::Image, ServerFnError> {
|
||||
let img = image::ImageReader::new(Cursor::new(image.file_contents))
|
||||
.with_guessed_format()?
|
||||
.decode()?;
|
||||
|
||||
let holds_dir = Path::new("datastore/public/holds");
|
||||
std::fs::create_dir_all(holds_dir)?;
|
||||
|
||||
let targets = [(50, 50), (150, 150), (300, 300), (400, 400)];
|
||||
|
||||
let uid = models::ImageUid::create();
|
||||
let mut resolutions = BTreeMap::new();
|
||||
for (width, height) in targets {
|
||||
let resized = img.resize_to_fill(width, height, image::imageops::FilterType::Lanczos3);
|
||||
|
||||
let filename = format!("hold_row{}_col{}_{width}x{height}_{uid}.webp", hold_position.row, hold_position.col);
|
||||
let path = holds_dir.join(&filename);
|
||||
let mut file = std::fs::File::open(&path)?;
|
||||
resized.write_to(&mut file, image::ImageFormat::WebP)?;
|
||||
|
||||
let res = models::ImageResolution {
|
||||
width: width.into(),
|
||||
height: height.into(),
|
||||
};
|
||||
resolutions.insert(res, models::ImageFilename { filename });
|
||||
}
|
||||
|
||||
// TODO: Clean up old image?
|
||||
|
||||
Ok(models::Image { uid, resolutions })
|
||||
})
|
||||
.await??;
|
||||
|
||||
db.write(move |txn| {
|
||||
use redb::ReadableTable;
|
||||
let mut walls = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||
let mut wall = walls.get(wall_uid)?.expect("todo").value();
|
||||
|
||||
if let Some(hold) = wall.holds.get_mut(&hold_position) {
|
||||
hold.image = Some(image);
|
||||
}
|
||||
|
||||
walls.insert(wall_uid, wall);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
todo!()
|
||||
// let state = expect_context::<State>();
|
||||
// state
|
||||
// .persistent
|
||||
@@ -192,5 +244,6 @@ async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::
|
||||
// // Return updated hold
|
||||
// let hold = state.persistent.with(|s| s.wall.holds.get(&hold_position).cloned().unwrap()).await;
|
||||
|
||||
// Ok(hold)
|
||||
let hold = todo!();
|
||||
Ok(hold)
|
||||
}
|
||||
|
||||
@@ -164,8 +164,8 @@ fn Hold(hold: models::Hold, role: Signal<Option<HoldRole>>) -> impl IntoView {
|
||||
};
|
||||
|
||||
let img = hold.image.map(|img| {
|
||||
let src = format!("/files/holds/{}", img.filename);
|
||||
view! { <img class="object-cover w-full h-full" src=src /> }
|
||||
let srcset = img.srcset();
|
||||
view! { <img class="object-cover w-full h-full" srcset=srcset /> }
|
||||
});
|
||||
|
||||
tracing::trace!("view");
|
||||
|
||||
@@ -10,14 +10,12 @@ use server_fn::ServerFnError;
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
// #[tracing::instrument(skip_all, err(Debug))]
|
||||
#[tracing::instrument(skip_all, err(Debug))]
|
||||
pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
use redb::ReadableTable;
|
||||
tracing::debug!("Enter");
|
||||
|
||||
// dbg!(leptos::prelude::Owner::current().map(|o| o.ancestry()));
|
||||
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
let walls = db
|
||||
|
||||
Reference in New Issue
Block a user