Web-based Calendar Aggregator
https://ticker.c3d2.de/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.9 KiB
152 lines
4.9 KiB
use std::convert::TryInto; |
|
use gotham::{ |
|
helpers::http::response::create_response, |
|
hyper::{Body, Response}, |
|
state::{FromState, State}, |
|
}; |
|
use http::status::StatusCode; |
|
use mime::TEXT_HTML; |
|
|
|
use typed_html::{html, text, dom::DOMTree, types::{Class, SpacedSet}}; |
|
use diesel::prelude::*; |
|
use chrono::{offset::Local, Datelike, Duration, NaiveDate}; |
|
|
|
use libticker::{ |
|
schema::{self, events::dsl::events}, |
|
model::Event, |
|
}; |
|
use crate::AppState; |
|
|
|
|
|
fn fix_url(s: &str) -> std::borrow::Cow<str> { |
|
if s.starts_with("http:") || s.starts_with("https:") { |
|
s.into() |
|
} else { |
|
format!("http://{}", s).into() |
|
} |
|
} |
|
|
|
struct DayEvents<'e> { |
|
date: NaiveDate, |
|
events: &'e [Event], |
|
} |
|
|
|
/// assumes pre-sorted input |
|
fn group_by_day(es: &[Event]) -> Vec<DayEvents> { |
|
let mut results = vec![]; |
|
|
|
let mut prev_date = None; |
|
let mut date_start = 0; |
|
for (i, event) in es.iter().enumerate() { |
|
if prev_date.is_some() && prev_date != Some(event.dtstart.date()) { |
|
if i > date_start { |
|
results.push(DayEvents { |
|
date: prev_date.unwrap().clone(), |
|
events: &es[date_start..i], |
|
}); |
|
date_start = i; |
|
} |
|
} |
|
prev_date = Some(event.dtstart.date()); |
|
} |
|
|
|
results |
|
} |
|
|
|
fn render_index(app_state: &AppState) -> String { |
|
let db = app_state.db.lock().unwrap(); |
|
let today = Local::today().naive_local().and_hms(0, 0, 0); |
|
let limit = Local::today().naive_local().and_hms(0, 0, 0) + |
|
Duration::weeks(2); |
|
let es = events |
|
.filter(schema::events::dtend.ge(&today)) |
|
.or_filter(schema::events::dtstart.ge(&today)) |
|
.filter(schema::events::dtstart.lt(&limit)) |
|
.order_by(schema::events::dtstart.asc()) |
|
.then_order_by(schema::events::dtend.desc()) |
|
.load::<Event>(&*db) |
|
.unwrap(); |
|
let days = group_by_day(&es); |
|
|
|
let config = &app_state.config; |
|
|
|
let doc: DOMTree<String> = html!( |
|
<html> |
|
<head> |
|
<title>"Ticker"</title> |
|
<meta charset="utf-8"/> |
|
<meta name="viewport" content="width=device-width"/> |
|
<link rel="stylesheet" title="Style" type="text/css" href="static/style.css"/> |
|
<link rel="icon" type="image/png" href="static/favicon.png"/> |
|
</head> |
|
<body> |
|
{ days.iter().map(|day| { |
|
let mut day_class: SpacedSet<Class> = ["date"].try_into().unwrap(); |
|
day_class.add(&format!("wd{}", day.date.weekday().num_days_from_monday())[..]); |
|
html!( |
|
<div> |
|
<h2> |
|
<span class={ day_class }> |
|
<span class="day"> |
|
{ text!("{}", day.date.day()) } |
|
</span> |
|
<span class="month"> |
|
{ text!("{}", &config.months[day.date.month0() as usize]) } |
|
</span> |
|
</span> |
|
<span class="weekday"> |
|
{ text!("{}", &config.weekdays[day.date.weekday().num_days_from_monday() as usize]) } |
|
</span> |
|
</h2> |
|
|
|
{ day.events.iter().map(|e| html!( |
|
<article class="event" |
|
style={ format!("border-left: 1.5rem solid {}", &config.calendars.get(&e.calendar).map(|o| &o.color[..]).unwrap_or("white")) } |
|
> |
|
<p class="dtstart" |
|
title={ format!("{}", e.dtstart.format("%c")) } |
|
> |
|
{ text!("{}", &e.dtstart.format("%H:%M")) } |
|
</p> |
|
{ match &e.url { |
|
None => html!( |
|
<h3>{ text!("{}", &e.summary) }</h3> |
|
), |
|
Some(url) => html!( |
|
<h3> |
|
<a href={ fix_url(url) }> |
|
{ text!("{}", &e.summary) } |
|
</a> |
|
</h3> |
|
), |
|
} } |
|
|
|
<p class="location"> |
|
{ text!("{}", e.location.as_ref().unwrap_or(&"".to_owned())) } |
|
</p> |
|
</article> |
|
)) } |
|
</div>) |
|
}) } |
|
<footer> |
|
<p> |
|
"Ein Projekt des " |
|
<a href="https://www.c3d2.de/">"C3D2"</a> |
|
" - " |
|
<a href="https://gitea.c3d2.de/astro/ticker">"Code"</a> |
|
</p> |
|
</footer> |
|
</body> |
|
</html> |
|
); |
|
format!("<!DOCTYPE html>\n{}", doc.to_string()) |
|
} |
|
|
|
pub fn index(state: State) -> (State, Response<Body>) { |
|
let message = { |
|
let app_state = AppState::borrow_from(&state); |
|
render_index(app_state) |
|
}; |
|
let res = create_response(&state, StatusCode::OK, TEXT_HTML, message); |
|
(state, res) |
|
}
|
|
|