quote frontend init, chips, basic stats
This commit is contained in:
@@ -39,6 +39,11 @@ pub enum PersonError {
|
||||
}
|
||||
|
||||
impl Person {
|
||||
pub fn total_count() -> Result<i64, PersonError> {
|
||||
let conn = database::conn()?;
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM persons", (), |r| r.get(0))?;
|
||||
Ok(count)
|
||||
}
|
||||
pub fn get_all() -> Result<Vec<Person>, PersonError> {
|
||||
Ok(database::conn()?
|
||||
.prepare("SELECT p.id, p.created_by, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = 1")?
|
||||
|
||||
@@ -38,6 +38,11 @@ pub enum QuoteError {
|
||||
}
|
||||
|
||||
impl Quote {
|
||||
pub fn total_count() -> Result<i64, QuoteError> {
|
||||
let conn = database::conn()?;
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM quotes", (), |r| r.get(0))?;
|
||||
Ok(count)
|
||||
}
|
||||
pub fn get_by_id(id: Uuid) -> Result<Quote, QuoteError> {
|
||||
let conn = database::conn()?;
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ pub struct Tag {
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn total_count() -> Result<i64, TagError> {
|
||||
let conn = database::conn()?;
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM tags", (), |r| r.get(0))?;
|
||||
Ok(count)
|
||||
}
|
||||
pub fn get_all() -> Result<Vec<Tag>, TagError> {
|
||||
Ok(database::conn()?
|
||||
.prepare("SELECT id, tagname FROM tags")?
|
||||
|
||||
@@ -45,6 +45,11 @@ pub enum UserError {
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn total_count() -> Result<i64, UserError> {
|
||||
let conn = database::conn()?;
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM users", (), |r| r.get(0))?;
|
||||
Ok(count)
|
||||
}
|
||||
pub fn get_by_id(id: Uuid) -> Result<User, UserError> {
|
||||
let res = database::conn()?
|
||||
.prepare("SELECT handle FROM users WHERE id = ?1")?
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
use maud::{Markup, html};
|
||||
|
||||
pub mod marquee;
|
||||
pub mod nav;
|
||||
pub mod quote;
|
||||
pub mod user_miniprofile;
|
||||
|
||||
pub fn chip(inner: Markup) -> Markup {
|
||||
html!(
|
||||
div class="rounded-full px-3 py-1 bg-neutral-200/10 border border-neutral-200/15 text-xs" {
|
||||
(inner)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
38
src/web/components/quote.rs
Normal file
38
src/web/components/quote.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
|
||||
use crate::{quotes::Quote, web::icons};
|
||||
|
||||
pub fn quote(quote: &Quote) -> Markup {
|
||||
html!(
|
||||
div class="border border-neutral-200/25 bg-neutral-200/5 p-3 pb-1 overflow-clip rounded-md relative flex flex-col" {
|
||||
div class="absolute top-4 right-6 -rotate-12 opacity-[.025] scale-x-[4.5] scale-y-[4]" {
|
||||
(PreEscaped(icons::QUOTE))
|
||||
}
|
||||
@for (i, line) in quote.lines.iter().enumerate() {
|
||||
@let show_author = i == quote.lines.len()-1 || quote.lines[i+1].attribution.id != line.attribution.id;
|
||||
div class="mb-2" {
|
||||
span class="flex flex-row gap-2 relative" {
|
||||
span class="scale-x-[.65] scale-y-[.5] absolute opacity-[.3]"{
|
||||
(PreEscaped(icons::QUOTE))
|
||||
}
|
||||
p class="font-lora ml-6"{(line.content)}
|
||||
}
|
||||
@if show_author {
|
||||
p class="text-sm italic ml-3 flex flex-row gap-[6px] text-neutral-400" {
|
||||
"— " (line.attribution.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="flex flex-row text-neutral-400 mt-auto pt-4 text-sm items-center font-light text-xs" {
|
||||
p {(quote.timestamp.format("%d/%m/%Y %H:%M"))}
|
||||
@if let Some(loc) = "e.location {
|
||||
span class="ml-3 scale-[.5]"{(PreEscaped(icons::MAP_PIN))} p { (loc) }
|
||||
}
|
||||
@if let Some(ctx) = "e.context {
|
||||
span class="ml-3 scale-[.5]"{(PreEscaped(icons::INFO))} p class="italic truncate pr-1" {(ctx)}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
1
src/web/icons/info.svg
Normal file
1
src/web/icons/info.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
|
||||
|
After Width: | Height: | Size: 306 B |
1
src/web/icons/map-pin.svg
Normal file
1
src/web/icons/map-pin.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg>
|
||||
|
After Width: | Height: | Size: 381 B |
@@ -4,7 +4,10 @@ pub const CALENDAR_1: &str = include_str!("calendar-1.svg");
|
||||
pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg");
|
||||
pub const CONTACT: &str = include_str!("contact.svg");
|
||||
pub const FILE_IMAGE: &str = include_str!("file-image.svg");
|
||||
pub const INFO: &str = include_str!("info.svg");
|
||||
pub const LAYOUT_DASHBOARD: &str = include_str!("layout-dashboard.svg");
|
||||
pub const MAP_PIN: &str = include_str!("map-pin.svg");
|
||||
pub const QUOTE: &str = include_str!("quote.svg");
|
||||
pub const SCROLL_TEXT: &str = include_str!("scroll-text.svg");
|
||||
pub const SERVER: &str = include_str!("server.svg");
|
||||
pub const SHIELD_USER: &str = include_str!("shield-user.svg");
|
||||
|
||||
1
src/web/icons/quote.svg
Normal file
1
src/web/icons/quote.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-quote-icon lucide-quote"><path d="M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"/><path d="M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 509 B |
@@ -1,11 +1,25 @@
|
||||
use axum::extract::Request;
|
||||
use maud::{Markup, html};
|
||||
use chrono::{DateTime, Utc};
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
persons::{Name, Person},
|
||||
quotes::{Quote, QuoteLine},
|
||||
tags::Tag,
|
||||
users::{User, auth::UserAuthenticate},
|
||||
web::{components::nav::nav, pages::base},
|
||||
web::{
|
||||
components::{chip, nav::nav, quote::quote},
|
||||
icons,
|
||||
pages::base,
|
||||
},
|
||||
};
|
||||
|
||||
const LINKS: &[(&str, &str, &str)] = &[
|
||||
("Add Quote", "/quotes/add", icons::QUOTE),
|
||||
("Add Person", "/persons/add", icons::CONTACT),
|
||||
];
|
||||
|
||||
pub async fn page(req: Request) -> Markup {
|
||||
let u = User::authenticate(req.headers()).ok().flatten();
|
||||
base(
|
||||
@@ -13,7 +27,130 @@ pub async fn page(req: Request) -> Markup {
|
||||
html!(
|
||||
(nav(u.as_ref(), req.uri().path()))
|
||||
|
||||
div class="text-6xl sm:text-8xl text-neutral-800/25 mt-16 text-center font-semibold font-lora select-none overflow-hidden" {"Mnemosyne"}
|
||||
div class="mx-auto max-w-4xl mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4" {
|
||||
div class="flex flex-col" {
|
||||
p {"Newest Quote"}
|
||||
p class="text-neutral-500 font-light mb-4" {"This just in! This quote was added 15s ago."}
|
||||
div class="flex-1 [&>div]:h-full" {(quote(&sample_quote_1()))}
|
||||
}
|
||||
div class="flex flex-col" {
|
||||
p {"Quote of the Day"}
|
||||
p class="text-neutral-500 font-light mb-4" {"This quote was voiced a year ago today."}
|
||||
div class="flex-1 [&>div]:h-full" {(quote(&sample_quote_2()))}
|
||||
}
|
||||
}
|
||||
div class="mx-auto max-w-4xl mt-4" {
|
||||
p class="mb-2" {"Quick access"}
|
||||
div class="flex gap-4" {
|
||||
@for (title, url, icon) in LINKS {
|
||||
a href=(url)
|
||||
class="border border-neutral-200/25 hover:border-neutral-200/35 bg-neutral-200/5 hover:bg-neutral-200/10 p-4 rounded flex-1 relative overflow-clip" {
|
||||
div class="absolute top-1 right-1 scale-[2] -rotate-12 text-neutral-700" {(PreEscaped(icon))}
|
||||
div class="absolute bottom-1 left-1 scale-[2] -rotate-12 text-neutral-700" {(PreEscaped(icon))}
|
||||
p class="sm:text-2xl font-semibold text-center" {(title)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
div class="mx-auto max-w-4xl mt-4 flex flex-row gap-2" {
|
||||
(chip(html!({
|
||||
@match Quote::total_count() {
|
||||
Ok(count) => {(count) " QUOTES TOTAL"},
|
||||
Err(_) => span class="text-red-400" {"QUOTE COUNT ERR"},
|
||||
}
|
||||
})))
|
||||
(chip(html!({
|
||||
@match Person::total_count() {
|
||||
Ok(count) => {(count) " PERSONS TOTAL"},
|
||||
Err(_) => span class="text-red-400" {"PERSON COUNT ERR"},
|
||||
}
|
||||
})))
|
||||
(chip(html!({
|
||||
@match Tag::total_count() {
|
||||
Ok(count) => {(count) " TAGS TOTAL"},
|
||||
Err(_) => span class="text-red-400" {"TAG COUNT ERR"}
|
||||
}
|
||||
})))
|
||||
(chip(html!({
|
||||
@match User::total_count() {
|
||||
Ok(count) => {(count) " USERS TOTAL"},
|
||||
Err(_) => span class="text-red-400" {"USER COUNT ERR"}
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
div class="text-4xl xs:text-6xl sm:text-8xl text-neutral-800/25 mt-16 text-center font-semibold font-lora select-none" {"Mnemosyne"}
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_quote_1() -> Quote {
|
||||
Quote {
|
||||
id: Uuid::now_v7(),
|
||||
public: true,
|
||||
location: Some(String::from("Poznań")),
|
||||
context: Some(String::from("Wykład z językoznawstwa")),
|
||||
created_by: Uuid::max(),
|
||||
timestamp: DateTime::from(Utc::now()),
|
||||
lines: vec![
|
||||
QuoteLine {
|
||||
id: Uuid::now_v7(),
|
||||
content: String::from("Nie wiem, czy są tutaj osoby fanowskie zipline-ów?"),
|
||||
attribution: Name {
|
||||
id: Uuid::nil(),
|
||||
created_by: Uuid::max(),
|
||||
person_id: Uuid::now_v7(),
|
||||
is_primary: true,
|
||||
name: String::from("dr. Barbara Konat"),
|
||||
},
|
||||
},
|
||||
QuoteLine {
|
||||
id: Uuid::now_v7(),
|
||||
content: String::from("Taka uprząż co robi pziuuum!"),
|
||||
attribution: Name {
|
||||
id: Uuid::nil(),
|
||||
created_by: Uuid::max(),
|
||||
person_id: Uuid::now_v7(),
|
||||
is_primary: true,
|
||||
name: String::from("dr. Barbara Konat"),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_quote_2() -> Quote {
|
||||
Quote {
|
||||
id: Uuid::now_v7(),
|
||||
public: true,
|
||||
location: Some(String::from("Discord VC")),
|
||||
context: Some(String::from("O narysowanej dziewczynie")),
|
||||
created_by: Uuid::max(),
|
||||
timestamp: DateTime::from(Utc::now()),
|
||||
lines: vec![
|
||||
QuoteLine {
|
||||
id: Uuid::now_v7(),
|
||||
content: String::from("Czy tu proporcje są zachowane?"),
|
||||
attribution: Name {
|
||||
id: Uuid::now_v7(),
|
||||
created_by: Uuid::max(),
|
||||
person_id: Uuid::now_v7(),
|
||||
is_primary: true,
|
||||
name: String::from("Adam"),
|
||||
},
|
||||
},
|
||||
QuoteLine {
|
||||
id: Uuid::now_v7(),
|
||||
content: String::from("Adam, ona nie ma kolan."),
|
||||
attribution: Name {
|
||||
id: Uuid::nil(),
|
||||
created_by: Uuid::max(),
|
||||
person_id: Uuid::now_v7(),
|
||||
is_primary: true,
|
||||
name: String::from("Mollin"),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user