Skip to content

Commit

Permalink
feat: added error notification
Browse files Browse the repository at this point in the history
  • Loading branch information
CalliEve committed Dec 19, 2024
1 parent 9dac7fa commit aca2ed3
Show file tree
Hide file tree
Showing 20 changed files with 310 additions and 134 deletions.
3 changes: 1 addition & 2 deletions src/algorithm/cost_calculation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,7 @@ fn calc_station_exit_cost(
station.get_pos(),
node,
true,
)
.map(|c| c / 5.0);
);
}
}
}
Expand Down
36 changes: 35 additions & 1 deletion src/algorithm/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::{
IDData,
IDManager,
},
Error,
};

/// The response from the algorithm.
Expand All @@ -40,6 +41,8 @@ pub struct AlgorithmResponse {
/// The data for the [`IDManager`] after the algorithm has run, ensuring the
/// main thread will not create IDs in conflict with those in the map.
pub id_manager_data: IDData,
/// If an error occurred during the algorithm, this contains it.
pub error: Option<Error>,
}

/// The inner state of the executor.
Expand All @@ -51,6 +54,8 @@ struct ExecutorState {
done: bool,
/// The waker for the stream.
waker: Option<std::task::Waker>,
/// Any error that occurred during the algorithm.
error: Option<Error>,
}

/// The executor for the algorithm.
Expand All @@ -74,6 +79,7 @@ impl AlgorithmExecutor {
last_res: None,
done: false,
waker: None,
error: None,
})),
midway_updates,
};
Expand Down Expand Up @@ -112,6 +118,9 @@ impl AlgorithmExecutor {
closure_executor
.update_last_res(map, IDManager::to_data(), res.is_ok())
.await;
closure_executor
.set_error(res.err())
.await;
closure_executor
.mark_done()
.await;
Expand All @@ -129,6 +138,7 @@ impl AlgorithmExecutor {
success,
map,
id_manager_data,
error: None,
};

self.inner
Expand All @@ -147,6 +157,23 @@ impl AlgorithmExecutor {
.take()
}

/// Set the error of the algorithm.
async fn set_error(&self, error: Option<Error>) {
self.inner
.lock()
.await
.error = error;
}

/// Get the error of the algorithm.
async fn get_error(&self) -> Option<Error> {
self.inner
.lock()
.await
.error
.clone()
}

/// Mark the algorithm and thus stream as done.
async fn mark_done(&self) {
self.inner
Expand Down Expand Up @@ -209,13 +236,20 @@ impl Stream for AlgorithmExecutor {
let res = this
.pop_last_res()
.now_or_never();
let error = this
.get_error()
.now_or_never()
.flatten();
let done = this
.get_done()
.now_or_never()
.unwrap_or(false);

match res {
Some(Some(res)) => std::task::Poll::Ready(Some(res)),
Some(Some(mut res)) => {
res.error = error;
std::task::Poll::Ready(Some(res))
},
Some(None) if done => std::task::Poll::Ready(None),
_ => std::task::Poll::Pending,
}
Expand Down
29 changes: 2 additions & 27 deletions src/components/atoms/canvas_info_box.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
//! Contains the [`CanvasInfoBox`] component.
use ev::MouseEvent;
use leptos::*;
use wasm_bindgen::JsCast;

use crate::MapState;

/// A generic canvas info box that others can be based upon.
#[allow(clippy::needless_pass_by_value)] // cannot be a reference because of the `Fn` trait
#[component]
pub fn CanvasInfoBox<S, C>(
pub fn CanvasInfoBox<S>(
/// The title of the info box,
title: S,
/// If the info box should be shown.
click_position: Signal<Option<(f64, f64)>>,
/// Gets called if the info box is closed by clicking outside the box.
on_close: C,
/// The body of the info box if applicable.
#[prop(optional)]
children: Option<Children>,
) -> impl IntoView
where
S: ToString + 'static,
C: Fn() + 'static,
{
let info_box_ref: NodeRef<html::Div> = create_node_ref();
let map_state =
use_context::<RwSignal<MapState>>().expect("to have found the global map state");

let on_outside_click = move |e: MouseEvent| {
// actual dom node that got clicked on
let target_node = e
.target()
.and_then(|t| {
t.dyn_ref::<web_sys::Node>()
.cloned()
});

// if the clicked node is outside the modal itself
if !info_box_ref
.get()
.unwrap()
.contains(target_node.as_ref())
{
on_close();
}
};

let show = move || {
click_position
.get()
Expand Down Expand Up @@ -90,8 +66,7 @@ where
tabindex="-1"
style:display=move || if show() {"block"} else {"none"}
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 w-full md:inset-0 h-[calc(100%-0.125rem)] max-h-full"
style:pointer-events="none"
on:click=on_outside_click>
style:pointer-events="none">
<div
_ref=info_box_ref
style:pointer-events="auto"
Expand Down
11 changes: 8 additions & 3 deletions src/components/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ use wasm_bindgen::{
JsValue,
};

use crate::components::MapState;
use crate::components::{
ErrorState,
MapState,
};

mod dbl_click;
mod keydown;
Expand Down Expand Up @@ -49,6 +52,8 @@ pub fn Canvas() -> impl IntoView {
let canvas_ref = create_node_ref::<HtmlCanvas>();
let map_state =
use_context::<RwSignal<MapState>>().expect("to have found the global map state");
let error_state =
use_context::<RwSignal<ErrorState>>().expect("to have found the global error state");

// ensures we know the size of the canvas and that one page resizing, the canvas
// is also resized.
Expand Down Expand Up @@ -99,13 +104,13 @@ pub fn Canvas() -> impl IntoView {
_ref=canvas_ref

on:mousedown=move |ev| map_state.update(|state| on_mouse_down(state, ev.as_ref(), ev.shift_key()))
on:mouseup=move |ev| map_state.update(|state| on_mouse_up(state, ev.as_ref(), ev.shift_key()))
on:mouseup=move |ev| map_state.update(|state| on_mouse_up(state, error_state, ev.as_ref(), ev.shift_key()))
on:mousemove=move |ev| on_mouse_move(&map_state, ev.as_ref())
on:mouseout=move |_| map_state.update(on_mouse_out)
on:dblclick=move |ev| map_state.update(|state| on_dbl_click(state, ev.as_ref(), ev.shift_key()))

on:touchstart=move |ev| map_state.update(|state| on_mouse_down(state, ev.as_ref(), ev.shift_key()))
on:touchend=move |ev| map_state.update(|state| on_mouse_up(state, ev.as_ref(), ev.shift_key()))
on:touchend=move |ev| map_state.update(|state| on_mouse_up(state, error_state, ev.as_ref(), ev.shift_key()))
on:touchmove=move |ev| on_mouse_move(&map_state, ev.as_ref())
on:touchcancel=move |_| map_state.update(on_mouse_out)

Expand Down
20 changes: 17 additions & 3 deletions src/components/canvas/mouse_up.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
//! Contains the mouseup event handler for the [`Canvas`] component.
use leptos::{
RwSignal,
SignalUpdate,
};
use web_sys::UiEvent;

use super::other::{
canvas_click_pos,
recalculate_edge_nodes,
};
use crate::{
components::state::ActionType,
components::{
state::ActionType,
ErrorState,
},
models::{
GridNode,
SelectedStation,
Expand All @@ -18,7 +25,12 @@ use crate::{
/// Listener for the [mouseup] event on the canvas.
///
/// [mouseup]: https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseup_event
pub fn on_mouse_up(map_state: &mut MapState, ev: &UiEvent, shift_key: bool) {
pub fn on_mouse_up(
map_state: &mut MapState,
error_state: RwSignal<ErrorState>,
ev: &UiEvent,
shift_key: bool,
) {
if ev.detail() > 1 {
return;
}
Expand Down Expand Up @@ -51,7 +63,9 @@ pub fn on_mouse_up(map_state: &mut MapState, ev: &UiEvent, shift_key: bool) {
},
ActionType::RemoveLine => {
if let Some(selected_line) = map.line_at_node(mouse_pos) {
map.remove_line(selected_line.get_id());
if let Err(err) = map.remove_line(selected_line.get_id()) {
error_state.update(|state| state.set_error(err));
}
}
},
ActionType::Lock => {
Expand Down
1 change: 1 addition & 0 deletions src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod state;
pub use pages::Home;
pub use state::{
CanvasState,
ErrorState,
MapState,
StateProvider,
};
7 changes: 1 addition & 6 deletions src/components/molecules/edge_info_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,7 @@ pub fn EdgeInfoBox() -> impl IntoView {
<Show when=edge_was_clicked>
<CanvasInfoBox
title="Edge Info"
click_position=position
on_close=move || {
map_state.update(|state| {
state.clear_clicked_on_station();
});
}>
click_position=position>
<div>
<For each=edge_lines
key=|(_, line)| line.get_id()
Expand Down
65 changes: 65 additions & 0 deletions src/components/molecules/error_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Contains the [`ErrorBox`] component.
use leptos::*;
use wasm_bindgen::{
prelude::Closure,
JsCast,
};

use crate::components::ErrorState;

/// A pop-up box for displaying errors.
#[component]
pub fn ErrorBox() -> impl IntoView {
let error_state =
use_context::<RwSignal<ErrorState>>().expect("to have found the global error state");

let on_click = move |_| {
error_state.update(|state| state.clear_error());
};

let has_error = move || {
error_state
.get()
.has_error()
};
let error_message = move || {
let err = error_state
.get()
.get_error()
.map(|e| e.to_user_friendly_string());

if err.is_some() {
let f = Closure::wrap(Box::new(move || {
error_state.update(|state| state.clear_error());
}) as Box<dyn Fn()>);
window()
.set_timeout_with_callback_and_timeout_and_arguments_0(
f.as_ref()
.unchecked_ref(),
3000,
)
.unwrap();
f.forget();
}

err
};

view! {
<Show when=has_error>
<div
id="error-box"
tabindex="-1"
style:pointer-events="none"
class="overflow-y-auto overflow-x-hidden fixed flex top-0 right-0 left-0 z-50 justify-center items-start w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="mt-3.5 z-50 w-fit min-w-14" on:click=on_click>
<div class="bg-red-500 text-white top-2 z-50 p-3.5 rounded-lg text-lg cursor-pointer relative">
<span class="absolute -top-1 right-2 text-base">x</span>
<span>{error_message}</span>
</div>
</div>
</div>
</Show>
}
}
Loading

0 comments on commit aca2ed3

Please sign in to comment.