Add a calendar to schedule a recipe to a chosen date (WIP)
This commit is contained in:
parent
d9449de02b
commit
9d3f9e9c60
15 changed files with 441 additions and 62 deletions
121
frontend/src/calendar.rs
Normal file
121
frontend/src/calendar.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
use std::{
|
||||
ops::{AddAssign, SubAssign},
|
||||
sync::{
|
||||
atomic::{AtomicI32, AtomicU32, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use chrono::{offset::Local, Datelike, Days, NaiveDate, Weekday};
|
||||
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};
|
||||
|
||||
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()));
|
||||
|
||||
display_month(calendar, Local::now().year(), Local::now().month());
|
||||
|
||||
let calendar_clone = calendar.clone();
|
||||
let current_month_clone = current_month.clone();
|
||||
let current_year_clone = current_year.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,
|
||||
);
|
||||
})
|
||||
.forget();
|
||||
|
||||
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();
|
||||
|
||||
// now.weekday()
|
||||
|
||||
// console!(now.to_string());
|
||||
}
|
||||
|
||||
// fn translate_month(month: u32) -> &'static str {
|
||||
// match
|
||||
// }
|
||||
|
||||
fn display_month(calendar: &Element, year: i32, month: u32) {
|
||||
log!(year, month);
|
||||
|
||||
calendar
|
||||
.selector::<Element>(".year")
|
||||
.set_inner_html(&year.to_string());
|
||||
|
||||
for (i, m) in calendar
|
||||
.selector_all::<Element>(".month")
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if i as u32 + 1 == 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 = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
|
||||
|
||||
// let mut day = Local:: ;
|
||||
while (current - Days::new(1)).month() == month {
|
||||
current = current - Days::new(1);
|
||||
}
|
||||
|
||||
while current.weekday() != Weekday::Mon {
|
||||
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());
|
||||
|
||||
if current == Local::now().date_naive() {
|
||||
li.set_class_name("current-month today");
|
||||
} else if current.month() == month {
|
||||
li.set_class_name("current-month");
|
||||
} else {
|
||||
li.set_class_name("");
|
||||
}
|
||||
|
||||
current = current + Days::new(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
mod calendar;
|
||||
mod modal_dialog;
|
||||
mod on_click;
|
||||
mod recipe_edit;
|
||||
mod recipe_view;
|
||||
mod request;
|
||||
mod toast;
|
||||
mod utils;
|
||||
|
|
@ -20,16 +22,24 @@ pub fn main() -> Result<(), JsValue> {
|
|||
let location = window().location().pathname()?;
|
||||
let path: Vec<&str> = location.split('/').skip(1).collect();
|
||||
|
||||
if let ["recipe", "edit", id] = path[..] {
|
||||
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
|
||||
if let Err(error) = recipe_edit::setup_page(id) {
|
||||
log!(error);
|
||||
// if let ["recipe", "edit", id] = path[..] {
|
||||
match path[..] {
|
||||
["recipe", "edit", id] => {
|
||||
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
|
||||
if let Err(error) = recipe_edit::setup_page(id) {
|
||||
log!(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable: user editing data are now submitted as classic form data.
|
||||
// ["user", "edit"] => {
|
||||
// handles::user_edit(document)?;
|
||||
// }
|
||||
["recipe", "view", id] => {
|
||||
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
|
||||
if let Err(error) = recipe_view::setup_page(id) {
|
||||
log!(error);
|
||||
}
|
||||
}
|
||||
_ => (), // Disable: user editing data are now submitted as classic form data.
|
||||
// ["user", "edit"] => {
|
||||
// handles::user_edit(document)?;
|
||||
// }
|
||||
}
|
||||
|
||||
let select_language: HtmlSelectElement = by_id("select-website-language");
|
||||
|
|
|
|||
|
|
@ -1,16 +1,39 @@
|
|||
use futures::{future::FutureExt, pin_mut, select};
|
||||
use web_sys::{Element, HtmlDialogElement};
|
||||
|
||||
use crate::utils::{by_id, SelectorExt};
|
||||
use crate::{
|
||||
on_click,
|
||||
utils::{by_id, selector_and_clone, SelectorExt},
|
||||
};
|
||||
|
||||
use crate::on_click;
|
||||
pub enum DialogContent<'a, T>
|
||||
where
|
||||
T: Fn(&Element),
|
||||
{
|
||||
Text(&'a str),
|
||||
CloneFromElement(&'a str, T),
|
||||
}
|
||||
|
||||
pub async fn show(message: &str) -> bool {
|
||||
pub async fn show<T>(content: DialogContent<'_, T>) -> bool
|
||||
where
|
||||
T: Fn(&Element),
|
||||
{
|
||||
let dialog: HtmlDialogElement = by_id("modal-dialog");
|
||||
|
||||
let input_ok: Element = dialog.selector(".ok");
|
||||
let input_cancel: Element = dialog.selector(".cancel");
|
||||
|
||||
dialog.selector::<Element>(".content").set_inner_html(message);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show_modal().unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,22 +20,6 @@ use crate::{
|
|||
utils::{by_id, selector, selector_and_clone, SelectorExt},
|
||||
};
|
||||
|
||||
async fn reload_recipes_list(current_recipe_id: i64) {
|
||||
match Request::get("/fragments/recipes_list")
|
||||
.query([("current_recipe_id", current_recipe_id.to_string())])
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Err(error) => {
|
||||
toast::show(Level::Info, &format!("Internal server error: {}", error));
|
||||
}
|
||||
Ok(response) => {
|
||||
let list = document().get_element_by_id("recipes-list").unwrap();
|
||||
list.set_outer_html(&response.text().await.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
||||
// Title.
|
||||
{
|
||||
|
|
@ -266,10 +250,10 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
|||
EventListener::new(&delete_button, "click", move |_event| {
|
||||
let title: HtmlInputElement = by_id("input-title");
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(&format!(
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the recipe '{}'",
|
||||
title.value()
|
||||
))
|
||||
)))
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: recipe_id };
|
||||
|
|
@ -314,7 +298,7 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
|||
// Add a new group.
|
||||
{
|
||||
let button_add_group: HtmlInputElement = by_id("input-add-group");
|
||||
let on_click_add_group = EventListener::new(&button_add_group, "click", move |_event| {
|
||||
EventListener::new(&button_add_group, "click", move |_event| {
|
||||
let body = ron_api::Id { id: recipe_id };
|
||||
spawn_local(async move {
|
||||
let response: ron_api::Id = request::post("recipe/add_group", body).await.unwrap();
|
||||
|
|
@ -325,8 +309,8 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
|
|||
steps: vec![],
|
||||
});
|
||||
});
|
||||
});
|
||||
on_click_add_group.forget();
|
||||
})
|
||||
.forget();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -397,7 +381,12 @@ fn create_group_element(group: &ron_api::Group) -> Element {
|
|||
.selector::<HtmlInputElement>(".input-group-name")
|
||||
.value();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(&format!("Are you sure to delete the group '{}'", name)).await {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the group '{}'",
|
||||
name
|
||||
)))
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: group_id };
|
||||
let _ = request::delete::<(), _>("recipe/remove_group", body).await;
|
||||
let group_element = by_id::<Element>(&format!("group-{}", group_id));
|
||||
|
|
@ -530,7 +519,12 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element
|
|||
.selector::<HtmlTextAreaElement>(".text-area-step-action")
|
||||
.value();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(&format!("Are you sure to delete the step '{}'", action)).await {
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the step '{}'",
|
||||
action
|
||||
)))
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: step_id };
|
||||
let _ = request::delete::<(), _>("recipe/remove_step", body).await;
|
||||
let step_element = by_id::<Element>(&format!("step-{}", step_id));
|
||||
|
|
@ -675,12 +669,17 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
|
|||
.selector::<HtmlInputElement>(".input-ingredient-name")
|
||||
.value();
|
||||
spawn_local(async move {
|
||||
if modal_dialog::show(&format!("Are you sure to delete the ingredient '{}'", name))
|
||||
.await
|
||||
if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
|
||||
"Are you sure to delete the ingredient '{}'",
|
||||
name
|
||||
)))
|
||||
.await
|
||||
{
|
||||
let body = ron_api::Id { id: ingredient_id };
|
||||
let _ = request::delete::<(), _>("recipe/remove_ingredient", body).await;
|
||||
by_id::<Element>(&format!("ingredient-{}", ingredient_id)).remove();
|
||||
let ingredient_element = by_id::<Element>(&format!("ingredient-{}", ingredient_id));
|
||||
ingredient_element.next_element_sibling().unwrap().remove();
|
||||
ingredient_element.remove();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -689,6 +688,22 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
|
|||
ingredient_element
|
||||
}
|
||||
|
||||
async fn reload_recipes_list(current_recipe_id: i64) {
|
||||
match Request::get("/fragments/recipes_list")
|
||||
.query([("current_recipe_id", current_recipe_id.to_string())])
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Err(error) => {
|
||||
toast::show(Level::Info, &format!("Internal server error: {}", error));
|
||||
}
|
||||
Ok(response) => {
|
||||
let list = document().get_element_by_id("recipes-list").unwrap();
|
||||
list.set_outer_html(&response.text().await.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CursorPosition {
|
||||
UpperPart,
|
||||
LowerPart,
|
||||
|
|
|
|||
40
frontend/src/recipe_view.rs
Normal file
40
frontend/src/recipe_view.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use gloo::{
|
||||
console::console,
|
||||
events::EventListener,
|
||||
net::http::Request,
|
||||
utils::{document, window},
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{
|
||||
DragEvent, Element, HtmlDivElement, HtmlInputElement, HtmlSelectElement, HtmlTextAreaElement,
|
||||
KeyboardEvent,
|
||||
};
|
||||
|
||||
use common::ron_api;
|
||||
|
||||
use crate::{
|
||||
calendar, modal_dialog, request,
|
||||
toast::{self, Level},
|
||||
utils::{by_id, selector, selector_and_clone, SelectorExt},
|
||||
};
|
||||
|
||||
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);
|
||||
},
|
||||
))
|
||||
.await;
|
||||
});
|
||||
})
|
||||
.forget();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue