Compare commits

..

25 Commits

Author SHA1 Message Date
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
51 changed files with 3137 additions and 4074 deletions

1717
Cargo.lock generated

File diff suppressed because it is too large Load Diff

2044
Cargo.nix

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
resolver = "2"
members = [
"authdb",
# "bike-lights/bike",
"bike-lights/core",
"bike-lights/simulator",
"changeset",
@ -23,16 +22,18 @@ members = [
"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",
"visions/server",
"visions/types",
"visions/ui",
"visions/yew-app",
# "bike-lights/bike",
]

View File

@ -3,6 +3,7 @@
"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#ahash@0.8.11": "04chdfkls5xmhp1d48gnjsmglbqibizs3bpbj6rsj604m10si7g8",
"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",
@ -14,6 +15,7 @@
"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#assert-json-diff@2.0.2": "04mg3w0rh3schpla51l18362hsirl23q93aisws2irrj32wg5r27",
"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",
@ -25,8 +27,13 @@
"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#auto-future@1.0.0": "0wykbakzh227vz6frx9p48zsq0wpswgmb7v3917m53m7gr2pw7iw",
"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#axum-core@0.4.5": "16b1496c4gm387q20hkv5ic3k5bd6xmnvk50kwsy6ymr8rhvvwh9",
"registry+https://github.com/rust-lang/crates.io-index#axum-macros@0.4.2": "1klv77c889jm05bzayaaiinalarhvh2crc2w4nvp3l581xaj7lap",
"registry+https://github.com/rust-lang/crates.io-index#axum-test@16.4.1": "1p5qxacvxsagnqq30nr2wznjyhgb8svsfb925ah3d2b0s91s9qv3",
"registry+https://github.com/rust-lang/crates.io-index#axum@0.7.9": "07z7wqczi9i8xb4460rvn39p4wjqwr32hx907crd1vwb2fy8ijpd",
"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",
@ -46,6 +53,7 @@
"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#bytesize@1.3.0": "1k3aak70iwz4s2gsjbxf0ws4xnixqbdz6p2ha96s06748fpniqx3",
"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",
@ -56,9 +64,9 @@
"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@4.5.26": "10v7qvn90calfbhap1c4r249i5c7fbxj09fn3szfz9pkis85xsx8",
"registry+https://github.com/rust-lang/crates.io-index#clap_builder@4.5.26": "08f1mzcvi7zjhm7hvz6al4jnv70ccqhwiaq74hihlspwnl0iic4n",
"registry+https://github.com/rust-lang/crates.io-index#clap_derive@4.5.24": "131ih3dm76srkbpfx7zfspp9b556zgzj31wqhl0ji2b39lcmbdsl",
"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",
@ -68,6 +76,7 @@
"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#cookie@0.18.1": "0iy749flficrlvgr3hjmf3igr738lk81n5akzf4ym4cs6cxg7pjd",
"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",
@ -85,6 +94,7 @@
"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#diff@0.1.13": "1j0nzjxci2zqx63hdcihkp0a4dkdmzxd7my4m7zk6cjyfy34j9an",
"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",
@ -100,6 +110,8 @@
"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#fallible-iterator@0.3.0": "0ja6l56yka5vn4y4pk6hn88z0bpny7a8k1919aqjzp0j1yhy9k1a",
"registry+https://github.com/rust-lang/crates.io-index#fallible-streaming-iterator@0.1.9": "0nj6j26p71bjy8h42x6jahx1hn0ng6mc2miwpgwnp8vnwqf4jq3k",
"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",
@ -134,7 +146,6 @@
"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",
@ -159,8 +170,10 @@
"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.14.5": "1wa1vy1xs3mp11bn3z9dv0jricgr6a2j0zkf1g19yz3vw4il89z5",
"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#hashlink@0.9.1": "1byq4nyrflm5s6wdx5qwp96l1qbp2d0nljvrr5yqrsfy51qzz93b",
"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",
@ -172,15 +185,19 @@
"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-util@0.1.2": "0kslwazg4400qnc2azkrgqqci0fppv12waicnsy5d8hncvbjjd3r",
"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-body@1.0.1": "111ir5k2b9ihz5nr9cz7cwm7fnydca7dx4hc7vr16scfzghxrzhy",
"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-util@0.1.10": "1d1iwrkysjhq63pg54zk3vfby1j7zmxzm9zzyfr4lwvp0szcybfz",
"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#hyper@1.5.2": "1q7akfb443yrjzkmnnbp2vs8zi15hgbk466rr4y144v4ppabhvr5",
"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",
@ -198,6 +215,8 @@
"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#include_dir@0.7.4": "1pfh3g45z88kwq93skng0n6g3r7zkhq9ldqs9y8rvr7i11s12gcj",
"registry+https://github.com/rust-lang/crates.io-index#include_dir_macros@0.7.4": "0x8smnf6knd86g69p19z5lpfsaqp8w0nx14kdpkz1m8bxnkqbavw",
"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",
@ -232,6 +251,7 @@
"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#matchit@0.7.3": "156bgdmmlv4crib31qhgg49nsjk88dxkdqp80ha2pk2rk6n6ax0f",
"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",
@ -297,9 +317,10 @@
"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_assertions@1.4.1": "0v8iq35ca4rw3rza5is3wjxwsf88303ivys07anc5yviybi31q9s",
"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-crate@2.0.2": "092x5acqnic14cw6vacqap5kgknq3jn4c6jij9zi6j85839jc3xh",
"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",
@ -332,7 +353,11 @@
"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#reserve-port@2.0.1": "10x21rdb1hjzp6n5flbbw3hfd7brmirckz1q0zsf3a7s5d516f4q",
"registry+https://github.com/rust-lang/crates.io-index#rsa@0.9.7": "06amqm85raq26v6zg00fbf93lbj3kx559n2lpxc3wrvbbiy5vis7",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite@0.32.1": "0vlx040bppl414pbjgbp7qr4jdxwszi9krx0m63zzf2f2whvflvp",
"registry+https://github.com/rust-lang/crates.io-index#rusqlite_migration@1.3.1": "076dm65g0sngzrb93r07va4l5zl3gjx9gq5mlsh21p7p0bl44fwj",
"registry+https://github.com/rust-lang/crates.io-index#rust-multipart-rfc7578_2@0.6.1": "0mwd3i2mk91n6diaxnkw28vyjbifhrm5ls73pcpfzz8a1i0lidq3",
"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",
@ -352,9 +377,10 @@
"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@1.0.217": "0w2ck1p1ajmrv1cf51qf7igjn2nc51r0izzc00fzmmhkvxjl5z02",
"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.217": "180r3rj5gi5s1m23q66cr5wlfgc5jrs6n1mdmql2njnhk37zg6ss",
"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.136": "1lipcjhh1zazh283i4wsl4l14knh81q2rlkwmag8v8s2rwihqsik",
"registry+https://github.com/rust-lang/crates.io-index#serde_path_to_error@0.1.16": "19hlz2359l37ifirskpcds7sxg0gzpqvfilibs7whdys0128i6dg",
"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",
@ -386,13 +412,14 @@
"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#sync_wrapper@1.0.2": "0qvjyasd6w18mjg5xlaq5jgy84jsjfsvmnn12c13gypxbv75dwhb",
"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#tempfile@3.15.0": "016pmkbwn3shas44gcwq1kc9lajalb90qafhiip5fvv8h6f5b2ls",
"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",
@ -413,12 +440,14 @@
"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@0.8.2": "0g9ysjaqvm2mv8q85xpqfn7hi710hj24sd56k49wyddvvyq8lp8q",
"registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.3": "0jsy7v8bdvmzsci6imj8fzgd255fmy5fzp6zsri14yrry7i77nkw",
"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-http@0.6.2": "15wnvhl6cpir9125s73bqjzjsvfb0fmndmsimnl2ddnlhfvs6gs0",
"registry+https://github.com/rust-lang/crates.io-index#tower-layer@0.3.3": "03kq92fdzxin51w8iqix06dcfgydyvx7yr6izjq0p626v9n2l70j",
"registry+https://github.com/rust-lang/crates.io-index#tower-service@0.3.3": "1hzfkvkci33ra94xjx64vv3pp0sq346w06fpkcdwjcid7zhvdycd",
"registry+https://github.com/rust-lang/crates.io-index#tower@0.5.2": "1ybmd59nm4abl9bsvy6rx31m4zvzp5rja2slzpn712y9b68ssffh",
"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",
@ -445,6 +474,7 @@
"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#urlencoding@2.1.3": "1nj99jp37k47n0hvaz5fvz7z6jd0sb4ppvfy3nphr1zbnyixpy6s",
"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",
@ -462,7 +492,6 @@
"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",
@ -499,13 +528,11 @@
"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#yansi@1.0.1": "0jdh55jyv0dpd38ij4qh60zglbw9aa8wafqai6m0wa7xaxk3mrfg",
"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",

24
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"
@ -35,16 +20,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1740339700,
"narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=",
"lastModified": 1704732714,
"narHash": "sha256-ABqK/HggMYA/jMUXgYyqVAcQ8QjeMyr1jcXfTpSHmps=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195",
"rev": "6723fa4e4f1a30d42a633bef5eb01caeb281adc3",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-24.11",
"ref": "nixos-23.11",
"type": "indirect"
}
},
@ -65,7 +50,6 @@
},
"root": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs",
"typeshare": "typeshare",
"unstable": "unstable"

View File

@ -2,13 +2,12 @@
description = "Lumenescent Dreams Tools";
inputs = {
nixpkgs.url = "nixpkgs/nixos-24.11";
nixpkgs.url = "nixpkgs/nixos-23.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,8 +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
@ -91,11 +89,6 @@
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;
all = pkgs.symlinkJoin {
@ -106,7 +99,6 @@
dashboard
# file-service
fitnesstrax
l10n-db
otg-gtk
];
};

View File

@ -1,24 +0,0 @@
[package]
name = "l10n-db"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = { version = "0.4.39", features = ["serde"] }
clap = { version = "4.5.30", features = ["derive"] }
icu_locid = { version = "1.5.0", features = ["serde"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.139"
tempfile = "3.17.1"
thiserror = "2.0.11"
toml = "0.8.20"
xml-rs = "0.8.25"
# [lib]
# name = "l10n_db"
# path = "src/lib.rs"
#
# [[bin]]
# name = "l10n-db"
# path = "src/main.rs"

View File

@ -1,8 +0,0 @@
db_path = "./i18n"
base_locale = "en"
locales = [
"en",
"eo",
"de",
"es",
]

View File

@ -1,22 +0,0 @@
key = "SaveSettings"
description = "This is a label on a button which will save the settings when clicked"
[variants.eo]
locale = "eo"
content = "Konservi Agordojn"
modified = "2025-02-24T19:32:11.246639077Z"
[variants.es]
locale = "es"
content = "Guardar Configuraciones"
modified = "2025-02-24T19:33:23.861329923Z"
[variants.en]
locale = "en"
content = "Save Settings"
modified = "2025-02-22T23:44:18.874218939Z"
[variants.de]
locale = "de"
content = "Einstellungen Speichern"
modified = "2025-02-24T19:33:19.516005843Z"

View File

@ -1,22 +0,0 @@
key = "TimeDistance"
description = "A summary of a workout or many workouts that involve a time and a distance"
[variants.es]
locale = "es"
content = "{distance} de {activity} en {hours, plural, one {}=1 {{hours} hora} other {{hours} horas}} y {minutes, plural, one {}=1 {{minutes} minuto} other {{minutes} minutos}}"
modified = "2025-02-24T19:33:23.861604738Z"
[variants.eo]
locale = "eo"
content = "{distance} de {activity} en {hours, plural, =1 {{hours} horo} other {{hours} horoj}} {minutes, plural, =1 {{minutes} minuto} other {{minutes} minutoj}}"
modified = "2025-02-24T19:32:11.246943602Z"
[variants.en]
locale = "en"
content = "{distance} of {activity} in {hours, plural, =1 {{hours} hour} other {{hours} hours}} and {minutes, plural, =1 {{minutes} minute} other {{minutes} minutes}}"
modified = "2025-02-24T14:09:17.361641899Z"
[variants.de]
locale = "de"
content = "{distance} von {activity} in {hours, plural, one {}=1 {{hours} Stunde} other {{hours} Stunden}} und {minutes, plural, one {}=1 {{minutes} Minute} other {{minutes} Minuten}}"
modified = "2025-02-24T19:33:19.516210807Z"

View File

@ -1,22 +0,0 @@
key = "Welcome"
description = "This is a welcome content that will be shown on first app opening, before configuration."
[variants.en]
locale = "en"
content = "Welcome to FitnessTrax, the privacy-centered fitness tracker"
modified = "2025-02-25T02:12:25.757240004Z"
[variants.eo]
locale = "eo"
content = "Bonvenon al FitnessTrax"
modified = "2025-02-24T19:32:11.246407627Z"
[variants.es]
locale = "es"
content = "Bienvenido a FitnessTrax"
modified = "2025-02-24T19:33:23.861143003Z"
[variants.de]
locale = "de"
content = "Willkommen bei FitnessTrax"
modified = "2025-02-24T19:33:19.515861453Z"

View File

@ -1,143 +0,0 @@
use std::{fmt, path::PathBuf};
use clap::{Parser, Subcommand};
use icu_locid::{langid, LanguageIdentifier};
use l10n_db::{
self, js, read_file,
xliff::{self, import_file},
Bundle, Editor, ReadError,
};
use serde::Deserialize;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
/// Edit, potentially creating, a key
EditKey {
#[arg(short, long)]
key: String,
#[arg(short, long)]
locale: Option<String>,
},
/// List al keys in the database
ListKeys,
// Search the database
// Search { },
Import {
#[arg(short, long)]
file: String,
},
/// Export the database
Export {
#[arg(short, long)]
format: String,
#[arg(short, long)]
file: String,
#[arg(short, long)]
locale: Option<String>,
},
Report,
}
#[derive(Debug, Deserialize)]
struct Config {
db_path: PathBuf,
base_locale: LanguageIdentifier,
}
fn edit_key(bundle: &mut Bundle, key: String, locale: LanguageIdentifier, editor: &str) {
let message = bundle.message(key);
Editor::edit(message, locale, editor);
bundle.save();
}
#[derive(Clone, Debug, Default)]
struct Report {
keys: Vec<String>,
// source_deleted: Vec<String>,
out_of_date: Vec<String>,
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{} messages in bundle", self.keys.len())?;
writeln!(f, "Out of date messages")?;
for key in self.out_of_date.iter() {
writeln!(f, "\t{}", key)?;
}
Ok(())
}
}
fn generate_report(bundle: &Bundle, base_locale: &LanguageIdentifier) -> Report {
let mut report: Report = Default::default();
for (key, message) in bundle.message_iter() {
report.keys.push(key.to_owned());
if !message.variants_out_of_date(base_locale).is_empty() {
report.out_of_date.push(key.to_owned())
}
}
report
}
fn main() {
let editor = std::env::var("EDITOR").expect("Set EDITOR to the path to your favorite editor");
let config: Config = read_file(&PathBuf::from("./config.toml"))
.and_then(|bytes| String::from_utf8(bytes).map_err(|_| ReadError::InvalidFormat))
.and_then(|content| toml::from_str(&content).map_err(|_| ReadError::InvalidFormat))
.unwrap();
let cli = Cli::parse();
let mut bundle = Bundle::load_from_disk(PathBuf::from(&config.db_path));
match &cli.command {
Some(Commands::EditKey { key, locale }) => {
let identifier = locale.as_ref()
.map(|l| l.parse::<LanguageIdentifier>().unwrap())
.unwrap_or(config.base_locale);
edit_key(&mut bundle, key.to_owned(), identifier, &editor)
}
Some(Commands::ListKeys) => {
for (key, _) in bundle.message_iter() {
println!("{}", key);
}
}
Some(Commands::Import { file }) => {
import_file(&mut bundle, &PathBuf::from(file)).unwrap();
bundle.save();
}
Some(Commands::Export {
format,
file,
locale,
}) => {
let locale = locale
.as_ref()
.map(|l| l.clone().parse::<LanguageIdentifier>().unwrap())
.unwrap_or(langid!("en"));
match format.as_ref() {
"js" => js::export_file(&bundle, locale, &PathBuf::from(file)).unwrap(),
"xliff" => {
xliff::export_file(&bundle, locale, &PathBuf::from(file)).unwrap()
}
_ => todo!(),
}
}
Some(Commands::Report) => {
let report = generate_report(&bundle, &config.base_locale);
println!("{}", report);
}
None => {}
}
}

View File

@ -1,143 +0,0 @@
use std::{collections::HashMap, fs::File, io::{BufReader, Read, Write}, path::{Path, PathBuf}};
use chrono::{DateTime, Utc};
use icu_locid::LanguageIdentifier;
use serde::{Deserialize, Serialize};
pub struct Bundle {
path: PathBuf,
messages: HashMap<String, Message>,
}
impl Bundle {
pub fn load_from_disk(path: PathBuf) -> Self {
let mut messages = HashMap::new();
if path.is_dir() {
for entry in std::fs::read_dir(&path).unwrap() {
let entry = entry.unwrap();
let path = entry.path().clone();
let key = path.file_stem().unwrap();
let message = Message::from_file(&entry.path());
messages.insert(key.to_str().unwrap().to_owned(), message);
}
}
Self { path, messages }
}
pub fn message_iter(&self) -> impl Iterator<Item = (&String, &Message)> {
self.messages.iter()
}
pub fn message(&mut self, name: String) -> &mut Message {
self.messages
.entry(name.to_owned())
.or_insert(Message::new(name.to_owned()))
}
pub fn save(&self) {
self.messages.iter().for_each(|(key, value)| {
let mut path = self.path.clone();
path.push(key);
path.set_extension("toml");
save_file(&path, toml::to_string(value).unwrap().as_bytes());
});
}
}
fn save_file(path: &PathBuf, s: &[u8]) {
let mut f = File::create(path).unwrap();
let _ = f.write(s).unwrap();
}
#[derive(Deserialize, Serialize, Debug)]
pub struct Message {
key: String,
description: String,
variants: HashMap<LanguageIdentifier, Variant>,
}
impl Message {
pub fn new(key: String) -> Self {
Self {
key,
description: "".to_owned(),
variants: HashMap::new(),
}
}
pub fn from_file(path: &Path) -> Message {
let file = std::fs::File::open(path).unwrap();
let mut content = Vec::new();
let mut reader = BufReader::new(file);
let _ = reader.read_to_end(&mut content);
toml::from_str(&String::from_utf8(content).unwrap()).unwrap()
}
pub fn set_description(&mut self, desc: String) {
self.description = desc;
}
pub fn description(&self) -> &str {
&self.description
}
pub fn variant(&self, locale: &LanguageIdentifier) -> Option<&Variant> {
self.variants.get(locale)
}
pub fn variant_mut(&mut self, locale: LanguageIdentifier) -> &mut Variant {
self.variants.entry(locale.clone()).or_insert(Variant {
locale,
content: "".to_owned(),
modified: Utc::now(),
})
}
pub fn variants_out_of_date(
&self,
base_locale: &LanguageIdentifier,
) -> Vec<LanguageIdentifier> {
match self
.variants
.get(base_locale)
.map(|variant| variant.modified())
{
Some(base_date) => self
.variants
.iter()
.filter(|(_, value)| base_date > value.modified())
.map(|(locale, _)| locale.clone())
.collect(),
None => vec![],
}
}
// pub fn missing_variants(&self, locals: Vec<LanguageIdentifier>) -> Vec<LanguageIdentifier> {}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Variant {
locale: LanguageIdentifier,
content: String,
modified: DateTime<Utc>,
}
impl Variant {
pub fn content(&self) -> &str {
&self.content
}
pub fn set_content(&mut self, content: String) {
self.content = content;
self.modified = Utc::now();
}
pub fn modified(&self) -> DateTime<Utc> {
self.modified
}
}
#[cfg(test)]
mod test {}

View File

@ -1,43 +0,0 @@
use std::{io::Write, process::Command};
use icu_locid::{langid, LanguageIdentifier};
use serde::{Deserialize, Serialize};
use crate::{read_fh, Message};
#[derive(Serialize, Deserialize, Debug, Clone)]
struct EditorMessage {
description: String,
source: String,
content: String,
}
pub struct Editor {}
impl Editor {
pub fn edit(msg: &mut Message, locale: LanguageIdentifier, editor: &str) {
let description = msg.description().to_owned();
let source_string = msg.variant_mut(langid!("en")).content().to_owned();
let variant = msg.variant_mut(locale);
// let editable_content = EditorMessage::from_variant(description, &variant);
let editable_content = EditorMessage {
description,
source: source_string,
content: variant.content().to_owned(),
};
let mut file = tempfile::NamedTempFile::new().unwrap();
let _ = file.write(toml::to_string(&editable_content).unwrap().as_bytes());
let _ = file.flush();
let mut cmd = Command::new(editor).args([file.path()]).spawn().unwrap();
cmd.wait().unwrap();
let file = file.reopen().unwrap();
let content = read_fh(&file).unwrap();
let new_variant: EditorMessage =
toml::from_str(&String::from_utf8(content).unwrap()).unwrap();
variant.set_content(new_variant.content);
msg.set_description(new_variant.description);
}
}

View File

@ -1,22 +0,0 @@
use std::{collections::BTreeMap, fs::File, io::Write, path::Path};
use icu_locid::LanguageIdentifier;
use crate::{Bundle, WriteError};
pub fn export_file(bundle: &Bundle, locale: LanguageIdentifier, path: &Path) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh(bundle: &Bundle, locale: LanguageIdentifier, fh: &mut File) -> Result<(), WriteError> {
let messages = bundle.message_iter().map(|(key, message)| {
let content = message.variant(&locale).unwrap().content().to_owned();
(key.to_owned(), content)
}).collect::<Vec<(String, String)>>();
let messages: BTreeMap<String, String> = messages.into_iter().collect();
let _ = fh.write(serde_json::to_string_pretty(&messages).unwrap().as_bytes()).unwrap();
Ok(())
}

View File

@ -1,4 +0,0 @@
pub mod js;
pub mod xliff;

View File

@ -1,146 +0,0 @@
use std::{
fs::File,
io::{BufReader, Read, Write},
path::Path,
};
use icu_locid::{langid, LanguageIdentifier};
use xml::{attribute::OwnedAttribute, reader, writer, EmitterConfig, EventReader, EventWriter};
use crate::{Bundle, Message, ReadError, WriteError};
pub fn export_file(
bundle: &Bundle,
locale: LanguageIdentifier,
path: &Path,
) -> Result<(), WriteError> {
let mut file = File::create(path).unwrap();
export_fh(bundle, locale, &mut file)
}
pub fn export_fh<W>(bundle: &Bundle, locale: LanguageIdentifier, fh: W) -> Result<(), WriteError>
where
W: Write,
{
let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh);
writer
.write(
writer::XmlEvent::start_element("xliff")
.attr("xmlns", "urn:oasis:names:tc:xliff:document:2.0")
.attr("version", "2.0")
.attr("srcLang", &format!("{}", locale)),
)
.unwrap();
writer
.write(writer::XmlEvent::start_element("file").attr("id", "main"))
.unwrap();
for (key, message) in bundle.message_iter() {
write_message(&mut writer, key, message, &locale);
}
writer.write(writer::XmlEvent::end_element()).unwrap();
writer.write(writer::XmlEvent::end_element()).unwrap();
Ok(())
}
pub fn import_file(bundle: &mut Bundle, path: &Path) -> Result<(), ReadError> {
let file = File::open(path).unwrap();
let file = BufReader::new(file);
import_reader(bundle, file)
}
pub fn import_reader<R>(bundle: &mut Bundle, fh: R) -> Result<(), ReadError>
where
R: Read,
{
let parser = EventReader::new(fh);
let mut locale: LanguageIdentifier = langid!("en");
let mut current_key = None;
let mut current_text: Option<String> = None;
let mut in_target = false;
for event in parser {
match event {
Ok(reader::XmlEvent::StartElement {
name, attributes, ..
}) => match name.local_name.as_ref() {
"xliff" => {
locale = find_attribute(&attributes, "trgLang")
.unwrap()
.parse::<LanguageIdentifier>()
.unwrap();
}
"unit" => current_key = find_attribute(&attributes, "id"),
"target" => in_target = true,
_ => println!("name: {}", name),
},
Ok(reader::XmlEvent::EndElement { name }) => match name.local_name.as_ref() {
"unit" => {
if let Some(key) = current_key {
let message = bundle.message(key);
let variant = message.variant_mut(locale.clone());
if let Some(ref text) = current_text {
variant.set_content(text.clone());
}
}
current_key = None;
}
"target" => in_target = false,
_ => {}
},
Ok(reader::XmlEvent::Characters(data)) => {
if in_target {
current_text = Some(data)
}
}
Err(e) => {
eprintln!("error: {e}");
break;
}
_ => {}
}
}
Ok(())
}
fn write_message<T>(
writer: &mut EventWriter<T>,
key: &str,
message: &Message,
locale: &LanguageIdentifier,
) where
T: std::io::Write,
{
[
writer::XmlEvent::start_element("unit")
.attr("id", key)
.into(),
writer::XmlEvent::start_element("notes").into(),
writer::XmlEvent::start_element("note").into(),
writer::XmlEvent::characters(message.description()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::start_element("segment").into(),
writer::XmlEvent::start_element("source").into(),
writer::XmlEvent::characters(message.variant(locale).unwrap().content()),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
writer::XmlEvent::end_element().into(),
]
.into_iter()
.for_each(|elem: writer::XmlEvent| writer.write(elem).unwrap());
}
fn find_attribute(attrs: &Vec<OwnedAttribute>, name: &str) -> Option<String> {
for f in attrs {
if name == f.name.local_name {
return Some(f.value.clone());
}
}
None
}

View File

@ -1,11 +0,0 @@
mod bundle;
pub use bundle::{Bundle, Message, Variant};
mod editor;
pub use editor::Editor;
mod formats;
pub use formats::{js, xliff};
mod utils;
pub use utils::*;

View File

@ -1,37 +0,0 @@
use std::{fs::File, io::{BufReader, ErrorKind, Read}, path::Path};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ReadError {
#[error("file not found")]
FileNotFound,
#[error("invalid file format")]
InvalidFormat,
#[error("unhandled read error")]
Unhandled(ErrorKind),
}
pub fn read_file(path: &Path) -> Result<Vec<u8>, ReadError> {
let file = File::open(path).map_err(|err| {
match err.kind() {
ErrorKind::NotFound => ReadError::FileNotFound,
_ => ReadError::Unhandled( err.kind()),
}
})?;
read_fh(&file)
}
pub fn read_fh(file: &File) -> Result<Vec<u8>, ReadError> {
let mut content = Vec::new();
let mut reader = BufReader::new(file);
reader.read_to_end(&mut content).map_err(|err| ReadError::Unhandled(err.kind()))?;
Ok(content)
}
#[derive(Debug, Error)]
pub enum WriteError {
#[error("unhandled write error")]
Unhandled(ErrorKind),
}

View File

@ -1,11 +0,0 @@
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
rustflags = [
"-C", "link-arg=--nmagic",
"-C", "link-arg=-Tlink.x",
"-C", "no-vectorize-loops",
]
runner = "elf2uf2-rs -d"

View File

@ -1,13 +0,0 @@
[package]
name = "pico-st7789"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.9.0"
cortex-m-rt = "0.7.3"
embedded-alloc = "0.6.0"
embedded-hal = "1.0.0"
fugit = "0.3.7"
panic-halt = "1.0.0"
rp-pico = "0.9.0"

View File

@ -1,62 +0,0 @@
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
use crate::font::{Font, Glyph};
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub trait Canvas {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB);
fn fill(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
for y in y1..y2 + 1 {
self.set_pixel(x, y, color);
}
}
}
fn square(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: &RGB) {
for x in x1..x2 + 1 {
self.set_pixel(x, y1, color);
}
for x in x1..x2 + 1 {
self.set_pixel(x, y2, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x1, y, color);
}
for y in y1..y2 + 1 {
self.set_pixel(x2, y, color);
}
}
}
pub fn print<A>(
canvas: &mut impl Canvas,
font: &impl Font<A>,
sx: usize,
sy: usize,
text: &str,
color: &RGB,
) where
A: Glyph,
{
let mut x = sx;
for c in text.chars().map(|c| font.glyph(c)) {
c.draw(canvas, x, sy, color);
let (dx, _) = c.extents();
x = x + dx + 1;
}
}

View File

@ -1,513 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
pub struct BitmapGlyph([u8; 7]);
pub struct BitmapFont(BTreeMap<char, BitmapGlyph>);
impl BitmapFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', BitmapGlyph([
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
]));
font.insert(
':',
BitmapGlyph([
0b00000,
0b00100,
0b00100,
0b00000,
0b00100,
0b00100,
0b00000,
]),
);
font.insert(
'/',
BitmapGlyph([
0b00001,
0b00010,
0b00010,
0b00100,
0b01000,
0b01000,
0b10000,
]),
);
font.insert(
'0',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10101,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'1',
BitmapGlyph([
0b00001,
0b00011,
0b00101,
0b00001,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'2',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b00010,
0b00100,
0b01000,
0b11111,
]),
);
font.insert(
'3',
BitmapGlyph([
0b01110,
0b10001,
0b00001,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'4',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b00001,
0b00001,
0b00001,
]),
);
font.insert(
'5',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'6',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b11110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'7',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00110,
0b00100,
0b01000,
0b01000,
]),
);
font.insert(
'8',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01110,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'9',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b01111,
0b00001,
0b00001,
0b01110,
]),
);
font.insert(
'A',
BitmapGlyph([
0b00100,
0b01010,
0b01010,
0b10001,
0b11111,
0b10001,
0b10001,
]),
);
font.insert(
'B',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b1111 ,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'C',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10000,
0b10000,
0b10001,
0b01110,
]),
);
font.insert(
'D',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b11110,
]),
);
font.insert(
'E',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11111,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'F',
BitmapGlyph([
0b11111,
0b10000,
0b10000,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'G',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b10011,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'H',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b11111,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'I',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b11111,
]),
);
font.insert(
'J',
BitmapGlyph([
0b00111,
0b00001,
0b00001,
0b00001,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'K',
BitmapGlyph([
0b10001,
0b10010,
0b10100,
0b11000,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'L',
BitmapGlyph([
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b11111,
]),
);
font.insert(
'M',
BitmapGlyph([
0b10001,
0b11011,
0b10101,
0b10001,
0b10001,
0b10001,
0b10001,
]),
);
font.insert(
'N',
BitmapGlyph([
0b10001,
0b11001,
0b11001,
0b10101,
0b10011,
0b10011,
0b10001,
]),
);
font.insert(
'O',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'P',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10000,
0b10000,
0b10000,
]),
);
font.insert(
'Q',
BitmapGlyph([
0b01110,
0b10001,
0b10001,
0b10001,
0b10101,
0b10011,
0b01110,
]),
);
font.insert(
'R',
BitmapGlyph([
0b11110,
0b10001,
0b10001,
0b11110,
0b10100,
0b10010,
0b10001,
]),
);
font.insert(
'S',
BitmapGlyph([
0b01110,
0b10001,
0b10000,
0b01110,
0b00001,
0b10001,
0b01110,
]),
);
font.insert(
'T',
BitmapGlyph([
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'U',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b01110,
]),
);
font.insert(
'V',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b01010,
0b01010,
0b00100,
]),
);
font.insert(
'W',
BitmapGlyph([
0b10001,
0b10001,
0b10001,
0b10001,
0b10101,
0b10101,
0b01010,
]),
);
font.insert(
'X',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b01010,
0b10001,
0b10001,
]),
);
font.insert(
'Y',
BitmapGlyph([
0b10001,
0b10001,
0b01010,
0b00100,
0b00100,
0b00100,
0b00100,
]),
);
font.insert(
'Z',
BitmapGlyph([
0b11111,
0b00001,
0b00010,
0b00100,
0b01000,
0b10000,
0b11111,
]),
);
Self(font)
}
}
impl Font<BitmapGlyph> for BitmapFont {
fn glyph(&self, c: char) -> &BitmapGlyph {
self.0.get(&c).unwrap_or(self.0.get(&' ').unwrap())
}
}
impl Glyph for BitmapGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
for row in 0..7 {
if self.0[row] & (1 << 4) > 0 {
canvas.set_pixel(x, y + row, color);
}
if self.0[row] & (1 << 3) > 0 {
canvas.set_pixel(x + 1, y + row, color);
}
if self.0[row] & (1 << 2) > 0 {
canvas.set_pixel(x + 2, y + row, color);
}
if self.0[row] & (1 << 1) > 0 {
canvas.set_pixel(x + 3, y + row, color);
}
if self.0[row] & 1 > 0 {
canvas.set_pixel(x + 4, y + row, color);
}
}
}
fn extents(&self) -> (usize, usize) {
(5, 7)
}
}

View File

@ -1,20 +0,0 @@
use crate::canvas::{Canvas, RGB};
mod bits_5_8;
pub use bits_5_8::BitmapFont;
mod seven_segment;
pub use seven_segment::SevenSegmentFont;
mod sixteen_segment;
pub use sixteen_segment::SixteenSegmentFont;
pub trait Font<A> {
fn glyph(&self, c: char) -> &A;
}
pub trait Glyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB);
fn extents(&self) -> (usize, usize);
}

View File

@ -1,211 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Seven Segments
//
// a a a
// f b
// f b
// f b
// g g g
// e c
// e c
// e c
// d d d
bitflags! {
pub struct SevenSegmentGlyph: u8 {
const NONE = 0;
const A = 0x01;
const B = 0x01 << 1;
const C = 0x01 << 2;
const D = 0x01 << 3;
const E = 0x01 << 4;
const F = 0x01 << 5;
const G = 0x01 << 6;
const DOT = 0x01 << 7;
}
}
pub struct SevenSegmentFont(BTreeMap<char, SevenSegmentGlyph>);
impl SevenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SevenSegmentGlyph::NONE);
font.insert(
'0',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert('1', SevenSegmentGlyph::B | SevenSegmentGlyph::C);
font.insert(
'2',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'3',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::G
| SevenSegmentGlyph::D,
);
font.insert(
'4',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'5',
SevenSegmentGlyph::A
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'6',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D,
);
font.insert(
'7',
SevenSegmentGlyph::A | SevenSegmentGlyph::B | SevenSegmentGlyph::C,
);
font.insert(
'8',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'9',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'A',
SevenSegmentGlyph::A
| SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'B',
SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'C',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F,
);
font.insert(
'D',
SevenSegmentGlyph::B
| SevenSegmentGlyph::C
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::G,
);
font.insert(
'E',
SevenSegmentGlyph::A
| SevenSegmentGlyph::D
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
font.insert(
'F',
SevenSegmentGlyph::A
| SevenSegmentGlyph::E
| SevenSegmentGlyph::F
| SevenSegmentGlyph::G,
);
Self(font)
}
}
impl Font<SevenSegmentGlyph> for SevenSegmentFont {
fn glyph(&self, c: char) -> &SevenSegmentGlyph {
self.0.get(&c).unwrap()
}
}
impl Glyph for SevenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SevenSegmentGlyph::A) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SevenSegmentGlyph::B) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
}
if self.contains(SevenSegmentGlyph::C) {
canvas.set_pixel(x + 4, y + 5, color);
canvas.set_pixel(x + 4, y + 6, color);
canvas.set_pixel(x + 4, y + 7, color);
}
if self.contains(SevenSegmentGlyph::D) {
canvas.set_pixel(x + 1, y + 8, color);
canvas.set_pixel(x + 2, y + 8, color);
canvas.set_pixel(x + 3, y + 8, color);
}
if self.contains(SevenSegmentGlyph::E) {
canvas.set_pixel(x, y + 5, color);
canvas.set_pixel(x, y + 6, color);
canvas.set_pixel(x, y + 7, color);
}
if self.contains(SevenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
}
if self.contains(SevenSegmentGlyph::G) {
canvas.set_pixel(x + 1, y + 4, color);
canvas.set_pixel(x + 2, y + 4, color);
canvas.set_pixel(x + 3, y + 4, color);
}
}
fn extents(&self) -> (usize, usize) {
(5, 8)
}
}

View File

@ -1,689 +0,0 @@
use alloc::collections::btree_map::BTreeMap;
use bitflags::bitflags;
use crate::canvas::{Canvas, RGB};
use super::{Font, Glyph};
// Sixteen Segments
// https://www.partsnotincluded.com/segmented-led-display-ascii-library/
//
// a1 a2
// f h i jb
// f i j b
// f hij b
// g1 g2
// e klm c
// e k l m c
// ek l mc
// d1 d2
bitflags! {
pub struct SixteenSegmentGlyph: u32 {
const NONE = 0;
const A1 = 0x0001;
const A2 = 0x0001 << 1;
const B = 0x0001 << 2;
const C = 0x0001 << 3;
const D1 = 0x0001 << 4;
const D2 = 0x0001 << 5;
const E = 0x0001 << 6;
const F = 0x0001 << 7;
const G1 = 0x0001 << 8;
const G2 = 0x0001 << 9;
const H = 0x0001 << 10;
const I = 0x0001 << 11;
const J = 0x0001 << 12;
const K = 0x0001 << 13;
const L = 0x0001 << 14;
const M = 0x0001 << 15;
const DOT = 0x0001 << 16;
}
}
pub struct SixteenSegmentFont(BTreeMap<char, SixteenSegmentGlyph>);
impl SixteenSegmentFont {
pub fn new() -> Self {
let mut font = BTreeMap::new();
font.insert(' ', SixteenSegmentGlyph::NONE);
font.insert(
'!',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::DOT,
);
font.insert('"', SixteenSegmentGlyph::B | SixteenSegmentGlyph::I);
font.insert(
'0',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'1',
SixteenSegmentGlyph::B | SixteenSegmentGlyph::C | SixteenSegmentGlyph::J,
);
font.insert(
'2',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'3',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'4',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'5',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::M,
);
font.insert(
'6',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'7',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C,
);
font.insert(
'8',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'9',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'A',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'B',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'C',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'D',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'E',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'F',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'G',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
font.insert(
'H',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'I',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'J',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E,
);
font.insert(
'K',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::M,
);
font.insert(
'L',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'M',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J,
);
font.insert(
'N',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::H
| SixteenSegmentGlyph::M,
);
font.insert(
'O',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'P',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::M,
);
font.insert(
'R',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::M,
);
font.insert(
'S',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'T',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'U',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F,
);
font.insert(
'V',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'W',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'X',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'Y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'Z',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K,
);
font.insert(
'a',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'b',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'c',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1,
);
font.insert(
'd',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::L,
);
font.insert(
'e',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::K,
);
font.insert(
'f',
SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'g',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'h',
SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert('i', SixteenSegmentGlyph::L);
font.insert(
'j',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert(
'k',
SixteenSegmentGlyph::I
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::M,
);
font.insert('l', SixteenSegmentGlyph::E | SixteenSegmentGlyph::F);
font.insert(
'm',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::L
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::G2,
);
font.insert(
'n',
SixteenSegmentGlyph::E | SixteenSegmentGlyph::L | SixteenSegmentGlyph::G1,
);
font.insert(
'o',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
'p',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I,
);
font.insert(
'q',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::L,
);
font.insert('r', SixteenSegmentGlyph::E | SixteenSegmentGlyph::G1);
font.insert(
's',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1
| SixteenSegmentGlyph::L,
);
font.insert(
't',
SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G1,
);
font.insert(
'u',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::E | SixteenSegmentGlyph::L,
);
font.insert('v', SixteenSegmentGlyph::E | SixteenSegmentGlyph::K);
font.insert(
'w',
SixteenSegmentGlyph::C
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'x',
SixteenSegmentGlyph::H
| SixteenSegmentGlyph::J
| SixteenSegmentGlyph::K
| SixteenSegmentGlyph::M,
);
font.insert(
'y',
SixteenSegmentGlyph::B
| SixteenSegmentGlyph::C
| SixteenSegmentGlyph::I
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::G2,
);
font.insert(
'z',
SixteenSegmentGlyph::D1 | SixteenSegmentGlyph::G1 | SixteenSegmentGlyph::K,
);
font.insert(
'@',
SixteenSegmentGlyph::A1
| SixteenSegmentGlyph::A2
| SixteenSegmentGlyph::B
| SixteenSegmentGlyph::D1
| SixteenSegmentGlyph::D2
| SixteenSegmentGlyph::E
| SixteenSegmentGlyph::F
| SixteenSegmentGlyph::G2,
);
Self(font)
}
}
impl Font<SixteenSegmentGlyph> for SixteenSegmentFont {
fn glyph(&self, c: char) -> &SixteenSegmentGlyph {
self.0.get(&c).unwrap_or(&SixteenSegmentGlyph::NONE)
}
}
impl Glyph for SixteenSegmentGlyph {
fn draw(&self, canvas: &mut impl Canvas, x: usize, y: usize, color: &RGB) {
if self.contains(SixteenSegmentGlyph::A1) {
canvas.set_pixel(x + 1, y, color);
canvas.set_pixel(x + 2, y, color);
canvas.set_pixel(x + 3, y, color);
}
if self.contains(SixteenSegmentGlyph::A2) {
canvas.set_pixel(x + 5, y, color);
canvas.set_pixel(x + 6, y, color);
canvas.set_pixel(x + 7, y, color);
}
if self.contains(SixteenSegmentGlyph::B) {
canvas.set_pixel(x + 8, y + 1, color);
canvas.set_pixel(x + 8, y + 2, color);
canvas.set_pixel(x + 8, y + 3, color);
canvas.set_pixel(x + 8, y + 4, color);
canvas.set_pixel(x + 8, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::C) {
canvas.set_pixel(x + 8, y + 7, color);
canvas.set_pixel(x + 8, y + 8, color);
canvas.set_pixel(x + 8, y + 9, color);
canvas.set_pixel(x + 8, y + 10, color);
canvas.set_pixel(x + 8, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::D1) {
canvas.set_pixel(x + 1, y + 12, color);
canvas.set_pixel(x + 2, y + 12, color);
canvas.set_pixel(x + 3, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::D2) {
canvas.set_pixel(x + 5, y + 12, color);
canvas.set_pixel(x + 6, y + 12, color);
canvas.set_pixel(x + 7, y + 12, color);
}
if self.contains(SixteenSegmentGlyph::E) {
canvas.set_pixel(x, y + 7, color);
canvas.set_pixel(x, y + 8, color);
canvas.set_pixel(x, y + 9, color);
canvas.set_pixel(x, y + 10, color);
canvas.set_pixel(x, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::F) {
canvas.set_pixel(x, y + 1, color);
canvas.set_pixel(x, y + 2, color);
canvas.set_pixel(x, y + 3, color);
canvas.set_pixel(x, y + 4, color);
canvas.set_pixel(x, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::G1) {
canvas.set_pixel(x + 1, y + 6, color);
canvas.set_pixel(x + 2, y + 6, color);
canvas.set_pixel(x + 3, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::G2) {
canvas.set_pixel(x + 5, y + 6, color);
canvas.set_pixel(x + 6, y + 6, color);
canvas.set_pixel(x + 7, y + 6, color);
}
if self.contains(SixteenSegmentGlyph::H) {
canvas.set_pixel(x + 1, y + 1, color);
canvas.set_pixel(x + 1, y + 2, color);
canvas.set_pixel(x + 2, y + 3, color);
canvas.set_pixel(x + 3, y + 4, color);
canvas.set_pixel(x + 3, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::I) {
canvas.set_pixel(x + 4, y + 1, color);
canvas.set_pixel(x + 4, y + 2, color);
canvas.set_pixel(x + 4, y + 3, color);
canvas.set_pixel(x + 4, y + 4, color);
canvas.set_pixel(x + 4, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::J) {
canvas.set_pixel(x + 7, y + 1, color);
canvas.set_pixel(x + 7, y + 2, color);
canvas.set_pixel(x + 6, y + 3, color);
canvas.set_pixel(x + 5, y + 4, color);
canvas.set_pixel(x + 5, y + 5, color);
}
if self.contains(SixteenSegmentGlyph::K) {
canvas.set_pixel(x + 3, y + 7, color);
canvas.set_pixel(x + 3, y + 8, color);
canvas.set_pixel(x + 2, y + 9, color);
canvas.set_pixel(x + 1, y + 10, color);
canvas.set_pixel(x + 1, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::L) {
canvas.set_pixel(x + 4, y + 7, color);
canvas.set_pixel(x + 4, y + 8, color);
canvas.set_pixel(x + 4, y + 9, color);
canvas.set_pixel(x + 4, y + 10, color);
canvas.set_pixel(x + 4, y + 11, color);
}
if self.contains(SixteenSegmentGlyph::M) {
canvas.set_pixel(x + 5, y + 7, color);
canvas.set_pixel(x + 5, y + 8, color);
canvas.set_pixel(x + 6, y + 9, color);
canvas.set_pixel(x + 7, y + 10, color);
canvas.set_pixel(x + 7, y + 11, color);
}
}
fn extents(&self) -> (usize, usize) {
(9, 13)
}
}

View File

@ -1,184 +0,0 @@
#![no_main]
#![no_std]
extern crate alloc;
use alloc::fmt::format;
use embedded_alloc::LlffHeap as Heap;
use embedded_hal::{delay::DelayNs, digital::OutputPin};
use fugit::RateExtU32;
use panic_halt as _;
use rp_pico::{
entry,
hal::{clocks::init_clocks_and_plls, spi::Spi, Clock, Sio, Timer, Watchdog},
pac, Pins,
};
mod canvas;
use canvas::{Canvas, RGB, print};
mod font;
use font::{BitmapFont, Font, Glyph, SevenSegmentFont, SixteenSegmentFont};
mod st7789;
use st7789::{ST7789Display, SETUP_PROGRAM};
const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // MHz, https://forums.raspberrypi.com/viewtopic.php?t=356764
const ROWS: usize = 320;
const COLUMNS: usize = 170;
const FRAMEBUF: usize = ROWS * COLUMNS * 3;
#[global_allocator]
static HEAP: Heap = Heap::empty();
static mut BUF: [u8; 163200] = [0; 163200];
pub struct FrameBuf {
pub buf: &'static mut [u8; 163200],
pub width: usize,
}
impl FrameBuf {
pub fn new() -> Self {
Self {
buf: unsafe { &mut BUF },
width: 170,
}
}
}
impl Canvas for FrameBuf {
fn set_pixel(&mut self, x: usize, y: usize, color: &RGB) {
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;
}
}
#[entry]
unsafe fn main() -> ! {
{
use core::mem::MaybeUninit;
const HEAP_SIZE: usize = 64 * 1024;
static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
let font_bitmap = BitmapFont::new();
// rp_pico::pac::Peripherals is a reference to physical hardware defined on the Pico.
let mut peripherals = pac::Peripherals::take().unwrap();
// SIO inidcates "Single Cycle IO". I don't know what this means, but it could mean that this
// is a class of IO operations that can be run in a single clock cycle, such as switching a
// GPIO pin on or off.
let sio = Sio::new(peripherals.SIO);
// Many of the following systems require a watchdog. I do not know what this does, either, but
// it may be some failsafe software that will reset operations if the watchdog detects a lack
// of activity.
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
// Here we grab the GPIO pins in bank 0.
let pins = Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
// Initialize an abstraction of the clock system with a batch of standard hardware clocks.
let clocks = init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// An abstraction for a timer which we can use to delay the code.
let mut timer = Timer::new(peripherals.TIMER, &mut peripherals.RESETS, &clocks);
let mut led = pins.led.into_function();
// Grab the clock and data pins for SPI1. For Clock pins and for Data pins, there are only two
// pins each on the Pico which can function for SPI1.
let spi_clk = pins.gpio2.into_function();
let spi_sdo = pins.gpio3.into_function();
// let spi_sdi = pins.gpio4.into_function();
// Chip select 1 means the chip is not enabled
let mut board_select = pins.gpio13.into_function();
let mut data_command = pins.gpio15.into_function();
let mut reset = pins.gpio14.into_function();
let _ = reset.set_low();
let _ = board_select.set_high();
let _ = data_command.set_high();
// Now, create the SPI function abstraction for SPI1 with spi_clk and spi_sdo.
let mut spi = Spi::<_, _, _, 8>::new(peripherals.SPI0, (spi_sdo, spi_clk)).init(
&mut peripherals.RESETS,
// The SPI system uses the peripheral clock
clocks.peripheral_clock.freq(),
// Transmit data at a rate of 32Mbit.
32_u32.MHz(),
// Run with SPI Mode 1. This means that the clock line should start high and that data will
// be sampled starting at the first falling edge.
embedded_hal::spi::MODE_3,
);
let mut display = ST7789Display::new(board_select, data_command, spi);
let _ = reset.set_high();
timer.delay_ms(10);
for step in SETUP_PROGRAM {
let mut display = display.acquire();
display.send_command(&step, &mut timer);
}
timer.delay_ms(1000);
/*
let mut framebuf = [0; FRAMEBUF];
let mut canvas = Canvas::new(&mut framebuf, COLUMNS);
*/
let mut canvas = FrameBuf::new();
let white = RGB { r: 63, g: 63, b: 63 };
let mut count = 0;
loop {
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 });
// canvas.square(10, 70, 160, 310, &white);
{
let display = display.acquire();
let _ = led.set_high();
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(1000);
}
}

View File

@ -1,204 +0,0 @@
use embedded_hal::{delay::DelayNs, digital::OutputPin, spi::SpiBus};
use rp_pico::hal::{
gpio::{FunctionSio, Pin, PinId, PullDown, SioOutput},
spi::{Enabled, SpiDevice, ValidSpiPinout},
Spi, Timer,
};
pub struct Step {
param_cnt: usize,
command: u8,
params: [u8; 4],
delay: Option<u32>,
}
impl Step {
pub fn send_command<D, Pinout, P>(
&self,
spi: &mut Spi<Enabled, D, Pinout, 8>,
data_command: &mut Pin<P, FunctionSio<SioOutput>, PullDown>,
) where
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
P: PinId,
{
let _ = data_command.set_low();
let _ = spi.write(&[self.command]);
if self.param_cnt > 0 {
let _ = data_command.set_high();
let _ = spi.write(&self.params[0..self.param_cnt]);
}
}
}
const NOP: u8 = 0x00;
const SWRESET: Step = Step {
param_cnt: 0,
command: 0x01,
params: [0, 0, 0, 0],
delay: Some(150),
};
const SLPOUT: Step = Step {
param_cnt: 0,
command: 0x11,
params: [0, 0, 0, 0],
delay: Some(10),
};
const COLMOD: u8 = 0x3a;
const MADCTL: Step = Step {
param_cnt: 1,
command: 0x36,
params: [0x00, 0, 0, 0],
delay: None,
};
const CASET: u8 = 0x2a;
const RASET: u8 = 0x2b;
const INVON: Step = Step {
param_cnt: 0,
command: 0x21,
params: [0, 0, 0, 0],
delay: Some(10),
};
const NORON: Step = Step {
param_cnt: 0,
command: 0x13,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPOFF: Step = Step {
param_cnt: 0,
command: 0x28,
params: [0, 0, 0, 0],
delay: Some(10),
};
const DISPON: Step = Step {
param_cnt: 0,
command: 0x29,
params: [0, 0, 0, 0],
delay: Some(10),
};
const RAMWR: u8 = 0x2c;
// Adafruit setup instructions
// SWRESET (0x01), 150ms delay
// SLPOUT (0x11), 10ms delay
// COLMOD (0x3a) 0x55 (65K RGB, 16bit/pixel), 10ms delay
// MADCTL (0x36) 0x00,
// memory data access control, RGB
// CASET 0x00, 0, 0, 170,
// column address set, 4 parameters
// 0x00, 0x00 indicates xstart is 0
// 0x00, 170 indicates xend is 170
// RASET 0x00, 0, 320 >> 8, 320 & 0xFF,
// row address set, 4 parameters
// 0x00, 0x00 indicates ystart is 0
// 3230 >> 8, 320 & 0xff indicates that 320 is the last y address
// INVON, 10ms delay
// invert the display
// NORON, 10ms delay
// normal display mode
// DISPON, 10ms delay
// turn the display on
pub const SETUP_PROGRAM: [Step; 8] = [
SWRESET,
SLPOUT,
Step {
param_cnt: 1,
command: COLMOD,
params: [0x66, 0, 0, 0],
delay: Some(10),
},
MADCTL,
Step {
param_cnt: 4,
command: CASET,
params: [0, 35, 0, 204],
delay: None,
},
/*
Step {
param_cnt: 4,
command: RASET,
params: [0, 0, (320 >> 8) as u8, (320 & 0xff) as u8],
delay: None,
},
*/
INVON,
NORON,
DISPON,
];
pub struct ST7789Display<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
inner: ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789Display<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn new(
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
) -> Self {
Self {
inner: ST7789DisplayEnabled {
board_select,
data_command,
spi,
},
}
}
pub fn acquire(
&mut self,
) -> &mut ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout> {
self.inner.board_select.set_low();
&mut self.inner
}
}
pub struct ST7789DisplayEnabled<
BoardSelectId: PinId,
DataCommandId: PinId,
D: SpiDevice,
Pinout: ValidSpiPinout<D>,
> {
board_select: Pin<BoardSelectId, FunctionSio<SioOutput>, PullDown>,
data_command: Pin<DataCommandId, FunctionSio<SioOutput>, PullDown>,
spi: Spi<Enabled, D, Pinout, 8>,
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>>
ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
pub fn send_command(&mut self, step: &Step, timer: &mut Timer) {
step.send_command(&mut self.spi, &mut self.data_command);
if let Some(delay) = step.delay {
timer.delay_ms(delay);
}
}
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);
// let _ = DISPON.send_command(&mut self.spi, &mut self.data_command);
}
}
impl<BoardSelectId: PinId, DataCommandId: PinId, D: SpiDevice, Pinout: ValidSpiPinout<D>> Drop
for ST7789DisplayEnabled<BoardSelectId, DataCommandId, D, Pinout>
{
fn drop(&mut self) {
self.board_select.set_high();
}
}

View File

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

View File

@ -4,3 +4,12 @@ version = "0.1.0"
edition = "2021"
[dependencies]
axum = { version = "0.8.1", features = ["macros"] }
visions-types = { path = "../types" }
serde = { version = "1.0.217", features = ["derive", "serde_derive"] }
tokio = { version = "1.43.0", features = ["full", "rt"] }
tower-http = { version = "0.6.2", features = ["cors"] }
typeshare = "1.0.4"
uuid = { version = "1.13.1", features = ["v4"] }
result-extended = { path = "../../result-extended" }
thiserror = "2.0.11"

View File

@ -1,3 +1,155 @@
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) => {
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 {
(
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(Deserialize, 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(Deserialize, 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*

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

@ -0,0 +1,17 @@
[package]
name = "visions-client"
version = "0.1.0"
edition = "2021"
[dependencies]
visions-types = { path = "../types" }
gloo-console = "0.3.0"
gloo-net = "0.6.0"
serde = { version = "1.0.217", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"
serde_json = "1.0.138"
wasm-bindgen = "0.2.100"
wasm-bindgen-futures = "0.4.50"
web-sys = "0.3.77"
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }

View File

@ -1,14 +0,0 @@
version: '3'
tasks:
dev:
cmds:
- cd ../visions-types && task build
- npm install
- npm run start
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

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

@ -0,0 +1,36 @@
:root {
--spacing-s: 4px;
--spacing-m: 8px;
--shadow-shallow: 2px 2px 1px;
}
body {
background-color: hsl(0, 0%, 95%);
font-family: Ariel, sans-serif;
}
.card {
display: flex;
flex-direction: column;
align-items: space-between;
border: 1px solid black;
box-shadow: var(--shadow-shallow);
border-radius: var(--spacing-s);
padding: var(--spacing-m);
}
.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;
}

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"
]
}
}

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

@ -0,0 +1,44 @@
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>>;
}
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> {
todo!()
}
}

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

@ -0,0 +1,128 @@
use std::rc::Rc;
use gloo_console::log;
use visions_types::AuthResponse;
use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement;
use yew::prelude::*;
mod client;
use client::*;
struct AuthInfo {
session_id: Option<String>,
}
impl Default for AuthInfo {
fn default() -> Self {
Self { session_id: None }
}
}
enum AuthAction {
Auth(String),
Unauth,
}
impl Reducible for AuthInfo {
type Action = AuthAction;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
// log!("reduce", action);
match action {
AuthAction::Auth(session_id) => Self {
session_id: Some(session_id),
}
.into(),
AuthAction::Unauth => Self { session_id: None }.into(),
}
}
}
#[derive(Properties, PartialEq)]
struct LoginProps {
on_login: Callback<(String, String)>,
}
#[function_component]
fn Login(LoginProps { on_login }: &LoginProps) -> Html {
let username = use_state(|| "".to_owned());
let password = use_state(|| "".to_owned());
let on_click = {
let on_login = on_login.clone();
let username = username.clone();
let password = password.clone();
Callback::from(move |_| on_login.emit((username.to_string(), password.to_string())))
};
let on_username_changed = {
let username = username.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
username.set(input.value());
}
})
};
let on_password_changed = {
let password = password.clone();
Callback::from(move |event: Event| {
let input = event
.target()
.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
password.set(input.value());
}
})
};
html! {
<div class="login-form">
<div class="card">
<h1>{"Welcome to Visions VTT"}</h1>
<input type="text" name="username" placeholder="username" onchange={on_username_changed} />
<input type="password" name="password" placeholder="password" onchange={on_password_changed} />
<button onclick={on_click}>{"Login"}</button>
</div>
</div>
}
}
#[function_component]
fn App() -> Html {
let auth_info = use_reducer(AuthInfo::default);
let on_login = {
let auth_info = auth_info.clone();
Callback::from(move |(username, password)| {
let auth_info = auth_info.clone();
wasm_bindgen_futures::spawn_local(async move {
let client = Connection::new();
match client.auth(username, password).await {
Ok(AuthResponse::Success(session_id)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Ok(AuthResponse::PasswordReset(session_id)) => {
auth_info.dispatch(AuthAction::Auth(session_id.as_str().to_owned()))
}
Err(ClientError::Unauthorized) => todo!(),
Err(ClientError::Err(status)) => todo!(),
};
})
})
};
if auth_info.session_id.is_some() {
html! { <p>{ "this is just a thing" }</p> }
} else {
html! { <Login on_login={on_login.clone()} /> }
}
}
fn main() {
yew::Renderer::<App>::new().render();
}

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();
}