Compare commits

..

61 Commits

Author SHA1 Message Date
41d2854339 Set up nested routes for dealing with the design page
This just makes the design a lot easier to work with
2025-04-23 22:33:35 -04:00
1a874f5cbb cyber-slides, dashboard, and otg-gtk should not be published 2025-04-23 10:02:26 -04:00
4889b0598c Add button styles. Improve some CSS naming conventions 2025-04-23 09:34:40 -04:00
8790df22f3 Set up the new user view 2025-04-22 21:37:57 -04:00
e1b74f2a21 Add a list selector component and a page selector for the design view 2025-04-20 16:53:31 -04:00
68fd5b7201 Start on the new user interface 2025-04-20 15:22:45 -04:00
d8f7df520b Further encapsulate the state management 2025-04-14 09:41:23 -04:00
7ad03c6be2 Wrap the application state in a state provider 2025-04-14 09:23:26 -04:00
20831f48fc Try a dark theme 2025-04-14 00:44:21 -04:00
de815337e7 Thread callbacks through the Login and user management interfaces 2025-04-14 00:34:48 -04:00
e5ffa35344 Establish a UI application state 2025-04-14 00:17:04 -04:00
0234a880cd Work on the user list page 2025-04-13 00:05:09 -04:00
e5a0c85e18 Update the Login view to use the styled components 2025-04-12 22:31:43 -04:00
d106cbe1d2 Work on the TextEntry component 2025-04-09 00:46:22 -04:00
9ae2325cbe Create an initial design system for the app 2025-04-09 00:02:58 -04:00
cb824cf00d Remove the lint command
Can't quite make it work right now. It will have to come later.
2025-04-08 22:08:58 -04:00
365dfab1f1 Try changing the calling convention 2025-04-08 22:06:15 -04:00
60fa2a4016 Set up a linting task 2025-04-08 22:05:00 -04:00
6a51ca7991 Bump to Rust 1.86
This fixes the weird glib build problems that I was running into
2025-04-08 21:54:50 -04:00
21bcece361 Comment out many projects and ensure a fundamental subset are working 2025-03-31 11:18:30 -04:00
a4258436a6 Merge the rebuilt visions application
Reviewed-on: 
2025-03-28 13:11:03 +00:00
56fe82c1e9 Rename the action 2025-03-28 09:08:59 -04:00
b2ef8b6da9 Merge branch 'main' into visions-ui-framework 2025-03-28 08:58:41 -04:00
452602b140 Tweak the runner target to something more descriptive 2025-03-28 08:54:58 -04:00
b8b7844ec2 Enable flakes as well 2025-03-28 00:06:37 -04:00
b88ca9e36f Try enabling the nix-command feature 2025-03-28 00:04:29 -04:00
76dea5592e Set the working directory 2025-03-28 00:02:49 -04:00
76b2a610f9 actions diagnostics 2025-03-27 23:59:40 -04:00
95d40800f4 Set up a nix build job 2025-03-27 23:52:37 -04:00
45cde32ff2 Change runner label to native 2025-03-27 23:47:16 -04:00
0e45e22cac Change the name of the target runner 2025-03-27 23:37:39 -04:00
9dd8b2716e Fix the workflow 2025-03-27 23:22:02 -04:00
187b536768 Add the actions demo 2025-03-27 23:18:00 -04:00
ee3fccba88 Merge branch 'main' into visions-ui-framework 2025-03-27 21:36:07 -04:00
43aecf485a Extract the login page into another file 2025-03-27 19:24:38 -04:00
6969bc659b Now set up a landing page of sorts 2025-03-26 23:30:18 -04:00
11e33eca2f Set up a shared types library 2025-02-20 09:45:53 -05:00
fd3ca9f561 Set up the most basic of authentication clients 2025-02-20 07:39:35 -05:00
e8a8a12de3 Start capturing input 2025-02-19 22:39:42 -05:00
7f0b7982ec Switch from println to log from gloo-console 2025-02-18 23:23:46 -05:00
5e4fd97aca Set up some callbacks to handle the login page state 2025-02-18 23:18:23 -05:00
1c4894df9a Start on the client module 2025-02-18 21:36:01 -05:00
20b214df10 Start adding some concepts around UI state 2025-02-18 08:25:40 -05:00
ca89455d4d Set up a Yew login page 2025-02-17 23:03:12 -05:00
2ff981e28a Nuke another speculative UI 2025-02-17 21:51:14 -05:00
672578b9a9 Add a micro-prototype Yew application 2025-02-17 18:28:18 -05:00
fb2fcf4d36 Abortive attempt to set up a trivial web application 2025-02-17 16:19:33 -05:00
a1dc573fc5 Adjust all build processes 2025-02-17 15:44:01 -05:00
0d39690560 Start rebuilding the typescript config, this time for web components 2025-02-17 15:17:30 -05:00
9439cfea34 Purge the Vite/React application 2025-02-17 09:34:26 -05:00
1d050f014a Set up rudimentary state, App, and a test for the App 2025-02-17 08:48:46 -05:00
df1dfeaae3 Set up dependencies 2025-02-16 20:50:50 -05:00
8ab8cd0684 Set up a package for just the types 2025-02-16 20:22:20 -05:00
aa7229eae4 Rename VResponse to AuthResponse and use it only in Authentication 2025-02-16 19:41:05 -05:00
0663a70c97 Force the password-reset state to Unauthorized on most auth-required routes 2025-02-16 15:54:59 -05:00
41bb21c254 Set up client tests
These are definitely temporary tests, as testing the lcient API will become more difficult and require more infrastructure as real data starts entering the system.
2025-02-16 14:12:17 -05:00
182020e136 Create a typescript client library for the server 2025-02-14 09:53:08 -05:00
79af050f53 Make a sample auth endpoint 2025-02-14 09:26:04 -05:00
dca9c3c39e Set up automated testing 2025-02-13 22:58:04 -05:00
e9f89e1bdb Create a tiny server for testing the Fetch API 2025-02-13 22:41:17 -05:00
f6534d5d05 Switch to vite instead of typescript 2025-02-13 19:01:21 -05:00
57 changed files with 5007 additions and 15499 deletions

View File

@ -0,0 +1,31 @@
name: Monorepo build
run-name: ${{ gitea.actor }} is testing out Gitea Actions
on: [push]
jobs:
# Explore-Gitea-Actions:
# runs-on: native
# steps:
# - run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
# - run: echo "This job is now running on ${{ runner.os }} server hosted by Gitea!"
# - run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
# - name: Check out repository code
# uses: actions/checkout@v4
# - run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
# - run: echo "The workflow is now ready to test your code on the runner."
# - name: List files in the repository
# run: |
# ls ${{ gitea.workspace }}
# - run: echo "This job's status is ${{ job.status }}."
build-flake:
runs-on: nixos
defaults.run.working-directory: ${{ gitea.workspace }}
steps:
- name: Checkout repository code
uses: actions/checkout@v4
- name: Build the apps
run: /run/current-system/sw/bin/nix --extra-experimental-features "nix-command flakes" build .#all
- name: Check the end of the build
run: ls ${{ gitea.workspace }}/result/bin

4652
Cargo.lock generated

File diff suppressed because it is too large Load Diff

12707
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,75 @@
[workspace]
resolver = "2"
members = [
"authdb",
# "bike-lights/bike",
"bike-lights/core",
"bike-lights/simulator",
"changeset",
"config",
"config-derive",
"coordinates",
"cyber-slides",
# "authdb",
# "bike-lights/core",
# "bike-lights/simulator",
# "changeset",
# "config",
# "config-derive",
# "coordinates",
# "cyber-slides",
"cyberpunk",
"cyberpunk-splash",
"dashboard",
"emseries",
"file-service",
# "dashboard",
# "emseries",
# "file-service",
"fitnesstrax/app",
"fitnesstrax/core",
"fluent-ergonomics",
"geo-types",
"gm-control-panel",
"gm-dash/server",
"hex-grid",
"icon-test",
# "fitnesstrax/core",
# "fluent-ergonomics",
# "geo-types",
# "gm-control-panel",
# "gm-dash/server",
# "hex-grid",
# "icon-test",
"l10n-db",
"memorycache",
"nom-training",
"otg/core",
"otg/gtk",
"pico-st7789",
"result-extended",
"screenplay",
"sgf",
"timezone-testing",
"tree",
# "memorycache",
# "nom-training",
# "otg/core",
# "otg/gtk",
# "pico-st7789",
# "result-extended",
# "screenplay",
# "sgf",
# "timezone-testing",
# "tree",
"visions/server",
# "visions/types",
"visions/ui",
# "bike-lights/bike",
]
[workspace.dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_4" ] }
async-channel = { version = "2.1" }
async-std = { version = "1.13" }
async-trait = { version = "0.1" }
axum = { version = "0.8", features = ["macros"] }
cairo-rs = { version = "0.18" }
chrono = { version = "0.4" }
chrono-tz = { version = "0.8" }
dimensioned = { version = "0.8", features = [ "serde" ] }
gdk = { version = "0.7", package = "gdk4" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gloo-console = { version = "0.3.0" }
gloo-net = { version = "0.6.0" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_10" ] }
serde = { version = "1.0", features = ["derive", "serde_derive"] }
serde-wasm-bindgen = { version = "0.6.5" }
serde_json = { version = "1.0.138" }
thiserror = { version = "2.0" }
tokio = { version = "1.43", features = ["full", "rt"] }
tower-http = { version = "0.6", features = ["cors"] }
uuid = { version = "1.13", features = ["v4"] }
wasm-bindgen = { version = "0.2.100" }
wasm-bindgen-futures = { version = "0.4.50" }
web-sys = { version = "0.3.77" }
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
yew-router = { git = "https://github.com/yewstack/yew/" }
# cairo-rs = { version = "0.18" }
# gio = { version = "0.18" }
# glib = { version = "0.18" }
# gtk = { version = "0.7", package = "gtk4" }

View File

@ -13,4 +13,8 @@ tasks:
lint:
cmds:
- cargo watch -x clippy
- cargo clippy
test:
cmds:
- cargo test

View File

@ -1,519 +1,4 @@
{
"registry+https://github.com/rust-lang/crates.io-index#addr2line@0.24.2": "1hd1i57zxgz08j6h5qrhsnm2fi0bcqvsh389fw400xm3arz2ggnz",
"registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.0": "09r6drylvgy8vv8k20lnbvwq8gp09h7smfn6h1rxsy15pgh629si",
"registry+https://github.com/rust-lang/crates.io-index#adler32@1.2.0": "0d7jq7jsjyhsgbhnfq5fvrlh9j0i9g1fqrl2735ibv5f75yjgqda",
"registry+https://github.com/rust-lang/crates.io-index#adler@1.0.2": "1zim79cvzd5yrkzl3nyfx0avijwgk9fqv3yrscdy1cc79ih02qpj",
"registry+https://github.com/rust-lang/crates.io-index#aho-corasick@1.1.3": "05mrpkvdgp5d20y2p989f187ry9diliijgwrs254fs9s1m1x6q4f",
"registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21": "08zrzs022xwndihvzdn78yqarv2b9696y67i6h78nla3ww87jgb8",
"registry+https://github.com/rust-lang/crates.io-index#android-tzdata@0.1.1": "1w7ynjxrfs97xg3qlcdns4kgfpwcdv824g611fq32cag4cdr96g9",
"registry+https://github.com/rust-lang/crates.io-index#android_system_properties@0.1.5": "04b3wrz12837j7mdczqd95b732gw5q7q66cv4yn4646lvccp57l1",
"registry+https://github.com/rust-lang/crates.io-index#annotate-snippets@0.9.2": "07p8r6jzb7nqydq0kr5pllckqcdxlyld2g275v425axnzffpxbyc",
"registry+https://github.com/rust-lang/crates.io-index#anstream@0.6.18": "16sjk4x3ns2c3ya1x28a44kh6p47c7vhk27251i015hik1lm7k4a",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-parse@0.2.6": "1acqayy22fwzsrvr6n0lz6a4zvjjcvgr5sm941m7m0b2fr81cb9v",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-query@1.1.2": "036nm3lkyk43xbps1yql3583fp4hg3b1600is7mcyxs1gzrpm53r",
"registry+https://github.com/rust-lang/crates.io-index#anstyle-wincon@3.0.7": "0kmf0fq4c8yribdpdpylzz1zccpy84hizmcsac3wrac1f7kk8dfa",
"registry+https://github.com/rust-lang/crates.io-index#anstyle@1.0.10": "1yai2vppmd7zlvlrp9grwll60knrmscalf8l2qpfz8b7y5lkpk2m",
"registry+https://github.com/rust-lang/crates.io-index#anyhow@1.0.95": "010vd1ki8w84dzgx6c81sc8qm9n02fxic1gkpv52zp4nwrn0kb1l",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@1.9.0": "0dbdlkzlncbibd3ij6y6jmvjd0cmdn48ydcfdpfhw09njd93r5c1",
"registry+https://github.com/rust-lang/crates.io-index#async-channel@2.3.1": "0skvwxj6ysfc6d7bhczz9a2550260g62bm5gl0nmjxxyn007id49",
"registry+https://github.com/rust-lang/crates.io-index#async-executor@1.13.1": "1v6w1dbvsmw6cs4dk4lxj5dvrikc6xi479wikwaab2qy3h09mjih",
"registry+https://github.com/rust-lang/crates.io-index#async-global-executor@2.4.1": "1762s45cc134d38rrv0hyp41hv4iv6nmx59vswid2p0il8rvdc85",
"registry+https://github.com/rust-lang/crates.io-index#async-io@2.4.0": "0n8h0vy53n4vdkq529scqnkzm9vcl3r73za9nj81s2nfrhiv78j3",
"registry+https://github.com/rust-lang/crates.io-index#async-lock@3.4.0": "060vh45i809wcqyxzs5g69nqiqah7ydz0hpkcjys9258vqn4fvpz",
"registry+https://github.com/rust-lang/crates.io-index#async-std@1.13.0": "059nbiyijwbndyrz0050skvlvzhds0dmnl0biwmxwbw055glfd66",
"registry+https://github.com/rust-lang/crates.io-index#async-task@4.7.1": "1pp3avr4ri2nbh7s6y9ws0397nkx1zymmcr14sq761ljarh3axcb",
"registry+https://github.com/rust-lang/crates.io-index#async-trait@0.1.85": "0mm0gwad44zs7mna4a0m1z4dhzpmydfj73w4wm23c8xpnhrli4rz",
"registry+https://github.com/rust-lang/crates.io-index#atoi@2.0.0": "0a05h42fggmy7h0ajjv6m7z72l924i7igbx13hk9d8pyign9k3gj",
"registry+https://github.com/rust-lang/crates.io-index#atomic-waker@1.1.2": "1h5av1lw56m0jf0fd3bchxq8a30xv0b4wv8s4zkp4s0i7mfvs18m",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@0.1.8": "0y4vw4l4izdxq1v0rrhvmlbqvalrqrmk60v1z0dqlgnlbzkl7phd",
"registry+https://github.com/rust-lang/crates.io-index#autocfg@1.4.0": "09lz3by90d2hphbq56znag9v87gfpd9gb8nr82hll8z6x2nhprdc",
"registry+https://github.com/rust-lang/crates.io-index#az@1.2.1": "0ww9k1w3al7x5qmb7f13v3s9c2pg1pdxbs8xshqy6zyrchj4qzkv",
"registry+https://github.com/rust-lang/crates.io-index#backtrace@0.3.74": "06pfif7nwx66qf2zaanc2fcq7m64i91ki9imw9xd3bnz5hrwp0ld",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.21.7": "0rw52yvsk75kar9wgqfwgb414kvil1gn7mqkrhn9zf1537mpsacx",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1": "1imqzgh7bxcikp5vx3shqvw9j09g9ly0xr0jma0q66i52r7jbcvj",
"registry+https://github.com/rust-lang/crates.io-index#base64@0.9.3": "0hs62r35bgxslawyrn1vp9rmvrkkm76fqv0vqcwd048vs876r7a8",
"registry+https://github.com/rust-lang/crates.io-index#base64ct@1.6.0": "0nvdba4jb8aikv60az40x2w1y96sjdq8z3yp09rwzmkhiwv1lg4c",
"registry+https://github.com/rust-lang/crates.io-index#bindgen@0.69.5": "1240snlcfj663k04bjsg629g4wx6f83flgbjh5rzpgyagk3864r7",
"registry+https://github.com/rust-lang/crates.io-index#bit-set@0.8.0": "18riaa10s6n59n39vix0cr7l2dgwdhcpbcm97x1xbyfp1q47x008",
"registry+https://github.com/rust-lang/crates.io-index#bit-vec@0.8.0": "1xxa1s2cj291r7k1whbxq840jxvmdsq9xgh7bvrxl46m80fllxjy",
"registry+https://github.com/rust-lang/crates.io-index#bit_field@0.10.2": "0qav5rpm4hqc33vmf4vc4r0mh51yjx5vmd9zhih26n9yjs3730nw",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@1.3.2": "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy",
"registry+https://github.com/rust-lang/crates.io-index#bitflags@2.8.0": "0dixc6168i98652jxf0z9nbyn0zcis3g6hi6qdr7z5dbhcygas4g",
"registry+https://github.com/rust-lang/crates.io-index#block-buffer@0.10.4": "0w9sa2ypmrsqqvc20nhwr75wbb5cjr4kkyhpjm1z1lv2kdicfy1h",
"registry+https://github.com/rust-lang/crates.io-index#blocking@1.6.1": "1si99l8zp7c4zq87y35ayjgc5c9b60jb8h0k14zfcs679z2l2gvh",
"registry+https://github.com/rust-lang/crates.io-index#build_html@2.5.0": "0p4k25yk3v0wf720wl5zcghvc9ik6l7lsh3fz86cq3g7x4nbhpi2",
"registry+https://github.com/rust-lang/crates.io-index#bumpalo@3.16.0": "0b015qb4knwanbdlp1x48pkb4pm57b8gidbhhhxr900q2wb6fabr",
"registry+https://github.com/rust-lang/crates.io-index#bytemuck@1.21.0": "18wj81x9xhqcd6985r8qxmbik6szjfjfj62q3xklw8h2p3x7srgg",
"registry+https://github.com/rust-lang/crates.io-index#byteorder@1.5.0": "0jzncxyf404mwqdbspihyzpkndfgda450l0893pz5xj685cg5l0z",
"registry+https://github.com/rust-lang/crates.io-index#bytes@1.9.0": "16ykzx24v1x4f42v2lxyvlczqhdfji3v7r4ghwckpwijzvb1hn9j",
"registry+https://github.com/rust-lang/crates.io-index#cairo-rs@0.18.5": "1qjfkcq3mrh3p01nnn71dy3kn99g21xx3j8xcdvzn8ll2pq6x8lc",
"registry+https://github.com/rust-lang/crates.io-index#cairo-sys-rs@0.18.2": "0lfsxl7ylw3phbnwmz3k58j1gnqi6kc2hdc7g3bb7f4hwnl9yp38",
"registry+https://github.com/rust-lang/crates.io-index#cc@1.2.10": "0aaj2ivamhfzhgb9maasnfkh03s2mzhzpzwrkghgzbkfnv5qy80k",
"registry+https://github.com/rust-lang/crates.io-index#cexpr@0.6.0": "0rl77bwhs5p979ih4r0202cn5jrfsrbgrksp40lkfz5vk1x3ib3g",
"registry+https://github.com/rust-lang/crates.io-index#cfg-expr@0.15.8": "00lgf717pmf5qd2qsxxzs815v6baqg38d6m5i6wlh235p14asryh",
"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.0": "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz-build@0.2.1": "03rmzd69cn7fp0fgkjr5042b3g54s2l941afjm3001ls7kqkjgj3",
"registry+https://github.com/rust-lang/crates.io-index#chrono-tz@0.8.6": "0vlksnmpb6rd4h55245agnfhphnpslwnq9al3aw3is43dd3f16nm",
"registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.39": "09g8nf409lb184kl9j4s85k0kn8wzgjkp5ls9zid50b886fwqdky",
"registry+https://github.com/rust-lang/crates.io-index#clang-sys@1.8.1": "1x1r9yqss76z8xwpdanw313ss6fniwc1r7dzb5ycjn0ph53kj0hb",
"registry+https://github.com/rust-lang/crates.io-index#clap@4.5.30": "0vcyrn4ymq2gd56sl3xnfki8q8llg64sj3rj3qx33mgsf66v3dwj",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.30": "0369xis2ar46icsaxqyy37976mlb62alzyx4j53k99vq2w3v4pd3",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.28": "1vgigkhljp3r8r5lwdrn1ij93nafmjwh8cx77nppb9plqsaysk5z",
"registry+https://github.com/rust-lang/crates.io-index#clap_lex@0.7.4": "19nwfls5db269js5n822vkc8dw0wjq2h1wf0hgr06ld2g52d2spl",
"registry+https://github.com/rust-lang/crates.io-index#cloudabi@0.0.3": "0kxcg83jlihy0phnd2g8c2c303px3l2p3pkjz357ll6llnd5pz6x",
"registry+https://github.com/rust-lang/crates.io-index#color_quant@1.1.0": "12q1n427h2bbmmm1mnglr57jaz2dj9apk0plcxw7nwqiai7qjyrx",
"registry+https://github.com/rust-lang/crates.io-index#colorchoice@1.0.3": "1439m3r3jy3xqck8aa13q658visn71ki76qa93cy55wkmalwlqsv",
"registry+https://github.com/rust-lang/crates.io-index#concurrent-queue@2.5.0": "0wrr3mzq2ijdkxwndhf79k952cp4zkz35ray8hvsxl96xrx1k82c",
"registry+https://github.com/rust-lang/crates.io-index#const-oid@0.9.6": "1y0jnqaq7p2wvspnx7qj76m7hjcqpz73qzvr9l2p9n2s51vr6if2",
"registry+https://github.com/rust-lang/crates.io-index#convert_case@0.6.0": "1jn1pq6fp3rri88zyw6jlhwwgf6qiyc08d6gjv0qypgkl862n67c",
"registry+https://github.com/rust-lang/crates.io-index#cookie-factory@0.3.3": "18mka6fk3843qq3jw1fdfvzyv05kx7kcmirfbs2vg2kbw9qzm1cq",
"registry+https://github.com/rust-lang/crates.io-index#cookie@0.17.0": "096c52jg9iq4lfcps2psncswv33fc30mmnaa2sbzzcfcw71kgyvy",
"registry+https://github.com/rust-lang/crates.io-index#cool_asserts@2.0.3": "1v18dg7ifx41k2f82j3gsnpm1fg9wk5s4zv7sf42c7pnad72b7zf",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation-sys@0.8.7": "12w8j73lazxmr1z0h98hf3z623kl8ms7g07jch7n4p8f9nwlhdkp",
"registry+https://github.com/rust-lang/crates.io-index#core-foundation@0.9.4": "13zvbbj07yk3b61b8fhwfzhy35535a583irf23vlcg59j7h9bqci",
"registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.16": "1hy466fkhxjbb16i7na95wz8yr14d0kd578pwzj5lbkz14jh5f0n",
"registry+https://github.com/rust-lang/crates.io-index#crc-catalog@2.4.0": "1xg7sz82w3nxp1jfn425fvn1clvbzb3zgblmxsyqpys0dckp9lqr",
"registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.4.2": "1czp7vif73b8xslr3c9yxysmh9ws2r8824qda7j47ffs9pcnjxx9",
"registry+https://github.com/rust-lang/crates.io-index#crc@3.2.1": "0dnn23x68qakzc429s1y9k9y3g8fn5v9jwi63jcz151sngby9rk9",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-deque@0.8.6": "0l9f1saqp1gn5qy0rxvkmz4m6n7fc0b3dbm6q1r5pmgpnyvi3lcx",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-epoch@0.9.18": "03j2np8llwf376m3fxqx859mgp9f83hj1w34153c7a9c7i5ar0jv",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-queue@0.3.12": "059igaxckccj6ndmg45d5yf7cm4ps46c18m21afq3pwiiz1bnn0g",
"registry+https://github.com/rust-lang/crates.io-index#crossbeam-utils@0.8.21": "0a3aa2bmc8q35fb67432w16wvi54sfmb69rk9h5bhd18vw0c99fh",
"registry+https://github.com/rust-lang/crates.io-index#crunchy@0.2.2": "1dx9mypwd5mpfbbajm78xcrg5lirqk7934ik980mmaffg3hdm0bs",
"registry+https://github.com/rust-lang/crates.io-index#crypto-common@0.1.6": "1cvby95a6xg7kxdz5ln3rl9xh66nz66w46mm3g56ri1z5x815yqv",
"registry+https://github.com/rust-lang/crates.io-index#data-encoding@2.7.0": "0vxdv88fnvnxw29gk84gj5662xlv0p5hmlxpwp7d60cckp8fwq0f",
"registry+https://github.com/rust-lang/crates.io-index#deflate@0.8.6": "0x6iqlayg129w63999kz97m279m0jj4x4sm6gkqlvmp73y70yxvk",
"registry+https://github.com/rust-lang/crates.io-index#der@0.7.9": "1h4vzjfa1lczxdf8avfj9qlwh1qianqlxdy1g5rn762qnvkzhnzm",
"registry+https://github.com/rust-lang/crates.io-index#deranged@0.3.11": "1d1ibqqnr5qdrpw8rclwrf1myn3wf0dygl04idf4j2s49ah6yaxl",
"registry+https://github.com/rust-lang/crates.io-index#digest@0.10.7": "14p2n6ih29x81akj097lvz7wi9b6b9hvls0lwrv7b6xwyy0s5ncy",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.7.0": "09ky8s3higkf677lmyqg30hmj66gpg7hx907s6hfvbk2a9av05r5",
"registry+https://github.com/rust-lang/crates.io-index#dimensioned@0.8.0": "15s3j4ry943xqlac63bp81sgdk9s3yilysabzww35j9ibmnaic50",
"registry+https://github.com/rust-lang/crates.io-index#displaydoc@0.2.5": "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp",
"registry+https://github.com/rust-lang/crates.io-index#dotenvy@0.15.7": "16s3n973n5aqym02692i1npb079n5mb0fwql42ikmwn8wnrrbbqs",
"registry+https://github.com/rust-lang/crates.io-index#either@1.13.0": "1w2c1mybrd7vljyxk77y9f4w9dyjrmp3yp82mk7bcm8848fazcb0",
"registry+https://github.com/rust-lang/crates.io-index#encoding_rs@0.8.35": "1wv64xdrr9v37rqqdjsyb8l8wzlcbab80ryxhrszvnj59wy0y0vm",
"registry+https://github.com/rust-lang/crates.io-index#env_logger@0.10.2": "1005v71kay9kbz1d5907l0y7vh9qn2fqsp2yfgb8bjvin6m0bm2c",
"registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.1": "1malmx5f4lkfvqasz319lq6gb3ddg19yzf9s8cykfsgzdmyq0hsl",
"registry+https://github.com/rust-lang/crates.io-index#errno@0.3.10": "0pgblicz1kjz9wa9m0sghkhh2zw1fhq1mxzj7ndjm746kg5m5n1k",
"registry+https://github.com/rust-lang/crates.io-index#etcetera@0.8.0": "0hxrsn75dirbjhwgkdkh0pnpqrnq17ypyhjpjaypgax1hd91nv8k",
"registry+https://github.com/rust-lang/crates.io-index#event-listener-strategy@0.5.3": "1ch5gf6knllyq12jkb5zdfag573dh44307q4pwwi2g37sc6lwgiw",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@2.5.3": "1q4w3pndc518crld6zsqvvpy9lkzwahp2zgza9kbzmmqh9gif1h2",
"registry+https://github.com/rust-lang/crates.io-index#event-listener@5.4.0": "1bii2gn3vaa33s0gr2zph7cagiq0ppcfxcxabs24ri9z9kgar4il",
"registry+https://github.com/rust-lang/crates.io-index#exr@1.73.0": "1q47yq78q9k210r6jy1wwrilxwwxqavik9l3l426rd17k7srfcgq",
"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0": "1ghiahsw1jd68df895cy5h3gzwk30hndidn3b682zmshpgmrx41p",
"registry+https://github.com/rust-lang/crates.io-index#fdeflate@0.3.7": "130ga18vyxbb5idbgi07njymdaavvk6j08yh1dfarm294ssm6s0y",
"registry+https://github.com/rust-lang/crates.io-index#field-offset@0.3.6": "0zq5sssaa2ckmcmxxbly8qgz3sxpb8g1lwv90sdh1z74qif2gqiq",
"registry+https://github.com/rust-lang/crates.io-index#fixed@1.28.0": "0nn85j5x8yzx10q49jdzia4yp6pnasnxpnwh0p9aqr7qkfwf1il5",
"registry+https://github.com/rust-lang/crates.io-index#flate2@1.0.35": "0z6h0wa095wncpfngx75wyhyjnqwld7wax401gsvnzjhzgdbydn9",
"registry+https://github.com/rust-lang/crates.io-index#fluent-bundle@0.15.3": "14zl0cjn361is69pb1zry4k2zzh5nzsfv0iz05wccl00x0ga5q3z",
"registry+https://github.com/rust-lang/crates.io-index#fluent-langneg@0.13.0": "152yxplc11vmxkslvmaqak9x86xnavnhdqyhrh38ym37jscd0jic",
"registry+https://github.com/rust-lang/crates.io-index#fluent-syntax@0.11.1": "0gd3cdvsx9ymbb8hijcsc9wyf8h1pbcbpsafg4ldba56ji30qlra",
"registry+https://github.com/rust-lang/crates.io-index#fluent@0.16.1": "0njmdpwz52yjzyp55iik9k6vrixqiy7190d98pk0rgdy0x3n6x5v",
"registry+https://github.com/rust-lang/crates.io-index#flume@0.11.1": "15ch0slxa8sqsi6c73a0ky6vdnh48q8cxjf7rksa3243m394s3ns",
"registry+https://github.com/rust-lang/crates.io-index#fnv@1.0.7": "1hc2mcqha06aibcaza94vbi81j6pr9a1bbxrxjfhc91zin8yr7iz",
"registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.4": "0vsxw2iwpgs7yy6l7pndm7b8nllaq5vdxwnmjn1qpm5kyzhzvlm0",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1": "0jxgzd04ra4imjv8jgkmdq59kj8fsz6w4zxsbmlai34h26225c00",
"registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2": "1cgk0vyd7r45cj769jym4a6s7vwshvd0z4bqrb92q1fwibmkkwzn",
"registry+https://github.com/rust-lang/crates.io-index#form_urlencoded@1.2.1": "0milh8x7nl4f450s3ddhg57a3flcv6yq8hlkyk6fyr3mcb128dp1",
"registry+https://github.com/rust-lang/crates.io-index#fuchsia-cprng@0.1.1": "1fnkqrbz7ixxzsb04bsz9p0zzazanma8znfdqjvh39n14vapfvx0",
"registry+https://github.com/rust-lang/crates.io-index#futures-channel@0.3.31": "040vpqpqlbk099razq8lyn74m0f161zd0rp36hciqrwcg2zibzrd",
"registry+https://github.com/rust-lang/crates.io-index#futures-core@0.3.31": "0gk6yrxgi5ihfanm2y431jadrll00n5ifhnpx090c2f2q1cr1wh5",
"registry+https://github.com/rust-lang/crates.io-index#futures-executor@0.3.31": "17vcci6mdfzx4gbk0wx64chr2f13wwwpvyf3xd5fb1gmjzcx2a0y",
"registry+https://github.com/rust-lang/crates.io-index#futures-intrusive@0.5.0": "0vwm08d1pli6bdaj0i7xhk3476qlx4pll6i0w03gzdnh7lh0r4qx",
"registry+https://github.com/rust-lang/crates.io-index#futures-io@0.3.31": "1ikmw1yfbgvsychmsihdkwa8a1knank2d9a8dk01mbjar9w1np4y",
"registry+https://github.com/rust-lang/crates.io-index#futures-lite@2.6.0": "0cmmgszlmkwsac9pyw5rfjakmshgx4wmzmlyn6mmjs0jav4axvgm",
"registry+https://github.com/rust-lang/crates.io-index#futures-macro@0.3.31": "0l1n7kqzwwmgiznn0ywdc5i24z72zvh9q1dwps54mimppi7f6bhn",
"registry+https://github.com/rust-lang/crates.io-index#futures-sink@0.3.31": "1xyly6naq6aqm52d5rh236snm08kw8zadydwqz8bip70s6vzlxg5",
"registry+https://github.com/rust-lang/crates.io-index#futures-task@0.3.31": "124rv4n90f5xwfsm9qw6y99755y021cmi5dhzh253s920z77s3zr",
"registry+https://github.com/rust-lang/crates.io-index#futures-util@0.3.31": "10aa1ar8bgkgbr4wzxlidkqkcxf77gffyj8j7768h831pcaq784z",
"registry+https://github.com/rust-lang/crates.io-index#futures@0.3.31": "0xh8ddbkm9jy8kc5gbvjp9a4b6rqqxvc8471yb2qaz5wm2qhgg35",
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf-sys@0.18.0": "1xya543c4ffd2n7aiwwrdxsyc9casdbasafi6ixcknafckm3k61z",
"registry+https://github.com/rust-lang/crates.io-index#gdk-pixbuf@0.18.5": "1v7svvl0g7zybndmis5inaqqgi1mvcc6s1n8rkb31f5zn3qzbqah",
"registry+https://github.com/rust-lang/crates.io-index#gdk4-sys@0.7.2": "1w7yvir565sjrrw828lss07749hfpfsr19jdjzwivkx36brl7ayv",
"registry+https://github.com/rust-lang/crates.io-index#gdk4@0.7.3": "1xiacc63p73apr033gjrb9dsk0y4yxnsljwfxbwfry41snd03nvy",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.11.2": "0a7w8w0rg47nmcinnfzv443lcyb8mplwc251p1jyr5xj2yh6wzv6",
"registry+https://github.com/rust-lang/crates.io-index#generic-array@0.14.7": "16lyyrzrljfq424c3n8kfwkqihlimmsg5nhshbbp48np3yjrqr45",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.15": "1mzlnrb3dgyd1fb84gvw10pyr8wdqdl4ry4sr64i1s8an66pqmn4",
"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.3.1": "1y154yzby383p63ndw6zpfm0fz3vf6c0zdwc7df6vkl150wrr923",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.11.4": "01hbw3isapzpzff8l6aw55jnaqx2bcscrbwyf3rglkbbfp397p9y",
"registry+https://github.com/rust-lang/crates.io-index#gif@0.13.1": "1whrkvdg26gp1r7f95c6800y6ijqw5y0z8rgj6xihpi136dxdciz",
"registry+https://github.com/rust-lang/crates.io-index#gimli@0.31.1": "0gvqc0ramx8szv76jhfd4dms0zyamvlg4whhiz11j34hh3dqxqh7",
"registry+https://github.com/rust-lang/crates.io-index#gio-sys@0.18.1": "1lip8z35iy9d184x2qwjxlbxi64q9cpayy7v1p5y9xdsa3w6smip",
"registry+https://github.com/rust-lang/crates.io-index#gio@0.18.4": "0wsc6mnx057s4ailacg99dwgna38dbqli5x7a6y9rdw75x9qzz6l",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.16.3": "1z73bl10zmxwrv16v4f5wcky1f3z5a2v0hknca54al4k2p5ka695",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.17.10": "05p7ab2vn8962cbchi7a6hndhvw64nqk4w5kpg5z53iizsgdfrbs",
"registry+https://github.com/rust-lang/crates.io-index#glib-build-tools@0.18.0": "0p5c2ayiam5bkp9wvq9f9ihwp06nqs5j801npjlwnhrl8rpwac9l",
"registry+https://github.com/rust-lang/crates.io-index#glib-macros@0.18.5": "1p5cla53fcp195zp0hkqpmnn7iwmkdswhy7xh34002bw8y7j5c0b",
"registry+https://github.com/rust-lang/crates.io-index#glib-sys@0.18.1": "164qhsfmlzd5mhyxs8123jzbdfldwxbikfpq5cysj3lddbmy4g06",
"registry+https://github.com/rust-lang/crates.io-index#glib@0.18.5": "1r8fw0627nmn19bgk3xpmcfngx3wkn7mcpq5a8ma3risx3valg93",
"registry+https://github.com/rust-lang/crates.io-index#glob@0.3.2": "1cm2w34b5w45fxr522h5b0fv1bxchfswcj560m3pnjbia7asvld8",
"registry+https://github.com/rust-lang/crates.io-index#gloo-timers@0.3.0": "1519157n7xppkk6pdw5w52vy1llzn5iljkqd7q1h5609jv7l7cdv",
"registry+https://github.com/rust-lang/crates.io-index#gobject-sys@0.18.0": "0i6fhp3m6vs3wkzyc22rk2cqj68qvgddxmpaai34l72da5xi4l08",
"registry+https://github.com/rust-lang/crates.io-index#graphene-rs@0.18.1": "00f4q1ra4haap5i7lazwhkdgnb49fs8adk2nm6ki6mjhl76jh8iv",
"registry+https://github.com/rust-lang/crates.io-index#graphene-sys@0.18.1": "0n8zlg7z26lwpnvlqp1hjlgrs671skqwagdpm7r8i1zwx3748hfc",
"registry+https://github.com/rust-lang/crates.io-index#grid@0.9.0": "0iswdcxggyxp9m1rz0m7bfg4xacinvn78zp2fgfp0l0079x10d06",
"registry+https://github.com/rust-lang/crates.io-index#gsk4-sys@0.7.3": "0mbdlm9qi1hql48rr29vsj9vlqwc7gxg67wg1q19z67azwz9xg8j",
"registry+https://github.com/rust-lang/crates.io-index#gsk4@0.7.3": "0zhzs2dkgiinhgc11akpn2harq3x5n1iq21dnc4h689g3lsqx58d",
"registry+https://github.com/rust-lang/crates.io-index#gtk4-macros@0.7.2": "0bw3cchiycf7dw1bw4p8946gv38azxy05a5w0ndgcmxnz6fc8znm",
"registry+https://github.com/rust-lang/crates.io-index#gtk4-sys@0.7.3": "1f2ylskyqkjdik9fij2m46pra4jagnif5xyalbxfk3334fmc9n2l",
"registry+https://github.com/rust-lang/crates.io-index#gtk4@0.7.3": "0hh8nzglmz94v1m1h6vy8z12m6fr7ia467ry0md5fa4p7sm53sss",
"registry+https://github.com/rust-lang/crates.io-index#h2@0.3.26": "1s7msnfv7xprzs6xzfj5sg6p8bjcdpcqcmjjbkd345cyi1x55zl1",
"registry+https://github.com/rust-lang/crates.io-index#half@2.4.1": "123q4zzw1x4309961i69igzd1wb7pj04aaii3kwasrz3599qrl3d",
"registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.2": "12dj0yfn59p3kh3679ac0w1fagvzf4z2zp87a13gbbqbzw0185dz",
"registry+https://github.com/rust-lang/crates.io-index#hashlink@0.10.0": "1h8lzvnl9qxi3zyagivzz2p1hp6shgddfmccyf6jv7s1cdicz0kk",
"registry+https://github.com/rust-lang/crates.io-index#headers-core@0.2.0": "0ab469xfpd411mc3dhmjhmzrhqikzyj8a17jn5bkj9zfpy0n9xp7",
"registry+https://github.com/rust-lang/crates.io-index#headers@0.3.9": "0w62gnwh2p1lml0zqdkrx9dp438881nhz32zrzdy61qa0a9kns06",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.4.1": "1a7mqsnycv5z4z5vnv1k34548jzmc0ajic7c1j8jsaspnhw5ql4m",
"registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0": "1sjmpsdl8czyh9ywl3qcsfsq9a307dg4ni2vnlwgnzzqhc4y0113",
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.3.9": "092hxjbjnq5fmz66grd9plxd0sh6ssg5fhgwwwqbrzgzkjwdycfj",
"registry+https://github.com/rust-lang/crates.io-index#hermit-abi@0.4.0": "1k1zwllx6nfq417hy38x4akw1ivlv68ymvnzyxs76ffgsqcskxpv",
"registry+https://github.com/rust-lang/crates.io-index#hex-string@0.1.0": "02sgrgrbp693jv0v5iga7z47y6aj93cq0ia39finby9x17fw53l4",
"registry+https://github.com/rust-lang/crates.io-index#hex@0.4.3": "0w1a4davm1lgzpamwnba907aysmlrnygbqmfis2mqjx5m552a93z",
"registry+https://github.com/rust-lang/crates.io-index#hkdf@0.12.4": "1xxxzcarz151p1b858yn5skmhyrvn8fs4ivx5km3i1kjmnr8wpvv",
"registry+https://github.com/rust-lang/crates.io-index#hmac@0.12.1": "0pmbr069sfg76z7wsssfk5ddcqd9ncp79fyz6zcm6yn115yc6jbc",
"registry+https://github.com/rust-lang/crates.io-index#home@0.5.11": "1kxb4k87a9sayr8jipr7nq9wpgmjk4hk4047hmf9kc24692k75aq",
"registry+https://github.com/rust-lang/crates.io-index#http-body@0.4.6": "1lmyjfk6bqk6k9gkn1dxq770sb78pqbqshga241hr5p995bb5skw",
"registry+https://github.com/rust-lang/crates.io-index#http@0.2.12": "1w81s4bcbmcj9bjp7mllm8jlz6b31wzvirz8bgpzbqkpwmbvn730",
"registry+https://github.com/rust-lang/crates.io-index#http@1.2.0": "1skglzdf98j5nzxlii540n11is0w4l80mi5sm3xrj716asps4v7i",
"registry+https://github.com/rust-lang/crates.io-index#httparse@1.9.5": "0ip9v8m9lvgvq1lznl31wvn0ch1v254na7lhid9p29yx9rbx6wbx",
"registry+https://github.com/rust-lang/crates.io-index#httpdate@1.0.3": "1aa9rd2sac0zhjqh24c9xvir96g188zldkx0hr6dnnlx5904cfyz",
"registry+https://github.com/rust-lang/crates.io-index#humantime@2.1.0": "1r55pfkkf5v0ji1x6izrjwdq9v6sc7bv99xj6srywcar37xmnfls",
"registry+https://github.com/rust-lang/crates.io-index#hyper-tls@0.5.0": "01crgy13102iagakf6q4mb75dprzr7ps1gj0l5hxm1cvm7gks66n",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.10.16": "0wwjh9p3mzvg3fss2lqz5r7ddcgl1fh9w6my2j69d6k0lbcm41ha",
"registry+https://github.com/rust-lang/crates.io-index#hyper@0.14.32": "1rvcb0smz8q1i0y6p7rwxr02x5sclfg2hhxf3g0774zczn0cgps1",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone-haiku@0.1.2": "17r6jmj31chn7xs9698r122mapq85mfnv98bb4pg6spm0si2f67k",
"registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.61": "085jjsls330yj1fnwykfzmb2f10zp6l7w4fhq81ng81574ghhpi3",
"registry+https://github.com/rust-lang/crates.io-index#icu_collections@1.5.0": "09j5kskirl59mvqc8kabhy7005yyy7dp88jw9f6f3gkf419a8byv",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid@1.5.0": "0dznvd1c5b02iilqm044q4hvar0sqibq1z46prqwjzwif61vpb0k",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform@1.5.0": "0kmmi1kmj9yph6mdgkc7v3wz6995v7ly3n80vbg0zr78bp1iml81",
"registry+https://github.com/rust-lang/crates.io-index#icu_locid_transform_data@1.5.0": "0vkgjixm0wzp2n3v5mw4j89ly05bg3lx96jpdggbwlpqi0rzzj7x",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer@1.5.0": "0kx8qryp8ma8fw1vijbgbnf7zz9f2j4d14rw36fmjs7cl86kxkhr",
"registry+https://github.com/rust-lang/crates.io-index#icu_normalizer_data@1.5.0": "05lmk0zf0q7nzjnj5kbmsigj3qgr0rwicnn5pqi9n7krmbvzpjpq",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties@1.5.1": "1xgf584rx10xc1p7zjr78k0n4zn3g23rrg6v2ln31ingcq3h5mlk",
"registry+https://github.com/rust-lang/crates.io-index#icu_properties_data@1.5.0": "0scms7pd5a7yxx9hfl167f5qdf44as6r3bd8myhlngnxqgxyza37",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider@1.5.0": "1nb8vvgw8dv2inqklvk05fs0qxzkw8xrg2n9vgid6y7gm3423m3f",
"registry+https://github.com/rust-lang/crates.io-index#icu_provider_macros@1.5.0": "1mjs0w7fcm2lcqmbakhninzrjwqs485lkps4hz0cv3k36y9rxj0y",
"registry+https://github.com/rust-lang/crates.io-index#idna@0.1.5": "0kl4gs5kaydn4v07c6ka33spm9qdh2np0x7iw7g5zd8z1c7rxw1q",
"registry+https://github.com/rust-lang/crates.io-index#idna@1.0.3": "0zlajvm2k3wy0ay8plr07w22hxkkmrxkffa6ah57ac6nci984vv8",
"registry+https://github.com/rust-lang/crates.io-index#idna_adapter@1.2.0": "0wggnkiivaj5lw0g0384ql2d7zk4ppkn3b1ry4n0ncjpr7qivjns",
"registry+https://github.com/rust-lang/crates.io-index#image@0.23.14": "18gn2f7xp30pf9aqka877knlq308khxqiwjvsccvzaa4f9zcpzr4",
"registry+https://github.com/rust-lang/crates.io-index#image@0.24.9": "17gnr6ifnpzvhjf6dwbl9hki8x6bji5mwcqp0048x1jm5yfi742n",
"registry+https://github.com/rust-lang/crates.io-index#indent_write@2.2.0": "1hqjp80argdskrhd66g9sh542yxy8qi77j6rc69qd0l7l52rdzhc",
"registry+https://github.com/rust-lang/crates.io-index#indexmap@2.7.0": "07s7jmdymvd0rm4yswp0j3napx57hkjm9gs9n55lvs2g78vj5y32",
"registry+https://github.com/rust-lang/crates.io-index#intl-memoizer@0.5.2": "1nkvql7c7b76axv4g68di1p2m9bnxq1cbn6mlqcawf72zhhf08py",
"registry+https://github.com/rust-lang/crates.io-index#intl_pluralrules@7.0.2": "0wprd3h6h8nfj62d8xk71h178q7zfn3srxm787w4sawsqavsg3h7",
"registry+https://github.com/rust-lang/crates.io-index#ipnet@2.10.1": "025p9wm94q1w2l13hbbr4cbmfygly3a2ag8g5s618l2jhq4l3hnx",
"registry+https://github.com/rust-lang/crates.io-index#iron@0.6.1": "1s4mf8395f693nhwsr0znw3j5frzn56gzllypyl50il85p50ily6",
"registry+https://github.com/rust-lang/crates.io-index#is-terminal@0.4.13": "0jwgjjz33kkmnwai3nsdk1pz9vb6gkqvw1d1vq7bs3q48kinh7r6",
"registry+https://github.com/rust-lang/crates.io-index#is_terminal_polyfill@1.70.1": "1kwfgglh91z33kl0w5i338mfpa3zs0hidq5j4ny4rmjwrikchhvr",
"registry+https://github.com/rust-lang/crates.io-index#itertools@0.12.1": "0s95jbb3ndj1lvfxyq5wanc0fm0r6hg6q4ngb92qlfdxvci10ads",
"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.14": "0x26kr9m062mafaxgcf2p6h2x7cmixm0zw95aipzn2hr3d5jlnnp",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.1.22": "1wnh0bmmswpgwhgmlizz545x8334nlbmkq8imy9k224ri3am7792",
"registry+https://github.com/rust-lang/crates.io-index#jpeg-decoder@0.3.1": "1c1k53svpdyfhibkmm0ir5w0v3qmcmca8xr8vnnmizwf6pdagm7m",
"registry+https://github.com/rust-lang/crates.io-index#js-sys@0.3.77": "13x2qcky5l22z4xgivi59xhjjx4kxir1zg7gcj0f1ijzd4yg7yhw",
"registry+https://github.com/rust-lang/crates.io-index#kv-log-macro@1.0.7": "0zwp4bxkkp87rl7xy2dain77z977rvcry1gmr5bssdbn541v7s0d",
"registry+https://github.com/rust-lang/crates.io-index#language-tags@0.2.2": "16hrjdpa827carq5x4b8zhas24d8kg4s16m6nmmn1kb7cr5qh7d9",
"registry+https://github.com/rust-lang/crates.io-index#lazy_static@1.5.0": "1zk6dqqni0193xg6iijh7i3i44sryglwgvx20spdvwk3r6sbrlmv",
"registry+https://github.com/rust-lang/crates.io-index#lazycell@1.3.0": "0m8gw7dn30i0zjjpjdyf6pc16c34nl71lpv461mix50x3p70h3c3",
"registry+https://github.com/rust-lang/crates.io-index#lebe@0.5.2": "1j2l6chx19qpa5gqcw434j83gyskq3g2cnffrbl3842ymlmpq203",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita-sys@0.5.3": "16n6xsy6jhbj0jbpz8yvql6c9b89a99v9vhdz5s37mg1inisl42y",
"registry+https://github.com/rust-lang/crates.io-index#libadwaita@0.5.3": "174pzn9dwsk8ikvrhx13vkh0zrpvb3rhg9yd2q5d2zjh0q6fgrrg",
"registry+https://github.com/rust-lang/crates.io-index#libc@0.2.169": "02m253hs8gw0m1n8iyrsc4n15yzbqwhddi7w1l0ds7i92kdsiaxm",
"registry+https://github.com/rust-lang/crates.io-index#libloading@0.8.6": "0d2ccr88f8kv3x7va2ccjxalcjnhrci4j2kwxp7lfmbkpjs4wbzw",
"registry+https://github.com/rust-lang/crates.io-index#libm@0.2.11": "1yjgk18rk71rjbqcw9l1zaqna89p9s603k7n327nqs8dn88vwmc3",
"registry+https://github.com/rust-lang/crates.io-index#libspa-sys@0.8.0": "07yh4i5grzbxkchg6dnxlwbdw2wm5jnd7ffbhl77jr0388b9f3dz",
"registry+https://github.com/rust-lang/crates.io-index#libspa@0.8.0": "044qs48yl0llp2dmrgwxj9y1pgfy09i6fhq661zqqb9a3fwa9wv5",
"registry+https://github.com/rust-lang/crates.io-index#libsqlite3-sys@0.30.1": "0jcikvgbj84xc7ikdmpc8m4y5lyqgrb9aqblphwk67kv95xgp69f",
"registry+https://github.com/rust-lang/crates.io-index#libyml@0.0.5": "106963pwg1gc3165bdlk8bbspmk919gk10vshhqglks3z8m700ik",
"registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15": "1aq7r2g7786hyxhv40spzf2nhag5xbw2axxc1k8z5k1dsgdm4v6j",
"registry+https://github.com/rust-lang/crates.io-index#litemap@0.7.4": "012ili3vppd4952sh6y3qwcd0jkd0bq2qpr9h7cppc8sj11k7saf",
"registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.12": "05qvxa6g27yyva25a5ghsg85apdxkvr77yhkyhapj6r8vnf8pbq7",
"registry+https://github.com/rust-lang/crates.io-index#log@0.3.9": "0jq23hhn5h35k7pa8r7wqnsywji6x3wn1q5q7lif5q536if8v7p1",
"registry+https://github.com/rust-lang/crates.io-index#log@0.4.25": "17ydv5zhfv1zzygy458bmg3f3jx1vfziv9d74817w76yhfqgbjq4",
"registry+https://github.com/rust-lang/crates.io-index#logger@0.4.0": "14xlxvkspcfnspjil0xi63qj5cybxn1hjmr5gq8m4v1g9k5p54bc",
"registry+https://github.com/rust-lang/crates.io-index#matches@0.1.10": "1994402fq4viys7pjhzisj4wcw894l53g798kkm2y74laxk0jci5",
"registry+https://github.com/rust-lang/crates.io-index#md-5@0.10.6": "1kvq5rnpm4fzwmyv5nmnxygdhhb2369888a06gdc9pxyrzh7x7nq",
"registry+https://github.com/rust-lang/crates.io-index#memchr@2.7.4": "18z32bhxrax0fnjikv475z7ii718hq457qwmaryixfxsl2qrmjkq",
"registry+https://github.com/rust-lang/crates.io-index#memoffset@0.9.1": "12i17wh9a9plx869g7j4whf62xw68k5zd4k0k5nh6ys5mszid028",
"registry+https://github.com/rust-lang/crates.io-index#mime@0.2.6": "1q1s1ax1gaz8ld3513nvhidfwnik5asbs1ma3hp6inp5dn56nqms",
"registry+https://github.com/rust-lang/crates.io-index#mime@0.3.17": "16hkibgvb9klh0w0jk5crr5xv90l3wlf77ggymzjmvl1818vnxv8",
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@1.8.8": "18qcd5aa3363mb742y7lf39j7ha88pkzbv9ff2qidlsdxsjjjs91",
"registry+https://github.com/rust-lang/crates.io-index#mime_guess@2.0.5": "03jmg3yx6j39mg0kayf7w4a886dl3j15y8zs119zw01ccy74zi7p",
"registry+https://github.com/rust-lang/crates.io-index#minimal-lexical@0.2.1": "16ppc5g84aijpri4jzv14rvcnslvlpphbszc7zzp6vfkddf4qdb8",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.3.7": "0dblrhgbm0wa8jjl8cjp81akaj36yna92df4z1h9b26n3spal7br",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.4.4": "0jsfv00hl5rmx1nijn59sr9jmjd4rjnjhh4kdjy8d187iklih9d9",
"registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.3": "093r1kd1r9dyf05cbvsibgmh96pxp3qhzfvpd6f15bpggamjqh5q",
"registry+https://github.com/rust-lang/crates.io-index#mio@1.0.3": "1gah0h4ia3avxbwym0b6bi6lr6rpysmj9zvw6zis5yq0z0xq91i8",
"registry+https://github.com/rust-lang/crates.io-index#modifier@0.1.0": "0n3fmgli1nsskl0whrfzm1gk0rmwwl6pw1q4nb9sqqmn5h8wkxa1",
"registry+https://github.com/rust-lang/crates.io-index#multer@2.1.0": "1hjiphaypj3phqaj5igrzcia9xfmf4rr4ddigbh8zzb96k1bvb01",
"registry+https://github.com/rust-lang/crates.io-index#nary_tree@0.4.3": "1iqray1a716995l9mmvz5sfqrwg9a235bvrkpcn8bcqwjnwfv1pv",
"registry+https://github.com/rust-lang/crates.io-index#native-tls@0.2.12": "0rkl65z70n7sy4d5w0qa99klg1hr43wx6kcprk4d2n9xr2r4wqd8",
"registry+https://github.com/rust-lang/crates.io-index#nix@0.27.1": "0ly0kkmij5f0sqz35lx9czlbk6zpihb7yh1bsy4irzwfd2f4xc1f",
"registry+https://github.com/rust-lang/crates.io-index#no-std-compat@0.4.1": "132vrf710zsdp40yp1z3kgc2ss8pi0z4gmihsz3y7hl4dpd56f5r",
"registry+https://github.com/rust-lang/crates.io-index#nom@7.1.3": "0jha9901wxam390jcf5pfa0qqfrgh8li787jx2ip0yk5b8y9hwyj",
"registry+https://github.com/rust-lang/crates.io-index#num-bigint-dig@0.8.4": "0lb12df24wgxxbspz4gw1sf1kdqwvpdcpwq4fdlwg4gj41c1k16w",
"registry+https://github.com/rust-lang/crates.io-index#num-conv@0.1.0": "1ndiyg82q73783jq18isi71a7mjh56wxrk52rlvyx0mi5z9ibmai",
"registry+https://github.com/rust-lang/crates.io-index#num-integer@0.1.46": "13w5g54a9184cqlbsq80rnxw4jj4s0d8wv75jsq5r2lms8gncsbr",
"registry+https://github.com/rust-lang/crates.io-index#num-iter@0.1.45": "1gzm7vc5g9qsjjl3bqk9rz1h6raxhygbrcpbfl04swlh0i506a8l",
"registry+https://github.com/rust-lang/crates.io-index#num-rational@0.3.2": "01sgiwny9iflyxh2xz02sak71v2isc3x608hfdpwwzxi3j5l5b0j",
"registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19": "0h984rhdkkqd4ny9cif7y2azl3xdfb7768hb9irhpsch4q3gq787",
"registry+https://github.com/rust-lang/crates.io-index#num_cpus@1.16.0": "0hra6ihpnh06dvfvz9ipscys0xfqa9ca9hzp384d5m02ssvgqqa1",
"registry+https://github.com/rust-lang/crates.io-index#object@0.36.7": "11vv97djn9nc5n6w1gc6bd96d2qk2c8cg1kw5km9bsi3v4a8x532",
"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.20.2": "0xb7rw1aqr7pa4z3b00y7786gyf8awx2gca3md73afy76dzgwq8j",
"registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1": "173xxvfc63rr5ybwqwylsir0vq6xsj4kxiv4hmg4c3vscdmncj59",
"registry+https://github.com/rust-lang/crates.io-index#openssl-probe@0.1.5": "1kq18qm48rvkwgcggfkqq6pm948190czqc94d6bm2sir5hq1l0gz",
"registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.104": "0hf712xcxmycnlc09r8d446b3mwqchsbfrjv374fp7grrc3g7as5",
"registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.68": "1xbiz2bmba2fibg70s462yk2fndp3f9vz11c7iw0ilh2y54bqx31",
"registry+https://github.com/rust-lang/crates.io-index#pango-sys@0.18.0": "1iaxalcaaj59cl9n10svh4g50v8jrc1a36kd7n9yahx8j7ikfrs3",
"registry+https://github.com/rust-lang/crates.io-index#pango@0.18.3": "1r5ygq7036sv7w32kp8yxr6vgggd54iaavh3yckanmq4xg0px8kw",
"registry+https://github.com/rust-lang/crates.io-index#parking@2.2.1": "1fnfgmzkfpjd69v4j9x737b1k8pnn054bvzcn5dm3pkgq595d3gk",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.3": "09ws9g6245iiq8z975h8ycf818a66q3c6zv4b5h8skpm7hc1igzi",
"registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.10": "1y3cf9ld9ijf7i4igwzffcn0xl16dxyn4c5bwgjck1dkgabiyh0y",
"registry+https://github.com/rust-lang/crates.io-index#parse-zoneinfo@0.3.1": "093cs8slbd6kyfi6h12isz0mnaayf5ha8szri1xrbqj4inqhaahz",
"registry+https://github.com/rust-lang/crates.io-index#pem-rfc7468@0.7.0": "04l4852scl4zdva31c1z6jafbak0ni5pi0j38ml108zwzjdrrcw8",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@1.0.1": "0cgq08v1fvr6bs5fvy390cz830lq4fak8havdasdacxcw790s09i",
"registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.1": "0gi8wgx0dcy8rnv1kywdv98lwcx67hz0a0zwpib5v2i08r88y573",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.11.3": "0y6hxp1d48rx2434wgi5g8j1pr8s5jja29ha2b65435fh057imhz",
"registry+https://github.com/rust-lang/crates.io-index#phf@0.7.24": "066xwv4dr6056a9adlkarwp4n94kbpwngbmd47ngm3cfbyw49nmk",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.11.3": "0si1n6zr93kzjs3wah04ikw8z6npsr39jw4dam8yi9czg2609y5f",
"registry+https://github.com/rust-lang/crates.io-index#phf_codegen@0.7.24": "0zjiblicfm0nrmr2xxrs6pnf6zz2394wgch6dcbd8jijkq98agmh",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.11.3": "0gc4np7s91ynrgw73s2i7iakhb4lzdv1gcyx7yhlc0n214a2701w",
"registry+https://github.com/rust-lang/crates.io-index#phf_generator@0.7.24": "0qi62gxk3x3whrmw5c4i71406icqk11qmpgln438p6qm7k4lqdh9",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.11.3": "1rallyvh28jqd9i916gk5gk2igdmzlgvv5q0l3xbf3m6y8pbrsk7",
"registry+https://github.com/rust-lang/crates.io-index#phf_shared@0.7.24": "18371fla0vsj7d6d5rlfb747xbr2in11ar9vgv5qna72bnhp2kr3",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-internal@1.1.8": "1yzfhf6l27nhzv7r5hfrwj2g0x7xmfhgil19fj9am4srqp06csnm",
"registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16": "16wzc7z7dfkf9bmjin22f5282783f6mdksnr0nv0j5ym5f9gyg1v",
"registry+https://github.com/rust-lang/crates.io-index#pin-project@1.1.8": "05jr3xfy1spgmz3q19l4mmvv46vgvkvsgphamifx7x45swxcabhy",
"registry+https://github.com/rust-lang/crates.io-index#pin-utils@0.1.0": "117ir7vslsl2z1a7qzhws4pd01cg2d3338c47swjyvqv2n60v1wb",
"registry+https://github.com/rust-lang/crates.io-index#piper@0.2.4": "0rn0mjjm0cwagdkay77wgmz3sqf8fqmv9d9czm79mvr2yj8c9j4n",
"registry+https://github.com/rust-lang/crates.io-index#pipewire-sys@0.8.0": "04hiy3rl8v3j2dfzp04gr7r8l5azzqqsvqdzwa7sipdij27ii7l4",
"registry+https://github.com/rust-lang/crates.io-index#pipewire@0.8.0": "1nldg1hz4v0qr26lzdxqpvrac4zbc3pb6436sl392425bjx4brh8",
"registry+https://github.com/rust-lang/crates.io-index#pkcs1@0.7.5": "0zz4mil3nchnxljdfs2k5ab1cjqn7kq5lqp62n9qfix01zqvkzy8",
"registry+https://github.com/rust-lang/crates.io-index#pkcs8@0.10.2": "1dx7w21gvn07azszgqd3ryjhyphsrjrmq5mmz1fbxkj5g0vv4l7r",
"registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.31": "1wk6yp2phl91795ia0lwkr3wl4a9xkrympvhqq8cxk4d75hwhglm",
"registry+https://github.com/rust-lang/crates.io-index#plugin@0.2.6": "1q7nghkpvxxr168y2jnzh3w7qc9vfrby9n7ygy3xpj0bj71hsshs",
"registry+https://github.com/rust-lang/crates.io-index#png@0.16.8": "1ipl44q3vy4kvx6j296vk7d4v8gvcg203lrkvvixwixq1j98fciw",
"registry+https://github.com/rust-lang/crates.io-index#png@0.17.16": "09kmkms9fmkbkarw0lnf0scqvjwwg3r7riddag0i3q39r0pil5c2",
"registry+https://github.com/rust-lang/crates.io-index#polling@3.7.4": "0bs4nhwfwsvlzlhah2gbhj3aa9ynvchv2g350wapswh26a65c156",
"registry+https://github.com/rust-lang/crates.io-index#powerfmt@0.2.0": "14ckj2xdpkhv3h6l5sdmb9f1d57z8hbfpdldjc2vl5givq2y77j3",
"registry+https://github.com/rust-lang/crates.io-index#ppv-lite86@0.2.20": "017ax9ssdnpww7nrl1hvqh2lzncpv04nnsibmnw9nxjnaqlpp5bp",
"registry+https://github.com/rust-lang/crates.io-index#pretty_env_logger@0.5.0": "076w9dnvcpx6d3mdbkqad8nwnsynb7c8haxmscyrz7g3vga28mw6",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@1.3.1": "069r1k56bvgk0f58dm5swlssfcp79im230affwk6d9ck20g04k3z",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-crate@2.0.0": "1s23imns07vmacn2xjd5hv2h6rr94iqq3fd2frwa6i4h2nk6d0vy",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error-attr@1.0.4": "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro-error@1.0.4": "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs",
"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.93": "169dw9wch753if1mgyi2nfl1il77gslvh6y2q46qplprwml6m530",
"registry+https://github.com/rust-lang/crates.io-index#proptest@1.6.0": "0l4y4bb8hffv7cys7d59qwqdmvmqjfzz0x9vblc08209clqfkjhl",
"registry+https://github.com/rust-lang/crates.io-index#qoi@0.4.1": "00c0wkb112annn2wl72ixyd78mf56p4lxkhlmsggx65l3v3n8vbz",
"registry+https://github.com/rust-lang/crates.io-index#quick-error@1.2.3": "1q6za3v78hsspisc197bg3g7rpc989qycy8ypr8ap8igv10ikl51",
"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.38": "1k0s75w61k6ch0rs263r4j69b7vj1wadqgb9dia4ylc9mymcqk8f",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.3.23": "0v679h38pjjqj5h4md7v2slsvj6686qgcn7p9fbw3h43iwnk1b34",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.4.6": "14qjfv3gggzhnma20k0sc1jf8y6pplsaq7n1j9ls5c8kf2wl0a2m",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.6.5": "1jl4449jcl4wgmzld6ffwqj5gwxrp8zvx8w573g1z368qg6xlwbd",
"registry+https://github.com/rust-lang/crates.io-index#rand@0.8.5": "013l6931nn7gkc23jz5mm3qdhf93jjf0fg64nz2lp4i51qd8vbrl",
"registry+https://github.com/rust-lang/crates.io-index#rand_chacha@0.1.1": "1vxwyzs4fy1ffjc8l00fsyygpiss135irjf7nyxgq2v0lqf3lvam",
"registry+https://github.com/rust-lang/crates.io-index#rand_chacha@0.3.1": "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6",
"registry+https://github.com/rust-lang/crates.io-index#rand_core@0.3.1": "0jzdgszfa4bliigiy4hi66k7fs3gfwi2qxn8vik84ph77fwdwvvs",
"registry+https://github.com/rust-lang/crates.io-index#rand_core@0.4.2": "1p09ynysrq1vcdlmcqnapq4qakl2yd1ng3kxh3qscpx09k2a6cww",
"registry+https://github.com/rust-lang/crates.io-index#rand_core@0.6.4": "0b4j2v4cb5krak1pv6kakv4sz6xcwbrmy2zckc32hsigbrwy82zc",
"registry+https://github.com/rust-lang/crates.io-index#rand_hc@0.1.0": "1i0vl8q5ddvvy0x8hf1zxny393miyzxkwqnw31ifg6p0gdy6fh3v",
"registry+https://github.com/rust-lang/crates.io-index#rand_isaac@0.1.1": "027flpjr4znx2csxk7gxb7vrf9c7y5mydmvg5az2afgisp4rgnfy",
"registry+https://github.com/rust-lang/crates.io-index#rand_jitter@0.1.4": "16z387y46bfz3csc42zxbjq89vcr1axqacncvv8qhyy93p4xarhi",
"registry+https://github.com/rust-lang/crates.io-index#rand_os@0.1.3": "0wahppm0s64gkr2vmhcgwc0lij37in1lgfxg5rbgqlz0l5vgcxbv",
"registry+https://github.com/rust-lang/crates.io-index#rand_pcg@0.1.2": "0i0bdla18a8x4jn1w0fxsbs3jg7ajllz6azmch1zw33r06dv1ydb",
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.1.1": "0p2x8nr00hricpi2m6ca5vysiha7ybnghz79yqhhx6sl4gkfkxyb",
"registry+https://github.com/rust-lang/crates.io-index#rand_xorshift@0.3.0": "13vcag7gmqspzyabfl1gr9ykvxd2142q2agrj8dkyjmfqmgg4nyj",
"registry+https://github.com/rust-lang/crates.io-index#rayon-core@1.12.1": "1qpwim68ai5h0j7axa8ai8z0payaawv3id0lrgkqmapx7lx8fr8l",
"registry+https://github.com/rust-lang/crates.io-index#rayon@1.10.0": "1ylgnzwgllajalr4v00y4kj22klq2jbwllm70aha232iah0sc65l",
"registry+https://github.com/rust-lang/crates.io-index#rdrand@0.4.0": "1cjq0kwx1bk7jx3kzyciiish5gqsj7620dm43dc52sr8fzmm9037",
"registry+https://github.com/rust-lang/crates.io-index#redox_syscall@0.5.8": "0d48ylyd6gsamynyp257p6n2zl4dw2fhnn5z9y3nhgpri6rn5a03",
"registry+https://github.com/rust-lang/crates.io-index#regex-automata@0.4.9": "02092l8zfh3vkmk47yjc8d631zhhcd49ck2zr133prvd3z38v7l0",
"registry+https://github.com/rust-lang/crates.io-index#regex-syntax@0.8.5": "0p41p3hj9ww7blnbwbj9h7rwxzxg0c1hvrdycgys8rxyhqqw859b",
"registry+https://github.com/rust-lang/crates.io-index#regex@1.11.1": "148i41mzbx8bmq32hsj1q4karkzzx5m60qza6gdw4pdc9qdyyi5m",
"registry+https://github.com/rust-lang/crates.io-index#remove_dir_all@0.5.3": "1rzqbsgkmr053bxxl04vmvsd1njyz0nxvly97aip6aa2cmb15k9s",
"registry+https://github.com/rust-lang/crates.io-index#reqwest@0.11.27": "0qjary4hpplpgdi62d2m0xvbn6lnzckwffm0rgkm2x51023m6ryx",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7",
"registry+https://github.com/rust-lang/crates.io-index#rustc-demangle@0.1.24": "07zysaafgrkzy2rjgwqdj2a8qdpsm6zv6f5pgpk9x0lm40z9b6vi",
"registry+https://github.com/rust-lang/crates.io-index#rustc-hash@1.1.0": "1qkc5khrmv5pqi5l5ca9p5nl5hs742cagrndhbrlk3dhlrx3zm08",
"registry+https://github.com/rust-lang/crates.io-index#rustc_version@0.4.1": "14lvdsmr5si5qbqzrajgb6vfn69k0sfygrvfvr2mps26xwi3mjyg",
"registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.43": "1xjfhdnmqsbwnfmm77vyh7ldhqx0g9waqm4982404d7jdgp93257",
"registry+https://github.com/rust-lang/crates.io-index#rustls-pemfile@1.0.4": "1324n5bcns0rnw6vywr5agff3rwfvzphi7rmbyzwnv6glkhclx0w",
"registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.19": "1m39qd65jcd1xgqzdm3017ppimiggh2446xngwp1ngr8hjbmpi7p",
"registry+https://github.com/rust-lang/crates.io-index#rusty-fork@0.3.0": "0kxwq5c480gg6q0j3bg4zzyfh2kwmc3v2ba94jw8ncjc8mpcqgfb",
"registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.18": "17xx2s8j1lln7iackzd9p0sv546vjq71i779gphjq923vjh5pjzk",
"registry+https://github.com/rust-lang/crates.io-index#safemem@0.3.3": "0wp0d2b2284lw11xhybhaszsczpbq1jbdklkxgifldcknmy3nw7g",
"registry+https://github.com/rust-lang/crates.io-index#schannel@0.1.27": "0gbbhy28v72kd5iina0z2vcdl3vz63mk5idvkzn5r52z6jmfna8z",
"registry+https://github.com/rust-lang/crates.io-index#scoped-tls@1.0.1": "15524h04mafihcvfpgxd8f4bgc3k95aclz8grjkg9a0rxcvn9kz1",
"registry+https://github.com/rust-lang/crates.io-index#scoped_threadpool@0.1.9": "1a26d3lk40s9mrf4imhbik7caahmw2jryhhb6vqv6fplbbgzal8x",
"registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0": "0jcz9sd47zlsgcnm1hdw0664krxwb5gczlif4qngj2aif8vky54l",
"registry+https://github.com/rust-lang/crates.io-index#security-framework-sys@2.14.0": "0chwn01qrnvs59i5220bymd38iddy4krbnmfnhf4k451aqfj7ns9",
"registry+https://github.com/rust-lang/crates.io-index#security-framework@2.11.1": "00ldclwx78dm61v7wkach9lcx76awlrv0fdgjdwch4dmy12j4yw9",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@0.10.3": "0pci3zh23b7dg6jmlxbn8k4plb7hcg5jprd1qiz0rp04p1ilskp1",
"registry+https://github.com/rust-lang/crates.io-index#self_cell@1.1.0": "1gmxk5bvnnimcif7v1jk8ai2azfvh9djki545nd86vsnphjgrzf2",
"registry+https://github.com/rust-lang/crates.io-index#semver@1.0.24": "1fmvjjkd3f64y5fqr1nakkq371mnwzv09fbz5mbmdxril63ypdiw",
"registry+https://github.com/rust-lang/crates.io-index#serde@0.9.15": "1bsla8l5xr9pp5sirkal6mngxcq6q961km88jvf339j5ff8j7dil",
"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.218": "0q6z4bnrwagnms0bds4886711l6mc68s979i49zd3xnvkg8wkpz8",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.218": "0azqd74xbpb1v5vf6w1fdbgmwp39ljjfj25cib5rgrzlj7hh75gh",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.139": "19kj3irpa22a7djz1jaf4wambzh7psiqa6zyafqnb76crhx6ry24",
"registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.8": "1q89g70azwi4ybilz5jb8prfpa575165lmrffd49vmcf76qpqq47",
"registry+https://github.com/rust-lang/crates.io-index#serde_urlencoded@0.7.1": "1zgklbdaysj3230xivihs30qi5vkhigg323a9m62k8jwf4a1qjfk",
"registry+https://github.com/rust-lang/crates.io-index#serde_yml@0.0.12": "1p8xwz4znd6fj962y22fdvvv16gb8c0hx4iv5hjplngiidcdvqjr",
"registry+https://github.com/rust-lang/crates.io-index#sha1@0.10.6": "1fnnxlfg08xhkmwf2ahv634as30l1i3xhlhkvxflmasi5nd85gz3",
"registry+https://github.com/rust-lang/crates.io-index#sha2@0.10.8": "1j1x78zk9il95w9iv46dh9wm73r6xrgj32y6lzzw7bxws9dbfgbr",
"registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0": "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg",
"registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.2": "1cb5akgq8ajnd5spyn587srvs4n26ryq0p78nswffwhv46sf1sd9",
"registry+https://github.com/rust-lang/crates.io-index#signature@2.2.0": "1pi9hd5vqfr3q3k49k37z06p7gs5si0in32qia4mmr1dancr6m3p",
"registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.7": "1zkq40c3iajcnr5936gjp9jjh1lpzhy44p3dq3fiw75iwr1w2vfn",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@0.2.3": "1b53m53l24lyhr505lwqzrpjyq5qfnic71mynrcfvm43rybf938b",
"registry+https://github.com/rust-lang/crates.io-index#siphasher@1.0.1": "17f35782ma3fn6sh21c027kjmd227xyrx06ffi8gw4xzv9yry6an",
"registry+https://github.com/rust-lang/crates.io-index#slab@0.4.9": "0rxvsgir0qw5lkycrqgb1cxsvxzjv9bmx73bk5y42svnzfba94lg",
"registry+https://github.com/rust-lang/crates.io-index#smallvec@1.13.2": "0rsw5samawl3wsw6glrsb127rx6sh89a8wyikicw6dkdcjd1lpiw",
"registry+https://github.com/rust-lang/crates.io-index#snowflake@1.3.0": "1wadr7bxdxbmkbqkqsvzan6q1h3mxqpxningi3ss3v9jaav7n817",
"registry+https://github.com/rust-lang/crates.io-index#socket2@0.5.8": "1s7vjmb5gzp3iaqi94rh9r63k9cj00kjgbfn7gn60kmnk6fjcw69",
"registry+https://github.com/rust-lang/crates.io-index#spin@0.9.8": "0rvam5r0p3a6qhc18scqpvpgb3ckzyqxpgdfyjnghh8ja7byi039",
"registry+https://github.com/rust-lang/crates.io-index#spki@0.7.3": "17fj8k5fmx4w9mp27l970clrh5qa7r5sjdvbsln987xhb34dc7nr",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-core@0.8.3": "1q31dawr61wc6q2f12my4fw082mbv8sxwz1082msjsk76rlpn03a",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros-core@0.8.3": "1bg7sn6l8dc4pzrqx2dwc3sp7dbn97msfqahpycnl55bqnn917sf",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-macros@0.8.3": "047k67sylscv0gdhwwqrn0s33jy1mvq8rmqq6s8fygv4g2ny44ii",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-mysql@0.8.3": "0czjzzjm2y6lkhxvvzrzwgp0pmlhymcnym20hn9n9kh01s7jfq25",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-postgres@0.8.3": "04wnjl51kfx0qbfsfmhqdshpmw32vzz2p8dksmj6gvb3ydbqmff5",
"registry+https://github.com/rust-lang/crates.io-index#sqlx-sqlite@0.8.3": "0h05ca26g428h4337k4nm0ww75bcdkiqzp883m7fc92v78fsfp7q",
"registry+https://github.com/rust-lang/crates.io-index#sqlx@0.8.3": "0pvlpq0plgyxf5kikcv786pf0pjv8dx5shlvz72l510d7hxyf424",
"registry+https://github.com/rust-lang/crates.io-index#stable_deref_trait@1.2.0": "1lxjr8q2n534b2lhkxd6l6wcddzjvnksi58zv11f9y0jjmr15wd8",
"registry+https://github.com/rust-lang/crates.io-index#stringprep@0.1.5": "1cb3jis4h2b767csk272zw92lc6jzfzvh8d6m1cd86yqjb9z6kbv",
"registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1": "0kzvqlw8hxqb7y598w1s0hxlnmi84sg5vsipp3yg5na5d1rvba3x",
"registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1": "14ijxaymghbl1p0wql9cib5zlwiina7kall6w7g89csprkgbvhhk",
"registry+https://github.com/rust-lang/crates.io-index#syn@1.0.109": "0ds2if4600bd59wsv7jjgfkayfzy3hnazs394kz6zdkmna8l3dkj",
"registry+https://github.com/rust-lang/crates.io-index#syn@2.0.96": "102wk3cgawimi3i0q3r3xw3i858zkyingg6y7gsxfy733amsvl6m",
"registry+https://github.com/rust-lang/crates.io-index#sync_wrapper@0.1.2": "0q01lyj0gr9a93n10nxsn8lwbzq97jqd6b768x17c8f7v7gccir0",
"registry+https://github.com/rust-lang/crates.io-index#synstructure@0.13.1": "0wc9f002ia2zqcbj0q2id5x6n7g1zjqba7qkg2mr0qvvmdk7dby8",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration-sys@0.5.0": "1jckxvdr37bay3i9v52izgy52dg690x5xfg3hd394sv2xf4b2px7",
"registry+https://github.com/rust-lang/crates.io-index#system-configuration@0.5.1": "1rz0r30xn7fiyqay2dvzfy56cvaa3km74hnbz2d72p97bkf3lfms",
"registry+https://github.com/rust-lang/crates.io-index#system-deps@6.2.2": "0j93ryw031n3h8b0nfpj5xwh3ify636xmv8kxianvlyyipmkbrd3",
"registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.12.16": "1cg3bnx1gdkdr5hac1hzxy64fhw4g7dqkd0n3dxy5lfngpr1mi31",
"registry+https://github.com/rust-lang/crates.io-index#tempdir@0.3.7": "1n5n86zxpgd85y0mswrp5cfdisizq2rv3la906g6ipyc03xvbwhm",
"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.17.1": "0c52ggq5vy5mzgk5ly36cgzs1cig3cv6r1jarijmzxgkn6na1r92",
"registry+https://github.com/rust-lang/crates.io-index#termcolor@1.4.1": "0mappjh3fj3p2nmrg4y7qv94rchwi9mzmgmfflr8p2awdj7lyy86",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@1.0.69": "1h84fmn2nai41cxbhk6pqf46bxqq1b344v8yz089w1chzi76rvjg",
"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.11": "1hkkn7p2y4cxbffcrprybkj0qy1rl1r6waxmxqvr764axaxc3br6",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@1.0.69": "0lizjay08agcr5hs9yfzzj6axs53a2rgx070a1dsi3jpkcrzbamn",
"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.11": "1z0649rpa8c2smzx129bz4qvxmdihj30r2km6vfpcv9yny2g4lnl",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.6.1": "0ds48vs919ccxa3fv1www7788pzkvpg434ilqkq7sjb5dmqg8lws",
"registry+https://github.com/rust-lang/crates.io-index#tiff@0.9.1": "0ghyxlz566dzc3scvgmzys11dhq2ri77kb8sznjakijlxby104xs",
"registry+https://github.com/rust-lang/crates.io-index#time-core@0.1.2": "1wx3qizcihw6z151hywfzzyd1y5dl804ydyxci6qm07vbakpr4pg",
"registry+https://github.com/rust-lang/crates.io-index#time-macros@0.2.19": "1pl558z26pp342l5y91n6dxb60xwhar975wk6jc4npiygq0ycd18",
"registry+https://github.com/rust-lang/crates.io-index#time@0.1.45": "0nl0pzv9yf56djy8y5dx25nka5pr2q1ivlandb3d24pksgx7ly8v",
"registry+https://github.com/rust-lang/crates.io-index#time@0.3.37": "08bvydyc14plkwhchzia5bcdbmm0mk5fzilsdpjx06w6hf48drrm",
"registry+https://github.com/rust-lang/crates.io-index#tinystr@0.7.6": "0bxqaw7z8r2kzngxlzlgvld1r6jbnwyylyvyjbv1q71rvgaga5wi",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec@1.8.1": "1s41rv7n39sjsxz3kd3d4adw45ndkxz1d18rfbz2wd7s9n8bhb82",
"registry+https://github.com/rust-lang/crates.io-index#tinyvec_macros@0.1.1": "081gag86208sc3y6sdkshgw3vysm5d34p431dzw0bshz66ncng0z",
"registry+https://github.com/rust-lang/crates.io-index#tokio-macros@2.5.0": "1f6az2xbvqp7am417b78d1za8axbvjvxnmkakz9vr8s52czx81kf",
"registry+https://github.com/rust-lang/crates.io-index#tokio-native-tls@0.3.1": "1wkfg6zn85zckmv4im7mv20ca6b1vmlib5xwz9p7g19wjfmpdbmv",
"registry+https://github.com/rust-lang/crates.io-index#tokio-stream@0.1.17": "0ix0770hfp4x5rh5bl7vsnr3d4iz4ms43i522xw70xaap9xqv9gc",
"registry+https://github.com/rust-lang/crates.io-index#tokio-tungstenite@0.21.0": "0f5wj0crsx74rlll97lhw0wk6y12nhdnqvmnjx002hjn08fmcfy8",
"registry+https://github.com/rust-lang/crates.io-index#tokio-util@0.7.13": "0y0h10a52c7hrldmr3410bp7j3fadq0jn9nf7awddgd2an6smz6p",
"registry+https://github.com/rust-lang/crates.io-index#tokio@1.43.0": "17pdm49ihlhfw3rpxix3kdh2ppl1yv7nwp1kxazi5r1xz97zlq9x",
"registry+https://github.com/rust-lang/crates.io-index#toml@0.8.20": "0j012b37iz1mihksr6a928s6dzszxvblzg3l5wxp7azzsv6sb1yd",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.8": "0hgv7v9g35d7y9r2afic58jvlwnf73vgd1mz2k8gihlgrf73bmqd",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.19.15": "08bl7rp5g6jwmfpad9s8jpw8wjrciadpnbaswgywpr9hv9qbfnqv",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.20.2": "0f7k5svmxw98fhi28jpcyv7ldr2s3c867pjbji65bdxjpd44svir",
"registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.24": "0x0lgp70x5cl9nla03xqs5vwwwlrwmd0djkdrp3h3lpdymgpkd0p",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
"registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.28": "0v92l9cxs42rdm4m5hsa8z7ln1xsiw1zc2iil8c6k7lzq0jf2nir",
"registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.33": "170gc7cxyjx824r9kr17zc9gvzx89ypqfdzq259pr56gg5bwjwp6",
"registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.41": "1l5xrzyjfyayrwhvhldfnwdyligi1mpqm8mzbi2m1d6y6p2hlkkq",
"registry+https://github.com/rust-lang/crates.io-index#traitobject@0.1.0": "0yb0n8822mr59j200fyr2fxgzzgqljyxflx9y8bdy3rlaqngilgg",
"registry+https://github.com/rust-lang/crates.io-index#try-lock@0.2.5": "0jqijrrvm1pyq34zn1jmy2vihd4jcrjlvsh4alkjahhssjnsn8g4",
"registry+https://github.com/rust-lang/crates.io-index#tungstenite@0.21.0": "1qaphb5kgwgid19p64grhv2b9kxy7f1059yy92l9kwrlx90sdwcy",
"registry+https://github.com/rust-lang/crates.io-index#type-map@0.5.0": "17qaga12nkankr7hi2mv43f4lnc78hg480kz6j9zmy4g0h28ddny",
"registry+https://github.com/rust-lang/crates.io-index#typeable@0.1.2": "11w8dywgnm32hb291izjvh4zjd037ccnkk77ahk63l913zwzc40l",
"registry+https://github.com/rust-lang/crates.io-index#typemap@0.3.3": "1xm1gbvz9qisj1l6d36hrl9pw8imr8ngs6qyanjnsad3h0yfcfv5",
"registry+https://github.com/rust-lang/crates.io-index#typenum@1.17.0": "09dqxv69m9lj9zvv6xw5vxaqx15ps0vxyy5myg33i0kbqvq0pzs2",
"registry+https://github.com/rust-lang/crates.io-index#typeshare-annotation@1.0.4": "0kx38ah6638pkqq5cac7nmvbg6x43v7fj5jgibla4lj8fv1dc5d6",
"registry+https://github.com/rust-lang/crates.io-index#typeshare@1.0.4": "1svc92lg35r12mqdpbs4wbkw7g72v2302niyw5v1w290250hzghr",
"registry+https://github.com/rust-lang/crates.io-index#unarray@0.1.4": "154smf048k84prsdgh09nkm2n0w0336v84jd4zikyn6v6jrqbspa",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid-impl@0.9.5": "1rckyn5wqd5h8jxhbzlbbagr459zkzg822r4k5n30jaryv0j4m0a",
"registry+https://github.com/rust-lang/crates.io-index#unic-langid@0.9.5": "0i2s024frmpfa68lzy8y8vnb1rz3m9v0ga13f7h2afx7f8g9vp93",
"registry+https://github.com/rust-lang/crates.io-index#unicase@1.4.2": "0cwazh4qsmm9msckjk86zc1z35xg7hjxjykrgjalzdv367w6aivz",
"registry+https://github.com/rust-lang/crates.io-index#unicase@2.8.1": "0fd5ddbhpva7wrln2iah054ar2pc1drqjcll0f493vj3fv8l9f3m",
"registry+https://github.com/rust-lang/crates.io-index#unicode-bidi@0.3.18": "1xcxwbsqa24b8vfchhzyyzgj0l6bn51ib5v8j6krha0m77dva72w",
"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.14": "10ywa1pg0glgkr4l3dppjxizr9r2b7im0ycbfa0137l69z5fdfdd",
"registry+https://github.com/rust-lang/crates.io-index#unicode-normalization@0.1.24": "0mnrk809z3ix1wspcqy97ld5wxdb31f3xz6nsvg5qcv289ycjcsh",
"registry+https://github.com/rust-lang/crates.io-index#unicode-properties@0.1.3": "1l3mbgzwz8g14xcs09p4ww3hjkjcf0i1ih13nsg72bhj8n5jl3z7",
"registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0": "14qla2jfx74yyb9ds3d2mpwpa4l4lzb9z57c6d2ba511458z5k7n",
"registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14": "1bzn2zv0gp8xxbxbhifw778a7fc93pa6a1kj24jgg9msj07f7mkx",
"registry+https://github.com/rust-lang/crates.io-index#unsafe-any@0.4.2": "0zwwphsqkw5qaiqmjwngnfpv9ym85qcsyj7adip9qplzjzbn00zk",
"registry+https://github.com/rust-lang/crates.io-index#url@1.7.2": "0nim1c90mxpi9wgdw2xh8dqd72vlklwlzam436akcrhjac6pqknx",
"registry+https://github.com/rust-lang/crates.io-index#url@2.5.4": "0q6sgznyy2n4l5lm16zahkisvc9nip9aa5q1pps7656xra3bdy1j",
"registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6": "1a9ns3fvgird0snjkd3wbdhwd3zdpc2h5gpyybrfr6ra5pkqxk09",
"registry+https://github.com/rust-lang/crates.io-index#utf16_iter@1.0.5": "0ik2krdr73hfgsdzw0218fn35fa09dg2hvbi1xp3bmdfrp9js8y8",
"registry+https://github.com/rust-lang/crates.io-index#utf8_iter@1.0.4": "1gmna9flnj8dbyd8ba17zigrp9c4c3zclngf5lnb5yvz1ri41hdn",
"registry+https://github.com/rust-lang/crates.io-index#utf8parse@0.2.2": "088807qwjq46azicqwbhlmzwrbkz7l4hpw43sdkdyyk524vdxaq6",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.4.0": "0cdj2v6v2yy3zyisij69waksd17cyir1n58kwyk1n622105wbzkw",
"registry+https://github.com/rust-lang/crates.io-index#uuid@0.8.2": "1dy4ldcp7rnzjy56dxh7d2sgrcvn4q77y0a8r0a48946h66zjp5w",
"registry+https://github.com/rust-lang/crates.io-index#uuid@1.12.0": "1i2i7ar5651d58ip1l8cghg3y60pn0rqmssvw6lm8d4s3xc1hh3l",
"registry+https://github.com/rust-lang/crates.io-index#value-bag@1.10.0": "1lnsixdpi1ldms1adxyafyx7lyrqxhhskgwrjckmml6majmc9x1y",
"registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15": "09i4nf5y8lig6xgj3f7fyrvzd3nlaw4znrihw8psidvv5yk4xkdc",
"registry+https://github.com/rust-lang/crates.io-index#version-compare@0.2.0": "12y9262fhjm1wp0aj3mwhads7kv0jz8h168nn5fb8b43nwf9abl5",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.1.5": "1pf91pvj8n6akh7w6j5ypka6aqz08b3qpzgs0ak2kjf4frkiljwi",
"registry+https://github.com/rust-lang/crates.io-index#version_check@0.9.5": "0nhhi4i5x89gm911azqbn7avs9mdacw2i3vcz3cnmz3mv4rqz4hb",
"registry+https://github.com/rust-lang/crates.io-index#wait-timeout@0.2.0": "1xpkk0j5l9pfmjfh1pi0i89invlavfrd9av5xp0zhxgb29dhy84z",
"registry+https://github.com/rust-lang/crates.io-index#want@0.3.1": "03hbfrnvqqdchb5kgxyavb9jabwza0dmh2vw5kg0dq8rxl57d9xz",
"registry+https://github.com/rust-lang/crates.io-index#warp@0.3.7": "07137zd13lchy5hxpspd0hs6sl19b0fv2zc1chf02nwnzw1d4y23",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.10.0+wasi-snapshot-preview1": "07y3l8mzfzzz4cj09c8y90yak4hpsi9g7pllyzpr6xvwrabka50s",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.11.0+wasi-snapshot-preview1": "08z4hxwkpdpalxjps1ai9y7ihin26y9f476i53dv98v45gkqg3cw",
"registry+https://github.com/rust-lang/crates.io-index#wasi@0.13.3+wasi-0.2.2": "1lnapbvdcvi3kc749wzqvwrpd483win2kicn1faa4dja38p6v096",
"registry+https://github.com/rust-lang/crates.io-index#wasite@0.1.0": "0nw5h9nmcl4fyf4j5d4mfdjfgvwi1cakpi349wc4zrr59wxxinmq",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-backend@0.2.100": "1ihbf1hq3y81c4md9lyh6lcwbx6a5j0fw4fygd423g62lm8hc2ig",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-futures@0.4.50": "0q8ymi6i9r3vxly551dhxcyai7nc491mspj0j1wbafxwq074fpam",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro-support@0.2.100": "1plm8dh20jg2id0320pbmrlsv6cazfv6b6907z19ys4z1jj7xs4a",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-macro@0.2.100": "01xls2dvzh38yj17jgrbiib1d3nyad7k2yw9s0mpklwys333zrkz",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen-shared@0.2.100": "0gffxvqgbh9r9xl36gprkfnh3w9gl8wgia6xrin7v11sjcxxf18s",
"registry+https://github.com/rust-lang/crates.io-index#wasm-bindgen@0.2.100": "1x8ymcm6yi3i1rwj78myl1agqv2m86i648myy3lc97s9swlqkp0y",
"registry+https://github.com/rust-lang/crates.io-index#web-sys@0.3.77": "1lnmc1ffbq34qw91nndklqqm75rasaffj2g4f8h1yvqqz4pdvdik",
"registry+https://github.com/rust-lang/crates.io-index#weezl@0.1.8": "10lhndjgs6y5djpg3b420xngcr6jkmv70q8rb1qcicbily35pa2k",
"registry+https://github.com/rust-lang/crates.io-index#whoami@1.5.2": "0vdvm6sga4v9515l6glqqfnmzp246nq66dd09cw5ri4fyn3mnb9p",
"registry+https://github.com/rust-lang/crates.io-index#winapi-i686-pc-windows-gnu@0.4.0": "1dmpa6mvcvzz16zg6d5vrfy4bxgg541wxrcip7cnshi06v38ffxc",
"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.9": "1fqhkcl9scd230cnfj8apfficpf5c9vhwnk4yy9xfc1sw69iq8ng",
"registry+https://github.com/rust-lang/crates.io-index#winapi-x86_64-pc-windows-gnu@0.4.0": "0gqq64czqb64kskjryj8isp62m2sgvx25yyj3kpc2myh85w24bki",
"registry+https://github.com/rust-lang/crates.io-index#winapi@0.3.9": "06gl025x418lchw1wxj64ycr7gha83m44cjr5sarhynd9xkrm0sw",
"registry+https://github.com/rust-lang/crates.io-index#windows-core@0.52.0": "1nc3qv7sy24x0nlnb32f7alzpd6f72l4p24vl65vydbyil669ark",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.48.0": "1aan23v5gs7gya1lc46hqn9mdh8yph3fhxmhxlw36pn6pqc28zb7",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.52.0": "0gd3v4ji88490zgb6b5mq5zgbvwv7zx1ibn8v3x83rwcdbryaar8",
"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.59.0": "0fw5672ziw8b3zpmnbp9pdv1famk74f1l9fcbc3zsrzdg56vqf0y",
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.48.5": "034ljxqshifs1lan89xwpcy1hp0lhdh4b5n0d2z4fwjx2piacbws",
"registry+https://github.com/rust-lang/crates.io-index#windows-targets@0.52.6": "0wwrx625nwlfp7k93r2rra568gad1mwd888h1jwnl0vfg5r4ywlv",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.48.5": "1n05v7qblg1ci3i567inc7xrkmywczxrs1z3lj3rkkxw18py6f1b",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_gnullvm@0.52.6": "1lrcq38cr2arvmz19v32qaggvj8bh1640mdm9c2fr877h0hn591j",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.48.5": "1g5l4ry968p73g6bg6jgyvy9lb8fyhcs54067yzxpcpkf44k2dfw",
"registry+https://github.com/rust-lang/crates.io-index#windows_aarch64_msvc@0.52.6": "0sfl0nysnz32yyfh773hpi49b1q700ah6y7sacmjbqjjn5xjmv09",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.48.5": "0gklnglwd9ilqx7ac3cn8hbhkraqisd0n83jxzf9837nvvkiand7",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnu@0.52.6": "02zspglbykh1jh9pi7gn8g1f97jh1rrccni9ivmrfbl0mgamm6wf",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_gnullvm@0.52.6": "0rpdx1537mw6slcpqa0rm3qixmsb79nbhqy5fsm3q2q9ik9m5vhf",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.48.5": "01m4rik437dl9rdf0ndnm2syh10hizvq0dajdkv2fjqcywrw4mcg",
"registry+https://github.com/rust-lang/crates.io-index#windows_i686_msvc@0.52.6": "0rkcqmp4zzmfvrrrx01260q3xkpzi6fzi2x2pgdcdry50ny4h294",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.48.5": "13kiqqcvz2vnyxzydjh73hwgigsdr2z1xpzx313kxll34nyhmm2k",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnu@0.52.6": "0y0sifqcb56a56mvn7xjgs8g43p33mfqkd8wj1yhrgxzma05qyhl",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.48.5": "1k24810wfbgz8k48c2yknqjmiigmql6kk3knmddkv8k8g1v54yqb",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_gnullvm@0.52.6": "03gda7zjx1qh8k9nnlgb7m3w3s1xkysg55hkd1wjch8pqhyv5m94",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.48.5": "0f4mdp895kkjh9zv8dxvn4pc10xr7839lf5pa9l0193i2pkgr57d",
"registry+https://github.com/rust-lang/crates.io-index#windows_x86_64_msvc@0.52.6": "1v7rb5cibyzx8vak29pdrk8nx9hycsjs4w0jgms08qk49jl6v7sq",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.5.40": "0xk8maai7gyxda673mmw3pj1hdizy5fpi7287vaywykkk19sk4zm",
"registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.3": "1c9bmhpdwbdmll6b4l6skabz0296dchnmnxw84hh2y3ggyllwzqf",
"registry+https://github.com/rust-lang/crates.io-index#winreg@0.50.0": "1cddmp929k882mdh6i9f2as848f13qqna6czwsqzkh1pqnr5fkjj",
"registry+https://github.com/rust-lang/crates.io-index#wit-bindgen-rt@0.33.0": "0g4lwfp9x6a2i1hgjn8k14nr4fsnpd5izxhc75zpi2s5cvcg6s1j",
"registry+https://github.com/rust-lang/crates.io-index#write16@1.0.0": "0dnryvrrbrnl7vvf5vb1zkmwldhjkf2n5znliviam7bm4900z2fi",
"registry+https://github.com/rust-lang/crates.io-index#writeable@0.5.5": "0lawr6y0bwqfyayf3z8zmqlhpnzhdx0ahs54isacbhyjwa7g778y",
"registry+https://github.com/rust-lang/crates.io-index#xml-rs@0.8.25": "1i73ajf6scni5bi1a51r19xykgrambdx5fkks0fyg5jqqbml1ff5",
"registry+https://github.com/rust-lang/crates.io-index#yansi-term@0.1.2": "1w8vjlvxba6yvidqdvxddx3crl6z66h39qxj8xi6aqayw2nk0p7y",
"registry+https://github.com/rust-lang/crates.io-index#yoke-derive@0.7.5": "0m4i4a7gy826bfvnqa9wy6sp90qf0as3wps3wb0smjaamn68g013",
"registry+https://github.com/rust-lang/crates.io-index#yoke@0.7.5": "0h3znzrdmll0a7sglzf9ji0p5iqml11wrj1dypaf6ad6kbpnl3hj",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy-derive@0.7.35": "0gnf2ap2y92nwdalzz3x7142f2b83sni66l39vxp2ijd6j080kzs",
"registry+https://github.com/rust-lang/crates.io-index#zerocopy@0.7.35": "1w36q7b9il2flg0qskapgi9ymgg7p985vniqd09vi0mwib8lz6qv",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom-derive@0.1.5": "022q55phhb44qbrcfbc48k0b741fl8gnazw3hpmmndbx5ycfspjr",
"registry+https://github.com/rust-lang/crates.io-index#zerofrom@0.1.5": "0bnd8vjcllzrvr3wvn8x14k2hkrpyy1fm3crkn2y3plmr44fxwyg",
"registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.1": "1pjdrmjwmszpxfd7r860jx54cyk94qk59x13sc307cvr5256glyf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec-derive@0.10.3": "1ik322dys6wnap5d3gcsn09azmssq466xryn5czfm13mn7gsdbvf",
"registry+https://github.com/rust-lang/crates.io-index#zerovec@0.10.4": "0yghix7n3fjfdppwghknzvx9v8cf826h2qal5nqvy8yzg4yqjaxa",
"registry+https://github.com/rust-lang/crates.io-index#zune-inflate@0.2.54": "00kg24jh3zqa3i6rg6yksnb71bch9yi1casqydl00s7nw8pk7avk"
"git+https://github.com/yewstack/yew/#yew-macro@0.21.0": "1g47mpyzd2mib73cjrbmcivrp7kr16f6hbrmpaap56kbc518khwf",
"git+https://github.com/yewstack/yew/#yew@0.21.0": "1g47mpyzd2mib73cjrbmcivrp7kr16f6hbrmpaap56kbc518khwf"
}

View File

@ -7,9 +7,9 @@ license = "GPL-3.0-only"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-std = "1.13.0"
cairo-rs = { version = "0.18" }
async-std = { workspace = true }
cairo-rs = { workspace = true }
cyberpunk = { path = "../cyberpunk" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }
gio = { workspace = true }
glib = { workspace = true }
gtk = { workspace = true }

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cairo-rs = { version = "0.18" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gtk = { version = "0.7", package = "gtk4" }
cairo-rs = { workspace = true }
gio = { workspace = true }
glib = { workspace = true }
gtk = { workspace = true }

View File

@ -6,20 +6,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_4" ] }
async-channel = { version = "2.1" }
async-trait = { version = "0.1" }
chrono = { version = "0.4" }
chrono-tz = { version = "0.8" }
dimensioned = { version = "0.8", features = [ "serde" ] }
adw = { workspace = true }
async-channel = { workspace = true }
async-trait = { workspace = true }
chrono = { workspace = true }
chrono-tz = { workspace = true }
dimensioned = { workspace = true }
emseries = { path = "../../emseries" }
ft-core = { path = "../core" }
gio = { version = "0.18" }
glib = { version = "0.18" }
gdk = { version = "0.7", package = "gdk4" }
gtk = { version = "0.7", package = "gtk4", features = [ "v4_10" ] }
thiserror = { version = "1.0" }
tokio = { version = "1.34", features = [ "full" ] }
gio = { workspace = true }
glib = { workspace = true }
gdk = { workspace = true }
gtk = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
[build-dependencies]
glib-build-tools = "0.18"

View File

@ -6,12 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4" }
chrono-tz = { version = "0.8" }
dimensioned = { version = "0.8", features = [ "serde" ] }
chrono = { workspace = true }
chrono-tz = { workspace = true }
dimensioned = { workspace = true }
emseries = { path = "../../emseries" }
serde = { version = "1", features = [ "derive" ] }
serde_json = { version = "1" }
serde = { workspace = true }
serde_json = { workspace = true }
[dev-dependencies]
tempfile = "*"

16
flake.lock generated
View File

@ -1,20 +1,5 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1739936662,
"narHash": "sha256-x4syUjNUuRblR07nDPeLDP7DpphaBVbUaSoeZkFbGSk=",
"owner": "ipetkov",
"repo": "crane",
"rev": "19de14aaeb869287647d9461cbd389187d8ecdb7",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@ -65,7 +50,6 @@
},
"root": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs",
"typeshare": "typeshare",
"unstable": "unstable"

View File

@ -5,10 +5,9 @@
nixpkgs.url = "nixpkgs/nixos-24.11";
unstable.url = "nixpkgs/nixos-unstable";
typeshare.url = "github:1Password/typeshare";
crane.url = "github:ipetkov/crane";
};
outputs = { self, nixpkgs, unstable, typeshare, crane, ... }:
outputs = { self, nixpkgs, unstable, typeshare, ... }:
let
version = builtins.string 0 8 self.lastModifiedDate;
supportedSystems = [ "x86_64-linux" ];
@ -26,6 +25,7 @@
pkgs.cargo-watch
pkgs.clang
pkgs.crate2nix
pkgs.trunk
pkgs.glib
pkgs.gst_all_1.gst-plugins-bad
pkgs.gst_all_1.gst-plugins-base
@ -57,9 +57,6 @@
packages."x86_64-linux" =
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
craneLib = crane.mkLib pkgs;
src = craneLib.cleanCargoSource ./.;
gtkNativeInputs = [
pkgs.pkg-config
pkgs.gtk4
@ -86,28 +83,24 @@
};
in rec {
cyber-slides = cargo_nix.workspaceMembers.cyber-slides.build;
# cyber-slides = cargo_nix.workspaceMembers.cyber-slides.build;
cyberpunk-splash = cargo_nix.workspaceMembers.cyberpunk-splash.build;
dashboard = cargo_nix.workspaceMembers.dashboard.build;
# dashboard = cargo_nix.workspaceMembers.dashboard.build;
# file-service = cargo_nix.workspaceMembers.file-service.build;
fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build;
l10n-db = craneLib.buildPackage {
pname = "l10n-db";
cargoExtraArgs = "-p l10n-db";
src = ./.;
};
otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
l10n-db = cargo_nix.workspaceMembers.l10n-db.build;
# otg-gtk = cargo_nix.workspaceMembers.otg-gtk.build;
all = pkgs.symlinkJoin {
name = "all";
paths = [
cyber-slides
# cyber-slides
cyberpunk-splash
dashboard
# dashboard
# file-service
fitnesstrax
l10n-db
otg-gtk
# otg-gtk
];
};

View File

@ -8,8 +8,6 @@
// e c
// d d d
use alloc::vec::Vec;
use crate::font::{Font, Glyph};
pub struct RGB {
@ -20,15 +18,10 @@ pub struct RGB {
pub trait Canvas {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB);
fn buf(&self) -> &[u8];
fn partial(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> impl Iterator<Item = &[u8]>;
fn clean(&mut self);
fn dirty(&self) -> Option<(usize, usize, usize, usize)>;
fn width(&self) -> usize;
fn fill(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 {
for y in y1..y2 {
for x in x1..x2 + 1 {
for y in y1..y2 + 1 {
self.set_pixel(x, y, color);
}
}

View File

@ -1,42 +0,0 @@
use embedded_hal::i2c::{I2c, Error};
const ADDR: u8 = 0x39;
pub struct LightSensor<I2C> {
i2c: I2C
}
impl <I2C: I2c> LightSensor<I2C> {
pub fn new(i2c: I2C) -> Self {
Self { i2c }
}
pub fn control(&mut self) -> Result<u8, I2C::Error> {
let mut ctrl = [0];
self.i2c.write_read(ADDR, &[0x80], &mut ctrl)?;
Ok(ctrl[0])
}
pub fn power_up(&mut self) -> Result<(), I2C::Error> {
self.i2c.write(ADDR, &[0x80, 0x03])?;
Ok(())
}
pub fn read_full_spectrum(&mut self) -> Result<u16, I2C::Error> {
let mut light_parts = [0, 0];
self.i2c.write_read(ADDR, &[0xAC], &mut light_parts)?;
let ll: u16 = light_parts[0].into();
let lh: u16 = light_parts[1].into();
let light: u16 = 256 * lh + ll;
Ok(light)
}
pub fn read_ir(&mut self) -> Result<u16, I2C::Error> {
let mut light_parts = [0, 0];
self.i2c.write_read(ADDR, &[0xAE], &mut light_parts)?;
let ll: u16 = light_parts[0].into();
let lh: u16 = light_parts[1].into();
let light: u16 = 256 * lh + ll;
Ok(light)
}
}

View File

@ -3,38 +3,23 @@
extern crate alloc;
use alloc::{
fmt::format,
vec::{self, Vec},
};
use alloc::fmt::format;
use embedded_alloc::LlffHeap as Heap;
use embedded_hal::{delay::DelayNs, digital::OutputPin};
use fugit::RateExtU32;
use light_sensor::LightSensor;
use panic_halt as _;
use rp_pico::{
entry,
hal::{
clocks::init_clocks_and_plls,
gpio::{
bank0::{Gpio20, Gpio21},
FunctionI2C, Pin, PullUp,
},
i2c::I2C,
spi::Spi,
Clock, Sio, Timer, Watchdog,
},
hal::{clocks::init_clocks_and_plls, spi::Spi, Clock, Sio, Timer, Watchdog},
pac, Pins,
};
mod canvas;
use canvas::{print, Canvas, RGB};
use canvas::{Canvas, RGB, print};
mod font;
use font::{BitmapFont, Font, Glyph, SevenSegmentFont, SixteenSegmentFont};
mod light_sensor;
mod st7789;
use st7789::{ST7789Display, SETUP_PROGRAM};
@ -51,7 +36,6 @@ static mut BUF: [u8; 163200] = [0; 163200];
pub struct FrameBuf {
pub buf: &'static mut [u8; 163200],
pub dirty: Option<(usize, usize, usize, usize)>,
pub width: usize,
}
@ -59,7 +43,6 @@ impl FrameBuf {
pub fn new() -> Self {
Self {
buf: unsafe { &mut BUF },
dirty: None,
width: 170,
}
}
@ -67,51 +50,9 @@ impl FrameBuf {
impl Canvas for FrameBuf {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB) {
let addr = y * self.width + x;
self.buf[addr * 3 + 0] = color.r << 2;
self.buf[addr * 3 + 1] = color.g << 2;
self.buf[addr * 3 + 2] = color.b << 2;
if let Some((ref mut x1, ref mut y1, ref mut x2, ref mut y2)) = self.dirty {
if x < *x1 {
*x1 = x
}
if y < *y1 {
*y1 = y
}
if x > *x2 {
*x2 = x
}
if y > *y2 {
*y2 = y
}
} else {
self.dirty = Some((x, y, x, y));
}
}
fn buf(&self) -> &[u8] {
self.buf
}
fn partial(&self, x1: usize, y1: usize, x2: usize, y2: usize) -> impl Iterator<Item = &[u8]> {
(y1..y2 + 1).map(move |y| {
let start = (y * self.width + x1) * 3;
let end = (y * self.width + x2) * 3;
&self.buf[start..end]
})
}
fn clean(&mut self) {
self.dirty = None;
}
fn dirty(&self) -> Option<(usize, usize, usize, usize)> {
self.dirty
}
fn width(&self) -> usize {
self.width
self.buf[(y * self.width + x) * 3 + 0] = color.r << 2;
self.buf[(y * self.width + x) * 3 + 1] = color.g << 2;
self.buf[(y * self.width + x) * 3 + 2] = color.b << 2;
}
}
@ -175,9 +116,6 @@ unsafe fn main() -> ! {
let mut data_command = pins.gpio15.into_function();
let mut reset = pins.gpio14.into_function();
let i2c_clk: Pin<Gpio21, FunctionI2C, PullUp> = pins.gpio21.reconfigure();
let i2c_sdo: Pin<Gpio20, FunctionI2C, PullUp> = pins.gpio20.reconfigure();
let _ = reset.set_low();
let _ = board_select.set_high();
let _ = data_command.set_high();
@ -194,17 +132,6 @@ unsafe fn main() -> ! {
embedded_hal::spi::MODE_3,
);
let i2c = I2C::i2c0(
peripherals.I2C0,
i2c_sdo,
i2c_clk,
400.kHz(),
&mut peripherals.RESETS,
125_000_000.Hz(),
);
let mut light_sensor = LightSensor::new(i2c);
let mut display = ST7789Display::new(board_select, data_command, spi);
let _ = reset.set_high();
@ -221,124 +148,37 @@ unsafe fn main() -> ! {
let mut canvas = Canvas::new(&mut framebuf, COLUMNS);
*/
let mut canvas = FrameBuf::new();
let white = RGB {
r: 63,
g: 63,
b: 63,
};
let white = RGB { r: 63, g: 63, b: 63 };
let mut count = 0;
light_sensor.power_up();
match light_sensor.control() {
Ok(v) if v == 0 => print(
&mut canvas,
&font_bitmap,
0,
0,
&format(format_args!("POWER_DOWN")),
&RGB {
r: 63,
g: 16,
b: 16,
},
),
Ok(_) => {}
Err(err) => print(
&mut canvas,
&font_bitmap,
0,
0,
&format(format_args!("ERROR: {:?}", err)),
&RGB {
r: 63,
g: 16,
b: 16,
},
),
}
{
let display = display.acquire();
display.blit_frame(&canvas);
}
loop {
canvas.fill(0, 0, 170, 60, &RGB { r: 0, g: 0, b: 0 });
if let Some((x1, y1, x2, y2)) = canvas.dirty() {
print(
&mut canvas,
&font_bitmap,
0,
0,
&format(format_args!("{} {} {} {}", x1, y1, x2, y2)),
&RGB {
r: 63,
g: 63,
b: 63,
},
);
}
canvas.fill(0, 10, 170, 20, &RGB{r: 0, g: 0, b: 0 });
print(&mut canvas, &font_bitmap, 1, 10, &format(format_args!("COUNT: {:03}", count)), &RGB{ r: 32, g: 32, b: 63 });
print(&mut canvas, &font_bitmap, 1, 200, " !\"#$%&'<>*+,-./", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 220, "0123456789|: = ?", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 240, "@ABCDEFGHIJKLMNO", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 260, "PQRSTUVWXYZ[\\]^_", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 280, "`abcdefghijklmno", &RGB{ r: 63, g: 63, b: 63 });
print(&mut canvas, &font_bitmap, 1, 300, "pqrstuvwxyz{|}", &RGB{ r: 63, g: 63, b: 63 });
match light_sensor.read_full_spectrum() {
Ok(full) => print(
&mut canvas,
&font_bitmap,
0,
20,
&format(format_args!("FULL: {}", full)),
&RGB {
r: 63,
g: 63,
b: 63,
},
),
Err(err) => print(
&mut canvas,
&font_bitmap,
0,
20,
&format(format_args!("FULL: {:?}", err)),
&RGB {
r: 63,
g: 16,
b: 16,
},
),
}
match light_sensor.read_ir() {
Ok(ir) => print(
&mut canvas,
&font_bitmap,
0,
40,
&format(format_args!("IR: {}", ir)),
&RGB {
r: 63,
g: 63,
b: 63,
},
),
Err(err) => print(
&mut canvas,
&font_bitmap,
0,
40,
&format(format_args!("IR: {:?}", err)),
&RGB {
r: 63,
g: 16,
b: 16,
},
),
}
// canvas.square(10, 70, 160, 310, &white);
{
let display = display.acquire();
let _ = led.set_high();
display.blit_dirty(&canvas);
timer.delay_ms(100);
display.send_buf(canvas.buf);
let _ = led.set_low();
}
count = count + 1;
/*
for x in 80..90 {
for y in 155..165 {
draw_pixel(&mut frame, x, y, (0, 0, 63));
}
}
*/
timer.delay_ms(100);
timer.delay_ms(1000);
}
}

View File

@ -5,8 +5,6 @@ use rp_pico::hal::{
Spi, Timer,
};
use crate::canvas::Canvas;
pub struct Step {
param_cnt: usize,
command: u8,
@ -187,52 +185,14 @@ impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiP
}
}
pub fn blit_frame(&mut self, frame: &impl Canvas) {
pub fn send_buf(&mut self, frame: &[u8]) {
// let _ = DISPOFF.send_command(&mut self.spi, &mut self.data_command);
let _ = self.data_command.set_low();
let _ = self.spi.write(&[RAMWR]);
let _ = self.data_command.set_high();
let _ = self.spi.write(frame.buf());
let _ = self.spi.write(&frame);
// let _ = DISPON.send_command(&mut self.spi, &mut self.data_command);
}
pub fn blit_dirty(&mut self, frame: &impl Canvas) {
if let Some((start_x, start_y, end_x, end_y)) = frame.dirty() {
let end_y = end_y + 60;
Step {
param_cnt: 4,
command: 0x30,
params: [
(start_y >> 8 & 0xff) as u8,
(start_y & 0xff) as u8,
(end_y >> 8 & 0xff) as u8,
(end_y & 0xff) as u8,
],
delay: None,
}
.send_command(&mut self.spi, &mut self.data_command);
Step {
param_cnt: 0,
command: 0x12,
params: [0, 0, 0, 0],
delay: None,
}
.send_command(&mut self.spi, &mut self.data_command);
let _ = self.data_command.set_low();
let _ = self.spi.write(&[RAMWR]);
let _ = self.data_command.set_high();
for row in frame.partial(0, start_y, frame.width(), end_y) {
let _ = self.spi.write(row);
}
Step {
param_cnt: 0,
command: 0x13,
params: [0, 0, 0, 0],
delay: None,
}
.send_command(&mut self.spi, &mut self.data_command);
}
}
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>> Drop

View File

@ -1,4 +1,4 @@
[toolchain]
channel = "1.85.0"
channel = "1.86.0"
targets = [ "wasm32-unknown-unknown", "thumbv6m-none-eabi" ]
components = [ "rustfmt", "rust-analyzer", "clippy" ]

View File

@ -4,3 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
axum = { workspace = true }
visions-types = { path = "../types" }
serde = { workspace = true }
tokio = { workspace = true }
tower-http = { workspace = true }
uuid = { workspace = true }
result-extended = { path = "../../result-extended" }
thiserror = { workspace = true }

View File

@ -1,3 +1,157 @@
fn main() {
println!("Hello, world!");
use std::future::Future;
use axum::{
http::{
header::{AUTHORIZATION, CONTENT_TYPE},
HeaderMap, Method, StatusCode,
},
routing::{get, post},
Json, Router,
};
use visions_types::{AccountStatus, AuthRequest, AuthResponse, SessionId, UserOverview};
use result_extended::{error, ok, ResultExt};
use thiserror::Error;
use tower_http::cors::{Any, CorsLayer};
#[axum::debug_handler]
async fn check_password(
request: Json<AuthRequest>,
) -> (StatusCode, Json<Option<AuthResponse>>) {
let Json(request) = request;
if request.username == "vakarian" && request.password == "aoeu" {
(
StatusCode::OK,
Json(Some(AuthResponse::Success("vakarian-session-id".into()))),
)
} else if request.username == "shephard" && request.password == "aoeu" {
(
StatusCode::OK,
Json(Some(AuthResponse::PasswordReset(
"shephard-session-id".into(),
))),
)
} else {
(StatusCode::UNAUTHORIZED, Json(None))
}
}
#[derive(Debug, Error)]
enum AppError {
#[error("no user authorized")]
Unauthorized,
#[error("bad request")]
BadRequest,
}
#[derive(Debug, Error)]
enum FatalError {
#[error("on unknown fatal error occurred")]
Unknown,
}
impl result_extended::FatalError for FatalError {}
fn parse_session_header(headers: HeaderMap) -> ResultExt<Option<SessionId>, AppError, FatalError> {
match headers.get("Authorization") {
Some(token) => {
println!("session token: {:?}", token);
match token
.to_str()
.unwrap()
.split(" ")
.collect::<Vec<&str>>()
.as_slice()
{
[_schema, token] => ok(Some(SessionId::from(*token))),
_ => error(AppError::BadRequest),
}
}
None => ok(None),
}
}
async fn auth_required<B, F, Fut>(headers: HeaderMap, f: F) -> (StatusCode, Json<Option<B>>)
where
F: Fn() -> Fut,
Fut: Future<Output = (StatusCode, B)>,
{
match parse_session_header(headers) {
ResultExt::Ok(Some(session_id)) => {
if session_id == "vakarian-session-id".into() {
let (code, result) = f().await;
(code, Json(Some(result)))
} else {
(StatusCode::UNAUTHORIZED, Json(None))
}
}
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
ResultExt::Err(AppError::Unauthorized) => (StatusCode::UNAUTHORIZED, Json(None)),
ResultExt::Err(AppError::BadRequest) => (StatusCode::BAD_REQUEST, Json(None)),
ResultExt::Fatal(err) => {
panic!("{}", err);
}
}
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route(
"/api/test/health",
get(|| async { (StatusCode::OK, Json(None::<String>)) }).layer(
CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any),
),
)
.route(
"/api/test/auth",
post(|req: Json<AuthRequest>| check_password(req)).layer(
CorsLayer::new()
.allow_methods([Method::POST])
.allow_headers([CONTENT_TYPE])
.allow_origin(Any),
),
)
.route(
"/api/test/list-users",
get(|headers: HeaderMap| {
auth_required(headers, || async {
println!("list_users is about to return a bunch of stuff");
(
StatusCode::OK,
Some(vec![
UserOverview {
id: "vakarian-id".into(),
name: "vakarian".to_owned(),
status: AccountStatus::Ok,
},
UserOverview {
id: "shephard-id".into(),
name: "shephard".to_owned(),
status: AccountStatus::PasswordReset(
"2050-01-01 00:00:00".to_owned(),
),
},
UserOverview {
id: "tali-id".into(),
name: "tali".to_owned(),
status: AccountStatus::Locked,
},
]),
)
})
})
.layer(
CorsLayer::new()
.allow_headers([AUTHORIZATION])
.allow_methods([Method::GET])
.allow_origin(Any),
),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:8001")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}

2
visions/types/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
gen/
dist/

8
visions/types/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "visions-types"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0.218", features = ["derive"] }
uuid = { version = "1.13.2", features = ["v4", "js"] }

View File

@ -1,8 +0,0 @@
version: '3'
tasks:
build:
cmds:
- npm install typescript
- typeshare --lang typescript --output-file visions.ts ../server/src
- npx tsc

View File

@ -1,14 +0,0 @@
{
"name": "visions-types",
"version": "0.0.1",
"description": "Shared data types for Visions",
"main": "visions.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.7.3"
}
}

81
visions/types/src/lib.rs Normal file
View File

@ -0,0 +1,81 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Clone, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type", content = "content", rename_all = "kebab-case")]
pub enum AccountStatus {
Ok,
PasswordReset(String),
Locked,
}
#[derive(Deserialize, Serialize)]
pub struct AuthRequest {
pub username: String,
pub password: String,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for SessionId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for SessionId {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct UserId(String);
impl UserId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<String> for UserId {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Clone, Deserialize, PartialEq, Serialize)]
pub struct UserOverview {
pub id: UserId,
pub name: String,
pub status: AccountStatus,
}
#[derive(Deserialize, Serialize)]
#[serde(tag = "type", content = "content", rename_all = "kebab-case")]
pub enum AuthResponse {
Success(SessionId),
PasswordReset(SessionId),
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["./visions.ts"]
}

23
visions/ui/.gitignore vendored
View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

18
visions/ui/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "visions-client"
version = "0.1.0"
edition = "2021"
[dependencies]
gloo-console = { workspace = true }
gloo-net = { workspace = true }
serde = { workspace = true }
serde-wasm-bindgen = { workspace = true }
serde_json = { workspace = true }
visions-types = { path = "../types" }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
web-sys = { workspace = true, features = ["Window", "Location"] }
yew = { workspace = true }
yew-router = { workspace = true }

View File

@ -3,12 +3,5 @@ version: '3'
tasks:
dev:
cmds:
- cd ../visions-types && task build
- npm install
- npm run start
- trunk serve --open
test:
cmds:
- cd ../visions-types && task build
- npm install
- npm run test

3
visions/ui/Trunk.toml Normal file
View File

@ -0,0 +1,3 @@
[[proxy]]
backend = "http://localhost:8001/api"
insecure = true

375
visions/ui/design.css Normal file
View File

@ -0,0 +1,375 @@
:root {
--spacing-s: 4px;
--spacing-m: 8px;
--spacing-l: 16px;
--grey-1: hsl(0, 0%, 95%);
--grey-2: hsl(0, 0%, 85%);
--grey-3: hsl(0, 0%, 60%);
--grey-4: hsl(0, 0%, 35%);
--grey-5: hsl(0, 0%, 25%);
--black: hsl(0, 0%, 10%);
--blue-1: hsl(240, 75%, 90%);
--blue-2: hsl(240, 75%, 75%);
--blue-3: hsl(240, 75%, 50%);
--blue-4: hsl(240, 75%, 35%);
--blue-5: hsl(240, 75%, 25%);
--purple-1: hsl(270, 100%, 90%);
--purple-2: hsl(270, 100%, 75%);
--purple-3: hsl(270, 100%, 50%);
--purple-4: hsl(270, 100%, 35%);
--purple-5: hsl(270, 100%, 25%);
--red-1: hsl(0, 75%, 90%);
--red-2: hsl(0, 75%, 75%);
--red-3: hsl(0, 75%, 50%);
--red-4: hsl(0, 75%, 35%);
--red-5: hsl(0, 75%, 25%);
--radius-s: 4px;
--radius-m: 8px;
--radius-l: 16px;
--shadow-shallow: 2px 2px 1px;
--border-light: 1px solid var(--grey-3);
--border-danger: 1px solid var(--red-3);
--border-hover: 2px solid var(--blue-2);
--background-color: var(--grey-2);
--text-normal: var(--grey-5);
--text-danger: var(--red-4);
--text-light: var(--grey-3);
--text-inverse: var(--grey-1);
--button-background-primary: var(--blue-4);
--button-background-danger: var(--red-3);
--highlight-background: var(--grey-1);
--selectable-border-light: 2px solid var(--highlight-background);
--selectable-border-normal: 2px solid var(--background-color);
--selectable-border-enabled: 2px solid var(--border-hover);
--selectable-background-enabled: var(--blue-4);
--toggleable-background-enabled: var(--blue-3);
--toggleable-text-enabled: var(--text-inverse);
}
body[data-theme="dark"] {
--shadow-shallow: 2px 2px 1px;
--border-light: 1px solid var(--grey-4);
--border-hover: 2px solid var(--blue-3);
--background-color: var(--black);
--text-normal: var(--grey-1);
--text-light: var(--grey-3);
--text-inverse: var(--grey-5);
--button-background-primary: var(--blue-1);
--highlight-background: var(--grey-4);
}
body[data-theme="cyber"] {
--shadow-shallow: 2px 2px 1px;
--border-light: 1px solid var(--grey-4);
--border-hover: 2px solid var(--purple-3);
--background-color: var(--black);
--text-normal: var(--purple-1);
--text-light: var(--grey-3);
--text-inverse: var(--grey-5);
--button-background-primary: var(--purple-2);
--highlight-background: var(--grey-4);
}
.design {
display: flex;
}
.design__sidebar {
min-width: 200px;
}
.swatch {
margin: var(--spacing-m);
border: var(--border-light);
border-radius: var(--radius-s);
width: 50px;
height: 50px;
}
.swatch__grey-1 {
background-color: var(--grey-1);
}
.swatch__grey-2 {
background-color: var(--grey-2);
}
.swatch__grey-3 {
background-color: var(--grey-3);
}
.swatch__grey-4 {
background-color: var(--grey-4);
}
.swatch__grey-5 {
background-color: var(--grey-5);
}
.swatch__blue-1 {
background-color: var(--blue-1);
}
.swatch__blue-2 {
background-color: var(--blue-2);
}
.swatch__blue-3 {
background-color: var(--blue-3);
}
.swatch__blue-4 {
background-color: var(--blue-4);
}
.swatch__blue-5 {
background-color: var(--blue-5);
}
.swatch__purple-1 {
background-color: var(--purple-1);
}
.swatch__purple-2 {
background-color: var(--purple-2);
}
.swatch__purple-3 {
background-color: var(--purple-3);
}
.swatch__purple-4 {
background-color: var(--purple-4);
}
.swatch__purple-5 {
background-color: var(--purple-5);
}
.swatch__red-1 {
background-color: var(--red-1);
}
.swatch__red-2 {
background-color: var(--red-2);
}
.swatch__red-3 {
background-color: var(--red-3);
}
.swatch__red-4 {
background-color: var(--red-4);
}
.swatch__red-5 {
background-color: var(--red-5);
}
body {
background-color: var(--background-color);
color: var(--text-normal);
font-family: Ariel, sans-serif;
}
.card {
display: flex;
flex-direction: column;
align-items: space-between;
border: var(--border-light);
box-shadow: var(--shadow-shallow);
border-radius: var(--radius-m);
padding: var(--spacing-m);
background-color: var(--highlight-background);
}
.card > h1 {
margin: 0px;
}
.card > * {
margin-top: var(--spacing-s);
margin-bottom: var(--spacing-s);
}
.login-form {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.label {
padding: var(--spacing-m);
padding-left: var(--spacing-l);
padding-right: var(--spacing-l);
border: var(--border-light);
border-radius: var(--radius-l);
background: var(--highlight-background);
}
.label__placeholder {
color: var(--text-light);
}
/*
.label__edit-button {
margin-left: var(--spacing-l);
padding: var(--spacing-s);
padding-left: var(--spacing-m);
padding-right: var(--spacing-m);
border-radius: var(--radius-l);
background-color: var(--button-background-dark);
color: var(--text-inverse);
}
*/
.label > .cta {
margin-left: var(--spacing-l);
}
.row {
margin: var(--spacing-m);
display: flex;
}
.row > * {
margin: var(--spacing-s);
}
.text-entry {
padding: var(--spacing-m);
padding-left: var(--spacing-l);
padding-right: var(--spacing-l);
border: var(--border-light);
border-radius: var(--radius-l);
background-color: var(--highlight-background);
}
.text-entry__placeholder {
color: var(--text-light);
}
.button {
padding: var(--spacing-m);
border: var(--border-light);
border-radius: var(--radius-l);
}
.button_destructive {
border: var(--border-danger);
color: var(--text-danger);
}
.button_primary-destructive {
border: var(--border-danger);
background-color: var(--button-background-danger);
color: var(--text-inverse);
}
.button_primary {
background-color: var(--button-background-primary);
color: var(--text-inverse);
}
.user-overview {
display: flex;
margin-top: var(--spacing-s);
padding: var(--spacing-m);
border: 2px solid var(--background-color);
border-radius: var(--radius-m);
}
.user-overview:hover {
border: var(--border-hover);
}
.user-overview:nth-child(odd) {
border: 2px solid var(--highlight-background);
background-color: var(--highlight-background);
}
.user-overview:nth-child(odd):hover {
border: var(--border-hover);
background-color: var(--highlight-background);
}
.user-overview__name {
width: 65%;
}
.button-bar {
justify-content: space-between;
}
.toggleable {
border: var(--border-light);
border-radius: var(--radius-l);
padding: var(--spacing-m);
}
.toggleable_enabled {
border: var(--border-hover);
border-radius: var(--radius-l);
padding: var(--spacing-m);
background: var(--toggleable-background-enabled);
color: var(--toggleable-text-enabled);
}
.list-selector {
border: var(--border-light);
border-radius: var(--radius-s);
padding: var(--spacing-s);
}
.list-selector__item {
border: var(--selectable-border-normal);
padding: var(--spacing-s);
border-radius: var(--radius-s);
}
.list-selector__item_enabled {
border: var(--selectable-border-enabled);
background-color: var(--selectable-background-enabled);
color: var(--text-inverse);
padding: var(--spacing-s);
border-radius: var(--radius-s);
}
.list-selector__item:hover {
border: var(--border-hover);
}
.list-selector__item:nth-child(odd) {
border: var(--selectable-border-light);
background-color: var(--highlight-background);
}
.list-selector__item:nth-child(odd):hover {
border: var(--border-hover);
background-color: var(--highlight-background);
}

9
visions/ui/index.html Normal file
View File

@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Visions Client Demo</title>
<link data-trunk rel="css" href="design.css" />
</head>
<body></body>
</html>

View File

@ -1,51 +0,0 @@
{
"name": "ui",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.119",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"classnames": "^2.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.28.0",
"react-router-dom": "^6.28.0",
"react-scripts": "5.0.1",
"react-use-websocket": "^4.11.1",
"typescript": "^4.9.5",
"visions-types": "../visions-types",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

56
visions/ui/src/client.rs Normal file
View File

@ -0,0 +1,56 @@
use std::future::Future;
use gloo_console::log;
use gloo_net::http::{Request, Response};
use visions_types::{AuthRequest, AuthResponse, SessionId, UserOverview};
pub enum ClientError {
Unauthorized,
Err(u16),
}
pub trait Client {
fn auth(&self, username: String, password: String) -> impl Future<Output = Result<AuthResponse, ClientError>>;
fn list_users(&self, session_id: &SessionId) -> impl Future<Output = Result<Vec<UserOverview>, ClientError>>;
}
#[derive(Clone, PartialEq)]
pub struct Connection;
impl Connection {
pub fn new() -> Self { Self }
}
impl Client for Connection {
async fn auth(&self, username: String, password: String) -> Result<AuthResponse, ClientError> {
log!("authenticating: ", &username, &password);
let response: Response = Request::post("/api/test/auth")
.header("Content-Type", "application/json")
.body(serde_wasm_bindgen::to_value(&serde_json::to_string(&AuthRequest{ username, password }).unwrap()).unwrap())
.unwrap()
.send()
.await
.unwrap();
if response.ok() {
Ok(serde_json::from_slice(&response.binary().await.unwrap()).unwrap())
} else {
Err(ClientError::Err(response.status()))
}
}
async fn list_users(&self, session_id: &SessionId) -> Result<Vec<UserOverview>, ClientError> {
let response: Response = Request::get("/api/test/list-users")
.header("Content-Type", "application/json")
.header("Authorization", &format!("Bearer {}", session_id.as_str()))
.send()
.await
.unwrap();
if response.ok() {
Ok(serde_json::from_slice(&response.binary().await.unwrap()).unwrap())
} else {
Err(ClientError::Err(response.status()))
}
}
}

View File

@ -0,0 +1,40 @@
use yew::prelude::*;
#[derive(PartialEq)]
pub enum ButtonStyle {
Primary,
Plain,
}
#[derive(Properties, PartialEq)]
pub struct ButtonProps {
#[prop_or(Callback::from(|_| ()))]
pub on_click: Callback<()>,
#[prop_or(ButtonStyle::Plain)]
pub style: ButtonStyle,
#[prop_or(false)]
pub destructive: bool,
pub children: Html,
}
#[function_component]
pub fn Button(ButtonProps{ on_click, style, destructive, children }: &ButtonProps) -> Html {
let on_click_: Callback<MouseEvent> = Callback::from({
let on_click = on_click.clone();
move |_| on_click.emit(())
});
let classes = match (style, destructive) {
(ButtonStyle::Primary, true) => "button button_primary-destructive",
(ButtonStyle::Primary, false) => "button button_primary",
(ButtonStyle::Plain, true) => "button button_destructive",
(ButtonStyle::Plain, false) => "button",
};
html! {
<button class={classes} onclick={on_click_}>{children.clone()}</button>
}
}

View File

@ -0,0 +1,18 @@
use yew::{function_component, html, AttrValue, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct CardProps {
#[prop_or(AttrValue::from(""))]
pub title: AttrValue,
pub children: Html,
}
#[function_component]
pub fn Card(CardProps { title, children }: &CardProps) -> Html {
html! {
<div class="card">
<h1>{title.clone()}</h1>
{children.clone()}
</div>
}
}

View File

@ -0,0 +1,19 @@
use yew::prelude::*;
pub trait SelectableOption: PartialEq {
fn as_html(self: &Self) -> Html;
}
#[derive(Properties, PartialEq)]
pub struct DropMenuProps<T: SelectableOption> {
pub options: Vec<T>,
}
#[function_component]
pub fn DropMenu<T: SelectableOption>(DropMenuProps{options}: &DropMenuProps<T>) -> Html {
html! {
<select>
{ (*options).iter().map(|option| option.as_html()).collect::<Html>() }
</select>
}
}

View File

@ -0,0 +1,115 @@
use serde::Serialize;
use wasm_bindgen::JsValue;
use yew::prelude::*;
use crate::components::*;
#[derive(Clone, PartialEq, Serialize)]
pub enum Role {
Gm,
Player,
}
impl SelectableOption for Role {
fn as_html(&self) -> Html {
match self {
Role::Gm => html! { <option value="gm">{"GM"}</option> },
Role::Player => html! { <option value="player">{"Player"}</option> },
}
.to_owned()
}
}
impl From<&Role> for JsValue {
fn from(r: &Role) -> JsValue {
match r {
Role::Gm => "gm".into(),
Role::Player => "player".into(),
}
}
}
impl From<Role> for JsValue {
fn from(r: Role) -> JsValue {
match r {
Role::Gm => "gm".into(),
Role::Player => "player".into(),
}
}
}
#[derive(Properties, PartialEq)]
pub struct EditUserProps {
pub username: Option<String>,
pub email: Option<String>,
pub admin: bool,
pub role: Role,
pub on_save: Callback<(String, String, bool, Role)>,
}
#[function_component]
pub fn EditUser(
EditUserProps {
username,
email,
admin,
role: _role,
on_save,
}: &EditUserProps,
) -> Html {
let username_field = use_state({
let username = username.clone();
move || username
});
let email_field = use_state({
let email = email.clone();
move || email
});
let admin_field = use_state({
let admin = admin.clone();
move || admin
});
let username_updated: Callback<String> = Callback::from({
let username_field = username_field.clone();
move |new_value| username_field.set(Some(new_value))
});
let email_updated: Callback<String> = Callback::from({
let email_field = email_field.clone();
move |new_value| email_field.set(Some(new_value))
});
let admin_toggled: Callback<()> = Callback::from({
let admin_field = admin_field.clone();
move |_| admin_field.set(!*admin_field)
});
let on_save_ = Callback::from({
let on_save = on_save.clone();
let username_field = username_field.clone();
let email_field = email_field.clone();
let admin_field = admin_field.clone();
move |_| match ((*username_field).clone(), (*email_field).clone()) {
(Some(unf), Some(ef)) => on_save.emit((unf, ef, *admin_field, Role::Gm)),
_ => (),
}
});
html! {
<div>
<Row>
<div><TextEntry placeholder="username" value={(*username_field).clone()} on_changed={username_updated} /></div>
<div><TextEntry placeholder="email" value={(*email_field).clone()} on_changed={email_updated} /></div>
</Row>
<Row>
<Toggleable value="admin" enabled={*admin_field} on_text="Admin" off_text="Admin" on_click={admin_toggled} />
<DropMenu<Role> options={vec![Role::Gm, Role::Player]} />
</Row>
<Row>
<Button on_click={on_save_}>{"Save User"}</Button>
</Row>
</div>
}
}

View File

@ -0,0 +1,32 @@
use yew::{function_component, html, AttrValue, Html, Properties};
use crate::components::Button;
#[derive(Properties, PartialEq)]
pub struct LabelProps {
#[prop_or(AttrValue::from(""))]
pub text: AttrValue,
#[prop_or(AttrValue::from("placeholder"))]
pub placeholder: AttrValue,
#[prop_or(false)]
pub editable: bool,
}
#[function_component]
pub fn Label(LabelProps { text, placeholder, editable }: &LabelProps) -> Html {
let body_text = if text.is_empty() {
html! { <span class="label__placeholder">{placeholder.clone()}</span> }
} else {
html! { text.clone() }
};
let edit_button = if *editable {
html! { <Button on_click={|_| ()}>{"Edit"}</Button> }
} else { html! { } };
html! {
<div class="label">
{body_text}{edit_button}
</div>
}
}

View File

@ -0,0 +1,76 @@
use std::borrow::Cow;
use yew::prelude::*;
pub trait ListItem: PartialEq {
fn id(&self) -> &str;
fn as_html(&self) -> Html;
}
#[derive(Properties, PartialEq)]
pub struct ListSelectorProps<T: ListItem> {
#[prop_or_default]
pub class: Option<AttrValue>,
pub elements: Vec<T>,
pub enabled_item: T,
pub on_select: Callback<String>,
}
#[function_component]
pub fn ListSelector<T: ListItem + Clone + 'static>(
ListSelectorProps {
class,
elements,
enabled_item,
on_select,
}: &ListSelectorProps<T>,
) -> Html {
let classes = format!(
"list-selector {}",
class
.as_ref()
.map(|cn| cn.as_str().to_string())
.unwrap_or("".to_string())
);
html! {
<div class={classes}>
{(*elements).iter().map(|item| {
let enabled = item == enabled_item;
html! { <ListItemComponent<T> enabled={enabled} item={item.clone()} on_select={on_select} /> }
}).collect::<Html>()}
</div>
}
}
#[derive(Properties, PartialEq)]
pub struct ListItemProps<T: ListItem> {
item: T,
enabled: bool,
on_select: Callback<String>,
}
#[function_component]
pub fn ListItemComponent<T: ListItem + Clone + 'static>(
ListItemProps {
item,
enabled,
on_select,
}: &ListItemProps<T>,
) -> Html {
let classes = if *enabled {
format!("list-selector__item_enabled")
} else {
format!("list-selector__item")
};
let on_click: Callback<MouseEvent> = {
let on_select = on_select.clone();
let item = item.clone();
Callback::from(move |_| on_select.emit(item.id().to_owned()))
};
html! {
<div class={classes} onclick={on_click}>
{item.as_html()}
</div>
}
}

View File

@ -0,0 +1,32 @@
mod button;
pub use button::*;
mod card;
pub use card::Card;
mod drop_menu;
pub use drop_menu::*;
mod edit_user;
pub use edit_user::*;
mod label;
pub use label::Label;
mod list_selector;
pub use list_selector::*;
mod row;
pub use row::Row;
mod swatch;
pub use swatch::Swatch;
mod text_entry;
pub use text_entry::TextEntry;
mod toggleable;
pub use toggleable::Toggleable;
mod user_list;
pub use user_list::UserListComponent;

View File

@ -0,0 +1,18 @@
use yew::{function_component, html, AttrValue, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct RowProps {
#[prop_or_default]
pub class: Option<AttrValue>,
pub children: Html
}
#[function_component]
pub fn Row(RowProps { ref class, children }: &RowProps) -> Html {
let classes = format!("row {}", class.as_ref().map(|cn| cn.as_str().to_string()).unwrap_or("".to_string()));
html! {
<div class={classes}>
{children.clone()}
</div>
}
}

View File

@ -0,0 +1,14 @@
use yew::{function_component, html, AttrValue, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct SwatchProps {
pub color: AttrValue,
}
#[function_component]
pub fn Swatch(SwatchProps { color }: &SwatchProps) -> Html {
let class_name = format!("swatch swatch__{}", color);
html! {
<div class={class_name} />
}
}

View File

@ -0,0 +1,53 @@
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct TextEntryProps {
#[prop_or(None)]
pub value: Option<String>,
#[prop_or(AttrValue::from("placeholder"))]
pub placeholder: AttrValue,
#[prop_or(None)]
pub on_changed: Option<Callback<String>>,
#[prop_or(false)]
pub sensitive: bool,
}
#[function_component]
pub fn TextEntry(
TextEntryProps {
value,
placeholder,
on_changed,
sensitive,
}: &TextEntryProps,
) -> Html {
let on_changed_ = {
let on_changed = on_changed.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Some(ref on_changed) = on_changed {
on_changed.emit(input.value());
}
}
})
};
let value_: String = value.to_owned().unwrap_or("".to_owned());
if *sensitive {
html! {
<input class="text-entry" type="password" placeholder={placeholder} onchange={on_changed_} value={value_} />
}
} else {
html! {
<input class="text-entry" type="text" placeholder={placeholder} onchange={on_changed_} value={value_} />
}
}
}

View File

@ -0,0 +1,38 @@
use yew::prelude::*;
#[derive(Properties, PartialEq)]
pub struct ToggleableProps {
pub value: String,
pub enabled: bool,
pub on_text: String,
pub off_text: String,
#[prop_or(Callback::from(|_| {}))]
pub on_click: Callback<()>,
}
#[function_component]
pub fn Toggleable(
ToggleableProps {
value: _value,
enabled,
on_text,
off_text,
on_click,
}: &ToggleableProps,
) -> Html {
let on_click_: Callback<MouseEvent> = Callback::from({
let on_click = on_click.clone();
move |_| on_click.emit(())
});
if *enabled {
html! {
<button class="toggleable_enabled" onclick={on_click_}>{on_text}</button>
}
} else {
html! {
<button class="toggleable" onclick={on_click_}>{off_text}</button>
}
}
}

View File

@ -0,0 +1,58 @@
use gloo_console::log;
use visions_types::{AccountStatus, UserId, UserOverview};
use yew::{function_component, html, Callback, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct UserListProps {
pub overviews: Vec<UserOverview>,
}
#[function_component]
pub fn UserListComponent(UserListProps { overviews }: &UserListProps) -> Html {
html! {
<div>
{
overviews.iter().map(|overview| html! {
<UserOverviewComponent overview={overview.clone()} />
}).collect::<Vec<Html>>()
}
</div>
}
}
#[derive(Properties, PartialEq)]
struct UserOverviewProps {
overview: UserOverview,
#[prop_or(Callback::from(|user_id: UserId| { log!("UserOverview on select user: ", user_id.as_str()) }))]
on_select_user: Callback<UserId>,
}
#[function_component]
fn UserOverviewComponent(
UserOverviewProps {
overview,
on_select_user,
}: &UserOverviewProps,
) -> Html {
let account_status = match overview.status {
AccountStatus::Ok => "",
AccountStatus::PasswordReset(_) => "Password Reset",
AccountStatus::Locked => "Locked",
};
let on_click = {
let user_id = overview.id.clone();
let on_select_user = on_select_user.clone();
Callback::from(move |_| {
on_select_user.emit(user_id.clone());
})
};
html! {
<div class="user-overview" onclick={on_click}>
<div class="user-overview__name">{overview.name.clone()}</div>
<div class="user-overview__status">{account_status}</div>
</div>
}
}

58
visions/ui/src/main.rs Normal file
View File

@ -0,0 +1,58 @@
mod client;
mod components;
mod state;
mod views;
use yew::prelude::*;
use yew_router::prelude::*;
use state::{session_id, AppState, StateProvider};
use views::*;
#[derive(Clone, Routable, PartialEq)]
enum Route {
#[at("/")]
Home,
#[at("/new-user")]
NewUser,
#[at("/design")]
DesignRoot,
#[at("/design/*")]
Design,
}
#[function_component]
fn Landing() -> Html {
let app_state = use_context::<UseReducerHandle<AppState>>().expect("app state not found");
match session_id(app_state) {
Some(_) => html! { <Admin /> },
None => html! { <Login /> },
}
}
fn switch(routes: Route) -> Html {
match routes {
Route::Home => html! { <Landing /> },
Route::NewUser => html! { <NewUser /> },
Route::DesignRoot | Route::Design => html! { <Design /> },
}
}
#[function_component]
fn AppWrapper() -> Html {
html! {
<StateProvider>
<BrowserRouter>
<Switch<Route> render={switch} />
</BrowserRouter>
</StateProvider>
}
}
fn main() {
yew::Renderer::<AppWrapper>::new().render();
}

115
visions/ui/src/state.rs Normal file
View File

@ -0,0 +1,115 @@
use std::rc::Rc;
use crate::client::Connection;
use gloo_console::log;
use visions_types::SessionId;
use yew::prelude::*;
pub enum AppAction {
Auth(String),
Unauth,
}
#[derive(Clone, PartialEq)]
pub enum View {
Admin,
NewUser,
GMView,
PlayerView,
}
#[derive(Clone, PartialEq)]
pub struct AppState {
pub client: Connection,
pub current_view: Option<View>,
pub session_id: Option<SessionId>,
}
impl Default for AppState {
fn default() -> Self {
Self {
client: Connection::new(),
current_view: None,
session_id: None,
}
}
}
impl Reducible for AppState {
type Action = AppAction;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
match action {
AppAction::Auth(session_id) => Self {
session_id: Some(session_id.into()),
..(*self).clone()
}
.into(),
AppAction::Unauth => Self { session_id: None, ..(*self).clone() }.into(),
}
}
}
pub fn init_state() -> AppState {
match web_sys::window() {
Some(window) => match window.session_storage() {
Ok(Some(storage)) => {
if let Ok(Some(session_id)) = storage.get_item("session_id") {
AppState {
client: Connection::new(),
current_view: Some(View::Admin),
session_id: Some(session_id.into()),
}
} else {
AppState::default()
}
}
_ => AppState::default(),
},
None => AppState::default(),
}
}
pub fn set_session_id(state: UseReducerHandle<AppState>, session_id: Option<SessionId>) {
let storage = match web_sys::window() {
Some(window) => match window.session_storage() {
Ok(storage) => storage,
_ => None,
}
None => None,
};
match session_id {
Some(session_id) => {
if let Some(ref storage) = storage {
storage.set_item("session_id", session_id.as_str()).unwrap();
}
state.dispatch(AppAction::Auth(session_id.as_str().to_owned()));
}
None => {
if let Some(ref storage) = storage {
storage.delete("session_id").unwrap();
}
state.dispatch(AppAction::Unauth);
}
}
}
pub fn session_id(state: UseReducerHandle<AppState>) -> Option<SessionId> {
state.session_id.clone()
}
#[derive(Properties, PartialEq)]
pub struct StateProviderProps {
pub children: Html
}
#[function_component]
pub fn StateProvider(StateProviderProps{ children }: &StateProviderProps) -> Html {
let app_state = use_reducer(init_state);
html! {
<ContextProvider<UseReducerHandle<AppState>> context={app_state}>
{children}
</ContextProvider<UseReducerHandle<AppState>>>
}
}

View File

@ -0,0 +1,63 @@
use gloo_console::log;
use yew::prelude::*;
use crate::{
client::{Client, ClientError},
components::*,
AppState,
};
#[derive(Properties, PartialEq)]
pub struct AdminProps {
#[prop_or(Callback::from(|_| {}))]
pub on_add_user: Callback<()>,
}
#[function_component]
pub fn Admin() -> Html {
let app_state = use_context::<UseReducerHandle<AppState>>().expect("app state not found");
let user_list = use_state(|| vec![]);
let on_add_user = {
Callback::from(move |_| {
match web_sys::window() {
Some(window) => { window.location().set_pathname("/new-user").expect("path navigation to succeed"); }
None => { todo!(); }
}
})
};
{
let user_list = user_list.clone();
let client = app_state.client.clone();
let session_id = app_state.session_id.clone();
use_effect(move || {
wasm_bindgen_futures::spawn_local(async move {
match session_id {
Some(session_id) => match client.list_users(&session_id).await {
Ok(users) => user_list.set(users),
Err(ClientError::Unauthorized) => {
todo!()
}
Err(ClientError::Err(status)) => {
log!("error: {:?}", status);
todo!()
}
},
None => {}
}
});
});
}
html! {
<>
<UserListComponent overviews={(*user_list).clone()} />
<Row class="button-bar">
<Button on_click={on_add_user}>{"+ Add User"}</Button>
<Button>{"Go to game"}</Button>
</Row>
</>
}
}

View File

@ -0,0 +1,211 @@
use gloo_console::log;
use yew::prelude::*;
use yew_router::prelude::*;
use crate::components::*;
#[derive(Properties, PartialEq)]
pub struct DesignProps {}
#[derive(Clone, PartialEq)]
pub enum Page {
Buttons,
Card,
FieldsLabels,
Swatches,
}
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/design")]
Default,
#[at("/design/buttons")]
Buttons,
#[at("/design/card")]
Card,
#[at("/design/fields-labels")]
FieldsLabels,
#[at("/design/swatches")]
Swatches,
}
impl ListItem for Page {
fn id(&self) -> &str {
match self {
Page::Buttons => "buttons",
Page::Card => "card",
Page::FieldsLabels => "fields-labels",
Page::Swatches => "swatches",
}
}
fn as_html(&self) -> Html {
let on_navigate = |page: Page| match web_sys::window() {
Some(window) => {
window
.location()
.set_pathname(&format!("/design/{}", page.id()))
.expect("path navigation to succeed");
}
None => {
todo!();
}
};
match self {
Page::Buttons => {
html! { <div onclick={Callback::from({
let on_navigate = on_navigate.clone();
move |_| on_navigate(Page::Buttons)
})}>{"Buttons"}</div> }
}
Page::Card => {
html! { <div onclick={Callback::from({
let on_navigate = on_navigate.clone();
move |_| on_navigate(Page::Card)
})}>{"Card"}</div> }
}
Page::FieldsLabels => {
html! { <div onclick={Callback::from({
let on_navigate = on_navigate.clone();
move |_| on_navigate(Page::FieldsLabels)
})}>{"Fields and Labels"}</div> }
}
Page::Swatches => {
html! { <div onclick={Callback::from({
let on_navigate = on_navigate.clone();
move |_| on_navigate(Page::Swatches)
})}>{"Swatches"}</div> }
}
}
}
}
fn switch(route: Route) -> Html {
let (page_, page) = {
match route {
Route::Buttons => (Page::Buttons, html! { <ButtonsPage /> }),
Route::Card => (Page::Card, html! { <CardsPage /> }),
Route::Default => (Page::Swatches, html! { <SwatchPage /> }),
Route::FieldsLabels => (Page::FieldsLabels, html! { <FieldsLabelsPage /> }),
Route::Swatches => (Page::Swatches, html! { <SwatchPage /> }),
}
};
html! {
<>
<ListSelector<Page> class="design__sidebar"
on_select={|_| {}}
enabled_item={page_}
elements={vec![Page::Swatches, Page::FieldsLabels, Page::Buttons, Page::Card]} />
<div>
{page}
</div>
</>
}
}
#[function_component]
pub fn Design(DesignProps {}: &DesignProps) -> Html {
html! {
<div class="design">
<BrowserRouter>
<Switch<Route> render={switch} />
</BrowserRouter>
</div>
}
}
#[function_component]
pub fn ButtonsPage() -> Html {
let toggle_state = use_state(|| false);
let on_toggle_click: Callback<()> = Callback::from({
let toggle_state = toggle_state.clone();
move |_| toggle_state.set(!*toggle_state)
});
html! {
<Row>
<Button on_click={|_| { log!("plain"); }}>{"Plain"}</Button>
<Button on_click={|_| { log!("plain destructive"); }} destructive=true>{"Destructive"}</Button>
<Button on_click={|_| { log!("primary"); }} style={ButtonStyle::Primary}>{"Primary"}</Button>
<Button on_click={|_| { log!("primary destructive"); }} style={ButtonStyle::Primary} destructive=true>{"Primary Destructive"}</Button>
<Toggleable value="on-or-off" enabled={*toggle_state} on_text="Toggle On" off_text="Toggle Off" on_click={on_toggle_click} />
</Row>
}
}
#[function_component]
pub fn CardsPage() -> Html {
html! {
<Row>
<Card title="Card Title">
<p>{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}</p>
</Card>
</Row>
}
}
#[function_component]
pub fn FieldsLabelsPage() -> Html {
html! {
<>
<Row>
<Label placeholder="username" />
<Label placeholder="username" editable=true />
<Label text="normal label" placeholder="empty" />
<Label text="editable label" placeholder="empty" editable=true />
</Row>
<Row>
<TextEntry placeholder="username" />
<TextEntry value="vakarian" placeholder="username" />
</Row>
</>
}
}
#[function_component]
pub fn SwatchPage() -> Html {
html! {
<>
<Row>
<Swatch color="grey-1" />
<Swatch color="grey-2" />
<Swatch color="grey-3" />
<Swatch color="grey-4" />
<Swatch color="grey-5" />
</Row>
<Row>
<Swatch color="blue-1" />
<Swatch color="blue-2" />
<Swatch color="blue-3" />
<Swatch color="blue-4" />
<Swatch color="blue-5" />
</Row>
<Row>
<Swatch color="purple-1" />
<Swatch color="purple-2" />
<Swatch color="purple-3" />
<Swatch color="purple-4" />
<Swatch color="purple-5" />
</Row>
<Row>
<Swatch color="red-1" />
<Swatch color="red-2" />
<Swatch color="red-3" />
<Swatch color="red-4" />
<Swatch color="red-5" />
</Row>
</>
}
}

View File

@ -0,0 +1,72 @@
use gloo_console::log;
use visions_types::AuthResponse;
use yew::prelude::*;
use crate::{
client::{Client, ClientError},
components::{Button, TextEntry},
state::{set_session_id, AppState},
};
#[derive(Properties, PartialEq)]
pub struct LoginProps {
}
#[function_component]
pub fn Login(LoginProps { }: &LoginProps) -> Html {
let app_state = use_context::<UseReducerHandle<AppState>>().expect("app state not found");
let username = use_state(|| "".to_owned());
let password = use_state(|| "".to_owned());
let on_login = {
let username = username.clone();
let password = password.clone();
Callback::from(move |_| {
let username = username.clone();
let password = password.clone();
let app_state = app_state.clone();
let client = app_state.client.clone();
wasm_bindgen_futures::spawn_local(async move {
match client
.auth(username.to_string(), password.to_string())
.await
{
Ok(AuthResponse::Success(session_id)) => {
set_session_id(app_state, Some(session_id));
}
Ok(AuthResponse::PasswordReset(session_id)) => {
set_session_id(app_state, Some(session_id));
}
Err(ClientError::Unauthorized) => todo!(),
Err(ClientError::Err(_status)) => todo!(),
}
});
})
};
let on_username_changed = {
let username = username.clone();
Callback::from(move |text: String| {
log!("on_username_changed", text.clone());
username.set(text);
})
};
let on_password_changed = {
let password = password.clone();
Callback::from(move |text: String| password.set(text))
};
html! {
<div class="login-form">
<div class="card">
<h1>{"Welcome to Visions VTT"}</h1>
<TextEntry placeholder="username" value={(*username).clone()} on_changed={on_username_changed} />
<TextEntry placeholder="password" value={(*password).clone()} on_changed={on_password_changed} sensitive={true} />
<Button on_click={on_login}>{"Login"}</Button>
</div>
</div>
}
}

View File

@ -0,0 +1,12 @@
mod admin;
pub use admin::Admin;
mod design;
pub use design::Design;
mod login;
pub use login::Login;
mod new_user;
pub use new_user::NewUser;

View File

@ -0,0 +1,30 @@
use gloo_console::log;
use yew::prelude::*;
use crate::{
components::*,
state::{session_id, AppState},
views::Login,
};
#[function_component]
pub fn NewUser() -> Html {
let app_state = use_context::<UseReducerHandle<AppState>>().expect("app state not found");
let empty_string: Option<String> = None;
let on_save: Callback<(String, String, bool, Role)> =
Callback::from(|(username, email, admin, role)| {
let role_val = serde_wasm_bindgen::to_value(&role).unwrap();
log!("on_save clicked", username, email, admin, role);
});
match session_id(app_state) {
Some(_) => {
html! { <EditUser username={empty_string.clone()} email={empty_string.clone()} admin=false role={Role::Player}
on_save={on_save}
/> }
}
None => html! { <Login /> },
}
}

View File

@ -1,27 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src",
"gen"
]
}

View File

@ -0,0 +1,11 @@
[package]
name = "yew-app"
version = "0.1.0"
edition = "2021"
[dependencies]
gloo-net = "0.6.0"
serde = { version = "1.0.217", features = ["derive"] }
wasm-bindgen-futures = "0.4.50"
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }

View File

@ -0,0 +1,3 @@
body {
background-color: hsl(0, 0%, 50%);
}

View File

@ -0,0 +1,5 @@
<!doctype html>
<html lang="en">
<head></head>
<body></body>
</html>

126
visions/yew-app/src/main.rs Normal file
View File

@ -0,0 +1,126 @@
use yew::prelude::*;
use serde::Deserialize;
use gloo_net::http::Request;
#[derive(Clone, PartialEq, Deserialize)]
struct Video {
id: usize,
title: String,
speaker: String,
url: String,
}
#[derive(Properties, PartialEq)]
struct VideosListProps {
videos: Vec<Video>,
on_click: Callback<Video>,
}
/*
fn videos() -> Vec<Video> {
vec![
Video {
id: 1,
title: "Building and breaking things".to_string(),
speaker: "John Doe".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 2,
title: "The development process".to_string(),
speaker: "Jane Smith".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 3,
title: "The Web 7.0".to_string(),
speaker: "Matt Miller".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
Video {
id: 4,
title: "Mouseless development".to_string(),
speaker: "Tom Jerry".to_string(),
url: "https://youtu.be/PsaFVLr8t4E".to_string(),
},
]
}
*/
#[function_component(VideosList)]
fn videos_list(VideosListProps { videos, on_click }: &VideosListProps) -> Html {
let on_click = on_click.clone();
videos
.iter()
.map(|video| {
let on_video_select = {
let on_click = on_click.clone();
let video = video.clone();
Callback::from(move |_| on_click.emit(video.clone()))
};
html! {
<p key={video.id} onclick={on_video_select}>{format!("{}: {}", video.speaker, video.title)}</p>
}
})
.collect()
}
#[derive(Properties, PartialEq)]
struct VideosDetailsProps {
video: Video,
}
#[function_component(VideoDetails)]
fn video_details(VideosDetailsProps { video }: &VideosDetailsProps) -> Html {
html! {
<div>
<h3>{video.title.clone()}</h3>
<img src="https://via.placeholder.com/640x360.png?text=Video+Player+Placeholder" alt="video thumbnail" />
</div>
}
}
#[function_component(App)]
fn app() -> Html {
let videos = use_state(|| vec![]);
{
let videos = videos.clone();
use_effect_with((), move |_| {
let videos = videos.clone();
wasm_bindgen_futures::spawn_local(async move {
let response = Request::get("/tutorial/data.json").send().await;
println!("response: {:?}", response);
let response = response.unwrap();
let fetched_videos: Vec<Video> = response.json().await.unwrap();
videos.set(fetched_videos);
});
|| ()
});
}
let selected_video = use_state(|| None);
let on_video_select = {
let selected_video = selected_video.clone();
Callback::from(move |video: Video| selected_video.set(Some(video)))
};
let details = selected_video.as_ref().map(|video| html! {
<VideoDetails video={video.clone()} />
});
html! {
<>
<h1>{ "RustConf Explorer" }</h1>
<div>
<h3>{"Videos to watch"}</h3>
<VideosList videos={(*videos).clone()} on_click={on_video_select.clone()} />
</div>
{ for details }
</>
}
}
fn main() {
yew::Renderer::<App>::new().render();
}