feat: international string sorting (#58)
This commit is contained in:
		
							
								
								
									
										31
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -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" | ||||
|   | ||||
| @@ -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 = [] | ||||
|   | ||||
| @@ -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<Duration>; 17] = [ | ||||
|     None, | ||||
| @@ -38,25 +38,25 @@ const REMINDER_OFFSETS: [Option<Duration>; 17] = [ | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()>) -> Element { | ||||
|     let projects = use_server_future(get_projects)?.unwrap().unwrap(); | ||||
|     let projects_query = use_projects_query(); | ||||
|  | ||||
|     let route = use_route::<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<Task>, 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:?}") | ||||
|                         } | ||||
|                     }, | ||||
|                 }, | ||||
|   | ||||
| @@ -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<Mutex<Collator>> = Lazy::new(|| Mutex::new(Collator::default())); | ||||
|  | ||||
| pub(crate) fn get_languages() -> Vec<Language> { | ||||
|     Vec::from([EN_US, CS_CZ]).into_iter().map(|texts| Language::from_str(texts).unwrap()).collect() | ||||
| } | ||||
|   | ||||
| @@ -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<Self> 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()) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Matouš Volf
					Matouš Volf