extern crate console_error_panic_hook; use anyhow::Context as _; use chrono::{DateTime, Duration, Local}; use futures_util::FutureExt; use icalendar::{Calendar, CalendarComponent::Event, Component as _, DatePerhapsTime, EventLike}; use std::{borrow::Cow, fmt::Debug, panic}; use tracing::{error, info}; use tracing_subscriber::{ fmt::{ format::{FmtSpan, Pretty}, time::UtcTime, }, prelude::*, }; use yew::{classes, function_component, html, Component, Context, Html, Properties}; const ICAL_URL: &str = "https://data.local.cccsbg.at/remote.php/dav/public-calendars/jrFa8wz3zZQESKrQ?export"; #[derive(Clone, Debug, PartialEq, Properties)] struct Meeting { start: DateTime, location: Cow<'static, str>, summary: Cow<'static, str>, description: Cow<'static, str>, } async fn fetch_ical(url: &'static str) -> anyhow::Result> { let calendar = reqwest::get(url) .await .context("reqwest")? .text() .await .context("text")? .parse::() .map_err(|e| anyhow::anyhow!("{e}"))?; let mut meetings: Vec<_> = calendar .components .into_iter() .filter_map(|component| { if let Event(event) = component { let local = Local::now() - Duration::days(1); if let DatePerhapsTime::DateTime(start) = event.get_start()? { let start = start.try_into_utc()?.with_timezone(&Local); if start < local { return None; } return Some(Meeting { start, location: event.get_location()?.to_string().into(), summary: event.get_summary()?.to_string().into(), description: event.get_description()?.to_string().into(), }); } } None }) .collect(); meetings.sort_by(|a, b| a.start.cmp(&b.start)); Ok(meetings) } #[function_component] fn MeetingBox(props: &Meeting) -> Html { let upcoming = props.start > Local::now(); html! {

{ props.start.to_string() }

{ &props.summary }

{ &props.description }

} } enum Msg { Fetched(Vec), FetchError(String), } struct Meetings { meetings: Option>, } impl Component for Meetings { type Message = Msg; type Properties = (); fn create(ctx: &Context) -> Self { let meetings = fetch_ical(ICAL_URL); ctx.link().send_future(meetings.map(|res| match res { Ok(meetings) => Msg::Fetched(meetings), Err(error) => Msg::FetchError(error.to_string()), })); Self { meetings: None } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::Fetched(meetings) => self.meetings = Some(meetings), Msg::FetchError(error) => error!(error), } true } fn view(&self, _ctx: &Context) -> Html { html! { if let Some(meetings) = &self.meetings { if meetings.is_empty() {

{ "empty ical" }

} else {
{ meetings.iter().map(|row| { html!{} }).collect::() }
} } else {

{ "fetching" }

} } } } #[function_component] fn App() -> Html { html! { <>

{ "¯\\_(ツ)_/¯ SRUG: Salzburg Rust User Group" }

{ "lazy_static!(std::collection::VecDeque), die gerne |etwas| unsafe { mit Rust machen } würde.await?;" }

{ "CCCSBG Space in der Arge Kultur, Ulrike-Gschwandtner-Straße 5, 5020 Salzburg" }

{ "CCCSBG" }
} } fn main() { let fmt_layer = tracing_subscriber::fmt::layer() .with_ansi(false) .with_timer(UtcTime::rfc_3339()) .with_writer(tracing_web::MakeConsoleWriter) .with_span_events(FmtSpan::ACTIVE); let perf_layer = tracing_web::performance_layer().with_details_from_fields(Pretty::default()); panic::set_hook(Box::new(console_error_panic_hook::hook)); tracing_subscriber::registry() .with(fmt_layer) .with(perf_layer) .init(); info!("starting client",); yew::Renderer::::new().render(); }