Calendar (WIP)
This commit is contained in:
parent
9d3f9e9c60
commit
79a0aeb1b8
24 changed files with 613 additions and 231 deletions
|
|
@ -13,7 +13,7 @@ default = ["console_error_panic_hook"]
|
|||
[dependencies]
|
||||
common = { path = "../common" }
|
||||
|
||||
chrono = "0.4"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
|
||||
ron = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -1,100 +1,144 @@
|
|||
use std::{
|
||||
ops::{AddAssign, SubAssign},
|
||||
sync::{
|
||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
use std::sync::{
|
||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
use chrono::{offset::Local, Datelike, Days, NaiveDate, Weekday};
|
||||
use chrono::{offset::Local, DateTime, Datelike, Days, Months, NaiveDate, Weekday};
|
||||
use common::ron_api;
|
||||
use gloo::{console::log, events::EventListener};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::Element;
|
||||
|
||||
use crate::utils::{by_id, SelectorExt};
|
||||
use crate::{
|
||||
request,
|
||||
utils::{by_id, selector, SelectorExt},
|
||||
};
|
||||
|
||||
pub fn setup(calendar: &Element) {
|
||||
struct CalendarStateInternal {
|
||||
current_date: DateTime<Local>,
|
||||
selected_date: DateTime<Local>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CalendarState {
|
||||
internal_state: Arc<Mutex<CalendarStateInternal>>,
|
||||
}
|
||||
|
||||
impl CalendarState {
|
||||
pub fn new() -> Self {
|
||||
let current_date = Local::now();
|
||||
Self {
|
||||
internal_state: Arc::new(Mutex::new(CalendarStateInternal {
|
||||
current_date,
|
||||
selected_date: current_date,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_next_month(&self) -> DateTime<Local> {
|
||||
let mut locker = self.internal_state.lock().unwrap();
|
||||
let new_date = locker
|
||||
.current_date
|
||||
.checked_add_months(Months::new(1))
|
||||
.unwrap();
|
||||
locker.current_date = new_date;
|
||||
new_date
|
||||
}
|
||||
|
||||
pub fn to_previous_month(&self) -> DateTime<Local> {
|
||||
let mut locker = self.internal_state.lock().unwrap();
|
||||
let new_date = locker
|
||||
.current_date
|
||||
.checked_sub_months(Months::new(1))
|
||||
.unwrap();
|
||||
locker.current_date = new_date;
|
||||
new_date
|
||||
}
|
||||
|
||||
pub fn get_current_date(&self) -> DateTime<Local> {
|
||||
self.internal_state.lock().unwrap().current_date
|
||||
}
|
||||
|
||||
pub fn get_selected_date(&self) -> DateTime<Local> {
|
||||
self.internal_state.lock().unwrap().selected_date
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(calendar: Element) {
|
||||
let prev: Element = calendar.selector(".prev");
|
||||
let next: Element = calendar.selector(".next");
|
||||
|
||||
let current_month = Arc::new(AtomicU32::new(Local::now().month()));
|
||||
let current_year = Arc::new(AtomicI32::new(Local::now().year()));
|
||||
let state = CalendarState::new();
|
||||
|
||||
display_month(calendar, Local::now().year(), Local::now().month());
|
||||
display_month(&calendar, state.get_current_date());
|
||||
|
||||
let calendar_clone = calendar.clone();
|
||||
let current_month_clone = current_month.clone();
|
||||
let current_year_clone = current_year.clone();
|
||||
let state_clone = state.clone();
|
||||
EventListener::new(&prev, "click", move |_event| {
|
||||
let mut m = current_month_clone.load(Ordering::Relaxed) - 1;
|
||||
if m == 0 {
|
||||
current_year_clone.fetch_sub(1, Ordering::Relaxed);
|
||||
m = 12
|
||||
}
|
||||
current_month_clone.store(m, Ordering::Relaxed);
|
||||
display_month(
|
||||
&calendar_clone,
|
||||
current_year_clone.load(Ordering::Relaxed),
|
||||
m,
|
||||
);
|
||||
let m = state_clone.to_previous_month();
|
||||
display_month(&calendar_clone, m);
|
||||
})
|
||||
.forget();
|
||||
|
||||
let calendar_clone = calendar.clone();
|
||||
let current_month_clone = current_month.clone();
|
||||
let current_year_clone = current_year.clone();
|
||||
let state_clone = state.clone();
|
||||
EventListener::new(&next, "click", move |_event| {
|
||||
let mut m = current_month_clone.load(Ordering::Relaxed) + 1;
|
||||
if m == 13 {
|
||||
current_year_clone.fetch_add(1, Ordering::Relaxed);
|
||||
m = 1
|
||||
}
|
||||
current_month_clone.store(m, Ordering::Relaxed);
|
||||
display_month(
|
||||
&calendar_clone,
|
||||
current_year_clone.load(Ordering::Relaxed),
|
||||
m,
|
||||
);
|
||||
let m = state_clone.to_next_month();
|
||||
display_month(&calendar_clone, m);
|
||||
})
|
||||
.forget();
|
||||
|
||||
// now.weekday()
|
||||
// let days: Element = calendar.selector(".days");
|
||||
// let state_clone = state.clone();
|
||||
// EventListener::new(&days, "click", move |event| {
|
||||
// log!(event);
|
||||
// let target: Element = event.target().unwrap().dyn_into().unwrap();
|
||||
// if
|
||||
// })
|
||||
// .forget();
|
||||
|
||||
// console!(now.to_string());
|
||||
// let calendar_clone = calendar.clone();
|
||||
// let current_month_clone = current_month.clone();
|
||||
// let current_year_clone = current_year.clone();
|
||||
// EventListener::new(&next, "click", move |_event| {
|
||||
// let mut m = current_month_clone.load(Ordering::Relaxed) + 1;
|
||||
// if m == 13 {
|
||||
// current_year_clone.fetch_add(1, Ordering::Relaxed);
|
||||
// m = 1
|
||||
// }
|
||||
// current_month_clone.store(m, Ordering::Relaxed);
|
||||
// display_month(
|
||||
// &calendar_clone,
|
||||
// current_year_clone.load(Ordering::Relaxed),
|
||||
// m,
|
||||
// );
|
||||
// })
|
||||
// .forget();
|
||||
}
|
||||
|
||||
// fn translate_month(month: u32) -> &'static str {
|
||||
// match
|
||||
// }
|
||||
|
||||
fn display_month(calendar: &Element, year: i32, month: u32) {
|
||||
log!(year, month);
|
||||
const NB_CALENDAR_ROW: u64 = 5;
|
||||
|
||||
fn display_month(calendar: &Element, date: DateTime<Local>) {
|
||||
calendar
|
||||
.selector::<Element>(".year")
|
||||
.set_inner_html(&year.to_string());
|
||||
.set_inner_html(&date.year().to_string());
|
||||
|
||||
for (i, m) in calendar
|
||||
.selector_all::<Element>(".month")
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if i as u32 + 1 == month {
|
||||
if i as u32 + 1 == date.month() {
|
||||
m.set_class_name("month current");
|
||||
} else {
|
||||
m.set_class_name("month");
|
||||
}
|
||||
}
|
||||
|
||||
// calendar
|
||||
// .selector::<Element>(".month")
|
||||
// .set_inner_html(&month.to_string());
|
||||
let mut current = date;
|
||||
|
||||
let mut current = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
|
||||
|
||||
// let mut day = Local:: ;
|
||||
while (current - Days::new(1)).month() == month {
|
||||
while (current - Days::new(1)).month() == date.month() {
|
||||
current = current - Days::new(1);
|
||||
}
|
||||
|
||||
|
|
@ -102,20 +146,46 @@ fn display_month(calendar: &Element, year: i32, month: u32) {
|
|||
current = current - Days::new(1);
|
||||
}
|
||||
|
||||
for i in 0..7 {
|
||||
for j in 0..5 {
|
||||
let li: Element = by_id(&format!("day-{}{}", i, j));
|
||||
li.set_inner_html(¤t.day().to_string());
|
||||
let first_day = current;
|
||||
|
||||
if current == Local::now().date_naive() {
|
||||
li.set_class_name("current-month today");
|
||||
} else if current.month() == month {
|
||||
li.set_class_name("current-month");
|
||||
for i in 0..7 {
|
||||
for j in 0..NB_CALENDAR_ROW {
|
||||
let day_element: Element = by_id(&format!("day-{}{}", i, j));
|
||||
let day_content: Element = day_element.selector(".number");
|
||||
day_content.set_inner_html(¤t.day().to_string());
|
||||
|
||||
if current == Local::now() {
|
||||
day_element.set_class_name("current-month today");
|
||||
} else if current.month() == date.month() {
|
||||
day_element.set_class_name("current-month");
|
||||
} else {
|
||||
li.set_class_name("");
|
||||
day_element.set_class_name("");
|
||||
}
|
||||
|
||||
current = current + Days::new(1);
|
||||
}
|
||||
}
|
||||
|
||||
spawn_local(async move {
|
||||
let scheduled_recipes: ron_api::ScheduledRecipes = request::get(
|
||||
"calendar/get_scheduled_recipes",
|
||||
[
|
||||
("start_date", first_day.date_naive().to_string()),
|
||||
(
|
||||
"end_date",
|
||||
(first_day + Days::new(NB_CALENDAR_ROW * 7))
|
||||
.date_naive()
|
||||
.to_string(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for recipe in scheduled_recipes.recipes {
|
||||
log!(recipe.1);
|
||||
}
|
||||
|
||||
// create_tag_elements(recipe_id, &tags.tags);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
use common::ron_api;
|
||||
use gloo::{console::log, events::EventListener, utils::window};
|
||||
use utils::by_id;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::HtmlSelectElement;
|
||||
|
||||
mod calendar;
|
||||
mod modal_dialog;
|
||||
mod on_click;
|
||||
|
|
@ -7,14 +14,6 @@ mod request;
|
|||
mod toast;
|
||||
mod utils;
|
||||
|
||||
use gloo::{console::log, events::EventListener, utils::window};
|
||||
use utils::by_id;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::HtmlSelectElement;
|
||||
|
||||
use common::ron_api;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ use crate::{
|
|||
utils::{by_id, selector_and_clone, SelectorExt},
|
||||
};
|
||||
|
||||
pub enum DialogContent<'a, T>
|
||||
where
|
||||
T: Fn(&Element),
|
||||
{
|
||||
Text(&'a str),
|
||||
CloneFromElement(&'a str, T),
|
||||
pub async fn show(element_selector: &str) -> bool {
|
||||
show_and_initialize(element_selector, async |_| {}).await
|
||||
}
|
||||
|
||||
pub async fn show<T>(content: DialogContent<'_, T>) -> bool
|
||||
pub async fn show_and_initialize<T>(element_selector: &str, initializer: T) -> bool
|
||||
where
|
||||
T: Fn(&Element),
|
||||
T: AsyncFn(Element),
|
||||
{
|
||||
let dialog: HtmlDialogElement = by_id("modal-dialog");
|
||||
|
||||
|
|
@ -25,15 +21,10 @@ where
|
|||
|
||||
let content_element = dialog.selector::<Element>(".content");
|
||||
|
||||
match content {
|
||||
DialogContent::Text(message) => content_element.set_inner_html(message),
|
||||
DialogContent::CloneFromElement(element_selector, initilizer) => {
|
||||
let element: Element = selector_and_clone(element_selector);
|
||||
content_element.set_inner_html("");
|
||||
content_element.append_child(&element).unwrap();
|
||||
initilizer(&element);
|
||||
}
|
||||
}
|
||||
let element: Element = selector_and_clone(element_selector);
|
||||
content_element.set_inner_html("");
|
||||
content_element.append_child(&element).unwrap();
|
||||
initializer(element).await;
|
||||
|
||||
dialog.show_modal().unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{cell::RefCell, rc, sync::Mutex};
|
||||
|
||||
use common::{ron_api, utils::substitute};
|
||||
use gloo::{
|
||||
events::{EventListener, EventListenerOptions},
|
||||
net::http::Request,
|
||||
|
|
@ -12,14 +13,17 @@ use web_sys::{
|
|||
KeyboardEvent,
|
||||
};
|
||||
|
||||
use common::ron_api;
|
||||
|
||||
use crate::{
|
||||
modal_dialog, request,
|
||||
toast::{self, Level},
|
||||
utils::{by_id, selector, selector_and_clone, SelectorExt},
|
||||
};
|
||||
|
||||
use futures::{
|
||||
future::{FutureExt, Ready},
|
||||
pin_mut, select, Future,
|
||||
};
|
||||
|
||||
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||
// Title.
|
||||
{
|
||||
|
|
@ -248,12 +252,18 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
|||
// Delete recipe button.
|
||||
let delete_button: HtmlInputElement = by_id("input-delete");
|
||||
EventListener::new(&delete_button, "click", move |_event| {
|
||||
let title: HtmlInputElement = by_id("input-title");
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the recipe '{}'",
|
||||
title.value()
|
||||
)))
|
||||
if modal_dialog::show_and_initialize(
|
||||
"#hidden-templates .recipe-delete-confirmation",
|
||||
async |element| {
|
||||
let title: HtmlInputElement = by_id("input-title");
|
||||
element.set_inner_html(&substitute(
|
||||
&element.inner_html(),
|
||||
"{}",
|
||||
&[&title.value()],
|
||||
));
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: recipe_id };
|
||||
|
|
@ -377,14 +387,18 @@ fn create_group_element(group: &ron_api::Group) -> Element {
|
|||
let group_element_cloned = group_element.clone();
|
||||
let delete_button: HtmlInputElement = group_element.selector(".input-group-delete");
|
||||
EventListener::new(&delete_button, "click", move |_event| {
|
||||
let name = group_element_cloned
|
||||
.selector::<HtmlInputElement>(".input-group-name")
|
||||
.value();
|
||||
// FIXME: How to avoid cloning twice?
|
||||
let group_element_cloned = group_element_cloned.clone();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the group '{}'",
|
||||
name
|
||||
)))
|
||||
if modal_dialog::show_and_initialize(
|
||||
"#hidden-templates .recipe-group-delete-confirmation",
|
||||
async move |element| {
|
||||
let name = group_element_cloned
|
||||
.selector::<HtmlInputElement>(".input-group-name")
|
||||
.value();
|
||||
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name]));
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: group_id };
|
||||
|
|
@ -515,14 +529,18 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element
|
|||
let step_element_cloned = step_element.clone();
|
||||
let delete_button: HtmlInputElement = step_element.selector(".input-step-delete");
|
||||
EventListener::new(&delete_button, "click", move |_event| {
|
||||
let action = step_element_cloned
|
||||
.selector::<HtmlTextAreaElement>(".text-area-step-action")
|
||||
.value();
|
||||
// FIXME: How to avoid cloning twice?
|
||||
let step_element_cloned = step_element_cloned.clone();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the step '{}'",
|
||||
action
|
||||
)))
|
||||
if modal_dialog::show_and_initialize(
|
||||
"#hidden-templates .recipe-step-delete-confirmation",
|
||||
async move |element| {
|
||||
let action = step_element_cloned
|
||||
.selector::<HtmlTextAreaElement>(".text-area-step-action")
|
||||
.value();
|
||||
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&action]));
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: step_id };
|
||||
|
|
@ -665,14 +683,18 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
|
|||
let ingredient_element_cloned = ingredient_element.clone();
|
||||
let delete_button: HtmlInputElement = ingredient_element.selector(".input-ingredient-delete");
|
||||
EventListener::new(&delete_button, "click", move |_event| {
|
||||
let name = ingredient_element_cloned
|
||||
.selector::<HtmlInputElement>(".input-ingredient-name")
|
||||
.value();
|
||||
// FIXME: How to avoid cloning twice?
|
||||
let ingredient_element_cloned = ingredient_element_cloned.clone();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the ingredient '{}'",
|
||||
name
|
||||
)))
|
||||
if modal_dialog::show_and_initialize(
|
||||
"#hidden-templates .recipe-ingredient-delete-confirmation",
|
||||
async move |element| {
|
||||
let name = ingredient_element_cloned
|
||||
.selector::<HtmlInputElement>(".input-ingredient-name")
|
||||
.value();
|
||||
element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name]));
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: ingredient_id };
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
use std::future::Future;
|
||||
|
||||
use common::ron_api;
|
||||
use gloo::{
|
||||
console::console,
|
||||
events::EventListener,
|
||||
|
|
@ -11,8 +14,6 @@ use web_sys::{
|
|||
KeyboardEvent,
|
||||
};
|
||||
|
||||
use common::ron_api;
|
||||
|
||||
use crate::{
|
||||
calendar, modal_dialog, request,
|
||||
toast::{self, Level},
|
||||
|
|
@ -22,15 +23,10 @@ use crate::{
|
|||
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||
let add_to_planner: Element = selector("#recipe-view .add-to-planner");
|
||||
EventListener::new(&add_to_planner, "click", move |_event| {
|
||||
// console!("CLICK".to_string());
|
||||
spawn_local(async move {
|
||||
modal_dialog::show(modal_dialog::DialogContent::CloneFromElement(
|
||||
"#hidden-templates .calendar",
|
||||
|element| {
|
||||
// console!("SETUP...".to_string());
|
||||
calendar::setup(element);
|
||||
},
|
||||
))
|
||||
modal_dialog::show_and_initialize("#hidden-templates .calendar", async |element| {
|
||||
calendar::setup(element);
|
||||
})
|
||||
.await;
|
||||
});
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use common::ron_api;
|
||||
use gloo::net::http::{Request, RequestBuilder};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use common::ron_api;
|
||||
|
||||
use crate::toast::{self, Level};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue