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)]
|
||||
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 sqlx::{Pool, Postgres};
|
||||
let pool = expect_context::<Pool<Postgres>>();
|
||||
|
@ -67,6 +72,13 @@ pub async fn add_series(name: String) -> Result<(), ServerFnError> {
|
|||
sqlx::query(&s)
|
||||
.persistent(true)
|
||||
.bind(name)
|
||||
.bind(source)
|
||||
.bind(if imdb.is_empty() { None } else { Some(imdb) })
|
||||
.bind(if wikipedia.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(wikipedia)
|
||||
})
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ pub fn SeriesList() -> impl IntoView {
|
|||
|_| async move { get_all().await },
|
||||
);
|
||||
view! {
|
||||
<Suspense fallback=|| view!{ <div>"Loading..."</div> }>
|
||||
<Suspense fallback=loading>
|
||||
{ move || all_series.get().map(format_all_series)}
|
||||
</Suspense>
|
||||
<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)]
|
||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||
pub struct Series {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
source: String,
|
||||
imdb: Option<String>,
|
||||
wikipedia: Option<String>,
|
||||
}
|
||||
|
||||
#[server]
|
||||
|
@ -100,7 +120,7 @@ pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
|
|||
|
||||
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 found = Vec::new();
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
use super::{format_errors, Series};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Params, PartialEq)]
|
||||
pub struct ViewParams {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
#[component]
|
||||
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! {
|
||||
<div class="card p-3">
|
||||
<div class="card-heading">
|
||||
|
@ -15,8 +34,60 @@ pub fn ViewSeries() -> impl IntoView {
|
|||
<h2>View Series</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
View Series
|
||||
<Suspense fallback=loading>
|
||||
{details.get().map(format_series_details)}
|
||||
</Suspense>
|
||||
</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