Fix links.
- Break source into enum and url. - Change details heading to use series name. - Change table layout into button group of active links.
This commit is contained in:
parent
5207d013ba
commit
75e388555b
5 changed files with 151 additions and 37 deletions
|
@ -1,3 +1,4 @@
|
|||
alter table "public"."series" add column source text not null;
|
||||
alter table "public"."series" add column source_url text not null;
|
||||
alter table "public"."series" add column imdb text null;
|
||||
alter table "public"."series" add column wikipedia text null
|
||||
|
|
|
@ -1 +1 @@
|
|||
insert into series (name, source, imdb, wikipedia) values ($1, $2, $3, $4);
|
||||
insert into series (name, source, source_url, imdb, wikipedia) values ($1, $2, $3, $4, $5);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::app::series::Source;
|
||||
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
|
@ -25,9 +27,21 @@ pub fn AddSeries() -> impl IntoView {
|
|||
/>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="source">Source Link</label>
|
||||
<label class="input-group-text" for="source">Source</label>
|
||||
<select class="form-control" name="source">
|
||||
<option value=Source::Disney.to_string()>{Source::Disney.to_string()}</option>
|
||||
<option value=Source::Hulu.to_string()>{Source::Hulu.to_string()}</option>
|
||||
<option value=Source::Paramount.to_string()>{Source::Paramount.to_string()}</option>
|
||||
<option value=Source::Prime.to_string()>{Source::Prime.to_string()}</option>
|
||||
<option value=Source::Max.to_string()>{Source::Max.to_string()}</option>
|
||||
<option value=Source::Netflix.to_string()>{Source::Netflix.to_string()}</option>
|
||||
<option value=Source::Shudder.to_string()>{Source::Shudder.to_string()}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<label class="input-group-text" for="source">Source Url</label>
|
||||
<input type="text"
|
||||
name="source"
|
||||
name="source_url"
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
|
@ -60,7 +74,8 @@ pub fn AddSeries() -> impl IntoView {
|
|||
#[server(AddSeries)]
|
||||
pub async fn add_series(
|
||||
name: String,
|
||||
source: String,
|
||||
source: Source,
|
||||
source_url: String,
|
||||
imdb: String,
|
||||
wikipedia: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
|
@ -72,7 +87,8 @@ pub async fn add_series(
|
|||
sqlx::query(&s)
|
||||
.persistent(true)
|
||||
.bind(name)
|
||||
.bind(source)
|
||||
.bind(source.to_string())
|
||||
.bind(source_url)
|
||||
.bind(if imdb.is_empty() { None } else { Some(imdb) })
|
||||
.bind(if wikipedia.is_empty() {
|
||||
None
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use self::add::AddSeries;
|
||||
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "ssr")]
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod add;
|
||||
|
@ -102,16 +103,92 @@ pub fn format_errors(errors: RwSignal<Errors>) -> impl IntoView {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub enum Source {
|
||||
Disney,
|
||||
Hulu,
|
||||
Paramount,
|
||||
Prime,
|
||||
Max,
|
||||
Netflix,
|
||||
Shudder,
|
||||
}
|
||||
|
||||
impl Display for Source {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Source::Disney => f.write_str("Disney"),
|
||||
Source::Hulu => f.write_str("Hulu"),
|
||||
Source::Paramount => f.write_str("Paramount"),
|
||||
Source::Prime => f.write_str("Prime"),
|
||||
Source::Max => f.write_str("Max"),
|
||||
Source::Netflix => f.write_str("Netflix"),
|
||||
Source::Shudder => f.write_str("Shudder"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Series {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
source: String,
|
||||
source: Source,
|
||||
source_url: String,
|
||||
imdb: Option<String>,
|
||||
wikipedia: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod ssr {
|
||||
use super::{Series, Source};
|
||||
use sqlx::{postgres::PgRow, prelude::*};
|
||||
|
||||
impl<'r> FromRow<'r, PgRow> for Series {
|
||||
fn from_row(row: &'r PgRow) -> Result<Self, sqlx::Error> {
|
||||
let id = row.try_get("id")?;
|
||||
let name = row.try_get("name")?;
|
||||
let source: String = row.try_get("source")?;
|
||||
|
||||
let source_enum: Source =
|
||||
source
|
||||
.try_into()
|
||||
.map_err(|e: String| sqlx::Error::ColumnDecode {
|
||||
index: "source".to_string(),
|
||||
source: e.into(),
|
||||
})?;
|
||||
let source_url = row.try_get("source_url")?;
|
||||
let imdb = row.try_get("imdb")?;
|
||||
let wikipedia = row.try_get("wikipedia")?;
|
||||
|
||||
Ok(Series {
|
||||
id,
|
||||
name,
|
||||
source_url,
|
||||
imdb,
|
||||
wikipedia,
|
||||
source: source_enum,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Source {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_str() {
|
||||
"disney" => Ok(Self::Disney),
|
||||
"hulu" => Ok(Self::Hulu),
|
||||
"paramount" => Ok(Self::Paramount),
|
||||
"prime" => Ok(Self::Prime),
|
||||
"max" => Ok(Self::Max),
|
||||
"netflix" => Ok(Self::Netflix),
|
||||
"shudder" => Ok(Self::Shudder),
|
||||
_ => Err(format!("Unrecognized source: {value}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_all() -> Result<Vec<Series>, ServerFnError> {
|
||||
use crate::db::load_statement;
|
||||
|
|
|
@ -14,25 +14,13 @@ pub fn ViewSeries() -> impl IntoView {
|
|||
let id = move || {
|
||||
ps.with(|ps| {
|
||||
ps.as_ref()
|
||||
.map(|ps| ps.id.clone())
|
||||
.map(|ps| ps.id)
|
||||
.expect("Failed to parse Id value from query string!")
|
||||
})
|
||||
};
|
||||
let details = create_resource(
|
||||
move || id(),
|
||||
|id| async move { get_series_details(id).await },
|
||||
);
|
||||
let details = create_resource(id, |id| async move { get_series_details(id).await });
|
||||
view! {
|
||||
<div class="card p-3">
|
||||
<div class="card-heading">
|
||||
<A href="/"
|
||||
attr:class="btn btn-close"
|
||||
attr:style="float: right;"
|
||||
>
|
||||
<span style="display: none;">x</span>
|
||||
</A>
|
||||
<h2>View Series</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<Suspense fallback=loading>
|
||||
{details.get().map(format_series_details)}
|
||||
|
@ -44,10 +32,20 @@ pub fn ViewSeries() -> impl IntoView {
|
|||
|
||||
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>))}
|
||||
<div class="card-heading">
|
||||
<A href="/"
|
||||
attr:class="btn btn-close"
|
||||
attr:style="float: right;"
|
||||
>
|
||||
<span style="display: none;">x</span>
|
||||
</A>
|
||||
<h2><span class="placeholder col-4"></span></h2>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary"><span class="placeholder col-4"></span></button>
|
||||
<button class="btn btn-primary">IMDB</button>
|
||||
<button class="btn btn-primary">Wikipedia</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,22 +55,44 @@ fn format_series_details(
|
|||
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)}
|
||||
<div class="card-heading">
|
||||
<A href="/"
|
||||
attr:class="btn btn-close"
|
||||
attr:style="float: right;"
|
||||
>
|
||||
<span style="display: none;">x</span>
|
||||
</A>
|
||||
<h2>{details.name}</h2>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a href={details.source_url}
|
||||
class="btn btn-primary"
|
||||
target="_new"
|
||||
>
|
||||
{details.source.to_string()}
|
||||
</a>
|
||||
<OptionalUrlButton label="IMDB" opt_url=details.imdb.clone() />
|
||||
<OptionalUrlButton label="Wikipedia" opt_url=details.wikipedia.clone() />
|
||||
</div>
|
||||
})
|
||||
}
|
||||
</ErrorBoundary>
|
||||
})
|
||||
}
|
||||
|
||||
fn format_field<F: IntoView>(label: &'static str, f: F) -> impl IntoView {
|
||||
#[component]
|
||||
fn OptionalUrlButton(label: &'static str, opt_url: Option<String>) -> 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>
|
||||
<Suspense fallback=|| ()>
|
||||
{opt_url.as_ref().map(|url| view! {
|
||||
<a href={url}
|
||||
class="btn btn-primary"
|
||||
target="_new"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
})}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue