This commit is contained in:
2025-02-04 01:17:27 +01:00
parent 83ad4ca784
commit 51ada6c9bd
8 changed files with 134 additions and 53 deletions

View File

@@ -15,7 +15,12 @@ chrono = { version = "0.4.39", features = ["now", "serde"] }
clap = { version = "4.5.7", features = ["derive"] }
confik = { version = "0.12", optional = true, features = ["camino"] }
console_error_panic_hook = "0.1"
derive_more = { version = "1", features = ["display", "error", "from"] }
derive_more = { version = "1", features = [
"display",
"error",
"from",
"from_str",
] }
http = "1"
leptos = { version = "0.7.4", features = ["tracing"] }
leptos_axum = { version = "0.7", optional = true }

View File

@@ -1,7 +1,10 @@
use crate::codec::ron::RonCodec;
use crate::models;
use crate::pages;
use leptos::prelude::*;
use leptos_router::components::*;
use leptos_router::path;
use std::sync::Arc;
pub fn shell(options: LeptosOptions) -> impl IntoView {
use leptos_meta::MetaTags;
@@ -39,13 +42,67 @@ pub fn App() -> impl leptos::IntoView {
<Title text="Ascend" />
<Router>
<main>
<Routes fallback=|| "Not found">
<Route path=path!("/") view=pages::wall::Wall />
<Route path=path!("/wall/edit") view=pages::edit_wall::EditWall />
<Route path=path!("/wall/routes") view=pages::routes::Routes />
</Routes>
</main>
<Routes fallback=|| "Not found">
<Route path=path!("/") view=Home />
<Route path=path!("/wall/:id") view=pages::wall::Wall />
<Route path=path!("/wall/:id/edit") view=pages::edit_wall::EditWall />
<Route path=path!("/wall/:id/routes") view=pages::routes::Routes />
</Routes>
</Router>
}
}
#[component]
pub fn Home() -> impl leptos::IntoView {
// TODO: show cards with walls, and a "new wall" button
tracing::debug!("Rendering home component");
let action = Action::new(|()| async move {
tracing::debug!("running action");
let walls = get_walls().await.unwrap().into_inner();
let wall = walls.first();
if let Some(wall) = wall {
let navigate = leptos_router::hooks::use_navigate();
let url = format!("/wall/{}", wall.uid);
navigate(&url, Default::default());
}
});
tracing::debug!("dispatching action...");
action.dispatch(());
tracing::debug!("dispatched action");
leptos::view! {}
}
#[server]
#[tracing::instrument(skip_all, err)]
async fn get_walls() -> Result<RonCodec<Vec<models::Wall>>, ServerFnError> {
use redb::ReadableTable;
tracing::debug!("get walls");
let db = expect_context::<Arc<redb::Database>>();
tracing::debug!("got db from context");
let walls = tokio::task::spawn_blocking(move || -> Result<Vec<models::Wall>, ServerFnError> {
tracing::debug!("beginning read transaction");
let read_txn = db.begin_read()?;
tracing::debug!("opening table");
let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?;
tracing::debug!("opened table");
let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?;
tracing::debug!("got walls {walls:?}");
Ok(walls)
})
.await??;
Ok(RonCodec::new(walls))
}

View File

@@ -6,10 +6,10 @@ pub use v1::HoldRole;
pub use v1::Image;
pub use v2::Method;
pub use v2::Problem;
pub use v2::ProblemId;
pub use v2::ProblemUid;
pub use v2::Root;
pub use v2::Wall;
pub use v2::WallId;
pub use v2::WallUid;
pub mod v2 {
use super::v1;
@@ -20,21 +20,21 @@ pub mod v2 {
#[derive(Serialize, Deserialize, Debug)]
pub struct Root {
pub walls: BTreeSet<WallId>,
pub walls: BTreeSet<WallUid>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Wall {
pub uid: WallId,
pub uid: WallUid,
pub rows: u64,
pub cols: u64,
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
pub problems: BTreeSet<ProblemId>,
pub problems: BTreeSet<ProblemUid>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct WallId(pub uuid::Uuid);
impl WallId {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, derive_more::FromStr, derive_more::Display)]
pub struct WallUid(pub uuid::Uuid);
impl WallUid {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4())
}
@@ -42,7 +42,7 @@ pub mod v2 {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Problem {
pub uid: ProblemId,
pub uid: ProblemUid,
pub name: String,
pub set_by: String,
pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
@@ -51,8 +51,8 @@ pub mod v2 {
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct ProblemId(pub uuid::Uuid);
impl ProblemId {
pub struct ProblemUid(pub uuid::Uuid);
impl ProblemUid {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4())
}

View File

@@ -5,19 +5,32 @@ use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader;
use crate::models;
use crate::models::HoldRole;
use leptos::Params;
use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode;
use serde::Deserialize;
use serde::Serialize;
use std::ops::Deref;
use leptos_router::params::Params;
use std::sync::Arc;
#[derive(Params, PartialEq, Clone)]
struct WallParams {
id: Option<models::WallUid>,
}
#[component]
pub fn Wall() -> impl leptos::IntoView {
let load = async move {
// TODO: What to do about this unwrap?
load_initial_data().await.unwrap()
};
let params = leptos_router::hooks::use_params::<WallParams>();
let wall = Resource::new(
move || params.get().unwrap().id,
move |wall_id| async move {
if let Some(wall_id) = wall_id {
let wall = get_wall(wall_id).await.unwrap().into_inner();
Some(wall)
} else {
None
}
},
);
let header_items = HeaderItems {
left: vec![],
@@ -38,20 +51,27 @@ pub fn Wall() -> impl leptos::IntoView {
};
leptos::view! {
<div class="min-w-screen min-h-screen bg-slate-900">
<StyledHeader items=header_items />
<div class="min-w-screen min-h-screen bg-slate-900">
<StyledHeader items=header_items />
<div class="m-2">
<Await future=load let:data>
<Ready data=data.deref().to_owned() />
</Await>
<div class="m-2">
<Suspense fallback=move || view! {<p>"Loading..."</p>}>
{move || Suspend::new(async move{
let wall: Option<Option<models::Wall>> = wall.get();
wall.map(|wall|{let wall = wall.unwrap();
view! {
<Ready wall />
}
})
})}
</Suspense>
</div>
</div>
</div>
}
}
}
#[component]
fn Ready(data: InitialData) -> impl leptos::IntoView {
fn Ready(wall: models::Wall) -> impl leptos::IntoView {
tracing::debug!("ready");
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
@@ -65,7 +85,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
});
let mut cells = vec![];
for (&hold_position, hold) in &data.wall.holds {
for (&hold_position, hold) in &wall.holds {
let role = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
let role = Signal::derive(role);
@@ -73,7 +93,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
cells.push(cell);
}
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", data.wall.rows, data.wall.cols);
let grid_classes = format!("grid grid-rows-{} grid-cols-{} gap-3", wall.rows, wall.cols);
view! {
<div class="grid grid-cols-[auto,1fr] gap-8">
@@ -120,20 +140,15 @@ fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> imp
view! { <div class=class>{img}</div> }
}
#[derive(Serialize, Deserialize, Clone)]
pub struct InitialData {
wall: models::Wall,
}
#[server]
#[tracing::instrument(skip_all, err)]
async fn load_initial_data(wall_id: models::WallId) -> Result<RonCodec<InitialData>, ServerFnError> {
async fn get_wall(wall_id: models::WallUid) -> Result<RonCodec<models::Wall>, ServerFnError> {
let db = expect_context::<Arc<redb::Database>>();
#[derive(Debug, derive_more::Error, derive_more::Display)]
enum Error {
#[display("Wall not found: {_0:?}")]
NotFound(#[error(not(source))] models::WallId),
NotFound(#[error(not(source))] models::WallUid),
}
let wall = tokio::task::spawn_blocking(move || -> Result<models::Wall, ServerFnError> {
@@ -146,13 +161,14 @@ async fn load_initial_data(wall_id: models::WallId) -> Result<RonCodec<InitialDa
})
.await??;
Ok(RonCodec::new(InitialData { wall }))
Ok(RonCodec::new(wall))
}
#[server]
#[tracing::instrument(skip_all, err)]
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
todo!()
Ok(RonCodec::new(None))
// use rand::seq::IteratorRandom;
// let state = expect_context::<State>();

View File

@@ -73,8 +73,8 @@ async fn serve(cli: Cli) -> Result<(), Error> {
&leptos_options,
routes,
move || {
leptos::prelude::provide_context(Arc::clone(&db));
leptos::prelude::provide_context(config.clone())
leptos::prelude::provide_context::<Arc<redb::Database>>(Arc::clone(&db));
leptos::prelude::provide_context::<Config>(config.clone())
},
{
let leptos_options = leptos_options.clone();

View File

@@ -51,8 +51,8 @@ pub mod v2 {
pub const VERSION: u64 = 2;
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v2::Root>> = TableDefinition::new("root");
pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallId>, Bincode<models::v2::Wall>> = TableDefinition::new("walls");
pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallId, models::v2::ProblemId)>, Bincode<models::v2::Problem>> =
pub const TABLE_WALLS: TableDefinition<Bincode<models::v2::WallUid>, Bincode<models::v2::Wall>> = TableDefinition::new("walls");
pub const TABLE_PROBLEMS: TableDefinition<Bincode<(models::v2::WallUid, models::v2::ProblemUid)>, Bincode<models::v2::Problem>> =
TableDefinition::new("problems");
}

View File

@@ -110,6 +110,7 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>>
let root_table_v1 = txn.open_table(db::v1::TABLE_ROOT)?;
let root_v1 = root_table_v1.get(())?.unwrap().value();
drop(root_table_v1);
txn.delete_table(db::v1::TABLE_ROOT)?;
let models::v1::PersistentState { version: _, wall, problems } = root_v1;
@@ -118,7 +119,7 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>>
drop(problems);
let mut walls = BTreeSet::new();
let wall_uid = models::v2::WallId(uuid::Uuid::new_v4());
let wall_uid = models::v2::WallUid(uuid::Uuid::new_v4());
let holds = wall
.holds
.into_iter()
@@ -152,9 +153,11 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>>
let mut root_table_v2 = txn.open_table(db::v2::TABLE_ROOT)?;
root_table_v2.insert((), root_v2)?;
drop(root_table_v2);
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
walls_table.insert(wall_v2.uid, wall_v2)?;
drop(walls_table);
}
}
txn.commit()?;

View File

@@ -11,7 +11,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
#[tracing::instrument(skip_all)]
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallId) -> Result<(), Error> {
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallUid) -> Result<(), Error> {
use moonboard_parser::mini_moonboard;
let mut problems = Vec::new();
@@ -48,7 +48,7 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Data
mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard,
};
let problem_id = models::ProblemId::new();
let problem_id = models::ProblemUid::new();
let problem = models::Problem {
uid: problem_id,