This commit is contained in:
2025-02-18 00:28:56 +01:00
parent aba6b4a329
commit bc7a6908b2
4 changed files with 98 additions and 52 deletions

View File

@@ -1,3 +1,5 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::components::StyledHeader; use crate::components::StyledHeader;
use crate::components::header::HeaderItem; use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems; use crate::components::header::HeaderItems;
@@ -12,9 +14,6 @@ use leptos_router::params::Params;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use server_fn::codec::Cbor; use server_fn::codec::Cbor;
use std::collections::BTreeMap;
use std::io::Cursor;
use std::path::Path;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::FileList; use web_sys::FileList;
@@ -110,7 +109,7 @@ fn Hold(wall_uid: models::WallUid, hold: models::Hold) -> impl IntoView {
let hold = Signal::derive(move || { let hold = Signal::derive(move || {
let refreshed = upload.value().get().map(Result::unwrap); let refreshed = upload.value().get().map(Result::unwrap);
refreshed.unwrap_or(hold.clone()) refreshed.map(RonEncoded::into_inner).unwrap_or(hold.clone())
}); });
// Callback to handle file selection // Callback to handle file selection
@@ -176,17 +175,28 @@ pub struct Image {
file_contents: Vec<u8>, file_contents: Vec<u8>,
} }
#[server(name = SetImage, input = Cbor)] #[server(
name = SetImage,
input = Cbor,
output = Ron,
)]
#[tracing::instrument(skip(image), err)] #[tracing::instrument(skip(image), err)]
async fn set_image(wall_uid: WallUid, hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> { async fn set_image(wall_uid: WallUid, hold_position: HoldPosition, image: Image) -> Result<RonEncoded<models::Hold>, ServerFnError> {
use image::ImageDecoder;
use std::collections::BTreeMap;
use std::path::Path;
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len()); tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());
let db = expect_context::<crate::server::db::Database>(); let db = expect_context::<crate::server::db::Database>();
let image = tokio::task::spawn_blocking(move || -> Result<models::Image, ServerFnError> { let image = tokio::task::spawn_blocking(move || -> Result<models::Image, ServerFnError> {
let img = image::ImageReader::new(Cursor::new(image.file_contents)) let mut decoder = image::ImageReader::new(std::io::Cursor::new(image.file_contents))
.with_guessed_format()? .with_guessed_format()?
.decode()?; .into_decoder()?;
let orientation = decoder.orientation()?;
let mut img = image::DynamicImage::from_decoder(decoder)?;
img.apply_orientation(orientation);
let holds_dir = Path::new("datastore/public/holds"); let holds_dir = Path::new("datastore/public/holds");
std::fs::create_dir_all(holds_dir)?; std::fs::create_dir_all(holds_dir)?;
@@ -200,7 +210,7 @@ async fn set_image(wall_uid: WallUid, hold_position: HoldPosition, image: Image)
let filename = format!("hold_row{}_col{}_{width}x{height}_{uid}.webp", hold_position.row, hold_position.col); let filename = format!("hold_row{}_col{}_{width}x{height}_{uid}.webp", hold_position.row, hold_position.col);
let path = holds_dir.join(&filename); let path = holds_dir.join(&filename);
let mut file = std::fs::File::open(&path)?; let mut file = std::fs::OpenOptions::new().write(true).append(false).create_new(true).open(&path)?;
resized.write_to(&mut file, image::ImageFormat::WebP)?; resized.write_to(&mut file, image::ImageFormat::WebP)?;
let res = models::ImageResolution { let res = models::ImageResolution {
@@ -216,34 +226,20 @@ async fn set_image(wall_uid: WallUid, hold_position: HoldPosition, image: Image)
}) })
.await??; .await??;
db.write(move |txn| { let hold = db
.write(move |txn| {
use redb::ReadableTable; use redb::ReadableTable;
let mut walls = txn.open_table(crate::server::db::current::TABLE_WALLS)?; let mut walls = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
let mut wall = walls.get(wall_uid)?.expect("todo").value(); let mut wall = walls.get(wall_uid)?.expect("todo").value();
if let Some(hold) = wall.holds.get_mut(&hold_position) { let hold = wall.holds.get_mut(&hold_position).expect("hold");
hold.image = Some(image); hold.image = Some(image);
} let hold = hold.clone();
walls.insert(wall_uid, wall)?;
walls.insert(wall_uid, wall); Ok(hold)
Ok(())
}) })
.await?; .await?;
// let state = expect_context::<State>(); Ok(RonEncoded::new(hold))
// state
// .persistent
// .update(|s| {
// if let Some(hold) = s.wall.holds.get_mut(&hold_position) {
// hold.image = Some(models::Image { filename });
// }
// })
// .await?;
// // Return updated hold
// let hold = state.persistent.with(|s| s.wall.holds.get(&hold_position).cloned().unwrap()).await;
let hold = todo!();
Ok(hold)
} }

View File

@@ -1,9 +1,12 @@
use super::db; use super::db;
use super::db::Database; use super::db::Database;
use crate::models; use crate::models;
use image::ImageDecoder;
use redb::ReadableTable; use redb::ReadableTable;
use redb::ReadableTableMetadata; use redb::ReadableTableMetadata;
use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use type_toppings::ResultExt; use type_toppings::ResultExt;
@@ -123,22 +126,66 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>>
let holds = wall let holds = wall
.holds .holds
.into_iter() .into_iter()
.map(|(hold_position, hold)| { .map(|(hold_position, hold)| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
( let image = hold
.image
.map(|i| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
let holds_dir = Path::new("datastore/public/holds");
let p = holds_dir.join(i.filename);
tracing::info!("reading {}", p.display());
let file_contents = std::fs::read(p)?;
let mut decoder = image::ImageReader::new(std::io::Cursor::new(file_contents))
.with_guessed_format()?
.into_decoder()?;
let orientation = decoder.orientation()?;
let mut img = image::DynamicImage::from_decoder(decoder)?;
img.apply_orientation(orientation);
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 {
tracing::info!("resizing to {width}x{height}");
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);
tracing::info!("opening {}", path.display());
let mut file = std::fs::OpenOptions::new().write(true).append(false).create_new(true).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 });
}
Ok(models::Image { uid, resolutions })
})
.transpose()?;
Ok((
models::v1::HoldPosition { models::v1::HoldPosition {
row: hold_position.row, row: hold_position.row,
col: hold_position.col, col: hold_position.col,
}, },
models::v1::Hold { models::v2::Hold {
position: models::v1::HoldPosition { position: models::v1::HoldPosition {
row: hold.position.row, row: hold.position.row,
col: hold.position.col, col: hold.position.col,
}, },
image: hold.image.map(|i| models::v1::Image { filename: i.filename }), image,
}, },
) ))
}) })
.collect(); .collect::<Result<_, _>>()
.unwrap();
let wall_v2 = models::v2::Wall { let wall_v2 = models::v2::Wall {
uid: wall_uid, uid: wall_uid,

View File

@@ -1,7 +1,6 @@
use crate::codec::ron::Ron; use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
use crate::models; use crate::models;
use leptos::prelude::expect_context;
use leptos::server; use leptos::server;
use server_fn::ServerFnError; use server_fn::ServerFnError;
@@ -13,6 +12,7 @@ use server_fn::ServerFnError;
#[tracing::instrument(skip_all, err(Debug))] #[tracing::instrument(skip_all, err(Debug))]
pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> { pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use leptos::prelude::expect_context;
use redb::ReadableTable; use redb::ReadableTable;
tracing::debug!("Enter"); tracing::debug!("Enter");
@@ -40,6 +40,7 @@ pub async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError>
pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> { pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context;
tracing::debug!("Enter"); tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display)] #[derive(Debug, derive_more::Error, derive_more::Display)]
@@ -76,6 +77,7 @@ pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<mod
pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<RonEncoded<Vec<models::Problem>>, ServerFnError> { pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<RonEncoded<Vec<models::Problem>>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context;
tracing::debug!("Enter"); tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)] #[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
@@ -134,6 +136,7 @@ pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<R
pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<models::Problem>, ServerFnError> { pub(crate) async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<models::Problem>, ServerFnError> {
use crate::server::db::Database; use crate::server::db::Database;
use crate::server::db::DatabaseOperationError; use crate::server::db::DatabaseOperationError;
use leptos::prelude::expect_context;
tracing::debug!("Enter"); tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display)] #[derive(Debug, derive_more::Error, derive_more::Display)]

View File

@@ -11,17 +11,17 @@ fmt:
serve: serve:
RUST_BACKTRACE=1 cargo leptos watch -- serve RUST_BACKTRACE=1 cargo leptos watch -- serve
build-release: # build-release:
rm -rf dist # rm -rf dist
mkdir dist # mkdir dist
cargo leptos build --release -vv # cargo leptos build --release -vv
cp target/release/ascend dist/ # cp target/release/ascend dist/
cp -r target/site dist/ # cp -r target/site dist/
run-release: # run-release:
#!/usr/bin/env bash # #!/usr/bin/env bash
cd dist # cd dist
LEPTOS_SITE_ROOT="site" LEPTOS_SITE_ADDR="127.0.0.1:1337" ./ascend serve # LEPTOS_SITE_ROOT="site" LEPTOS_SITE_ADDR="127.0.0.1:1337" ./ascend serve
reset-state: reset-state:
cargo run --features ssr -- reset-state cargo run --features ssr -- reset-state