build: migrate to Dioxus 0.6

This commit is contained in:
2024-12-12 18:23:02 +01:00
parent f56a85277a
commit a3708198ce
25 changed files with 3193 additions and 960 deletions

View File

@ -1,21 +1,45 @@
use crate::query::{QueryErrors, QueryKey, QueryValue};
use crate::route::Route;
use crate::server::internationalization::get_language_identifier;
use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_query::prelude::{use_init_query_client};
use crate::query::{QueryErrors, QueryKey, QueryValue};
use dioxus_sdk::i18n::{use_init_i18n};
use crate::internationalization::get_languages;
use crate::server::internationalization::get_language_identifier;
use dioxus_i18n::prelude::*;
use dioxus_i18n::unic_langid::langid;
use dioxus_query::prelude::use_init_query_client;
const FAVICON: Asset = asset!("/assets/favicon.ico");
const TAILWIND_CSS: Asset = asset!("/assets/styles/tailwind_output.css");
const FONTS_CSS: Asset = asset!("/assets/styles/fonts.css");
const INPUT_NUMBER_ARROWS_CSS: Asset = asset!("/assets/styles/input_number_arrows.css");
const INPUT_RANGE_CSS: Asset = asset!("/assets/styles/input_range.css");
#[component]
pub(crate) fn App() -> Element {
use_init_query_client::<QueryValue, QueryErrors, QueryKey>();
let language_identifier = use_server_future(get_language_identifier)?.unwrap().unwrap();
use_init_i18n(language_identifier.clone(), language_identifier, get_languages);
let language_identifier = use_server_future(get_language_identifier)?
.unwrap()
.unwrap();
use_init_i18n(|| {
I18nConfig::new(language_identifier)
.with_locale(Locale::new_static(
langid!("cs-CZ"),
include_str!("../internationalization/cs_cz.ftl"),
))
.with_locale(Locale::new_static(
langid!("en-US"),
include_str!("../internationalization/en_us.ftl"),
))
});
rsx! {
document::Link { rel: "icon", href: FAVICON }
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
document::Link { rel: "stylesheet", href: FONTS_CSS }
document::Link { rel: "stylesheet", href: INPUT_NUMBER_ARROWS_CSS }
document::Link { rel: "stylesheet", href: INPUT_RANGE_CSS }
div {
class: "min-h-screen text-zinc-200 bg-zinc-800 pt-4 pb-36",
Router::<Route> {}

View File

@ -75,7 +75,7 @@ pub(crate) fn Navigation(expanded: Signal<bool>) -> Element {
}
}
}
} else { None }}
} else { VNode::empty() }}
}
}
}

View File

@ -8,9 +8,9 @@ use chrono::{Datelike, Local};
use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_i18n::prelude::i18n;
use dioxus_i18n::t;
use dioxus_query::prelude::QueryResult;
use dioxus_sdk::i18n::use_i18;
use dioxus_sdk::translate;
const CALENDAR_LENGTH_DAYS: usize = 366 * 3;
@ -23,14 +23,12 @@ pub(crate) fn CategoryCalendarPage() -> Element {
});
let tasks_query_result = tasks.result();
let i18 = use_i18();
rsx! {
match tasks_query_result.value() {
QueryResult::Ok(QueryValue::TasksWithSubtasks(tasks))
| QueryResult::Loading(Some(QueryValue::TasksWithSubtasks(tasks))) => {
let today_date = Local::now().date_naive();
rsx! {
div {
class: "pt-4 flex flex-col gap-8",
@ -42,16 +40,15 @@ pub(crate) fn CategoryCalendarPage() -> Element {
div {
class: "pt-1",
{
date_current.format_localized(translate!(
i18,
date_current.format_localized(t!(
if date_current.year() == Local::now().year() {
"formats.date_weekday_format"
"date-weekday-format"
} else {
"formats.date_weekday_year_format"
"date-weekday-year-format"
}
).as_str(),
LocaleFromLanguageIdentifier::from(
&(i18.selected_language)()
&i18n().language()
).into()
)
.to_string()
@ -60,7 +57,7 @@ pub(crate) fn CategoryCalendarPage() -> Element {
}
TaskList {
tasks: tasks.iter().filter(|task| {
if let Category::Calendar { date, .. }
if let Category::Calendar { date, .. }
= task.task().category() {
*date == date_current
} else {
@ -70,7 +67,7 @@ pub(crate) fn CategoryCalendarPage() -> Element {
}
}
}
}
}
}
},
QueryResult::Loading(None) => rsx! {

View File

@ -7,9 +7,9 @@ use crate::query::tasks::use_tasks_with_subtasks_in_category_query;
use crate::query::QueryValue;
use chrono::Local;
use dioxus::prelude::*;
use dioxus_i18n::t;
use dioxus_i18n::use_i18n::i18n;
use dioxus_query::prelude::QueryResult;
use dioxus_sdk::i18n::use_i18;
use dioxus_sdk::translate;
use voca_rs::Voca;
#[component]
@ -25,9 +25,7 @@ pub(crate) fn CategoryTodayPage() -> Element {
let long_term_tasks_query = use_tasks_with_subtasks_in_category_query(Category::LongTerm);
let long_term_tasks_query_result = long_term_tasks_query.result();
let i18 = use_i18();
rsx! {
div {
class: "pt-4 flex flex-col gap-8",
@ -46,7 +44,7 @@ pub(crate) fn CategoryTodayPage() -> Element {
}
div {
class: "mt-1",
{translate!(i18, "long_term")._upper_first()}
{t!("long-term")._upper_first()}
}
}
div {
@ -97,7 +95,7 @@ pub(crate) fn CategoryTodayPage() -> Element {
panic!("Unexpected category.");
}
}).cloned().collect::<Vec<TaskWithSubtasks>>();
rsx! {
if !overdue_tasks.is_empty() {
div {
@ -109,7 +107,7 @@ pub(crate) fn CategoryTodayPage() -> Element {
}
div {
class: "mt-1",
{translate!(i18, "overdue")._upper_first()}
{t!("overdue")._upper_first()}
}
}
TaskList {
@ -128,18 +126,17 @@ pub(crate) fn CategoryTodayPage() -> Element {
div {
class: "mt-1",
{
let format = translate!(i18, "formats.date_weekday_format");
let format = t!("date-weekday-format");
let today_date = today_date.format_localized(
format.as_str(),
LocaleFromLanguageIdentifier::from(
&(i18.selected_language)()
&i18n().language()
).into()
).to_string();
format!(
"{} {}",
translate!(i18, "today")._upper_first(),
if translate!(i18, "formats.weekday_lowercase_first")
.parse().unwrap() {
t!("today")._upper_first(),
if t!("weekday-lowercase-first").parse().unwrap() {
today_date._lower_first()
} else {
today_date

View File

@ -116,7 +116,7 @@ pub(crate) fn SubtasksForm(task: Task) -> Element {
onchange: {
let subtask = subtask.clone();
let task = task.clone();
move |event| {
move |event: Event<FormData>| {
let subtask = subtask.clone();
let task = task.clone();
async move {

View File

@ -11,9 +11,8 @@ use chrono::Duration;
use dioxus::core_macro::{component, rsx};
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_i18n::t;
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] = [
@ -81,8 +80,6 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>();
let task_for_submit = task.clone();
let i18 = use_i18();
rsx! {
div {
class: "p-4 flex flex-col gap-4",
@ -176,7 +173,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
id: "input_project",
option {
value: 0,
{translate!(i18, "none")}
{t!("none")}
},
match projects_query.result().value() {
QueryResult::Ok(QueryValue::Projects(projects))
@ -359,12 +356,12 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
} else {
format!("{} h", offset.num_hours())
}
).unwrap_or_else(|| translate!(i18, "none"))}
).unwrap_or_else(|| t!("none"))}
}
}
}
},
_ => None
_ => VNode::empty()
}
},
if let Some(task) = task.as_ref() {

View File

@ -4,6 +4,7 @@ use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_query::prelude::use_query_client;
use tracing::info;
use crate::components::task_list_item::TaskListItem;
use crate::query::{QueryErrors, QueryKey, QueryValue};
use crate::server::tasks::complete_task;
@ -22,17 +23,17 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
div {
key: "{task.task().id()}",
class: format!(
"px-8 pt-5 {} flex flex-row gap-4 select-none {}",
"px-8 pt-4 {} flex flex-row items-center gap-4 select-none {}",
if task.task().deadline().is_some() || !task.subtasks().is_empty() {
"pb-0.5"
} else if let Category::Calendar { time, .. } = task.task().category() {
if time.is_some() {
"pb-0.5"
} else {
"pb-5"
"pb-4"
}
} else {
"pb-5"
"pb-4"
},
if task_being_edited().is_some_and(|t| t.id() == task.task().id()) {
"bg-zinc-700"
@ -44,7 +45,7 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
},
i {
class: format!(
"{} text-3xl text-zinc-500",
"{} text-3xl align-middle h-9 text-zinc-500",
if *(task.task().category()) == Category::Done {
"fa solid fa-square-check"
} else {
@ -53,12 +54,13 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
),
onclick: {
let task = task.clone();
move |event| {
move |event: Event<MouseData>| {
// To prevent editing the task.
event.stop_propagation();
let task = task.clone();
async move {
let completed_task = complete_task(task.task().id()).await.unwrap();
let completed_task = complete_task(task.task().id()).await
.unwrap();
let mut query_keys = vec![
QueryKey::Tasks,
QueryKey::TasksInCategory(

View File

@ -1,3 +1,4 @@
use dioxus_i18n::prelude::i18n;
use crate::internationalization::LocaleFromLanguageIdentifier;
use crate::models::category::Category;
use crate::models::task::TaskWithSubtasks;
@ -5,19 +6,16 @@ use chrono::{Datelike, Local};
use dioxus::core_macro::rsx;
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_sdk::i18n::use_i18;
use dioxus_sdk::translate;
use dioxus_i18n::t;
use voca_rs::Voca;
#[component]
pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
let i18 = use_i18();
rsx! {
div {
class: "flex flex-col",
div {
class: "mt-1 grow font-medium",
class: "grow font-medium",
{task.task().title()}
},
div {
@ -33,37 +31,37 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
format!(
" {}",
if deadline == today_date - chrono::Days::new(1) {
translate!(i18, "yesterday")
t!("yesterday")
} else if deadline == today_date {
translate!(i18, "today")
t!("today")
} else if deadline == today_date + chrono::Days::new(1) {
translate!(i18, "tomorrow")
t!("tomorrow")
} else if deadline > today_date
&& deadline <= today_date + chrono::Days::new(7) {
let deadline = deadline.format_localized(
"%A",
LocaleFromLanguageIdentifier::from(
&(i18.selected_language)()
&i18n().language()
).into()
).to_string();
if translate!(i18, "formats.weekday_lowercase_first")
if t!("weekday-lowercase-first")
.parse().unwrap() {
deadline._lower_first()
} else {
deadline
}
} else {
let format = translate!(i18,
let format = t!(
if deadline.year() == today_date.year() {
"formats.date_format"
"date-format"
} else {
"formats.date_year_format"
"date-year-format"
}
);
deadline.format_localized(
format.as_str(),
LocaleFromLanguageIdentifier::from(
&(i18.selected_language)()
&i18n().language()
).into()
).to_string()
}
@ -79,8 +77,8 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
class: "fa-solid fa-clock"
},
{
let format = translate!(i18, "formats.time_format");
format!(" {}",calendar_time.time().format(format.as_str()))
let format = t!("time-format");
format!(" {}", calendar_time.time().format(format.as_str()))
}
}
}

View File

@ -0,0 +1,14 @@
none = žádný
long-term = dlouhodobé
yesterday = včera
today = dnes
tomorrow = zítra
overdue = zpožděné
## Date and time formats
date-format = %-d. %B
date-year-format = %-d. %B %Y
date-weekday-format = %A %-d. %B
date-weekday-year-format = %A %-d. %B %Y
weekday-lowercase-first = true
time-format = %-H:%M

View File

@ -1,19 +0,0 @@
{
"id": "cs-CZ",
"texts": {
"none": "žádný",
"long_term": "dlouhodobé",
"yesterday": "včera",
"today": "dnes",
"tomorrow": "zítra",
"overdue": "zpožděné",
"formats": {
"date_format": "%-d. %B",
"date_year_format": "%-d. %B %Y",
"date_weekday_format": "%A %-d. %B",
"date_weekday_year_format": "%A %-d. %B %Y",
"weekday_lowercase_first": "true",
"time_format": "%-H:%M"
}
}
}

View File

@ -0,0 +1,14 @@
none = none
long-term = long-term
yesterday = yesterday
today = today
tomorrow = tomorrow
overdue = overdue
## Date and time formats
date-format = %B %-d
date-year-format = %B %-d, %Y
date-weekday-format = %A, %B %-d
date-weekday-year-format = %A, %B %-d, %Y
weekday-lowercase-first = false
time-format = %-I:%M %P

View File

@ -1,19 +0,0 @@
{
"id": "en-US",
"texts": {
"none": "none",
"long_term": "long-term",
"yesterday": "yesterday",
"today": "today",
"tomorrow": "tomorrow",
"overdue": "overdue",
"formats": {
"date_format": "%B %-d",
"date_year_format": "%B %-d, %Y",
"date_weekday_format": "%A, %B %-d",
"date_weekday_year_format": "%A, %B %-d, %Y",
"weekday_lowercase_first": "false",
"time_format": "%-I:%M %P"
}
}
}

View File

@ -1,21 +1,12 @@
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()
}
pub(crate) struct LocaleFromLanguageIdentifier<'a>(&'a LanguageIdentifier);
impl<'a> Deref for LocaleFromLanguageIdentifier<'a> {

View File

@ -11,19 +11,14 @@ mod migrations;
use components::app::App;
use dioxus::prelude::*;
use dioxus_logger::tracing::{info, Level};
use tracing::info;
fn main() {
dioxus_logger::init(Level::INFO).expect("Failed to initialize the logger.");
info!("Running migrations.");
server_only!(
migrations::run_migrations().expect("Failed to run migrations.");
);
info!("Starting app.");
let cfg = server_only!(
dioxus::fullstack::Config::new().addr(std::net::SocketAddr::from(([0, 0, 0, 0], 8000)))
);
LaunchBuilder::fullstack().with_cfg(cfg).launch(App);
launch(App);
}

View File

@ -7,7 +7,7 @@ use dotenvy::dotenv;
#[server]
pub(crate) async fn get_language_identifier() -> Result<LanguageIdentifier, ServerFnError> {
dotenv().expect("Could not load environment variables from the .env file.");
Ok(env::var("LANGUAGE_CODE")
.expect("The environment variable LANGUAGE_CODE must be set.")
.parse::<LanguageIdentifier>()?)