app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit b676b39b8e1cc2c47e1bf76ce23cebefe18da438
parent 50a672e2dd6dc3df0e270e7c0bf0aec1af01f492
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Thu, 24 Oct 2024 13:09:38 +0000

Edit `/cfg/init` add fetch handlers to create nip-05 profiles, add check for existing configuration keypair, add/edit page error handling, utils, add locales. Edit root, (app), (cfg) layout load functions. Edit `/models/` routes add load data logic and page error handling. Edit `/` add conditional render for app_cfg_type, add/edit styles. Edit page layouts. Add/edit conf, utils, styles, types. Update rust/js packages.

Diffstat:
MCargo.lock | 362+++++++++++++------------------------------------------------------------------
Mcrates/tauri/Cargo.toml | 16++++++++--------
Mcrates/tauri/capabilities/default.json | 1+
Mcrates/tauri/src/database.rs | 2+-
Mcrates/tauri/src/lib.rs | 4++--
Mpackage.json | 4++--
Msrc/app.css | 15++++++++++-----
Msrc/lib/client.ts | 9++++++---
Dsrc/lib/components/button_appearing_pair.svelte | 61-------------------------------------------------------------
Msrc/lib/conf.ts | 23++++++++++++++++-------
Msrc/lib/utils/client.ts | 16++++++++--------
Msrc/routes/(app)/+layout.svelte | 12+++++++++++-
Asrc/routes/(app)/+layout.ts | 38++++++++++++++++++++++++++++++++++++++
Msrc/routes/(app)/+page.svelte | 272+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/routes/(app)/models/nostr-profile/+page.svelte | 11++++++++---
Msrc/routes/(app)/models/nostr-profile/view/+page.svelte | 2+-
Msrc/routes/(app)/models/trade-product/+page.svelte | 7++-----
Msrc/routes/(app)/models/trade-product/add/+page.svelte | 4++--
Msrc/routes/(app)/settings/+page.svelte | 629+++++++++++++++++++++++++++++++++++++++----------------------------------------
Dsrc/routes/(app)/test/+page.svelte | 9---------
Asrc/routes/(cfg)/cfg/+layout.ts | 29+++++++++++++++++++++++++++++
Asrc/routes/(cfg)/cfg/error/+page.svelte | 25+++++++++++++++++++++++++
Asrc/routes/(cfg)/cfg/init/+page.svelte | 1250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/routes/(conf)/conf/error/+page.svelte | 22----------------------
Dsrc/routes/(conf)/conf/init/+layout.ts | 27---------------------------
Dsrc/routes/(conf)/conf/init/+page.svelte | 975-------------------------------------------------------------------------------
Msrc/routes/+layout.svelte | 77++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/routes/+layout.ts | 4+++-
Mtailwind.config.ts | 9+++++++++
29 files changed, 1982 insertions(+), 1933 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -25,17 +25,6 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" @@ -83,23 +72,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] -name = "android_log-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937" - -[[package]] -name = "android_logger" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" -dependencies = [ - "android_log-sys", - "env_filter", - "log", -] - -[[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -164,12 +136,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] name = "ashpd" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -426,18 +392,6 @@ dependencies = [ ] [[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -475,30 +429,6 @@ dependencies = [ ] [[package]] -name = "borsh" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d4d6dafc1a3bb54687538972158f07b2c948bc57d5890df22c0739098b3028" -dependencies = [ - "borsh-derive", - "cfg_aliases 0.1.1", -] - -[[package]] -name = "borsh-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" -dependencies = [ - "once_cell", - "proc-macro-crate 2.0.2", - "proc-macro2", - "quote", - "syn 2.0.79", - "syn_derive", -] - -[[package]] name = "brotli" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -526,39 +456,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "byte-unit" -version = "5.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" -dependencies = [ - "rust_decimal", - "serde", - "utf8-width", -] - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "bytemuck" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -690,12 +587,6 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" @@ -1352,15 +1243,6 @@ dependencies = [ ] [[package]] -name = "fern" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" -dependencies = [ - "log", -] - -[[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1443,12 +1325,6 @@ dependencies = [ ] [[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1690,6 +1566,16 @@ dependencies = [ ] [[package]] +name = "gethostname" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" +dependencies = [ + "rustix", + "windows-targets 0.52.6", +] + +[[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1889,9 +1775,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] [[package]] name = "hashbrown" @@ -1899,7 +1782,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", ] @@ -2460,9 +2343,6 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" -dependencies = [ - "value-bag", -] [[package]] name = "mac" @@ -2743,15 +2623,6 @@ dependencies = [ ] [[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3042,6 +2913,17 @@ dependencies = [ ] [[package]] +name = "os_info" +version = "3.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +dependencies = [ + "log", + "serde", + "windows-sys 0.52.0", +] + +[[package]] name = "os_pipe" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3444,26 +3326,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "publicsuffix" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3558,12 +3420,6 @@ dependencies = [ ] [[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] name = "radroots" version = "0.0.1" dependencies = [ @@ -3579,9 +3435,9 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-geolocation", "tauri-plugin-http", - "tauri-plugin-log", "tauri-plugin-map-display", "tauri-plugin-notification", + "tauri-plugin-os", "tauri-plugin-shell", "tauri-plugin-store", ] @@ -3737,15 +3593,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] name = "reqwest" version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3833,35 +3680,6 @@ dependencies = [ ] [[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "rsa" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3882,22 +3700,6 @@ dependencies = [ ] [[package]] -name = "rust_decimal" -version = "1.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - -[[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4026,12 +3828,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] name = "security-framework" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4295,12 +4091,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4341,7 +4131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", - "cfg_aliases 0.2.1", + "cfg_aliases", "core-graphics", "foreign-types", "js-sys", @@ -4723,24 +4513,21 @@ dependencies = [ ] [[package]] -name = "syn_derive" -version = "0.1.8" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.79", + "futures-core", ] [[package]] -name = "sync_wrapper" -version = "1.0.1" +name = "sys-locale" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" dependencies = [ - "futures-core", + "libc", ] [[package]] @@ -4828,12 +4615,6 @@ dependencies = [ ] [[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4841,9 +4622,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.3" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd96d46534b10765ce0c6208f9451d98ea38636364a41b272d3610c70dd0e4c3" +checksum = "5ce2818e803ce3097987296623ed8c0d9f65ed93b4137ff9a83e168bdbf62932" dependencies = [ "anyhow", "bytes", @@ -5048,28 +4829,6 @@ dependencies = [ ] [[package]] -name = "tauri-plugin-log" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49f2c05d15e6375ab7f7e528b3049150ba4dfafdf61f85e5178d0aef18e3f5" -dependencies = [ - "android_logger", - "byte-unit", - "cocoa", - "fern", - "log", - "objc", - "serde", - "serde_json", - "serde_repr", - "swift-rs", - "tauri", - "tauri-plugin", - "thiserror", - "time", -] - -[[package]] name = "tauri-plugin-map-display" version = "0.1.0" source = "git+https://github.com/inkibra/tauri-plugins?tag=@inkibra/tauri-plugin-map-display@0.2.0#31aa60a18d19f35828649e84c36597d18661b7e9" @@ -5100,6 +4859,24 @@ dependencies = [ ] [[package]] +name = "tauri-plugin-os" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc5f23a86f37687c7f4fecfdc706b279087bc44f7a46702f7307ff1551ee03a" +dependencies = [ + "gethostname", + "log", + "os_info", + "serde", + "serde_json", + "serialize-to-javascript", + "sys-locale", + "tauri", + "tauri-plugin", + "thiserror", +] + +[[package]] name = "tauri-plugin-shell" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5122,9 +4899,9 @@ dependencies = [ [[package]] name = "tauri-plugin-store" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5058f179f7215390fc5a68eeffcb805b7e2681d6e817a5d08094fae7ab649e68" +checksum = "e9a580be53f04bb62422d239aa798e88522877f58a0d4a0e745f030055a51bb4" dependencies = [ "dunce", "log", @@ -5157,9 +4934,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac63b65df8e85570993eaf93ae1dd73a6fb66d8bd99674ce65f41dc3c63e7d" +checksum = "1431602bcc71f2f840ad623915c9842ecc32999b867c4a787d975a17a9625cc6" dependencies = [ "gtk", "http", @@ -5296,9 +5073,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.11", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -5664,12 +5439,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] -name = "utf8-width" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" - -[[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5686,12 +5455,6 @@ dependencies = [ ] [[package]] -name = "value-bag" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" - -[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6441,9 +6204,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.46.0" +version = "0.46.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a3765ecc3e8aa9ccdf3c5a52c82697ec03037cd60494488763880d31a1b3a" +checksum = "cd5cdf57c66813d97601181349c63b96994b3074fc3d7a31a8cce96e968e3bbd" dependencies = [ "base64 0.22.1", "block2", @@ -6481,15 +6244,6 @@ dependencies = [ ] [[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] name = "x11" version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml @@ -11,7 +11,7 @@ name = "radroots_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2.0.0-rc", features = [] } +tauri-build = { version = "2.0.0", features = [] } [dependencies] env_logger = "0.11.5" @@ -21,15 +21,15 @@ radroots_core = { path = "../core" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio"] } -tauri = { version = "2.0.0-rc", features = ["protocol-asset"] } -tauri-plugin-dialog = "2.0.0-rc" +tauri = { version = "2.0.0", features = ["protocol-asset"] } +tauri-plugin-dialog = "2.0.0" tauri-plugin-geolocation = "2.0.0" -tauri-plugin-http = "2.0.0-rc" -tauri-plugin-log = "2.0.0-rc" +tauri-plugin-http = "2.0.0" tauri-plugin-map-display = { git = "https://github.com/inkibra/tauri-plugins", tag = "@inkibra/tauri-plugin-map-display@0.2.0", package="tauri-plugin-map-display" } -tauri-plugin-notification = "2.0.0-rc" -tauri-plugin-store = "2.0.0-rc" -tauri-plugin-shell = "2.0.0-rc" +tauri-plugin-notification = "2.0.0" +tauri-plugin-os = "2.0.0" +tauri-plugin-store = "2.1.0" +tauri-plugin-shell = "2.0.0" [profile.dev] incremental = true diff --git a/crates/tauri/capabilities/default.json b/crates/tauri/capabilities/default.json @@ -27,6 +27,7 @@ "deny": [] }, "notification:default", + "os:default", "store:default", "shell:allow-open", "map-display:default" diff --git a/crates/tauri/src/database.rs b/crates/tauri/src/database.rs @@ -10,7 +10,7 @@ pub async fn setup_db(data_dir: &PathBuf) -> sqlx::Pool<sqlx::Sqlite> { panic!("Error resolving databse directory {}", err); } }; - path.push("radroots.sqlite"); + path.push("radroots_db.sqlite"); let result = OpenOptions::new().create_new(true).write(true).open(&path); match result { Ok(_) => println!("Database file created"), diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs @@ -43,13 +43,13 @@ pub fn run() { Ok(()) }) }) - .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_geolocation::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_store::Builder::new().build()) - .plugin(tauri_plugin_map_display::init()) .invoke_handler(tauri::generate_handler![ model_location_gcs_add, model_location_gcs_get, diff --git a/package.json b/package.json @@ -19,11 +19,10 @@ "tauri": "tauri" }, "devDependencies": { - "@capacitor/cli": "^6.1.2", "@sveltejs/adapter-static": "^3.0.0", "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0", - "@tauri-apps/cli": "2.0.0-rc.16", + "@tauri-apps/cli": "2.0.4", "@types/node": "^20.12.7", "@types/sql.js": "^1.4.9", "autoprefixer": "^10.4.19", @@ -48,6 +47,7 @@ "@radroots/svelte-maplibre": "workspace:*", "@radroots/theme": "workspace:*", "@radroots/utils": "workspace:*", + "chart.js": "^4.4.5", "sql.js": "^1.11.0" } } \ No newline at end of file diff --git a/src/app.css b/src/app.css @@ -8,22 +8,27 @@ @import "/static/webfonts/lust/styles.css"; @import "/static/webfonts/magda-text/styles.css"; @import "/static/webfonts/sf-pro-display/styles.css"; +@import "/static/webfonts/circular/styles.css"; +@import "/static/webfonts/archivo/styles.css"; +@import "/static/webfonts/space-grotesk/styles.css"; @tailwind base; @tailwind components; @tailwind utilities; +@layer base { + .cds--cc--chart-wrapper { + --cds-charts-font-family: Circular; + --cds-charts-font-family-condensed: 'Circular'; + } +} + @layer components { .map-trellis-1 { height: 400px; width: 100%; } - .tap-rise-1 { - @apply active:scale-[101%] group-active:scale-[101%] delay-75 duration-700 ease-in-out transition-all; - } - - .tap-scale { @apply active:scale-[97%] group-active:scale-[97%] delay-75 duration-700 ease-in-out transition-all; } diff --git a/src/lib/client.ts b/src/lib/client.ts @@ -1,15 +1,18 @@ -import { ClientNostr, TauriClientDb, TauriClientDialog, TauriClientGeolocation, TauriClientHaptics, TauriClientHttp, TauriClientKeying, TauriClientKeystore, TauriClientMap, TauriClientNotification, TauriClientWindow } from "@radroots/client"; +import { ClientNostr, TauriClientDb, TauriClientDevice, TauriClientDialog, TauriClientGeolocation, TauriClientHaptics, TauriClientHttp, TauriClientKeying, TauriClientKeystore, TauriClientLogger, TauriClientMap, TauriClientNotification, TauriClientOs, TauriClientWindow } from "@radroots/client"; import { Geocoder } from "@radroots/geocoder"; export const geoc = new Geocoder(`/geonames/geonames.db`); export const db = new TauriClientDb(); +export const device = new TauriClientDevice(); export const dialog = new TauriClientDialog(); export const geol = new TauriClientGeolocation(); export const haptics = new TauriClientHaptics(); +export const os = new TauriClientOs(); export const http = new TauriClientHttp(); export const map = new TauriClientMap(); export const keystore = new TauriClientKeystore(); export const keyring = new TauriClientKeying(); export const nostr = new ClientNostr(); export const notification = new TauriClientNotification(); -export const win = new TauriClientWindow(); -\ No newline at end of file +export const win = new TauriClientWindow(); +export const logger = new TauriClientLogger(); +\ No newline at end of file diff --git a/src/lib/components/button_appearing_pair.svelte b/src/lib/components/button_appearing_pair.svelte @@ -1,61 +0,0 @@ -<script lang="ts"> - import { - app_layout, - Fill, - t, - type CallbackPromise, - } from "@radroots/svelte-lib"; - - export let basis: { - continue: { - disabled?: boolean; - label?: string; - callback: CallbackPromise; - }; - back?: { - visible: boolean; - disabled?: boolean; - label?: string; - callback: CallbackPromise; - }; - }; -</script> - -<div class={`flex flex-col justify-center items-center`}> - <button - class={`group flex flex-row h-touch_guide w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-touch ${basis.continue.disabled ? `opacity-60` : `touch-layer-1`} transition-all`} - on:click|stopPropagation={async () => { - if (!basis.continue.disabled) await basis.continue.callback(); - }} - > - <p - class={`font-sans font-[600] tracking-wide text-layer-1-glyph/80 ${basis.continue.disabled ? `` : `group-active:text-layer-1-glyph/40 `}transition-all`} - > - {basis.continue.label || `${$t(`common.continue`)}`} - </p> - </button> - {#if basis.back} - <div class={`flex flex-col justify-center items-center transition-all`}> - {#if basis.back?.visible} - <button - class={`group flex flex-row h-12 w-${$app_layout} justify-center items-center fade-in`} - on:click|stopPropagation={async () => { - if (!basis.back?.disabled) await basis.back?.callback(); - }} - > - <p - class={`font-sans font-[600] tracking-wide text-layer-1-glyph-shade ${basis.back?.disabled ? `` : `group-active:text-layer-1-glyph/40`} transition-all`} - > - {basis.back?.label || `${$t(`common.back`)}`} - </p> - </button> - {:else} - <div - class={`flex flex-row h-4 w-full justify-start items-center`} - > - <Fill /> - </div> - {/if} - </div> - {/if} -</div> diff --git a/src/lib/conf.ts b/src/lib/conf.ts @@ -1,11 +1,19 @@ import type { NumberTuple } from "@radroots/utils"; +//import tailwindConfig from '../..//tailwind.config'; +//export const tw = tailwindConfig; export const ks = { - nostr: { - conf_init_key: `conf:init:nostr:key`, - conf_init_profile: `conf:init:nostr:profile`, - nostr_key: (public_key: string) => `nostr:key:${public_key}`, - nostr_key_active: `nostr:key:active`, + cfg_init: { + nostr_secretkey: `cfg:init:nostr:secretkey`, + nostr_profilename: `cfg:init:nostr:profilename`, + radroots_tok: `cfg:init:radroots:tok` + }, + pref: { + cfg_type: `pref:cfg_type`, + }, + keys: { + nostr_secretkey: (public_key: string) => `nostr:key:${public_key}`, + nostr_publickey: `keys:nostr:`, } }; @@ -16,6 +24,7 @@ export const cfg = { description: `Creating networks between farmers, communities and small businesses that give customers greater access to natural foods and grow circular economies where profits are more fairly distributed. Radroots is built on the Nostr protocol and released under a copyleft open source license to provide transparency and give users the option to offer feedback and add or request new features.` }, nostr: { + relay_url: `wss://radroots.org`, relay_polling_count_max: 10, }, delay: { @@ -46,4 +55,5 @@ export const cfg = { } } } -}; -\ No newline at end of file +}; + diff --git a/src/lib/utils/client.ts b/src/lib/utils/client.ts @@ -1,5 +1,5 @@ import { keystore } from "$lib/client"; -import { app_notify, type NavigationRoute } from "@radroots/svelte-lib"; +import { app_notify, route, type NavigationRoute } from "@radroots/svelte-lib"; export const keystore_reset = async (): Promise<void> => { try { @@ -11,14 +11,14 @@ export const keystore_reset = async (): Promise<void> => { } }; -export const restart = async (route_to: true | NavigationRoute, notify_message?: string): Promise<void> => { +export const restart = async (opts?: { + notify_message?: string; + route?: NavigationRoute; +}): Promise<void> => { try { - //await window.splash_show(); - if (notify_message) { - app_notify.set(notify_message); - } - //await route(typeof route_to === `string` ? route_to : `/`) - //location.reload(); + if (opts?.notify_message) app_notify.set(opts.notify_message); + if (opts?.route) await route(opts.route); + else location.reload(); } catch (e) { console.log(`(error) restart `, e); } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte @@ -1,14 +1,24 @@ <script lang="ts"> import { geoc } from "$lib/client"; - import { app_geoc } from "@radroots/svelte-lib"; + import { app_geoc, app_splash } from "@radroots/svelte-lib"; import { onMount } from "svelte"; onMount(async () => { try { const geoc_connected = await geoc.connect(); app_geoc.set(!!geoc_connected); + + /* + const nostr_publickey = await keystore.get(ks.keys.nostr_publickey); + if (`result` in nostr_publickey) { + await sleep(4000); + await notification.init(); + } + */ } catch (e) { + console.log(`e (app) onMount`, e); } finally { + app_splash.set(false); } }); </script> diff --git a/src/routes/(app)/+layout.ts b/src/routes/(app)/+layout.ts @@ -0,0 +1,38 @@ +import { keystore } from '$lib/client'; +import { ks } from '$lib/conf'; +import { app_cfg_type, app_nostr_key, app_splash, parse_cfg_type, route } from '@radroots/svelte-lib'; +import type { LayoutLoad, LayoutLoadEvent } from './$types'; + +export const load: LayoutLoad = async ({ url }: LayoutLoadEvent) => { + try { + console.log(`(load) (app) `, url.pathname) + const ks_nostr_publickey = await keystore.get( + ks.keys.nostr_publickey, + ); + if (`err` in ks_nostr_publickey) { + await route(`/cfg/init`); + return; + } + const ks_nostr_secretkey = await keystore.get( + ks.keys.nostr_secretkey(ks_nostr_publickey.result), + ); + if (`err` in ks_nostr_secretkey) { + await route(`/cfg/error`); + return; + } + const ks_pref_cfg_type = await keystore.get( + ks.pref.cfg_type + ); + //@todo handle err + if (`result` in ks_pref_cfg_type) { + app_cfg_type.set(parse_cfg_type(ks_pref_cfg_type.result)) + } + app_splash.set(false); + app_nostr_key.set(ks_nostr_publickey.result); + } catch (e) { + console.log(`(load) (conf) app ERROR`, e) + } finally { + //await win.splash_hide(); + return {}; + }; +}; diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte @@ -1,21 +1,69 @@ <script lang="ts"> - import { keyring, nostr } from "$lib/client"; + import { notification } from "$lib/client"; import { + app_cfg_type, + app_layout, app_nostr_key, - type CallbackPromise, EnvelopeLower, Glyph, - type GlyphKey, - type GlyphWeight, LayoutView, nav_prev, - type NavigationRoute, route, + t, Tabs, + type AppConfigType, + type CallbackPromise, + type GlyphKey, + type GlyphWeight, + type NavigationRoute, } from "@radroots/svelte-lib"; import { onMount } from "svelte"; + type PageParamButtons = { + route: NavigationRoute; + label: string; + key: GlyphKey; + weight?: GlyphWeight; + callback?: CallbackPromise; + }; + const page_param: { + buttons: Record<AppConfigType, PageParamButtons[]>; + } = { + buttons: { + personal: [ + { + route: `/`, + label: `Marketplace`, + key: `basket`, + }, + { + route: `/`, + label: `Forum`, + key: `basket`, + }, + ], + farmer: [ + { + route: `/`, + label: `Post New`, + key: `note-blank`, + }, + { + route: `/models/trade-product`, + label: `Farm Products`, + key: `basket`, + }, + { + route: `/`, + label: `Cooperatives`, + key: `users-three`, + }, + ], + }, + }; + let tmp_show_envelope = false; + let tmp_show_no_profile = false; onMount(async () => { try { @@ -24,133 +72,107 @@ } finally { } }); - - let buttons: { - route: NavigationRoute; - label: string; - key: GlyphKey; - weight?: GlyphWeight; - callback?: CallbackPromise; - }[] = [ - { - route: `/models/location-gcs`, - label: `Locations`, - key: `globe`, - }, - { - route: `/models/trade-product`, - label: `Products`, - key: `handbag-simple`, - weight: `fill`, - }, - { - route: `/models/nostr-profile`, - label: `Profiles`, - key: `address-book-tabs`, - weight: `fill`, - }, - { - route: `/`, - label: `Keys`, - key: `key`, - weight: `fill`, - callback: async () => { - nav_prev.set([ - ...$nav_prev, - { - route: `/`, - label: `Home`, - }, - ]); - }, - }, - { - route: `/models/nostr-relay`, - label: `Relays`, - key: `network`, - weight: `fill`, - callback: async () => { - nav_prev.set([ - ...$nav_prev, - { - route: `/`, - label: `Home`, - }, - ]); - }, - }, - ]; </script> -<LayoutView> - <div - class={`flex flex-col w-full justify-start items-start pt-6 gap-6 px-6`} - > - <div class={`flex flex-row w-full px-1 justify-start items-center`}> - <p - class={`font-mono font-[600] text-layer-2-glyph max-sm:text-lg text-xl tracking-wide`} - > - {`radroots app (beta-1.0.0)`} - </p> - </div> +<LayoutView basis={{ classes: `gap-4` }}> + <div class={`flex flex-row w-full justify-start items-center`}> <button - class={`flex flex-row w-full h-[34px] px-4 gap-2 justify-start items-center rounded-xl bg-layer-1-surface active:bg-layer-1-surface_a transition-all`} + class={`flex flex-row pt-4 px-6 gap-2 justify-center items-center`} on:click={async () => { - tmp_show_envelope = !tmp_show_envelope; + await notification.send(`hi`); }} > - <Glyph - basis={{ - key: `magnifying-glass`, - dim: `sm-`, - weight: `bold`, - classes: `text-layer-2-glyph/90`, - }} - /> - <p class={`font-sans font-[500] text-layer-2-glyph/80`}> - {`Search`} + <p + class={`font-mono font-[600] text-layer-2-glyph max-sm:text-lg text-xl tracking-wide`} + > + {`radroots (alpha-1.0.0)`} </p> </button> - <div - class={`grid grid-cols-12 flex flex-row w-full gap-4 gap-y-5 justify-start items-center`} - > - {#each buttons as btn} + </div> + <div class={`flex flex-col justify-center items-center`}> + {#if tmp_show_no_profile} + <button + class={`relative flex flex-col h-24 w-${$app_layout} p-4 justify-center items-center bg-layer-2-surface/60 rounded-touch touch-layer-1 touch-layer-1-raise-less el-re`} + on:click={async () => { + await route(`/models/nostr-profile`); + }} + > + <div class={`flex flex-row gap-2 justify-start items-center`}> + <Glyph + basis={{ + classes: `text-layer-2-glyph`, + key: `user-circle-plus`, + dim: `lg`, + weight: `bold`, + }} + /> + <p + class={`font-circ font-[500] text-lg text-layer-2-glyph/90`} + > + {`No farm profile added.`} + </p> + </div> + <div class={`flex flex-row justify-start items-center`}> + <p + class={`font-circ font-[500] text-sm text-layer-2-glyph/90`} + > + {`Click to add your details`} + </p> + </div> + <Glyph + basis={{ + classes: `absolute top-2 right-3 text-layer-2-glyph/40`, + key: `x`, + dim: `sm`, + weight: `bold`, + }} + /> + </button> + {/if} + </div> + <div class={`flex flex-col w-full gap-2 justify-start items-center`}> + <div class={`flex flex-row w-full px-8 justify-start items-center`}> + <p class={`font-circ font-[500] text-layer-0-glyph`}> + {`${$t(`common.options_list`)}`} + </p> + </div> + <div class={`flex flex-col w-full gap-4 justify-start items-center`}> + {#each page_param.buttons[$app_cfg_type] as btn} <button - class={`col-span-6 flex flex-col h-20 py-2 px-3 justify-between rounded-2xl bg-layer-1-surface text-layer-2-glyph font-[500] text-lg font-mono tap-rise active-ring-gray transition-all`} + class={`flex flex-row h-20 w-${$app_layout} py-2 px-6 justify-between items-center rounded-touch bg-layer-1-surface touch-layer-1 touch-layer-1-raise-less el-re`} on:click={async () => { if (btn.callback) await btn.callback(); await route(btn.route); }} > <div - class={`flex flex-row w-full p-[2px] justify-between items-center`} + class={`flex flex-row gap-4 justify-start items-center`} > + <Glyph + basis={{ + classes: `text-layer-2-glyph`, + key: btn.key, + dim: `md`, + weight: btn.weight || `bold`, + }} + /> <div class={`flex flex-row justify-start items-center`}> - <Glyph - basis={{ - key: btn.key, - dim: `md`, - weight: btn.weight || `bold`, - }} - /> - </div> - <div class={`flex flex-row justify-start items-center`}> - <Glyph - basis={{ - key: `caret-right`, - dim: `sm-`, - weight: `bold`, - classes: `text-layer-2-glyph`, - }} - /> + <p + class={`font-mono font-[500] text-lg text-layer-2-glyph`} + > + {btn.label} + </p> </div> </div> - <div - class={`flex flex-row w-full justify-start items-center`} - > - <div class={`flex flex-row justify-start items-center`}> - {btn.label} - </div> + <div class={`flex flex-row justify-start items-center`}> + <Glyph + basis={{ + key: `caret-right`, + dim: `sm-`, + weight: `bold`, + classes: `text-layer-2-glyph`, + }} + /> </div> </button> {/each} @@ -162,29 +184,29 @@ list: [ { icon: `house-line`, + label: `Home`, callback: async (tab_i) => { await route(`/`); }, }, { - icon: `compass`, - callback: async (tab_i) => { - // await route(`/map/choose-location`); - const new_sk = await nostr.lib.generate_key(); - console.log(`new_sk `, new_sk); - const res = keyring.set_nostr_key(new_sk); - console.log(`res `, res); - }, + icon: `arrows-down-up`, + label: `Transactions`, + callback: async (tab_i) => {}, }, { - icon: `network`, + icon: `cardholder`, + label: `Wallet`, callback: async (tab_i) => { await route(`/models/nostr-profile`); }, }, { - icon: `bell-simple`, - callback: async (tab_i) => {}, + icon: `squares-four`, + label: `Menu`, + callback: async (tab_i) => { + await route(`/settings`); + }, }, ], }} @@ -197,7 +219,7 @@ }, }} > - <div class={`flex flex-col w-full justify-center items-center px-2`}> + <div class={`flex flex-col h-full w-full justify-center items-center px-2`}> <p class={`font-apercu font-[400] text-layer-2-glyph break-all`}> {`Your nostr key is ${$app_nostr_key}`} </p> diff --git a/src/routes/(app)/models/nostr-profile/+page.svelte b/src/routes/(app)/models/nostr-profile/+page.svelte @@ -29,6 +29,10 @@ } }); + $: { + console.log(JSON.stringify(ld, null, 4), `ld`); + } + const load_data = async (): Promise<LoadData | undefined> => { try { const nostr_profiles = await db.nostr_profile_get({ @@ -37,10 +41,11 @@ if (`err` in nostr_profiles) { app_notify.set(`${$t(`error.client.page.load`)}`); return; - } else if (nostr_profiles.results.length < 1) { - app_notify.set(`${$t(`error.client.page.load`)}`); - return; } + const data: LoadData = { + nostr_profiles: nostr_profiles.results, + }; + return data; } catch (e) { console.log(`(error) load_data `, e); } diff --git a/src/routes/(app)/models/nostr-profile/view/+page.svelte b/src/routes/(app)/models/nostr-profile/view/+page.svelte @@ -58,7 +58,7 @@ } const ks_secret_key = await keystore.get( - ks.nostr.nostr_key($qp_nostr_pk), + ks.keys.nostr_secretkey($qp_nostr_pk), ); if (`err` in ks_secret_key) { app_notify.set(`Error loading profile`); diff --git a/src/routes/(app)/models/trade-product/+page.svelte b/src/routes/(app)/models/trade-product/+page.svelte @@ -6,11 +6,10 @@ LayoutArea, LayoutTrellis, LayoutView, - locale, Nav, route, t, - time_fmt_iso, + time_iso, Trellis, } from "@radroots/svelte-lib"; import { onMount } from "svelte"; @@ -98,8 +97,7 @@ ], right: [ { - value: time_fmt_iso( - $locale, + value: time_iso( li.created_at, ), }, @@ -177,7 +175,6 @@ </div> </LayoutArea> {/if} - <Nav basis={{ prev: { diff --git a/src/routes/(app)/models/trade-product/add/+page.svelte b/src/routes/(app)/models/trade-product/add/+page.svelte @@ -604,7 +604,7 @@ bind:this={el_trellis_wrap_price} id={fmt_id(`price_wrap`)} tabindex={-1} - class={`relative el-responsive flex flex-row w-full pl-2 justify-between items-center h-form_line bg-layer-1-surface rounded-2xl`} + class={`relative el-re flex flex-row w-full pl-2 justify-between items-center h-form_line bg-layer-1-surface rounded-2xl`} > <InputElement basis={{ @@ -706,7 +706,7 @@ <div id={fmt_id(`qty_wrap`)} tabindex={-1} - class={`relative el-responsive flex flex-row w-full gap-3 justify-between items-center h-form_line bg-layer-1-surface text-layer-1-glyph rounded-2xl`} + class={`relative el-re flex flex-row w-full gap-3 justify-between items-center h-form_line bg-layer-1-surface text-layer-1-glyph rounded-2xl`} > {#if show_sel_trade_product_qty_tup_other} <div diff --git a/src/routes/(app)/settings/+page.svelte b/src/routes/(app)/settings/+page.svelte @@ -4,7 +4,6 @@ import { app_notify, app_thc, - LayoutTrellis, LayoutView, Nav, t, @@ -14,368 +13,366 @@ </script> <LayoutView> - <LayoutTrellis> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Appearance`, - }, - list: [ - { - hide_active: true, - touch: { - label: { - left: [ - { - value: `Toggle Color Mode (${toggle_color_mode($app_thc)})`, - classes: `capitalize`, - }, - ], - }, - callback: async () => { - await haptics.impact(); - app_thc.set(toggle_color_mode($app_thc)); - }, + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Appearance`, + }, + list: [ + { + hide_active: true, + touch: { + label: { + left: [ + { + value: `Toggle Color Mode (${toggle_color_mode($app_thc)})`, + classes: `capitalize`, + }, + ], + }, + callback: async () => { + await haptics.impact(); + app_thc.set(toggle_color_mode($app_thc)); }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Nostr Keys`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Nostr Key (public)`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Nostr Keys`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Nostr Key (public)`, + classes: `capitalize`, }, + ], + }, + end: { + icon: { + key: `caret-right`, }, - callback: async () => { - const public_key = await keystore.get( - ks.nostr.nostr_key_active, - ); - if (`err` in public_key) return; - await dialog.alert( - `Hi! This is your nostr public key ${public_key.result}`, - ); - }, + }, + callback: async () => { + const public_key = await keystore.get( + ks.keys.nostr_publickey, + ); + if (`err` in public_key) return; + await dialog.alert( + `Hi! This is your nostr public key ${public_key.result}`, + ); }, }, - { - touch: { - label: { - left: [ - { - value: `Nostr Key (secret)`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + }, + { + touch: { + label: { + left: [ + { + value: `Nostr Key (secret)`, + classes: `capitalize`, }, + ], + }, + end: { + icon: { + key: `caret-right`, }, - callback: async () => { - const public_key = await keystore.get( - ks.nostr.nostr_key_active, - ); - if (`err` in public_key) return; - const secret_key = await keystore.get( - ks.nostr.nostr_key(public_key.result), - ); - if (`err` in secret_key) return; - await dialog.alert( - `Hi! This is your nostr secret key ${secret_key.result}`, - ); - }, + }, + callback: async () => { + const public_key = await keystore.get( + ks.keys.nostr_publickey, + ); + if (`err` in public_key) return; + const secret_key = await keystore.get( + ks.keys.nostr_secretkey(public_key.result), + ); + if (`err` in secret_key) return; + await dialog.alert( + `Hi! This is your nostr secret key ${secret_key.result}`, + ); }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Configuration`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Reset Device`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Configuration`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Reset Device`, + classes: `capitalize`, }, + ], + }, + end: { + icon: { + key: `caret-right`, }, - callback: async () => { - const confirm = await dialog.confirm( - `Hi! This will delete your saved keys.`, + }, + callback: async () => { + const confirm = await dialog.confirm( + `Hi! This will delete your saved keys.`, + ); + if (confirm) { + const ks_nostr_pk = await keystore.get( + ks.keys.nostr_publickey, ); - if (confirm) { - const ks_nostr_pk = await keystore.get( - ks.nostr.nostr_key_active, - ); - if (`err` in ks_nostr_pk) { - await dialog.alert( - `There was an error.`, - ); - return; - } - await keystore.remove( - ks.nostr.nostr_key( - ks_nostr_pk.result, - ), - ); - await keystore.remove( - ks.nostr.nostr_key_active, - ); - app_notify.set( - `Your device has been reset.`, + if (`err` in ks_nostr_pk) { + await dialog.alert( + `There was an error.`, ); + return; } - }, + await keystore.remove( + ks.keys.nostr_secretkey( + ks_nostr_pk.result, + ), + ); + await keystore.remove( + ks.keys.nostr_publickey, + ); + app_notify.set( + `Your device has been reset.`, + ); + } }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Location`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Geolocation Current`, - classes: `capitalize`, - }, - ], - }, - callback: async () => { - const pos = await geol.current(); - await dialog.alert(JSON.stringify(pos)); - }, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Location`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Geolocation Current`, + classes: `capitalize`, + }, + ], + }, + callback: async () => { + const pos = await geol.current(); + await dialog.alert(JSON.stringify(pos)); }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Web`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Radroots.Org (Open Homepage)`, - }, - ], - }, - callback: async () => { - //const url = `https://radroots.org`; - //await browser.open(url); - }, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Web`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Radroots.Org (Open Homepage)`, + }, + ], + }, + callback: async () => { + //const url = `https://radroots.org`; + //await browser.open(url); }, }, - { - touch: { - label: { - left: [ - { - value: `Radroots.Org (Share Homepage)`, - }, - ], - }, - callback: async () => { - //await share.open({ - // title: `Radroots Home Page`, - // text: `Find farmers around the world.`, - // url: `https://radroots.org`, - // dialog_title: `This is the dialog title`, - //}); - }, + }, + { + touch: { + label: { + left: [ + { + value: `Radroots.Org (Share Homepage)`, + }, + ], + }, + callback: async () => { + //await share.open({ + // title: `Radroots Home Page`, + // text: `Find farmers around the world.`, + // url: `https://radroots.org`, + // dialog_title: `This is the dialog title`, + //}); }, }, - { - touch: { - label: { - left: [ - { - value: `Primal.Net (Open Profile)`, - }, - ], - }, - callback: async () => { - //const public_key = await keystore.get( - // ks.nostr.nostr_key_active, - //); - //const npub = nostr.lib.npub(public_key); - //const url = `https://primal.net/p/${npub}`; - //await browser.open(url); - }, + }, + { + touch: { + label: { + left: [ + { + value: `Primal.Net (Open Profile)`, + }, + ], + }, + callback: async () => { + //const public_key = await keystore.get( + // ks.keys.nostr_publickey, + //); + //const npub = nostr.lib.npub(public_key); + //const url = `https://primal.net/p/${npub}`; + //await browser.open(url); }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Device`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Device (Info)`, - }, - ], - }, - callback: async () => { - //const data = await device.info(); - //await dialog.alert(JSON.stringify(data)); - }, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Device`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Device (Info)`, + }, + ], + }, + callback: async () => { + //const data = await device.info(); + //await dialog.alert(JSON.stringify(data)); }, }, - { - touch: { - label: { - left: [ - { - value: `Device (Battery)`, - }, - ], - }, - callback: async () => { - //const data = await device.battery(); - //await dialog.alert(JSON.stringify(data)); - }, + }, + { + touch: { + label: { + left: [ + { + value: `Device (Battery)`, + }, + ], + }, + callback: async () => { + //const data = await device.battery(); + //await dialog.alert(JSON.stringify(data)); }, }, - ], - }, - }} - /> - <Trellis - basis={{ - args: { - layer: 1, - title: { - value: `Tests`, }, - list: [ - { - touch: { - label: { - left: [ - { - value: `Haptics Touch (less)`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + ], + }, + }} + /> + <Trellis + basis={{ + args: { + layer: 1, + title: { + value: `Tests`, + }, + list: [ + { + touch: { + label: { + left: [ + { + value: `Haptics Touch (less)`, + classes: `capitalize`, }, - }, - callback: async () => { - await haptics.impact(`light`); + ], + }, + end: { + icon: { + key: `caret-right`, }, }, + callback: async () => { + await haptics.impact(`light`); + }, }, - { - touch: { - label: { - left: [ - { - value: `Haptics Touch (more)`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + }, + { + touch: { + label: { + left: [ + { + value: `Haptics Touch (more)`, + classes: `capitalize`, }, + ], + }, + end: { + icon: { + key: `caret-right`, }, - callback: async () => { - await haptics.impact(`heavy`); - }, + }, + callback: async () => { + await haptics.impact(`heavy`); }, }, - { - touch: { - label: { - left: [ - { - value: `Haptics Vibrate (500ms)`, - classes: `capitalize`, - }, - ], - }, - end: { - icon: { - key: `caret-right`, + }, + { + touch: { + label: { + left: [ + { + value: `Haptics Vibrate (500ms)`, + classes: `capitalize`, }, + ], + }, + end: { + icon: { + key: `caret-right`, }, - callback: async () => { - await haptics.vibrate(500); - }, + }, + callback: async () => { + await haptics.vibrate(500); }, }, - ], - }, - }} - /> - </LayoutTrellis> + }, + ], + }, + }} + /> </LayoutView> <Nav basis={{ diff --git a/src/routes/(app)/test/+page.svelte b/src/routes/(app)/test/+page.svelte @@ -1,9 +0,0 @@ -<script lang="ts"> - import { LayoutView } from "@radroots/svelte-lib"; -</script> - -<LayoutView> - <div class={`flex flex-col p-20 justify-start items-center`}> - <p class={`font-sans font-[400] text-layer-0-glyph`}>test</p> - </div> -</LayoutView> diff --git a/src/routes/(cfg)/cfg/+layout.ts b/src/routes/(cfg)/cfg/+layout.ts @@ -0,0 +1,29 @@ +import { keystore } from '$lib/client'; +import { ks } from '$lib/conf'; +import { route } from '@radroots/svelte-lib'; +import type { LayoutLoad, LayoutLoadEvent } from './$types'; + +export const load: LayoutLoad = async ({ url }: LayoutLoadEvent) => { + try { + await keystore.init(); + + console.log(`(load) (cfg) device `, url.pathname) + const ks_nostr_publickey = await keystore.get( + ks.keys.nostr_publickey, + ); + if (`result` in ks_nostr_publickey) { + const ks_nostr_secretkey = await keystore.get( + ks.keys.nostr_secretkey(ks_nostr_publickey.result), + ); + if (`result` in ks_nostr_secretkey) { + await route(`/`); + return; + } + } + } catch (e) { + console.log(`(load) (cfg) device ERROR`, e) + } finally { + //await win.splash_hide(); + return {}; + }; +}; diff --git a/src/routes/(cfg)/cfg/error/+page.svelte b/src/routes/(cfg)/cfg/error/+page.svelte @@ -0,0 +1,25 @@ +<script lang="ts"> + import { restart } from "$lib/utils/client"; + import { LayoutView } from "@radroots/svelte-lib"; +</script> + +<LayoutView> + <div class={`flex flex-col gap-2 justify-start items-center`}> + <p class={`font-sans font-[400] text-layer-0-glyph`}> + The device is improperly configured. + </p> + <button + class={`flex flex-row justify-center items-center`} + on:click={async () => { + await restart({ + route: `/`, + notify_message: `Device restarted`, + }); + }} + > + <p class={`font-sans font-[400] text-layer-0-glyph-hl`}> + Click to restart + </p> + </button> + </div> +</LayoutView> diff --git a/src/routes/(cfg)/cfg/init/+page.svelte b/src/routes/(cfg)/cfg/init/+page.svelte @@ -0,0 +1,1250 @@ +<script lang="ts"> + import { PUBLIC_RADROOTS_URL } from "$env/static/public"; + import { db, dialog, http, keystore, nostr } from "$lib/client"; + import { cfg, ks } from "$lib/conf"; + import { restart } from "$lib/utils/client"; + import type { IClientUnlisten } from "@radroots/client"; + import { + app_layout, + app_loading, + app_splash, + ButtonCarouselPair, + carousel_index, + carousel_index_max, + carousel_next, + carousel_prev, + DisplayLine, + EntryLine, + fmt_id, + Glyph, + InputElement, + kv, + LayoutView, + route, + sleep, + t, + view_effect, + } from "@radroots/svelte-lib"; + import { err_msg, regex, type ErrorMessage } from "@radroots/utils"; + import { onDestroy, onMount } from "svelte"; + + const page_param: { + kv: { + nostr_secretkey: string; + nostr_profilename: string; + }; + carousel: Record<View, { max_index: number }>; + } = { + kv: { + nostr_secretkey: `kv_secretkey`, + nostr_profilename: `kv_profilename`, + }, + carousel: { + cfg_init: { + max_index: 2, + }, + cfg_main: { + max_index: 2, + }, + eula: { + max_index: 1, + }, + }, + }; + + let el_eula: HTMLDivElement; + let el_eula_scrolled = false; + + type CfgInitKeyOption = `key_gen` | `kv_nostr_secretkey`; + let cgf_init_key_option: CfgInitKeyOption | undefined = undefined; + + type CfgMainOptionIndex1 = `farmer` | `personal`; + let cfg_main_opt_idx1: CfgMainOptionIndex1 | undefined = undefined; + let cfg_main_nostr_publickey = ``; + let cfg_main_nostr_publickey_npub = ``; + $: cfg_main_nostr_publickey_npub = cfg_main_nostr_publickey + ? nostr.lib.npub(cfg_main_nostr_publickey) || `` + : ``; + let cfg_main_profilename_valid = false; + let cfg_main_profilename_loading = false; + + type View = `cfg_init` | `cfg_main` | `eula`; + let initial_view: View = `cfg_init`; + let view: View = initial_view; + $: { + view_effect<View>(view); + } + + let unlisten_cfg_init_nostr_secretkey: IClientUnlisten | undefined = + undefined; + + onMount(async () => { + try { + handle_view(initial_view); + + const unlisten_1 = await keystore.on_key_change( + ks.cfg_init.nostr_secretkey, + async (_cfg_init_nostr_secretkey) => { + console.log( + `_cfg_init_nostr_secretkey `, + _cfg_init_nostr_secretkey, + ); + if (_cfg_init_nostr_secretkey) { + cfg_main_nostr_publickey = + nostr.lib.secretkey_to_publickey( + _cfg_init_nostr_secretkey, + ) || ``; + } else cfg_main_nostr_publickey = ``; + }, + ); + if (!(`err` in unlisten_1)) + unlisten_cfg_init_nostr_secretkey = unlisten_1; + el_eula?.addEventListener(`scroll`, () => { + const client_h = el_eula?.clientHeight; + const scroll_h = el_eula?.scrollHeight; + const scroll_top = el_eula?.scrollTop; + if (scroll_top + client_h >= scroll_h) el_eula_scrolled = true; + }); + await lookup_ks(); + } catch (e) { + console.log(`e mount`, e); + } finally { + app_splash.set(false); + } + }); + + onDestroy(async () => { + try { + await reset_kv(); + if (unlisten_cfg_init_nostr_secretkey) + unlisten_cfg_init_nostr_secretkey(); + el_eula?.removeEventListener(`scroll`, () => null); + } catch (e) { + console.log(`e destroy`, e); + } finally { + } + }); + + const reset_page = async (alert_message?: string): Promise<void> => { + try { + //@todo call reset_ks ? + app_loading.set(true); + handle_view(`cfg_init`); + if (alert_message) await dialog.alert(alert_message); + await sleep(cfg.delay.load); + } catch (e) { + console.log(`(error) reset `, e); + } finally { + app_loading.set(false); + } + }; + + const lookup_ks = async (): Promise<void> => { + try { + app_splash.set(false); + const ks_nostr_secretkey = await keystore.get( + ks.cfg_init.nostr_secretkey, + ); + const ks_radroots_tok = await keystore.get( + ks.cfg_init.radroots_tok, + ); + if (`result` in ks_nostr_secretkey) { + app_loading.set(true); + cfg_main_nostr_publickey; + if (`result` in ks_radroots_tok) { + cfg_main_nostr_publickey = nostr.lib.public_key( + ks_nostr_secretkey.result, + ); + const profile_status = await fetch_radroots_profile_status( + ks_radroots_tok.result, + ); + if (`active` in profile_status) { + if ( + profile_status.active.public_key !== + cfg_main_nostr_publickey + ) { + await keystore.remove(ks.cfg_init.nostr_secretkey); + await keystore.remove(ks.cfg_init.radroots_tok); + await keystore.remove( + ks.cfg_init.nostr_profilename, + ); + return; + } + const confirm = await dialog.confirm({ + message: `There is an existing configuration in progress${`nip_05` in profile_status.active ? ` for "${profile_status.active.nip_05}".` : `.`} ${`${$t(`common.do_you_want_to_continue_q`)}`}`, //@todo + cancel_label: `${$t(`common.reset`)}`, + ok_label: `${$t(`common.yes`)}`, + }); + + if (confirm === false) { + await keystore.remove(ks.cfg_init.nostr_secretkey); + await keystore.remove(ks.cfg_init.radroots_tok); + await keystore.remove( + ks.cfg_init.nostr_profilename, + ); + return; + } else { + if (`nip_05` in profile_status.active) { + await kv.set( + page_param.kv.nostr_profilename, + profile_status.active.nip_05, + ); + } + await kv.set( + page_param.kv.nostr_profilename, + profile_status.active.nip_05, + ); + handle_view(`eula`); + } + } + } else { + await keystore.remove(ks.cfg_init.nostr_secretkey); + } + } + } catch (e) { + console.log(`(error) lookup_ks `, e); + } finally { + app_loading.set(false); + } + }; + + const reset_ks = async (): Promise<void> => { + try { + const ks_entries = await keystore.entries(); + if (`results` in ks_entries) { + for (const [ks_key] of ks_entries.results.filter(([k]) => + k.startsWith(`cfg:init:`), + )) + await keystore.remove(ks_key); + } + } catch (e) { + console.log(`(error) reset_ks `, e); + } + }; + + const reset_kv = async (): Promise<void> => { + try { + for (const kv_key of Object.values(page_param.kv)) + await kv.delete(kv_key); + } catch (e) { + console.log(`(error) reset_ks `, e); + } + }; + + const handle_view = (new_view: View): void => { + if (new_view === `cfg_init` && view === `cfg_main`) { + const offset = cgf_init_key_option === `key_gen` ? 1 : 0; + carousel_index.set( + page_param.carousel[new_view].max_index - offset, + ); + } else { + carousel_index.set(0); + carousel_index_max.set(page_param.carousel[new_view].max_index); + } + view = new_view; + }; + + const handle_back = async (): Promise<void> => { + try { + switch (view) { + case `cfg_init`: + { + switch ($carousel_index) { + case 1: + { + cgf_init_key_option = undefined; + await carousel_prev(view); + } + break; + case 2: + { + await carousel_prev(view); + } + break; + } + } + break; + case `cfg_main`: { + switch ($carousel_index) { + case 0: + { + handle_view(`cfg_init`); + } + break; + case 1: + { + carousel_prev(view); + } + break; + } + } + } + } catch (e) { + console.log(`(error) handle_back `, e); + } + }; + + const handle_continue = async (): Promise<void> => { + try { + switch (view) { + case `cfg_init`: + { + switch ($carousel_index) { + case 0: + { + await carousel_next(view); + } + break; + case 1: + { + if ( + cgf_init_key_option === + `kv_nostr_secretkey` + ) { + await carousel_next(view); + return; + } + const ks_nostr_secretkey = + await keystore.get( + ks.cfg_init.nostr_secretkey, + ); + if (`result` in ks_nostr_secretkey) { + handle_view(`cfg_main`); + return; + } + await keystore.set( + ks.cfg_init.nostr_secretkey, + nostr.lib.generate_key(), + ); + handle_view(`cfg_main`); + } + break; + case 2: + { + const kv_nostr_secretkey = await kv.get( + fmt_id(page_param.kv.nostr_secretkey), + ); + if (!kv_nostr_secretkey) { + await dialog.alert( + `${$t(`icu.enter_a_*`, { value: `${$t(`icu.valid_*`, { value: `${$t(`common.key`)}` })}`.toLowerCase() })}`, + ); + return; + } + const nostr_secretkey_valid_nsec = + nostr.lib.nsec_decode( + kv_nostr_secretkey, + ); + const nostr_secretkey_valid_hex = + nostr.lib.public_key( + kv_nostr_secretkey, + ); + if ( + nostr_secretkey_valid_nsec || + nostr_secretkey_valid_hex + ) { + await keystore.set( + ks.cfg_init.nostr_secretkey, + kv_nostr_secretkey, + ); + handle_view(`cfg_main`); + return; + } + await dialog.alert( + `${$t(`icu.invalid_*`, { value: `${$t(`common.key`)}`.toLowerCase() })}`, + ); + } + break; + } + } + break; + case `cfg_main`: + { + switch ($carousel_index) { + case 0: + { + if (cfg_main_profilename_loading) return; + const ks_nostr_secretkey = + await keystore.get( + ks.cfg_init.nostr_secretkey, + ); + if (`err` in ks_nostr_secretkey) { + await reset_page( + `${$t(`error.device.configuration_failure`)}`, + ); + return; + } + const kv_profile_name = await kv.get( + fmt_id(page_param.kv.nostr_profilename), + ); + if (!kv_profile_name) { + await dialog.alert( + `${$t(`icu.enter_a_*`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })}`, + ); + return; + } + + cfg_main_profilename_loading = true; + const profilename_validated = + await fetch_radroots_profile_validate({ + profile_name: kv_profile_name, + }); + if (`err` in profilename_validated) { + cfg_main_profilename_loading = false; + await dialog.alert( + profilename_validated.err, + ); + return; + } + const confirm = await dialog.confirm({ + message: `${`${$t(`icu.the_*_is_available`, { value: `${$t(`common.profile_name`).toLowerCase()} "${profilename_validated.profile_name}"` })}`}. Would you like to use it?`, //@todo + cancel_label: `${$t(`common.no`)}`, + ok_label: `${$t(`common.yes`)}`, + }); + if (!confirm) { + cfg_main_profilename_loading = false; + return; + } + const profilename_added = + await fetch_radroots_profile_init({ + profile_name: + profilename_validated.profile_name, + secret_key: + ks_nostr_secretkey.result, + }); + cfg_main_profilename_loading = false; + if (`err` in profilename_added) { + await dialog.alert( + profilename_added.err, + ); + return; + } + await keystore.set( + ks.cfg_init.nostr_profilename, + profilename_validated.profile_name, + ); + await keystore.set( + ks.cfg_init.radroots_tok, + profilename_added.tok, + ); + + carousel_next(view); + } + break; + case 1: { + if (!cfg_main_opt_idx1) + cfg_main_opt_idx1 = `personal`; + await keystore.set( + ks.pref.cfg_type, + cfg_main_opt_idx1, + ); + handle_view(`eula`); + } + } + } + break; + } + } catch (e) { + console.log(`(error) handle_continue `, e); + } + }; + + const fetch_radroots_profile_validate = async (opts: { + profile_name: string; + }): Promise<{ profile_name: string } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/list`, + method: `post`, + }); + console.log(JSON.stringify(res, null, 4), `res`); + if (`err` in res) + return err_msg(`${$t(`error.client.network_failure`)}`); + else if (Array.isArray(res.data.results)) { + const existing_profile = res.data.results.find( + (i: any) => + `nip_05` in i && + String(i.nip_05).toLowerCase() === + opts.profile_name.toLowerCase(), + ); + if (existing_profile) + return err_msg( + `${`${$t(`icu.the_*_is_registered`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })} `}`, + ); + return { profile_name: opts.profile_name }; + } + + return err_msg(`${$t(`error.client.request_failure`)}`); + } catch (e) { + console.log(`(error) fetch_radroots_profile_validate `, e); + return err_msg(`${$t(`error.client.network_failure`)}`); + } + }; + + const fetch_radroots_profile_init = async (opts: { + profile_name: string; + secret_key: string; + nostr_relays?: string[]; + }): Promise<{ tok: string } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/init`, + method: `post`, + data: { + nip_05: opts.profile_name, + public_key: nostr.lib.public_key(opts.secret_key), + nostr_relays: opts.nostr_relays?.length + ? Array.from( + new Set([ + ...opts.nostr_relays, + cfg.nostr.relay_url, + ]), + ).join(`,`) + : [cfg.nostr.relay_url].join(`,`), + }, + }); + console.log(JSON.stringify(res, null, 4), `res`); + if (`err` in res) return res; + else if (res.data && `tok` in res.data) { + return { tok: res.data.tok }; + } else if (res.data && `message` in res.data) + return err_msg( + `${$t(`radroots-org.error.${res.data.message}`, { default: `${$t(`error.client.request_failure`)}` })}`, + ); + return err_msg(`${$t(`error.client.request_failure`)}`); + } catch (e) { + console.log(`(error) fetch_radroots_profile_init `, e); + return err_msg(`${$t(`error.client.network_failure`)}`); + } + }; + + const fetch_radroots_profile_confirm = async ( + authorization: string, + ): Promise<{ pass: true } | ErrorMessage<string>> => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/conf`, + method: `post`, + authorization, + }); + console.log(JSON.stringify(res, null, 4), `res`); + if (`err` in res) return res; + return { pass: true }; + } catch (e) { + console.log(`(error) fetch_radroots_profile_confirm `, e); + return err_msg(`${$t(`error.client.network_failure`)}`); + } + }; + + const fetch_radroots_profile_status = async ( + authorization: string, + ): Promise< + | { active: { public_key: string; nip_05?: string } } + | ErrorMessage<string> + > => { + try { + const res = await http.fetch({ + url: `${PUBLIC_RADROOTS_URL}/public/accounts/add/status`, + method: `post`, + authorization, + }); + console.log(JSON.stringify(res, null, 4), `res`); + if (`err` in res) return res; + else if ( + `public_key` in res.data && + typeof res.data.public_key === `string` + ) + return { + active: { + public_key: res.data.public_key, + nip_05: + `nip_05` in res.data && + typeof res.data.nip_05 === `string` + ? res.data.nip_05 + : undefined, + }, + }; + return err_msg(`${$t(`error.client.network_failure`)}`); + } catch (e) { + console.log(`(error) fetch_radroots_profile_confirm `, e); + return err_msg(`${$t(`error.client.network_failure`)}`); + } + }; + + const submit = async (): Promise<void> => { + try { + const ks_nostr_secretkey = await keystore.get( + ks.cfg_init.nostr_secretkey, + ); + console.log( + JSON.stringify(ks_nostr_secretkey, null, 4), + `ks_nostr_secretkey`, + ); + if (`err` in ks_nostr_secretkey) { + await dialog.alert( + `${$t(`error.device.configuration_failure`)}`, + ); + return; //@todo + } + const ks_radroots_tok = await keystore.get( + ks.cfg_init.radroots_tok, + ); + if (`result` in ks_radroots_tok) { + const profile_activated = await fetch_radroots_profile_confirm( + ks_radroots_tok.result, + ); + if (`err` in profile_activated) { + await dialog.alert( + `${$t(`icu.*_failure`, { value: `${$t(`common.activation`)}` })}`, + ); + return; //@todo + } + } + const ks_nostr_profilename = await keystore.get( + ks.cfg_init.nostr_profilename, + ); + const nostr_publickey = nostr.lib.public_key( + ks_nostr_secretkey.result, + ); + if (!nostr_publickey) { + await dialog.alert( + `${$t(`error.device.public_key_not_derived`)}`, + ); + return; //@todo + } + const nostr_profile_add = await db.nostr_profile_add({ + public_key: nostr_publickey, + name: + `result` in ks_nostr_profilename + ? ks_nostr_profilename.result + : undefined, + }); + console.log( + JSON.stringify(nostr_profile_add, null, 4), + `nostr_profile_add`, + ); + if (`id` in nostr_profile_add) { + await keystore.set(ks.keys.nostr_publickey, nostr_publickey); + await keystore.set( + ks.keys.nostr_secretkey(nostr_publickey), + ks_nostr_secretkey.result, + ); + await restart({ + route: `/`, + notify_message: `${$t(`app.page.cfg.init.notification.welcome`)}`, + }); + return; + } + await dialog.alert(`There was an error saving to the database.`); //@todo + } catch (e) { + console.log(`(error) submit `, e); + } + }; +</script> + +<LayoutView> + <div + data-view={`cfg_init`} + class={`flex flex-col h-full w-full max-mobile_base:pt-12 pt-16 justify-start items-center`} + > + <div + data-carousel-container={`cfg_init`} + class={`carousel-container flex h-full w-full`} + > + <div + data-carousel-item={`cfg_init`} + class={`carousel-item flex flex-col w-full max-mobile_y:pt-28 pt-32 pb-4 justify-start items-center`} + > + <div class={`flex flex-col gap-8 justify-start items-center`}> + <div + class={`flex flex-col gap-1 justify-start items-center`} + > + <button + class={`flex flex-row justify-center items-center`} + on:click={async () => { + await route(`/`); + }} + > + <p + class={`font-mono font-[700] text-layer-0-glyph text-4xl`} + > + {`${`${$t(`app.name`)}`}`} + </p> + </button> + <button + class={`flex flex-row justify-center items-center`} + on:click={async () => { + const sk = nostr.lib.generate_key(); + const res1 = await db.nostr_profile_add({ + public_key: nostr.lib.public_key(sk), + }); + console.log( + JSON.stringify(res1, null, 4), + `res1`, + ); + }} + > + <p + class={`font-mono font-[700] text-layer-0-glyph text-4xl`} + > + {`${`${$t(`common.setup`)}`}`} + </p> + </button> + </div> + <div + class={`grid grid-cols-12 flex flex-col gap-4 w-full justify-start items-center`} + > + {#each [`Configure your device`, `Choose a profile name`, `Terms of Use agreement`] as li, li_i} + <div + class={`col-span-12 flex flex-row justify-start items-center`} + > + <p + class={`font-mono font-[400] text-layer-0-glyph text-xl`} + > + {`${li_i + 1}. ${li}`} + </p> + </div> + {/each} + </div> + </div> + </div> + <button + data-carousel-item={`cfg_init`} + class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} + on:click={async () => { + cgf_init_key_option = undefined; + }} + > + <div + class={`flex flex-col w-full gap-10 justify-start items-center`} + > + <div + class={`flex flex-row w-full justify-center items-center`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph text-3xl`} + > + {`${$t(`icu.configure_*`, { value: `${$t(`common.device`)}` })}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-5 justify-center items-center`} + > + <button + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${cgf_init_key_option === `key_gen` ? `layer-1-ring` : ``}`} + on:click|stopPropagation={async () => { + cgf_init_key_option = `key_gen`; + }} + > + <p + class={`font-sans font-[600] text-layer-0-glyph text-xl`} + > + {`${$t(`icu.create_new_*`, { value: `${$t(`common.keypair`)}`.toLowerCase() })}`} + </p> + </button> + <button + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${cgf_init_key_option === `kv_nostr_secretkey` ? `layer-1-ring` : ``}`} + on:click|stopPropagation={async () => { + cgf_init_key_option = `kv_nostr_secretkey`; + }} + > + <p + class={`font-sans font-[600] text-layer-0-glyph text-xl`} + > + {`${$t(`icu.use_existing_*`, { + value: `${$t(`common.keypair`)}`.toLowerCase(), + })}`} + </p> + </button> + </div> + </div> + </button> + <div + data-carousel-item={`cfg_init`} + class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} + > + <div + class={`flex flex-col w-full gap-8 justify-start items-center`} + > + <div + class={`flex flex-col w-full gap-6 justify-center items-center`} + > + {#if cfg_main_nostr_publickey} + <p + class={`font-mono font-[600] text-layer-0-glyph text-3xl`} + > + {`Using Public Key`} + </p> + <DisplayLine + basis={{ + classes: `w-${$app_layout}`, + label: { + classes: `pl-4 font-mono text-lg text-start truncate`, + value: + cfg_main_nostr_publickey_npub || + cfg_main_nostr_publickey, + }, + style: `guide`, + }} + /> + {:else} + <p + class={`font-mono font-[600] text-layer-0-glyph text-3xl`} + > + {`${$t(`icu.add_existing_*`, { value: `${$t(`common.key`)}`.toLowerCase() })}`} + </p> + <InputElement + basis={{ + classes: `h-entry_guide w-${$app_layout} bg-layer-1-surface rounded-touch font-mono text-lg placeholder:opacity-60 items-end text-center`, + id: fmt_id(page_param.kv.nostr_secretkey), + sync: true, + sync_init: true, + placeholder: `${$t(`icu.enter_*`, { value: `nostr nsec/hex` })}`, + field: { + charset: regex.profile_name_ch, + validate: regex.profile_name, + validate_keypress: true, + }, + callback_keydown: async ({ key, el }) => { + if (key === `Enter`) { + el.blur(); + await handle_continue(); + } + }, + }} + /> + {/if} + </div> + </div> + </div> + </div> + <div + class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center`} + > + <ButtonCarouselPair + basis={{ + continue: { + disabled: $carousel_index === 1 && !cgf_init_key_option, + callback: async () => await handle_continue(), + }, + back: { + visible: $carousel_index > 0, + callback: async () => await handle_back(), + }, + }} + /> + </div> + </div> + <div + data-view={`cfg_main`} + class={`hidden flex flex-col h-full w-full max-mobile_base:pt-12 pt-16 justify-start items-center`} + > + <div + data-carousel-container={`cfg_main`} + class={`carousel-container flex h-full w-full`} + > + <div + data-carousel-item={`cfg_main`} + class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} + > + <div + class={`flex flex-col w-full gap-8 justify-start items-center`} + > + <div + class={`flex flex-col w-full gap-6 justify-center items-center`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph text-3xl`} + > + {`${$t(`icu.add_*`, { value: `${$t(`common.profile`)}` })}`} + </p> + <EntryLine + basis={{ + loading: cfg_main_profilename_loading, + classes: `w-${$app_layout}`, + style: `guide`, + el: { + classes: `font-mono text-lg text-center placeholder:opacity-60`, + id: fmt_id(page_param.kv.nostr_profilename), + sync: true, + sync_init: true, + placeholder: `${$t(`icu.enter_*`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })}`, + field: { + charset: regex.profile_name_ch, + validate: regex.profile_name, + validate_keypress: true, + }, + callback: async ({ pass }) => { + cfg_main_profilename_valid = pass; + }, + callback_keydown: async ({ key, el }) => { + if (key === `Enter`) { + el.blur(); + await handle_continue(); + } + }, + }, + }} + /> + </div> + </div> + </div> + <button + data-carousel-item={`cfg_main`} + class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} + on:click={async () => { + cfg_main_opt_idx1 = undefined; + }} + > + <div + class={`flex flex-col w-full gap-10 justify-start items-center`} + > + <div + class={`flex flex-row w-full justify-center items-center`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph text-3xl`} + > + {`${$t(`common.setup_for_farmer`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-5 justify-center items-center`} + > + <button + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${cfg_main_opt_idx1 === `farmer` ? `layer-1-ring` : ``}`} + on:click|stopPropagation={async () => { + cfg_main_opt_idx1 = `farmer`; + }} + > + <p + class={`font-sans font-[600] text-layer-0-glyph text-xl`} + > + {`${$t(`common.yes`)}`} + </p> + </button> + <button + class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${cfg_main_opt_idx1 === `personal` ? `layer-1-ring` : ``}`} + on:click|stopPropagation={async () => { + cfg_main_opt_idx1 = `personal`; + }} + > + <p + class={`font-sans font-[600] text-layer-0-glyph text-xl`} + > + {`${$t(`common.no`)}`} + </p> + </button> + </div> + </div> + </button> + </div> + <div + class={`absolute top-16 left-4 flex flex-row gap-2 justify-start items-center ${view === `cfg_main` ? `fade-in-long` : ``}`} + > + <button + class={`group flex flex-row justify-center items-center`} + on:click={async () => { + await handle_back(); + }} + > + <Glyph + basis={{ + classes: `text-layer-1-glyph-shade group-active:opacity-60 transition-all`, + key: `caret-left`, + dim: `lg`, + weight: `bold`, + }} + /> + <p + class={`font-sans font-[400] text-layer-0-glyph text-lg capitalize group-active:opacity-60 transition-all`} + > + {`${$t(`icu.go_*`, { value: `${$t(`common.back`)}` })}`} + </p> + </button> + </div> + <div + class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center ${view === `cfg_main` ? `fade-in-long` : ``}`} + > + <ButtonCarouselPair + basis={{ + continue: { + disabled: + ($carousel_index === 0 && + !cfg_main_profilename_valid) || + ($carousel_index === 1 && !cfg_main_opt_idx1), + callback: async () => await handle_continue(), + }, + back: { + visible: true, + label: + $carousel_index === 0 + ? `${$t(`common.skip`)}` + : `${$t(`common.back`)}`, + callback: async () => { + if ($carousel_index === 0) { + await dialog.alert( + `${$t(`app.page.cfg.init.notification.no_profile_name`)}`, + ); + carousel_next(view); + return; + } + await handle_back(); + }, + }, + }} + /> + </div> + </div> + <div + data-view={`eula`} + class={`hidden flex flex-col h-full w-full max-mobile_base:pt-12 pt-12 justify-start items-center`} + > + <div + data-carousel-container={`eula`} + class={`carousel-container flex h-full w-full rounded-2xl scroll-hide`} + > + <div + data-carousel-item={`eula`} + class={`carousel-item flex flex-col w-full max-mobile_base:pt-16 justify-start items-center`} + > + <div + class={`flex flex-col w-full px-4 pb-2 justify-start items-center ${view === `eula` ? `fade-in-long` : ``} overflow-hidden`} + > + <div + class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} + > + <div + class={`flex flex-row w-full justify-center items-center`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph text-2xl`} + > + {`${$t(`eula.title`)}`} + </p> + </div> + <div + bind:this={el_eula} + class={`flex flex-col h-[34rem] w-full gap-6 justify-start items-center overflow-y-scroll scroll-hide`} + > + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.introduction.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.introduction.body`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.prohibited_content.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} + > + {`${$t(`eula.prohibited_content.body_0_title`)}`} + </p> + <div + class={`flex flex-col w-full justify-start items-start`} + > + {#each [0, 1, 2, 3, 4, 5] as li} + <div + class={`flex flex-row w-full justify-start items-center`} + > + <div + class={`flex flex-row h-full w-8 justify-start items-start`} + > + <p + class={` font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} + > + {`*`} + </p> + </div> + <div + class={`flex flex-row h-full w-full justify-start items-start`} + > + <p + class={`col-span-10 font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} + > + {`${$t(`eula.prohibited_content.body_li_0_${li}`)}`} + </p> + </div> + </div> + {/each} + </div> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.prohibited_conduct.title`)}**`} + </p> + <div + class={`flex flex-col w-full justify-start items-start`} + > + {#each [0, 1, 2, 3] as li} + <div + class={`flex flex-row w-full justify-start items-center`} + > + <div + class={`flex flex-row h-full w-8 justify-start items-start`} + > + <p + class={` font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} + > + {`*`} + </p> + </div> + <div + class={`flex flex-row h-full w-full justify-start items-start`} + > + <p + class={`col-span-10 font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} + > + {`${$t(`eula.prohibited_conduct.body_li_0_${li}`)}`} + </p> + </div> + </div> + {/each} + </div> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.consequences_of_violation.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.consequences_of_violation.body`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.disclaimer.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.disclaimer.body`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.changes.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.changes.body`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.contact.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.contact.body`)}`} + </p> + </div> + <div + class={`flex flex-col w-full gap-2 justify-start items-start`} + > + <p + class={`font-mono font-[600] text-layer-0-glyph`} + > + {`**${$t(`eula.acceptance_of_terms.title`)}**`} + </p> + <p + class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} + > + {`${$t(`eula.acceptance_of_terms.body`)}`} + </p> + </div> + </div> + </div> + <div + class={`flex flex-row w-full pt-8 justify-center items-center`} + > + <button + class={`group flex flex-row basis-1/2 gap-4 justify-center items-center`} + on:click={async () => { + const confirm = await dialog.confirm({ + message: `${$t(`eula.error.required`)}`, + cancel_label: `${$t(`common.quit`)}`, + }); + if (confirm === false) location.reload(); //@todo + }} + > + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} + > + {`-`} + </p> + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} + > + {`${`${$t(`common.disagree`)}`}`} + </p> + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} + > + {`-`} + </p> + </button> + <button + class={`group flex flex-row basis-1/2 gap-4 justify-center items-center ${el_eula_scrolled ? `` : `opacity-40`}`} + on:click={async () => { + if (el_eula_scrolled) await submit(); + }} + > + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} + > + {`-`} + </p> + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} + > + {`${`${$t(`common.agree`)}`}`} + </p> + <p + class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} + > + {`- `} + </p> + </button> + </div> + </div> + </div> + </div> + </div> +</LayoutView> diff --git a/src/routes/(conf)/conf/error/+page.svelte b/src/routes/(conf)/conf/error/+page.svelte @@ -1,22 +0,0 @@ -<script lang="ts"> - import { restart } from "$lib/utils/client"; - import { LayoutView } from "@radroots/svelte-lib"; -</script> - -<LayoutView> - <div class={`flex flex-col gap-2 justify-start items-center`}> - <p class={`font-sans font-[400] text-layer-0-glyph`}> - The device is improperly configured. - </p> - <button - class={`flex flex-row justify-center items-center`} - on:click={async () => { - await restart(true); - }} - > - <p class={`font-sans font-[400] text-layer-0-glyph-hl`}> - Click to restart - </p> - </button> - </div> -</LayoutView> diff --git a/src/routes/(conf)/conf/init/+layout.ts b/src/routes/(conf)/conf/init/+layout.ts @@ -1,27 +0,0 @@ -import { keystore } from '$lib/client'; -import { ks } from '$lib/conf'; -import { app_nostr_key, route } from '@radroots/svelte-lib'; -import type { LayoutLoad, LayoutLoadEvent } from './$types'; - -export const load: LayoutLoad = async (_: LayoutLoadEvent) => { - try { - const nostr_publickey = await keystore.get( - ks.nostr.nostr_key_active, - ); - if (`result` in nostr_publickey) { - const nostr_secretkey = await keystore.get( - ks.nostr.nostr_key(nostr_publickey.result), - ); - if (`result` in nostr_secretkey) { - app_nostr_key.set(nostr_publickey.result); - await route(`/`); - return; - } - } - } catch (e) { - console.log(`(load) (conf) init`, e) - } finally { - //await win.splash_hide(); - return {}; - }; -}; diff --git a/src/routes/(conf)/conf/init/+page.svelte b/src/routes/(conf)/conf/init/+page.svelte @@ -1,975 +0,0 @@ -<script lang="ts"> - import { - PUBLIC_NOSTR_RELAY_DEFAULTS, - PUBLIC_RADROOTS_URL, - } from "$env/static/public"; - import { db, dialog, http, keystore, nostr } from "$lib/client"; - import ButtonAppearingPair from "$lib/components/button_appearing_pair.svelte"; - import { cfg, ks } from "$lib/conf"; - import { restart } from "$lib/utils/client"; - import { - app_layout, - carousel_index, - carousel_index_max, - carousel_next, - carousel_prev, - fmt_id, - Glyph, - InputElement, - kv, - LayoutView, - Loading, - sleep, - t, - view_effect, - } from "@radroots/svelte-lib"; - import { err_msg, regex, type ErrorMessage } from "@radroots/utils"; - import { onDestroy, onMount } from "svelte"; - - const carousel_param: Record<View, { max_index: number }> = { - setup: { - max_index: 2, - }, - profile_name: { - max_index: 2, - }, - eula: { - max_index: 1, - }, - }; - - let el_eula: HTMLDivElement; - let el_eula_scrolled = false; - - type KeypairOption = `nostr_key_gen` | `nostr_key_existing`; - let setup_keypair_option: KeypairOption | undefined = undefined; - - let profile_name_is_valid = false; - let profile_name_loading = false; - - type View = `setup` | `profile_name` | `eula`; - let initial_view: View = `profile_name`; - let view: View = initial_view; - $: { - view_effect<View>(view); - } - - onMount(async () => { - try { - handle_view(initial_view); - - await keystore.remove(ks.nostr.conf_init_key); - await keystore.remove(ks.nostr.conf_init_profile); - - el_eula.addEventListener("scroll", () => { - const client_h = el_eula.clientHeight; - const scroll_h = el_eula.scrollHeight; - const scroll_top = el_eula.scrollTop; - if (scroll_top + client_h >= scroll_h) el_eula_scrolled = true; - }); - } catch (e) { - } finally { - } - }); - - onDestroy(async () => { - try { - el_eula.removeEventListener("scroll", () => null); - } catch (e) { - } finally { - } - }); - - const handle_view = (new_view: View): void => { - if (new_view === `setup` && view === `profile_name`) { - const offset = setup_keypair_option === `nostr_key_gen` ? 1 : 0; - carousel_index.set(carousel_param[new_view].max_index - offset); - } else { - carousel_index.set(0); - } - carousel_index_max.set(carousel_param[new_view].max_index); - view = new_view; - }; - - const handle_back = async (): Promise<void> => { - try { - switch (view) { - case `setup`: - { - switch ($carousel_index) { - case 1: - { - setup_keypair_option = undefined; - await carousel_prev(view); - } - break; - case 2: - { - await carousel_prev(view); - } - break; - } - } - break; - case `profile_name`: { - switch ($carousel_index) { - case 0: - { - handle_view(`setup`); - } - break; - } - } - } - } catch (e) { - console.log(`(error) handle_back `, e); - } - }; - - const handle_continue = async (): Promise<void> => { - try { - switch (view) { - case `setup`: - { - switch ($carousel_index) { - case 0: - { - await carousel_next(view); - } - break; - case 1: - { - if ( - setup_keypair_option === `nostr_key_gen` - ) { - const nostr_sk_previous = - await keystore.get( - ks.nostr.conf_init_key, - ); - if (`result` in nostr_sk_previous) { - handle_view(`profile_name`); - return; - } - const nostr_sk = - nostr.lib.generate_key(); - const ks_set = await keystore.set( - ks.nostr.conf_init_key, - nostr_sk, - ); - if (`err` in ks_set) { - await dialog.alert( - `${$t(`error.client.unhandled`)}`, - ); - return; //@todo - } - handle_view(`profile_name`); - } else if ( - setup_keypair_option === - `nostr_key_existing` - ) - await carousel_next(view); - } - break; - case 2: - { - const nostr_key_existing = await kv.get( - fmt_id(`setup_nostr_key_existing`), - ); - if (!nostr_key_existing) { - await dialog.alert( - `${$t(`icu.enter_a_*`, { value: `${$t(`icu.valid_*`, { value: `${$t(`common.key`)}` })}`.toLowerCase() })}`, - ); - return; - } - const valid_nostr_key_nsec = - nostr.lib.nsec_decode( - nostr_key_existing, - ); - if (valid_nostr_key_nsec) { - const ks_set = await keystore.set( - ks.nostr.conf_init_key, - valid_nostr_key_nsec, - ); - if (`err` in ks_set) { - await dialog.alert( - `${$t(`error.client.unhandled`)}`, - ); - return; //@todo - } - handle_view(`eula`); - } - const valid_nostr_key_hex = - nostr.lib.public_key( - nostr_key_existing, - ); - if (valid_nostr_key_hex) { - const ks_set = await keystore.set( - ks.nostr.conf_init_key, - nostr_key_existing, - ); - if (`err` in ks_set) { - await dialog.alert( - `${$t(`error.client.unhandled`)}`, - ); - return; //@todo - } - handle_view(`eula`); - } - await dialog.alert( - `${$t(`icu.invalid_*`, { value: `${$t(`common.key`)}`.toLowerCase() })}`, - ); - } - break; - } - } - break; - case `profile_name`: - { - switch ($carousel_index) { - case 0: - { - if (profile_name_loading) return; - - const ks_init_secretkey = { - result: nostr.lib.generate_key(), - }; - /*const ks_init_secretkey = - await keystore.get( - ks.nostr.conf_init_key, - ); - if (`err` in ks_init_secretkey) { - await dialog.alert( - `To create a profile a keypair must be assigned`, - ); - return; //@todo - }*/ - - const kv_profile_name = await kv.get( - fmt_id(`profile_name`), - ); - if (!kv_profile_name) { - await dialog.alert( - `${$t(`icu.enter_a_*`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })}`, - ); - return; - } - const profile_name_validate = - await fetch_profile_name_validate({ - profile_name: kv_profile_name, - }); - if (`err` in profile_name_validate) { - await dialog.alert( - profile_name_validate.err, - ); - return; - } - const confirm = await dialog.confirm({ - message: `${`${$t(`icu.the_*_is_available`, { value: `${$t(`common.profile_name`).toLowerCase()} "${profile_name_validate.profile_name}"` })}`}. Would you like to use it?`, //@todo - cancel_label: `${$t(`common.no`)}`, - ok_label: `${$t(`common.yes`)}`, - }); - if (!confirm) return; - - const profile_name_create = - await fetch_profile_name_create({ - profile_name: - profile_name_validate.profile_name, - secret_key: - ks_init_secretkey.result, - }); - if (`err` in profile_name_create) { - await dialog.alert( - profile_name_create.err, - ); - return; - } - console.log( - JSON.stringify( - profile_name_create, - null, - 4, - ), - `profile_name_create`, - ); - } - break; - } - } - break; - } - } catch (e) { - console.log(`(error) handle_continue `, e); - } - }; - - const fetch_profile_name_validate = async (opts: { - profile_name: string; - }): Promise<{ profile_name: string } | ErrorMessage<string>> => { - try { - const profile_name = opts.profile_name.toLowerCase(); - profile_name_loading = true; - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/.well-known/nostr.json`, - }); - console.log(JSON.stringify(res, null, 4), `res`); - if (`err` in res) - return err_msg(`${$t(`error.client.network_failure`)}`); - else if (`names` in res.data) { - if (typeof res.data.names[profile_name] === `undefined`) - return { profile_name }; - return err_msg( - `${`${$t(`icu.the_*_is_registered`, { value: `${$t(`common.profile_name`)}`.toLowerCase() })} `}`, - ); - } - return err_msg(`${$t(`error.client.request_failure`)}`); - } catch (e) { - console.log(`(error) fetch_profile_name_validate `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } finally { - profile_name_loading = false; - } - }; - - const fetch_profile_name_create = async (opts: { - profile_name: string; - secret_key: string; - nostr_relays?: string[]; - }): Promise<{ id: string } | ErrorMessage<string>> => { - try { - profile_name_loading = true; - const res = await http.fetch({ - url: `${PUBLIC_RADROOTS_URL}/models/account/add`, - method: `post`, - data: { - active: true, - nip_05: opts.profile_name, - public_key: nostr.lib.public_key(opts.secret_key), - nostr_relays: opts.nostr_relays?.length - ? Array.from( - new Set([ - ...opts.nostr_relays, - `wss://radroots.org`, - ]), - ).join(`,`) - : [`wss://radroots.org`].join(`,`), - }, - }); - if (`err` in res) return res; - else if (`id` in res.data) { - alert(`account created ${res.data.id}`); - //@todo - } else if (`message` in res.data) - return err_msg( - `${$t(`radroots-org.error.${res.data.message}`, { default: `${$t(`error.client.request_failure`)}` })}`, - ); - return err_msg(`${$t(`error.client.request_failure`)}`); - } catch (e) { - console.log(`(error) fetch_profile_name_create `, e); - return err_msg(`${$t(`error.client.network_failure`)}`); - } finally { - profile_name_loading = false; - } - }; - - const configure_device = async (): Promise<void> => { - try { - const conf_init_secret_key = await keystore.get( - ks.nostr.conf_init_key, - ); - if (`err` in conf_init_secret_key) { - alert(`!conf_init_secret_key`); - return; //@todo - } - - const secret_key = conf_init_secret_key.result; - const public_key = nostr.lib.public_key(secret_key); - const ks_key_add = await keystore.set( - ks.nostr.nostr_key(public_key), - secret_key, - ); - if (!ks_key_add) { - alert(`!ks_key_add`); - return; //@todo - } - - const pref_key_add = await keystore.set( - ks.nostr.nostr_key_active, - public_key, - ); - if (!pref_key_add) { - alert(`!pref_key_add`); - return; //@todo - } - - let profile_name = ``; - const conf_init_profile = await keystore.get( - ks.nostr.conf_init_profile, - ); - if (`result` in conf_init_profile) { - profile_name = conf_init_profile.result; - } - - const nostr_profile_add = await db.nostr_profile_add({ - public_key, - name: profile_name ? profile_name : undefined, - }); - - if (`err` in nostr_profile_add) { - await dialog.alert(nostr_profile_add.err); - return; //@todo - } else if (`err_s` in nostr_profile_add) { - await dialog.alert(nostr_profile_add.err_s.join(` `)); - return; //@todo - } - - for (const url of PUBLIC_NOSTR_RELAY_DEFAULTS.split(",") || []) { - const nostr_relay_add = await db.nostr_relay_add({ url }); - if (`err` in nostr_relay_add) { - await dialog.alert(nostr_relay_add.err); - return; //@todo - } else if (`err_s` in nostr_relay_add) { - return; //@todo - } - await db.set_nostr_profile_relay({ - nostr_profile: { - id: nostr_profile_add.id, - }, - nostr_relay: { - id: nostr_relay_add.id, - }, - }); - } - - await keystore.remove(ks.nostr.conf_init_key); - await keystore.remove(ks.nostr.conf_init_profile); - - await sleep(cfg.delay.load); - await restart( - true, - `${$t(`app.page.conf.init.notification.welcome`)}`, - ); - } catch (e) { - console.log(`(error) configure_device `, e); - } - }; -</script> - -<LayoutView> - <div - data-view={`setup`} - class={`hidden flex flex-col h-full w-full max-mobile_base:pt-12 pt-16 justify-start items-center`} - > - <div - data-carousel-container={`setup`} - class={`carousel-container flex h-full w-full`} - > - <div - data-carousel-item={`setup`} - class={`carousel-item flex flex-col w-full max-mobile_y:pt-28 pt-32 pb-4 justify-start items-center`} - > - <div class={`flex flex-col gap-8 justify-start items-center`}> - <div - class={`flex flex-col gap-1 justify-start items-center`} - > - <p - class={`font-mono font-[700] text-layer-0-glyph text-4xl`} - > - {`${`${$t(`app.name`)}`}`} - </p> - <p - class={`font-mono font-[700] text-layer-0-glyph text-4xl`} - > - {`${`${$t(`common.setup`)}`}`} - </p> - </div> - <div - class={`grid grid-cols-12 flex flex-col gap-4 w-full justify-start items-center`} - > - {#each [`Configure your device`, `Choose a profile name`, `Terms of Use agreement`] as li, li_i} - <div - class={`col-span-12 flex flex-row justify-start items-center`} - > - <p - class={`font-mono font-[400] text-layer-0-glyph text-xl`} - > - {`${li_i + 1}. ${li}`} - </p> - </div> - {/each} - </div> - </div> - </div> - <button - data-carousel-item={`setup`} - class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} - on:click={async () => { - setup_keypair_option = undefined; - }} - > - <div - class={`flex flex-col w-full gap-10 justify-start items-center`} - > - <div - class={`flex flex-row w-full justify-center items-center`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph text-3xl`} - > - {`${$t(`icu.configure_*`, { value: `${$t(`common.device`)}` })}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-5 justify-center items-center`} - > - <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${setup_keypair_option === `nostr_key_gen` ? `layer-1-ring` : ``}`} - on:click|stopPropagation={async () => { - setup_keypair_option = `nostr_key_gen`; - }} - > - <p - class={`font-sans font-[600] text-layer-0-glyph text-xl`} - > - {`${$t(`icu.create_new_*`, { value: `${$t(`common.keypair`)}`.toLowerCase() })}`} - </p> - </button> - <button - class={`flex flex-col h-touch_bold w-${$app_layout} justify-center items-center bg-layer-1-surface rounded-3xl touch-layer-1 touch-layer-1-raise-less ${setup_keypair_option === `nostr_key_existing` ? `layer-1-ring` : ``}`} - on:click|stopPropagation={async () => { - setup_keypair_option = `nostr_key_existing`; - }} - > - <p - class={`font-sans font-[600] text-layer-0-glyph text-xl`} - > - {`${$t(`icu.use_existing_*`, { - value: `${$t(`common.keypair`)}`.toLowerCase(), - })}`} - </p> - </button> - </div> - </div> - </button> - <div - data-carousel-item={`setup`} - class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} - > - <div - class={`flex flex-col w-full gap-8 justify-start items-center ${view === `profile_name` ? `fade-in-long` : ``}`} - > - <div - class={`flex flex-col w-full gap-6 justify-center items-center`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph text-3xl`} - > - {`${$t(`icu.add_existing_*`, { value: `${$t(`common.key`)}`.toLowerCase() })}`} - </p> - <InputElement - basis={{ - classes: `h-entry_guide w-${$app_layout} bg-layer-1-surface rounded-touch font-mono text-lg placeholder:opacity-60 items-end text-center`, - id: fmt_id(`setup_nostr_key_existing`), - sync: true, - sync_init: true, - placeholder: `${$t(`icu.enter_*`, { value: `nostr nsec/hex` })}`, - field: { - charset: regex.profile_name_ch, - validate: regex.profile_name, - validate_keypress: true, - }, - callback_keydown: async ({ key, el }) => { - if (key === `Enter`) { - el.blur(); - await handle_continue(); - } - }, - }} - /> - </div> - </div> - </div> - </div> - <div - class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center ${view === `profile_name` ? `fade-in-long` : ``}`} - > - <ButtonAppearingPair - basis={{ - continue: { - disabled: - $carousel_index === 1 && !setup_keypair_option, - callback: async () => await handle_continue(), - }, - back: { - visible: $carousel_index > 0, - callback: async () => await handle_back(), - }, - }} - /> - </div> - </div> - <div - data-view={`profile_name`} - class={` flex flex-col h-full w-full max-mobile_base:pt-12 pt-16 justify-start items-center`} - > - <div - data-carousel-container={`profile_name`} - class={`carousel-container flex h-full w-full`} - > - <div - data-carousel-item={`profile_name`} - class={`carousel-item flex flex-col w-full max-mobile_y:pt-32 pt-36 pb-4 justify-start items-center`} - > - <div - class={`flex flex-col w-full gap-4 justify-start items-center ${view === `profile_name` ? `fade-in-long` : ``}`} - > - <div - class={`flex flex-col w-full gap-2 justify-center items-center`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph text-3xl capitalize`} - > - {`${$t(`icu.add_*`, { value: `${$t(`common.profile`)}` })}`} - </p> - </div> - <div - class={`flex flex-col h-[6.7rem] w-${$app_layout} pt-6 pb-6 px-8 justify-start items-center bg-layer-1-surface rounded-3xl`} - > - <div - class={`relative flex flex-row w-full justify-center items-center border-b-[2px] border-b-layer-1-surface-edge`} - > - <InputElement - basis={{ - classes: `font-mono text-[1.05rem] placeholder:text-layer-1-glyph/40 tracking-wider items-end text-center`, - id: fmt_id(`profile_name`), - sync: true, - sync_init: true, - placeholder: `${$t(`icu.name_your_*`, { value: `${$t(`common.profile`)}` })}`, - field: { - charset: regex.profile_name_ch, - validate: regex.profile_name, - validate_keypress: true, - }, - callback: async ({ pass }) => { - profile_name_is_valid = pass; - }, - callback_keydown: async ({ key, el }) => { - if (key === `Enter`) { - el.blur(); - await handle_continue(); - } - }, - }} - /> - {#if profile_name_loading} - <div - class={`absolute top-0 right-0 flex flex-row h-full pr-2 justify-center items-center`} - > - <Loading /> - </div> - {/if} - </div> - </div> - </div> - </div> - </div> - <div - class={`absolute top-16 left-4 flex flex-row gap-2 justify-start items-center ${view === `profile_name` ? `fade-in-long` : ``}`} - > - <button - class={`group flex flex-row justify-center items-center`} - on:click={async () => { - await handle_back(); - }} - > - <Glyph - basis={{ - classes: `text-layer-1-glyph-shade group-active:opacity-60 transition-all`, - key: `caret-left`, - dim: `lg`, - weight: `bold`, - }} - /> - <p - class={`font-sans font-[400] text-layer-0-glyph text-lg capitalize group-active:opacity-60 transition-all`} - > - {`${$t(`icu.go_*`, { value: `${$t(`common.back`)}` })}`} - </p> - </button> - </div> - <div - class={`absolute max-mobile_base:bottom-0 bottom-8 left-0 flex flex-col w-full justify-center items-center ${view === `profile_name` ? `fade-in-long` : ``}`} - > - <ButtonAppearingPair - basis={{ - continue: { - disabled: !profile_name_is_valid, - callback: async () => await handle_continue(), - }, - back: { - visible: true, - label: `${$t(`common.skip`)}`, - callback: async () => { - //@todo - await dialog.alert( - `${$t(`app.page.conf.init.notification.no_profile_name`)}`, - ); - handle_view(`eula`); - }, - }, - }} - /> - </div> - </div> - <div - data-view={`eula`} - class={`hidden flex flex-col h-full w-full max-mobile_base:pt-12 pt-12 justify-start items-center`} - > - <div - data-carousel-container={`eula`} - class={`carousel-container flex h-full w-full rounded-2xl scroll-hide`} - > - <div - data-carousel-item={`eula`} - class={`carousel-item flex flex-col w-full max-mobile_base:pt-16 justify-start items-center`} - > - <div - class={`flex flex-col w-full px-4 pb-8 justify-start items-center ${view === `eula` ? `fade-in-long` : ``}`} - > - <div - class={`flex flex-col w-full px-4 gap-4 justify-start items-center`} - > - <div - class={`flex flex-row w-full justify-center items-center`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph text-2xl`} - > - {`${$t(`eula.title`)}`} - </p> - </div> - <div - bind:this={el_eula} - class={`flex flex-col h-[34rem] w-full gap-6 justify-start items-center overflow-y-scroll scroll-hide`} - > - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.introduction.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.introduction.body`)}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.prohibited_content.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} - > - {`${$t(`eula.prohibited_content.body_0_title`)}`} - </p> - <div - class={`flex flex-col w-full justify-start items-start`} - > - {#each [0, 1, 2, 3, 4, 5] as li} - <div - class={`flex flex-row w-full justify-start items-center`} - > - <div - class={`flex flex-row h-full w-8 justify-start items-start`} - > - <p - class={` font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} - > - {`*`} - </p> - </div> - <div - class={`flex flex-row h-full w-full justify-start items-start`} - > - <p - class={`col-span-10 font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} - > - {`${$t(`eula.prohibited_content.body_li_0_${li}`)}`} - </p> - </div> - </div> - {/each} - </div> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.prohibited_conduct.title`)}**`} - </p> - <div - class={`flex flex-col w-full justify-start items-start`} - > - {#each [0, 1, 2, 3] as li} - <div - class={`flex flex-row w-full justify-start items-center`} - > - <div - class={`flex flex-row h-full w-8 justify-start items-start`} - > - <p - class={` font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} - > - {`*`} - </p> - </div> - <div - class={`flex flex-row h-full w-full justify-start items-start`} - > - <p - class={`col-span-10 font-mono font-[500] text-sm text-layer-0-glyph text-justify break-word`} - > - {`${$t(`eula.prohibited_conduct.body_li_0_${li}`)}`} - </p> - </div> - </div> - {/each} - </div> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.consequences_of_violation.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.consequences_of_violation.body`)}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.disclaimer.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.disclaimer.body`)}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.changes.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.changes.body`)}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.contact.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.contact.body`)}`} - </p> - </div> - <div - class={`flex flex-col w-full gap-2 justify-start items-start`} - > - <p - class={`font-mono font-[600] text-layer-0-glyph`} - > - {`**${$t(`eula.acceptance_of_terms.title`)}**`} - </p> - <p - class={`font-mono font-[500] text-layer-0-glyph text-sm text-justify break-word`} - > - {`${$t(`eula.acceptance_of_terms.body`)}`} - </p> - </div> - </div> - </div> - <div - class={`flex flex-row h-20 w-full justify-center items-center`} - > - <button - class={`group flex flex-row basis-1/2 gap-4 justify-center items-center`} - on:click={async () => { - const confirm = await dialog.confirm({ - message: `${$t(`eula.error.required`)}`, - cancel_label: `${$t(`common.quit`)}`, - }); - if (confirm === false) location.reload(); //@todo - }} - > - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} - > - {`-`} - </p> - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} - > - {`${`${$t(`common.disagree`)}`}`} - </p> - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph/60 group-active:text-layer-0-glyph transition-all`} - > - {`-`} - </p> - </button> - <button - class={`group flex flex-row basis-1/2 gap-4 justify-center items-center ${el_eula_scrolled ? `` : `opacity-40`}`} - on:click={async () => { - if (el_eula_scrolled) await configure_device(); - }} - > - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} - > - {`-`} - </p> - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} - > - {`${`${$t(`common.agree`)}`}`} - </p> - <p - class={`font-mono font-[400] text-sm text-layer-0-glyph-hl group-active:text-layer-0-glyph-hl/80 transition-all`} - > - {`- `} - </p> - </button> - </div> - </div> - </div> - </div> - </div> -</LayoutView> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte @@ -1,14 +1,14 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import { dialog, keystore, notification } from "$lib/client"; - import { cfg, ks } from "$lib/conf"; + import { device, dialog, http, os } from "$lib/client"; + import { cfg } from "$lib/conf"; + import type { IClientDeviceMetadata } from "@radroots/client"; import { - app_config, + app_cfg_type, app_db, app_geoc, app_loading, - app_nostr_key, app_notify, + app_splash, app_th, app_thc, AppControls, @@ -16,20 +16,39 @@ CssStyles, LayoutWindow, LoadingView, + locale, route, sleep, + SplashScreen, theme_set, type NavigationRoute, } from "@radroots/svelte-lib"; import { parse_color_mode, parse_theme_key } from "@radroots/theme"; - import { onMount } from "svelte"; + import { onDestroy, onMount } from "svelte"; import "../app.css"; + //let unlisten_logger: IClientUnlisten; let route_render: NavigationRoute | undefined = undefined; onMount(async () => { try { - app_loading.set(true); + //unlisten_logger = await logger.init(); + const metadata: IClientDeviceMetadata = { + version: os.version(), + platform: os.platform(), + locale: $locale, + }; + await device.init(metadata); + await http.init(metadata); + } catch (e) { + } finally { + } + }); + + onDestroy(async () => { + try { + //unlisten_logger(); + route_render = undefined; } catch (e) { } finally { } @@ -57,47 +76,14 @@ console.log(`(app_geoc) success`); }); - app_config.subscribe(async (_app_config) => { - try { - if (!_app_config) { - console.log(`(app_config) done`); - return; - } - console.log(`(app_config) start`); - - await keystore.init(); - await notification.init(); - - const ks_public_key = await keystore.get(ks.nostr.nostr_key_active); - if (`result` in ks_public_key) { - const ks_secret_key = await keystore.get( - ks.nostr.nostr_key(ks_public_key.result), - ); - if (`result` in ks_secret_key) { - app_nostr_key.set(ks_public_key.result); - } - } else { - route_render = "/conf/init"; - } - await goto(`/`); - if (route_render) { - await sleep(cfg.delay.load); - await route(route_render); - } - } catch (e) { - console.log(`(app_config) error `, e); - } finally { - app_loading.set(false); - //await win.splash_hide(); - } + app_cfg_type.subscribe(async (_app_cfg_type) => { + console.log(`_app_cfg_type `, _app_cfg_type); }); app_notify.subscribe(async (_app_notify) => { if (!_app_notify) { - app_loading.set(false); return; } - app_loading.set(true); route(`/`); await sleep(cfg.delay.notify); dialog.alert(_app_notify); @@ -112,11 +98,12 @@ </svelte:head> <LayoutWindow> - {#if !$app_loading} - <slot /> - {:else} + {#if $app_splash} + <SplashScreen /> + {:else if $app_loading} <LoadingView /> {/if} + <slot /> </LayoutWindow> <AppControls /> <CssStatic /> diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts @@ -13,7 +13,9 @@ export const load: LayoutLoad = async ({ url }: LayoutLoadEvent) => { else if (locales.get().some(i => i === nav_locale.slice(0, 2).toLowerCase())) locale = nav_locale.slice(0, 2); await load_translations(locale.toLowerCase(), url.pathname); await translations_loading.toPromise(); - } catch (e) { } finally { + } catch (e) { + console.log(`(load) ERROR`, e) + } finally { return {}; }; }; diff --git a/tailwind.config.ts b/tailwind.config.ts @@ -30,6 +30,7 @@ const heights = { envelope_top: "56px", toast_min: `56px`, envelope_button: `50px`, + line_label: `1.75rem` }; @@ -71,6 +72,8 @@ const config: Config = { extend: { colors: { ...theme_colors, + 'chart-green': 'var(--chart-color-green)', + 'chart-red': 'var(--chart-color-red)', }, fontFamily: { sans: ['SF Pro Display', ...tw_font.sans], @@ -79,8 +82,12 @@ const config: Config = { apercu: ['Apercu Mono Pro'], magda: [`Magda Text`], lust: [`Lust`], + circ: [`Circular`], + arch: [`Archivo`], + spaceg: [`Space Grotesk`] }, fontSize: { + line_label: [`1.3rem`, { lineHeight: `1.3rem` }], trellisTitle: ["0.8rem", { lineHeight: "1rem", fontWeight: 200 }], trellisTitleNote: ["0.76rem", { lineHeight: "1rem", fontWeight: 200 }], line_display: ["1.05rem", { lineHeight: "1.33rem", fontWeight: 300 }], @@ -140,6 +147,8 @@ const config: Config = { ], daisyui: { themes: [ + themes.theme_garden_light, + themes.theme_garden_dark, themes.theme_earth_light, themes.theme_earth_dark, themes.theme_os_dark,