wip
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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 />
|
||||
<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>
|
||||
</main>
|
||||
</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))
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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![],
|
||||
@@ -42,16 +55,23 @@ pub fn Wall() -> impl leptos::IntoView {
|
||||
<StyledHeader items=header_items />
|
||||
|
||||
<div class="m-2">
|
||||
<Await future=load let:data>
|
||||
<Ready data=data.deref().to_owned() />
|
||||
</Await>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
#[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>();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user