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"] }
|
clap = { version = "4.5.7", features = ["derive"] }
|
||||||
confik = { version = "0.12", optional = true, features = ["camino"] }
|
confik = { version = "0.12", optional = true, features = ["camino"] }
|
||||||
console_error_panic_hook = "0.1"
|
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"
|
http = "1"
|
||||||
leptos = { version = "0.7.4", features = ["tracing"] }
|
leptos = { version = "0.7.4", features = ["tracing"] }
|
||||||
leptos_axum = { version = "0.7", optional = true }
|
leptos_axum = { version = "0.7", optional = true }
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use crate::codec::ron::RonCodec;
|
||||||
|
use crate::models;
|
||||||
use crate::pages;
|
use crate::pages;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::components::*;
|
use leptos_router::components::*;
|
||||||
use leptos_router::path;
|
use leptos_router::path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||||
use leptos_meta::MetaTags;
|
use leptos_meta::MetaTags;
|
||||||
@@ -39,13 +42,67 @@ pub fn App() -> impl leptos::IntoView {
|
|||||||
<Title text="Ascend" />
|
<Title text="Ascend" />
|
||||||
|
|
||||||
<Router>
|
<Router>
|
||||||
<main>
|
<Routes fallback=|| "Not found">
|
||||||
<Routes fallback=|| "Not found">
|
<Route path=path!("/") view=Home />
|
||||||
<Route path=path!("/") view=pages::wall::Wall />
|
<Route path=path!("/wall/:id") view=pages::wall::Wall />
|
||||||
<Route path=path!("/wall/edit") view=pages::edit_wall::EditWall />
|
<Route path=path!("/wall/:id/edit") view=pages::edit_wall::EditWall />
|
||||||
<Route path=path!("/wall/routes") view=pages::routes::Routes />
|
<Route path=path!("/wall/:id/routes") view=pages::routes::Routes />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
|
||||||
</Router>
|
</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 v1::Image;
|
||||||
pub use v2::Method;
|
pub use v2::Method;
|
||||||
pub use v2::Problem;
|
pub use v2::Problem;
|
||||||
pub use v2::ProblemId;
|
pub use v2::ProblemUid;
|
||||||
pub use v2::Root;
|
pub use v2::Root;
|
||||||
pub use v2::Wall;
|
pub use v2::Wall;
|
||||||
pub use v2::WallId;
|
pub use v2::WallUid;
|
||||||
|
|
||||||
pub mod v2 {
|
pub mod v2 {
|
||||||
use super::v1;
|
use super::v1;
|
||||||
@@ -20,21 +20,21 @@ pub mod v2 {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
pub walls: BTreeSet<WallId>,
|
pub walls: BTreeSet<WallUid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Wall {
|
pub struct Wall {
|
||||||
pub uid: WallId,
|
pub uid: WallUid,
|
||||||
pub rows: u64,
|
pub rows: u64,
|
||||||
pub cols: u64,
|
pub cols: u64,
|
||||||
pub holds: BTreeMap<v1::HoldPosition, v1::Hold>,
|
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)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, derive_more::FromStr, derive_more::Display)]
|
||||||
pub struct WallId(pub uuid::Uuid);
|
pub struct WallUid(pub uuid::Uuid);
|
||||||
impl WallId {
|
impl WallUid {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(uuid::Uuid::new_v4())
|
Self(uuid::Uuid::new_v4())
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ pub mod v2 {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Problem {
|
pub struct Problem {
|
||||||
pub uid: ProblemId,
|
pub uid: ProblemUid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub set_by: String,
|
pub set_by: String,
|
||||||
pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
pub holds: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
||||||
@@ -51,8 +51,8 @@ pub mod v2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||||
pub struct ProblemId(pub uuid::Uuid);
|
pub struct ProblemUid(pub uuid::Uuid);
|
||||||
impl ProblemId {
|
impl ProblemUid {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(uuid::Uuid::new_v4())
|
Self(uuid::Uuid::new_v4())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,32 @@ use crate::components::header::HeaderItems;
|
|||||||
use crate::components::header::StyledHeader;
|
use crate::components::header::StyledHeader;
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::HoldRole;
|
use crate::models::HoldRole;
|
||||||
|
use leptos::Params;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos::reactive::graph::ReactiveNode;
|
use leptos::reactive::graph::ReactiveNode;
|
||||||
use serde::Deserialize;
|
use leptos_router::params::Params;
|
||||||
use serde::Serialize;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Params, PartialEq, Clone)]
|
||||||
|
struct WallParams {
|
||||||
|
id: Option<models::WallUid>,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Wall() -> impl leptos::IntoView {
|
pub fn Wall() -> impl leptos::IntoView {
|
||||||
let load = async move {
|
let params = leptos_router::hooks::use_params::<WallParams>();
|
||||||
// TODO: What to do about this unwrap?
|
|
||||||
load_initial_data().await.unwrap()
|
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 {
|
let header_items = HeaderItems {
|
||||||
left: vec![],
|
left: vec![],
|
||||||
@@ -38,20 +51,27 @@ pub fn Wall() -> impl leptos::IntoView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
leptos::view! {
|
leptos::view! {
|
||||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||||
<StyledHeader items=header_items />
|
<StyledHeader items=header_items />
|
||||||
|
|
||||||
<div class="m-2">
|
<div class="m-2">
|
||||||
<Await future=load let:data>
|
<Suspense fallback=move || view! {<p>"Loading..."</p>}>
|
||||||
<Ready data=data.deref().to_owned() />
|
{move || Suspend::new(async move{
|
||||||
</Await>
|
let wall: Option<Option<models::Wall>> = wall.get();
|
||||||
|
wall.map(|wall|{let wall = wall.unwrap();
|
||||||
|
view! {
|
||||||
|
<Ready wall />
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})}
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Ready(data: InitialData) -> impl leptos::IntoView {
|
fn Ready(wall: models::Wall) -> impl leptos::IntoView {
|
||||||
tracing::debug!("ready");
|
tracing::debug!("ready");
|
||||||
|
|
||||||
let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
|
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![];
|
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 = move || current_problem.get().and_then(|problem| problem.holds.get(&hold_position).copied());
|
||||||
let role = Signal::derive(role);
|
let role = Signal::derive(role);
|
||||||
|
|
||||||
@@ -73,7 +93,7 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
|
|||||||
cells.push(cell);
|
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! {
|
view! {
|
||||||
<div class="grid grid-cols-[auto,1fr] gap-8">
|
<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> }
|
view! { <div class=class>{img}</div> }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct InitialData {
|
|
||||||
wall: models::Wall,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
#[tracing::instrument(skip_all, err)]
|
#[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>>();
|
let db = expect_context::<Arc<redb::Database>>();
|
||||||
|
|
||||||
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||||
enum Error {
|
enum Error {
|
||||||
#[display("Wall not found: {_0:?}")]
|
#[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> {
|
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??;
|
.await??;
|
||||||
|
|
||||||
Ok(RonCodec::new(InitialData { wall }))
|
Ok(RonCodec::new(wall))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
#[tracing::instrument(skip_all, err)]
|
#[tracing::instrument(skip_all, err)]
|
||||||
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
|
async fn get_random_problem() -> Result<RonCodec<Option<models::Problem>>, ServerFnError> {
|
||||||
todo!()
|
Ok(RonCodec::new(None))
|
||||||
|
|
||||||
// use rand::seq::IteratorRandom;
|
// use rand::seq::IteratorRandom;
|
||||||
|
|
||||||
// let state = expect_context::<State>();
|
// let state = expect_context::<State>();
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ async fn serve(cli: Cli) -> Result<(), Error> {
|
|||||||
&leptos_options,
|
&leptos_options,
|
||||||
routes,
|
routes,
|
||||||
move || {
|
move || {
|
||||||
leptos::prelude::provide_context(Arc::clone(&db));
|
leptos::prelude::provide_context::<Arc<redb::Database>>(Arc::clone(&db));
|
||||||
leptos::prelude::provide_context(config.clone())
|
leptos::prelude::provide_context::<Config>(config.clone())
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let leptos_options = leptos_options.clone();
|
let leptos_options = leptos_options.clone();
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ pub mod v2 {
|
|||||||
pub const VERSION: u64 = 2;
|
pub const VERSION: u64 = 2;
|
||||||
|
|
||||||
pub const TABLE_ROOT: TableDefinition<(), Bincode<models::v2::Root>> = TableDefinition::new("root");
|
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_WALLS: TableDefinition<Bincode<models::v2::WallUid>, 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_PROBLEMS: TableDefinition<Bincode<(models::v2::WallUid, models::v2::ProblemUid)>, Bincode<models::v2::Problem>> =
|
||||||
TableDefinition::new("problems");
|
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_table_v1 = txn.open_table(db::v1::TABLE_ROOT)?;
|
||||||
let root_v1 = root_table_v1.get(())?.unwrap().value();
|
let root_v1 = root_table_v1.get(())?.unwrap().value();
|
||||||
|
drop(root_table_v1);
|
||||||
txn.delete_table(db::v1::TABLE_ROOT)?;
|
txn.delete_table(db::v1::TABLE_ROOT)?;
|
||||||
|
|
||||||
let models::v1::PersistentState { version: _, wall, problems } = root_v1;
|
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);
|
drop(problems);
|
||||||
|
|
||||||
let mut walls = BTreeSet::new();
|
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
|
let holds = wall
|
||||||
.holds
|
.holds
|
||||||
.into_iter()
|
.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)?;
|
let mut root_table_v2 = txn.open_table(db::v2::TABLE_ROOT)?;
|
||||||
root_table_v2.insert((), root_v2)?;
|
root_table_v2.insert((), root_v2)?;
|
||||||
|
drop(root_table_v2);
|
||||||
|
|
||||||
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
|
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
|
||||||
walls_table.insert(wall_v2.uid, wall_v2)?;
|
walls_table.insert(wall_v2.uid, wall_v2)?;
|
||||||
|
drop(walls_table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
txn.commit()?;
|
txn.commit()?;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[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;
|
use moonboard_parser::mini_moonboard;
|
||||||
|
|
||||||
let mut problems = Vec::new();
|
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,
|
mini_moonboard::Method::FootlessPlusKickboard => models::Method::FootlessPlusKickboard,
|
||||||
};
|
};
|
||||||
|
|
||||||
let problem_id = models::ProblemId::new();
|
let problem_id = models::ProblemUid::new();
|
||||||
|
|
||||||
let problem = models::Problem {
|
let problem = models::Problem {
|
||||||
uid: problem_id,
|
uid: problem_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user