All checks were successful
actionlint check / actionlint check (pull_request) Successful in 12s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 22s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 4s
checkov check / checkov check (pull_request) Successful in 1m19s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 10s
GitLeaks check / GitLeaks check (pull_request) Successful in 13s
hadolint check / hadolint check (pull_request) Successful in 11s
htmlhint check / htmlhint check (pull_request) Successful in 41s
markdownlint check / markdownlint check (pull_request) Successful in 34s
Prettier check / Prettier check (pull_request) Successful in 37s
ShellCheck check / ShellCheck check (pull_request) Successful in 25s
Stylelint check / Stylelint check (pull_request) Successful in 40s
yamllint check / yamllint check (pull_request) Successful in 29s
Rust check / Rust check (pull_request) Successful in 1h1m8s
117 lines
3.7 KiB
Rust
117 lines
3.7 KiB
Rust
use dioxus::{
|
|
CapturedError,
|
|
fullstack::{Loader, Loading, WebSocketOptions},
|
|
prelude::*,
|
|
};
|
|
use serde::{Serialize, de::DeserializeOwned};
|
|
|
|
use crate::{
|
|
models::{category::Category, project::Project, subtask::Subtask, task::TaskWithSubtasks},
|
|
server::{
|
|
projects::get_projects, subtasks::get_subtasks_of_task,
|
|
tasks::get_tasks_with_subtasks_in_category, updates::subscribe_to_updates,
|
|
},
|
|
};
|
|
|
|
fn use_on_document_become_visible(mut callback: impl FnMut() + 'static) {
|
|
let callback = use_callback(move |_| callback());
|
|
use_effect(move || {
|
|
spawn(async move {
|
|
let mut eval = document::eval(
|
|
r#"
|
|
document.addEventListener("visibilitychange", () => {
|
|
if (!document.hidden) {
|
|
dioxus.send(0);
|
|
}
|
|
});
|
|
"#,
|
|
);
|
|
loop {
|
|
eval.recv::<u8>()
|
|
.await
|
|
.expect("The JS code returned a value not parsable to `u8`.");
|
|
callback.call(());
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn sort_loader_result<T: Ord + Clone>(
|
|
result: Result<Loader<Vec<T>>, Loading>,
|
|
) -> Result<Vec<T>, Loading> {
|
|
result.map(|loader| {
|
|
let mut items_sorted = loader();
|
|
items_sorted.sort();
|
|
items_sorted
|
|
})
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
fn use_loader_with_update_subscription<F, T, E>(
|
|
mut future: impl FnMut() -> F + 'static,
|
|
) -> Result<Loader<T>, Loading>
|
|
where
|
|
F: Future<Output = Result<T, E>> + 'static,
|
|
T: 'static + PartialEq + Serialize + DeserializeOwned,
|
|
E: Into<CapturedError> + 'static,
|
|
{
|
|
let mut refresh_tick = use_signal(|| 0u64);
|
|
let mut websocket_reset_tick = use_signal(|| 0u64);
|
|
|
|
let loader = use_loader(move || {
|
|
let _ = refresh_tick(); // Read => dependency.
|
|
future()
|
|
});
|
|
|
|
use_effect(move || {
|
|
let initial_websocket_reset_tick = websocket_reset_tick();
|
|
spawn(async move {
|
|
let Ok(socket) =
|
|
subscribe_to_updates(WebSocketOptions::new().with_automatic_reconnect()).await
|
|
else {
|
|
return;
|
|
};
|
|
while socket.recv().await.is_ok() {
|
|
if websocket_reset_tick() != initial_websocket_reset_tick {
|
|
// A new WebSocket has been created (a new task spawned), cleaning this one up.
|
|
break;
|
|
}
|
|
refresh_tick += 1;
|
|
}
|
|
});
|
|
});
|
|
|
|
/* So that when the device goes to sleep or suspends the app, the WebSocket gets recreated on
|
|
waking up. It is important to do this only on becoming visible (document.hidden == false),
|
|
because becoming hidden is the part when network may not work and thus cause errors. */
|
|
use_on_document_become_visible(move || {
|
|
websocket_reset_tick += 1;
|
|
refresh_tick += 1;
|
|
});
|
|
|
|
loader
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
pub(crate) fn use_projects() -> Result<Vec<Project>, Loading> {
|
|
let result = use_loader_with_update_subscription(get_projects);
|
|
sort_loader_result(result)
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
pub(crate) fn use_tasks_with_subtasks_in_category(
|
|
filtered_category: Category,
|
|
) -> Result<Vec<TaskWithSubtasks>, Loading> {
|
|
let result = use_loader_with_update_subscription(move || {
|
|
get_tasks_with_subtasks_in_category(filtered_category.clone())
|
|
});
|
|
sort_loader_result(result)
|
|
}
|
|
|
|
#[allow(clippy::result_large_err)]
|
|
pub(crate) fn use_subtasks_of_task(task_id: i32) -> Result<Vec<Subtask>, Loading> {
|
|
let result = use_loader_with_update_subscription(move || get_subtasks_of_task(task_id));
|
|
sort_loader_result(result)
|
|
}
|