Rough out multi-pane layout

- Introduce nested routing for series.
- Rough out add and view routes.
- Add buttons to access new routes.
This commit is contained in:
Thomas Gideon 2024-09-07 09:10:16 -04:00
parent c7e4983e46
commit ddb06d2993
6 changed files with 172 additions and 99 deletions

View file

@ -1,99 +0,0 @@
use crate::error_template::{AppError, ErrorTemplate};
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "ssr")]
use sqlx::FromRow;
use uuid::Uuid;
#[component]
pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
view! {
// injects a stylesheet into the document <head>
// id=leptos means cargo-leptos will hot-reload this stylesheet
<Stylesheet id="leptos" href="/pkg/watch.css"/>
<Link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
// sets the document title
<Title text="Watch"/>
// content for this welcome page
<Router fallback=|| {
let mut outside_errors = Errors::default();
outside_errors.insert_with_default_key(AppError::NotFound);
view! {
<ErrorTemplate outside_errors/>
}
.into_view()
}>
<main class="container">
<Routes>
<Route path="" view=HomePage/>
</Routes>
</main>
</Router>
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "ssr", derive(FromRow))]
struct Series {
id: Uuid,
name: String,
}
#[server]
pub async fn get_all() -> Result<Vec<String>, ServerFnError> {
use sqlx::{Pool, Postgres};
use tokio_stream::StreamExt;
let pool: Pool<Postgres> = expect_context();
let mut r = sqlx::query_as::<_, Series>("select * from series;").fetch(&pool);
let mut found = Vec::new();
while let Some(series) = r.try_next().await? {
found.push(series.name);
}
Ok(found)
}
/// Renders the home page of your application.
#[component]
fn HomePage() -> impl IntoView {
// Creates a reactive value to update the button
let load = create_resource(|| (), |_| async move { get_all().await });
view! {
<h1>"What We're Watching"</h1>
<div class="card">
<Suspense fallback=|| view!{ <div>"Loading..."</div> }>
<ul>
{load().map(format_series)}
</ul>
</Suspense>
</div>
}
}
fn format_series(data: Result<Vec<String>, ServerFnError>) -> Vec<impl IntoView> {
data.unwrap_or_default()
.into_iter()
.map(|d| {
view! {
<li>{d}</li>
}
})
.collect::<Vec<_>>()
}

18
src/app/home.rs Normal file
View file

@ -0,0 +1,18 @@
use super::series::SeriesList;
use leptos::*;
use leptos_router::*;
#[component]
pub fn HomePage() -> impl IntoView {
view! {
<h1 class="mt-3 mb-5">"What We're Watching"</h1>
<div class="row">
<div class="col">
<SeriesList />
</div>
<div class="col">
<Outlet />
</div>
</div>
}
}

53
src/app/mod.rs Normal file
View file

@ -0,0 +1,53 @@
use self::{
home::HomePage,
series::{add::AddSeries, view::ViewSeries},
};
use crate::error_template::{AppError, ErrorTemplate};
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
mod home;
mod series;
#[component]
pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context();
view! {
// injects a stylesheet into the document <head>
// id=leptos means cargo-leptos will hot-reload this stylesheet
<Stylesheet id="leptos" href="/pkg/watch.css"/>
<Link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
// sets the document title
<Title text="Watch"/>
// content for this welcome page
<Router fallback=|| {
let mut outside_errors = Errors::default();
outside_errors.insert_with_default_key(AppError::NotFound);
view! {
<ErrorTemplate outside_errors/>
}
.into_view()
}>
<main class="container">
<Routes>
<Route path="" view=HomePage>
<Route path="add" view=AddSeries />
<Route path="view/:id" view=ViewSeries />
<Route path="" view=|| () />
</Route>
</Routes>
</main>
</Router>
}
}

15
src/app/series/add.rs Normal file
View file

@ -0,0 +1,15 @@
use leptos::*;
#[component]
pub fn AddSeries() -> impl IntoView {
view! {
<div class="card p-3">
<div class="card-heading">
<h2>Add Series</h2>
</div>
<div class="card-body">
To Do
</div>
</div>
}
}

71
src/app/series/mod.rs Normal file
View file

@ -0,0 +1,71 @@
use leptos::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "ssr")]
use sqlx::FromRow;
use uuid::Uuid;
pub mod add;
pub mod view;
#[component]
pub fn SeriesList() -> impl IntoView {
let load = create_resource(|| (), |_| async move { get_all().await });
view! {
<Suspense fallback=|| view!{ <div>"Loading..."</div> }>
<ul class="list-group mb-3">
{load().map(format_series)}
</ul>
</Suspense>
<div>
<A href="add"
attr:class="btn btn-primary"
>
Add Series
</A>
</div>
}
}
fn format_series(data: Result<Vec<Series>, ServerFnError>) -> impl IntoView {
data.unwrap_or_default()
.into_iter()
.map(|d| {
view! {
<li class="list-group-item">
<A href=format!("view/{}", d.id)
attr:class="btn btn-sm btn-secondary"
attr:style="float: right"
>
View Series
</A>
{d.name}
</li>
}
})
.collect_view()
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "ssr", derive(FromRow))]
pub struct Series {
id: Uuid,
name: String,
}
#[server]
pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
use sqlx::{Pool, Postgres};
use tokio_stream::StreamExt;
let pool: Pool<Postgres> = expect_context();
let mut r = sqlx::query_as::<_, Series>("select * from series;").fetch(&pool);
let mut found = Vec::new();
while let Some(series) = r.try_next().await? {
found.push(series);
}
Ok(found)
}

15
src/app/series/view.rs Normal file
View file

@ -0,0 +1,15 @@
use leptos::*;
#[component]
pub fn ViewSeries() -> impl IntoView {
view! {
<div class="card p-3">
<div class="card-heading">
<h2>View Series</h2>
</div>
<div class="card-body">
View Series
</div>
</div>
}
}