✨ push changes
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
<link data-trunk rel="copy-file" href="public/misc/oneko.gif" />
|
||||
<link data-trunk rel="copy-file" href="public/misc/monocraft.ttf" />
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/shaders/post.bloom.svg" />
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/buttons/iouring.png" />
|
||||
|
||||
15
main.scss
15
main.scss
@@ -40,6 +40,21 @@ html * {
|
||||
0.02) !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
backdrop-filter: blur(4px) !important;
|
||||
--bulma-card-background-color: #1E1E2E80 !important;
|
||||
--bulma-shadow-h: 232 !important;
|
||||
--bulma-shadow-s: 97% !important;
|
||||
--bulma-shadow-l: 85% !important;
|
||||
--bulma-shadow: 0 0.5em 1em -0.125em hsla(var(--bulma-shadow-h),
|
||||
var(--bulma-shadow-s),
|
||||
var(--bulma-shadow-l),
|
||||
0.3), 0 0px 0 1px hsla(var(--bulma-shadow-h),
|
||||
var(--bulma-shadow-s),
|
||||
var(--bulma-shadow-l),
|
||||
0.02) !important;
|
||||
}
|
||||
|
||||
.input,
|
||||
.select select,
|
||||
.textarea {
|
||||
|
||||
BIN
public/blog/testimage.jpg
Normal file
BIN
public/blog/testimage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 MiB |
@@ -23,11 +23,11 @@ pub enum Route {
|
||||
About,
|
||||
|
||||
#[at("/blog/entries/:id")]
|
||||
Entry { id: u64 },
|
||||
Entry { id: u8 },
|
||||
#[at("/blog/entries")]
|
||||
Entries,
|
||||
#[at("/blog/authors/:id")]
|
||||
Author { id: u64 },
|
||||
Author { id: u8 },
|
||||
#[at("/blog/authors")]
|
||||
Authors,
|
||||
|
||||
@@ -131,7 +131,7 @@ fn switch(routes: Route) -> Html {
|
||||
Route::About => { html! { <About /> } }
|
||||
|
||||
Route::Entries => { html! { <Entries /> } }
|
||||
Route::Entry { id } => { html! { <Entry seed={id as u32} /> } }
|
||||
Route::Entry { id } => { html! { <Entry seed={id as u8} /> } }
|
||||
Route::Authors => { html! { <Authors /> } }
|
||||
Route::Author { id } => { html! { <Author seed={id} /> } }
|
||||
|
||||
|
||||
@@ -12,18 +12,7 @@ impl Component for About {
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<div class="tile is-parent container">
|
||||
{ self.view_info_card() }
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
impl About {
|
||||
fn view_info_card(&self) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<div class="box is-small is-flex-shrink-5 mb-5 mt-5">
|
||||
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
|
||||
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src="profile.avif" />
|
||||
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
|
||||
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;"><img loading="eager" alt="ket" class="image is-24x24 is-square is-inline-block mg-small" src="oneko.gif" />{r" Lucielle R. Hoerner"}</p>
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::pages::blog::content::BlogEntry;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub seed: u64,
|
||||
pub seed: u8,
|
||||
}
|
||||
|
||||
pub struct Author {
|
||||
@@ -25,8 +25,26 @@ impl Component for Author {
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { author } = self;
|
||||
|
||||
html! {
|
||||
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
|
||||
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src={author.image_url.clone()} />
|
||||
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
|
||||
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;">{author.name.clone()}</p>
|
||||
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
|
||||
{ for author.keywords.iter().map(|tag| html! { <p>{ tag }</p> }) }
|
||||
</p>
|
||||
</div>
|
||||
<hr style="background-color: #B4BEFE60 !important;" />
|
||||
<p class="subtitle">{r#"about me"#}</p>
|
||||
<div class="content is-size-7">
|
||||
{ author.about.clone() }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
<div class="section container">
|
||||
<div class="tile is-ancestor is-vertical">
|
||||
<div class="tile is-parent">
|
||||
@@ -53,7 +71,7 @@ impl Component for Author {
|
||||
<div class="content">
|
||||
<p class="title">{ "About me" }</p>
|
||||
<div class="content">
|
||||
{ "This author has chosen not to reveal anything about themselves" }
|
||||
{ author.about.clone() }
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -61,6 +79,4 @@ impl Component for Author {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,26 +1,26 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::pages::blog::content::BlogEntry;
|
||||
use crate::pages::blog::content::Author;
|
||||
|
||||
use crate::content::Author;
|
||||
use crate::generator::Generated;
|
||||
use crate::Route;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct Props {
|
||||
pub seed: u64,
|
||||
pub struct PropsAuthorCard {
|
||||
pub seed: u8,
|
||||
}
|
||||
|
||||
pub struct AuthorCard {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
impl Component for AuthorCard {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
type Properties = PropsAuthorCard;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
author: Author::from_seed(ctx.props().seed),
|
||||
}
|
||||
Self { author: Author::from_seed(ctx.props().seed), }
|
||||
}
|
||||
|
||||
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||
@@ -31,12 +31,13 @@ impl Component for AuthorCard {
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { author } = self;
|
||||
html! {
|
||||
<>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-128x128">
|
||||
<img alt="Author's profile picture" src={author.image_url.clone()} />
|
||||
<img alt="this should normally show an image mew,," src={author.image_url.clone()} />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
@@ -54,6 +55,7 @@ impl Component for AuthorCard {
|
||||
</Link<Route>>
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,31 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::pages::blog::content::AuthorCard;
|
||||
use crate::pages::blog::authorcard::AuthorCard;
|
||||
|
||||
pub enum Msg {
|
||||
/*pub enum Msg {
|
||||
NextAuthors,
|
||||
}
|
||||
}*/
|
||||
|
||||
pub struct Authors {
|
||||
seeds: Vec<u64>,
|
||||
seeds: Vec<u8>,
|
||||
}
|
||||
impl Component for Authors {
|
||||
type Message = Msg;
|
||||
type Message = (); //Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self { seeds: vec![1], }
|
||||
Self { seeds: vec![0], }
|
||||
}
|
||||
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg { Msg::NextAuthors => { self.seeds = vec![1]; true } }
|
||||
}
|
||||
/*fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg { Msg::NextAuthors => { self.seeds = vec![0]; true } }
|
||||
}*/
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
fn view(&self, _: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div class="container section tile is-ancestor">
|
||||
{ for self.seeds.iter().map(|&seed| { html! {
|
||||
<div class="tile is-parent">
|
||||
<div class="tile is-child">
|
||||
<AuthorCard {seed} />
|
||||
</div>
|
||||
</div>
|
||||
} }) }
|
||||
</div>
|
||||
{ for self.seeds.iter().map(|&seed| { html! {
|
||||
<AuthorCard {seed} />
|
||||
} }) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
use std::rc::Rc;
|
||||
use std::ops::Range;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::Route;
|
||||
|
||||
const ELLIPSIS: &str = "\u{02026}";
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Author {
|
||||
pub seed: u64,
|
||||
pub seed: u8,
|
||||
pub name: String,
|
||||
pub keywords: Vec<String>,
|
||||
pub image_url: String,
|
||||
pub about: String
|
||||
}
|
||||
impl BlogEntry for Author {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
return match entry.seed {
|
||||
1 => Self {seed: entry.seed, name: "iouring".to_string(), keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()], image_url: "".to_string() },
|
||||
_ => Self {seed: entry.seed, name: "none".to_string(), keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()], image_url: "".to_string()}
|
||||
};
|
||||
0 => Self {
|
||||
seed: entry.seed,
|
||||
name: "iouring".to_string(),
|
||||
image_url: "profile.avif".to_string(),
|
||||
about: "sup".to_string(),
|
||||
keywords: vec![
|
||||
"meow".to_string(),
|
||||
"mrrp".to_string(),
|
||||
"mew".to_string()
|
||||
]
|
||||
},
|
||||
_ => Self {
|
||||
seed: u8::MAX,
|
||||
name: "not found".to_string(),
|
||||
image_url: "".to_string(),
|
||||
about: "".to_string(),
|
||||
keywords: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PostMeta {
|
||||
pub seed: u64,
|
||||
pub seed: u8,
|
||||
pub title: String,
|
||||
pub author: Author,
|
||||
pub keywords: Vec<String>,
|
||||
@@ -36,12 +41,21 @@ pub struct PostMeta {
|
||||
}
|
||||
impl BlogEntry for PostMeta {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
return Self {
|
||||
seed: entry.seed,
|
||||
title: "awawa title".to_string(),
|
||||
author: Author::from_entry(entry),
|
||||
keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()],
|
||||
image_url: "".to_string()
|
||||
return match entry.seed {
|
||||
0 => Self {
|
||||
seed: entry.seed,
|
||||
title: "awawa title".to_string(),
|
||||
author: Author::from_entry(entry),
|
||||
keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()],
|
||||
image_url: "testimage.jpg".to_string()
|
||||
},
|
||||
_ => Self {
|
||||
seed: entry.seed,
|
||||
title: "not found".to_string(),
|
||||
author: Author::from_entry(entry),
|
||||
keywords: vec![],
|
||||
image_url: "".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +67,10 @@ pub struct Post {
|
||||
}
|
||||
impl BlogEntry for Post {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
return Self { meta: PostMeta::from_entry(entry), content: vec![PostPart::from_entry(entry)] }
|
||||
return Self {
|
||||
meta: PostMeta::from_entry(entry),
|
||||
content: vec![PostPart::from_entry(entry)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +81,10 @@ pub enum PostPart {
|
||||
}
|
||||
impl BlogEntry for PostPart {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
return Self::Quote(Quote::from_entry(entry));
|
||||
// TODO: ... | add proper logic
|
||||
|
||||
return Self::Section(Section::from_entry(entry))
|
||||
// return Self::Quote(Quote::from_entry(entry))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +96,22 @@ pub struct Section {
|
||||
}
|
||||
impl BlogEntry for Section {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
return Self { title: "awawa title".to_string(), paragraphs: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()], image_url: "".to_string() }
|
||||
return match entry.seed {
|
||||
0 => Self {
|
||||
title: "awawa title".to_string(),
|
||||
image_url: "".to_string(),
|
||||
paragraphs: vec![
|
||||
"meow 1".to_string(),
|
||||
"mrrp 2".to_string(),
|
||||
"mew 3".to_string()
|
||||
]
|
||||
},
|
||||
_ => Self {
|
||||
title: "not found".to_string(),
|
||||
image_url: "".to_string(),
|
||||
paragraphs: vec![]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,332 +127,18 @@ impl BlogEntry for Quote {
|
||||
}
|
||||
|
||||
pub struct Entry {
|
||||
pub seed: u64
|
||||
pub seed: u8
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn from_seed(seed: u64) -> Self {
|
||||
pub fn from_seed(seed: u8) -> Self {
|
||||
Self { seed }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BlogEntry: Sized {
|
||||
fn from_entry(entry: &mut Entry) -> Self;
|
||||
fn from_seed(seed: u64) -> Self {
|
||||
fn from_seed(seed: u8) -> Self {
|
||||
Self::from_entry(&mut Entry::from_seed(seed))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsAuthorCard {
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
pub struct AuthorCard {
|
||||
author: Author,
|
||||
}
|
||||
impl Component for AuthorCard {
|
||||
type Message = ();
|
||||
type Properties = PropsAuthorCard;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
author: Author::from_seed(ctx.props().seed),
|
||||
}
|
||||
}
|
||||
|
||||
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||
self.author = Author::from_seed(ctx.props().seed);
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { author } = self;
|
||||
html! {
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-128x128">
|
||||
<img alt="this should normally show an image mew,," src={author.image_url.clone()} />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-3">{ &author.name }</p>
|
||||
<p>
|
||||
{ "I like " }
|
||||
<b>{ author.keywords.join(", ") }</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}>
|
||||
{ "Profile" }
|
||||
</Link<Route>>
|
||||
</footer>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsPostCard {
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct PostMetaState {
|
||||
inner: PostMeta,
|
||||
}
|
||||
|
||||
impl Reducible for PostMetaState {
|
||||
type Action = u64;
|
||||
|
||||
fn reduce(self: Rc<Self>, action: u64) -> Rc<Self> {
|
||||
Self { inner: PostMeta::from_seed(action.into()), }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn PostCard(props: &PropsPostCard) -> Html {
|
||||
let seed = props.seed;
|
||||
|
||||
let post = use_reducer_eq(|| PostMetaState {
|
||||
inner: PostMeta::from_seed(seed.into()),
|
||||
});
|
||||
|
||||
{
|
||||
let post_dispatcher = post.dispatcher();
|
||||
use_effect_with(seed, move |seed| {
|
||||
post_dispatcher.dispatch(*seed);
|
||||
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
let post = &post.inner;
|
||||
|
||||
html! {
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
<figure class="image is-2by1">
|
||||
<img alt="This post's image" src={post.image_url.clone()} loading="lazy" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.seed }}>
|
||||
{ &post.title }
|
||||
</Link<Route>>
|
||||
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.seed }}>
|
||||
{ &post.author.name }
|
||||
</Link<Route>>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
pub struct PageQuery {
|
||||
pub page: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsNavigation {
|
||||
pub page: u64,
|
||||
pub total_pages: u64,
|
||||
pub route_to_page: Route,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RelNavButtons(props: &PropsNavigation) -> Html {
|
||||
let PropsNavigation {
|
||||
page,
|
||||
total_pages,
|
||||
route_to_page: to,
|
||||
} = props.clone();
|
||||
|
||||
html! {
|
||||
<>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-previous")}
|
||||
disabled={page==1}
|
||||
query={Some(PageQuery{page: page - 1})}
|
||||
to={to.clone()}
|
||||
>
|
||||
{ "Previous" }
|
||||
</Link<Route, PageQuery>>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-next")}
|
||||
disabled={page==total_pages}
|
||||
query={Some(PageQuery{page: page + 1})}
|
||||
{to}
|
||||
>
|
||||
{ "Next page" }
|
||||
</Link<Route, PageQuery>>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RenderLinksProps {
|
||||
range: Range<u64>,
|
||||
len: usize,
|
||||
max_links: usize,
|
||||
props: PropsNavigation,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RenderLinks(props: &RenderLinksProps) -> Html {
|
||||
let RenderLinksProps {
|
||||
range,
|
||||
len,
|
||||
max_links,
|
||||
props,
|
||||
} = props.clone();
|
||||
|
||||
let mut range = range;
|
||||
|
||||
if len > max_links {
|
||||
let last_link =
|
||||
html! {<RenderLink to_page={range.next_back().unwrap()} props={props.clone()} />};
|
||||
// remove 1 for the ellipsis and 1 for the last link
|
||||
let links = range
|
||||
.take(max_links - 2)
|
||||
.map(|page| html! {<RenderLink to_page={page} props={props.clone()} />});
|
||||
html! {
|
||||
<>
|
||||
{ for links }
|
||||
<li><span class="pagination-ellipsis">{ ELLIPSIS }</span></li>
|
||||
{ last_link }
|
||||
</>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
for page in range {
|
||||
<RenderLink to_page={page} props={props.clone()} />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RenderLinkProps {
|
||||
to_page: u64,
|
||||
props: PropsNavigation,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RenderLink(props: &RenderLinkProps) -> Html {
|
||||
let RenderLinkProps { to_page, props } = props.clone();
|
||||
|
||||
let PropsNavigation {
|
||||
page,
|
||||
route_to_page,
|
||||
..
|
||||
} = props;
|
||||
|
||||
let is_current_class = if to_page == page { "is-current" } else { "" };
|
||||
|
||||
html! {
|
||||
<li>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-link", is_current_class)}
|
||||
to={route_to_page}
|
||||
query={Some(PageQuery{page: to_page})}
|
||||
>
|
||||
{ to_page }
|
||||
</Link<Route, PageQuery>>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Links(props: &PropsNavigation) -> Html {
|
||||
const LINKS_PER_SIDE: usize = 3;
|
||||
|
||||
let PropsNavigation {
|
||||
page, total_pages, ..
|
||||
} = *props;
|
||||
|
||||
let pages_prev = page.checked_sub(1).unwrap_or_default() as usize;
|
||||
let pages_next = (total_pages - page) as usize;
|
||||
|
||||
let links_left = LINKS_PER_SIDE.min(pages_prev)
|
||||
// if there are less than `LINKS_PER_SIDE` to the right, we add some more on the left.
|
||||
+ LINKS_PER_SIDE.checked_sub(pages_next).unwrap_or_default();
|
||||
let links_right = 2 * LINKS_PER_SIDE - links_left;
|
||||
|
||||
html! {
|
||||
<>
|
||||
<RenderLinks range={ 1..page } len={pages_prev} max_links={links_left} props={props.clone()} />
|
||||
<RenderLink to_page={page} props={props.clone()} />
|
||||
<RenderLinks range={ page + 1..total_pages + 1 } len={pages_next} max_links={links_right} props={props.clone()} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Pagination(props: &PropsNavigation) -> Html {
|
||||
html! {
|
||||
<nav class="pagination is-right" role="navigation" aria-label="pagination">
|
||||
<RelNavButtons ..{props.clone()} />
|
||||
<ul class="pagination-list">
|
||||
<Links ..{props.clone()} />
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Nav() -> Html {
|
||||
let navbar_active = use_state_eq(|| false);
|
||||
|
||||
let toggle_navbar = {
|
||||
let navbar_active = navbar_active.clone();
|
||||
|
||||
Callback::from(move |_| {
|
||||
navbar_active.set(!*navbar_active);
|
||||
})
|
||||
};
|
||||
|
||||
let active_class = if !*navbar_active { "is-active" } else { "" };
|
||||
|
||||
html! {
|
||||
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<h1 class="navbar-item is-size-3">{ "Yew Blog" }</h1>
|
||||
|
||||
<button class={classes!("navbar-burger", "burger", active_class)}
|
||||
aria-label="menu" aria-expanded="false"
|
||||
onclick={toggle_navbar}
|
||||
>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class={classes!("navbar-menu", active_class)}>
|
||||
<div class="navbar-start">
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}> // TODO: ...
|
||||
{ "Home" }
|
||||
</Link<Route>>
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>
|
||||
{ "Posts" }
|
||||
</Link<Route>>
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">
|
||||
{ "More" }
|
||||
</div>
|
||||
<div class="navbar-dropdown">
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Authors}>
|
||||
{ "Meet the authors" }
|
||||
</Link<Route>>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::pages::blog::content::PostCard;
|
||||
use crate::pages::blog::content::{PageQuery, Pagination};
|
||||
use crate::pages::blog::content::Author;
|
||||
use crate::pages::blog::content::BlogEntry;
|
||||
use crate::pages::blog::entrycard::EntryCard;
|
||||
use crate::pages::blog::navigation::{PageQuery, Pagination};
|
||||
use crate::Route;
|
||||
|
||||
const ITEMS_PER_PAGE: u64 = 10;
|
||||
const TOTAL_PAGES: u64 = u64::MAX / ITEMS_PER_PAGE;
|
||||
const ITEMS_PER_PAGE: u8 = 10;
|
||||
const TOTAL_PAGES: u8 = 1;
|
||||
|
||||
pub enum Msg {
|
||||
PageUpdated,
|
||||
}
|
||||
|
||||
pub struct Entries {
|
||||
page: u64,
|
||||
page: u8,
|
||||
_listener: LocationHandle,
|
||||
}
|
||||
|
||||
fn current_page(ctx: &Context<Entries>) -> u64 {
|
||||
fn current_page(ctx: &Context<Entries>) -> u8 {
|
||||
let location = ctx.link().location().unwrap();
|
||||
|
||||
location.query::<PageQuery>().map(|it| it.page as u64).unwrap_or(1)
|
||||
location.query::<PageQuery>().map(|it| it.page as u8).unwrap_or(1)
|
||||
}
|
||||
|
||||
impl Component for Entries {
|
||||
@@ -52,8 +54,8 @@ impl Component for Entries {
|
||||
|
||||
html! {
|
||||
<div class="section container">
|
||||
<h1 class="title">{ "Posts" }</h1>
|
||||
<h2 class="subtitle">{ "All of our quality writing in one place" }</h2>
|
||||
<h1 class="title">{ "entries" }</h1>
|
||||
<h2 class="subtitle">{ "here are some things i yapped about" }</h2>
|
||||
{ self.view_posts(ctx) }
|
||||
<Pagination
|
||||
{page}
|
||||
@@ -67,20 +69,17 @@ impl Component for Entries {
|
||||
impl Entries {
|
||||
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
|
||||
let start_seed = (self.page - 1) * ITEMS_PER_PAGE;
|
||||
let mut cards = (0..ITEMS_PER_PAGE).map(|seed_offset| {
|
||||
html! {
|
||||
<li class="list-item mb-5">
|
||||
<PostCard seed={start_seed + seed_offset} />
|
||||
</li>
|
||||
}
|
||||
});
|
||||
let cards: Vec<_> = (0..ITEMS_PER_PAGE)
|
||||
.filter(|&seed_offset| Author::from_seed(seed_offset).seed != u8::MAX)
|
||||
.map(|seed_offset| {
|
||||
html! {
|
||||
<li class="list-item mb-5">
|
||||
<EntryCard seed={start_seed + seed_offset} />
|
||||
</li>
|
||||
}
|
||||
}).collect();
|
||||
html! {
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<ul class="list">
|
||||
{ for cards.by_ref().take(ITEMS_PER_PAGE as usize / 2) }
|
||||
</ul>
|
||||
</div>
|
||||
<div class="column">
|
||||
<ul class="list">
|
||||
{ for cards }
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::pages::blog::content::BlogEntry;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub seed: u32,
|
||||
pub seed: u8,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
@@ -18,8 +18,8 @@ pub struct PostState {
|
||||
}
|
||||
|
||||
impl Reducible for PostState {
|
||||
type Action = u32;
|
||||
fn reduce(self: Rc<Self>, action: u32) -> Rc<Self> {
|
||||
type Action = u8;
|
||||
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||
Self { inner: content::Post::from_seed(action.into()), }.into()
|
||||
}
|
||||
}
|
||||
|
||||
68
src/pages/blog/entrycard.rs
Normal file
68
src/pages/blog/entrycard.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use yew::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::pages::blog::content::BlogEntry;
|
||||
use crate::pages::blog::content::PostMeta;
|
||||
|
||||
use crate::Route;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsEntryCard {
|
||||
pub seed: u8,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct PostMetaState {
|
||||
inner: PostMeta,
|
||||
}
|
||||
|
||||
impl Reducible for PostMetaState {
|
||||
type Action = u8;
|
||||
|
||||
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||
Self { inner: PostMeta::from_seed(action.into()), }.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn EntryCard(props: &PropsEntryCard) -> Html {
|
||||
let seed = props.seed;
|
||||
|
||||
let post = use_reducer_eq(|| PostMetaState {
|
||||
inner: PostMeta::from_seed(seed.into()),
|
||||
});
|
||||
|
||||
{
|
||||
let post_dispatcher = post.dispatcher();
|
||||
use_effect_with(seed, move |seed| {
|
||||
post_dispatcher.dispatch(*seed);
|
||||
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
let post = &post.inner;
|
||||
|
||||
html! {
|
||||
<div class="card" style="height: 300px !important; max-height: 300px !important;">
|
||||
<img loading="eager" class="card-image image is-2by1" src={post.image_url.clone()} alt="blog image" style="
|
||||
object-fit: cover !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
height: 200px !important;
|
||||
max-height: 200px !important;
|
||||
filter: blur(6px) !important;
|
||||
" />
|
||||
<div class="card-content" style="height: fit-content !important; width: fit-content !important;">
|
||||
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.seed }}>
|
||||
{ &post.title }
|
||||
</Link<Route>>
|
||||
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.seed }}>
|
||||
{ &post.author.name }
|
||||
</Link<Route>>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
pub mod content;
|
||||
pub mod entries;
|
||||
pub mod entry;
|
||||
pub mod entrycard;
|
||||
pub mod authors;
|
||||
pub mod author;
|
||||
pub mod authorcard;
|
||||
pub mod navigation;
|
||||
|
||||
213
src/pages/blog/navigation.rs
Normal file
213
src/pages/blog/navigation.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yew::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::Route;
|
||||
|
||||
const ELLIPSIS: &str = "\u{02026}";
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
pub struct PageQuery {
|
||||
pub page: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsNavigation {
|
||||
pub page: u8,
|
||||
pub total_pages: u8,
|
||||
pub route_to_page: Route,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RelNavButtons(props: &PropsNavigation) -> Html {
|
||||
let PropsNavigation {
|
||||
page,
|
||||
total_pages,
|
||||
route_to_page: to,
|
||||
} = props.clone();
|
||||
|
||||
html! {
|
||||
<>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-previous")}
|
||||
disabled={page==1}
|
||||
query={Some(PageQuery{page: page - 1})}
|
||||
to={to.clone()}
|
||||
>
|
||||
{ "Previous" }
|
||||
</Link<Route, PageQuery>>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-next")}
|
||||
disabled={page==total_pages}
|
||||
query={Some(PageQuery{page: page + 1})}
|
||||
{to}
|
||||
>
|
||||
{ "Next page" }
|
||||
</Link<Route, PageQuery>>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RenderLinksProps {
|
||||
range: Range<u8>,
|
||||
len: usize,
|
||||
max_links: usize,
|
||||
props: PropsNavigation,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RenderLinks(props: &RenderLinksProps) -> Html {
|
||||
let RenderLinksProps {
|
||||
range,
|
||||
len,
|
||||
max_links,
|
||||
props,
|
||||
} = props.clone();
|
||||
|
||||
let mut range = range;
|
||||
|
||||
if len > max_links {
|
||||
let last_link =
|
||||
html! {<RenderLink to_page={range.next_back().unwrap()} props={props.clone()} />};
|
||||
let links = range
|
||||
.take(max_links - 2)
|
||||
.map(|page| html! {<RenderLink to_page={page} props={props.clone()} />});
|
||||
html! {
|
||||
<>
|
||||
{ for links }
|
||||
<li><span class="pagination-ellipsis">{ ELLIPSIS }</span></li>
|
||||
{ last_link }
|
||||
</>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
for page in range {
|
||||
<RenderLink to_page={page} props={props.clone()} />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RenderLinkProps {
|
||||
to_page: u8,
|
||||
props: PropsNavigation,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RenderLink(props: &RenderLinkProps) -> Html {
|
||||
let RenderLinkProps { to_page, props } = props.clone();
|
||||
|
||||
let PropsNavigation {
|
||||
page,
|
||||
route_to_page,
|
||||
..
|
||||
} = props;
|
||||
|
||||
let is_current_class = if to_page == page { "is-current" } else { "" };
|
||||
|
||||
html! {
|
||||
<li>
|
||||
<Link<Route, PageQuery>
|
||||
classes={classes!("pagination-link", is_current_class)}
|
||||
to={route_to_page}
|
||||
query={Some(PageQuery{page: to_page})}
|
||||
>
|
||||
{ to_page }
|
||||
</Link<Route, PageQuery>>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Links(props: &PropsNavigation) -> Html {
|
||||
const LINKS_PER_SIDE: usize = 3;
|
||||
|
||||
let PropsNavigation {
|
||||
page, total_pages, ..
|
||||
} = *props;
|
||||
|
||||
let pages_prev = page.checked_sub(1).unwrap_or_default() as usize;
|
||||
let pages_next = (total_pages - page) as usize;
|
||||
|
||||
let links_left = LINKS_PER_SIDE.min(pages_prev)
|
||||
// if there are less than `LINKS_PER_SIDE` to the right, we add some more on the left.
|
||||
+ LINKS_PER_SIDE.checked_sub(pages_next).unwrap_or_default();
|
||||
let links_right = 2 * LINKS_PER_SIDE - links_left;
|
||||
|
||||
html! {
|
||||
<>
|
||||
<RenderLinks range={ 1..page } len={pages_prev} max_links={links_left} props={props.clone()} />
|
||||
<RenderLink to_page={page} props={props.clone()} />
|
||||
<RenderLinks range={ page + 1..total_pages + 1 } len={pages_next} max_links={links_right} props={props.clone()} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Pagination(props: &PropsNavigation) -> Html {
|
||||
html! {
|
||||
<nav class="pagination is-right" role="navigation" aria-label="pagination">
|
||||
<RelNavButtons ..{props.clone()} />
|
||||
<ul class="pagination-list">
|
||||
<Links ..{props.clone()} />
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Nav() -> Html {
|
||||
let navbar_active = use_state_eq(|| false);
|
||||
|
||||
let toggle_navbar = {
|
||||
let navbar_active = navbar_active.clone();
|
||||
|
||||
Callback::from(move |_| {
|
||||
navbar_active.set(!*navbar_active);
|
||||
})
|
||||
};
|
||||
|
||||
let active_class = if !*navbar_active { "is-active" } else { "" };
|
||||
|
||||
html! {
|
||||
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<h1 class="navbar-item is-size-3">{ "Yew Blog" }</h1>
|
||||
|
||||
<button class={classes!("navbar-burger", "burger", active_class)}
|
||||
aria-label="menu" aria-expanded="false"
|
||||
onclick={toggle_navbar}
|
||||
>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class={classes!("navbar-menu", active_class)}>
|
||||
<div class="navbar-start">
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}> // TODO: ...
|
||||
{ "Home" }
|
||||
</Link<Route>>
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>
|
||||
{ "Posts" }
|
||||
</Link<Route>>
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">
|
||||
{ "More" }
|
||||
</div>
|
||||
<div class="navbar-dropdown">
|
||||
<Link<Route> classes={classes!("navbar-item")} to={Route::Authors}>
|
||||
{ "Meet the authors" }
|
||||
</Link<Route>>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user