From bbced4bc3355b569d4ee8f8c1a4f3b551f3e7b99 Mon Sep 17 00:00:00 2001 From: marcantoinem <marc-antoine.m@outlook.com> Date: Sat, 27 Jul 2024 22:55:50 -0400 Subject: [PATCH] (feat) rework reactive system --- aep-schedule-website/src/frontend/app.rs | 10 +++--- .../components/options/courses_selector.rs | 16 ++++++--- .../src/frontend/components/options/form.rs | 5 +++ .../frontend/components/options/personal.rs | 6 ++-- .../src/frontend/components/options/search.rs | 34 +++---------------- .../src/frontend/components/options/state.rs | 34 +++++++++++++++---- .../src/frontend/components/options/todo.rs | 17 +++------- .../src/frontend/components/schedules.rs | 18 +++++----- aep-schedule-website/style/main.scss | 3 +- aep-schedule-website/style/options.scss | 8 ----- 10 files changed, 72 insertions(+), 79 deletions(-) diff --git a/aep-schedule-website/src/frontend/app.rs b/aep-schedule-website/src/frontend/app.rs index 9c5c705..eca8392 100644 --- a/aep-schedule-website/src/frontend/app.rs +++ b/aep-schedule-website/src/frontend/app.rs @@ -31,16 +31,16 @@ pub fn App() -> impl IntoView { <span class="text-2xl font-semibold leading-none font-sans tracking-tight">"Générateur d'horaire de l'AEP" <span class="text-amber-600">"v2"</span> </span> - <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/">"Générateur d'horaire"</A> - <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/local">"Horaire d'un local"</A> - <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/apropos">"À propos"</A> + <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/">"Générateur d'horaire"</A> + <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/local">"Horaire d'un local"</A> + <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/apropos">"À propos"</A> <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty/-/issues/new" class="sources pad-left" target="_blank"> - <span class="rounded-md font-medium text-lg font-sans tracking-tight">"Signaler un bug"</span> + <span class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight">"Signaler un bug"</span> <Bug weight=IconWeight::Regular size="3vh"/> </a> - <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty" class="sources" target="_blank" ><span class="rounded-md font-medium text-lg font-sans tracking-tight">"Sources "</span><GitlabLogo weight=IconWeight::Regular size="3vh"/></a> + <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty" class="sources" target="_blank" ><span class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight">"Sources "</span><GitlabLogo weight=IconWeight::Regular size="3vh"/></a> </nav> <div class=move || is_active.get() + " hamburger" on:click=move |_| { set_active.update(|text| { diff --git a/aep-schedule-website/src/frontend/components/options/courses_selector.rs b/aep-schedule-website/src/frontend/components/options/courses_selector.rs index f1d46f5..ca0c25e 100644 --- a/aep-schedule-website/src/frontend/components/options/courses_selector.rs +++ b/aep-schedule-website/src/frontend/components/options/courses_selector.rs @@ -150,14 +150,16 @@ pub fn CoursesSelector<F>(state: OptionState, submit: F) -> impl IntoView where F: Fn() + Copy + 'static, { - let (selections, set_selections) = state.selections; let (active_tab, set_active_tab) = create_signal("".to_string()); + + let action_courses = state.action_courses; + view! { <Await future=get_courses let:courses > - <SearchCourse courses=courses.clone() set_selections set_active_tab submit/> + <SearchCourse courses=courses.clone() action_courses set_active_tab/> </Await> <div class="row-container tab-width"> <button class="tab-button chips" class=("tab-selected", move || active_tab.get() == "") id="personal" on:click={ @@ -167,7 +169,7 @@ where {"Horaire personnel"} </button> <For - each=selections + each=move || {action_courses.value().get().unwrap_or_default()} key=|c| c.sigle.clone() children=move |course| { let sigle = course.sigle.to_string(); @@ -182,7 +184,11 @@ where <button class="close" on:click={ let sigle = sigle.clone(); move |_| { - set_selections.update(|courses| courses.retain(|c| c.sigle.as_str() != sigle)); + action_courses.value().update(|courses| { + if let Some(courses) = courses { + courses.retain(|c| c.sigle.as_str() != sigle); + }} + ); submit(); } }><X weight=IconWeight::Regular size="16px"/></button> @@ -195,7 +201,7 @@ where <PersonalTimeSelector week=state.week submit></PersonalTimeSelector> </Tab> <For - each=selections + each=move || {action_courses.value().get().unwrap_or_default()} key=|c| c.sigle.clone() let:course > diff --git a/aep-schedule-website/src/frontend/components/options/form.rs b/aep-schedule-website/src/frontend/components/options/form.rs index 4eee55c..d7d6efa 100644 --- a/aep-schedule-website/src/frontend/components/options/form.rs +++ b/aep-schedule-website/src/frontend/components/options/form.rs @@ -31,6 +31,11 @@ where action.dispatch((&state).into()); }; + create_local_resource(state.action_courses.pending(), move |_| { + submit(); + async move {} + }); + let submit_mobile = move |_| action.dispatch((&state).into()); view! { diff --git a/aep-schedule-website/src/frontend/components/options/personal.rs b/aep-schedule-website/src/frontend/components/options/personal.rs index 308490e..be7f6e0 100644 --- a/aep-schedule-website/src/frontend/components/options/personal.rs +++ b/aep-schedule-website/src/frontend/components/options/personal.rs @@ -73,13 +73,13 @@ where let day = week[i].get(); let hour = day & (1 << j); if hour != 0 { - "selected-hour" + "touch-none selected-hour" } else { - "" + "touch-none" } }; view! { - <div class="pointer-events-none touch-none" style=style class=class + <div style=style class=class on:pointerdown=move |e| { set_initial.set(Some((i, j))); set_positive.set((week[i].get() & (1 << j)) == 0); diff --git a/aep-schedule-website/src/frontend/components/options/search.rs b/aep-schedule-website/src/frontend/components/options/search.rs index ea932b6..02773d9 100644 --- a/aep-schedule-website/src/frontend/components/options/search.rs +++ b/aep-schedule-website/src/frontend/components/options/search.rs @@ -1,21 +1,14 @@ use super::state::ReactiveCourse; -use crate::{ - backend::routes::get_course, - frontend::components::common::autocomplete::{AutoComplete, AutoCompleteOption}, -}; +use crate::frontend::components::common::autocomplete::{AutoComplete, AutoCompleteOption}; use aep_schedule_generator::data::course::CourseName; use leptos::*; #[component] -pub fn SearchCourse<F>( +pub fn SearchCourse( courses: Result<Vec<CourseName>, ServerFnError>, - set_selections: WriteSignal<Vec<ReactiveCourse>>, set_active_tab: WriteSignal<String>, - submit: F, -) -> impl IntoView -where - F: Fn() + Copy + 'static, -{ + action_courses: Action<String, Vec<ReactiveCourse>>, +) -> impl IntoView { let Ok(courses) = courses else { return None; }; @@ -24,26 +17,9 @@ where .map(|c| AutoCompleteOption::new(c.sigle.clone(), c.sigle + " - " + &c.name)) .collect(); - let add_course = create_action( - move |(sigle, set): &(String, WriteSignal<Vec<ReactiveCourse>>)| { - let sigle = sigle.clone(); - let set = *set; - async move { - if let Ok(c) = get_course(sigle).await { - set.update(|s| { - if !s.iter().any(|react_c| react_c.sigle == c.sigle) { - s.push(c.into()) - } - }); - submit(); - } - } - }, - ); - let on_submit = move |sigle: String| { set_active_tab(sigle.clone()); - add_course.dispatch((sigle, set_selections)); + action_courses.dispatch(sigle); }; Some(view! { diff --git a/aep-schedule-website/src/frontend/components/options/state.rs b/aep-schedule-website/src/frontend/components/options/state.rs index 23537ae..8c9989b 100644 --- a/aep-schedule-website/src/frontend/components/options/state.rs +++ b/aep-schedule-website/src/frontend/components/options/state.rs @@ -5,12 +5,12 @@ use aep_schedule_generator::{ use compact_str::CompactString; use leptos::*; +use crate::backend::routes::get_course; + #[derive(Copy, Clone)] pub struct OptionState { - pub selections: ( - ReadSignal<Vec<ReactiveCourse>>, - WriteSignal<Vec<ReactiveCourse>>, - ), + pub stored_courses: StoredValue<Vec<ReactiveCourse>>, + pub action_courses: Action<String, Vec<ReactiveCourse>>, pub week: [RwSignal<u64>; 5], pub max_nb_conflicts: RwSignal<u8>, pub day_off: RwSignal<u8>, @@ -51,8 +51,27 @@ pub struct ReactiveCourse { impl Default for OptionState { fn default() -> Self { + let stored_courses: StoredValue<Vec<ReactiveCourse>> = store_value(vec![]); + + let action_courses = create_action(move |sigle: &String| { + let sigle = sigle.clone(); + async move { + if let Ok(c) = get_course(sigle).await { + if !stored_courses + .get_value() + .iter() + .any(|react_c| react_c.sigle == c.sigle) + { + stored_courses.update_value(|courses| courses.push(c.into())); + } + } + stored_courses.get_value() + } + }); + Self { - selections: create_signal(vec![]), + stored_courses, + action_courses, max_nb_conflicts: create_rw_signal(0), week: std::array::from_fn(|_i| create_rw_signal(0)), day_off: create_rw_signal(3), @@ -179,9 +198,10 @@ impl From<Course> for ReactiveCourse { impl From<&OptionState> for SchedulesOptions { fn from(state: &OptionState) -> Self { let courses_to_take = state - .selections - .0 + .action_courses + .value() .get() + .unwrap_or_default() .into_iter() .map(|c| c.into()) .collect(); diff --git a/aep-schedule-website/src/frontend/components/options/todo.rs b/aep-schedule-website/src/frontend/components/options/todo.rs index 88d9f10..526f973 100644 --- a/aep-schedule-website/src/frontend/components/options/todo.rs +++ b/aep-schedule-website/src/frontend/components/options/todo.rs @@ -16,9 +16,9 @@ pub fn Step( ) -> impl IntoView { let bg_color = move || { match n.cmp(&step.get()) { - Ordering::Less => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-green-400", - Ordering::Greater => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full", - Ordering::Equal => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-amber-400", + Ordering::Less => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-green-400", + Ordering::Greater => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-gray-100", + Ordering::Equal => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-amber-400", } }; @@ -66,13 +66,6 @@ pub fn Todo( } }; - let bg_color = move || { - match step.get().cmp(&5) { - Ordering::Less => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full", - Ordering::Greater | Ordering::Equal => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-green-400", - } - }; - view! { <div class="px-4 py-4 mx-auto"> <div class="grid gap-6 row-gap-10"> @@ -84,14 +77,14 @@ pub fn Todo( <div class="flex items-center"> <div class="flex flex-col items-center mr-4"> <div> - <div class=bg_color> + <div class="flex transition-colors items-center justify-center w-10 h-10 border rounded-full" class=("bg-gray-100", move || step.get() != 5) class=("bg-green-400", move || step.get() == 5)> <svg class="w-6 text-gray-600" stroke="currentColor" viewBox="0 0 24 24"> <polyline fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="6,12 10,16 18,8"></polyline> </svg> </div> </div> </div> - <button on:click=submit class="select-none rounded-lg bg-amber-500 py-1 text-lg font-sans font-semibold px-2 w-64 self-center text-center align-middle text-black shadow-md shadow-amber-500/20 transition-all hover:shadow-lg hover:shadow-amber-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none" prop:disabled=disab disabled>"Générer les horaires"</button> + <button on:click=submit class="select-none rounded-lg bg-amber-500 py-1 text-lg font-sans font-semibold px-2 w-64 self-center text-center align-middle text-black shadow-md shadow-amber-500/20 transition-all hover:shadow-lg hover:shadow-amber-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none" prop:disabled=disab>"Générer les horaires"</button> </div> </div> </div> diff --git a/aep-schedule-website/src/frontend/components/schedules.rs b/aep-schedule-website/src/frontend/components/schedules.rs index caf96ee..2ec6422 100644 --- a/aep-schedule-website/src/frontend/components/schedules.rs +++ b/aep-schedule-website/src/frontend/components/schedules.rs @@ -14,15 +14,15 @@ pub fn SchedulesComponent( <Await future=get_calendar children=move |calendar| { - match read_signal.get() { - Some(result) => result.into_iter().rev().map(|schedule| { - let calendar = calendar.clone().unwrap(); - let schedule = schedule.clone(); - view! { - <ScheduleComponent schedule calendar/> - } - }).collect_view(), - None => view ! { + match (read_signal.get(), step.get() == 5) { + (Some(result), true) => result.into_iter().rev().map(|schedule| { + let calendar = calendar.clone().unwrap(); + let schedule = schedule.clone(); + view! { + <ScheduleComponent schedule calendar/> + } + }).collect_view(), + _ => view ! { <Todo action step/> } } diff --git a/aep-schedule-website/style/main.scss b/aep-schedule-website/style/main.scss index 93b18b3..8ff3c65 100644 --- a/aep-schedule-website/style/main.scss +++ b/aep-schedule-website/style/main.scss @@ -229,6 +229,7 @@ nav.active { } [aria-current]:not([aria-current="false"]) { - font-weight: bold; + color: black; text-decoration: underline rgb(245 158 11); + text-decoration-thickness: 2px; } \ No newline at end of file diff --git a/aep-schedule-website/style/options.scss b/aep-schedule-website/style/options.scss index d92cd29..5fea310 100644 --- a/aep-schedule-website/style/options.scss +++ b/aep-schedule-website/style/options.scss @@ -80,9 +80,6 @@ .group-text-col { justify-content: space-between; - max-height: 22px; - gap: 8px; - flex-wrap: nowrap; } @keyframes translateOpen { @@ -95,11 +92,6 @@ } } -.group-text-col:hover { - max-height: fit-content; - animation: 0.5s ease-out 1 translateOpen; -} - .group-text { gap: 3px; width: 100%; -- GitLab