Build a view pane
- Use techniques from refactor of user list, to work with suspense and error boundary from the start. - Add in necessary migration and query. - Extract out some common view factories for fallback. - Extract a field display function.
This commit is contained in:
parent
a2c8a15d84
commit
878d3fa59b
6 changed files with 114 additions and 6 deletions
|
@ -1 +1 @@
|
||||||
insert into series (name) values ($1);
|
insert into series (name, source, imdb, wikipedia) values ($1, $2, $3, $4);
|
||||||
|
|
3
sql/select_all_series.sql
Normal file
3
sql/select_all_series.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
select *
|
||||||
|
from series
|
||||||
|
order by updated desc;
|
|
@ -1 +1,3 @@
|
||||||
select * from series;
|
select *
|
||||||
|
from series
|
||||||
|
where id = $1;
|
||||||
|
|
|
@ -58,7 +58,12 @@ pub fn AddSeries() -> impl IntoView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server(AddSeries)]
|
#[server(AddSeries)]
|
||||||
pub async fn add_series(name: String) -> Result<(), ServerFnError> {
|
pub async fn add_series(
|
||||||
|
name: String,
|
||||||
|
source: String,
|
||||||
|
imdb: String,
|
||||||
|
wikipedia: String,
|
||||||
|
) -> Result<(), ServerFnError> {
|
||||||
use crate::db::load_statement;
|
use crate::db::load_statement;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
let pool = expect_context::<Pool<Postgres>>();
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
|
@ -67,6 +72,13 @@ pub async fn add_series(name: String) -> Result<(), ServerFnError> {
|
||||||
sqlx::query(&s)
|
sqlx::query(&s)
|
||||||
.persistent(true)
|
.persistent(true)
|
||||||
.bind(name)
|
.bind(name)
|
||||||
|
.bind(source)
|
||||||
|
.bind(if imdb.is_empty() { None } else { Some(imdb) })
|
||||||
|
.bind(if wikipedia.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(wikipedia)
|
||||||
|
})
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub fn SeriesList() -> impl IntoView {
|
||||||
|_| async move { get_all().await },
|
|_| async move { get_all().await },
|
||||||
);
|
);
|
||||||
view! {
|
view! {
|
||||||
<Suspense fallback=|| view!{ <div>"Loading..."</div> }>
|
<Suspense fallback=loading>
|
||||||
{ move || all_series.get().map(format_all_series)}
|
{ move || all_series.get().map(format_all_series)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<div>
|
<div>
|
||||||
|
@ -85,11 +85,31 @@ fn format_series(series: Series) -> impl IntoView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn loading() -> impl IntoView {
|
||||||
|
view! { <div>Loading...</div> }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_errors(errors: RwSignal<Errors>) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div>
|
||||||
|
{errors.get()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, e)| e.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||||
pub struct Series {
|
pub struct Series {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
|
source: String,
|
||||||
|
imdb: Option<String>,
|
||||||
|
wikipedia: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
|
@ -100,7 +120,7 @@ pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
|
||||||
|
|
||||||
let pool: Pool<Postgres> = expect_context();
|
let pool: Pool<Postgres> = expect_context();
|
||||||
|
|
||||||
let s = load_statement("select_series.sql");
|
let s = load_statement("select_all_series.sql");
|
||||||
let mut r = sqlx::query_as::<_, Series>(&s).fetch(&pool);
|
let mut r = sqlx::query_as::<_, Series>(&s).fetch(&pool);
|
||||||
|
|
||||||
let mut found = Vec::new();
|
let mut found = Vec::new();
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
|
use super::{format_errors, Series};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Params, PartialEq)]
|
||||||
|
pub struct ViewParams {
|
||||||
|
id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ViewSeries() -> impl IntoView {
|
pub fn ViewSeries() -> impl IntoView {
|
||||||
|
let ps = use_params::<ViewParams>();
|
||||||
|
let id = move || {
|
||||||
|
ps.with(|ps| {
|
||||||
|
ps.as_ref()
|
||||||
|
.map(|ps| ps.id.clone())
|
||||||
|
.expect("Failed to parse Id value from query string!")
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let details = create_resource(
|
||||||
|
move || id(),
|
||||||
|
|id| async move { get_series_details(id).await },
|
||||||
|
);
|
||||||
view! {
|
view! {
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="card-heading">
|
<div class="card-heading">
|
||||||
|
@ -15,8 +34,60 @@ pub fn ViewSeries() -> impl IntoView {
|
||||||
<h2>View Series</h2>
|
<h2>View Series</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
View Series
|
<Suspense fallback=loading>
|
||||||
|
{details.get().map(format_series_details)}
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn loading() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
{format_field("Name", view!(<span class="placeholder col-4"></span>))}
|
||||||
|
{format_field("Source", view!(<span class="placeholder col-12"></span>))}
|
||||||
|
{format_field("IMDB", view!(<span class="placeholder col-12"></span>))}
|
||||||
|
{format_field("Wikpedia", view!(<span class="placeholder col-12"></span>))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_series_details(
|
||||||
|
details: Result<Series, ServerFnError>,
|
||||||
|
) -> Result<impl IntoView, ServerFnError> {
|
||||||
|
Ok(view! {
|
||||||
|
<ErrorBoundary fallback=format_errors>
|
||||||
|
{details.map(|details| view! {
|
||||||
|
{format_field("Name", details.name)}
|
||||||
|
{format_field("Source", details.source)}
|
||||||
|
{format_field("IMDB", details.imdb)}
|
||||||
|
{format_field("Wikipedia", details.wikipedia)}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</ErrorBoundary>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_field<F: IntoView>(label: &'static str, f: F) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-2 text-bg-secondary border-bottom border-secondary-subtle">{label}</div>
|
||||||
|
<div class="col col-md-auto">{f}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server(GetSeriesDetails)]
|
||||||
|
pub async fn get_series_details(id: Uuid) -> Result<Series, ServerFnError> {
|
||||||
|
use crate::db::load_statement;
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
let pool = expect_context::<Pool<Postgres>>();
|
||||||
|
let s = load_statement("select_series.sql");
|
||||||
|
let r = sqlx::query_as::<_, Series>(&s)
|
||||||
|
.persistent(true)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue