diff --git a/Cargo.lock b/Cargo.lock index 59f64c8..77e2d3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,17 @@ dependencies = [ "piper", ] +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1258,6 +1269,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "feruca" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25789ad6dfe8de73de0d96ea93ea8270dd15e2c51c3ee9212241da3701a343ee" +dependencies = [ + "bincode", + "bstr", + "once_cell", + "rustc-hash", + "unicode-canonical-combining-class", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2991,6 +3015,7 @@ dependencies = [ "dioxus-query", "dioxus-sdk", "dotenvy", + "feruca", "serde", "serde_json", "serde_with", @@ -3295,6 +3320,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-canonical-combining-class" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6925586af9268182c711e47c0853ed84131049efaca41776d0ca97f983865c32" + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 3ba6c40..6c3c6b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ dioxus-sdk = { version = "0.5.0", features = ["i18n"] } unic-langid-impl = "0.9.5" voca_rs = "1.15.2" diesel_migrations = { version = "2.2.0", features = ["postgres"] } +feruca = "0.10.0" [features] default = [] diff --git a/src/components/task_form.rs b/src/components/task_form.rs index 909ef0f..678871e 100644 --- a/src/components/task_form.rs +++ b/src/components/task_form.rs @@ -6,15 +6,15 @@ use crate::models::task::NewTask; use crate::models::task::Task; use crate::query::{QueryErrors, QueryKey, QueryValue}; use crate::route::Route; -use crate::server::projects::get_projects; use crate::server::tasks::{create_task, delete_task, edit_task}; use chrono::Duration; use dioxus::core_macro::{component, rsx}; use dioxus::dioxus_core::Element; use dioxus::prelude::*; -use dioxus_query::prelude::use_query_client; +use dioxus_query::prelude::{use_query_client, QueryResult}; use dioxus_sdk::i18n::use_i18; use dioxus_sdk::translate; +use crate::query::projects::use_projects_query; const REMINDER_OFFSETS: [Option; 17] = [ None, @@ -38,25 +38,25 @@ const REMINDER_OFFSETS: [Option; 17] = [ #[component] pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<()>) -> Element { - let projects = use_server_future(get_projects)?.unwrap().unwrap(); + let projects_query = use_projects_query(); let route = use_route::(); let selected_category = use_signal(|| if let Some(task) = &task { - task.category().clone() - } else { - match route { - Route::CategorySomedayMaybePage => Category::SomedayMaybe, - Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), - Route::CategoryNextStepsPage => Category::NextSteps, - Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { - date: chrono::Local::now().date_naive(), - reoccurrence: None, - time: None, - }, - Route::CategoryLongTermPage => Category::LongTerm, - _ => Category::Inbox, + task.category().clone() + } else { + match route { + Route::CategorySomedayMaybePage => Category::SomedayMaybe, + Route::CategoryWaitingForPage => Category::WaitingFor(String::new()), + Route::CategoryNextStepsPage => Category::NextSteps, + Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar { + date: chrono::Local::now().date_naive(), + reoccurrence: None, + time: None, + }, + Route::CategoryLongTermPage => Category::LongTerm, + _ => Category::Inbox, + } } - } ); let category_calendar_reoccurrence_interval = use_signal(|| task.as_ref().and_then(|task| if let Category::Calendar { reoccurrence: Some(reoccurrence), .. } = task.category() { @@ -178,14 +178,32 @@ pub(crate) fn TaskForm(task: Option, on_successful_submit: EventHandler<() value: 0, {translate!(i18, "none")} }, - for project in projects { - option { - value: project.id().to_string(), - initial_selected: task.as_ref().is_some_and( - |task| task.project_id() == Some(project.id()) - ), - {project.title()} - } + match projects_query.result().value() { + QueryResult::Ok(QueryValue::Projects(projects)) + | QueryResult::Loading(Some(QueryValue::Projects(projects))) => { + let mut projects = projects.clone(); + projects.sort(); + rsx! { + for project in projects { + option { + value: project.id().to_string(), + initial_selected: task.as_ref().is_some_and( + |task| task.project_id() == Some(project.id()) + ), + {project.title()} + } + } + } + }, + QueryResult::Loading(None) => rsx! { + // TODO: Add a loading indicator. + }, + QueryResult::Err(errors) => rsx! { + div { + "Errors occurred: {errors:?}" + } + }, + value => panic!("Unexpected query result: {value:?}") } }, }, diff --git a/src/internationalization/mod.rs b/src/internationalization/mod.rs index cc2560c..db3035f 100644 --- a/src/internationalization/mod.rs +++ b/src/internationalization/mod.rs @@ -1,12 +1,17 @@ use std::ops::Deref; use std::str::FromStr; +use std::sync::Mutex; use chrono::Locale; +use dioxus::fullstack::once_cell::sync::Lazy; use dioxus_sdk::i18n::Language; +use feruca::Collator; use unic_langid_impl::LanguageIdentifier; const EN_US: &str = include_str!("en_us.json"); const CS_CZ: &str = include_str!("cs_cz.json"); +pub(crate) static COLLATOR: Lazy> = Lazy::new(|| Mutex::new(Collator::default())); + pub(crate) fn get_languages() -> Vec { Vec::from([EN_US, CS_CZ]).into_iter().map(|texts| Language::from_str(texts).unwrap()).collect() } diff --git a/src/models/project.rs b/src/models/project.rs index 8dcd419..078d283 100644 --- a/src/models/project.rs +++ b/src/models/project.rs @@ -4,6 +4,7 @@ use crate::schema::projects; use diesel::prelude::*; use serde::{Deserialize, Serialize}; use validator::Validate; +use crate::internationalization::COLLATOR; const TITLE_LENGTH_MIN: u64 = 1; const TITLE_LENGTH_MAX: u64 = 255; @@ -46,7 +47,7 @@ impl PartialOrd for Project { impl Ord for Project { fn cmp(&self, other: &Self) -> Ordering { - self.title().cmp(other.title()) + COLLATOR.lock().unwrap().collate(self.title(), other.title()) } }