15 Commits

Author SHA1 Message Date
1b571ba842 docs: add a guide for running
All checks were successful
conventional pull request title check / conventional pull request title check (pull_request) Successful in 10s
actionlint check / actionlint check (pull_request) Successful in 16s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 14s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 16s
hadolint check / hadolint check (pull_request) Successful in 19s
GitLeaks check / GitLeaks check (pull_request) Successful in 21s
markdownlint check / markdownlint check (pull_request) Successful in 39s
Prettier check / Prettier check (pull_request) Successful in 40s
Stylelint check / Stylelint check (pull_request) Successful in 33s
ShellCheck check / ShellCheck check (pull_request) Successful in 44s
htmlhint check / htmlhint check (pull_request) Successful in 54s
yamllint check / yamllint check (pull_request) Successful in 41s
checkov check / checkov check (pull_request) Successful in 1m54s
Rust check / Rust check (pull_request) Successful in 13m29s
2026-03-05 14:44:34 +01:00
fd31d2f725 chore: bump Docker dependencies (#141) 2026-03-05 13:30:00 +00:00
7368d61b2f chore: bump Docker dependencies
All checks were successful
conventional pull request title check / conventional pull request title check (pull_request) Successful in 3s
actionlint check / actionlint check (pull_request) Successful in 7s
hadolint check / hadolint check (pull_request) Successful in 8s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 17s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 18s
GitLeaks check / GitLeaks check (pull_request) Successful in 34s
Prettier check / Prettier check (pull_request) Successful in 36s
Stylelint check / Stylelint check (pull_request) Successful in 33s
ShellCheck check / ShellCheck check (pull_request) Successful in 38s
yamllint check / yamllint check (pull_request) Successful in 37s
htmlhint check / htmlhint check (pull_request) Successful in 57s
markdownlint check / markdownlint check (pull_request) Successful in 55s
checkov check / checkov check (pull_request) Successful in 1m57s
Rust check / Rust check (pull_request) Successful in 17m0s
2026-03-05 14:09:43 +01:00
ed40870fc4 ci: update the checks (#140) 2026-03-05 13:03:22 +00:00
a6231f5166 chore: make the Docker production stack agnostic (#139) 2026-03-05 13:03:09 +00:00
5083c794cf ci: update the checks
All checks were successful
conventional pull request title check / conventional pull request title check (pull_request) Successful in 7s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 9s
hadolint check / hadolint check (pull_request) Successful in 8s
GitLeaks check / GitLeaks check (pull_request) Successful in 17s
actionlint check / actionlint check (pull_request) Successful in 28s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 26s
markdownlint check / markdownlint check (pull_request) Successful in 30s
Prettier check / Prettier check (pull_request) Successful in 32s
Stylelint check / Stylelint check (pull_request) Successful in 28s
htmlhint check / htmlhint check (pull_request) Successful in 42s
ShellCheck check / ShellCheck check (pull_request) Successful in 39s
yamllint check / yamllint check (pull_request) Successful in 41s
checkov check / checkov check (pull_request) Successful in 1m27s
Rust check / Rust check (pull_request) Successful in 10m22s
2026-03-05 13:50:24 +01:00
5d14df3f33 chore: make the Docker production stack agnostic
All checks were successful
conventional commit messages check / conventional commit messages check (pull_request) Successful in 5s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 9s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 10s
GitLeaks check / GitLeaks check (pull_request) Successful in 11s
actionlint check / actionlint check (pull_request) Successful in 17s
hadolint check / hadolint check (pull_request) Successful in 20s
markdownlint check / markdownlint check (pull_request) Successful in 26s
ShellCheck check / ShellCheck check (pull_request) Successful in 25s
yamllint check / yamllint check (pull_request) Successful in 23s
Stylelint check / Stylelint check (pull_request) Successful in 28s
Prettier check / Prettier check (pull_request) Successful in 29s
htmlhint check / htmlhint check (pull_request) Successful in 31s
checkov check / checkov check (pull_request) Successful in 1m37s
Rust check / Rust check (pull_request) Successful in 15m33s
2026-03-05 13:47:03 +01:00
c0d0000bcb feat: wider screen UI responsivity (#137) 2026-03-05 10:50:37 +00:00
fc0d0c158c ci: support x86 in the checks (#138) 2026-02-09 23:25:43 +00:00
11c150c1e8 ci: support x86 in the checks
All checks were successful
actionlint check / actionlint check (pull_request) Successful in 22s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 1m8s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 56s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 1m15s
GitLeaks check / GitLeaks check (pull_request) Successful in 12s
hadolint check / hadolint check (pull_request) Successful in 21s
markdownlint check / markdownlint check (pull_request) Successful in 35s
htmlhint check / htmlhint check (pull_request) Successful in 41s
Prettier check / Prettier check (pull_request) Successful in 28s
ShellCheck check / ShellCheck check (pull_request) Successful in 46s
Stylelint check / Stylelint check (pull_request) Successful in 42s
checkov check / checkov check (pull_request) Successful in 2m48s
yamllint check / yamllint check (pull_request) Successful in 45s
Rust check / Rust check (pull_request) Successful in 17m53s
2026-02-09 23:36:34 +01:00
64389d8c0d feat: wider screen UI responsivity
All checks were successful
conventional pull request title check / conventional pull request title check (pull_request) Successful in 5s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 7s
actionlint check / actionlint check (pull_request) Successful in 6s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 9s
GitLeaks check / GitLeaks check (pull_request) Successful in 16s
hadolint check / hadolint check (pull_request) Successful in 20s
htmlhint check / htmlhint check (pull_request) Successful in 1m10s
markdownlint check / markdownlint check (pull_request) Successful in 1m4s
Prettier check / Prettier check (pull_request) Successful in 58s
checkov check / checkov check (pull_request) Successful in 1m58s
ShellCheck check / ShellCheck check (pull_request) Successful in 34s
Stylelint check / Stylelint check (pull_request) Successful in 32s
yamllint check / yamllint check (pull_request) Successful in 29s
Rust check / Rust check (pull_request) Successful in 27m44s
2026-02-09 22:10:12 +01:00
70e7021a6e chore: enable Clippy pedantic lints (#136) 2026-02-09 19:25:31 +00:00
2d3e2cb3a9 chore: enable Clippy pedantic lints
All checks were successful
actionlint check / actionlint check (pull_request) Successful in 7s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 3m44s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 3m46s
GitLeaks check / GitLeaks check (pull_request) Successful in 1m45s
Prettier check / Prettier check (pull_request) Successful in 2m1s
markdownlint check / markdownlint check (pull_request) Successful in 2m4s
htmlhint check / htmlhint check (pull_request) Successful in 2m6s
ShellCheck check / ShellCheck check (pull_request) Successful in 1m15s
yamllint check / yamllint check (pull_request) Successful in 1m11s
Stylelint check / Stylelint check (pull_request) Successful in 1m13s
Rust check / Rust check (pull_request) Successful in 22m46s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 19s
hadolint check / hadolint check (pull_request) Successful in 25s
checkov check / checkov check (pull_request) Successful in 56s
2026-02-09 19:54:40 +01:00
08a5930842 feat: add an artificial task to empty the inbox (#135) 2026-02-09 17:28:58 +00:00
bdced31b4e feat: add an artificial task to empty the inbox
All checks were successful
conventional commit messages check / conventional commit messages check (pull_request) Successful in 19s
actionlint check / actionlint check (pull_request) Successful in 25s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 6s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 21s
GitLeaks check / GitLeaks check (pull_request) Successful in 20s
hadolint check / hadolint check (pull_request) Successful in 19s
htmlhint check / htmlhint check (pull_request) Successful in 57s
checkov check / checkov check (pull_request) Successful in 1m51s
markdownlint check / markdownlint check (pull_request) Successful in 47s
ShellCheck check / ShellCheck check (pull_request) Successful in 42s
Prettier check / Prettier check (pull_request) Successful in 1m0s
yamllint check / yamllint check (pull_request) Successful in 45s
Stylelint check / Stylelint check (pull_request) Successful in 59s
Rust check / Rust check (pull_request) Successful in 28m51s
2026-02-09 15:25:13 +01:00
35 changed files with 288 additions and 66 deletions

View File

@@ -1,2 +1,5 @@
# en-US or cs-CZ.
LANGUAGE_CODE=en-US
# Required only for mobile native client builds.
MOBILE_SERVER_URL=

View File

@@ -26,7 +26,16 @@ jobs:
ACTIONLINT_VERSION="1.7.5"
INSTALL_SCRIPT_CHECKSUM="99ab9f1d97c31c9a051e6902305f7ea9f48e7e7e1b0ee41f64aa831c86655168 download-actionlint.bash"
ACTIONLINT_CHECKSUM="3d74253aa0cf645e6224fd53f2d56776998c7c05a0d3c12307463285515898f8 actionlint"
ARCH="$(uname -m)"
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
ACTIONLINT_CHECKSUM="76e1b008a05f55effccb39355d76c74e5312fefa6c98253032a499b227d01149 actionlint"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
ACTIONLINT_CHECKSUM="3d74253aa0cf645e6224fd53f2d56776998c7c05a0d3c12307463285515898f8 actionlint"
else
echo "Unsupported architecture: $ARCH" >&2
exit 1
fi
wget -O download-actionlint.bash "$INSTALL_SCRIPT_URL"
echo "$INSTALL_SCRIPT_CHECKSUM" | sha256sum --check

View File

@@ -22,7 +22,7 @@ jobs:
with:
python-version: "3.13.1"
- name: Rust toolchain installation
uses: dtolnay/rust-toolchain@9bc92bc5598b4f3bec5d910d352094982cb0c3b9 # 1.92.0
uses: dtolnay/rust-toolchain@c2b55edffaf41a251c410bb32bed22afefa800f1 # 1.92.0
- name: code checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:

View File

@@ -26,7 +26,16 @@ jobs:
DOTENV_LINTER_VERSION="v3.3.0"
INSTALL_SCRIPT_CHECKSUM="3b883cbc2bc3b48b6acd794802326a50fcbfeff7d5cd61e457c54fd6072bd809 dotenv-linter-install.sh"
DOTENV_LINTER_CHECKSUM="4bf3efb743a1e3383ab3407ff48c6147dd527dac35b736e224a26425df00a2ee dotenv-linter"
ARCH="$(uname -m)"
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
DOTENV_LINTER_CHECKSUM="cfd8c16319d8ebfd7849016ed381f239e5e26e4ea5d957a26e32c12813658f26 dotenv-linter"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
DOTENV_LINTER_CHECKSUM="4bf3efb743a1e3383ab3407ff48c6147dd527dac35b736e224a26425df00a2ee dotenv-linter"
else
echo "Unsupported architecture: $ARCH" >&2
exit 1
fi
wget -O dotenv-linter-install.sh "$INSTALL_SCRIPT_URL"
echo "$INSTALL_SCRIPT_CHECKSUM" | sha256sum --check

View File

@@ -22,10 +22,21 @@ jobs:
run: |
set -e
VERSION="v8.22.0"
CHECKSUM="3f95fef7e361adafed2b1bb9c591ba3bc6b595b4f296b346257301b7bf04be15 gitleaks.tar.gz"
VERSION="8.22.0"
wget -O "gitleaks.tar.gz" "https://github.com/gitleaks/gitleaks/releases/download/$VERSION/gitleaks_8.22.0_linux_arm64.tar.gz"
ARCH="$(uname -m)"
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
TARBALL="gitleaks_${VERSION}_linux_x64.tar.gz"
CHECKSUM="ad66410e1e0bf262f864b6837b09cfa585f6b5816164023ee64847d3f7415eed gitleaks.tar.gz"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
TARBALL="gitleaks_${VERSION}_linux_arm64.tar.gz"
CHECKSUM="3f95fef7e361adafed2b1bb9c591ba3bc6b595b4f296b346257301b7bf04be15 gitleaks.tar.gz"
else
echo "Unsupported architecture: $ARCH" >&2
exit 1
fi
wget -O "gitleaks.tar.gz" "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/${TARBALL}"
echo "$CHECKSUM" | sha256sum --check
tar xzf gitleaks.tar.gz

View File

@@ -22,10 +22,21 @@ jobs:
run: |
set -e
VERSION="v2.12.0"
CHECKSUM="5798551bf19f33951881f15eb238f90aef023f11e7ec7e9f4c37961cb87c5df6 hadolint"
VERSION="2.12.0"
wget -O hadolint "https://github.com/hadolint/hadolint/releases/download/$VERSION/hadolint-Linux-arm64"
ARCH="$(uname -m)"
if [ "$ARCH" = "x86_64" ] || [ "$ARCH" = "amd64" ]; then
ASSET="hadolint-Linux-x86_64"
CHECKSUM="56de6d5e5ec427e17b74fa48d51271c7fc0d61244bf5c90e828aab8362d55010 hadolint"
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
ASSET="hadolint-Linux-arm64"
CHECKSUM="5798551bf19f33951881f15eb238f90aef023f11e7ec7e9f4c37961cb87c5df6 hadolint"
else
echo "Unsupported architecture: $ARCH" >&2
exit 1
fi
wget -O hadolint "https://github.com/hadolint/hadolint/releases/download/v${VERSION}/${ASSET}"
echo "$CHECKSUM" | sha256sum --check
chmod +x hadolint

View File

@@ -26,7 +26,7 @@ jobs:
libwebkit2gtk-4.1-dev=2.50.4-0ubuntu0.22.04.1
libxdo-dev=1:3.20160805.1-4
- name: Rust toolchain installation
uses: dtolnay/rust-toolchain@9bc92bc5598b4f3bec5d910d352094982cb0c3b9 # 1.92.0
uses: dtolnay/rust-toolchain@c2b55edffaf41a251c410bb32bed22afefa800f1 # 1.92.0
with:
components: clippy, rustfmt
- name: code checkout

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/.env
/docker-compose.yaml
/docker-compose.override.yaml
/node_modules
/target

View File

@@ -5,8 +5,6 @@ services:
dockerfile: docker/prod/app/Dockerfile
networks:
- default
- web-server-network
ports: ["8000:80"]
restart: always
depends_on: ["db"]
@@ -23,7 +21,3 @@ services:
volumes:
db_data:
networks:
web-server-network:
external: true

View File

@@ -0,0 +1,10 @@
---
services:
app:
networks:
- default
- web-server-network
networks:
web-server-network:
external: true

View File

@@ -21,7 +21,7 @@ ENV ANDROID_SDK_ROOT=/opt/android-sdk
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update && apt-get install -y --no-install-recommends \
openjdk-17-jdk-headless=17.0.17+10-1~deb12u1 \
openjdk-17-jdk-headless=17.0.18+8-1~deb12u1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& rustup target add aarch64-linux-android \
@@ -46,14 +46,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
FROM builder_base AS builder_web
RUN dx bundle --locked --release
FROM debian:bookworm@sha256:b877a1a3fdf02469440f1768cf69c9771338a875b7add5e80c45b756c92ac20a AS runner_web
RUN apt-get update \
&& apt-get install -y --no-install-recommends libpq5=15.10-0+deb12u1 \
&& apt-get install -y --no-install-recommends libpq5=15.16-0+deb12u1 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

59
readme.md Normal file
View File

@@ -0,0 +1,59 @@
# Todo Baggins
A task management app tailored specifically to my personal GTD workflow.
## Running the web server
### Development
1. Copy `.env.dev` to `.env` and, if desired, edit it.
2. Create a symlink from `docker-compose.yaml` to `docker-compose-dev.yaml`.
3. Run the Docker compose stack with
```sh
docker compose up --build -d
```
4. Enter the container shell with
```sh
docker compose exec -it app sh
```
5. Inside, start the development server with
```sh
dx serve --locked --addr 0.0.0.0 --port 8000
```
6. Once the build is finished, the web app will be accessible at
<http://localhost:8000>.
### Production
1. Copy `.env.dev` to `.env` and, if desired, edit it.
2. Create a symlink from `docker-compose.yaml` to `docker-compose-prod.yaml`.
3. Run the Docker compose stack with
```sh
docker compose up --build -d
```
4. Once the build is finished, the web app will be accessible at the Docker
container, on port 80.
## Running the Android client
1. Copy `.env.dev` to `.env` and, if desired, edit it. Set the
`MOBILE_SERVER_URL` to the URL of the server which will be available as a
backend for the mobile client.
2. Start the build with
```sh
./scripts/export_android_bundle.sh
```
3. Once it is finished, the APK will be present at `bundle/android`.
4. Run the web server as described in a previous section and make it accessible
from the client running the mobile app at the URL specified in the
environment variable.

0
scripts/export_android_bundle.sh Normal file → Executable file
View File

View File

@@ -48,7 +48,7 @@ pub(crate) fn App() -> Element {
document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" }
div {
class: "min-h-screen py-4 flex flex-col text-gray-300 bg-gray-900",
class: "min-h-screen flex flex-col text-gray-300 bg-gray-900",
Router::<Route> {}
}
}

View File

@@ -1,6 +1,7 @@
use crate::components::navigation::Navigation;
use dioxus::prelude::*;
use crate::components::navigation;
#[component]
pub(crate) fn BottomPanel() -> Element {
let navigation_expanded = use_signal(|| false);
@@ -10,12 +11,12 @@ pub(crate) fn BottomPanel() -> Element {
class: format!(
"flex flex-col pointer-events-auto bg-gray-800 transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}",
if navigation_expanded() {
"h-[130px]"
"h-[132px]"
} else {
"h-[66px]"
}
),
Navigation {
navigation::Bottom {
is_expanded: navigation_expanded,
}
}

View File

@@ -23,12 +23,12 @@ pub(crate) fn CategoryCalendarTaskList() -> Element {
rsx! {
div {
class: "pt-4 flex flex-col gap-8",
class: "pt-3 flex flex-col gap-8",
for date_current in today_date.iter_days().take(CALENDAR_LENGTH_DAYS) {
div {
class: "flex flex-col gap-4",
div {
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
class: "px-7 sm:px-8 flex flex-row items-center gap-2 text-gray-500 font-bold",
div {
class: "pt-1",
{

View File

@@ -2,8 +2,9 @@ use crate::components::task_list::TaskList;
use crate::hooks::use_tasks_with_subtasks_in_category;
use crate::internationalization::LocaleFromLanguageIdentifier;
use crate::models::category::Category;
use crate::models::task::TaskWithSubtasks;
use chrono::Local;
use crate::models::task::{Task, TaskWithSubtasks};
use crate::route::Route;
use chrono::{Local, NaiveDateTime};
use dioxus::prelude::*;
use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::fa_solid_icons::{FaCalendarCheck, FaCalendarXmark, FaWater};
@@ -42,6 +43,7 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
.cloned()
.collect::<Vec<TaskWithSubtasks>>();
let long_term_tasks = use_tasks_with_subtasks_in_category(Category::LongTerm)?;
let inbox_tasks = use_tasks_with_subtasks_in_category(Category::Inbox)?;
rsx! {
div {
@@ -50,7 +52,7 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
div {
class: "flex flex-col gap-4",
div {
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
class: "px-7 sm:px-8 flex flex-row items-center gap-2 text-gray-500 font-bold",
Icon {
class: "mx-1.5",
icon: FaWater
@@ -68,7 +70,7 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
div {
class: "flex flex-col gap-4",
div {
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
class: "px-7 sm:px-8 flex flex-row items-center gap-2 text-gray-500 font-bold",
Icon {
class: "mx-1.25",
height: 22,
@@ -88,7 +90,7 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
div {
class: "flex flex-col gap-4",
div {
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
class: "px-7 sm:px-8 flex flex-row items-center gap-2 text-gray-500 font-bold",
Icon {
class: "mx-1.25",
height: 22,
@@ -116,8 +118,32 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
}
}
}
TaskList {
tasks: today_tasks
div {
TaskList {
tasks: today_tasks
}
if !inbox_tasks.is_empty() {
Link {
to: Route::CategoryInboxPage {},
TaskList {
is_interactive: false,
tasks: vec![
TaskWithSubtasks {
task: Task {
id: 0,
title: t!("empty-inbox")._upper_first(),
deadline: None,
category: Category::Inbox,
project_id: None,
created_at: NaiveDateTime::default(),
updated_at: NaiveDateTime::default()
},
subtasks: Vec::new()
}
]
}
}
}
}
}
}

View File

@@ -10,7 +10,7 @@ pub(crate) fn CreateButton() -> Element {
let current_route = use_route();
rsx! {
ButtonPrimary {
class: "pointer-events-auto m-4 self-end *:rounded-full! *:p-4",
class: "pointer-events-auto sm:self-auto *:rounded-full! *:p-4",
onclick: move |_| {
*TASK_BEING_EDITED.write() = None;
*PROJECT_BEING_EDITED.write() = None;

View File

@@ -10,7 +10,6 @@ pub(crate) mod error_boundary_message;
pub(crate) mod input;
pub(crate) mod input_label;
pub(crate) mod navigation;
pub(crate) mod navigation_item;
pub(crate) mod project_form;
pub(crate) mod project_list;
pub(crate) mod project_select;

View File

@@ -1,4 +1,3 @@
use crate::components::navigation_item::NavigationItem;
use crate::route::Route;
use dioxus::prelude::*;
use dioxus_free_icons::Icon;
@@ -9,13 +8,13 @@ use dioxus_free_icons::icons::fa_solid_icons::{
};
#[component]
pub(crate) fn Navigation(is_expanded: Signal<bool>) -> Element {
pub(crate) fn Bottom(is_expanded: Signal<bool>) -> Element {
rsx! {
div {
class: "grid grid-cols-5 justify-stretch",
button {
class: format!(
"py-2 flex flex-row justify-center items-center cursor-pointer",
"py-2 flex flex-row justify-center items-center sm:hidden cursor-pointer",
),
onclick: move |_| is_expanded.set(!is_expanded()),
div {
@@ -30,41 +29,41 @@ pub(crate) fn Navigation(is_expanded: Signal<bool>) -> Element {
}
}
},
NavigationItem {
super::Item {
route: Route::CategoryNextStepsPage,
icon: FaSignsPost
},
NavigationItem {
super::Item {
route: Route::CategoryCalendarPage,
icon: FaCalendarDays
},
NavigationItem {
super::Item {
route: Route::CategoryTodayPage,
icon: FaCalendarDay
},
NavigationItem {
super::Item {
route: Route::CategoryInboxPage,
icon: FaInbox
},
{if is_expanded() {
rsx! {
NavigationItem {
super::Item {
route: Route::ProjectsPage,
icon: FaList
},
NavigationItem {
super::Item {
route: Route::CategoryTrashPage,
icon: FaTrashCan
},
NavigationItem {
super::Item {
route: Route::CategoryDonePage,
icon: FaVolcano
},
NavigationItem {
super::Item {
route: Route::CategorySomedayMaybePage,
icon: FaLightbulb
},
NavigationItem {
super::Item {
route: Route::CategoryWaitingForPage,
icon: FaHourglassHalf
}

View File

@@ -3,17 +3,14 @@ use dioxus::prelude::*;
use dioxus_free_icons::{Icon, IconShape};
#[component]
pub(crate) fn NavigationItem<I: IconShape + Clone + PartialEq + 'static>(
route: Route,
icon: I,
) -> Element {
pub(crate) fn Item<I: IconShape + Clone + PartialEq + 'static>(route: Route, icon: I) -> Element {
let current_route = use_route::<Route>();
rsx! {
Link {
to: route.clone(),
class: format!(
"py-2.5 flex flex-row justify-center items-center hover:*:bg-gray-900 active:*:text-gray-400",
"py-2.5 sm:px-6 flex flex-row justify-center items-center hover:*:bg-gray-900 active:*:text-gray-400",
),
div {
class: format!("pt-2.5 px-4 {} transition-all duration-150",

View File

@@ -0,0 +1,7 @@
mod bottom;
mod item;
mod side;
pub(crate) use bottom::Bottom;
use item::Item;
pub(crate) use side::Side;

View File

@@ -0,0 +1,52 @@
use crate::route::Route;
use dioxus::prelude::*;
use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb;
use dioxus_free_icons::icons::fa_solid_icons::{
FaCalendarDay, FaCalendarDays, FaHourglassHalf, FaInbox, FaList, FaSignsPost, FaTrashCan,
FaVolcano,
};
#[component]
pub(crate) fn Side(class: Option<String>) -> Element {
rsx! {
div {
class: format!("flex flex-col {}", class.unwrap_or(String::new())),
super::Item {
route: Route::CategoryInboxPage,
icon: FaInbox
},
super::Item {
route: Route::CategoryTodayPage,
icon: FaCalendarDay
},
super::Item {
route: Route::CategoryCalendarPage,
icon: FaCalendarDays
},
super::Item {
route: Route::CategoryNextStepsPage,
icon: FaSignsPost
},
super::Item {
route: Route::CategoryWaitingForPage,
icon: FaHourglassHalf
},
super::Item {
route: Route::CategorySomedayMaybePage,
icon: FaLightbulb
},
super::Item {
route: Route::CategoryDonePage,
icon: FaVolcano
},
super::Item {
route: Route::CategoryTrashPage,
icon: FaTrashCan
},
super::Item {
route: Route::ProjectsPage,
icon: FaList
}
}
}
}

View File

@@ -11,7 +11,7 @@ pub(crate) fn ProjectList() -> Element {
class: "flex flex-col",
for project in projects {
div {
class: "px-7 py-4 hover:bg-gray-800 font-medium text-pretty wrap-anywhere select-none transition-all duration-150 cursor-pointer",
class: "px-7 sm:px-8 py-4 hover:bg-gray-800 font-medium text-pretty wrap-anywhere select-none transition-all duration-150 cursor-pointer",
key: "{project.id}",
onclick: move |_| {
*PROJECT_BEING_EDITED.write() = Some(project.clone());

View File

@@ -4,7 +4,7 @@ use dioxus::prelude::*;
pub(crate) fn StickyBottom(children: Element) -> Element {
rsx! {
div {
class: "fixed bottom-0 left-0 right-0 flex flex-col pointer-events-none",
class: "fixed bottom-0 left-0 right-0 flex flex-col pointer-events-none sm:hidden",
{children}
}
}

View File

@@ -9,7 +9,12 @@ use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
#[component]
pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>) -> Element {
pub(crate) fn TaskList(
tasks: Vec<TaskWithSubtasks>,
/// Whether to open and complete tasks on clicks.
is_interactive: Option<bool>,
class: Option<&'static str>,
) -> Element {
let navigator = use_navigator();
rsx! {
div {
@@ -18,7 +23,7 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
div {
key: "{task.task.id}",
class: format!(
"px-7 pt-3.75 {} flex flex-row items-start gap-4 hover:bg-gray-800 cursor-pointer select-none transition-all duration-150",
"px-7 sm:px-8 pt-3.75 {} flex flex-row items-start gap-4 hover:bg-gray-800 cursor-pointer select-none transition-all duration-150",
if task.task.deadline.is_some() || !task.subtasks.is_empty() {
"pb-0.25"
} else if let Category::Calendar { time, .. } = &task.task.category {
@@ -34,6 +39,9 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
onclick: {
let task = task.clone();
move |_| {
if let Some(false) = is_interactive {
return;
}
*TASK_BEING_EDITED.write() = Some(task.task.clone());
navigator.push(Route::TaskFormPage);
}
@@ -49,6 +57,9 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
// To prevent editing the task.
event.stop_propagation();
async move {
if let Some(false) = is_interactive {
return;
}
let _ = complete_task(task.task.id).await;
}
}

View File

@@ -4,6 +4,7 @@ yesterday = včera
today = dnes
tomorrow = zítra
overdue = zpožděné
empty-inbox = vyprázdnit schránku
## Date and time formats
date-format = %-d. %B

View File

@@ -4,6 +4,7 @@ yesterday = yesterday
today = today
tomorrow = tomorrow
overdue = overdue
empty-inbox = empty the inbox
## Date and time formats
date-format = %B %-d

View File

@@ -1,3 +1,4 @@
use crate::components;
use crate::components::bottom_panel::BottomPanel;
use crate::components::create_button::CreateButton;
use crate::components::sticky_bottom::StickyBottom;
@@ -27,11 +28,24 @@ pub(crate) fn Navigation() -> Element {
rsx! {
div {
class: "grow flex flex-col pb-36",
Outlet::<Route> {}
class: "grow flex flex-col sm:flex-row pb-36 sm:pb-0",
div {
class: "hidden sm:flex sm:flex-col gap-4 py-3.25 items-center bg-gray-800",
components::navigation::Side {},
div {
CreateButton {}
}
}
div {
class: "grow flex flex-col py-4",
Outlet::<Route> {}
}
}
StickyBottom {
CreateButton {},
div {
class: "m-4 self-end",
CreateButton {},
}
BottomPanel {}
}
}

View File

@@ -1,4 +1,7 @@
#![warn(clippy::pedantic)]
#![allow(clippy::unused_async)]
// TODO: Enable once false positives are fixed.
#![allow(clippy::assigning_clones)]
mod components;
mod dotenv;

View File

@@ -41,6 +41,7 @@ pub enum Category {
#[cfg(feature = "server")]
impl Category {
pub fn eq_sql_predicate(&self) -> Box<dyn BoxableExpression<tasks::table, Pg, SqlType = Bool>> {
#![allow(clippy::wildcard_imports)]
use crate::schema::tasks::dsl::*;
match self {

View File

@@ -1,3 +1,6 @@
// For Diesel DSL.
#![allow(clippy::wildcard_imports)]
#[cfg(feature = "server")]
pub(crate) mod database_connection;
pub(crate) mod projects;

View File

@@ -138,7 +138,7 @@ pub(crate) async fn complete_task(task_id: i32) -> Result<Task> {
} = &mut new_task.category
{
match reoccurrence.interval {
ReoccurrenceInterval::Day => *date = *date + Days::new(reoccurrence.length as u64),
ReoccurrenceInterval::Day => *date = *date + Days::new(u64::from(reoccurrence.length)),
ReoccurrenceInterval::Month | ReoccurrenceInterval::Year => {
*date = *date
+ Months::new(
@@ -152,13 +152,13 @@ pub(crate) async fn complete_task(task_id: i32) -> Result<Task> {
*date = NaiveDate::from_ymd_opt(
date.year(),
date.month(),
reoccurrence.start_date.day().min(
Month::try_from(date.month() as u8)
reoccurrence.start_date.day().min(u32::from(
Month::try_from(u8::try_from(date.month()).unwrap())
.unwrap()
.length(date.year()) as u32,
),
.length(date.year()),
)),
)
.unwrap()
.unwrap();
}
}
restore_subtasks_of_task(task_id).await?;

View File

@@ -5,7 +5,7 @@ use dioxus::prelude::*;
pub(crate) fn ProjectFormPage() -> Element {
rsx! {
ErrorBoundaryMessage {
class: "grow py-4 flex flex-col gap-12",
class: "grow py-8 max-w-xl w-full mx-auto flex flex-col gap-12",
ProjectForm {}
}
}

View File

@@ -5,7 +5,7 @@ use dioxus::prelude::*;
pub(crate) fn TaskFormPage() -> Element {
rsx! {
ErrorBoundaryMessage {
class: "grow py-4 flex flex-col gap-12",
class: "grow py-8 max-w-xl w-full mx-auto flex flex-col gap-12",
TaskForm {}
}
}