From bd89ba48b0042d14462ca15f05371232ba1e9551 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Manningham <marc-antoine.m@outlook.com> Date: Tue, 10 Dec 2024 22:53:47 -0500 Subject: [PATCH] fix: make the generator work again --- .../src/frontend/components/options/form.rs | 17 +---- .../src/frontend/components/options/search.rs | 5 +- .../src/frontend/components/options/todo.rs | 8 +- .../src/frontend/components/schedules.rs | 73 ++++++++----------- .../src/frontend/pages/generator.rs | 35 ++++----- .../src/frontend/state/action_add_course.rs | 31 ++++++++ .../src/frontend/state/mod.rs | 61 +++++++++------- .../src/frontend/state/reactive_course.rs | 4 +- 8 files changed, 125 insertions(+), 109 deletions(-) create mode 100644 aep-schedule-website/src/frontend/state/action_add_course.rs diff --git a/aep-schedule-website/src/frontend/components/options/form.rs b/aep-schedule-website/src/frontend/components/options/form.rs index 0e1f693..f8e91c1 100644 --- a/aep-schedule-website/src/frontend/components/options/form.rs +++ b/aep-schedule-website/src/frontend/components/options/form.rs @@ -3,31 +3,20 @@ use crate::frontend::{ common::number_input::NumberInput, options::{courses_selector::CoursesSelector, optimizations::SelectOptimizations}, }, - pages::generator::FirstGenerationDone, state::OptionState, }; use leptos::prelude::*; #[component] pub fn OptionsForms() -> impl IntoView { - let first_generation_done: FirstGenerationDone = use_context().unwrap(); - let state = OptionState::from_context(); + let submit = move || { - state.validate(); - if !first_generation_done.0.get() || state.step.get() < 5 { - return; - } - state.generate(); + state.submit(); }; let submit_mobile = move |_| { - state.validate(); - if state.step.get() < 5 { - state.hide.set(true); - return; - } - state.generate(); + state.submit_mobile(); }; view! { diff --git a/aep-schedule-website/src/frontend/components/options/search.rs b/aep-schedule-website/src/frontend/components/options/search.rs index 43d5070..3b66f1a 100644 --- a/aep-schedule-website/src/frontend/components/options/search.rs +++ b/aep-schedule-website/src/frontend/components/options/search.rs @@ -1,6 +1,6 @@ use crate::frontend::{ components::common::autocomplete::{AutoComplete, AutoCompleteOption}, - state::OptionState, + state::action_add_course::ActionAddCourse, }; use aep_schedule_generator::data::course::CourseName; use leptos::prelude::*; @@ -13,13 +13,12 @@ pub fn SearchCourse( let Ok(courses) = all_courses else { return None; }; - let state = OptionState::from_context(); let courses = courses .into_iter() .map(|c| AutoCompleteOption::new(c.sigle.clone(), c.sigle + " - " + &c.name)) .collect(); - let action_courses = state.action_courses; + let action_courses = ActionAddCourse::from_context().0; let on_submit = move |sigle: String| { set_active_tab(sigle.clone()); action_courses.dispatch(sigle); diff --git a/aep-schedule-website/src/frontend/components/options/todo.rs b/aep-schedule-website/src/frontend/components/options/todo.rs index 50acbbb..8104fd7 100644 --- a/aep-schedule-website/src/frontend/components/options/todo.rs +++ b/aep-schedule-website/src/frontend/components/options/todo.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use crate::frontend::components::icons::warning_circle::WarningCircle; -use crate::frontend::{pages::generator::FirstGenerationDone, state::OptionState}; +use crate::frontend::state::OptionState; use leptos::prelude::*; #[component] @@ -62,12 +62,10 @@ pub fn Step( #[component] pub fn Todo() -> impl IntoView { - let first_generation_done: FirstGenerationDone = use_context().unwrap(); - let state = OptionState::from_context(); let submit = move |_| { - first_generation_done.0.set(true); - state.generate(); + state.first_generation_done.set_value(true); + state.submit(); }; let step: RwSignal<u8> = state.step; diff --git a/aep-schedule-website/src/frontend/components/schedules.rs b/aep-schedule-website/src/frontend/components/schedules.rs index 2ebe5ca..4b661e4 100644 --- a/aep-schedule-website/src/frontend/components/schedules.rs +++ b/aep-schedule-website/src/frontend/components/schedules.rs @@ -17,49 +17,40 @@ pub fn SchedulesComponent() -> impl IntoView { let calendar = Arc::new(calendar.clone().unwrap()); let bad_generation = state.schedule.get().is_empty(); let generated = state.step.get() == 6; - match generated && !bad_generation { - true => { - view! { - <For - each=move || state.schedule.get() - key=|course| course.id - children=move |schedule| { - let calendar = Arc::clone(&calendar); - view! { <ScheduleComponent schedule calendar /> } - } - /> + view! { + {move || { + let bad_generation = state.schedule.get().is_empty(); + let generated = state.step.get() == 6; + if !(generated && !bad_generation) { + Some(view! { <Todo /> }) + } else { + None } - .into_any() - } - _ => { - view! { - <Todo /> - <For - each=move || state.schedule.get() - key=|course| course.id - children=move |schedule| { - let calendar = Arc::clone(&calendar); - view! { <ScheduleComponent schedule calendar /> } - } - /> - {match generated && bad_generation { - true => { - Some( - view! { - <div class="warning-box"> - <WarningCircle size="4em" /> - <span> - "Aucun horaire n'a pu être généré, augmentez le nombre de conflits ou ouvrez des sections. Probablement que deux groupes sont toujours en conflits." - </span> - </div> - }, - ) - } - false => None, - }} + }} + {move || { + if generated && bad_generation { + Some( + view! { + <div class="warning-box"> + <WarningCircle size="4em" /> + <span> + "Aucun horaire n'a pu être généré, augmentez le nombre de conflits ou ouvrez des sections. Probablement que deux groupes sont toujours en conflits." + </span> + </div> + }, + ) + } else { + None } - .into_any() - } + }} + <For + each=move || state.schedule.get() + key=|course| course.id + children=move |schedule| { + let calendar = Arc::clone(&calendar); + view! { <ScheduleComponent schedule calendar /> } + } + /> } } /> diff --git a/aep-schedule-website/src/frontend/pages/generator.rs b/aep-schedule-website/src/frontend/pages/generator.rs index 8babed2..c51087b 100644 --- a/aep-schedule-website/src/frontend/pages/generator.rs +++ b/aep-schedule-website/src/frontend/pages/generator.rs @@ -1,9 +1,12 @@ use crate::frontend::components::icons::{caret_double_right::CaretDoubleRight, IconWeight}; use crate::frontend::components::notifications::Notifications; use crate::frontend::components::{options::form::OptionsForms, schedules::SchedulesComponent}; +use crate::frontend::state::action_add_course::ActionAddCourse; use crate::frontend::state::OptionState; use aep_schedule_generator::data::group_sigle::SigleGroup; use leptos::prelude::*; +#[cfg(feature = "hydrate")] +use web_sys::Event; #[derive(Clone, Copy)] pub struct SetModal(WriteSignal<Option<SigleGroup>>); @@ -14,38 +17,32 @@ impl SetModal { } } -#[derive(Clone, Copy)] -pub struct FirstGenerationDone(pub RwSignal<bool>); - #[component] pub fn GeneratorPage() -> impl IntoView { - let first_generation_done = RwSignal::new(false); let (modal, set_modal) = signal(None); let state = OptionState::default(); let hide = state.hide; provide_context(state); provide_context(SetModal(set_modal)); - provide_context(FirstGenerationDone(first_generation_done)); + provide_context(ActionAddCourse::new(state)); + #[cfg(feature = "hydrate")] + let onscroll = move |ev: Event| { + use web_sys::wasm_bindgen::JsCast; + let target = ev.target().unwrap().dyn_into::<web_sys::Element>().unwrap(); + let scroll_top = target.scroll_top() as f64; + let client_height = target.client_height() as f64; + let scroll_height = target.scroll_height() as f64; + if (scroll_top + client_height >= scroll_height - 500.0) && state.step.get() == 6 { + state.regenerate(); + } + }; view! { <aside class="left-panel" class=("hide-left-panel", hide)> <OptionsForms /> </aside> <section class="right-panel"> - // on:scroll=move |ev| { - // use web_sys::wasm_bindgen::JsCast; - // let target = ev - // .target() - // .unwrap() - // .dyn_into::<web_sys::Element>() - // .unwrap(); - // let scroll_top = target.scroll_top() as f64; - // let client_height = target.client_height() as f64; - // let scroll_height = target.scroll_height() as f64; - // if (scroll_top + client_height >= scroll_height - 500.0) && state.step.get() == 6 { - // state.regenerate(); - // } - + on:scroll=onscroll <SchedulesComponent /> </section> <Notifications modal set_modal /> diff --git a/aep-schedule-website/src/frontend/state/action_add_course.rs b/aep-schedule-website/src/frontend/state/action_add_course.rs new file mode 100644 index 0000000..8bd7241 --- /dev/null +++ b/aep-schedule-website/src/frontend/state/action_add_course.rs @@ -0,0 +1,31 @@ +use leptos::prelude::*; + +use crate::backend::routes::get_course; + +use super::OptionState; + +#[derive(Clone)] +pub struct ActionAddCourse(pub Action<String, ()>); + +impl ActionAddCourse { + pub fn from_context() -> Self { + use_context().unwrap() + } + + pub fn new(state: OptionState) -> Self { + let action_courses = Action::new(move |sigle: &String| { + let sigle = sigle.clone(); + async move { + if let Ok(c) = get_course(sigle.clone()).await { + state.courses.update(|courses| { + if !courses.iter().any(|c| c.sigle == sigle) { + courses.push(c.into()); + } + }); + state.submit(); + } + } + }); + Self(action_courses) + } +} diff --git a/aep-schedule-website/src/frontend/state/mod.rs b/aep-schedule-website/src/frontend/state/mod.rs index c51a6e6..fd687f9 100644 --- a/aep-schedule-website/src/frontend/state/mod.rs +++ b/aep-schedule-website/src/frontend/state/mod.rs @@ -7,14 +7,13 @@ use aep_schedule_generator::{ use leptos::prelude::*; use reactive_course::ReactiveCourse; -use crate::backend::routes::get_course; - +pub mod action_add_course; pub mod reactive_course; #[derive(Copy, Clone)] pub struct OptionState { + pub first_generation_done: StoredValue<bool>, pub courses: RwSignal<Vec<ReactiveCourse>>, - pub action_courses: Action<String, ()>, pub week: [RwSignal<u64>; 5], pub max_nb_conflicts: RwSignal<u8>, pub day_off: RwSignal<u8>, @@ -33,10 +32,28 @@ impl OptionState { use_context().unwrap() } - pub fn validate(&self) { + pub fn submit(&self) { + self.validate(); + if !self.first_generation_done.get_value() || self.step.get() < 5 { + return; + } + self.generate(); + } + + pub fn submit_mobile(&self) { + self.validate(); + if self.step.get() < 5 { + self.hide.set(true); + return; + } + self.generate(); + } + + fn validate(&self) { let mut options: SchedulesOptions = self.into(); if options.courses_to_take.is_empty() { self.step.set(1); + self.schedule.set(vec![]); return; } let mut impossible_courses = options.get_impossible_course().into_iter(); @@ -49,6 +66,7 @@ impl OptionState { error.push_str(" sont toutes fermées."); self.section_error.set(error); self.step.set(2); + self.schedule.set(vec![]); return; } self.section_error.set("".to_string()); @@ -63,6 +81,7 @@ impl OptionState { error.push_str(" sont en conflits avec les heures libres sélectionnées."); self.personal_error.set(error); self.step.set(3); + self.schedule.set(vec![]); return; } self.personal_error.set("".to_string()); @@ -73,7 +92,7 @@ impl OptionState { }); } - pub fn generate(&self) { + fn generate(&self) { self.max_size .update_value(|v| v.store(8, Ordering::Relaxed)); self.hide.set(true); @@ -102,22 +121,9 @@ impl Default for OptionState { fn default() -> Self { let courses: RwSignal<Vec<ReactiveCourse>> = RwSignal::new(vec![]); - let action_courses = Action::new(move |sigle: &String| { - let sigle = sigle.clone(); - async move { - if let Ok(c) = get_course(sigle.clone()).await { - courses.update(|courses| { - if !courses.iter().any(|c| c.sigle == sigle) { - courses.push(c.into()); - } - }); - } - } - }); - Self { + first_generation_done: StoredValue::new(false), courses, - action_courses, max_nb_conflicts: RwSignal::new(0), week: std::array::from_fn(|_i| RwSignal::new(0)), day_off: RwSignal::new(3), @@ -135,18 +141,23 @@ impl Default for OptionState { impl From<&OptionState> for SchedulesOptions { fn from(state: &OptionState) -> Self { - let courses_to_take = state.courses.get().into_iter().map(|c| c.into()).collect(); + let courses_to_take = state + .courses + .get_untracked() + .into_iter() + .map(|c| c.into()) + .collect(); let mut max_size = 8; state .max_size .update_value(|v| max_size = v.load(Ordering::Relaxed)); - let max_nb_conflicts = state.max_nb_conflicts.get(); + let max_nb_conflicts = state.max_nb_conflicts.get_untracked(); let evaluation = EvaluationOption { - day_off: state.day_off.get(), - morning: state.morning.get(), - finish_early: state.finish_early.get(), + day_off: state.day_off.get_untracked(), + morning: state.morning.get_untracked(), + finish_early: state.finish_early.get_untracked(), }; - let user_conflicts = Week::new(state.week.map(|s| s.get() << 2)); + let user_conflicts = Week::new(state.week.map(|s| s.get_untracked() << 2)); Self { courses_to_take, max_nb_conflicts, diff --git a/aep-schedule-website/src/frontend/state/reactive_course.rs b/aep-schedule-website/src/frontend/state/reactive_course.rs index 2bda6cd..8f18bb4 100644 --- a/aep-schedule-website/src/frontend/state/reactive_course.rs +++ b/aep-schedule-website/src/frontend/state/reactive_course.rs @@ -61,10 +61,10 @@ impl From<ReactiveCourse> for Course { mut lab_groups, } => { for (i, theo) in theo_open.iter().enumerate() { - theo_groups.get_mut(i.into()).unwrap().open = theo.get(); + theo_groups.get_mut(i.into()).unwrap().open = theo.get_untracked(); } for (i, lab) in lab_open.iter().enumerate() { - lab_groups.get_mut(i.into()).unwrap().open = lab.get(); + lab_groups.get_mut(i.into()).unwrap().open = lab.get_untracked(); } CourseType::Both { theo_groups, -- GitLab