Compare commits
1 Commits
main
...
404dca86e1
| Author | SHA1 | Date | |
|---|---|---|---|
|
404dca86e1
|
8
.github/workflows/rust-check.yaml
vendored
8
.github/workflows/rust-check.yaml
vendored
@@ -21,9 +21,9 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
libgtk-3-dev=3.24.33-1ubuntu2.2
|
libgtk-3-dev=3.24.33-1ubuntu2.2
|
||||||
libjavascriptcoregtk-4.1-dev=2.50.4-0ubuntu0.22.04.1
|
libjavascriptcoregtk-4.1-dev=2.50.3-0ubuntu0.22.04.1
|
||||||
libsoup-3.0-dev=3.0.7-0ubuntu1
|
libsoup-3.0-dev=3.0.7-0ubuntu1
|
||||||
libwebkit2gtk-4.1-dev=2.50.4-0ubuntu0.22.04.1
|
libwebkit2gtk-4.1-dev=2.50.3-0ubuntu0.22.04.1
|
||||||
libxdo-dev=1:3.20160805.1-4
|
libxdo-dev=1:3.20160805.1-4
|
||||||
- name: Rust toolchain installation
|
- name: Rust toolchain installation
|
||||||
uses: dtolnay/rust-toolchain@9bc92bc5598b4f3bec5d910d352094982cb0c3b9 # 1.92.0
|
uses: dtolnay/rust-toolchain@9bc92bc5598b4f3bec5d910d352094982cb0c3b9 # 1.92.0
|
||||||
@@ -40,6 +40,6 @@ jobs:
|
|||||||
- name: rustfmt check
|
- name: rustfmt check
|
||||||
run: cargo fmt --all --check
|
run: cargo fmt --all --check
|
||||||
- name: Clippy check
|
- name: Clippy check
|
||||||
run: cargo clippy --locked --all-targets --all-features -- --deny warnings
|
run: cargo clippy --all-targets --all-features -- --deny warnings
|
||||||
- name: test check
|
- name: test check
|
||||||
run: cargo --locked test --all --all-targets --all-features
|
run: cargo test --all --all-targets --all-features
|
||||||
|
|||||||
177
Cargo.lock
generated
177
Cargo.lock
generated
@@ -534,9 +534,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.43"
|
version = "0.4.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -634,8 +634,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "const-serialize"
|
name = "const-serialize"
|
||||||
version = "0.8.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "9e42cd5aabba86f128b3763da1fec1491c0f728ce99245062cd49b6f9e6d235b"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-serialize 0.7.2",
|
"const-serialize 0.7.2",
|
||||||
"const-serialize-macro 0.8.0-alpha.0",
|
"const-serialize-macro 0.8.0-alpha.0",
|
||||||
@@ -656,8 +655,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "const-serialize-macro"
|
name = "const-serialize-macro"
|
||||||
version = "0.8.0-alpha.0"
|
version = "0.8.0-alpha.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "42571ed01eb46d2e1adcf99c8ca576f081e46f2623d13500eba70d1d99a4c439"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1016,9 +1014,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel"
|
name = "diesel"
|
||||||
version = "2.3.6"
|
version = "2.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b6c2fc184a6fb6ebcf5f9a5e3bbfa84d8fd268cdfcce4ed508979a6259494d"
|
checksum = "0c415189028b232660655e4893e8bc25ca7aee8e96888db66d9edb400535456a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -1076,8 +1074,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus"
|
name = "dioxus"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "92b583b48ac77158495e6678fe3a2b5954fc8866fc04cb9695dd146e88bc329d"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-asset-resolver",
|
"dioxus-asset-resolver",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
@@ -1110,8 +1107,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-asset-resolver"
|
name = "dioxus-asset-resolver"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "c0161af1d3cfc8ff31503ff1b7ee0068c97771fc38d0cc6566e23483142ddf4f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"http",
|
"http",
|
||||||
@@ -1131,8 +1127,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-cli-config"
|
name = "dioxus-cli-config"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "ccd67ab405e1915a47df9769cd5408545d1b559d5c01ce7a0f442caef520d1f3"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
@@ -1140,8 +1135,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-config-macro"
|
name = "dioxus-config-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "f040ec7c41aa5428283f56bb0670afba9631bfe3ffd885f4814807f12c8c9d91"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1150,14 +1144,12 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-config-macros"
|
name = "dioxus-config-macros"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "10c41b47b55a433b61f7c12327c85ba650572bacbcc42c342ba2e87a57975264"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-core"
|
name = "dioxus-core"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "b389b0e3cc01c7da292ad9b884b088835fdd1671d45fbd2f737506152b22eef0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"const_format",
|
"const_format",
|
||||||
@@ -1178,8 +1170,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-core-macro"
|
name = "dioxus-core-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "6a82d65f0024fc86f01911a16156d280eea583be5a82a3bed85e7e8e4194302d"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"dioxus-rsx",
|
"dioxus-rsx",
|
||||||
@@ -1191,14 +1182,12 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-core-types"
|
name = "dioxus-core-types"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "bfc4b8cdc440a55c17355542fc2089d97949bba674255d84cac77805e1db8c9f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-desktop"
|
name = "dioxus-desktop"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "7e6ec66749d1556636c5b4f661495565c155a7f78a46d4d007d7478c6bdc288c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -1252,8 +1241,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-devtools"
|
name = "dioxus-devtools"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "dcf89488bad8fb0f18b9086ee2db01f95f709801c10c68be42691a36378a0f2d"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
@@ -1272,8 +1260,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-devtools-types"
|
name = "dioxus-devtools-types"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "6e7381d9d7d0a0f66b9d5082d584853c3d53be21d34007073daca98ddf26fc4d"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1283,8 +1270,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-document"
|
name = "dioxus-document"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "6ba0aeeff26d9d06441f59fd8d7f4f76098ba30ca9728e047c94486161185ceb"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-core-macro",
|
"dioxus-core-macro",
|
||||||
@@ -1299,19 +1285,10 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dioxus-free-icons"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "git+https://github.com/matous-volf/dioxus-free-icons?rev=6488400003a3d6829e771a84a565c5c5f08a9aa0#6488400003a3d6829e771a84a565c5c5f08a9aa0"
|
|
||||||
dependencies = [
|
|
||||||
"dioxus",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-fullstack"
|
name = "dioxus-fullstack"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "7db1f8b70338072ec408b48d09c96559cf071f87847465d8161294197504c498"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@@ -1375,8 +1352,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-fullstack-core"
|
name = "dioxus-fullstack-core"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "cda8b152e85121243741b9d5f2a3d8cb3c47a7b2299e902f98b6a7719915b0a2"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
@@ -1403,8 +1379,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-fullstack-macro"
|
name = "dioxus-fullstack-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "255104d4a4f278f1a8482fa30536c91d22260c561c954b753e72987df8d65b2e"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const_format",
|
"const_format",
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
@@ -1417,8 +1392,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-history"
|
name = "dioxus-history"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "8d00ba43bfe6e5ca226fef6128f240ca970bea73cac0462416188026360ccdcf"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1427,8 +1401,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-hooks"
|
name = "dioxus-hooks"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "dab2da4f038c33cb38caa37ffc3f5d6dfbc018f05da35b238210a533bb075823"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-signals",
|
"dioxus-signals",
|
||||||
@@ -1443,8 +1416,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-html"
|
name = "dioxus-html"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "eded5fa6d2e677b7442a93f4228bf3c0ad2597a8bd3292cae50c869d015f3a99"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -1470,8 +1442,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-html-internal-macro"
|
name = "dioxus-html-internal-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "45462ab85fe059a36841508d40545109fd0e25855012d22583a61908eb5cd02a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1482,8 +1453,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-i18n"
|
name = "dioxus-i18n"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ceebf715471a986307cdfe422d645c0784602003758171102ba9225624be9f55"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"fluent",
|
"fluent",
|
||||||
@@ -1495,8 +1464,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-interpreter-js"
|
name = "dioxus-interpreter-js"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "a42a7f73ad32a5054bd8c1014f4ac78cca3b7f6889210ee2b57ea31b33b6d32f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-core-types",
|
"dioxus-core-types",
|
||||||
@@ -1515,8 +1483,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-liveview"
|
name = "dioxus-liveview"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "a3f7a1cfe6f8e9f2e303607c8ae564d11932fd80714c8a8c97e3860d55538997"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
@@ -1543,8 +1510,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-logger"
|
name = "dioxus-logger"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "f1eeab114cb009d9e6b85ea10639a18cfc54bb342f3b837770b004c4daeb89c2"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1555,8 +1521,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-router"
|
name = "dioxus-router"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "1d5b31f9e27231389bf5a117b7074d22d8c58358b484a2558e56fbab20e64ca4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
@@ -1576,8 +1541,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-router-macro"
|
name = "dioxus-router-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "838b9b441a95da62b39cae4defd240b5ebb0ec9f2daea1126099e00a838dc86f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16",
|
"base16",
|
||||||
"digest",
|
"digest",
|
||||||
@@ -1591,8 +1555,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-rsx"
|
name = "dioxus-rsx"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "53128858f0ccca9de54292a4d48409fda1df75fd5012c6243f664042f0225d68"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
@@ -1604,8 +1567,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-server"
|
name = "dioxus-server"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "d8adb2d4e0f0f3a157bda6af2d90f22bac40070e509a66e3ea58abf3b35f904c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -1662,8 +1624,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-signals"
|
name = "dioxus-signals"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "2f48020bc23bc9766e7cce986c0fd6de9af0b8cbfd432652ec6b1094439c1ec6"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@@ -1678,8 +1639,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-ssr"
|
name = "dioxus-ssr"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "44cf9294a21fcd1098e02ad7a3ba61b99be8310ad3395fecf8210387c83f26b9"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama_escape",
|
"askama_escape",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
@@ -1690,8 +1650,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-stores"
|
name = "dioxus-stores"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "77aaa9ac56d781bb506cf3c0d23bea96b768064b89fe50d3b4d4659cc6bd8058"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-signals",
|
"dioxus-signals",
|
||||||
@@ -1702,8 +1661,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-stores-macro"
|
name = "dioxus-stores-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "5b1a728622e7b63db45774f75e71504335dd4e6115b235bbcff272980499493a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.8.0",
|
"convert_case 0.8.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1714,8 +1672,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-web"
|
name = "dioxus-web"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "3b33fe739fed4e8143dac222a9153593f8e2451662ce8fc4c9d167a9d6ec0923"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
@@ -2345,8 +2302,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "generational-box"
|
name = "generational-box"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "cc4ed190b9de8e734d47a70be59b1e7588b9e8e0d0036e332f4c014e8aed1bc5"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -3150,8 +3106,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy-js-bundle"
|
name = "lazy-js-bundle"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "c7b88b715ab1496c6e6b8f5e927be961c4235196121b6ae59bcb51077a21dd36"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
@@ -3333,8 +3288,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "manganis"
|
name = "manganis"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "6cce7d688848bf9d034168513b9a2ffbfe5f61df2ff14ae15e6cfc866efdd344"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-serialize 0.7.2",
|
"const-serialize 0.7.2",
|
||||||
"const-serialize 0.8.0-alpha.0",
|
"const-serialize 0.8.0-alpha.0",
|
||||||
@@ -3345,8 +3299,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "manganis-core"
|
name = "manganis-core"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "84ce917b978268fe8a7db49e216343ec7c8f471f7e686feb70940d67293f19d4"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-serialize 0.7.2",
|
"const-serialize 0.7.2",
|
||||||
"const-serialize 0.8.0-alpha.0",
|
"const-serialize 0.8.0-alpha.0",
|
||||||
@@ -3359,8 +3312,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "manganis-macro"
|
name = "manganis-macro"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "ad513e990f7c0bca86aa68659a7a3dc4c705572ed4c22fd6af32ccf261334cc2"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dunce",
|
"dunce",
|
||||||
"macro-string",
|
"macro-string",
|
||||||
@@ -4816,15 +4768,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.149"
|
version = "1.0.145"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"zmij",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5144,8 +5096,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "subsecond"
|
name = "subsecond"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "8438668e545834d795d04c4335aafc332ce046106521a29f0a5c6501de34187c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -5163,8 +5114,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "subsecond-types"
|
name = "subsecond-types"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/matous-volf/dioxus?rev=627d5ca5b80aeed57c23e253024665f103117f5e#627d5ca5b80aeed57c23e253024665f103117f5e"
|
||||||
checksum = "1e72f747606fc19fe81d6c59e491af93ed7dcbcb6aad9d1d18b05129914ec298"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -5384,30 +5334,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.45"
|
version = "0.3.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
"time-macros",
|
"time-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.7"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.25"
|
version = "0.2.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"time-core",
|
"time-core",
|
||||||
@@ -5448,7 +5398,6 @@ dependencies = [
|
|||||||
"diesel",
|
"diesel",
|
||||||
"diesel_migrations",
|
"diesel_migrations",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-free-icons",
|
|
||||||
"dioxus-html",
|
"dioxus-html",
|
||||||
"dioxus-i18n",
|
"dioxus-i18n",
|
||||||
"feruca",
|
"feruca",
|
||||||
@@ -5467,9 +5416,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.49.0"
|
version = "1.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -5698,9 +5647,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -5721,9 +5670,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.36"
|
version = "0.1.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
@@ -6947,9 +6896,3 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.111",
|
"syn 2.0.111",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
|
||||||
|
|||||||
25
Cargo.toml
25
Cargo.toml
@@ -7,21 +7,19 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.43", features = ["serde", "unstable-locales"] }
|
chrono = { version = "0.4.42", features = ["serde", "unstable-locales"] }
|
||||||
# Remember to update the CLI as well.
|
dioxus = { git = "https://github.com/matous-volf/dioxus", rev = "627d5ca5b80aeed57c23e253024665f103117f5e", features = ["fullstack", "router"] }
|
||||||
dioxus = { version = "0.7.3", features = ["fullstack", "router"] }
|
|
||||||
# TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4765 is resolved.
|
# TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4765 is resolved.
|
||||||
dioxus-html = { version = "0.7.3", features = ["serialize"] }
|
dioxus-html = { git = "https://github.com/matous-volf/dioxus", rev = "627d5ca5b80aeed57c23e253024665f103117f5e", features = ["serialize"] }
|
||||||
feruca = { version = "0.11.5" }
|
feruca = { version = "0.11.5" }
|
||||||
serde = { version = "1.0.228" }
|
serde = { version = "1.0.228" }
|
||||||
serde_json = { version = "1.0.149" }
|
serde_json = { version = "1.0.145" }
|
||||||
serde_with = { version = "3.16.1", features = ["chrono_0_4"] }
|
serde_with = { version = "3.16.1", features = ["chrono_0_4"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.43"
|
||||||
unic-langid-impl = { version = "0.9.6", features = ["serde"] }
|
unic-langid-impl = { version = "0.9.6", features = ["serde"] }
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
|
|
||||||
# Remember to update the CLI as well.
|
diesel = { version = "2.3.4", features = [
|
||||||
diesel = { version = "2.3.6", features = [
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"postgres",
|
"postgres",
|
||||||
"postgres_backend",
|
"postgres_backend",
|
||||||
@@ -31,18 +29,13 @@ diesel_migrations = { version = "2.3.1", features = [
|
|||||||
"postgres",
|
"postgres",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
rand = { version = "0.9.2", optional = true }
|
rand = { version = "0.9.2", optional = true }
|
||||||
time = { version = "0.3.45", optional = true }
|
time = { version = "0.3.44", optional = true }
|
||||||
tokio = { version = "1.49.0", optional = true }
|
tokio = { version = "1.48.0", optional = true }
|
||||||
|
|
||||||
async-std = { version = "1.13.2", optional = true }
|
async-std = { version = "1.13.2", optional = true }
|
||||||
dioxus-i18n = "0.5.1"
|
dioxus-i18n = { path = "dioxus-i18n" }
|
||||||
voca_rs = "1.15.2"
|
voca_rs = "1.15.2"
|
||||||
load-dotenv = "0.1.2"
|
load-dotenv = "0.1.2"
|
||||||
# TODO: Switch to upstream once it merges the changes.
|
|
||||||
dioxus-free-icons = { git = "https://github.com/matous-volf/dioxus-free-icons", rev = "6488400003a3d6829e771a84a565c5c5f08a9aa0", features = [
|
|
||||||
"font-awesome-regular",
|
|
||||||
"font-awesome-solid",
|
|
||||||
] }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["web"]
|
default = ["web"]
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 108 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 22 KiB |
203
assets/images/icon.svg
Normal file
203
assets/images/icon.svg
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:zoom="1.1020922"
|
||||||
|
inkscape:cx="188.27826"
|
||||||
|
inkscape:cy="204.15715"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="fillet_chamfer"
|
||||||
|
id="path-effect2"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||||
|
radius="0"
|
||||||
|
unit="px"
|
||||||
|
method="auto"
|
||||||
|
mode="F"
|
||||||
|
chamfer_steps="1"
|
||||||
|
flexible="false"
|
||||||
|
use_knot_distance="true"
|
||||||
|
apply_no_radius="true"
|
||||||
|
apply_with_radius="true"
|
||||||
|
only_selected="false"
|
||||||
|
hide_knots="false" />
|
||||||
|
<inkscape:path-effect
|
||||||
|
effect="fillet_chamfer"
|
||||||
|
id="path-effect1"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1"
|
||||||
|
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||||
|
radius="0"
|
||||||
|
unit="px"
|
||||||
|
method="auto"
|
||||||
|
mode="F"
|
||||||
|
chamfer_steps="1"
|
||||||
|
flexible="false"
|
||||||
|
use_knot_distance="true"
|
||||||
|
apply_no_radius="true"
|
||||||
|
apply_with_radius="true"
|
||||||
|
only_selected="false"
|
||||||
|
hide_knots="false" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="display:inline;fill:#27272a;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect1"
|
||||||
|
width="512"
|
||||||
|
height="512"
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
sodipodi:insensitive="true"
|
||||||
|
inkscape:label="background"
|
||||||
|
ry="128.00018"
|
||||||
|
sodipodi:type="rect"
|
||||||
|
rx="129.98714" />
|
||||||
|
<g
|
||||||
|
id="g17"
|
||||||
|
inkscape:label="logo"
|
||||||
|
transform="translate(8)">
|
||||||
|
<g
|
||||||
|
id="g8"
|
||||||
|
inkscape:label="ring">
|
||||||
|
<g
|
||||||
|
id="g7"
|
||||||
|
inkscape:label="back">
|
||||||
|
<circle
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:#d97706;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1"
|
||||||
|
cx="224"
|
||||||
|
cy="256"
|
||||||
|
r="128"
|
||||||
|
inkscape:label="ring back" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:21.8936;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect2"
|
||||||
|
width="48"
|
||||||
|
height="288"
|
||||||
|
x="224"
|
||||||
|
y="112"
|
||||||
|
inkscape:label="rect2" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g1"
|
||||||
|
transform="translate(-4.163147,-0.69235229)"
|
||||||
|
inkscape:label="front">
|
||||||
|
<circle
|
||||||
|
style="fill:#27272a;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1-5-2"
|
||||||
|
cx="276.16315"
|
||||||
|
cy="256.69235"
|
||||||
|
r="128" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fbbf24;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1-5"
|
||||||
|
cx="276.16315"
|
||||||
|
cy="256.69235"
|
||||||
|
r="128" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g16"
|
||||||
|
inkscape:label="tick"
|
||||||
|
transform="translate(16.000231,-8.3918418e-5)">
|
||||||
|
<g
|
||||||
|
id="g6"
|
||||||
|
transform="rotate(45,-57.96574,415.4208)"
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||||
|
inkscape:label="back">
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect9"
|
||||||
|
width="32.000458"
|
||||||
|
height="32"
|
||||||
|
x="273.94067"
|
||||||
|
y="210.74516"
|
||||||
|
transform="rotate(-45,-57.96574,415.4208)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect8"
|
||||||
|
width="32.000458"
|
||||||
|
height="32"
|
||||||
|
x="206.05841"
|
||||||
|
y="233.37257"
|
||||||
|
transform="rotate(-45,-57.96574,415.4208)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect7"
|
||||||
|
height="16"
|
||||||
|
x="228.686"
|
||||||
|
y="285.255"
|
||||||
|
width="32"
|
||||||
|
transform="rotate(-45,-57.96574,415.4208)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect5"
|
||||||
|
width="64"
|
||||||
|
height="32"
|
||||||
|
x="0"
|
||||||
|
y="100" />
|
||||||
|
<rect
|
||||||
|
style="fill:#d97706;fill-opacity:1;stroke:none;stroke-width:39.1918;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect6"
|
||||||
|
width="32"
|
||||||
|
height="96"
|
||||||
|
x="32"
|
||||||
|
y="36" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g4"
|
||||||
|
transform="rotate(45,-41.965512,454.04877)"
|
||||||
|
style="fill:#fbbf24;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||||
|
inkscape:label="front">
|
||||||
|
<rect
|
||||||
|
style="fill:#fbbf24;fill-opacity:1;stroke:none;stroke-width:32;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect3"
|
||||||
|
width="64"
|
||||||
|
height="32"
|
||||||
|
x="0"
|
||||||
|
y="100" />
|
||||||
|
<rect
|
||||||
|
style="fill:#fbbf24;fill-opacity:1;stroke:none;stroke-width:39.1918;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect4"
|
||||||
|
width="32"
|
||||||
|
height="96"
|
||||||
|
x="32"
|
||||||
|
y="36" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -3,8 +3,8 @@
|
|||||||
"short_name": "Todo Baggins",
|
"short_name": "Todo Baggins",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#101828",
|
"background_color": "#27272a",
|
||||||
"theme_color": "#b89a2e",
|
"theme_color": "#27272a",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/assets/images/icon.png",
|
"src": "/assets/images/icon.png",
|
||||||
|
|||||||
17
assets/styles/fonts.css
Normal file
17
assets/styles/fonts.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@layer base {
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/assets/fonts/inter_variable.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/assets/fonts/inter_variable_italic.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,37 +8,56 @@ input[type="range"] {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-moz-range-thumb,
|
input[type="range"]::-moz-range-thumb {
|
||||||
input[type="range"]::-webkit-slider-thumb {
|
|
||||||
width: 1.25rem;
|
width: 1.25rem;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
background: var(--color-gray-400);
|
background: rgba(228 228 231);
|
||||||
filter: drop-shadow(0 var(--spacing) 0 var(--color-gray-500));
|
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-webkit-slider-thumb {
|
input[type="range"]::-moz-range-progress {
|
||||||
position: relative;
|
background: #525259;
|
||||||
top: -9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="range"]::-moz-range-track,
|
|
||||||
input[type="range"]::-webkit-slider-runnable-track {
|
|
||||||
background: var(--color-gray-800-muted);
|
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
filter: drop-shadow(
|
|
||||||
0 calc(0px - var(--spacing)) 0 var(--color-gray-900-muted)
|
|
||||||
);
|
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-moz-range-track {
|
input[type="range"]::-moz-range-track {
|
||||||
transform: translateY(3px);
|
background: rgba(39 39 42 / 50%);
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].input-range-reverse::-moz-range-progress {
|
||||||
|
background: #2d2d31;
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].input-range-reverse::-moz-range-track {
|
||||||
|
background: rgba(113 113 122 / 50%);
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
background: rgba(228 228 231);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
top: -0.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-webkit-slider-runnable-track {
|
input[type="range"]::-webkit-slider-runnable-track {
|
||||||
position: relative;
|
background: rgba(39 39 42 / 50%);
|
||||||
top: 3px;
|
height: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"].input-range-reverse::-webkit-slider-runnable-track {
|
||||||
|
background: rgba(39 39 42 / 50%);
|
||||||
|
height: 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
select {
|
|
||||||
appearance: none;
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 640'%3E%3Cpath fill='%239ca3af' d='M300.3 440.8C312.9 451 331.4 450.3 343.1 438.6L471.1 310.6C480.3 301.4 483 287.7 478 275.7C473 263.7 461.4 256 448.5 256L192.5 256C179.6 256 167.9 263.8 162.9 275.8C157.9 287.8 160.7 301.5 169.9 310.6L297.9 438.6L300.3 440.8z'/%3E%3C/svg%3E");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 2rem;
|
|
||||||
background-position: right 0.5rem center;
|
|
||||||
}
|
|
||||||
48
dioxus-i18n/.github/release-drafter.yml
vendored
Normal file
48
dioxus-i18n/.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name-template: "Release v$RESOLVED_VERSION 🦀"
|
||||||
|
tag-template: "v$RESOLVED_VERSION"
|
||||||
|
categories:
|
||||||
|
- title: "🚀 Features"
|
||||||
|
label: "feature"
|
||||||
|
- title: "🐛 Bug Fixes"
|
||||||
|
label: "bug"
|
||||||
|
- title: "♻️ Refactor"
|
||||||
|
label: "refactor"
|
||||||
|
- title: "📝 Documentation"
|
||||||
|
label: "documentation"
|
||||||
|
- title: "🧰 Maintenance"
|
||||||
|
labels:
|
||||||
|
- "chore"
|
||||||
|
- "dependencies"
|
||||||
|
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
|
||||||
|
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
|
||||||
|
version-resolver:
|
||||||
|
major:
|
||||||
|
labels:
|
||||||
|
- "major"
|
||||||
|
minor:
|
||||||
|
labels:
|
||||||
|
- "minor"
|
||||||
|
patch:
|
||||||
|
labels:
|
||||||
|
- "patch"
|
||||||
|
default: patch
|
||||||
|
template: |
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
$CHANGES
|
||||||
|
autolabeler:
|
||||||
|
- label: feature
|
||||||
|
branch:
|
||||||
|
- "/^feat(ure)?[/-].+/"
|
||||||
|
- label: bug
|
||||||
|
branch:
|
||||||
|
- "/^fix[/-].+/"
|
||||||
|
- label: refactor
|
||||||
|
branch:
|
||||||
|
- "/(refactor|refactoring)[/-].+/"
|
||||||
|
- label: documentation
|
||||||
|
branch:
|
||||||
|
- "/doc(s|umentation)[/-].+/"
|
||||||
|
- label: chore
|
||||||
|
branch:
|
||||||
|
- "/^chore[/-].+/"
|
||||||
19
dioxus-i18n/.github/workflows/publish.yml
vendored
Normal file
19
dioxus-i18n/.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Cargo Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Publish to crate.io
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: cargo publish
|
||||||
|
env:
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
19
dioxus-i18n/.github/workflows/release-drafter.yml
vendored
Normal file
19
dioxus-i18n/.github/workflows/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Release Drafter
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_release_draft:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: release-drafter/release-drafter@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
40
dioxus-i18n/.github/workflows/test_runs.yml
vendored
Normal file
40
dioxus-i18n/.github/workflows/test_runs.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: Test Runs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
types: [opened, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Updates
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libwebkit2gtk-4.1-dev \
|
||||||
|
build-essential \
|
||||||
|
libxdo-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libayatana-appindicator3-dev \
|
||||||
|
librsvg2-dev \
|
||||||
|
libglib2.0-dev
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: cargo test
|
||||||
|
|
||||||
|
- name: Compile
|
||||||
|
run: |
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
cargo build --target wasm32-unknown-unknown
|
||||||
|
cargo build --release
|
||||||
2
dioxus-i18n/.gitignore
vendored
Normal file
2
dioxus-i18n/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Cargo.lock
|
||||||
|
/target
|
||||||
117
dioxus-i18n/CHANGELOG.md
Normal file
117
dioxus-i18n/CHANGELOG.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [0.4.3]
|
||||||
|
|
||||||
|
- [Issue #19](https://github.com/dioxus-community/dioxus-i18n/issues/19) Enable use of "message-id.attribute-id"
|
||||||
|
syntax in the `t!`, `te!`, `tid!` macros in order to extract attribute definition, e.g. `t!("mycomponent.placeholder")`
|
||||||
|
in:
|
||||||
|
```
|
||||||
|
mycomponent = Component Name
|
||||||
|
.placeholder = Some placeholder
|
||||||
|
.aria-text = Some aria text
|
||||||
|
```
|
||||||
|
|
||||||
|
- Added examples for all fluent grammar constructs and configuration variants.
|
||||||
|
|
||||||
|
## [0.4.2] 2025-02-08
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- [Issue #15](https://github.com/dioxus-community/dioxus-i18n/issues/15) Recent change to t! macro unnecessarily breaks v0.3 code.
|
||||||
|
|
||||||
|
### Amended
|
||||||
|
|
||||||
|
- t! macro amended to use unwrap_or_else rather than panic!.
|
||||||
|
|
||||||
|
- Error messages made consistant across all macros.
|
||||||
|
|
||||||
|
## [0.4.1] 2025-02-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New methods (`I18nConfig::with_auto_locales`) to determine supported locales from deep search for translation files.
|
||||||
|
|
||||||
|
- New methods returning `Result<_, Error>` rather than `panic!`, such that:
|
||||||
|
| __`panic!` version__ | __`Result<_, Error>` vesion__ |
|
||||||
|
|-----------------------------------------|------------------------------------------|
|
||||||
|
| `LocaleResource::to_resource_string` | `LocaleResource::try_to_resource_string` |
|
||||||
|
| `I18n::translate` | `I18n::try_translate` |
|
||||||
|
| `I18n::translate_with_args` | `I18n::try_translate_with_args` |
|
||||||
|
| `I18n::set_fallback_language` | `I18n::try_set_fallback_language` |
|
||||||
|
| `I18n::set_language` | `I18n::try_set_language` |
|
||||||
|
| `use_init_i18n` | `try_use_init_i18n` |
|
||||||
|
| `I18nConfig::with_auto_locales` | `I18nConfig::try_with_auto_locales` |
|
||||||
|
|
||||||
|
- New `te!` macro which acts like `t!` but returns `Error`.
|
||||||
|
|
||||||
|
- New `tid!` macro which acts like `t!` but returns the message-id.
|
||||||
|
|
||||||
|
### Change
|
||||||
|
|
||||||
|
- t! macro amended to use `try_translate` and `try_translate_with_args`, but will perform `.expect("..")`
|
||||||
|
and therefore panic! on error. This retains backwards compatibility for this macro.
|
||||||
|
|
||||||
|
- Use of `set_fallback_language` / `try_set_fallback_language` without a corresponding locale
|
||||||
|
translation is treated as an error.
|
||||||
|
|
||||||
|
## [0.4.0] 2025-01-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Code:
|
||||||
|
- Doc comments
|
||||||
|
- Module tests for `cargo test`
|
||||||
|
|
||||||
|
- Amended `I18nConfig::with_locale` so that the `Locale` dynamic or static
|
||||||
|
constructors no longer have to be _explicitly_ given.
|
||||||
|
They can be determined implicitly from `(LanguageIdentifier, &str)` or
|
||||||
|
`(LanguageIdentifer, PathBuf)`.
|
||||||
|
|
||||||
|
- Enabled shared 'LocaleResource's, where two dialect can use the same translation file.
|
||||||
|
For example ["en", "en-GB"] share "en-GB.ftl".
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- The translations used are determined when `I18n::set_language` or
|
||||||
|
`I18n::set_fallback_language` is called, and not each time a message is translated.
|
||||||
|
|
||||||
|
- __Fallback handling has changed__. It no longer just uses _fallback_language_ when the message
|
||||||
|
id is missing from the current _locale_. It performs a graceful fallback from
|
||||||
|
_<language>-<region>_ to _<language>_ before using the actual _fallback_ (in fact it
|
||||||
|
falls back along the _<language>-<optionalScript>-<optionalRegion>-<optionalVariants>_
|
||||||
|
hiearchy).
|
||||||
|
|
||||||
|
__Note:__ this is a breaking change which may impact the selected translation.
|
||||||
|
|
||||||
|
- `LocaleResource::to_string` renamed to `LocaleResource::to_resource_string`
|
||||||
|
|
||||||
|
## [0.3.0] 2024-12-10
|
||||||
|
|
||||||
|
- [Dioxus 0.6](https://dioxuslabs.com/) support
|
||||||
|
|
||||||
|
## [0.2.4] 2024-09-11
|
||||||
|
|
||||||
|
- Hide new_dynamic in WASM
|
||||||
|
- New t!() macro
|
||||||
|
|
||||||
|
## [0.2.3] 2024-09-04
|
||||||
|
|
||||||
|
- Support dynamic loading of locales
|
||||||
|
|
||||||
|
## [0.2.2] 2024-09-02
|
||||||
|
|
||||||
|
- Enable macros instead of serde in unic-langid
|
||||||
|
|
||||||
|
## [0.2.1] 2024-09-02
|
||||||
|
|
||||||
|
- Export unic_langid and fluent
|
||||||
|
- Use absolute path to import fluent in the translate macro
|
||||||
|
- Updated freya example
|
||||||
|
|
||||||
|
## [0.2.0] 2024-09-01
|
||||||
|
|
||||||
|
- Now based in the [Fluent Project](https://github.com/projectfluent/fluent-rs)
|
||||||
|
|
||||||
|
## [0.1.0] 2024-08-31
|
||||||
|
|
||||||
|
- Initial release
|
||||||
30
dioxus-i18n/Cargo.toml
Normal file
30
dioxus-i18n/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "dioxus-i18n"
|
||||||
|
version = "0.5.1"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Marc Espín <mespinsanz@gmail.com>"]
|
||||||
|
description = "i18n integration for Dioxus apps based on Fluent Project."
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/dioxus-community/dioxus-i18n"
|
||||||
|
readme = "./README.md"
|
||||||
|
categories = ["accessibility", "gui", "localization", "internationalization"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { git = "https://github.com/matous-volf/dioxus", rev = "627d5ca5b80aeed57c23e253024665f103117f5e", default-features = false, features = [
|
||||||
|
"hooks",
|
||||||
|
"macro",
|
||||||
|
"signals",
|
||||||
|
] }
|
||||||
|
fluent = "0.17"
|
||||||
|
thiserror = "2.0"
|
||||||
|
unic-langid = { version = "0.9", features = ["macros"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
walkdir = "2.5.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dioxus = { git = "https://github.com/matous-volf/dioxus", rev = "627d5ca5b80aeed57c23e253024665f103117f5e", features = ["desktop"] }
|
||||||
|
freya = "0.3"
|
||||||
|
futures = "0.3.31"
|
||||||
|
pretty_assertions = "1.4.1"
|
||||||
|
unic-langid = { version = "0.9.5", features = ["macros"] }
|
||||||
20
dioxus-i18n/LICENSE.md
Normal file
20
dioxus-i18n/LICENSE.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Marc Espín Sanz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
85
dioxus-i18n/README.md
Normal file
85
dioxus-i18n/README.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# dioxus-i18n 🌍
|
||||||
|
|
||||||
|
i18n integration for Dioxus apps based on the [Project Fluent](https://github.com/projectfluent/fluent-rs).
|
||||||
|
|
||||||
|
> This crate used to be in the [Dioxus SDK](https://github.com/DioxusLabs/sdk).
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Dioxus v0.6** 🧬
|
||||||
|
- Renderers:
|
||||||
|
- [web](https://dioxuslabs.com/learn/0.6/guides/web/),
|
||||||
|
- [desktop](https://dioxuslabs.com/learn/0.6/guides/desktop/),
|
||||||
|
- [freya](https://github.com/marc2332/freya)
|
||||||
|
- Both WASM and native targets
|
||||||
|
|
||||||
|
## Example:
|
||||||
|
|
||||||
|
```ftl
|
||||||
|
# en-US.ftl
|
||||||
|
|
||||||
|
hello = Hello, {$name}!
|
||||||
|
```
|
||||||
|
|
||||||
|
```rs
|
||||||
|
// main.rs
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
let i18 = use_init_i18n(|| {
|
||||||
|
I18nConfig::new(langid!("en-US"))
|
||||||
|
// implicit [`Locale`]
|
||||||
|
.with_locale(( // Embed
|
||||||
|
langid!("en-US"),
|
||||||
|
include_str!("./en-US.ftl")
|
||||||
|
))
|
||||||
|
.with_locale(( // Load at launch
|
||||||
|
langid!("es-ES"),
|
||||||
|
PathBuf::from("./es-ES.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale(( // Locales will share duplicated locale_resources
|
||||||
|
langid!("en"), // which is useful to assign a specific region for
|
||||||
|
include_str!("./en-US.ftl") // the primary language
|
||||||
|
))
|
||||||
|
// explicit [`Locale`]
|
||||||
|
.with_locale(Locale::new_static( // Embed
|
||||||
|
langid!("en-US"),
|
||||||
|
include_str!("./en-US.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale(Locale::new_dynamic( // Load at launch
|
||||||
|
langid!("es-ES"),
|
||||||
|
PathBuf::from("./es-ES.ftl"),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
label { { t!("hello", name: "World") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Further examples
|
||||||
|
|
||||||
|
The examples folder contains a number of working examples:
|
||||||
|
|
||||||
|
* Desktop examples:
|
||||||
|
* [Dioxus](./examples/dioxus-desktop.rs)
|
||||||
|
* [Freya](./examples/freya.rs)
|
||||||
|
* Configuration variants:
|
||||||
|
* [Auto locales](./examples/config-auto-locales.rs)
|
||||||
|
* [Dynamic (PathBuf)](./examples/config-dynamic-pathbuf.rs)
|
||||||
|
* [Static (include_str!)](./examples/config-static-includestr.rs)
|
||||||
|
* Fluent grammer:
|
||||||
|
* [Application](./examples/fluent-grammar.rs)
|
||||||
|
* [FTL file](./examples/data/fluent/en.ftl)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Checks clean compile against `#[cfg(not(target_arch = "wasm32"))]`
|
||||||
|
cargo build --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
# Runs all tests
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
[MIT License](./LICENSE.md)
|
||||||
47
dioxus-i18n/examples/config-auto-locales.rs
Normal file
47
dioxus-i18n/examples/config-auto-locales.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//! This example demonstrates how to use an auto_locales derived I18nConfig.
|
||||||
|
//! This is useful when you have a lot of locales and you don't want to manually add them.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_i18n::{prelude::*, t};
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Body() -> Element {
|
||||||
|
let mut i18n = i18n();
|
||||||
|
|
||||||
|
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
|
||||||
|
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
button {
|
||||||
|
onclick: change_to_english,
|
||||||
|
label {
|
||||||
|
"English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: change_to_spanish,
|
||||||
|
label {
|
||||||
|
"Spanish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p { { t!("hello_world") } }
|
||||||
|
p { { t!("hello", name: "Dioxus") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
// This initialisation performs a deep search for all locales in the given path.
|
||||||
|
// It IS NOT supported in WASM targets.
|
||||||
|
I18nConfig::new(langid!("en-US")).with_auto_locales(PathBuf::from("./examples/data/i18n/"))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
62
dioxus-i18n/examples/config-dynamic-pathbuf.rs
Normal file
62
dioxus-i18n/examples/config-dynamic-pathbuf.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//! This example demonstrates how to use pathbuf derived I18nConfig.
|
||||||
|
//! This is useful when the path to the translation files is not known at compile time.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_i18n::{prelude::*, t};
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Body() -> Element {
|
||||||
|
let mut i18n = i18n();
|
||||||
|
|
||||||
|
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
|
||||||
|
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
button {
|
||||||
|
onclick: change_to_english,
|
||||||
|
label {
|
||||||
|
"English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: change_to_spanish,
|
||||||
|
label {
|
||||||
|
"Spanish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p { { t!("hello_world") } }
|
||||||
|
p { { t!("hello", name: "Dioxus") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
// This initialisation allows individual translation files to be selected.
|
||||||
|
// The locales can be added with an implicitly derived locale (see config-static-includestr.rs for a comparison)
|
||||||
|
// or using an explicit Locale::new_dynamic call.
|
||||||
|
//
|
||||||
|
// The two examples are functionally equivalent.
|
||||||
|
//
|
||||||
|
// It IS NOT supported in WASM targets.
|
||||||
|
I18nConfig::new(langid!("en-US"))
|
||||||
|
// Implicit...
|
||||||
|
.with_locale((
|
||||||
|
langid!("es-ES"),
|
||||||
|
PathBuf::from("./examples/data/i18n/es-ES.ftl"),
|
||||||
|
))
|
||||||
|
// Explicit...
|
||||||
|
.with_locale(Locale::new_dynamic(
|
||||||
|
langid!("en-US"),
|
||||||
|
PathBuf::from("./examples/data/i18n/en-US.ftl"),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
57
dioxus-i18n/examples/config-static-includestr.rs
Normal file
57
dioxus-i18n/examples/config-static-includestr.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//! This example demonstrates how to use pathbuf derived I18nConfig.
|
||||||
|
//! This is useful for WASM targets; the paths to the translation files must be known at compile time.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_i18n::{prelude::*, t};
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Body() -> Element {
|
||||||
|
let mut i18n = i18n();
|
||||||
|
|
||||||
|
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
|
||||||
|
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
button {
|
||||||
|
onclick: change_to_english,
|
||||||
|
label {
|
||||||
|
"English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: change_to_spanish,
|
||||||
|
label {
|
||||||
|
"Spanish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p { { t!("hello_world") } }
|
||||||
|
p { { t!("hello", name: "Dioxus") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
// This initialisation allows individual translation files to be selected.
|
||||||
|
// The locales can be added with an implicitly derived locale (see config-dynamic-pathbuf.rs for a comparison)
|
||||||
|
// or using an explicit Locale::new_static call.
|
||||||
|
//
|
||||||
|
// The two examples are functionally equivalent.
|
||||||
|
//
|
||||||
|
// It IS supported in WASM targets.
|
||||||
|
I18nConfig::new(langid!("en-US"))
|
||||||
|
// Implicit...
|
||||||
|
.with_locale((langid!("es-ES"), include_str!("./data/i18n/es-ES.ftl")))
|
||||||
|
// Explicit...
|
||||||
|
.with_locale(Locale::new_static(
|
||||||
|
langid!("en-US"),
|
||||||
|
include_str!("./data/i18n/en-US.ftl"),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
124
dioxus-i18n/examples/data/fluent/en.ftl
Normal file
124
dioxus-i18n/examples/data/fluent/en.ftl
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
### Fluent grammar examples for dioxus-i18n.
|
||||||
|
|
||||||
|
## These examples demonstrate Fluent file grammar and how dioxus-i18n can be
|
||||||
|
## used to access these translations.
|
||||||
|
|
||||||
|
## Examples derived from: https://projectfluent.org/fluent/guide/index.html
|
||||||
|
|
||||||
|
# Simple message
|
||||||
|
simple-message = This is a simple message.
|
||||||
|
|
||||||
|
# $name (String) - The name you want to display.
|
||||||
|
message-with-variable = This is a message with a variable: { $name }.
|
||||||
|
|
||||||
|
# Reference to a term.
|
||||||
|
-a-term = This is a common term used by many messages.
|
||||||
|
message-referencing-a-term = This is a message with a reference: { -a-term }.
|
||||||
|
|
||||||
|
# Use of special characters.
|
||||||
|
message-with-special-character = This message contain opening curly brace {"{"} and a closing curly brace {"}"}.
|
||||||
|
|
||||||
|
# Message with blanks.
|
||||||
|
blank-is-removed = This message starts with no blanks.
|
||||||
|
blank-is-preserved = {" "}This message starts with 4 spaces (note HTML contracts them).
|
||||||
|
|
||||||
|
# Message with attributes.
|
||||||
|
message-with-attributes = Predefined value
|
||||||
|
.placeholder = email@example.com
|
||||||
|
.aria-label = Login input value
|
||||||
|
.title = Type your login email
|
||||||
|
|
||||||
|
# Message with quotes.
|
||||||
|
literal-quote-cryptic = Text in {"\""}double quotes{"\""}.
|
||||||
|
literal-quote-preferred = Text in "double quotes".
|
||||||
|
|
||||||
|
# Message with Unicode characters.
|
||||||
|
unicode-cryptic = {"\u2605"} {"\u2606"} {"\u2728"} {"\u262F"} {"\u263A"}
|
||||||
|
unicode-preferred = ★ ☆ ✨ ☯ ☺
|
||||||
|
|
||||||
|
# Message with a placeable.
|
||||||
|
single-line = Text can be written in a single line.
|
||||||
|
|
||||||
|
multi-line = Text can also span multiple lines
|
||||||
|
as long as each new line is indented
|
||||||
|
by at least one space.
|
||||||
|
|
||||||
|
block-line =
|
||||||
|
Sometimes it's more readable to format
|
||||||
|
multiline text as a "block", which means
|
||||||
|
starting it on a new line. All lines must
|
||||||
|
be indented by at least one space.
|
||||||
|
|
||||||
|
# Message using functions.
|
||||||
|
#
|
||||||
|
# Note: Builtin functions are currently unsupported: See Fluent issue https://github.com/projectfluent/fluent-rs/issues/181
|
||||||
|
# The Bundle::add_builtins() function is not published at the time of writing this example.
|
||||||
|
#
|
||||||
|
# Using a builtin currently results in an error.
|
||||||
|
#
|
||||||
|
# $duration (Number) - The duration in seconds.
|
||||||
|
time-elapsed-no-function = Time elapsed: { $duration }s.
|
||||||
|
time-elapsed-function = Currently unsupported: error raised: { NUMBER($duration) }.
|
||||||
|
|
||||||
|
# Message reference.
|
||||||
|
referenced-message = Referenced message
|
||||||
|
message-referencing-another-message = Message referencing another message: { referenced-message }.
|
||||||
|
|
||||||
|
# Message selection plurals.
|
||||||
|
message-selection-plurals =
|
||||||
|
{ $value ->
|
||||||
|
*[one] Value is one: { $value }.
|
||||||
|
[other] Value is more than one: { $value }.
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message selection numeric.
|
||||||
|
# Argument must be numeric.
|
||||||
|
message-selection-numeric =
|
||||||
|
{ NUMERIC($value) ->
|
||||||
|
[0.0] Zero: { $value }.
|
||||||
|
*[0.5] A half: { $value }.
|
||||||
|
[other] Other ($value)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message selection number.
|
||||||
|
#
|
||||||
|
# Note: Builtin functions are currently unsupported: See Fluent issue https://github.com/projectfluent/fluent-rs/issues/181
|
||||||
|
# The Bundle::add_builtins() function is not published at the time of writing this example.
|
||||||
|
#
|
||||||
|
# Using the NUMBER builtin always results in a default behaviour.
|
||||||
|
#
|
||||||
|
message-selection-number = { NUMBER($pos, type: "ordinal") ->
|
||||||
|
[1] First!
|
||||||
|
[one] {$pos}st
|
||||||
|
[two] {$pos}nd
|
||||||
|
[few] {$pos}rd
|
||||||
|
*[other] {$pos}th
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Variables in references.
|
||||||
|
-term-using-variable = https://{ $host }
|
||||||
|
message-using-term-with-variable = For example: { -term-using-variable(host: "example.com") }.
|
||||||
|
|
||||||
|
-term-using-variable-2 =
|
||||||
|
{ $case ->
|
||||||
|
*[nominative] Firefox
|
||||||
|
[locative] Firefoksie
|
||||||
|
}
|
||||||
|
message-using-term-with-variable-2-1 = Informacje o { -term-using-variable-2(case: "locative") }.
|
||||||
|
message-using-term-with-variable-2-2 = About { -term-using-variable-2(case: "nominative") }.
|
||||||
|
message-using-term-with-variable-2-default = About { -term-using-variable-2(case: "") }.
|
||||||
|
message-using-term-with-variable-2-not-provided = About { -term-using-variable-2 }.
|
||||||
|
|
||||||
|
-brand-name =
|
||||||
|
{ $case ->
|
||||||
|
*[nominative] Firefox
|
||||||
|
[locative] Firefoksie
|
||||||
|
}
|
||||||
|
|
||||||
|
string-literal = { "string literal" }
|
||||||
|
number-literal-1 = { 1 }
|
||||||
|
number-literal-2 = { -123 }
|
||||||
|
number-literal-3 = { 3.14 }
|
||||||
|
inline-expression-placeable-1 = { { "string literal" } }
|
||||||
|
inline-expression-placeable-2 = { { 123 } }
|
||||||
3
dioxus-i18n/examples/data/i18n/en-US.ftl
Normal file
3
dioxus-i18n/examples/data/i18n/en-US.ftl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
hello_world = Hello, World!
|
||||||
|
|
||||||
|
hello = Hello, {$name}!
|
||||||
3
dioxus-i18n/examples/data/i18n/es-ES.ftl
Normal file
3
dioxus-i18n/examples/data/i18n/es-ES.ftl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
hello_world = Hola, Mundo!
|
||||||
|
|
||||||
|
hello = Hola, {$name}!
|
||||||
47
dioxus-i18n/examples/dioxus-desktop.rs
Normal file
47
dioxus-i18n/examples/dioxus-desktop.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_i18n::{prelude::*, t};
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Body() -> Element {
|
||||||
|
let mut i18n = i18n();
|
||||||
|
|
||||||
|
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
|
||||||
|
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
button {
|
||||||
|
onclick: change_to_english,
|
||||||
|
label {
|
||||||
|
"English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: change_to_spanish,
|
||||||
|
label {
|
||||||
|
"Spanish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p { { t!("hello_world") } }
|
||||||
|
p { { t!("hello", name: "Dioxus") } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
I18nConfig::new(langid!("en-US"))
|
||||||
|
.with_locale((langid!("en-US"), include_str!("./data/i18n/en-US.ftl")))
|
||||||
|
.with_locale((
|
||||||
|
langid!("es-ES"),
|
||||||
|
PathBuf::from("./examples/data/i18n/es-ES.ftl"),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
233
dioxus-i18n/examples/fluent-grammar.rs
Normal file
233
dioxus-i18n/examples/fluent-grammar.rs
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
//! This example demonstrates many of the Fluent grammar constructs, and how they are
|
||||||
|
//! used in dioxus-i18n.
|
||||||
|
//! This performs a lookup only, no additional translation files are provided
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_i18n::{prelude::*, tid};
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[component]
|
||||||
|
fn Body() -> Element {
|
||||||
|
rsx! {
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
td { "Simple message" }
|
||||||
|
td { {tid!("simple-message")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Non-existing message: id provided by default when using tid! macro" }
|
||||||
|
td { {tid!("non-existing-message")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a variable" }
|
||||||
|
td { {tid!("message-with-variable", name: "Value 1")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-with-variable", name: "Value 2")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Reference to a term" }
|
||||||
|
td { {tid!("message-referencing-a-term")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Use of special characters." }
|
||||||
|
td { {tid!("message-with-special-character")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with blanks." }
|
||||||
|
td { "'" {tid!("blank-is-removed")} "'" }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { "'" {tid!("blank-is-preserved")} "'" }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with attributes: root" }
|
||||||
|
td { {tid!("message-with-attributes")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with attributes: attribute" }
|
||||||
|
td { {tid!("message-with-attributes.placeholder")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-with-attributes.aria-label")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-with-attributes.title")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with attributes: not existing" }
|
||||||
|
td { {tid!("message-with-attributes.not-existing")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with attributes: invalid" }
|
||||||
|
td { {tid!("message-with-attributes.placeholder.invalid")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with quotes: cryptic" }
|
||||||
|
td { {tid!("literal-quote-cryptic")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with quotes: preferred" }
|
||||||
|
td { {tid!("literal-quote-preferred")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with Unicode characters: cryptic" }
|
||||||
|
td { {tid!("unicode-cryptic")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with Unicode characters: preferred" }
|
||||||
|
td { {tid!("unicode-preferred")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: single-line" }
|
||||||
|
td { {tid!("line-single")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: single-line" }
|
||||||
|
td { {tid!("single-line")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: multi-line (1)" }
|
||||||
|
td { {tid!("multi-line")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: multi-line (2)" }
|
||||||
|
td { pre { {tid!("multi-line")} } }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: block-line (1)" }
|
||||||
|
td { {tid!("block-line")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message with a placeable: block-line (2)" }
|
||||||
|
td { pre { {tid!("block-line")} } }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message using functions: no function" }
|
||||||
|
td { pre { {tid!("time-elapsed-no-function", duration: 23.7114812589)} } }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message using functions: function" }
|
||||||
|
td { pre { {tid!("time-elapsed-function", duration: 23.7114812589)} } }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Reference to a message" }
|
||||||
|
td { {tid!("message-referencing-another-message")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: plurals" }
|
||||||
|
td { {tid!("message-selection-plurals", value: 1)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-selection-plurals", value: 2)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: plurals (default: an 'empty' value must be provided...)" }
|
||||||
|
td { {tid!("message-selection-plurals", value: "")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: plurals (default: ... otherwise an error is raised)" }
|
||||||
|
td { {tid!("message-selection-plurals")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: numeric" }
|
||||||
|
td { {tid!("message-selection-numeric", value: 0.0)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-selection-numeric", value: 0.5)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-selection-numeric", value: 42.0)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: numeric (default)" }
|
||||||
|
td { {tid!("message-selection-numeric", value: "")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Message selection: number" }
|
||||||
|
td { {tid!("message-selection-number", pos: 1)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "" }
|
||||||
|
td { {tid!("message-selection-number", pos: 2)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "" }
|
||||||
|
td { {tid!("message-selection-number", pos: 3)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "" }
|
||||||
|
td { {tid!("message-selection-number", pos: 4)} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Variables in references (1)" }
|
||||||
|
td { {tid!("message-using-term-with-variable")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Variables in references (2)" }
|
||||||
|
td { {tid!("message-using-term-with-variable-2-1")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-using-term-with-variable-2-2")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-using-term-with-variable-2-default")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("message-using-term-with-variable-2-not-provided")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Literals: string" }
|
||||||
|
td { {tid!("string-literal")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("number-literal-1")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("number-literal-2")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("number-literal-3")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("inline-expression-placeable-1")} }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { }
|
||||||
|
td { {tid!("inline-expression-placeable-2")} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
// Only one example in this path, which contains the complete Fluent grammar.
|
||||||
|
I18nConfig::new(langid!("en")).with_auto_locales(PathBuf::from("./examples/data/fluent/"))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
57
dioxus-i18n/examples/freya.rs
Normal file
57
dioxus-i18n/examples/freya.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#![cfg_attr(
|
||||||
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
|
||||||
|
use dioxus_i18n::{prelude::*, t};
|
||||||
|
use freya::prelude::*;
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
launch_with_props(app, "freya + i18n", (300.0, 200.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Body() -> Element {
|
||||||
|
let mut i18n = i18n();
|
||||||
|
|
||||||
|
let change_to_english = move |_| i18n.set_language(langid!("en-US"));
|
||||||
|
let change_to_spanish = move |_| i18n.set_language(langid!("es-ES"));
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
rect {
|
||||||
|
rect {
|
||||||
|
direction: "horizontal",
|
||||||
|
Button {
|
||||||
|
onclick: change_to_english,
|
||||||
|
label {
|
||||||
|
"English"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
onclick: change_to_spanish,
|
||||||
|
label {
|
||||||
|
"Spanish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
label { { t!("hello_world") } }
|
||||||
|
label { { t!("hello", name: "Dioxus") } }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_init_i18n(|| {
|
||||||
|
I18nConfig::new(langid!("en-US"))
|
||||||
|
.with_locale((langid!("en-US"), include_str!("./data/i18n/en-US.ftl")))
|
||||||
|
.with_locale((
|
||||||
|
langid!("es-ES"),
|
||||||
|
PathBuf::from("./examples/data/i18n/es-ES.ftl"),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!(Body {})
|
||||||
|
}
|
||||||
31
dioxus-i18n/src/error.rs
Normal file
31
dioxus-i18n/src/error.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("invalid message id: '{0}'")]
|
||||||
|
InvalidMessageId(String),
|
||||||
|
|
||||||
|
#[error("message id not found for key: '{0}'")]
|
||||||
|
MessageIdNotFound(String),
|
||||||
|
|
||||||
|
#[error("attribute id not found for key: '{0}'")]
|
||||||
|
AttributeIdNotFound(String),
|
||||||
|
|
||||||
|
#[error("message pattern not found for key: '{0}'")]
|
||||||
|
MessagePatternNotFound(String),
|
||||||
|
|
||||||
|
#[error("fluent errors during lookup:\n{0}")]
|
||||||
|
FluentErrorsDetected(String),
|
||||||
|
|
||||||
|
#[error("failed to read locale resource from path: {0}")]
|
||||||
|
LocaleResourcePathReadFailed(String),
|
||||||
|
|
||||||
|
#[error("fallback for \"{0}\" must have locale")]
|
||||||
|
FallbackMustHaveLocale(String),
|
||||||
|
|
||||||
|
#[error("language id cannot be determined - reason: {0}")]
|
||||||
|
InvalidLanguageId(String),
|
||||||
|
|
||||||
|
#[error("invalid path: {0}")]
|
||||||
|
InvalidPath(String),
|
||||||
|
}
|
||||||
101
dioxus-i18n/src/i18n_macro.rs
Normal file
101
dioxus-i18n/src/i18n_macro.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//! Key translation macros.
|
||||||
|
//!
|
||||||
|
//! Using file:
|
||||||
|
//!
|
||||||
|
//! ```ftl
|
||||||
|
//! # en-US.ftl
|
||||||
|
//! #
|
||||||
|
//! hello = Hello, {$name}!
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
/// Translate message from key, returning [`crate::prelude::DioxusI18nError`] if id not found...
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use dioxus_i18n::{te, prelude::*};
|
||||||
|
/// # use unic_langid::langid;
|
||||||
|
/// # #[component]
|
||||||
|
/// # fn Example() -> Element {
|
||||||
|
/// # let lang = langid!("en-US");
|
||||||
|
/// # let config = I18nConfig::new(lang.clone()).with_locale((lang.clone(), "hello = Hello, {$name}")).with_fallback(lang.clone());
|
||||||
|
/// # let mut i18n = use_init_i18n(|| config);
|
||||||
|
/// let name = "Avery Gigglesworth";
|
||||||
|
/// let hi = te!("hello", name: {name}).expect("message id 'name' should be present");
|
||||||
|
/// assert_eq!(hi, "Hello, Avery Gigglesworth");
|
||||||
|
/// # rsx! { "" }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! te {
|
||||||
|
($id:expr, $( $name:ident : $value:expr ),* ) => {
|
||||||
|
{
|
||||||
|
let mut params_map = $crate::fluent::FluentArgs::new();
|
||||||
|
$(
|
||||||
|
params_map.set(stringify!($name), $value);
|
||||||
|
)*
|
||||||
|
$crate::prelude::i18n().try_translate_with_args($id, Some(¶ms_map))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($id:expr ) => {{
|
||||||
|
$crate::prelude::i18n().try_translate($id)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translate message from key, panic! if id not found...
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use dioxus_i18n::{t, prelude::*};
|
||||||
|
/// # use unic_langid::langid;
|
||||||
|
/// # #[component]
|
||||||
|
/// # fn Example() -> Element {
|
||||||
|
/// # let lang = langid!("en-US");
|
||||||
|
/// # let config = I18nConfig::new(lang.clone()).with_locale((lang.clone(), "hello = Hello, {$name}")).with_fallback(lang.clone());
|
||||||
|
/// # let mut i18n = use_init_i18n(|| config);
|
||||||
|
/// let name = "Avery Gigglesworth";
|
||||||
|
/// let hi = t!("hello", name: {name});
|
||||||
|
/// assert_eq!(hi, "Hello, Avery Gigglesworth");
|
||||||
|
/// # rsx! { "" }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! t {
|
||||||
|
($id:expr, $( $name:ident : $value:expr ),* ) => {
|
||||||
|
$crate::te!($id, $( $name : $value ),*).unwrap_or_else(|e| panic!("{}", e.to_string()))
|
||||||
|
};
|
||||||
|
|
||||||
|
($id:expr ) => {{
|
||||||
|
$crate::te!($id).unwrap_or_else(|e| panic!("{}", e.to_string()))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translate message from key, return id if no translation found...
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
|
/// # use dioxus_i18n::{tid, prelude::*};
|
||||||
|
/// # use unic_langid::langid;
|
||||||
|
/// # #[component]
|
||||||
|
/// # fn Example() -> Element {
|
||||||
|
/// # let lang = langid!("en-US");
|
||||||
|
/// # let config = I18nConfig::new(lang.clone()).with_locale((lang.clone(), "hello = Hello, {$name}")).with_fallback(lang.clone());
|
||||||
|
/// # let mut i18n = use_init_i18n(|| config);
|
||||||
|
/// let message = tid!("no-key");
|
||||||
|
/// assert_eq!(message, "message-id: no-key should be translated");
|
||||||
|
/// # rsx! { "" }
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! tid {
|
||||||
|
($id:expr, $( $name:ident : $value:expr ),* ) => {
|
||||||
|
$crate::te!($id, $( $name : $value ),*).unwrap_or_else(|e| e.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
($id:expr ) => {{
|
||||||
|
$crate::te!($id).unwrap_or_else(|e| e.to_string())
|
||||||
|
}};
|
||||||
|
}
|
||||||
12
dioxus-i18n/src/lib.rs
Normal file
12
dioxus-i18n/src/lib.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
mod error;
|
||||||
|
pub mod i18n_macro;
|
||||||
|
pub mod use_i18n;
|
||||||
|
|
||||||
|
pub use fluent;
|
||||||
|
pub use unic_langid;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::error::Error as DioxusI18nError;
|
||||||
|
pub use crate::use_i18n::*;
|
||||||
|
}
|
||||||
703
dioxus-i18n/src/use_i18n.rs
Normal file
703
dioxus-i18n/src/use_i18n.rs
Normal file
@@ -0,0 +1,703 @@
|
|||||||
|
use super::error::Error;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use fluent::{FluentArgs, FluentBundle, FluentResource};
|
||||||
|
use unic_langid::LanguageIdentifier;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// `Locale` is a "place-holder" around what will eventually be a `fluent::FluentBundle`
|
||||||
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
pub struct Locale {
|
||||||
|
id: LanguageIdentifier,
|
||||||
|
resource: LocaleResource,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Locale {
|
||||||
|
pub fn new_static(id: LanguageIdentifier, str: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
resource: LocaleResource::Static(str),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn new_dynamic(id: LanguageIdentifier, path: impl Into<PathBuf>) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
resource: LocaleResource::Path(path.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<(LanguageIdentifier, T)> for Locale
|
||||||
|
where
|
||||||
|
T: Into<LocaleResource>,
|
||||||
|
{
|
||||||
|
fn from((id, resource): (LanguageIdentifier, T)) -> Self {
|
||||||
|
let resource = resource.into();
|
||||||
|
Self { id, resource }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `LocaleResource` can be static text, or a filesystem file (not supported in WASM).
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum LocaleResource {
|
||||||
|
Static(&'static str),
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
Path(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocaleResource {
|
||||||
|
pub fn try_to_resource_string(&self) -> Result<String, Error> {
|
||||||
|
match self {
|
||||||
|
Self::Static(str) => Ok(str.to_string()),
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
Self::Path(path) => std::fs::read_to_string(path)
|
||||||
|
.map_err(|e| Error::LocaleResourcePathReadFailed(e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_resource_string(&self) -> String {
|
||||||
|
let result = self.try_to_resource_string();
|
||||||
|
match result {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(err) => panic!("failed to create resource string {:?}: {}", self, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for LocaleResource {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
Self::Static(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
impl From<PathBuf> for LocaleResource {
|
||||||
|
fn from(value: PathBuf) -> Self {
|
||||||
|
Self::Path(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The configuration for `I18n`.
|
||||||
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
pub struct I18nConfig {
|
||||||
|
/// The initial language, can be later changed with [`I18n::set_language`]
|
||||||
|
id: LanguageIdentifier,
|
||||||
|
|
||||||
|
/// The final fallback language if no other locales are found for `id`.
|
||||||
|
/// A `Locale` must exist in `locales' if `fallback` is defined.
|
||||||
|
fallback: Option<LanguageIdentifier>,
|
||||||
|
|
||||||
|
/// The locale_resources added to the configuration.
|
||||||
|
locale_resources: Vec<LocaleResource>,
|
||||||
|
|
||||||
|
/// The locales added to the configuration.
|
||||||
|
locales: HashMap<LanguageIdentifier, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I18nConfig {
|
||||||
|
/// Create an i18n config with the selected [LanguageIdentifier].
|
||||||
|
pub fn new(id: LanguageIdentifier) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: Vec::new(),
|
||||||
|
locales: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a fallback [LanguageIdentifier].
|
||||||
|
pub fn with_fallback(mut self, fallback: LanguageIdentifier) -> Self {
|
||||||
|
self.fallback = Some(fallback);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add [Locale].
|
||||||
|
/// It is possible to share locales resources. If this locale's resource
|
||||||
|
/// matches a previously added one, then this locale will use the existing one.
|
||||||
|
/// This is primarily for the static locale_resources to avoid string duplication.
|
||||||
|
pub fn with_locale<T>(mut self, locale: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<Locale>,
|
||||||
|
{
|
||||||
|
let locale = locale.into();
|
||||||
|
let locale_resources_len = self.locale_resources.len();
|
||||||
|
|
||||||
|
let index = self
|
||||||
|
.locale_resources
|
||||||
|
.iter()
|
||||||
|
.position(|r| *r == locale.resource)
|
||||||
|
.unwrap_or(locale_resources_len);
|
||||||
|
|
||||||
|
if index == locale_resources_len {
|
||||||
|
self.locale_resources.push(locale.resource)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.locales.insert(locale.id, index);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple locales from given folder, based on their filename.
|
||||||
|
///
|
||||||
|
/// If the path represents a folder, then the folder will be deep traversed for
|
||||||
|
/// all '*.ftl' files. If the filename represents a [LanguageIdentifier] then it
|
||||||
|
/// will be added to the config.
|
||||||
|
///
|
||||||
|
/// If the path represents a file, then the filename must represent a
|
||||||
|
/// unic_langid::LanguageIdentifier for it to be added to the config.
|
||||||
|
///
|
||||||
|
/// The method is not available for `wasm32` builds.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn try_with_auto_locales(self, path: PathBuf) -> Result<Self, Error> {
|
||||||
|
if path.is_dir() {
|
||||||
|
let files = find_ftl_files(&path)?;
|
||||||
|
files
|
||||||
|
.into_iter()
|
||||||
|
.try_fold(self, |acc, file| acc.with_auto_pathbuf(file))
|
||||||
|
} else if is_ftl_file(&path) {
|
||||||
|
self.with_auto_pathbuf(path)
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidPath(path.to_string_lossy().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn with_auto_pathbuf(self, file: PathBuf) -> Result<Self, Error> {
|
||||||
|
assert!(is_ftl_file(&file));
|
||||||
|
|
||||||
|
let stem = file.file_stem().ok_or_else(|| {
|
||||||
|
Error::InvalidLanguageId(format!("No file stem: '{}'", file.display()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let id_str = stem.to_str().ok_or_else(|| {
|
||||||
|
Error::InvalidLanguageId(format!("Cannot convert: {}", stem.to_string_lossy()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let id = LanguageIdentifier::from_bytes(id_str.as_bytes())
|
||||||
|
.map_err(|e| Error::InvalidLanguageId(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(self.with_locale((id, file)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple locales from given folder, based on their filename.
|
||||||
|
///
|
||||||
|
/// Will panic! on error.
|
||||||
|
///
|
||||||
|
/// The method is not available for `wasm32` builds.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn with_auto_locales(self, path: PathBuf) -> Self {
|
||||||
|
let path_name = path.display().to_string();
|
||||||
|
let result = self.try_with_auto_locales(path);
|
||||||
|
match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(err) => panic!(
|
||||||
|
"with_auto_locales must have valid pathbuf {}: {}",
|
||||||
|
path_name, err
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn find_ftl_files(folder: &PathBuf) -> Result<Vec<PathBuf>, Error> {
|
||||||
|
let ftl_files: Vec<PathBuf> = WalkDir::new(folder)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| is_ftl_file(entry.path()))
|
||||||
|
.map(|entry| entry.path().to_path_buf())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(ftl_files)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn is_ftl_file(entry: &Path) -> bool {
|
||||||
|
entry.is_file() && entry.extension().map(|ext| ext == "ftl").unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize an i18n provider.
|
||||||
|
pub fn try_use_init_i18n(init: impl FnOnce() -> I18nConfig) -> Result<I18n, Error> {
|
||||||
|
use_context_provider(move || {
|
||||||
|
// Coverage false -ve: See https://github.com/xd009642/tarpaulin/issues/1675
|
||||||
|
let I18nConfig {
|
||||||
|
id,
|
||||||
|
fallback,
|
||||||
|
locale_resources,
|
||||||
|
locales,
|
||||||
|
} = init();
|
||||||
|
|
||||||
|
I18n::try_new(id, fallback, locale_resources, locales)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize an i18n provider.
|
||||||
|
pub fn use_init_i18n(init: impl FnOnce() -> I18nConfig) -> I18n {
|
||||||
|
use_context_provider(move || {
|
||||||
|
// Coverage false -ve: See https://github.com/xd009642/tarpaulin/issues/1675
|
||||||
|
let I18nConfig {
|
||||||
|
id,
|
||||||
|
fallback,
|
||||||
|
locale_resources,
|
||||||
|
locales,
|
||||||
|
} = init();
|
||||||
|
|
||||||
|
match I18n::try_new(id, fallback, locale_resources, locales) {
|
||||||
|
Ok(i18n) => i18n,
|
||||||
|
Err(e) => panic!("Failed to create I18n context: {}", e),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct I18n {
|
||||||
|
selected_language: Signal<LanguageIdentifier>,
|
||||||
|
fallback_language: Signal<Option<LanguageIdentifier>>,
|
||||||
|
locale_resources: Signal<Vec<LocaleResource>>,
|
||||||
|
locales: Signal<HashMap<LanguageIdentifier, usize>>,
|
||||||
|
active_bundle: Signal<FluentBundle<FluentResource>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I18n {
|
||||||
|
pub fn try_new(
|
||||||
|
selected_language: LanguageIdentifier,
|
||||||
|
fallback_language: Option<LanguageIdentifier>,
|
||||||
|
locale_resources: Vec<LocaleResource>,
|
||||||
|
locales: HashMap<LanguageIdentifier, usize>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let bundle = try_create_bundle(
|
||||||
|
&selected_language,
|
||||||
|
&fallback_language,
|
||||||
|
&locale_resources,
|
||||||
|
&locales,
|
||||||
|
)?;
|
||||||
|
Ok(Self {
|
||||||
|
selected_language: Signal::new(selected_language),
|
||||||
|
fallback_language: Signal::new(fallback_language),
|
||||||
|
locale_resources: Signal::new(locale_resources),
|
||||||
|
locales: Signal::new(locales),
|
||||||
|
active_bundle: Signal::new(bundle),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
selected_language: LanguageIdentifier,
|
||||||
|
fallback_language: Option<LanguageIdentifier>,
|
||||||
|
locale_resources: Vec<LocaleResource>,
|
||||||
|
locales: HashMap<LanguageIdentifier, usize>,
|
||||||
|
) -> Self {
|
||||||
|
let result = Self::try_new(
|
||||||
|
selected_language,
|
||||||
|
fallback_language,
|
||||||
|
locale_resources,
|
||||||
|
locales,
|
||||||
|
);
|
||||||
|
match result {
|
||||||
|
Ok(i18n) => i18n,
|
||||||
|
Err(err) => panic!("I18n cannot be created: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_translate_with_args(
|
||||||
|
&self,
|
||||||
|
msg: &str,
|
||||||
|
args: Option<&FluentArgs>,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let (message_id, attribute_name) = Self::decompose_identifier(msg)?;
|
||||||
|
|
||||||
|
let bundle = self.active_bundle.read();
|
||||||
|
|
||||||
|
let message = bundle
|
||||||
|
.get_message(message_id)
|
||||||
|
.ok_or_else(|| Error::MessageIdNotFound(message_id.into()))?;
|
||||||
|
|
||||||
|
let pattern = if let Some(attribute_name) = attribute_name {
|
||||||
|
let attribute = message
|
||||||
|
.get_attribute(attribute_name)
|
||||||
|
.ok_or_else(|| Error::AttributeIdNotFound(msg.to_string()))?;
|
||||||
|
attribute.value()
|
||||||
|
} else {
|
||||||
|
message
|
||||||
|
.value()
|
||||||
|
.ok_or_else(|| Error::MessagePatternNotFound(message_id.into()))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut errors = vec![];
|
||||||
|
let translation = bundle
|
||||||
|
.format_pattern(pattern, args, &mut errors)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
(errors.is_empty())
|
||||||
|
.then_some(translation)
|
||||||
|
.ok_or_else(|| Error::FluentErrorsDetected(format!("{:#?}", errors)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decompose_identifier(msg: &str) -> Result<(&str, Option<&str>), Error> {
|
||||||
|
let parts: Vec<&str> = msg.split('.').collect();
|
||||||
|
match parts.as_slice() {
|
||||||
|
[message_id] => Ok((message_id, None)),
|
||||||
|
[message_id, attribute_name] => Ok((message_id, Some(attribute_name))),
|
||||||
|
_ => Err(Error::InvalidMessageId(msg.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate_with_args(&self, msg: &str, args: Option<&FluentArgs>) -> String {
|
||||||
|
let result = self.try_translate_with_args(msg, args);
|
||||||
|
match result {
|
||||||
|
Ok(translation) => translation,
|
||||||
|
Err(err) => panic!("Failed to translate {}: {}", msg, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn try_translate(&self, msg: &str) -> Result<String, Error> {
|
||||||
|
self.try_translate_with_args(msg, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translate(&self, msg: &str) -> String {
|
||||||
|
let result = self.try_translate(msg);
|
||||||
|
match result {
|
||||||
|
Ok(translation) => translation,
|
||||||
|
Err(err) => panic!("Failed to translate {}: {}", msg, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the selected language.
|
||||||
|
#[inline]
|
||||||
|
pub fn language(&self) -> LanguageIdentifier {
|
||||||
|
self.selected_language.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the fallback language.
|
||||||
|
pub fn fallback_language(&self) -> Option<LanguageIdentifier> {
|
||||||
|
self.fallback_language.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the selected language.
|
||||||
|
pub fn try_set_language(&mut self, id: LanguageIdentifier) -> Result<(), Error> {
|
||||||
|
*self.selected_language.write() = id;
|
||||||
|
self.try_update_active_bundle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the selected language.
|
||||||
|
pub fn set_language(&mut self, id: LanguageIdentifier) {
|
||||||
|
let id_name = id.to_string();
|
||||||
|
let result = self.try_set_language(id);
|
||||||
|
match result {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => panic!("cannot set language {}: {}", id_name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the fallback language.
|
||||||
|
pub fn try_set_fallback_language(&mut self, id: LanguageIdentifier) -> Result<(), Error> {
|
||||||
|
self.locales
|
||||||
|
.read()
|
||||||
|
.get(&id)
|
||||||
|
.ok_or_else(|| Error::FallbackMustHaveLocale(id.to_string()))?;
|
||||||
|
|
||||||
|
*self.fallback_language.write() = Some(id);
|
||||||
|
self.try_update_active_bundle()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the fallback language.
|
||||||
|
pub fn set_fallback_language(&mut self, id: LanguageIdentifier) {
|
||||||
|
let id_name = id.to_string();
|
||||||
|
let result = self.try_set_fallback_language(id);
|
||||||
|
match result {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => panic!("cannot set fallback language {}: {}", id_name, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_update_active_bundle(&mut self) -> Result<(), Error> {
|
||||||
|
let bundle = try_create_bundle(
|
||||||
|
&self.selected_language.peek(),
|
||||||
|
&self.fallback_language.peek(),
|
||||||
|
&self.locale_resources.peek(),
|
||||||
|
&self.locales.peek(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.active_bundle.set(bundle);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_create_bundle(
|
||||||
|
selected_language: &LanguageIdentifier,
|
||||||
|
fallback_language: &Option<LanguageIdentifier>,
|
||||||
|
locale_resources: &[LocaleResource],
|
||||||
|
locales: &HashMap<LanguageIdentifier, usize>,
|
||||||
|
) -> Result<FluentBundle<FluentResource>, Error> {
|
||||||
|
let add_resource = move |bundle: &mut FluentBundle<FluentResource>,
|
||||||
|
langid: &LanguageIdentifier,
|
||||||
|
locale_resources: &[LocaleResource]| {
|
||||||
|
if let Some(&i) = locales.get(langid) {
|
||||||
|
let resource = &locale_resources[i];
|
||||||
|
let resource =
|
||||||
|
FluentResource::try_new(resource.try_to_resource_string()?).map_err(|e| {
|
||||||
|
Error::FluentErrorsDetected(format!("resource langid: {}\n{:#?}", langid, e))
|
||||||
|
})?;
|
||||||
|
bundle.add_resource_overriding(resource);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bundle = FluentBundle::new(vec![selected_language.clone()]);
|
||||||
|
if let Some(fallback_language) = fallback_language {
|
||||||
|
add_resource(&mut bundle, fallback_language, locale_resources)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (language, script, region, variants) = selected_language.clone().into_parts();
|
||||||
|
let variants_lang = LanguageIdentifier::from_parts(language, script, region, &variants);
|
||||||
|
let region_lang = LanguageIdentifier::from_parts(language, script, region, &[]);
|
||||||
|
let script_lang = LanguageIdentifier::from_parts(language, script, None, &[]);
|
||||||
|
let language_lang = LanguageIdentifier::from_parts(language, None, None, &[]);
|
||||||
|
|
||||||
|
add_resource(&mut bundle, &language_lang, locale_resources)?;
|
||||||
|
add_resource(&mut bundle, &script_lang, locale_resources)?;
|
||||||
|
add_resource(&mut bundle, ®ion_lang, locale_resources)?;
|
||||||
|
add_resource(&mut bundle, &variants_lang, locale_resources)?;
|
||||||
|
|
||||||
|
/* Add this code when the fluent crate includes FluentBundle::add_builtins.
|
||||||
|
* This will allow the use of built-in functions like `NUMBER` and `DATETIME`.
|
||||||
|
* See [Fluent issue](https://github.com/projectfluent/fluent-rs/issues/181) for more information.
|
||||||
|
bundle
|
||||||
|
.add_builtins()
|
||||||
|
.map_err(|e| Error::FluentErrorsDetected(e.to_string()))?;
|
||||||
|
*/
|
||||||
|
|
||||||
|
Ok(bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn i18n() -> I18n {
|
||||||
|
consume_context()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use unic_langid::langid;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_locale_to_config_explicit_locale() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||||
|
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.with_locale(Locale::new_static(LANG_B, "lang = lang_b"))
|
||||||
|
.with_locale(Locale::new_dynamic(LANG_C, PathBuf::new()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![
|
||||||
|
LocaleResource::Static("lang = lang_b"),
|
||||||
|
LocaleResource::Path(PathBuf::new()),
|
||||||
|
],
|
||||||
|
locales: HashMap::from([(LANG_B, 0), (LANG_C, 1)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_locale_to_config_implicit_locale() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||||
|
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.with_locale((LANG_B, "lang = lang_b"))
|
||||||
|
.with_locale((LANG_C, PathBuf::new()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![
|
||||||
|
LocaleResource::Static("lang = lang_b"),
|
||||||
|
LocaleResource::Path(PathBuf::new())
|
||||||
|
],
|
||||||
|
locales: HashMap::from([(LANG_B, 0), (LANG_C, 1)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_locale_string_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A).with_locale((LANG_B, "lang = lang_b"));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![LocaleResource::Static("lang = lang_b")],
|
||||||
|
locales: HashMap::from([(LANG_B, 0)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_shared_locale_string_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||||
|
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||||
|
|
||||||
|
let shared_string = "lang = a language";
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.with_locale((LANG_B, shared_string))
|
||||||
|
.with_locale((LANG_C, shared_string));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![LocaleResource::Static(shared_string)],
|
||||||
|
locales: HashMap::from([(LANG_B, 0), (LANG_C, 0)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_locale_pathbuf_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.with_locale((LANG_C, PathBuf::from("./test/data/fallback/la.ftl")));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![LocaleResource::Path(PathBuf::from(
|
||||||
|
"./test/data/fallback/la.ftl"
|
||||||
|
))],
|
||||||
|
locales: HashMap::from([(LANG_C, 0)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_shared_locale_pathbuf_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||||
|
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||||
|
|
||||||
|
let shared_pathbuf = PathBuf::from("./test/data/fallback/la.ftl");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.with_locale((LANG_B, shared_pathbuf.clone()))
|
||||||
|
.with_locale((LANG_C, shared_pathbuf.clone()));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
config,
|
||||||
|
I18nConfig {
|
||||||
|
id: LANG_A,
|
||||||
|
fallback: None,
|
||||||
|
locale_resources: vec![LocaleResource::Path(shared_pathbuf)],
|
||||||
|
locales: HashMap::from([(LANG_B, 0), (LANG_C, 0)]),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_auto_add_locales_folder_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
|
||||||
|
let root_path_str = &format!("{}/tests/data/fallback/", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let pathbuf = PathBuf::from(root_path_str);
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.try_with_auto_locales(pathbuf)
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let expected_locales = [
|
||||||
|
"fb-FB",
|
||||||
|
"la",
|
||||||
|
"la-Scpt",
|
||||||
|
"la-Scpt-LA",
|
||||||
|
"la-Scpt-LA-variants",
|
||||||
|
];
|
||||||
|
|
||||||
|
assert_eq!(config.locales.len(), expected_locales.len());
|
||||||
|
assert_eq!(config.locale_resources.len(), expected_locales.len());
|
||||||
|
|
||||||
|
expected_locales.into_iter().for_each(|l| {
|
||||||
|
let expected_filename = format!("{root_path_str}/{l}.ftl");
|
||||||
|
let id = LanguageIdentifier::from_bytes(l.as_bytes()).unwrap();
|
||||||
|
assert!(config.locales.get(&id).is_some());
|
||||||
|
assert!(config
|
||||||
|
.locale_resources
|
||||||
|
.contains(&LocaleResource::Path(PathBuf::from(expected_filename))));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_auto_add_locales_file_to_config() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
|
||||||
|
let path_str = &format!(
|
||||||
|
"{}/tests/data/fallback/fb-FB.ftl",
|
||||||
|
env!("CARGO_MANIFEST_DIR")
|
||||||
|
);
|
||||||
|
let pathbuf = PathBuf::from(path_str);
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A)
|
||||||
|
.try_with_auto_locales(pathbuf.clone())
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(config.locales.len(), 1);
|
||||||
|
assert!(config.locales.get(&langid!("fb-FB")).is_some());
|
||||||
|
|
||||||
|
assert_eq!(config.locale_resources.len(), 1);
|
||||||
|
assert!(config
|
||||||
|
.locale_resources
|
||||||
|
.contains(&LocaleResource::Path(pathbuf)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn will_fail_auto_locales_with_invalid_folder() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
|
||||||
|
let root_path_str = &format!("{}/non_existing_path/", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let pathbuf = PathBuf::from(root_path_str);
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A).try_with_auto_locales(pathbuf);
|
||||||
|
assert_eq!(config.is_err(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn will_fail_auto_locales_with_invalid_file() {
|
||||||
|
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||||
|
|
||||||
|
let path_str = &format!(
|
||||||
|
"{}/tests/data/fallback/invalid_language_id.ftl",
|
||||||
|
env!("CARGO_MANIFEST_DIR")
|
||||||
|
);
|
||||||
|
let pathbuf = PathBuf::from(path_str);
|
||||||
|
|
||||||
|
let config = I18nConfig::new(LANG_A).try_with_auto_locales(pathbuf);
|
||||||
|
assert_eq!(config.is_err(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
dioxus-i18n/tests/README.md
Normal file
13
dioxus-i18n/tests/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Note
|
||||||
|
|
||||||
|
//*****************************************************************************
|
||||||
|
//
|
||||||
|
// This set of tests takes a heavy handed approach to errors, whereby the
|
||||||
|
// process is exited. This is done because panic! and assert_eq! failures
|
||||||
|
// are trapped within `dioxus::runtime::RuntimeGuard`.
|
||||||
|
// Unfortunately panic! is still made silent.
|
||||||
|
//
|
||||||
|
// Errors will be shown with:
|
||||||
|
// cargo test -- --nocapture
|
||||||
|
//
|
||||||
|
//*****************************************************************************
|
||||||
3
dioxus-i18n/tests/common/mod.rs
Normal file
3
dioxus-i18n/tests/common/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod test_hook;
|
||||||
|
|
||||||
|
pub(crate) use test_hook::test_hook;
|
||||||
85
dioxus-i18n/tests/common/test_hook.rs
Normal file
85
dioxus-i18n/tests/common/test_hook.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Lifted from: https://dioxuslabs.com/learn/0.6/cookbook/testing
|
||||||
|
//
|
||||||
|
// Much curtialed functionality and massaged to use in the local testing
|
||||||
|
// here. This hook isn't intended for reuse.
|
||||||
|
//
|
||||||
|
|
||||||
|
use dioxus::{dioxus_core::NoOpMutations, prelude::*};
|
||||||
|
use futures::FutureExt;
|
||||||
|
|
||||||
|
use std::{cell::RefCell, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
|
pub(crate) fn test_hook<V: 'static>(
|
||||||
|
initialize: impl FnMut() -> V + 'static,
|
||||||
|
check: impl FnMut(V, &mut Assertions) + 'static,
|
||||||
|
) {
|
||||||
|
#[derive(Props)]
|
||||||
|
struct MockAppComponent<I: 'static, C: 'static> {
|
||||||
|
hook: Rc<RefCell<I>>,
|
||||||
|
check: Rc<RefCell<C>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> PartialEq for MockAppComponent<I, C> {
|
||||||
|
fn eq(&self, _: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> Clone for MockAppComponent<I, C> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
hook: self.hook.clone(),
|
||||||
|
check: self.check.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mock_app<I: FnMut() -> V, C: FnMut(V, &mut Assertions), V>(
|
||||||
|
props: MockAppComponent<I, C>,
|
||||||
|
) -> Element {
|
||||||
|
let value = props.hook.borrow_mut()();
|
||||||
|
|
||||||
|
let mut assertions = Assertions::new();
|
||||||
|
|
||||||
|
props.check.borrow_mut()(value, &mut assertions);
|
||||||
|
|
||||||
|
rsx! { div {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vdom = VirtualDom::new_with_props(
|
||||||
|
mock_app,
|
||||||
|
MockAppComponent {
|
||||||
|
hook: Rc::new(RefCell::new(initialize)),
|
||||||
|
check: Rc::new(RefCell::new(check)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
vdom.rebuild_in_place();
|
||||||
|
|
||||||
|
while vdom.wait_for_work().now_or_never().is_some() {
|
||||||
|
vdom.render_immediate(&mut NoOpMutations);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Assertions {}
|
||||||
|
|
||||||
|
impl Assertions {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert<T>(&mut self, actual: T, expected: T, id: &str)
|
||||||
|
where
|
||||||
|
T: PartialEq + Debug,
|
||||||
|
{
|
||||||
|
if actual != expected {
|
||||||
|
eprintln!(
|
||||||
|
"***** ERROR in {}: actual: '{:?}' != expected: '{:?}' *****\n",
|
||||||
|
id, actual, expected
|
||||||
|
);
|
||||||
|
std::process::exit(-1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
5
dioxus-i18n/tests/data/fallback/fb-FB.ftl
Normal file
5
dioxus-i18n/tests/data/fallback/fb-FB.ftl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
fallback = fallback only
|
||||||
|
language = fallback language
|
||||||
|
script = fallback script
|
||||||
|
region = fallback region
|
||||||
|
variants = fallback variants
|
||||||
1
dioxus-i18n/tests/data/fallback/la-Scpt-LA-variants.ftl
Normal file
1
dioxus-i18n/tests/data/fallback/la-Scpt-LA-variants.ftl
Normal file
@@ -0,0 +1 @@
|
|||||||
|
variants = variants only
|
||||||
2
dioxus-i18n/tests/data/fallback/la-Scpt-LA.ftl
Normal file
2
dioxus-i18n/tests/data/fallback/la-Scpt-LA.ftl
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
region = region only
|
||||||
|
variants = region variants
|
||||||
3
dioxus-i18n/tests/data/fallback/la-Scpt.ftl
Normal file
3
dioxus-i18n/tests/data/fallback/la-Scpt.ftl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
script = script only
|
||||||
|
region = script region
|
||||||
|
variants = script variants
|
||||||
4
dioxus-i18n/tests/data/fallback/la.ftl
Normal file
4
dioxus-i18n/tests/data/fallback/la.ftl
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
language = language only
|
||||||
|
script = language script
|
||||||
|
region = language region
|
||||||
|
variants = language variants
|
||||||
5
dioxus-i18n/tests/data/i18n/en.ftl
Normal file
5
dioxus-i18n/tests/data/i18n/en.ftl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
hello = Hello, {$name}!
|
||||||
|
simple = Hello, Zaphod!
|
||||||
|
my_component = My Component
|
||||||
|
.placeholder = Component's placeholder
|
||||||
|
.hint = Component's hint with parameter {$name}
|
||||||
44
dioxus-i18n/tests/defects_spec.rs
Normal file
44
dioxus-i18n/tests/defects_spec.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
use dioxus_i18n::{
|
||||||
|
prelude::{use_init_i18n, I18n, I18nConfig},
|
||||||
|
t,
|
||||||
|
};
|
||||||
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn issue_15_recent_change_to_t_macro_unnecessarily_breaks_v0_3_code_test_attr() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
t!(&format!("hello"), name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "translate_from_static_source");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"translate_from_static_source",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn issue_15_recent_change_to_t_macro_unnecessarily_breaks_v0_3_code_test_no_attr() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| t!(&format!("simple")));
|
||||||
|
proxy.assert(panic.is_ok(), true, "translate_from_static_source");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, Zaphod!".to_string(),
|
||||||
|
"translate_from_static_source",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const EN: LanguageIdentifier = langid!("en");
|
||||||
|
|
||||||
|
fn i18n_from_static() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN).with_locale((EN, include_str!("./data/i18n/en.ftl")));
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
99
dioxus-i18n/tests/graceful_fallback_spec.rs
Normal file
99
dioxus-i18n/tests/graceful_fallback_spec.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
use dioxus_i18n::prelude::{use_init_i18n, I18n, I18nConfig};
|
||||||
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exact_locale_match_will_use_translation() {
|
||||||
|
test_hook(i18n, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
value
|
||||||
|
.try_translate("variants")
|
||||||
|
.expect("test message id must exist"),
|
||||||
|
"variants only".to_string(),
|
||||||
|
"exact_locale_match_will_use_translation",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_exact_locale_match_will_use_region() {
|
||||||
|
test_hook(i18n, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
value
|
||||||
|
.try_translate("region")
|
||||||
|
.expect("test message id must exist"),
|
||||||
|
"region only".to_string(),
|
||||||
|
"non_exact_locale_match_will_use_region",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_exact_locale_match_will_use_script() {
|
||||||
|
test_hook(i18n, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
value
|
||||||
|
.try_translate("script")
|
||||||
|
.expect("test message id must exist"),
|
||||||
|
"script only".to_string(),
|
||||||
|
"non_exact_locale_match_will_use_script",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_exact_locale_match_will_use_language() {
|
||||||
|
test_hook(i18n, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
value
|
||||||
|
.try_translate("language")
|
||||||
|
.expect("test message id must exist"),
|
||||||
|
"language only".to_string(),
|
||||||
|
"non_exact_locale_match_will_use_language",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_locale_match_will_use_fallback() {
|
||||||
|
test_hook(i18n, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
value
|
||||||
|
.try_translate("fallback")
|
||||||
|
.expect("test message id must exist"),
|
||||||
|
"fallback only".to_string(),
|
||||||
|
"no_locale_match_will_use_fallback",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i18n() -> I18n {
|
||||||
|
const FALLBACK_LANG: LanguageIdentifier = langid!("fb-FB");
|
||||||
|
const LANGUAGE_LANG: LanguageIdentifier = langid!("la");
|
||||||
|
const SCRIPT_LANG: LanguageIdentifier = langid!("la-Scpt");
|
||||||
|
const REGION_LANG: LanguageIdentifier = langid!("la-Scpt-LA");
|
||||||
|
let variants_lang: LanguageIdentifier = langid!("la-Scpt-LA-variants");
|
||||||
|
|
||||||
|
let config = I18nConfig::new(variants_lang.clone())
|
||||||
|
.with_locale((LANGUAGE_LANG, include_str!("../tests/data/fallback/la.ftl")))
|
||||||
|
.with_locale((
|
||||||
|
SCRIPT_LANG,
|
||||||
|
include_str!("../tests/data/fallback/la-Scpt.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale((
|
||||||
|
REGION_LANG,
|
||||||
|
include_str!("../tests/data/fallback/la-Scpt-LA.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale((
|
||||||
|
variants_lang.clone(),
|
||||||
|
include_str!("../tests/data/fallback/la-Scpt-LA-variants.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale((
|
||||||
|
FALLBACK_LANG,
|
||||||
|
include_str!("../tests/data/fallback/fb-FB.ftl"),
|
||||||
|
))
|
||||||
|
.with_fallback(FALLBACK_LANG);
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
85
dioxus-i18n/tests/macro_reexport_spec.rs
Normal file
85
dioxus-i18n/tests/macro_reexport_spec.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// Test that macros work correctly when re-exported from another module
|
||||||
|
// This verifies that $crate is used correctly instead of hard-coded dioxus_i18n
|
||||||
|
|
||||||
|
mod reexport_module {
|
||||||
|
// Re-export the macros as if they were from a different crate
|
||||||
|
pub use dioxus_i18n::{t, te, tid};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
use dioxus_i18n::prelude::{use_init_i18n, I18n, I18nConfig};
|
||||||
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reexported_t_macro_works() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
reexport_module::t!("hello", name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "reexported_t_macro_works");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"reexported_t_macro_works",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reexported_te_macro_works() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
reexport_module::te!("hello", name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "reexported_te_macro_works");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap().ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"reexported_te_macro_works",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reexported_tid_macro_works() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
reexport_module::tid!("hello", name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "reexported_tid_macro_works");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"reexported_tid_macro_works",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reexported_macro_with_invalid_key_as_error() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| reexport_module::te!("invalid"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"reexported_macro_with_invalid_key_as_error",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap().err().unwrap().to_string(),
|
||||||
|
"message id not found for key: 'invalid'".to_string(),
|
||||||
|
"reexported_macro_with_invalid_key_as_error",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const EN: LanguageIdentifier = langid!("en");
|
||||||
|
|
||||||
|
fn i18n_from_static() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN).with_locale((EN, include_str!("./data/i18n/en.ftl")));
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
343
dioxus-i18n/tests/translations_spec.rs
Normal file
343
dioxus-i18n/tests/translations_spec.rs
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
|
||||||
|
use dioxus_i18n::{
|
||||||
|
prelude::{use_init_i18n, I18n, I18nConfig},
|
||||||
|
t, te, tid,
|
||||||
|
};
|
||||||
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_from_static_source() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
t!("hello", name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "translate_from_static_source");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"translate_from_static_source",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_to_translate_with_invalid_key() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let _ = &t!("invalid");
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_err(), true, "failed_to_translate_with_invalid_key");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_to_translate_with_invalid_key_as_error() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| te!("invalid"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"failed_to_translate_with_invalid_key_as_error",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap().err().unwrap().to_string(),
|
||||||
|
"message id not found for key: 'invalid'".to_string(),
|
||||||
|
"failed_to_translate_with_invalid_key_as_error",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_to_translate_with_invalid_key_with_args_as_error() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| te!("invalid", name: "<don't care>"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"failed_to_translate_with_invalid_key_with_args_as_error",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap().err().unwrap().to_string(),
|
||||||
|
"message id not found for key: 'invalid'".to_string(),
|
||||||
|
"failed_to_translate_with_invalid_key_with_args_as_error",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_to_translate_with_invalid_key_as_id() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("invalid"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"failed_to_translate_with_invalid_key_as_id",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"message id not found for key: 'invalid'".to_string(),
|
||||||
|
"failed_to_translate_with_invalid_key_as_id",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn failed_to_translate_with_invalid_key_with_args_as_id() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("invalid", name: "<don't care>"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"failed_to_translate_with_invalid_key_with_args_as_id",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"message id not found for key: 'invalid'".to_string(),
|
||||||
|
"failed_to_translate_with_invalid_key_with_args_as_id",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_root_message_in_attributed_definition() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"translate_root_message_in_attributed_definition",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"My Component".to_string(),
|
||||||
|
"translate_root_message_in_attributed_definition",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_attribute_with_no_args_in_attributed_definition() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component.placeholder"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"translate_attribute_with_no_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Component's placeholder".to_string(),
|
||||||
|
"translate_attribute_with_no_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_attribute_with_args_in_attributed_definition() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component.hint", name: "Zaphod"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"translate_attribute_with_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Component's hint with parameter \u{2068}Zaphod\u{2069}".to_string(),
|
||||||
|
"translate_attribute_with_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fail_translate_invalid_attribute_with_no_args_in_attributed_definition() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component.not_a_placeholder"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"fail_translate_invalid_attribute_with_no_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"attribute id not found for key: 'my_component.not_a_placeholder'".to_string(),
|
||||||
|
"fail_translate_invalid_attribute_with_no_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fail_translate_invalid_attribute_with_args_in_attributed_definition() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component.not_a_hint", name: "Zaphod"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"fail_translate_invalid_attribute_with_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"attribute id not found for key: 'my_component.not_a_hint'".to_string(),
|
||||||
|
"fail_translate_invalid_attribute_with_args_in_attributed_definition",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fail_translate_with_invalid_attribute_key() {
|
||||||
|
test_hook(i18n_from_static, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| tid!("my_component.placeholder.invalid"));
|
||||||
|
proxy.assert(
|
||||||
|
panic.is_ok(),
|
||||||
|
true,
|
||||||
|
"fail_translate_with_invalid_attribute_key",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"invalid message id: 'my_component.placeholder.invalid'".to_string(),
|
||||||
|
"fail_translate_with_invalid_attribute_key",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translate_from_dynamic_source() {
|
||||||
|
test_hook(i18n_from_dynamic, |_, proxy| {
|
||||||
|
let panic = std::panic::catch_unwind(|| {
|
||||||
|
let name = "World";
|
||||||
|
t!("hello", name: name)
|
||||||
|
});
|
||||||
|
proxy.assert(panic.is_ok(), true, "translate_from_dynamic_source");
|
||||||
|
proxy.assert(
|
||||||
|
panic.ok().unwrap(),
|
||||||
|
"Hello, \u{2068}World\u{2069}!".to_string(),
|
||||||
|
"translate_from_dynamic_source",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
#[ignore] // Panic hidden within test_hook.
|
||||||
|
fn fail_translate_from_dynamic_source_when_file_does_not_exist() {
|
||||||
|
test_hook(i18n_from_dynamic_none_existing, |_, _| unreachable!());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initial_language_is_set() {
|
||||||
|
test_hook(i18n_from_static, |value, proxy| {
|
||||||
|
proxy.assert(value.language(), EN, "initial_language_is_set");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn language_can_be_set() {
|
||||||
|
test_hook(i18n_from_static, |mut value, proxy| {
|
||||||
|
value
|
||||||
|
.try_set_language(JP)
|
||||||
|
.expect("set_language must succeed");
|
||||||
|
proxy.assert(value.language(), JP, "language_can_be_set");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_default_fallback_language() {
|
||||||
|
test_hook(i18n_from_static, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
format!("{:?}", value.fallback_language()),
|
||||||
|
"None".to_string(),
|
||||||
|
"no_default_fallback_language",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn some_default_fallback_language() {
|
||||||
|
test_hook(i18n_from_static_with_fallback, |value, proxy| {
|
||||||
|
proxy.assert(
|
||||||
|
format!("{:?}", value.fallback_language().map(|l| l.to_string())),
|
||||||
|
"Some(\"jp\")".to_string(),
|
||||||
|
"some_default_fallback_language",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallback_language_can_be_set() {
|
||||||
|
test_hook(i18n_from_static_with_fallback, |mut value, proxy| {
|
||||||
|
value
|
||||||
|
.try_set_fallback_language(EN)
|
||||||
|
.expect("try_set_fallback_language must succeed");
|
||||||
|
proxy.assert(
|
||||||
|
format!("{:?}", value.fallback_language().map(|l| l.to_string())),
|
||||||
|
"Some(\"en\")".to_string(),
|
||||||
|
"fallback_language_can_be_set",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fallback_language_must_have_locale_translation() {
|
||||||
|
test_hook(i18n_from_static_with_fallback, |mut value, proxy| {
|
||||||
|
let result = value.try_set_fallback_language(IT);
|
||||||
|
|
||||||
|
proxy.assert(
|
||||||
|
result.is_err(),
|
||||||
|
true,
|
||||||
|
"fallback_language_must_have_locale_translation",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
"fallback for \"it\" must have locale".to_string(),
|
||||||
|
"fallback_language_must_have_locale_translation",
|
||||||
|
);
|
||||||
|
proxy.assert(
|
||||||
|
format!("{:?}", value.fallback_language().map(|l| l.to_string())),
|
||||||
|
"Some(\"jp\")".to_string(),
|
||||||
|
"fallback_language_must_have_locale_translation",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const EN: LanguageIdentifier = langid!("en");
|
||||||
|
const IT: LanguageIdentifier = langid!("it");
|
||||||
|
const JP: LanguageIdentifier = langid!("jp");
|
||||||
|
|
||||||
|
fn i18n_from_static() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN).with_locale((EN, include_str!("./data/i18n/en.ftl")));
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i18n_from_static_with_fallback() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN)
|
||||||
|
.with_locale((EN, include_str!("./data/i18n/en.ftl")))
|
||||||
|
.with_fallback(JP);
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i18n_from_dynamic() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN).with_locale((
|
||||||
|
EN,
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}/tests/data/i18n/en.ftl",
|
||||||
|
env!("CARGO_MANIFEST_DIR")
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn i18n_from_dynamic_none_existing() -> I18n {
|
||||||
|
let config = I18nConfig::new(EN).with_locale((
|
||||||
|
EN,
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}/tests/data/i18n/non_existing.ftl",
|
||||||
|
env!("CARGO_MANIFEST_DIR")
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
use_init_i18n(|| config)
|
||||||
|
}
|
||||||
@@ -2,5 +2,4 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
dockerfile: docker/prod/app/Dockerfile
|
|
||||||
target: builder_android
|
target: builder_android
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ services:
|
|||||||
- ./Cargo.toml:/srv/app/Cargo.toml
|
- ./Cargo.toml:/srv/app/Cargo.toml
|
||||||
- ./diesel.toml:/srv/app/diesel.toml
|
- ./diesel.toml:/srv/app/diesel.toml
|
||||||
- ./Dioxus.toml:/srv/app/Dioxus.toml
|
- ./Dioxus.toml:/srv/app/Dioxus.toml
|
||||||
- ./tailwind.css:/srv/app/tailwind.css
|
|
||||||
restart: always
|
restart: always
|
||||||
ports: ["8000:8000"]
|
ports: ["8000:8000"]
|
||||||
depends_on: ["db"]
|
depends_on: ["db"]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
- web-server-network
|
- web-server-network
|
||||||
ports: ["8000:80"]
|
|
||||||
restart: always
|
restart: always
|
||||||
depends_on: ["db"]
|
depends_on: ["db"]
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ RUN useradd -m -u 1000 -s /bin/bash app_user \
|
|||||||
|
|
||||||
USER app_user
|
USER app_user
|
||||||
|
|
||||||
RUN cargo install --git https://github.com/diesel-rs/diesel --rev 207604888d28a490061698f07a25090438be42fe --locked diesel_cli \
|
RUN cargo install --git https://github.com/diesel-rs/diesel --rev 2e85ba060d3d70ea605ea58a79b8a435749a7adc --locked diesel_cli \
|
||||||
&& cargo install --git https://github.com/DioxusLabs/dioxus --rev 22b06badde44ba1af0fcf339c91b66483175b660 --locked dioxus-cli
|
&& cargo install --git https://github.com/DioxusLabs/dioxus --rev 22b06badde44ba1af0fcf339c91b66483175b660 --locked dioxus-cli
|
||||||
|
|
||||||
COPY --chown=app_user . /srv/app
|
COPY --chown=app_user . /srv/app
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM rust:1.92.0-bookworm@sha256:9676d0547a259997add8f5924eb6b959c589ed39055338e23b99aba7958d6d31 AS builder_base
|
FROM rust:1.92.0-bookworm@sha256:9676d0547a259997add8f5924eb6b959c589ed39055338e23b99aba7958d6d31 AS builder_base
|
||||||
|
|
||||||
RUN cargo install --git https://github.com/diesel-rs/diesel --rev 207604888d28a490061698f07a25090438be42fe --locked diesel_cli \
|
RUN cargo install --git https://github.com/diesel-rs/diesel --rev 2e85ba060d3d70ea605ea58a79b8a435749a7adc --locked diesel_cli \
|
||||||
&& cargo install --git https://github.com/DioxusLabs/dioxus --rev 22b06badde44ba1af0fcf339c91b66483175b660 --locked dioxus-cli --features disable-telemetry
|
&& cargo install --git https://github.com/DioxusLabs/dioxus --rev 22b06badde44ba1af0fcf339c91b66483175b660 --locked dioxus-cli --features disable-telemetry
|
||||||
|
|
||||||
COPY . /srv/app
|
COPY . /srv/app
|
||||||
@@ -39,7 +39,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& keytool -genkeypair -noprompt -keystore /tmp/android_keystore.jks -alias key -keyalg RSA -keysize 2048 -validity 3660 -dname "CN=" -storepass 123456 -keypass 123456 \
|
&& keytool -genkeypair -noprompt -keystore /tmp/android_keystore.jks -alias key -keyalg RSA -keysize 2048 -validity 3660 -dname "CN=" -storepass 123456 -keypass 123456 \
|
||||||
&& export ANDROID_HOME="$ANDROID_SDK_ROOT" \
|
&& export ANDROID_HOME="$ANDROID_SDK_ROOT" \
|
||||||
&& export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" \
|
&& export ANDROID_NDK_HOME="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" \
|
||||||
&& dx bundle --locked --platform android --target aarch64-linux-android --release \
|
&& dx bundle --platform android --target aarch64-linux-android --release \
|
||||||
&& java -jar /tmp/bundletool-all.jar build-apks --bundle=/srv/app/target/dx/todo_baggins/release/android/app/app/build/outputs/bundle/release/TodoBaggins-aarch64-linux-android.aab --output=/tmp/todo_baggins.apks --mode=universal --ks=/tmp/android_keystore.jks --ks-key-alias=key --ks-pass=pass:123456 \
|
&& java -jar /tmp/bundletool-all.jar build-apks --bundle=/srv/app/target/dx/todo_baggins/release/android/app/app/build/outputs/bundle/release/TodoBaggins-aarch64-linux-android.aab --output=/tmp/todo_baggins.apks --mode=universal --ks=/tmp/android_keystore.jks --ks-key-alias=key --ks-pass=pass:123456 \
|
||||||
&& mkdir -p /srv/app/bundle \
|
&& mkdir -p /srv/app/bundle \
|
||||||
&& unzip -qp /tmp/todo_baggins.apks universal.apk > /srv/app/bundle/todo_baggins.apk
|
&& unzip -qp /tmp/todo_baggins.apks universal.apk > /srv/app/bundle/todo_baggins.apk
|
||||||
@@ -47,7 +47,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
FROM builder_base AS builder_web
|
FROM builder_base AS builder_web
|
||||||
|
|
||||||
RUN dx bundle --locked --release
|
RUN dx bundle --release
|
||||||
|
|
||||||
|
|
||||||
FROM debian:bookworm@sha256:b877a1a3fdf02469440f1768cf69c9771338a875b7add5e80c45b756c92ac20a AS runner_web
|
FROM debian:bookworm@sha256:b877a1a3fdf02469440f1768cf69c9771338a875b7add5e80c45b756c92ac20a AS runner_web
|
||||||
|
|||||||
@@ -7,22 +7,15 @@ use dioxus_i18n::prelude::*;
|
|||||||
use dioxus_i18n::unic_langid::langid;
|
use dioxus_i18n::unic_langid::langid;
|
||||||
|
|
||||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||||
/* Once https://github.com/DioxusLabs/dioxus/issues/4490 is resolved, hopefully it will be
|
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
|
||||||
sufficient to just include the single icon.png. */
|
|
||||||
#[used]
|
|
||||||
static IMAGES_DIRECTORY: Asset = asset!(
|
|
||||||
"/assets/images",
|
|
||||||
AssetOptions::builder().with_hash_suffix(false)
|
|
||||||
);
|
|
||||||
#[used]
|
#[used]
|
||||||
static FONTS_DIRECTORY: Asset = asset!(
|
static FONTS_DIRECTORY: Asset = asset!(
|
||||||
"/assets/fonts",
|
"/assets/fonts",
|
||||||
AssetOptions::builder().with_hash_suffix(false)
|
AssetOptions::builder().with_hash_suffix(false)
|
||||||
);
|
);
|
||||||
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.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_NUMBER_ARROWS_CSS: Asset = asset!("/assets/styles/input_number_arrows.css");
|
||||||
const INPUT_RANGE_CSS: Asset = asset!("/assets/styles/input_range.css");
|
const INPUT_RANGE_CSS: Asset = asset!("/assets/styles/input_range.css");
|
||||||
const SELECT_ARROW_CSS: Asset = asset!("/assets/styles/select_arrow.css");
|
|
||||||
const MANIFEST: Asset = asset!("/assets/manifest.json");
|
const MANIFEST: Asset = asset!("/assets/manifest.json");
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@@ -42,13 +35,14 @@ pub(crate) fn App() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
document::Link { rel: "icon", href: FAVICON }
|
document::Link { rel: "icon", href: FAVICON }
|
||||||
document::Stylesheet { href: TAILWIND_CSS }
|
document::Stylesheet { href: TAILWIND_CSS }
|
||||||
|
document::Stylesheet { href: FONTS_CSS }
|
||||||
document::Stylesheet { href: INPUT_NUMBER_ARROWS_CSS }
|
document::Stylesheet { href: INPUT_NUMBER_ARROWS_CSS }
|
||||||
document::Stylesheet { href: INPUT_RANGE_CSS }
|
document::Stylesheet { href: INPUT_RANGE_CSS }
|
||||||
document::Stylesheet { href: SELECT_ARROW_CSS }
|
|
||||||
document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" }
|
document::Link { rel: "manifest", href: MANIFEST, crossorigin: "use-credentials" }
|
||||||
|
document::Script { src: "https://kit.fontawesome.com/3c1b409f8f.js" }
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: "min-h-screen py-4 flex flex-col text-gray-300 bg-gray-900",
|
class: "min-h-screen pt-4 pb-36 flex flex-col text-zinc-200 bg-zinc-800",
|
||||||
Router::<Route> {}
|
Router::<Route> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,78 @@
|
|||||||
|
use crate::components::error_boundary_message::ErrorBoundaryMessage;
|
||||||
use crate::components::navigation::Navigation;
|
use crate::components::navigation::Navigation;
|
||||||
|
use crate::components::project_form::ProjectForm;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
use crate::models::project::Project;
|
||||||
|
use crate::models::task::Task;
|
||||||
|
use crate::route::Route;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn BottomPanel() -> Element {
|
pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element {
|
||||||
|
// A signal for delaying the application of styles.
|
||||||
|
#[allow(clippy::redundant_closure)]
|
||||||
|
let mut expanded = use_signal(|| display_form());
|
||||||
let navigation_expanded = use_signal(|| false);
|
let navigation_expanded = use_signal(|| false);
|
||||||
|
let current_route = use_route();
|
||||||
|
|
||||||
|
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
|
||||||
|
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
|
||||||
|
|
||||||
|
use_effect(use_reactive(&display_form, move |display_form| {
|
||||||
|
if display_form() {
|
||||||
|
expanded.set(true);
|
||||||
|
} else {
|
||||||
|
spawn(async move {
|
||||||
|
// Necessary for a smooth – not instant – height transition.
|
||||||
|
#[cfg(not(feature = "server"))]
|
||||||
|
async_std::task::sleep(std::time::Duration::from_millis(500)).await;
|
||||||
|
/* The check is necessary for the situation when the user expands the panel while
|
||||||
|
it is being closed. */
|
||||||
|
if !display_form() {
|
||||||
|
expanded.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: format!(
|
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 {}",
|
"flex flex-col pointer-events-auto bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}",
|
||||||
if navigation_expanded() {
|
match (display_form(), current_route, navigation_expanded()) {
|
||||||
"h-[130px]"
|
(false, _, false) => "h-[66px]",
|
||||||
} else {
|
(false, _, true) => "h-[130px]",
|
||||||
"h-[66px]"
|
(true, Route::ProjectsPage, _) => "h-[130px]",
|
||||||
|
(true, _, _) => "h-[506px]",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Navigation {
|
if expanded() {
|
||||||
is_expanded: navigation_expanded,
|
ErrorBoundaryMessage {
|
||||||
|
match current_route {
|
||||||
|
Route::ProjectsPage => rsx! {
|
||||||
|
ProjectForm {
|
||||||
|
project: project_being_edited(),
|
||||||
|
on_successful_submit: move |_| {
|
||||||
|
display_form.set(false);
|
||||||
|
project_being_edited.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => rsx! {
|
||||||
|
TaskForm {
|
||||||
|
task: task_being_edited(),
|
||||||
|
on_successful_submit: move |_| {
|
||||||
|
display_form.set(false);
|
||||||
|
task_being_edited.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Navigation {
|
||||||
|
expanded: navigation_expanded,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn ButtonPrimary(
|
|
||||||
class: Option<String>,
|
|
||||||
children: Element,
|
|
||||||
#[props(extends = GlobalAttributes, extends = button)] attributes: Vec<Attribute>,
|
|
||||||
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
|
|
||||||
onclick: Option<Callback<Event<MouseData>>>,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
button {
|
|
||||||
class: format!(
|
|
||||||
"cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] active:*:drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] transition-all duration-150 {}",
|
|
||||||
class.unwrap_or("".to_owned())
|
|
||||||
),
|
|
||||||
onclick: move |event| {
|
|
||||||
if let Some(onclick) = onclick {
|
|
||||||
onclick.call(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
..attributes,
|
|
||||||
div {
|
|
||||||
class: "py-3.5 px-4 flex flex-row justify-center items-center bg-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)] text-amber-700-muted rounded-xl transition-all duration-150",
|
|
||||||
{children}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn ButtonSecondary(
|
|
||||||
class: Option<String>,
|
|
||||||
children: Element,
|
|
||||||
#[props(extends = GlobalAttributes, extends = button)] attributes: Vec<Attribute>,
|
|
||||||
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
|
|
||||||
onclick: Option<Callback<Event<MouseData>>>,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
button {
|
|
||||||
class: format!(
|
|
||||||
"cursor-pointer pb-[6px] hover:pb-[7px] active:pb-[2px] mt-[1px] hover:mt-0 active:mt-[5px] hover:*:drop-shadow-[0_7px_0_var(--color-gray-800)] active:*:drop-shadow-[0_2px_0_var(--color-gray-800)] transition-all duration-150 {}",
|
|
||||||
class.unwrap_or("".to_owned())
|
|
||||||
),
|
|
||||||
onclick: move |event| {
|
|
||||||
if let Some(onclick) = onclick {
|
|
||||||
onclick.call(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
..attributes,
|
|
||||||
div {
|
|
||||||
class: "py-3.5 px-4 flex flex-row justify-center items-center bg-gray-600 drop-shadow-[0_6px_0_var(--color-gray-800)] rounded-xl transition-all duration-150",
|
|
||||||
{children}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -28,12 +28,11 @@ pub(crate) fn CategoryCalendarTaskList() -> Element {
|
|||||||
div {
|
div {
|
||||||
class: "flex flex-col gap-4",
|
class: "flex flex-col gap-4",
|
||||||
div {
|
div {
|
||||||
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
|
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||||
div {
|
div {
|
||||||
class: "pt-1",
|
class: "pt-1",
|
||||||
{
|
{
|
||||||
date_current.format_localized(
|
date_current.format_localized(t!(
|
||||||
t!(
|
|
||||||
if date_current.year() == Local::now().year() {
|
if date_current.year() == Local::now().year() {
|
||||||
"date-weekday-format"
|
"date-weekday-format"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
use crate::components::select_button::SelectButton;
|
|
||||||
use crate::models::category::Category;
|
use crate::models::category::Category;
|
||||||
use dioxus::core_macro::rsx;
|
use dioxus::core_macro::rsx;
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{
|
|
||||||
FaCalendarDays, FaHourglassHalf, FaInbox, FaSignsPost, FaWater,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn CategoryInput(
|
pub(crate) fn CategoryInput(
|
||||||
@@ -15,51 +10,93 @@ pub(crate) fn CategoryInput(
|
|||||||
) -> Element {
|
) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: format!("grid grid-cols-3 gap-3 {}", class.unwrap_or("")),
|
class: format!("flex flex-row gap-2 {}", class.unwrap_or("")),
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaLightbulb,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::SomedayMaybe),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if selected_category() == Category::SomedayMaybe { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::SomedayMaybe);
|
selected_category.set(Category::SomedayMaybe);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-question"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaWater,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::LongTerm),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if selected_category() == Category::LongTerm { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::LongTerm);
|
selected_category.set(Category::LongTerm);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-water"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaHourglassHalf,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::WaitingFor(_)),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if let Category::WaitingFor(_) = selected_category() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::WaitingFor(String::new()));
|
selected_category.set(Category::WaitingFor(String::new()));
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-hourglass-half"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaSignsPost,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::NextSteps),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if selected_category() == Category::NextSteps { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::NextSteps);
|
selected_category.set(Category::NextSteps);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-forward"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaCalendarDays,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::Calendar { .. }),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if let Category::Calendar { .. } = selected_category() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::Calendar {
|
selected_category.set(Category::Calendar {
|
||||||
date: chrono::Local::now().date_naive(),
|
date: chrono::Local::now().date_naive(),
|
||||||
reoccurrence: None,
|
reoccurrence: None,
|
||||||
time: None,
|
time: None,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-calendar-days"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
SelectButton {
|
button {
|
||||||
icon: FaInbox,
|
r#type: "button",
|
||||||
is_selected: matches!(selected_category(), Category::Inbox),
|
class: format!(
|
||||||
on_select: move |_| {
|
"py-2 rounded-lg grow basis-0 {} cursor-pointer",
|
||||||
|
if selected_category() == Category::Inbox { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
selected_category.set(Category::Inbox);
|
selected_category.set(Category::Inbox);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-inbox"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use crate::components::task_list::TaskList;
|
use crate::components::task_list::TaskList;
|
||||||
|
use crate::components::task_list_item::TaskListItem;
|
||||||
use crate::hooks::use_tasks_with_subtasks_in_category;
|
use crate::hooks::use_tasks_with_subtasks_in_category;
|
||||||
use crate::internationalization::LocaleFromLanguageIdentifier;
|
use crate::internationalization::LocaleFromLanguageIdentifier;
|
||||||
use crate::models::category::Category;
|
use crate::models::category::Category;
|
||||||
use crate::models::task::TaskWithSubtasks;
|
use crate::models::task::TaskWithSubtasks;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{FaCalendarCheck, FaCalendarXmark, FaWater};
|
|
||||||
use dioxus_i18n::t;
|
use dioxus_i18n::t;
|
||||||
use dioxus_i18n::use_i18n::i18n;
|
use dioxus_i18n::use_i18n::i18n;
|
||||||
use voca_rs::Voca;
|
use voca_rs::Voca;
|
||||||
@@ -46,21 +45,34 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "pt-4 flex flex-col gap-8",
|
class: "pt-4 flex flex-col gap-8",
|
||||||
if !long_term_tasks.is_empty() {
|
div {
|
||||||
|
class: "flex flex-col gap-4",
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col gap-4",
|
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||||
div {
|
i {
|
||||||
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
|
class: "fa-solid fa-water text-xl w-6 text-center"
|
||||||
Icon {
|
|
||||||
class: "mx-1.5",
|
|
||||||
icon: FaWater
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
{t!("long-term")._upper_first()}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TaskList {
|
div {
|
||||||
tasks: long_term_tasks
|
class: "mt-1",
|
||||||
|
{t!("long-term")._upper_first()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
for task in long_term_tasks {
|
||||||
|
div {
|
||||||
|
key: "{task.task.id}",
|
||||||
|
class: format!(
|
||||||
|
"px-8 pt-5 {} flex flex-row gap-4",
|
||||||
|
if task.task.deadline.is_some() {
|
||||||
|
"pb-0.5"
|
||||||
|
} else {
|
||||||
|
"pb-5"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TaskListItem {
|
||||||
|
task: task.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,14 +80,12 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
|
|||||||
div {
|
div {
|
||||||
class: "flex flex-col gap-4",
|
class: "flex flex-col gap-4",
|
||||||
div {
|
div {
|
||||||
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
|
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||||
Icon {
|
i {
|
||||||
class: "mx-1.25",
|
class: "fa-solid fa-calendar-xmark text-xl w-6 text-center"
|
||||||
height: 22,
|
|
||||||
width: 22,
|
|
||||||
icon: FaCalendarXmark
|
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
|
class: "mt-1",
|
||||||
{t!("overdue")._upper_first()}
|
{t!("overdue")._upper_first()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,14 +98,12 @@ pub(crate) fn CategoryTodayTaskList() -> Element {
|
|||||||
div {
|
div {
|
||||||
class: "flex flex-col gap-4",
|
class: "flex flex-col gap-4",
|
||||||
div {
|
div {
|
||||||
class: "px-7 flex flex-row items-center gap-2 text-gray-500 font-bold",
|
class: "px-8 flex flex-row items-center gap-2 font-bold",
|
||||||
Icon {
|
i {
|
||||||
class: "mx-1.25",
|
class: "fa-solid fa-calendar-check text-xl w-6 text-center"
|
||||||
height: 22,
|
|
||||||
width: 22,
|
|
||||||
icon: FaCalendarCheck
|
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
|
class: "mt-1",
|
||||||
{
|
{
|
||||||
let format = t!("date-weekday-format");
|
let format = t!("date-weekday-format");
|
||||||
let today_date = today_date.format_localized(
|
let today_date = today_date.format_localized(
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
use crate::components::project_form::PROJECT_BEING_EDITED;
|
|
||||||
use crate::components::{button_primary::ButtonPrimary, task_form::TASK_BEING_EDITED};
|
|
||||||
use crate::route::Route;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_free_icons::{Icon, icons::fa_solid_icons::FaGavel};
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn CreateButton() -> Element {
|
|
||||||
let navigator = use_navigator();
|
|
||||||
let current_route = use_route();
|
|
||||||
rsx! {
|
|
||||||
ButtonPrimary {
|
|
||||||
class: "pointer-events-auto m-4 self-end *:rounded-full! *:p-4",
|
|
||||||
onclick: move |_| {
|
|
||||||
*TASK_BEING_EDITED.write() = None;
|
|
||||||
*PROJECT_BEING_EDITED.write() = None;
|
|
||||||
navigator.push(
|
|
||||||
match current_route {
|
|
||||||
Route::ProjectsPage => Route::ProjectFormPage,
|
|
||||||
_ => Route::TaskFormPage,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
Icon {
|
|
||||||
icon: FaGavel,
|
|
||||||
height: 24,
|
|
||||||
width: 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
use dioxus::core_macro::rsx;
|
use dioxus::core_macro::rsx;
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::FaTriangleExclamation;
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn ErrorBoundaryMessage(children: Element, class: Option<String>) -> Element {
|
pub(crate) fn ErrorBoundaryMessage(children: Element, class: Option<String>) -> Element {
|
||||||
@@ -13,11 +11,8 @@ pub(crate) fn ErrorBoundaryMessage(children: Element, class: Option<String>) ->
|
|||||||
div {
|
div {
|
||||||
class: "grow flex flex-col justify-center items-center",
|
class: "grow flex flex-col justify-center items-center",
|
||||||
div {
|
div {
|
||||||
Icon {
|
i {
|
||||||
class: "text-gray-500",
|
class: "text-3xl fa-solid fa-triangle-exclamation"
|
||||||
icon: FaTriangleExclamation,
|
|
||||||
height: 32,
|
|
||||||
width: 32
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/components/form_open_button.rs
Normal file
25
src/components/form_open_button.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::models::project::Project;
|
||||||
|
use crate::models::task::Task;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn FormOpenButton(opened: Signal<bool>) -> Element {
|
||||||
|
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
|
||||||
|
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
class: "pointer-events-auto m-4 py-3 px-5 self-end text-center bg-zinc-300/50 rounded-xl border-t-zinc-200 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] text-2xl text-zinc-200 cursor-pointer",
|
||||||
|
onclick: move |_| {
|
||||||
|
if opened() {
|
||||||
|
project_being_edited.set(None);
|
||||||
|
task_being_edited.set(None);
|
||||||
|
}
|
||||||
|
opened.set(!opened());
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: format!("min-w-6 fa-solid {}", if opened() { "fa-xmark" } else { "fa-plus" }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn Input(
|
|
||||||
class: Option<String>,
|
|
||||||
name: String,
|
|
||||||
r#type: String,
|
|
||||||
id: Option<String>,
|
|
||||||
#[props(extends = GlobalAttributes, extends = input)] attributes: Vec<Attribute>,
|
|
||||||
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/5271 gets resolved.
|
|
||||||
autofocus: Option<bool>,
|
|
||||||
// TODO: Remove this once https://github.com/DioxusLabs/dioxus/issues/4019 gets resolved.
|
|
||||||
oninput: Option<Callback<Event<FormData>>>,
|
|
||||||
onchange: Option<Callback<Event<FormData>>>,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
input {
|
|
||||||
class: format!(
|
|
||||||
/* `w-full` is required for the Chromium renderer to allow the input to shrink
|
|
||||||
properly. */
|
|
||||||
"pt-3 pb-2.25 w-full {} bg-gray-800-muted enabled:hover:bg-gray-800 enabled:focus:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl outline-0 {} transition-all duration-150 {}",
|
|
||||||
match r#type.as_str() {
|
|
||||||
"date" => "ps-3.25 pe-3",
|
|
||||||
_ => "px-4"
|
|
||||||
},
|
|
||||||
match r#type.as_str() {
|
|
||||||
"text" | "number" => "",
|
|
||||||
_ => "enabled:cursor-pointer"
|
|
||||||
},
|
|
||||||
class.unwrap_or("".to_owned())
|
|
||||||
),
|
|
||||||
name: name.clone(),
|
|
||||||
r#type,
|
|
||||||
id: id.unwrap_or(format!("input_{}", name)),
|
|
||||||
oninput: move |event| {
|
|
||||||
if let Some(oninput) = oninput {
|
|
||||||
oninput.call(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onchange: move |event| {
|
|
||||||
if let Some(onchange) = oninput {
|
|
||||||
onchange.call(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmounted: move |element| async move {
|
|
||||||
if let Some(true) = autofocus {
|
|
||||||
let _ = element.set_focus(true).await;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
..attributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_free_icons::{Icon, IconShape};
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn InputLabel<I: IconShape + Clone + PartialEq + 'static>(
|
|
||||||
icon: I,
|
|
||||||
r#for: Option<String>,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
label {
|
|
||||||
r#for,
|
|
||||||
class: "mt-0.5 min-w-7 flex flex-row justify-center items-center",
|
|
||||||
Icon {
|
|
||||||
class: "text-gray-600",
|
|
||||||
icon,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
pub(crate) mod app;
|
pub(crate) mod app;
|
||||||
pub(crate) mod bottom_panel;
|
pub(crate) mod bottom_panel;
|
||||||
pub(crate) mod button_primary;
|
|
||||||
pub(crate) mod button_secondary;
|
|
||||||
pub(crate) mod category_calendar_task_list;
|
pub(crate) mod category_calendar_task_list;
|
||||||
pub(crate) mod category_input;
|
pub(crate) mod category_input;
|
||||||
pub(crate) mod category_today_task_list;
|
pub(crate) mod category_today_task_list;
|
||||||
pub(crate) mod create_button;
|
|
||||||
pub(crate) mod error_boundary_message;
|
pub(crate) mod error_boundary_message;
|
||||||
pub(crate) mod input;
|
pub(crate) mod form_open_button;
|
||||||
pub(crate) mod input_label;
|
|
||||||
pub(crate) mod navigation;
|
pub(crate) mod navigation;
|
||||||
pub(crate) mod navigation_item;
|
pub(crate) mod navigation_item;
|
||||||
pub(crate) mod project_form;
|
pub(crate) mod project_form;
|
||||||
pub(crate) mod project_list;
|
pub(crate) mod project_list;
|
||||||
pub(crate) mod project_select;
|
pub(crate) mod project_select;
|
||||||
pub(crate) mod reoccurrence_interval_input;
|
pub(crate) mod reoccurrence_input;
|
||||||
pub(crate) mod select_button;
|
|
||||||
pub(crate) mod sticky_bottom;
|
pub(crate) mod sticky_bottom;
|
||||||
pub(crate) mod subtasks_form;
|
pub(crate) mod subtasks_form;
|
||||||
pub(crate) mod task_form;
|
pub(crate) mod task_form;
|
||||||
|
|||||||
@@ -1,72 +1,78 @@
|
|||||||
use crate::components::navigation_item::NavigationItem;
|
use crate::components::navigation_item::NavigationItem;
|
||||||
use crate::route::Route;
|
use crate::route::Route;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_regular_icons::FaLightbulb;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{
|
|
||||||
FaBars, FaCalendarDay, FaCalendarDays, FaHourglassHalf, FaInbox, FaList, FaSignsPost,
|
|
||||||
FaTrashCan, FaVolcano,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn Navigation(is_expanded: Signal<bool>) -> Element {
|
pub(crate) fn Navigation(expanded: Signal<bool>) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "grid grid-cols-5 justify-stretch",
|
class: "grid grid-cols-5 justify-stretch",
|
||||||
button {
|
button {
|
||||||
class: format!(
|
class: format!(
|
||||||
"py-2 flex flex-row justify-center items-center cursor-pointer",
|
"py-4 text-center text-2xl {} cursor-pointer",
|
||||||
|
if expanded() { "text-zinc-200" }
|
||||||
|
else { "text-zinc-500" }
|
||||||
),
|
),
|
||||||
onclick: move |_| is_expanded.set(!is_expanded()),
|
onclick: move |_| expanded.set(!expanded()),
|
||||||
div {
|
i {
|
||||||
class: format!("pt-2.5 px-4 {} transition-all duration-150",
|
class: "fa-solid fa-bars"
|
||||||
if is_expanded() { "pb-2 mt-1 bg-gray-900 text-gray-400 rounded-xl drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-950)]" }
|
|
||||||
else { "pb-3 bg-gray-800 rounded-xl drop-shadow-[0_0_0_var(--color-gray-950)] text-gray-600" }
|
|
||||||
),
|
|
||||||
Icon {
|
|
||||||
icon: FaBars,
|
|
||||||
height: 24,
|
|
||||||
width: 24
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryNextStepsPage,
|
route: Route::CategoryNextStepsPage,
|
||||||
icon: FaSignsPost
|
i {
|
||||||
|
class: "fa-solid fa-forward"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryCalendarPage,
|
route: Route::CategoryCalendarPage,
|
||||||
icon: FaCalendarDays
|
i {
|
||||||
|
class: "fa-solid fa-calendar-days"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryTodayPage,
|
route: Route::CategoryTodayPage,
|
||||||
icon: FaCalendarDay
|
i {
|
||||||
|
class: "fa-solid fa-calendar-day"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryInboxPage,
|
route: Route::CategoryInboxPage,
|
||||||
icon: FaInbox
|
i {
|
||||||
|
class: "fa-solid fa-inbox"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{if is_expanded() {
|
{if expanded() {
|
||||||
rsx! {
|
rsx! {
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::ProjectsPage,
|
route: Route::ProjectsPage,
|
||||||
icon: FaList
|
i {
|
||||||
|
class: "fa-solid fa-list"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryTrashPage,
|
route: Route::CategoryTrashPage,
|
||||||
icon: FaTrashCan
|
i {
|
||||||
|
class: "fa-solid fa-trash-can"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryDonePage,
|
route: Route::CategoryDonePage,
|
||||||
icon: FaVolcano
|
i {
|
||||||
|
class: "fa-solid fa-check"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategorySomedayMaybePage,
|
route: Route::CategoryLongTermPage,
|
||||||
icon: FaLightbulb
|
i {
|
||||||
|
class: "fa-solid fa-water"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
NavigationItem {
|
NavigationItem {
|
||||||
route: Route::CategoryWaitingForPage,
|
route: Route::CategoryWaitingForPage,
|
||||||
icon: FaHourglassHalf
|
i {
|
||||||
|
class: "fa-solid fa-hourglass-half"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { VNode::empty() }}
|
} else { VNode::empty() }}
|
||||||
|
|||||||
@@ -1,31 +1,19 @@
|
|||||||
use crate::route::Route;
|
use crate::route::Route;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::{Icon, IconShape};
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn NavigationItem<I: IconShape + Clone + PartialEq + 'static>(
|
pub(crate) fn NavigationItem(route: Route, children: Element) -> Element {
|
||||||
route: Route,
|
|
||||||
icon: I,
|
|
||||||
) -> Element {
|
|
||||||
let current_route = use_route::<Route>();
|
let current_route = use_route::<Route>();
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
Link {
|
Link {
|
||||||
to: route.clone(),
|
to: route.clone(),
|
||||||
class: format!(
|
class: format!(
|
||||||
"py-2.5 flex flex-row justify-center items-center hover:*:bg-gray-900 active:*:text-gray-400",
|
"py-4 text-center text-2xl {}",
|
||||||
|
if current_route == route { "text-zinc-200" }
|
||||||
|
else { "text-zinc-500" }
|
||||||
),
|
),
|
||||||
div {
|
children
|
||||||
class: format!("pt-2.5 px-4 {} transition-all duration-150",
|
|
||||||
if current_route == route { "pb-2 mt-1 bg-gray-900 text-gray-400 rounded-xl drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-950)]" }
|
|
||||||
else { "pb-3 bg-gray-800 rounded-xl drop-shadow-[0_0_0_var(--color-gray-950)] text-gray-600" }
|
|
||||||
),
|
|
||||||
Icon {
|
|
||||||
icon,
|
|
||||||
height: 24,
|
|
||||||
width: 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,73 @@
|
|||||||
use crate::components::button_primary::ButtonPrimary;
|
|
||||||
use crate::components::button_secondary::ButtonSecondary;
|
|
||||||
use crate::components::input::Input;
|
|
||||||
use crate::components::input_label::InputLabel;
|
|
||||||
use crate::models::project::Project;
|
use crate::models::project::Project;
|
||||||
use crate::server::projects::{create_project, delete_project, edit_project};
|
use crate::server::projects::{create_project, delete_project, edit_project};
|
||||||
use dioxus::core_macro::{component, rsx};
|
use dioxus::core_macro::{component, rsx};
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{FaFeatherPointed, FaStamp, FaTrashCan, FaXmark};
|
|
||||||
|
|
||||||
pub(crate) static PROJECT_BEING_EDITED: GlobalSignal<Option<Project>> = Signal::global(|| None);
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn ProjectForm() -> Element {
|
pub(crate) fn ProjectForm(
|
||||||
let navigator = use_navigator();
|
project: Option<Project>,
|
||||||
let project = PROJECT_BEING_EDITED();
|
on_successful_submit: EventHandler<()>,
|
||||||
|
) -> Element {
|
||||||
let project_for_submit = project.clone();
|
let project_for_submit = project.clone();
|
||||||
rsx! {
|
rsx! {
|
||||||
form {
|
form {
|
||||||
class: "px-4 flex flex-col gap-4",
|
|
||||||
onsubmit: move |event| {
|
onsubmit: move |event| {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
let project = project_for_submit.clone();
|
let project = project_for_submit.clone();
|
||||||
async move {
|
async move {
|
||||||
let new_project = event.parsed_values().unwrap();
|
let new_project = event.parsed_values().unwrap();
|
||||||
let result = if let Some(project) = project {
|
if let Some(project) = project {
|
||||||
edit_project(project.id, new_project).await
|
let _ = edit_project(project.id, new_project).await;
|
||||||
} else {
|
} else {
|
||||||
create_project(new_project).await
|
let _ = create_project(new_project).await;
|
||||||
};
|
|
||||||
if result.is_ok() {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
}
|
||||||
|
on_successful_submit.call(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
id: "form_project",
|
class: "p-4 flex flex-col gap-4",
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaFeatherPointed,
|
r#for: "input_title",
|
||||||
r#for: "input_title"
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-pen-clip text-zinc-400/50"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Input {
|
input {
|
||||||
class: "grow",
|
|
||||||
name: "title",
|
name: "title",
|
||||||
required: true,
|
required: true,
|
||||||
r#type: "text",
|
|
||||||
initial_value: project.as_ref().map(|project| project.title.to_owned()),
|
initial_value: project.as_ref().map(|project| project.title.to_owned()),
|
||||||
|
r#type: "text",
|
||||||
|
class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg",
|
||||||
|
id: "input_title"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
div {
|
||||||
div {
|
class: "flex flex-row justify-between mt-auto",
|
||||||
class: "px-4 grid grid-cols-3 gap-3 mt-auto",
|
button {
|
||||||
ButtonSecondary {
|
r#type: "button",
|
||||||
r#type: "button",
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg cursor-pointer",
|
||||||
class: "grow",
|
onclick: move |_| {
|
||||||
onclick: {
|
|
||||||
let project = project.clone();
|
|
||||||
move |_| {
|
|
||||||
let project = project.clone();
|
let project = project.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(project) = project {
|
if let Some(project) = project {
|
||||||
let result = delete_project(project.id).await;
|
let _ = delete_project(project.id).await;
|
||||||
if result.is_ok() {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Icon {
|
|
||||||
icon: FaTrashCan,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if project.is_some() {
|
|
||||||
div {
|
|
||||||
class: "grow flex flex-col items-stretch",
|
|
||||||
GoBackButton {
|
|
||||||
ButtonSecondary {
|
|
||||||
/* TODO: Replace w-full` with proper flexbox styling once
|
|
||||||
https://github.com/DioxusLabs/dioxus/issues/5269 is solved. */
|
|
||||||
class: "w-full",
|
|
||||||
r#type: "button",
|
|
||||||
Icon {
|
|
||||||
icon: FaXmark,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
}
|
||||||
|
on_successful_submit.call(());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-trash-can"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
button {
|
||||||
div {}
|
r#type: "submit",
|
||||||
}
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg cursor-pointer",
|
||||||
ButtonPrimary {
|
i {
|
||||||
form: "form_project",
|
class: "fa-solid fa-floppy-disk"
|
||||||
r#type: "submit",
|
}
|
||||||
Icon {
|
|
||||||
icon: FaStamp,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
use crate::route::Route;
|
use crate::{hooks::use_projects, models::project::Project};
|
||||||
use crate::{components::project_form::PROJECT_BEING_EDITED, hooks::use_projects};
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn ProjectList() -> Element {
|
pub(crate) fn ProjectList() -> Element {
|
||||||
let navigator = use_navigator();
|
|
||||||
let projects = use_projects()?;
|
let projects = use_projects()?;
|
||||||
|
let mut project_being_edited = use_context::<Signal<Option<Project>>>();
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col",
|
class: "flex flex-col",
|
||||||
for project in projects {
|
for project in projects {
|
||||||
div {
|
div {
|
||||||
class: "px-7 py-4 hover:bg-gray-800 font-medium text-pretty wrap-anywhere select-none transition-all duration-150 cursor-pointer",
|
|
||||||
key: "{project.id}",
|
key: "{project.id}",
|
||||||
onclick: move |_| {
|
class: format!(
|
||||||
*PROJECT_BEING_EDITED.write() = Some(project.clone());
|
"px-8 py-4 select-none {}",
|
||||||
navigator.push(Route::ProjectFormPage);
|
if project_being_edited().is_some_and(|p| p.id == project.id) {
|
||||||
},
|
"bg-zinc-700"
|
||||||
|
} else { "" }
|
||||||
|
),
|
||||||
|
onclick: move |_| project_being_edited.set(Some(project.clone())),
|
||||||
{project.title.clone()}
|
{project.title.clone()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ pub(crate) fn ProjectSelect(initial_selected_id: Option<i32>) -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
select {
|
select {
|
||||||
name: "project_id",
|
name: "project_id",
|
||||||
class: "px-4 pt-3 pb-2.25 bg-gray-800-muted enabled:hover:bg-gray-800 enabled:active:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer transition-all duration-150",
|
class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow cursor-pointer",
|
||||||
id: "input_project_id",
|
id: "input_project",
|
||||||
option {
|
option {
|
||||||
value: 0,
|
value: 0,
|
||||||
{t!("none")}
|
{t!("none")}
|
||||||
|
|||||||
76
src/components/reoccurrence_input.rs
Normal file
76
src/components/reoccurrence_input.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::models::category::ReoccurrenceInterval;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn ReoccurrenceIntervalInput(
|
||||||
|
reoccurrence_interval: Signal<Option<ReoccurrenceInterval>>,
|
||||||
|
class_buttons: Option<&'static str>,
|
||||||
|
) -> Element {
|
||||||
|
rsx! {
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {} cursor-pointer",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if reoccurrence_interval().is_none() { "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(None);
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-ban"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {} cursor-pointer",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Day) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Day))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-sun"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {} cursor-pointer",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Month) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Month))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-moon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
r#type: "button",
|
||||||
|
class: format!(
|
||||||
|
"py-2 rounded-lg {} {} cursor-pointer",
|
||||||
|
class_buttons.unwrap_or(""),
|
||||||
|
if let Some(ReoccurrenceInterval::Year) = reoccurrence_interval()
|
||||||
|
{ "bg-zinc-500/50" }
|
||||||
|
else { "bg-zinc-800/50" }
|
||||||
|
),
|
||||||
|
onclick: move |_| {
|
||||||
|
reoccurrence_interval.set(Some(ReoccurrenceInterval::Year))
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-earth-europe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use crate::components::select_button::SelectButton;
|
|
||||||
use crate::models::category::ReoccurrenceInterval;
|
|
||||||
use dioxus::core_macro::rsx;
|
|
||||||
use dioxus::dioxus_core::Element;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{FaBan, FaEarthEurope, FaMoon, FaSun};
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn ReoccurrenceIntervalInput(
|
|
||||||
reoccurrence_interval: Signal<Option<ReoccurrenceInterval>>,
|
|
||||||
class_buttons: Option<&'static str>,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
SelectButton {
|
|
||||||
icon: FaBan,
|
|
||||||
is_selected: reoccurrence_interval().is_none(),
|
|
||||||
on_select: move |_| {
|
|
||||||
reoccurrence_interval.set(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectButton {
|
|
||||||
icon: FaSun,
|
|
||||||
is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Day)),
|
|
||||||
on_select: move |_| {
|
|
||||||
reoccurrence_interval.set(Some(ReoccurrenceInterval::Day))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectButton {
|
|
||||||
icon: FaMoon,
|
|
||||||
is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Month)),
|
|
||||||
on_select: move |_| {
|
|
||||||
reoccurrence_interval.set(Some(ReoccurrenceInterval::Month));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectButton {
|
|
||||||
icon: FaEarthEurope,
|
|
||||||
is_selected: matches!(reoccurrence_interval(), Some(ReoccurrenceInterval::Year)),
|
|
||||||
on_select: move |_| {
|
|
||||||
reoccurrence_interval.set(Some(ReoccurrenceInterval::Year));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_free_icons::{Icon, IconShape};
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn SelectButton<I: IconShape + Clone + PartialEq + 'static>(
|
|
||||||
icon: I,
|
|
||||||
is_selected: bool,
|
|
||||||
on_select: Callback,
|
|
||||||
) -> Element {
|
|
||||||
rsx! {
|
|
||||||
button {
|
|
||||||
r#type: "button",
|
|
||||||
class: format!(
|
|
||||||
"pt-4.5 flex flex-row justify-center items-center {} rounded-xl transition-all duration-150",
|
|
||||||
if is_selected { "pb-3.75 bg-gray-900 drop-shadow-[0_0_0_var(--color-gray-900-muted)]" }
|
|
||||||
else { "pb-2.75 mt-1 bg-gray-800-muted hover:bg-gray-800 drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] text-gray-400 cursor-pointer" }
|
|
||||||
),
|
|
||||||
onclick: move |_| {
|
|
||||||
on_select.call(());
|
|
||||||
},
|
|
||||||
Icon {
|
|
||||||
icon,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
use crate::components::button_secondary::ButtonSecondary;
|
|
||||||
use crate::components::input::Input;
|
|
||||||
use crate::components::input_label::InputLabel;
|
|
||||||
use crate::hooks::use_subtasks_of_task;
|
use crate::hooks::use_subtasks_of_task;
|
||||||
use crate::models::subtask::NewSubtask;
|
use crate::models::subtask::NewSubtask;
|
||||||
use crate::models::task::Task;
|
use crate::models::task::Task;
|
||||||
@@ -8,132 +5,131 @@ use crate::server::subtasks::{create_subtask, delete_subtask, edit_subtask};
|
|||||||
use dioxus::core_macro::{component, rsx};
|
use dioxus::core_macro::{component, rsx};
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{FaGavel, FaListCheck, FaTrashCan};
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn SubtasksForm(task: Task) -> Element {
|
pub(crate) fn SubtasksForm(task: Task) -> Element {
|
||||||
let subtasks = use_subtasks_of_task(task.id)?;
|
let subtasks = use_subtasks_of_task(task.id)?;
|
||||||
let mut new_title = use_signal(String::new);
|
let mut new_title = use_signal(String::new);
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
form {
|
||||||
class: "flex flex-col gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
form {
|
onsubmit: move |event| {
|
||||||
class: "flex flex-row items-center gap-3",
|
event.prevent_default();
|
||||||
onsubmit: move |event| {
|
let task = task.clone();
|
||||||
event.prevent_default();
|
async move {
|
||||||
let task = task.clone();
|
let new_subtask = NewSubtask {
|
||||||
async move {
|
task_id: task.id,
|
||||||
let new_subtask = NewSubtask {
|
title: event.get("title").first().cloned().and_then(|value| match value {
|
||||||
task_id: task.id,
|
FormValue::Text(value) => Some(value),
|
||||||
title: event.get("new_title").first().cloned().and_then(|value| match value {
|
FormValue::File(_) => None
|
||||||
FormValue::Text(value) => Some(value),
|
}).unwrap(),
|
||||||
FormValue::File(_) => None
|
is_completed: false
|
||||||
}).unwrap(),
|
};
|
||||||
is_completed: false
|
let _ = create_subtask(new_subtask).await;
|
||||||
};
|
new_title.set(String::new());
|
||||||
let _ = create_subtask(new_subtask).await;
|
|
||||||
new_title.set(String::new());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
InputLabel {
|
|
||||||
icon: FaListCheck,
|
|
||||||
r#for: "input_new_title"
|
|
||||||
}
|
}
|
||||||
div {
|
},
|
||||||
class: "grow flex flex-row items-end gap-3",
|
label {
|
||||||
Input {
|
r#for: "input_new_title",
|
||||||
class: "grow",
|
class: "min-w-6 text-center",
|
||||||
name: "new_title",
|
i {
|
||||||
r#type: "text",
|
class: "fa-solid fa-list-check text-zinc-400/50"
|
||||||
required: true,
|
}
|
||||||
value: new_title,
|
}
|
||||||
onchange: move |event: Event<FormData>| new_title.set(event.value())
|
div {
|
||||||
}
|
class: "grow grid grid-cols-6 gap-2",
|
||||||
ButtonSecondary {
|
input {
|
||||||
r#type: "submit",
|
name: "title",
|
||||||
Icon {
|
required: true,
|
||||||
icon: FaGavel,
|
value: new_title,
|
||||||
height: 16,
|
r#type: "text",
|
||||||
width: 16
|
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
|
||||||
}
|
id: "input_new_title",
|
||||||
|
onchange: move |event| new_title.set(event.value())
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
r#type: "submit",
|
||||||
|
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-plus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for subtask in subtasks {
|
}
|
||||||
|
for subtask in subtasks {
|
||||||
|
div {
|
||||||
|
key: "{subtask.id}",
|
||||||
|
class: "flex flex-row items-center gap-3",
|
||||||
|
i {
|
||||||
|
class: format!(
|
||||||
|
"{} min-w-6 text-center text-2xl text-zinc-400/50",
|
||||||
|
if subtask.is_completed {
|
||||||
|
"fa solid fa-square-check"
|
||||||
|
} else {
|
||||||
|
"fa-regular fa-square"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
onclick: {
|
||||||
|
let subtask = subtask.clone();
|
||||||
|
move |_| {
|
||||||
|
let subtask = subtask.clone();
|
||||||
|
async move {
|
||||||
|
let new_subtask = NewSubtask {
|
||||||
|
task_id: subtask.task_id,
|
||||||
|
title: subtask.title.clone(),
|
||||||
|
is_completed: !subtask.is_completed
|
||||||
|
};
|
||||||
|
let _ = edit_subtask(
|
||||||
|
subtask.id,
|
||||||
|
new_subtask
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
div {
|
div {
|
||||||
key: "{subtask.id}",
|
class: "grow grid grid-cols-6 gap-2",
|
||||||
class: "flex flex-row items-center gap-3",
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
|
||||||
|
id: "input_title_{subtask.id}",
|
||||||
|
initial_value: subtask.title.clone(),
|
||||||
|
onchange: {
|
||||||
|
let subtask = subtask.clone();
|
||||||
|
move |event: Event<FormData>| {
|
||||||
|
let subtask = subtask.clone();
|
||||||
|
async move {
|
||||||
|
let new_subtask = NewSubtask {
|
||||||
|
task_id: subtask.task_id,
|
||||||
|
title: event.value(),
|
||||||
|
is_completed: subtask.is_completed
|
||||||
|
};
|
||||||
|
if new_subtask.title.is_empty() {
|
||||||
|
let _ = delete_subtask(subtask.id).await;
|
||||||
|
} else {
|
||||||
|
let _ = edit_subtask(
|
||||||
|
subtask.id,
|
||||||
|
new_subtask
|
||||||
|
).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
button {
|
button {
|
||||||
class: "mt-1.5 hover:mt-1 hover:pb-0.5 min-w-7 cursor-pointer transition-all duration-150",
|
r#type: "button",
|
||||||
|
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
|
||||||
onclick: {
|
onclick: {
|
||||||
let subtask = subtask.clone();
|
let subtask = subtask.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
let subtask = subtask.clone();
|
let subtask = subtask.clone();
|
||||||
async move {
|
async move {
|
||||||
let new_subtask = NewSubtask {
|
let _ = delete_subtask(subtask.id).await;
|
||||||
task_id: subtask.task_id,
|
|
||||||
title: subtask.title.clone(),
|
|
||||||
is_completed: !subtask.is_completed
|
|
||||||
};
|
|
||||||
let _ = edit_subtask(
|
|
||||||
subtask.id,
|
|
||||||
new_subtask
|
|
||||||
).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
div {
|
i {
|
||||||
class: format!("grow h-7 w-7 mb-[4px] drop-shadow-[0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800),0_1px_0_var(--color-gray-800)] rounded-full {}",
|
class: "fa-solid fa-trash-can"
|
||||||
if subtask.is_completed {"bg-gray-600"} else {"border-3 border-gray-600"}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
class: "grow flex flex-row items-end gap-3",
|
|
||||||
Input {
|
|
||||||
class: "grow",
|
|
||||||
name: "title_edit_{subtask.id}",
|
|
||||||
r#type: "text",
|
|
||||||
initial_value: subtask.title.clone(),
|
|
||||||
onchange: {
|
|
||||||
let subtask = subtask.clone();
|
|
||||||
move |event: Event<FormData>| {
|
|
||||||
let subtask = subtask.clone();
|
|
||||||
async move {
|
|
||||||
let new_subtask = NewSubtask {
|
|
||||||
task_id: subtask.task_id,
|
|
||||||
title: event.value(),
|
|
||||||
is_completed: subtask.is_completed
|
|
||||||
};
|
|
||||||
if new_subtask.title.is_empty() {
|
|
||||||
let _ = delete_subtask(subtask.id).await;
|
|
||||||
} else {
|
|
||||||
let _ = edit_subtask(
|
|
||||||
subtask.id,
|
|
||||||
new_subtask
|
|
||||||
).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ButtonSecondary {
|
|
||||||
r#type: "button",
|
|
||||||
onclick: {
|
|
||||||
let subtask = subtask.clone();
|
|
||||||
move |_| {
|
|
||||||
let subtask = subtask.clone();
|
|
||||||
async move {
|
|
||||||
let _ = delete_subtask(subtask.id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Icon {
|
|
||||||
icon: FaTrashCan,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
use crate::components::button_primary::ButtonPrimary;
|
|
||||||
use crate::components::button_secondary::ButtonSecondary;
|
|
||||||
use crate::components::category_input::CategoryInput;
|
use crate::components::category_input::CategoryInput;
|
||||||
use crate::components::input::Input;
|
|
||||||
use crate::components::input_label::InputLabel;
|
|
||||||
use crate::components::project_select::ProjectSelect;
|
use crate::components::project_select::ProjectSelect;
|
||||||
use crate::components::reoccurrence_interval_input::ReoccurrenceIntervalInput;
|
use crate::components::reoccurrence_input::ReoccurrenceIntervalInput;
|
||||||
use crate::components::subtasks_form::SubtasksForm;
|
use crate::components::subtasks_form::SubtasksForm;
|
||||||
use crate::models::category::{CalendarTime, Category, Reoccurrence};
|
use crate::models::category::{CalendarTime, Category, Reoccurrence};
|
||||||
use crate::models::task::NewTask;
|
use crate::models::task::NewTask;
|
||||||
use crate::models::task::Task;
|
use crate::models::task::Task;
|
||||||
|
use crate::route::Route;
|
||||||
use crate::server::tasks::{create_task, delete_task, edit_task};
|
use crate::server::tasks::{create_task, delete_task, edit_task};
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use dioxus::core_macro::{component, rsx};
|
use dioxus::core_macro::{component, rsx};
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{
|
|
||||||
FaBell, FaBomb, FaClock, FaFeatherPointed, FaHourglassEnd, FaList, FaRepeat, FaScroll, FaStamp,
|
|
||||||
FaTrashCan, FaXmark,
|
|
||||||
};
|
|
||||||
use dioxus_i18n::t;
|
use dioxus_i18n::t;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -54,19 +46,25 @@ struct InputData {
|
|||||||
project_id: Option<String>,
|
project_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) static TASK_BEING_EDITED: GlobalSignal<Option<Task>> = Signal::global(|| None);
|
|
||||||
pub(crate) static LATEST_VISITED_CATEGORY: GlobalSignal<Category> =
|
|
||||||
Signal::global(|| Category::Inbox);
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn TaskForm() -> Element {
|
pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()>) -> Element {
|
||||||
let navigator = use_navigator();
|
let route = use_route::<Route>();
|
||||||
let task = TASK_BEING_EDITED();
|
|
||||||
let selected_category = use_signal(|| {
|
let selected_category = use_signal(|| {
|
||||||
if let Some(task) = &task {
|
if let Some(task) = &task {
|
||||||
task.category.clone()
|
task.category.clone()
|
||||||
} else {
|
} else {
|
||||||
LATEST_VISITED_CATEGORY()
|
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(|| {
|
let category_calendar_reoccurrence_interval = use_signal(|| {
|
||||||
@@ -106,9 +104,9 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "grow px-4 flex flex-col gap-6.5",
|
class: "p-4 flex flex-col gap-4",
|
||||||
form {
|
form {
|
||||||
class: "flex flex-col gap-8",
|
class: "flex flex-col gap-4",
|
||||||
id: "form_task",
|
id: "form_task",
|
||||||
onsubmit: move |event| {
|
onsubmit: move |event| {
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
@@ -150,45 +148,50 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
project_id: input_data.project_id
|
project_id: input_data.project_id
|
||||||
.and_then(|deadline| deadline.parse().ok()).filter(|&id| id > 0),
|
.and_then(|deadline| deadline.parse().ok()).filter(|&id| id > 0),
|
||||||
};
|
};
|
||||||
let result = if let Some(task) = task {
|
if let Some(task) = task {
|
||||||
edit_task(task.id, new_task).await
|
let _ = edit_task(task.id, new_task).await;
|
||||||
} else {
|
} else {
|
||||||
create_task(new_task).await
|
let _ = create_task(new_task).await;
|
||||||
};
|
|
||||||
if result.is_ok() {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
}
|
||||||
|
on_successful_submit.call(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
r#for: "input_title",
|
r#for: "input_title",
|
||||||
icon: FaFeatherPointed
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-pen-clip text-zinc-400/50"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Input {
|
input {
|
||||||
class: "grow",
|
|
||||||
name: "title",
|
name: "title",
|
||||||
required: true,
|
required: true,
|
||||||
initial_value: task.as_ref().map(|task| task.title.clone()),
|
initial_value: task.as_ref().map(|task| task.title.clone()),
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
autofocus: task.is_none()
|
class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg",
|
||||||
}
|
id: "input_title"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
r#for: "input_project_id",
|
r#for: "input_project",
|
||||||
icon: FaList
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-list text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
SuspenseBoundary {
|
SuspenseBoundary {
|
||||||
fallback: |_| {
|
fallback: |_| {
|
||||||
rsx ! {
|
rsx ! {
|
||||||
select {
|
select {
|
||||||
class: "px-4 pt-3 pb-2.25 bg-gray-800-muted drop-shadow-[0_calc(0px_-_var(--spacing))_0_var(--color-gray-900-muted)] rounded-xl grow cursor-pointer",
|
class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow cursor-pointer",
|
||||||
option {
|
option {
|
||||||
value: 0
|
value: 0,
|
||||||
}
|
{t!("none")}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -199,69 +202,86 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaBomb,
|
r#for: "input_deadline",
|
||||||
r#for: "input_deadline"
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-bomb text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Input {
|
input {
|
||||||
name: "deadline",
|
name: "deadline",
|
||||||
initial_value: task.as_ref().and_then(|task| task.deadline)
|
initial_value: task.as_ref().and_then(|task| task.deadline)
|
||||||
.map(|deadline| deadline.format("%Y-%m-%d").to_string()),
|
.map(|deadline| deadline.format("%Y-%m-%d").to_string()),
|
||||||
r#type: "date",
|
r#type: "date",
|
||||||
class: "grow basis-0"
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow basis-0 cursor-pointer",
|
||||||
|
id: "input_deadline"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaScroll
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-layer-group text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
CategoryInput {
|
CategoryInput {
|
||||||
class: "grow",
|
selected_category: selected_category,
|
||||||
selected_category: selected_category
|
class: "grow"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match selected_category() {
|
match selected_category() {
|
||||||
Category::WaitingFor(waiting_for) => rsx! {
|
Category::WaitingFor(waiting_for) => rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaHourglassEnd,
|
r#for: "input_deadline",
|
||||||
r#for: "input_category_waiting_for",
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-hourglass-end text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Input {
|
input {
|
||||||
class: "grow",
|
|
||||||
name: "category_waiting_for",
|
name: "category_waiting_for",
|
||||||
required: true,
|
required: true,
|
||||||
initial_value: waiting_for,
|
initial_value: waiting_for,
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
}
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
|
||||||
|
id: "input_category_waiting_for"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Category::Calendar { date, reoccurrence, time } => rsx! {
|
Category::Calendar { date, reoccurrence, time } => rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaClock,
|
r#for: "input_category_calendar_date",
|
||||||
r#for: "input_category_calendar_date"
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-clock text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "grow grid grid-cols-7 gap-3",
|
class: "grow flex flex-row gap-2",
|
||||||
Input {
|
input {
|
||||||
class: "grow col-span-4",
|
|
||||||
name: "category_calendar_date",
|
|
||||||
r#type: "date",
|
r#type: "date",
|
||||||
|
name: "category_calendar_date",
|
||||||
required: true,
|
required: true,
|
||||||
initial_value: date.format("%Y-%m-%d").to_string(),
|
initial_value: date.format("%Y-%m-%d").to_string(),
|
||||||
|
class:
|
||||||
|
"py-2 px-3 bg-zinc-800/50 rounded-lg grow cursor-pointer",
|
||||||
|
id: "input_category_calendar_date"
|
||||||
},
|
},
|
||||||
Input {
|
input {
|
||||||
class: "grow col-span-3",
|
|
||||||
name: "category_calendar_time",
|
|
||||||
r#type: "time",
|
r#type: "time",
|
||||||
|
name: "category_calendar_time",
|
||||||
initial_value: time.map(|calendar_time|
|
initial_value: time.map(|calendar_time|
|
||||||
calendar_time.time.format("%H:%M").to_string()
|
calendar_time.time.format("%H:%M").to_string()
|
||||||
),
|
),
|
||||||
oninput: move |event: Event<FormData>| {
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow cursor-pointer",
|
||||||
|
id: "input_category_calendar_time",
|
||||||
|
oninput: move |event| {
|
||||||
category_calendar_has_time.set(!event.value().is_empty());
|
category_calendar_has_time.set(!event.value().is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,46 +289,54 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
icon: FaRepeat,
|
r#for: "category_calendar_reoccurrence_length",
|
||||||
r#for: "category_calendar_reoccurrence_length"
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-repeat text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "grow grid grid-cols-5 items-end gap-3",
|
class: "grow grid grid-cols-6 gap-2",
|
||||||
ReoccurrenceIntervalInput {
|
ReoccurrenceIntervalInput {
|
||||||
reoccurrence_interval: category_calendar_reoccurrence_interval
|
reoccurrence_interval: category_calendar_reoccurrence_interval
|
||||||
},
|
},
|
||||||
Input {
|
input {
|
||||||
class: "text-right",
|
|
||||||
r#type: "number",
|
r#type: "number",
|
||||||
inputmode: "numeric",
|
inputmode: "numeric",
|
||||||
name: "category_calendar_reoccurrence_length",
|
name: "category_calendar_reoccurrence_length",
|
||||||
disabled: category_calendar_reoccurrence_interval().is_none(),
|
disabled: category_calendar_reoccurrence_interval().is_none(),
|
||||||
required: true,
|
required: true,
|
||||||
min: "1",
|
min: 1,
|
||||||
initial_value: category_calendar_reoccurrence_interval().map_or(
|
initial_value: category_calendar_reoccurrence_interval().map_or(
|
||||||
String::new(),
|
String::new(),
|
||||||
|_| reoccurrence.map_or(1, |reoccurrence|
|
|_| reoccurrence.map_or(1, |reoccurrence|
|
||||||
reoccurrence.length).to_string()
|
reoccurrence.length).to_string()
|
||||||
)
|
),
|
||||||
|
class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right",
|
||||||
|
id: "category_calendar_reoccurrence_length"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if category_calendar_has_time() {
|
if category_calendar_has_time() {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-3",
|
class: "flex flex-row items-center gap-3",
|
||||||
InputLabel {
|
label {
|
||||||
r#for: "input_category_calendar_reminder_offset_index",
|
r#for: "category_calendar_reminder_offset_index",
|
||||||
icon: FaBell
|
class: "min-w-6 text-center",
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-bell text-zinc-400/50"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
input {
|
input {
|
||||||
class: "grow",
|
|
||||||
name: "category_calendar_reminder_offset_index",
|
|
||||||
r#type: "range",
|
r#type: "range",
|
||||||
|
name: "category_calendar_reminder_offset_index",
|
||||||
min: 0,
|
min: 0,
|
||||||
max: REMINDER_OFFSETS.len() as i64 - 1,
|
max: REMINDER_OFFSETS.len() as i64 - 1,
|
||||||
initial_value: category_calendar_reminder_offset_index()
|
initial_value: category_calendar_reminder_offset_index()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
class: "grow input-range-reverse cursor-pointer",
|
||||||
|
id: "category_calendar_has_reminder",
|
||||||
oninput: move |event| {
|
oninput: move |event| {
|
||||||
category_calendar_reminder_offset_index.set(
|
category_calendar_reminder_offset_index.set(
|
||||||
event.value().parse().unwrap()
|
event.value().parse().unwrap()
|
||||||
@@ -316,8 +344,8 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
label {
|
label {
|
||||||
class: "pr-3 min-w-16 text-right",
|
|
||||||
r#for: "category_calendar_reminder_offset_index",
|
r#for: "category_calendar_reminder_offset_index",
|
||||||
|
class: "pr-3 min-w-16 text-right",
|
||||||
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()]
|
{REMINDER_OFFSETS[category_calendar_reminder_offset_index()]
|
||||||
.map(
|
.map(
|
||||||
|offset| if offset.num_hours() < 1 {
|
|offset| if offset.num_hours() < 1 {
|
||||||
@@ -343,20 +371,17 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
div {
|
||||||
div {
|
class: "flex flex-row justify-between mt-auto",
|
||||||
class: "px-4 grid grid-cols-3 gap-3 mt-auto",
|
button {
|
||||||
ButtonSecondary {
|
r#type: "button",
|
||||||
r#type: "button",
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg cursor-pointer",
|
||||||
class: "grow",
|
onclick: move |_| {
|
||||||
onclick: {
|
|
||||||
let task = task.clone();
|
|
||||||
move |_| {
|
|
||||||
let task = task.clone();
|
let task = task.clone();
|
||||||
async move {
|
async move {
|
||||||
if let Some(task) = task {
|
if let Some(task) = task {
|
||||||
let result = if let Category::Trash = task.category {
|
if let Category::Trash = task.category {
|
||||||
delete_task(task.id).await
|
let _ = delete_task(task.id).await;
|
||||||
} else {
|
} else {
|
||||||
let new_task = NewTask {
|
let new_task = NewTask {
|
||||||
title: task.title.to_owned(),
|
title: task.title.to_owned(),
|
||||||
@@ -364,50 +389,23 @@ pub(crate) fn TaskForm() -> Element {
|
|||||||
category: Category::Trash,
|
category: Category::Trash,
|
||||||
project_id: task.project_id
|
project_id: task.project_id
|
||||||
};
|
};
|
||||||
edit_task(task.id, new_task).await.map(|_| ())
|
let _ = edit_task(task.id, new_task).await;
|
||||||
};
|
|
||||||
if result.is_ok() {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
navigator.go_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Icon {
|
|
||||||
icon: FaTrashCan,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if task.is_some() {
|
|
||||||
div {
|
|
||||||
class: "grow flex flex-col items-stretch",
|
|
||||||
GoBackButton {
|
|
||||||
ButtonSecondary {
|
|
||||||
/* TODO: Replace w-full` with proper flexbox styling once
|
|
||||||
https://github.com/DioxusLabs/dioxus/issues/5269 is solved. */
|
|
||||||
class: "w-full",
|
|
||||||
r#type: "button",
|
|
||||||
Icon {
|
|
||||||
icon: FaXmark,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
}
|
||||||
|
on_successful_submit.call(());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
i {
|
||||||
|
class: "fa-solid fa-trash-can"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
button {
|
||||||
div {}
|
form: "form_task",
|
||||||
}
|
r#type: "submit",
|
||||||
ButtonPrimary {
|
class: "py-2 px-4 bg-zinc-300/50 rounded-lg cursor-pointer",
|
||||||
form: "form_task",
|
i {
|
||||||
r#type: "submit",
|
class: "fa-solid fa-floppy-disk"
|
||||||
Icon {
|
}
|
||||||
icon: FaStamp,
|
|
||||||
height: 16,
|
|
||||||
width: 16
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use crate::components::task_form::TASK_BEING_EDITED;
|
|
||||||
use crate::components::task_list_item::TaskListItem;
|
use crate::components::task_list_item::TaskListItem;
|
||||||
use crate::models::category::Category;
|
use crate::models::category::Category;
|
||||||
use crate::models::task::TaskWithSubtasks;
|
use crate::models::task::{Task, TaskWithSubtasks};
|
||||||
use crate::route::Route;
|
|
||||||
use crate::server::tasks::complete_task;
|
use crate::server::tasks::complete_task;
|
||||||
use dioxus::core_macro::rsx;
|
use dioxus::core_macro::rsx;
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
@@ -10,7 +8,7 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>) -> Element {
|
pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>) -> Element {
|
||||||
let navigator = use_navigator();
|
let mut task_being_edited = use_context::<Signal<Option<Task>>>();
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: format!("flex flex-col {}", class.unwrap_or("")),
|
class: format!("flex flex-col {}", class.unwrap_or("")),
|
||||||
@@ -18,31 +16,34 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
|
|||||||
div {
|
div {
|
||||||
key: "{task.task.id}",
|
key: "{task.task.id}",
|
||||||
class: format!(
|
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-8 pt-4 {} flex flex-row gap-4 select-none {}",
|
||||||
if task.task.deadline.is_some() || !task.subtasks.is_empty() {
|
if task.task.deadline.is_some() || !task.subtasks.is_empty() {
|
||||||
"pb-0.25"
|
"pb-0.5"
|
||||||
} else if let Category::Calendar { time, .. } = &task.task.category {
|
} else if let Category::Calendar { time, .. } = &task.task.category {
|
||||||
if time.is_some() {
|
if time.is_some() {
|
||||||
"pb-0.25"
|
"pb-0.5"
|
||||||
} else {
|
} else {
|
||||||
"pb-3.75"
|
"pb-4"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"pb-3.75"
|
"pb-4"
|
||||||
},
|
},
|
||||||
|
if task_being_edited().is_some_and(|t| t.id == task.task.id) {
|
||||||
|
"bg-zinc-700"
|
||||||
|
} else { "" }
|
||||||
),
|
),
|
||||||
onclick: {
|
onclick: {
|
||||||
let task = task.clone();
|
let task = task.clone();
|
||||||
move |_| {
|
move |_| task_being_edited.set(Some(task.task.clone()))
|
||||||
*TASK_BEING_EDITED.write() = Some(task.task.clone());
|
|
||||||
navigator.push(Route::TaskFormPage);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
button {
|
i {
|
||||||
class: format!(
|
class: format!(
|
||||||
"mt-0.5 hover:mt-0 hover:pb-0.5 transition-all duration-150 cursor-pointer {}",
|
"{} text-3xl align-middle h-9 text-zinc-500",
|
||||||
if let Category::Done = task.task.category { "pointer-events-none" }
|
if let Category::Done = task.task.category {
|
||||||
else { "" }
|
"fa solid fa-square-check"
|
||||||
|
} else {
|
||||||
|
"fa-regular fa-square cursor-pointer"
|
||||||
|
}
|
||||||
),
|
),
|
||||||
onclick: {
|
onclick: {
|
||||||
move |event: Event<MouseData>| {
|
move |event: Event<MouseData>| {
|
||||||
@@ -52,22 +53,10 @@ pub(crate) fn TaskList(tasks: Vec<TaskWithSubtasks>, class: Option<&'static str>
|
|||||||
let _ = complete_task(task.task.id).await;
|
let _ = complete_task(task.task.id).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
div {
|
|
||||||
class: format!("h-8 w-8 rounded-full {}",
|
|
||||||
if let Category::Done = task.task.category {
|
|
||||||
"mt-[3px] mb-[2px] bg-amber-300-muted"
|
|
||||||
} else {
|
|
||||||
"mb-[5px] border-3 border-amber-300-muted drop-shadow-[0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted),0_1px_0_var(--color-amber-700-muted)]"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
div {
|
TaskListItem {
|
||||||
class: "mt-1.5",
|
task: task.clone()
|
||||||
TaskListItem {
|
|
||||||
task: task.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ use chrono::{Datelike, Local};
|
|||||||
use dioxus::core_macro::rsx;
|
use dioxus::core_macro::rsx;
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::{FaBomb, FaClock, FaListCheck};
|
|
||||||
use dioxus_i18n::prelude::i18n;
|
use dioxus_i18n::prelude::i18n;
|
||||||
use dioxus_i18n::t;
|
use dioxus_i18n::t;
|
||||||
use voca_rs::Voca;
|
use voca_rs::Voca;
|
||||||
@@ -16,21 +14,19 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
|
|||||||
let today_date = Local::now().date_naive();
|
let today_date = Local::now().date_naive();
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "pt-0.75 flex flex-col",
|
class: "flex flex-col",
|
||||||
div {
|
div {
|
||||||
class: "grow font-medium text-pretty wrap-anywhere",
|
class: "mt-1 grow font-medium",
|
||||||
{task.task.title}
|
{task.task.title}
|
||||||
},
|
},
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row gap-4",
|
class: "flex flex-row gap-4",
|
||||||
if let Some(deadline) = task.task.deadline {
|
if let Some(deadline) = task.task.deadline {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-1 text-sm text-gray-500",
|
class: "text-sm text-zinc-400",
|
||||||
Icon {
|
i {
|
||||||
icon: FaBomb,
|
class: "fa-solid fa-bomb"
|
||||||
height: 14,
|
},
|
||||||
width: 14
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
format!(
|
format!(
|
||||||
" {}",
|
" {}",
|
||||||
@@ -76,12 +72,10 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
|
|||||||
if let Category::Calendar { time, .. } = task.task.category {
|
if let Category::Calendar { time, .. } = task.task.category {
|
||||||
if let Some(calendar_time) = time {
|
if let Some(calendar_time) = time {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-1 text-sm text-gray-500",
|
class: "text-sm text-zinc-400",
|
||||||
Icon {
|
i {
|
||||||
icon: FaClock,
|
class: "fa-solid fa-clock"
|
||||||
height: 14,
|
},
|
||||||
width: 14
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
let format = t!("time-format");
|
let format = t!("time-format");
|
||||||
format!(" {}", calendar_time.time.format(format.as_str()))
|
format!(" {}", calendar_time.time.format(format.as_str()))
|
||||||
@@ -91,12 +85,10 @@ pub(crate) fn TaskListItem(task: TaskWithSubtasks) -> Element {
|
|||||||
}
|
}
|
||||||
if !task.subtasks.is_empty() {
|
if !task.subtasks.is_empty() {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-row items-center gap-1 text-sm text-gray-500",
|
class: "text-sm text-zinc-400",
|
||||||
Icon {
|
i {
|
||||||
icon: FaListCheck,
|
class: "fa-solid fa-list-check"
|
||||||
height: 14,
|
},
|
||||||
width: 14
|
|
||||||
}
|
|
||||||
{format!(
|
{format!(
|
||||||
" {}/{}",
|
" {}/{}",
|
||||||
task.subtasks.iter()
|
task.subtasks.iter()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use dioxus::{
|
use dioxus::{
|
||||||
CapturedError,
|
CapturedError,
|
||||||
fullstack::{Loader, Loading, WebSocketOptions},
|
document::document,
|
||||||
|
fullstack::{Loader, Loading, WebSocketOptions, WebsocketState, use_websocket},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
@@ -13,7 +16,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn use_on_document_become_visible(mut callback: impl FnMut() + 'static) {
|
fn use_on_document_visibility_change(mut callback: impl FnMut() + 'static) {
|
||||||
let callback = use_callback(move |_| callback());
|
let callback = use_callback(move |_| callback());
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
@@ -24,6 +27,13 @@ fn use_on_document_become_visible(mut callback: impl FnMut() + 'static) {
|
|||||||
dioxus.send(0);
|
dioxus.send(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// window.addEventListener("focus", () => resume());
|
||||||
|
|
||||||
|
// Keep this eval alive so dioxus.send keeps working.
|
||||||
|
// while (true) {
|
||||||
|
// await new Promise(r => setTimeout(r, 3600_000));
|
||||||
|
// }
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
@@ -31,6 +41,7 @@ fn use_on_document_become_visible(mut callback: impl FnMut() + 'static) {
|
|||||||
.await
|
.await
|
||||||
.expect("The JS code returned a value not parsable to `u8`.");
|
.expect("The JS code returned a value not parsable to `u8`.");
|
||||||
callback.call(());
|
callback.call(());
|
||||||
|
log("received resume");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -47,6 +58,19 @@ fn sort_loader_result<T: Ord + Clone>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static LOG_MESSAGES: GlobalSignal<Vec<String>> =
|
||||||
|
Signal::global(|| vec!["=== LOG ===".to_string()]);
|
||||||
|
|
||||||
|
fn log(message: impl Display) {
|
||||||
|
let mut old = LOG_MESSAGES();
|
||||||
|
old.push(format!(
|
||||||
|
"[{}] {}",
|
||||||
|
chrono::Local::now().format("%H:%M:%S"),
|
||||||
|
message.to_string()
|
||||||
|
));
|
||||||
|
*LOG_MESSAGES.write() = old;
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::result_large_err)]
|
#[allow(clippy::result_large_err)]
|
||||||
fn use_loader_with_update_subscription<F, T, E>(
|
fn use_loader_with_update_subscription<F, T, E>(
|
||||||
mut future: impl FnMut() -> F + 'static,
|
mut future: impl FnMut() -> F + 'static,
|
||||||
@@ -67,25 +91,29 @@ where
|
|||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
let initial_websocket_reset_tick = websocket_reset_tick();
|
let initial_websocket_reset_tick = websocket_reset_tick();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
|
log("spawning based on ws_gen");
|
||||||
let Ok(socket) =
|
let Ok(socket) =
|
||||||
subscribe_to_updates(WebSocketOptions::new().with_automatic_reconnect()).await
|
subscribe_to_updates(WebSocketOptions::new().with_automatic_reconnect()).await
|
||||||
else {
|
else {
|
||||||
|
log("failed to spawn");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
while socket.recv().await.is_ok() {
|
while socket.recv().await.is_ok() {
|
||||||
|
log("running the future recv");
|
||||||
if websocket_reset_tick() != initial_websocket_reset_tick {
|
if websocket_reset_tick() != initial_websocket_reset_tick {
|
||||||
|
log("new WS gen, breaking");
|
||||||
// A new WebSocket has been created (a new task spawned), cleaning this one up.
|
// A new WebSocket has been created (a new task spawned), cleaning this one up.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
refresh_tick += 1;
|
refresh_tick += 1;
|
||||||
}
|
}
|
||||||
|
log("future ending");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* So that when the device goes to sleep or suspends the app, the WebSocket gets recreated on
|
/* 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),
|
waking up. */
|
||||||
because becoming hidden is the part when network may not work and thus cause errors. */
|
use_on_document_visibility_change(move || {
|
||||||
use_on_document_become_visible(move || {
|
|
||||||
websocket_reset_tick += 1;
|
websocket_reset_tick += 1;
|
||||||
refresh_tick += 1;
|
refresh_tick += 1;
|
||||||
});
|
});
|
||||||
|
|||||||
47
src/layouts/main.rs
Normal file
47
src/layouts/main.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use crate::components::bottom_panel::BottomPanel;
|
||||||
|
use crate::components::form_open_button::FormOpenButton;
|
||||||
|
use crate::components::sticky_bottom::StickyBottom;
|
||||||
|
use crate::models::project::Project;
|
||||||
|
use crate::models::task::Task;
|
||||||
|
use crate::route::Route;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn Main() -> Element {
|
||||||
|
let mut display_form = use_signal(|| false);
|
||||||
|
let project_being_edited =
|
||||||
|
use_context_provider::<Signal<Option<Project>>>(|| Signal::new(None));
|
||||||
|
let task_being_edited = use_context_provider::<Signal<Option<Task>>>(|| Signal::new(None));
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
display_form.set(project_being_edited().is_some() || task_being_edited().is_some());
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
SuspenseBoundary {
|
||||||
|
fallback: |_| {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "grow flex flex-col justify-center items-center",
|
||||||
|
div {
|
||||||
|
i {
|
||||||
|
class: "text-3xl fa-solid fa-cog fa-spin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Outlet::<Route> {}
|
||||||
|
}
|
||||||
|
StickyBottom {
|
||||||
|
FormOpenButton {
|
||||||
|
opened: display_form,
|
||||||
|
}
|
||||||
|
BottomPanel {
|
||||||
|
display_form: display_form,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
pub(crate) mod navigation;
|
mod main;
|
||||||
pub(crate) mod suspense;
|
pub(crate) use main::Main;
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
use crate::components::bottom_panel::BottomPanel;
|
|
||||||
use crate::components::create_button::CreateButton;
|
|
||||||
use crate::components::sticky_bottom::StickyBottom;
|
|
||||||
use crate::components::task_form::LATEST_VISITED_CATEGORY;
|
|
||||||
use crate::models::category::Category;
|
|
||||||
use crate::route::Route;
|
|
||||||
use dioxus::core_macro::rsx;
|
|
||||||
use dioxus::dioxus_core::Element;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn Navigation() -> Element {
|
|
||||||
let current_route = use_route();
|
|
||||||
use_effect(use_reactive(¤t_route, move |current_route| {
|
|
||||||
*LATEST_VISITED_CATEGORY.write() = match current_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,
|
|
||||||
},
|
|
||||||
_ => Category::Inbox,
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
|
|
||||||
rsx! {
|
|
||||||
div {
|
|
||||||
class: "grow flex flex-col pb-36",
|
|
||||||
Outlet::<Route> {}
|
|
||||||
}
|
|
||||||
StickyBottom {
|
|
||||||
CreateButton {},
|
|
||||||
BottomPanel {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
use crate::route::Route;
|
|
||||||
use dioxus::core_macro::rsx;
|
|
||||||
use dioxus::dioxus_core::Element;
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
use dioxus_free_icons::Icon;
|
|
||||||
use dioxus_free_icons::icons::fa_solid_icons::FaCog;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn Suspense() -> Element {
|
|
||||||
rsx! {
|
|
||||||
SuspenseBoundary {
|
|
||||||
fallback: |_| {
|
|
||||||
rsx! {
|
|
||||||
div {
|
|
||||||
class: "grow flex flex-col justify-center items-center",
|
|
||||||
Icon {
|
|
||||||
class: "text-gray-500 animate-[spin_3000ms_linear_infinite]",
|
|
||||||
icon: FaCog,
|
|
||||||
height: 32,
|
|
||||||
width: 32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Outlet::<Route> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,14 @@ use crate::layouts;
|
|||||||
use crate::views::category_calendar_page::CategoryCalendarPage;
|
use crate::views::category_calendar_page::CategoryCalendarPage;
|
||||||
use crate::views::category_done_page::CategoryDonePage;
|
use crate::views::category_done_page::CategoryDonePage;
|
||||||
use crate::views::category_inbox_page::CategoryInboxPage;
|
use crate::views::category_inbox_page::CategoryInboxPage;
|
||||||
|
use crate::views::category_long_term_page::CategoryLongTermPage;
|
||||||
use crate::views::category_next_steps_page::CategoryNextStepsPage;
|
use crate::views::category_next_steps_page::CategoryNextStepsPage;
|
||||||
use crate::views::category_someday_maybe_page::CategorySomedayMaybePage;
|
use crate::views::category_someday_maybe_page::CategorySomedayMaybePage;
|
||||||
use crate::views::category_today_page::CategoryTodayPage;
|
use crate::views::category_today_page::CategoryTodayPage;
|
||||||
use crate::views::category_trash_page::CategoryTrashPage;
|
use crate::views::category_trash_page::CategoryTrashPage;
|
||||||
use crate::views::category_waiting_for_page::CategoryWaitingForPage;
|
use crate::views::category_waiting_for_page::CategoryWaitingForPage;
|
||||||
use crate::views::not_found_page::NotFoundPage;
|
use crate::views::not_found_page::NotFoundPage;
|
||||||
use crate::views::project_form_page::ProjectFormPage;
|
|
||||||
use crate::views::projects_page::ProjectsPage;
|
use crate::views::projects_page::ProjectsPage;
|
||||||
use crate::views::task_form_page::TaskFormPage;
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
// All variants have the same postfix because they have to match the component names.
|
// All variants have the same postfix because they have to match the component names.
|
||||||
@@ -18,33 +17,28 @@ use dioxus::prelude::*;
|
|||||||
#[derive(Clone, Routable, Debug, PartialEq)]
|
#[derive(Clone, Routable, Debug, PartialEq)]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub(crate) enum Route {
|
pub(crate) enum Route {
|
||||||
#[layout(layouts::navigation::Navigation)]
|
#[layout(layouts::Main)]
|
||||||
#[layout(layouts::suspense::Suspense)]
|
#[redirect("/", || Route::CategoryTodayPage {})]
|
||||||
#[route("/today")]
|
#[route("/today")]
|
||||||
CategoryTodayPage,
|
CategoryTodayPage,
|
||||||
#[route("/inbox")]
|
#[route("/inbox")]
|
||||||
CategoryInboxPage,
|
CategoryInboxPage,
|
||||||
#[route("/someday-maybe")]
|
#[route("/someday-maybe")]
|
||||||
CategorySomedayMaybePage,
|
CategorySomedayMaybePage,
|
||||||
#[route("/waiting-for")]
|
#[route("/waiting-for")]
|
||||||
CategoryWaitingForPage,
|
CategoryWaitingForPage,
|
||||||
#[route("/next-steps")]
|
#[route("/next-steps")]
|
||||||
CategoryNextStepsPage,
|
CategoryNextStepsPage,
|
||||||
#[route("/calendar")]
|
#[route("/calendar")]
|
||||||
CategoryCalendarPage,
|
CategoryCalendarPage,
|
||||||
#[route("/done")]
|
#[route("/long-term")]
|
||||||
CategoryDonePage,
|
CategoryLongTermPage,
|
||||||
#[route("/trash")]
|
#[route("/done")]
|
||||||
CategoryTrashPage,
|
CategoryDonePage,
|
||||||
#[route("/projects")]
|
#[route("/trash")]
|
||||||
ProjectsPage,
|
CategoryTrashPage,
|
||||||
#[end_layout]
|
#[route("/projects")]
|
||||||
#[end_layout]
|
ProjectsPage,
|
||||||
#[layout(layouts::suspense::Suspense)]
|
|
||||||
#[route("/task")]
|
|
||||||
TaskFormPage,
|
|
||||||
#[route("/project")]
|
|
||||||
ProjectFormPage,
|
|
||||||
#[end_layout]
|
#[end_layout]
|
||||||
#[redirect("/", || Route::CategoryTodayPage)]
|
#[redirect("/", || Route::CategoryTodayPage)]
|
||||||
#[route("/:..route")]
|
#[route("/:..route")]
|
||||||
|
|||||||
12
src/styles/tailwind.config.js
Normal file
12
src/styles/tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import("tailwindcss").Config} */
|
||||||
|
module.exports = {
|
||||||
|
mode: "all",
|
||||||
|
content: ["./src/**/*.{rs,html,css}"],
|
||||||
|
theme: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Inter", "sans-serif"],
|
||||||
|
},
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
10
src/styles/tailwind.css
Normal file
10
src/styles/tailwind.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* stylelint-disable */
|
||||||
|
|
||||||
|
/* noinspection CssInvalidAtRule */
|
||||||
|
@tailwind base;
|
||||||
|
/* noinspection CssInvalidAtRule */
|
||||||
|
@tailwind components;
|
||||||
|
/* noinspection CssInvalidAtRule */
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* stylelint-enable */
|
||||||
17
src/views/category_long_term_page.rs
Normal file
17
src/views/category_long_term_page.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use crate::components::error_boundary_message::ErrorBoundaryMessage;
|
||||||
|
use crate::models::category::Category;
|
||||||
|
use crate::views::category_page::CategoryPage;
|
||||||
|
use dioxus::core_macro::rsx;
|
||||||
|
use dioxus::dioxus_core::Element;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub(crate) fn CategoryLongTermPage() -> Element {
|
||||||
|
rsx! {
|
||||||
|
ErrorBoundaryMessage {
|
||||||
|
CategoryPage {
|
||||||
|
category: Category::LongTerm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,8 @@ pub(crate) fn CategoryPage(category: Category) -> Element {
|
|||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
TaskList {
|
TaskList {
|
||||||
tasks: tasks.clone()
|
tasks: tasks.clone(),
|
||||||
|
class: "pb-36"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub(crate) mod category_calendar_page;
|
pub(crate) mod category_calendar_page;
|
||||||
pub(crate) mod category_done_page;
|
pub(crate) mod category_done_page;
|
||||||
pub(crate) mod category_inbox_page;
|
pub(crate) mod category_inbox_page;
|
||||||
|
pub(crate) mod category_long_term_page;
|
||||||
pub(crate) mod category_next_steps_page;
|
pub(crate) mod category_next_steps_page;
|
||||||
pub(crate) mod category_page;
|
pub(crate) mod category_page;
|
||||||
pub(crate) mod category_someday_maybe_page;
|
pub(crate) mod category_someday_maybe_page;
|
||||||
@@ -8,6 +9,4 @@ pub(crate) mod category_today_page;
|
|||||||
pub(crate) mod category_trash_page;
|
pub(crate) mod category_trash_page;
|
||||||
pub(crate) mod category_waiting_for_page;
|
pub(crate) mod category_waiting_for_page;
|
||||||
pub(crate) mod not_found_page;
|
pub(crate) mod not_found_page;
|
||||||
pub(crate) mod project_form_page;
|
|
||||||
pub(crate) mod projects_page;
|
pub(crate) mod projects_page;
|
||||||
pub(crate) mod task_form_page;
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
use crate::components::{error_boundary_message::ErrorBoundaryMessage, project_form::ProjectForm};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn ProjectFormPage() -> Element {
|
|
||||||
rsx! {
|
|
||||||
ErrorBoundaryMessage {
|
|
||||||
class: "grow py-4 flex flex-col gap-12",
|
|
||||||
ProjectForm {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
use crate::components::{error_boundary_message::ErrorBoundaryMessage, task_form::TaskForm};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub(crate) fn TaskFormPage() -> Element {
|
|
||||||
rsx! {
|
|
||||||
ErrorBoundaryMessage {
|
|
||||||
class: "grow py-4 flex flex-col gap-12",
|
|
||||||
TaskForm {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
tailwind.css
25
tailwind.css
@@ -1,27 +1,2 @@
|
|||||||
/* stylelint-disable-next-line import-notation */
|
/* stylelint-disable-next-line import-notation */
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Inter;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/assets/fonts/inter_variable.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Inter;
|
|
||||||
font-style: italic;
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/assets/fonts/inter_variable_italic.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* stylelint-disable-next-line */
|
|
||||||
@theme {
|
|
||||||
--font-sans: "Inter", "sans";
|
|
||||||
--color-amber-300-muted: #b89a2e;
|
|
||||||
--color-amber-700-muted: #80390b;
|
|
||||||
--color-gray-800-muted: #141d2d;
|
|
||||||
--color-gray-900-muted: #0b111f;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user