#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub enum Header {
#[default]
Title,
Previous,
Next,
Today,
PreviousYear,
NextYear,
}
impl AsRef<str> for Header {
fn as_ref(&self) -> &str {
match self {
Self::Title => "title",
Self::Previous => "prev",
Self::Next => "next",
Self::Today => "today",
Self::PreviousYear => "prevYear",
Self::NextYear => "nextYear",
}
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub struct HeaderSections {
pub left: String,
pub center: String,
pub right: String,
}
impl Default for HeaderSections {
fn default() -> Self {
Self {
left: Header::Title.as_ref().to_string(),
center: String::new(),
right: format!(
"{} {},{}",
Header::Today.as_ref(),
Header::Previous.as_ref(),
Header::Next.as_ref()
),
}
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum HeaderOptions {
Active(bool),
Options(HeaderSections),
}
impl Default for HeaderOptions {
fn default() -> Self {
Self::Options(HeaderSections::default())
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub struct EventTimeFormat {
pub hour: DateFormat,
pub minute: DateFormat,
pub meridiem: DateFormat,
#[serde(skip_serializing_if = "Option::is_none")]
pub omit_zero_minute: Option<bool>,
}
impl Default for EventTimeFormat {
fn default() -> Self {
Self {
hour: DateFormat::Numeric,
minute: DateFormat::TwoDigit,
meridiem: DateFormat::Short,
omit_zero_minute: None,
}
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub enum DateFormat {
#[default]
Short,
Numeric,
Long,
#[serde(rename = "2-digit")]
TwoDigit,
Narrow,
#[serde(untagged)]
False(bool),
}
impl AsRef<str> for DateFormat {
fn as_ref(&self) -> &str {
match self {
Self::Short => "short",
Self::Numeric => "numeric",
Self::Long => "long",
Self::TwoDigit => "2-digit",
Self::Narrow => "narrow",
Self::False(_) => "false",
}
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct DayHeaderFormat {
#[serde(skip_serializing_if = "Option::is_none")]
weekday: Option<DateFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
month: Option<DateFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
day: Option<DateFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
omit_commas: Option<bool>,
}
impl DayHeaderFormat {
#[must_use]
pub const fn weekday(mut self, weekday: DateFormat) -> Self {
self.weekday = Some(weekday);
self
}
#[must_use]
pub const fn month(mut self, month: DateFormat) -> Self {
self.month = Some(month);
self
}
#[must_use]
pub const fn day(mut self, day: DateFormat) -> Self {
self.day = Some(day);
self
}
#[must_use]
pub const fn omit_commas(mut self, omit_commas: bool) -> Self {
self.omit_commas = Some(omit_commas);
self
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq, Default, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum InitialView {
#[default]
DayGridMonth,
TimeGridWeek,
TimeGridDay,
ListMonth,
ListWeek,
ListDay,
ListYear,
}
impl AsRef<str> for InitialView {
fn as_ref(&self) -> &str {
match self {
Self::DayGridMonth => "dayGridMonth",
Self::TimeGridWeek => "timeGridWeek",
Self::TimeGridDay => "timeGridDay",
Self::ListMonth => "listMonth",
Self::ListWeek => "listWeek",
Self::ListDay => "listDay",
Self::ListYear => "listYear",
}
}
}
impl std::str::FromStr for InitialView {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"dayGridMonth" => Ok(Self::DayGridMonth),
"timeGridWeek" => Ok(Self::TimeGridWeek),
"timeGridDay" => Ok(Self::TimeGridDay),
"listMonth" => Ok(Self::ListMonth),
"listWeek" => Ok(Self::ListWeek),
"listDay" => Ok(Self::ListDay),
"listYear" => Ok(Self::ListYear),
_ => Err(format!("Invalid view name: {s}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub enum Locale {
#[default]
#[serde(rename = "en")]
En,
#[serde(rename = "es")]
Es,
#[serde(rename = "fr")]
Fr,
}
impl AsRef<str> for Locale {
fn as_ref(&self) -> &str {
match self {
Self::En => "en",
Self::Es => "es",
Self::Fr => "fr",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize)]
pub enum TimeZone {
#[default]
#[serde(rename = "local")]
Local,
#[serde(rename = "UTC")]
Utc,
#[serde(untagged)]
NamedTimeZone(String),
}
impl std::str::FromStr for TimeZone {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"local" => Self::Local,
"UTC" => Self::Utc,
s => Self::NamedTimeZone(s.to_string()),
})
}
}
#[derive(Debug, Clone, serde::Serialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Options {
pub initial_view: InitialView,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_date: Option<crate::EventDate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<Locale>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_zone: Option<TimeZone>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expand_rows: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub all_day_slot: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub selectable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_day: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot_duration: Option<crate::EventDuration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header_toolbar: Option<HeaderOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub select_long_press_delay: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_event_time: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_time_format: Option<EventTimeFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub day_headers: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub day_header_format: Option<DayHeaderFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slot_label_format: Option<EventTimeFormat>,
#[serde(skip)]
event_click_handler: Option<web_sys::js_sys::Function>,
#[serde(skip)]
select_handler: Option<web_sys::js_sys::Function>,
#[serde(skip)]
date_click_handler: Option<web_sys::js_sys::Function>,
#[serde(skip)]
date_set_handler: Option<web_sys::js_sys::Function>,
}
impl Options {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_initial_view(mut self, initial_view: InitialView) -> Self {
self.initial_view = initial_view;
self
}
#[must_use]
pub fn with_initial_date(mut self, initial_date: crate::EventDate) -> Self {
self.initial_date = Some(initial_date);
self
}
#[must_use]
pub const fn with_locale(mut self, locale: Locale) -> Self {
self.locale = Some(locale);
self
}
#[must_use]
pub fn with_time_zone(mut self, time_zone: TimeZone) -> Self {
self.time_zone.replace(time_zone);
self
}
#[must_use]
pub const fn with_expand_rows(mut self, expand_rows: bool) -> Self {
self.expand_rows = Some(expand_rows);
self
}
#[must_use]
pub const fn with_all_day_slot(mut self, all_day_slot: bool) -> Self {
self.all_day_slot = Some(all_day_slot);
self
}
#[must_use]
pub const fn with_selectable(mut self, selectable: bool) -> Self {
self.selectable = Some(selectable);
self
}
#[must_use]
pub const fn with_first_day(mut self, first_day: u32) -> Self {
self.first_day = Some(first_day);
self
}
#[must_use]
pub fn with_slot_duration(mut self, slot_duration: crate::EventDuration) -> Self {
self.slot_duration = Some(slot_duration);
self
}
#[must_use]
pub fn with_header_toolbar(mut self, header_toolbar: HeaderOptions) -> Self {
self.header_toolbar = Some(header_toolbar);
self
}
#[must_use]
pub const fn with_select_long_press_delay(mut self, select_long_press_delay: u32) -> Self {
self.select_long_press_delay = Some(select_long_press_delay);
self
}
#[must_use]
pub const fn with_display_event_time(mut self, display_event_time: bool) -> Self {
self.display_event_time = Some(display_event_time);
self
}
#[must_use]
pub const fn with_event_time_format(mut self, event_time_format: EventTimeFormat) -> Self {
self.event_time_format = Some(event_time_format);
self
}
#[must_use]
pub const fn with_day_headers(mut self, day_headers: bool) -> Self {
self.day_headers = Some(day_headers);
self
}
#[must_use]
pub const fn with_day_header_format(mut self, day_header_format: DayHeaderFormat) -> Self {
self.day_header_format = Some(day_header_format);
self
}
#[must_use]
pub const fn with_slot_label_format(mut self, slot_label_format: EventTimeFormat) -> Self {
self.slot_label_format = Some(slot_label_format);
self
}
#[must_use]
pub fn with_event_click<F>(mut self, f: F) -> Self
where
F: Fn(crate::bindings::EventClickInfo) + 'static,
{
let closure = web_sys::wasm_bindgen::closure::Closure::<
dyn Fn(crate::bindings::EventClickInfo),
>::new(f);
self.event_click_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
closure.into_js_value(),
));
self
}
#[must_use]
pub fn with_select<F>(mut self, f: F) -> Self
where
F: Fn(crate::bindings::SelectionInfo) + 'static,
{
let closure = web_sys::wasm_bindgen::closure::Closure::<
dyn Fn(crate::bindings::SelectionInfo),
>::new(f);
self.select_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
closure.into_js_value(),
));
self
}
#[must_use]
pub fn with_date_click<F>(mut self, f: F) -> Self
where
F: Fn(crate::bindings::DateClickInfo) + 'static,
{
let closure = web_sys::wasm_bindgen::closure::Closure::<
dyn Fn(crate::bindings::DateClickInfo),
>::new(f);
self.date_click_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
closure.into_js_value(),
));
self
}
#[must_use]
pub fn with_date_set<F>(mut self, f: F) -> Self
where
F: Fn(crate::bindings::DateSetEvent) + 'static,
{
let closure = web_sys::wasm_bindgen::closure::Closure::<
dyn Fn(crate::bindings::DateSetEvent),
>::new(f);
self.date_set_handler = Some(web_sys::wasm_bindgen::JsCast::unchecked_into(
closure.into_js_value(),
));
self
}
fn validate_handler(&self) -> Result<(), web_sys::wasm_bindgen::JsValue> {
let validate_fn = |handler: &web_sys::js_sys::Function,
name: &str|
-> Result<(), web_sys::wasm_bindgen::JsValue> {
if web_sys::js_sys::Reflect::get(handler, &"call".into())?.is_undefined() {
return Err(web_sys::wasm_bindgen::JsValue::from_str(&format!(
"Invalid {name} handler"
)));
}
Ok(())
};
if let Some(handler) = &self.event_click_handler {
validate_fn(handler, "event click")?;
}
if let Some(handler) = &self.select_handler {
validate_fn(handler, "select")?;
}
if let Some(handler) = &self.date_click_handler {
validate_fn(handler, "date click")?;
}
if let Some(handler) = &self.date_set_handler {
validate_fn(handler, "date set")?;
}
Ok(())
}
pub fn build(self) -> Result<web_sys::wasm_bindgen::JsValue, web_sys::wasm_bindgen::JsValue> {
self.validate_handler()?;
let obj = web_sys::js_sys::Object::new();
let base_options = serde_wasm_bindgen::to_value(&self)?;
let base_obj: web_sys::js_sys::Object = base_options.into();
let keys = web_sys::js_sys::Object::keys(&base_obj);
for i in 0..keys.length() {
let key = keys.get(i);
let value = web_sys::js_sys::Reflect::get(&base_obj, &key)?;
web_sys::js_sys::Reflect::set(&obj, &key, &value)?;
}
if let Some(handler) = &self.event_click_handler {
web_sys::js_sys::Reflect::set(
&obj,
&web_sys::wasm_bindgen::JsValue::from_str("eventClick"),
handler,
)?;
}
if let Some(handler) = &self.select_handler {
web_sys::js_sys::Reflect::set(
&obj,
&web_sys::wasm_bindgen::JsValue::from_str("select"),
handler,
)?;
}
if let Some(handler) = &self.date_click_handler {
web_sys::js_sys::Reflect::set(
&obj,
&web_sys::wasm_bindgen::JsValue::from_str("dateClick"),
handler,
)?;
}
if let Some(handler) = &self.date_set_handler {
web_sys::js_sys::Reflect::set(
&obj,
&web_sys::wasm_bindgen::JsValue::from_str("datesSet"),
handler,
)?;
}
Ok(obj.into())
}
}