Compare commits
640 Commits
cojson-sto
...
jazz-nodej
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db53161296 | ||
|
|
cb4a116cec | ||
|
|
013199b9b2 | ||
|
|
a8b74ff703 | ||
|
|
b1985a9161 | ||
|
|
3bf512719f | ||
|
|
d83ed69d41 | ||
|
|
fdde8db664 | ||
|
|
dd5581ba2d | ||
|
|
07fe2b9dcf | ||
|
|
b297c93b80 | ||
|
|
d2e62e5b44 | ||
|
|
fe73ce7514 | ||
|
|
0fed16cea4 | ||
|
|
08804ad435 | ||
|
|
79fa7724ad | ||
|
|
4604c2184a | ||
|
|
11bac697fb | ||
|
|
96cec27f89 | ||
|
|
bcd412b8f9 | ||
|
|
6b456e2841 | ||
|
|
1df72b3dc8 | ||
|
|
402d692739 | ||
|
|
fad46b2fb5 | ||
|
|
0153c80cf2 | ||
|
|
4f75dc8d97 | ||
|
|
e2c79cccb5 | ||
|
|
c14a0e05be | ||
|
|
016dd3a5dd | ||
|
|
5c4ca9103c | ||
|
|
b4aad92907 | ||
|
|
56d1e095a1 | ||
|
|
6dee9aae49 | ||
|
|
a10bff981e | ||
|
|
e333f7884a | ||
|
|
8ea7bf237b | ||
|
|
5e8409fa08 | ||
|
|
23354c1767 | ||
|
|
0efb69d0db | ||
|
|
0462c4e41b | ||
|
|
70a5673197 | ||
|
|
9ec3203485 | ||
|
|
1a46f9b2e1 | ||
|
|
77bb26a8d7 | ||
|
|
2a36dcf592 | ||
|
|
fc2bcadbe2 | ||
|
|
46b0cc1adb | ||
|
|
d75d1c6a3f | ||
|
|
13b236aeed | ||
|
|
1c0a61b0b2 | ||
|
|
ceb92438f4 | ||
|
|
9bdd62ed4c | ||
|
|
3f5ef7e799 | ||
|
|
e7a573fa94 | ||
|
|
364060eaa7 | ||
|
|
a3ddc3d5e0 | ||
|
|
185f747adb | ||
|
|
895d281088 | ||
|
|
b44e4354f7 | ||
|
|
3fcb0665ec | ||
|
|
be49d33ce5 | ||
|
|
c7dae1608b | ||
|
|
b020c5868b | ||
|
|
eae42d3afe | ||
|
|
a816e2436e | ||
|
|
b09e35e372 | ||
|
|
d2c8121c9c | ||
|
|
380bb88ffa | ||
|
|
e0e3726b3c | ||
|
|
c2253a7979 | ||
|
|
9d244226ec | ||
|
|
71df5e3a59 | ||
|
|
3a738dad88 | ||
|
|
56d301cfde | ||
|
|
5efec6d5ea | ||
|
|
32769b24f1 | ||
|
|
6ab53c263d | ||
|
|
e7f3e4e242 | ||
|
|
8bb5201647 | ||
|
|
a9fc94f53d | ||
|
|
ca7c0510d1 | ||
|
|
1bf16f0859 | ||
|
|
21b503c188 | ||
|
|
0053e9796c | ||
|
|
e84941b1b1 | ||
|
|
57f6f8d67e | ||
|
|
5b8e69d973 | ||
|
|
7213b1bfa3 | ||
|
|
11f0770f08 | ||
|
|
44e6dc3ae8 | ||
|
|
b5d20d2488 | ||
|
|
0185545838 | ||
|
|
8c8f85859c | ||
|
|
104384409e | ||
|
|
179827ae56 | ||
|
|
6645829876 | ||
|
|
68cb302722 | ||
|
|
8dc33f2790 | ||
|
|
5f64ba326c | ||
|
|
7ccb15107c | ||
|
|
b102964743 | ||
|
|
216d50a09c | ||
|
|
07ea59fdcb | ||
|
|
932a84a47f | ||
|
|
34dda7bdbd | ||
|
|
49fa153581 | ||
|
|
c80b827775 | ||
|
|
a2bf9f988a | ||
|
|
ac27b2d5c2 | ||
|
|
c813518fdc | ||
|
|
d5034ed5c3 | ||
|
|
cf2c29a365 | ||
|
|
d948823db6 | ||
|
|
060ad4630d | ||
|
|
0ddceac4c0 | ||
|
|
a862cb8819 | ||
|
|
4246aed7db | ||
|
|
41554e0e0b | ||
|
|
93c4d8155e | ||
|
|
24eefd49f1 | ||
|
|
e712f1e8ef | ||
|
|
33db0fd654 | ||
|
|
478ded93de | ||
|
|
89ad1fb79d | ||
|
|
1ba40806ec | ||
|
|
73ae281e4a | ||
|
|
a35353c987 | ||
|
|
1cb91003cc | ||
|
|
d850022491 | ||
|
|
93792ab6f6 | ||
|
|
95dfe7af6a | ||
|
|
734258eb17 | ||
|
|
f3bcf96fad | ||
|
|
5cf0bc1911 | ||
|
|
d32a6b275f | ||
|
|
6caba9f8e7 | ||
|
|
641f1dbfbe | ||
|
|
58d9a104d6 | ||
|
|
7b9d24c8ef | ||
|
|
4225fdd537 | ||
|
|
9fdc91c6de | ||
|
|
93d8c85e5c | ||
|
|
929cddc3c3 | ||
|
|
e0bc63f016 | ||
|
|
b29ac306ea | ||
|
|
e8e883f4d6 | ||
|
|
3325ff1cd6 | ||
|
|
4fe14f03b4 | ||
|
|
90e2a661e4 | ||
|
|
6ed53ecb79 | ||
|
|
c18775766c | ||
|
|
4bb3a6209a | ||
|
|
0f44a547a4 | ||
|
|
1e2f6d8f14 | ||
|
|
7e5b176930 | ||
|
|
b420eab503 | ||
|
|
b370e2e14e | ||
|
|
1fabee297d | ||
|
|
484dc460c5 | ||
|
|
0cb8756124 | ||
|
|
95d0f0221b | ||
|
|
0c9c0fcd60 | ||
|
|
8be0dd133c | ||
|
|
e68e0ada0d | ||
|
|
49a7349e4d | ||
|
|
979c7241a4 | ||
|
|
e011e4a049 | ||
|
|
92bccf5974 | ||
|
|
2c1d6dcb8f | ||
|
|
75f45ec0b2 | ||
|
|
c0ebcadda9 | ||
|
|
109e9b6a5b | ||
|
|
d0c3d08e42 | ||
|
|
7514830edb | ||
|
|
6f27128b87 | ||
|
|
376518d4ef | ||
|
|
272fc85c13 | ||
|
|
579e4f93ee | ||
|
|
a9accdc3a8 | ||
|
|
b403db51d4 | ||
|
|
108cae037f | ||
|
|
c51897ab9e | ||
|
|
67171cb07a | ||
|
|
6ae3bf6ac9 | ||
|
|
b64b15877a | ||
|
|
19f52b7361 | ||
|
|
9dcd15dbc8 | ||
|
|
a423eeea3b | ||
|
|
99be6a3566 | ||
|
|
e97f730c0f | ||
|
|
f5642335ff | ||
|
|
bf0f8ec824 | ||
|
|
fb9ef4ea20 | ||
|
|
86363197cd | ||
|
|
e93187c971 | ||
|
|
1a86e13cf1 | ||
|
|
b863c1d20a | ||
|
|
4c8d658c25 | ||
|
|
69d37437ef | ||
|
|
220bdbae62 | ||
|
|
b23b556b79 | ||
|
|
ce721cf3d1 | ||
|
|
41363415fe | ||
|
|
b91d0769d5 | ||
|
|
ad16690826 | ||
|
|
ca7b81c36a | ||
|
|
a632ce1477 | ||
|
|
ca7011e9af | ||
|
|
34df432ee8 | ||
|
|
dfa7178041 | ||
|
|
a3e9a3b686 | ||
|
|
599db049f2 | ||
|
|
7cba6dd690 | ||
|
|
2d406c9d58 | ||
|
|
d8fe2b10f1 | ||
|
|
c8343626ba | ||
|
|
5b188ec093 | ||
|
|
7ebbd80049 | ||
|
|
74c9e5d36d | ||
|
|
1a3530747f | ||
|
|
79899b9b18 | ||
|
|
6ef6a2f507 | ||
|
|
ab6d15c9f7 | ||
|
|
a0dc9139a2 | ||
|
|
220fa319d5 | ||
|
|
183d505a5e | ||
|
|
f960e7e736 | ||
|
|
d68ac84e03 | ||
|
|
30fbe2b6d7 | ||
|
|
e3a00570e1 | ||
|
|
499e02685a | ||
|
|
6b0418f772 | ||
|
|
1200aae47d | ||
|
|
20fdb09b33 | ||
|
|
522b12dc42 | ||
|
|
a7cd0dcce5 | ||
|
|
fd86c11336 | ||
|
|
4fc414d744 | ||
|
|
6d49e9b06c | ||
|
|
8b866e288b | ||
|
|
bf22588b0e | ||
|
|
60d5ca2811 | ||
|
|
9e5dcdfa69 | ||
|
|
3cc39dd5ed | ||
|
|
6878060346 | ||
|
|
9840061137 | ||
|
|
6f84e00463 | ||
|
|
1e82d0d34e | ||
|
|
f35bc468b3 | ||
|
|
719071c286 | ||
|
|
c4b439e2e6 | ||
|
|
77c2b56ceb | ||
|
|
0b17b7ad5a | ||
|
|
db3011a1c9 | ||
|
|
b47c695b97 | ||
|
|
55c1c893ba | ||
|
|
bde684fe30 | ||
|
|
89b6c9004b | ||
|
|
97cdfbddaf | ||
|
|
584ee2d136 | ||
|
|
21771c4725 | ||
|
|
7e8f1bed15 | ||
|
|
226ae03603 | ||
|
|
96c494f5ee | ||
|
|
23ba00422f | ||
|
|
52675c9c68 | ||
|
|
1b113e0114 | ||
|
|
0a930f5eeb | ||
|
|
84f5a83648 | ||
|
|
5fa277c254 | ||
|
|
d49c7f2dd4 | ||
|
|
a78f1688d9 | ||
|
|
cd37a846d8 | ||
|
|
63374ccb6d | ||
|
|
efe2d91fb3 | ||
|
|
234b2a019b | ||
|
|
4bbcd366bc | ||
|
|
5724f8747a | ||
|
|
38d44103d1 | ||
|
|
96a7ff68e7 | ||
|
|
fb78a55f76 | ||
|
|
fdc7fc7bcf | ||
|
|
ed5643aaf1 | ||
|
|
ac431ef9ef | ||
|
|
0940508637 | ||
|
|
704af7d04c | ||
|
|
e4e476a834 | ||
|
|
ece35b3c6f | ||
|
|
b26eab50b3 | ||
|
|
b42313a285 | ||
|
|
129e2c1668 | ||
|
|
87ddb81562 | ||
|
|
daee49cd9d | ||
|
|
3aaf773b0a | ||
|
|
460478fc65 | ||
|
|
fe8b5f45b9 | ||
|
|
01ac646c8e | ||
|
|
d4b9fbcc60 | ||
|
|
1cfa279543 | ||
|
|
e35be73bcc | ||
|
|
f8a5c46e18 | ||
|
|
1c7d85ce76 | ||
|
|
19004b4c36 | ||
|
|
930fa689a7 | ||
|
|
18a7b2d6b4 | ||
|
|
d2e03ff9d3 | ||
|
|
77a9c8395e | ||
|
|
c4151fcb95 | ||
|
|
4c5c21bba2 | ||
|
|
f0f6f1b71c | ||
|
|
a9d6d5a1db | ||
|
|
7849ce6de7 | ||
|
|
354bdcdbfb | ||
|
|
8ecd3e88c8 | ||
|
|
85d2b627f1 | ||
|
|
88fd92e4dc | ||
|
|
952982e7ea | ||
|
|
22e7c27af7 | ||
|
|
59c18c34de | ||
|
|
6acbaede44 | ||
|
|
1a44f875b3 | ||
|
|
9d935fe1d0 | ||
|
|
e5eed5b9b7 | ||
|
|
05a549f04f | ||
|
|
a5e68a4fae | ||
|
|
016a9e342a | ||
|
|
627d8950ae | ||
|
|
770ce08c10 | ||
|
|
69ac514b3b | ||
|
|
b1481748f9 | ||
|
|
49944e323f | ||
|
|
15310db389 | ||
|
|
ea5c5a2604 | ||
|
|
e461dd1355 | ||
|
|
e299c3e9d8 | ||
|
|
406c47271f | ||
|
|
05c7efea85 | ||
|
|
ce7ddf7055 | ||
|
|
beb40b5db6 | ||
|
|
2def752cc4 | ||
|
|
bacf3ae86a | ||
|
|
0fef382f2e | ||
|
|
95523d8538 | ||
|
|
71f7220bfd | ||
|
|
2212c6deac | ||
|
|
fb3efe4cfd | ||
|
|
e66ac6a7d0 | ||
|
|
7ab3908848 | ||
|
|
921f1fbfe8 | ||
|
|
2ac455f8b5 | ||
|
|
1ce881aed2 | ||
|
|
b1b5140951 | ||
|
|
b109c23233 | ||
|
|
a7a34a0b6e | ||
|
|
4bf63934e1 | ||
|
|
16f572282f | ||
|
|
44380c3700 | ||
|
|
dc46cb1386 | ||
|
|
3ccb1e8ad7 | ||
|
|
d973c5f48b | ||
|
|
f4af78c834 | ||
|
|
e6d323fd30 | ||
|
|
e6ab56aeb5 | ||
|
|
779765b649 | ||
|
|
6da730779a | ||
|
|
a3e77edc57 | ||
|
|
ed00308986 | ||
|
|
89e9092e0f | ||
|
|
f8b11754c8 | ||
|
|
4b38d0793c | ||
|
|
b2156f8154 | ||
|
|
3a5422e635 | ||
|
|
54d3d76868 | ||
|
|
f4dc0ec1b7 | ||
|
|
f500db2dd3 | ||
|
|
95f64f9934 | ||
|
|
cccb0e1a21 | ||
|
|
b434a4227f | ||
|
|
6ba4dc1f04 | ||
|
|
2fe4c81d1e | ||
|
|
5c00264184 | ||
|
|
c744849c9b | ||
|
|
f59b278f00 | ||
|
|
b26c155d5f | ||
|
|
6da79b8745 | ||
|
|
0b92591b17 | ||
|
|
974456db54 | ||
|
|
a1326a80fe | ||
|
|
00d6946b24 | ||
|
|
c4ffde93c0 | ||
|
|
37bfe967ea | ||
|
|
9abbbfd6fb | ||
|
|
155cd08e39 | ||
|
|
e2e6bdf3bd | ||
|
|
810c42c743 | ||
|
|
99e4c1301e | ||
|
|
8c86a831fc | ||
|
|
5e976416a4 | ||
|
|
0339e14260 | ||
|
|
4b94fcebf1 | ||
|
|
ddd2a79f37 | ||
|
|
01a8f2dab3 | ||
|
|
801629d2c1 | ||
|
|
87d62c941f | ||
|
|
7e6e0fdcc5 | ||
|
|
a73b07424c | ||
|
|
0f9b983132 | ||
|
|
43e25902d3 | ||
|
|
2c27c8517f | ||
|
|
b496058a0e | ||
|
|
4313663bd1 | ||
|
|
dbdbfbd07a | ||
|
|
184b23d61f | ||
|
|
5c03b4f668 | ||
|
|
bdbe777d68 | ||
|
|
a838a18647 | ||
|
|
dd8dba63ea | ||
|
|
3f5a664ee7 | ||
|
|
707292e1ff | ||
|
|
9a81b63943 | ||
|
|
30216b7b80 | ||
|
|
b2fc91c2ce | ||
|
|
ef0328833c | ||
|
|
6a93f17a4a | ||
|
|
01bd07ac66 | ||
|
|
88859cfeca | ||
|
|
dfe563e2bc | ||
|
|
7fc0ff981d | ||
|
|
1a9132102d | ||
|
|
d39638282f | ||
|
|
219071654d | ||
|
|
7c415db7bd | ||
|
|
4354c340fc | ||
|
|
a4b484fa36 | ||
|
|
3757d12dc4 | ||
|
|
c3a97b29a9 | ||
|
|
b65e30ec70 | ||
|
|
23a1e0266a | ||
|
|
76acecfe50 | ||
|
|
5031c77afb | ||
|
|
af90b8c989 | ||
|
|
d06b4adad0 | ||
|
|
b961cde946 | ||
|
|
8cbbe2f312 | ||
|
|
c15a49d82d | ||
|
|
fc5b670c73 | ||
|
|
c8adcc4c47 | ||
|
|
41a755fe41 | ||
|
|
8def1bb29e | ||
|
|
d379b04e33 | ||
|
|
17a30e054e | ||
|
|
93809911de | ||
|
|
edeb2ca9f4 | ||
|
|
01662fc3b8 | ||
|
|
7d8f4b4c00 | ||
|
|
e2a3896bf0 | ||
|
|
446de8e0ff | ||
|
|
5ae6c95878 | ||
|
|
7cde349a50 | ||
|
|
61e640f574 | ||
|
|
ed122d9d8e | ||
|
|
34817f4536 | ||
|
|
134d2f0fda | ||
|
|
142973827c | ||
|
|
0998a0eabf | ||
|
|
a96108478b | ||
|
|
47444888c3 | ||
|
|
a4769058f4 | ||
|
|
a4cf4c40d4 | ||
|
|
934fe4d29b | ||
|
|
408012f2e5 | ||
|
|
d0078b830e | ||
|
|
f7f091e18c | ||
|
|
a969430247 | ||
|
|
e52948b2b7 | ||
|
|
53bb1b230b | ||
|
|
54e83aeaaa | ||
|
|
aa3129cab5 | ||
|
|
90520dddd7 | ||
|
|
03eb77070a | ||
|
|
4ba5c255b6 | ||
|
|
01817db873 | ||
|
|
46fcbd6c01 | ||
|
|
aa3e3de09e | ||
|
|
af3d48764d | ||
|
|
091f36b736 | ||
|
|
7107f79f42 | ||
|
|
9922db2336 | ||
|
|
75db570198 | ||
|
|
28a09f377b | ||
|
|
fd2e0855bb | ||
|
|
82e1d57bd6 | ||
|
|
a2fbb0b0c8 | ||
|
|
8feddf9932 | ||
|
|
feed34b1cf | ||
|
|
662c980cf2 | ||
|
|
f5ae530890 | ||
|
|
46bf7dd3ce | ||
|
|
5d4eb38204 | ||
|
|
66da658075 | ||
|
|
3477b74573 | ||
|
|
f3de4906b7 | ||
|
|
caded3f189 | ||
|
|
5196395495 | ||
|
|
8089a7ed9f | ||
|
|
99230d31d2 | ||
|
|
94bca03f59 | ||
|
|
49719b6e6d | ||
|
|
1bdb781452 | ||
|
|
c336f69a6b | ||
|
|
c8cb1ce208 | ||
|
|
814a6a80cd | ||
|
|
5fdfe18b32 | ||
|
|
7b7a74778b | ||
|
|
39dbd46556 | ||
|
|
1db4a14be4 | ||
|
|
4a4ea4e196 | ||
|
|
e0724441eb | ||
|
|
5d47895515 | ||
|
|
c1dfac7260 | ||
|
|
bf29cb3bae | ||
|
|
a0a9b3f851 | ||
|
|
4c4deb22c9 | ||
|
|
a42c497055 | ||
|
|
f1dcdb20bc | ||
|
|
46330ae201 | ||
|
|
bfe3595b4c | ||
|
|
34c39e6a55 | ||
|
|
5a85501919 | ||
|
|
97a4282e5e | ||
|
|
39c13b50a3 | ||
|
|
ad304e321b | ||
|
|
8c0b2da461 | ||
|
|
72fce45b2b | ||
|
|
1f49d7fda6 | ||
|
|
eec8ee7027 | ||
|
|
188eb2e1e3 | ||
|
|
62867b32d9 | ||
|
|
ccebd2447d | ||
|
|
08dca75789 | ||
|
|
16b3e1381b | ||
|
|
f1cd639a09 | ||
|
|
be18e4de14 | ||
|
|
7e62c91d44 | ||
|
|
b2d5a103b5 | ||
|
|
4ee2cad39e | ||
|
|
b7c8a0038b | ||
|
|
8c27e8c379 | ||
|
|
0133aa47ff | ||
|
|
5659c925a2 | ||
|
|
27779ac792 | ||
|
|
3f1bfa4629 | ||
|
|
15a693c3ed | ||
|
|
b1d620e145 | ||
|
|
478fbd0aa9 | ||
|
|
ee906b7351 | ||
|
|
dd15f21ccb | ||
|
|
d7cd5fda7c | ||
|
|
174300b00f | ||
|
|
b2c8d8c855 | ||
|
|
2bad2b6bfe | ||
|
|
880d0ff855 | ||
|
|
e66cbee6cd | ||
|
|
03e470721e | ||
|
|
ecf73bcfa7 | ||
|
|
2c3a500286 | ||
|
|
8b83061cf4 | ||
|
|
e75c3207d6 | ||
|
|
41d4b5ba0b | ||
|
|
21fa1b168b | ||
|
|
91e5e7f2ab | ||
|
|
e3f7e2f1bd | ||
|
|
084cf80c60 | ||
|
|
632e3bbb08 | ||
|
|
17d17833b2 | ||
|
|
8e22bd9c1e | ||
|
|
98213743f3 | ||
|
|
bb855ed83d | ||
|
|
a8ef49e228 | ||
|
|
e0ad32dbd2 | ||
|
|
62bf769cad | ||
|
|
7488ff25b2 | ||
|
|
b69c9da983 | ||
|
|
d30fdef8aa | ||
|
|
9c5a6b9833 | ||
|
|
d300d265c4 | ||
|
|
1d72ce587f | ||
|
|
3fdb41dcb9 | ||
|
|
f20de2f04a | ||
|
|
31b31f111b | ||
|
|
2ae9fb9778 | ||
|
|
cd0da0f6bf | ||
|
|
cd9bfbb9fa | ||
|
|
ed0428bf97 | ||
|
|
c038a02051 | ||
|
|
31abcfeef4 | ||
|
|
5f32d9ccf5 | ||
|
|
0510600104 | ||
|
|
7f30fbf3c5 | ||
|
|
3d56260ca4 | ||
|
|
1137775da9 | ||
|
|
3951fdc938 | ||
|
|
5779e357dd | ||
|
|
2842d80f26 | ||
|
|
96387d8023 | ||
|
|
6720c19233 | ||
|
|
ef732b4700 | ||
|
|
ee7e3ee5a7 | ||
|
|
ceeed88fa5 | ||
|
|
79353a1d97 | ||
|
|
7fdc42c62f | ||
|
|
3a2e854a88 | ||
|
|
661a2d023a | ||
|
|
6ef5b6b2ab | ||
|
|
1384ebed84 | ||
|
|
17e53f9998 | ||
|
|
cfb1f39efe | ||
|
|
2234276dcf | ||
|
|
bb0a6a0600 | ||
|
|
0a6eb0c10a | ||
|
|
88b67d89e0 | ||
|
|
1a65d826b2 | ||
|
|
6c65ec2b46 | ||
|
|
5b578a832d | ||
|
|
042afc52d7 | ||
|
|
1b83493964 | ||
|
|
3b50da1a74 | ||
|
|
8e0fc74d9f | ||
|
|
e28326f32c | ||
|
|
d7e8b0b9da | ||
|
|
c46a1f6b0a | ||
|
|
7947918278 | ||
|
|
50c36e7255 | ||
|
|
c39a7ed1b7 | ||
|
|
83762dbb0f | ||
|
|
7c82e12508 | ||
|
|
6db149be36 | ||
|
|
909a101f99 | ||
|
|
df0b6fe138 | ||
|
|
0543756016 |
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
5
.changeset/angry-carpets-explain.md
Normal file
5
.changeset/angry-carpets-explain.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson-transport-ws": patch
|
||||
---
|
||||
|
||||
Handle ws closing while buffering
|
||||
5
.changeset/bright-files-help.md
Normal file
5
.changeset/bright-files-help.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Improve logging
|
||||
5
.changeset/calm-experts-dance.md
Normal file
5
.changeset/calm-experts-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson-transport-ws": patch
|
||||
---
|
||||
|
||||
Wait if WS buffer is full
|
||||
24
.changeset/config.json
Normal file
24
.changeset/config.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [
|
||||
[
|
||||
"cojson",
|
||||
"jazz-tools",
|
||||
"jazz-browser",
|
||||
"jazz-browser-media-images",
|
||||
"jazz-react",
|
||||
"jazz-nodejs",
|
||||
"jazz-run",
|
||||
"cojson-transport-ws",
|
||||
"cojson-storage-indexeddb",
|
||||
"cojson-storage-sqlite"
|
||||
]
|
||||
],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
29
.changeset/pre.json
Normal file
29
.changeset/pre.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "hotfixes",
|
||||
"initialVersions": {
|
||||
"jazz-example-chat": "0.0.79",
|
||||
"jazz-inspector": "0.0.57",
|
||||
"jazz-example-pets": "0.0.97",
|
||||
"jazz-example-todo": "0.0.96",
|
||||
"cojson": "0.7.31",
|
||||
"cojson-storage-indexeddb": "0.7.31",
|
||||
"cojson-storage-sqlite": "0.7.31",
|
||||
"cojson-transport-ws": "0.7.31",
|
||||
"hash-slash": "0.2.0",
|
||||
"jazz-browser": "0.7.32",
|
||||
"jazz-browser-media-images": "0.7.32",
|
||||
"jazz-nodejs": "0.7.32",
|
||||
"jazz-react": "0.7.32",
|
||||
"jazz-run": "0.7.32",
|
||||
"jazz-tools": "0.7.32"
|
||||
},
|
||||
"changesets": [
|
||||
"angry-carpets-explain",
|
||||
"bright-files-help",
|
||||
"calm-experts-dance",
|
||||
"rude-carrots-fetch",
|
||||
"shaggy-hats-beg",
|
||||
"small-items-laugh"
|
||||
]
|
||||
}
|
||||
8
.changeset/rude-carrots-fetch.md
Normal file
8
.changeset/rude-carrots-fetch.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"cojson-storage-indexeddb": patch
|
||||
"cojson-storage-sqlite": patch
|
||||
"cojson-transport-ws": patch
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Allow crashing whole node on peer errors
|
||||
5
.changeset/shaggy-hats-beg.md
Normal file
5
.changeset/shaggy-hats-beg.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Throw properly on peer that should crash on close
|
||||
6
.changeset/small-items-laugh.md
Normal file
6
.changeset/small-items-laugh.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"cojson-storage-sqlite": patch
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Make simulated errors even more likely
|
||||
72
.github/workflows/build-and-deploy.yaml
vendored
72
.github/workflows/build-and-deploy.yaml
vendored
@@ -3,54 +3,66 @@ name: Build and Deploy
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
build-examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["chat", "pets", "todo", "inspector"]
|
||||
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
- name: Nuke Workspace
|
||||
run: |
|
||||
rm package.json yarn.lock;
|
||||
- name: Yarn Build
|
||||
run: |
|
||||
yarn install --frozen-lockfile;
|
||||
yarn build;
|
||||
working-directory: ./examples/todo
|
||||
node-version: 16
|
||||
cache: 'pnpm'
|
||||
|
||||
- uses: satackey/action-docker-layer-caching@v0.0.11
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: docker-layer-caching-${{ github.workflow }}-{hash}
|
||||
restore-keys: |
|
||||
docker-layer-caching-${{ github.workflow }}-
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: gardencmp
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker Build & Push
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
export DOCKER_TAG=ghcr.io/gardencmp/jazz-example-todo:${{github.head_ref || github.ref_name}}-${{github.sha}}-$(date +%s) ;
|
||||
docker build . --file Dockerfile --tag $DOCKER_TAG;
|
||||
docker push $DOCKER_TAG;
|
||||
echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV
|
||||
working-directory: ./examples/todo
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
|
||||
- name: Docker Build & Push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ./examples/${{ matrix.example }}
|
||||
push: true
|
||||
tags: ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
deploy-examples:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-examples
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["chat", "pets", "todo", "inspector"]
|
||||
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: gacts/install-nomad@v1
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v1
|
||||
@@ -69,9 +81,9 @@ jobs:
|
||||
|
||||
export DOCKER_USER=gardencmp;
|
||||
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
|
||||
export DOCKER_TAG=${{ env.DOCKER_TAG }};
|
||||
export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
|
||||
|
||||
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
|
||||
cat job-instance.nomad;
|
||||
NOMAD_ADDR='http://control1v2-london:4646' nomad job run job-instance.nomad;
|
||||
working-directory: ./examples/todo
|
||||
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node_modules
|
||||
yarn-error.log
|
||||
lerna-debug.log
|
||||
docsTmp
|
||||
docsTmp
|
||||
.DS_Store
|
||||
.turbo
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
19
LICENSE.txt
Normal file
19
LICENSE.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright 2024, Garden Computing, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
84
README.md
84
README.md
@@ -1,82 +1,12 @@
|
||||
# Jazz - instant sync
|
||||
|
||||
Homepage: [jazz.tools](https://jazz.tools) — [Discord](https://discord.gg/utDMjHYg42)
|
||||
|
||||
Jazz is an open-source toolkit for *secure telepathic data.*
|
||||
|
||||
- Ship faster & simplify your frontend and backend
|
||||
- Get cross-device sync, real-time collaboration & offline support for free
|
||||
|
||||
[Jazz Global Mesh](https://jazz.tools/mesh) is serverless sync & storage for Jazz apps. (currently free!)
|
||||
# Jazz - Instant sync
|
||||
|
||||
|
||||
|
||||
## What is Secure Telepathic Data?
|
||||
**Jazz is an open-source toolkit for building apps with *distributed state.***
|
||||
|
||||
**Telepathic** means:
|
||||
- Homepage: [jazz.tools](https://jazz.tools)
|
||||
- Docs: [jazz.tools/docs](https://jazz.tools/docs)
|
||||
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
|
||||
- Updates: [Twitter](https://twitter.com/jazz_tools) & [Email](https://gcmp.io/news)
|
||||
|
||||
- **Read and write data as if it was local,** from anywhere in your app.
|
||||
- **Always have that data synced, instantly.** Across devices of the same user — or to other users (coming soon: to your backend, workers, etc.)
|
||||
|
||||
**Secure** means:
|
||||
|
||||
- **Fine-grained, role-based permissions are *baked into* your data.**
|
||||
- **Permissions are enforced everywhere, locally.** (using cryptography instead of through an API)
|
||||
- Roles can be changed dynamically, supporting changing teams, invite links and more.
|
||||
|
||||
## How to build an app with Jazz?
|
||||
|
||||
### Building a new app, completely with Jazz
|
||||
|
||||
It's still a bit early, but these are the rough steps:
|
||||
|
||||
1. Define your data model with [CoJSON Values](#cojson).
|
||||
2. Implement permission logic using [CoJSON Groups](#group).
|
||||
3. Hook up a user interface with [jazz-react](#jazz-react).
|
||||
|
||||
The best example is currently the [Todo List app](#example-app-todo-list).
|
||||
|
||||
### Gradually adding Jazz to an existing app
|
||||
|
||||
Coming soon: Jazz will support gradual adoption by integrating with your existing UI, auth and database.
|
||||
|
||||
## Example App: Todo List
|
||||
|
||||
The best example of Jazz is currently the Todo List app.
|
||||
|
||||
- Live version: https://example-todo.jazz.tools
|
||||
- Source code: [`./examples/todo`](./examples/todo). See the README there for a walk-through and running instructions.
|
||||
|
||||
# Documentation
|
||||
|
||||
Note: Since it's early days, this is the only source of documentation so far.
|
||||
|
||||
If you want to build something with Jazz, [join the Jazz Discord](https://discord.gg/utDMjHYg42) for encouragement and help!
|
||||
|
||||
## Overview: Main Packages
|
||||
|
||||
**`cojson`** → [DOCS](./DOCS.md#cojson)
|
||||
|
||||
A library implementing abstractions and protocols for "Collaborative JSON". This will soon be standardized and forms the basis of secure telepathic data.
|
||||
|
||||
**`jazz-react`** → [DOCS](./DOCS.md#jazz-react)
|
||||
|
||||
Provides you with everything you need to build react apps around CoJSON, including reactive hooks for telepathic data, local IndexedDB persistence, support for different auth providers and helpers for simple invite links for CoJSON groups.
|
||||
|
||||
### Supporting packages
|
||||
<small>
|
||||
|
||||
**`cojson-simple-sync`**
|
||||
|
||||
A generic CoJSON sync server you can run locally if you don't want to use Jazz Global Mesh (the default sync backend, at `wss://sync.jazz.tools`)
|
||||
|
||||
**`jazz-browser`** → [DOCS](./DOCS.md#jazz-browser)
|
||||
|
||||
framework-agnostic primitives that allow you to use CoJSON in the browser. Used to implement `jazz-react`, will be used to implement bindings for other frameworks in the future.
|
||||
|
||||
**`jazz-react-auth-local`** (and `jazz-browser-auth-local`): A simple auth provider that stores cryptographic keys on user devices using WebAuthentication/Passkeys. Lets you build Jazz apps completely without a backend, with end-to-end encryption by default.
|
||||
|
||||
**`jazz-storage-indexeddb`**
|
||||
|
||||
Provides local, offline-capable persistence. Included and enabled in `jazz-react` by default.
|
||||
</small>
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
13
examples/chat/.eslintrc.cjs
Normal file
13
examples/chat/.eslintrc.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {},
|
||||
}
|
||||
24
examples/chat/.gitignore
vendored
Normal file
24
examples/chat/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
754
examples/chat/CHANGELOG.md
Normal file
754
examples/chat/CHANGELOG.md
Normal file
@@ -0,0 +1,754 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.80-hotfixes.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.5
|
||||
- jazz-react@0.7.33-hotfixes.5
|
||||
- jazz-tools@0.7.33-hotfixes.5
|
||||
|
||||
## 0.0.80-hotfixes.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.4
|
||||
- jazz-react@0.7.33-hotfixes.4
|
||||
- jazz-tools@0.7.33-hotfixes.4
|
||||
|
||||
## 0.0.80-hotfixes.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.3
|
||||
- jazz-react@0.7.33-hotfixes.3
|
||||
- jazz-tools@0.7.33-hotfixes.3
|
||||
|
||||
## 0.0.80-hotfixes.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.2
|
||||
|
||||
## 0.0.80-hotfixes.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.1
|
||||
|
||||
## 0.0.80-hotfixes.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.0
|
||||
- jazz-react@0.7.33-hotfixes.0
|
||||
- jazz-tools@0.7.33-hotfixes.0
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.32
|
||||
- jazz-react@0.7.32
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.31
|
||||
- jazz-react@0.7.31
|
||||
- jazz-tools@0.7.31
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.30
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.29
|
||||
- jazz-react@0.7.29
|
||||
- jazz-tools@0.7.29
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.28
|
||||
- jazz-react@0.7.28
|
||||
- jazz-tools@0.7.28
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.27
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.26
|
||||
- jazz-react@0.7.26
|
||||
- jazz-tools@0.7.26
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.25
|
||||
- jazz-react@0.7.25
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.24
|
||||
- jazz-react@0.7.24
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.23
|
||||
- jazz-react@0.7.23
|
||||
- jazz-tools@0.7.23
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.22
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.21
|
||||
- jazz-react@0.7.21
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.20
|
||||
- jazz-react@0.7.20
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.19
|
||||
- jazz-react@0.7.19
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.18
|
||||
- jazz-react@0.7.18
|
||||
- jazz-tools@0.7.18
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.17
|
||||
- jazz-react@0.7.17
|
||||
- jazz-tools@0.7.17
|
||||
|
||||
## 0.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.16
|
||||
- jazz-react@0.7.16
|
||||
|
||||
## 0.0.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.15
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.14
|
||||
- jazz-tools@0.7.14
|
||||
- jazz-react@0.7.14
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.13
|
||||
- jazz-react@0.7.13
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.12
|
||||
- jazz-react@0.7.12
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.11
|
||||
- jazz-react@0.7.11
|
||||
- jazz-tools@0.7.11
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.10
|
||||
- jazz-react@0.7.10
|
||||
- jazz-tools@0.7.10
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.9
|
||||
- jazz-react@0.7.9
|
||||
- jazz-tools@0.7.9
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.8
|
||||
- jazz-react@0.7.8
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9fdc91c]
|
||||
- jazz-react@0.7.7
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.6
|
||||
- jazz-react@0.7.6
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.5
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.4
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.3
|
||||
- jazz-react@0.7.3
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.2
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.1
|
||||
- jazz-react@0.7.1
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [96c494f]
|
||||
- Updated dependencies [59c18c3]
|
||||
- Updated dependencies [19f52b7]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [d8fe2b1]
|
||||
- Updated dependencies [19004b4]
|
||||
- Updated dependencies [a78f168]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [52675c9]
|
||||
- Updated dependencies [129e2c1]
|
||||
- Updated dependencies [6d49e9b]
|
||||
- Updated dependencies [1cfa279]
|
||||
- Updated dependencies [704af7d]
|
||||
- Updated dependencies [e97f730]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [460478f]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [e299c3e]
|
||||
- Updated dependencies [ed5643a]
|
||||
- Updated dependencies [bde684f]
|
||||
- Updated dependencies [bf0f8ec]
|
||||
- Updated dependencies [c4151fc]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [01ac646]
|
||||
- Updated dependencies [a5e68a4]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [952982e]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [5fa277c]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [21771c4]
|
||||
- Updated dependencies [77c2b56]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [d2e03ff]
|
||||
- Updated dependencies [354bdcd]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [69ac514]
|
||||
- Updated dependencies [f8a5c46]
|
||||
- Updated dependencies [f0f6f1b]
|
||||
- Updated dependencies [e5eed5b]
|
||||
- Updated dependencies [1a44f87]
|
||||
- Updated dependencies [627d895]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [38d4410]
|
||||
- Updated dependencies [85d2b62]
|
||||
- Updated dependencies [fd86c11]
|
||||
- Updated dependencies [52675c9]
|
||||
- jazz-tools@0.7.0
|
||||
- cojson@0.7.0
|
||||
- jazz-react@0.7.0
|
||||
- hash-slash@0.2.0
|
||||
|
||||
## 0.0.47-alpha.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.42
|
||||
- cojson@0.7.0-alpha.42
|
||||
- jazz-react@0.7.0-alpha.42
|
||||
|
||||
## 0.0.47-alpha.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-tools@0.7.0-alpha.41
|
||||
- jazz-react@0.7.0-alpha.41
|
||||
|
||||
## 0.0.47-alpha.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.40
|
||||
|
||||
## 0.0.47-alpha.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.0-alpha.39
|
||||
- jazz-react@0.7.0-alpha.39
|
||||
- jazz-tools@0.7.0-alpha.39
|
||||
|
||||
## 0.0.47-alpha.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.38
|
||||
- jazz-react@0.7.0-alpha.38
|
||||
- cojson@0.7.0-alpha.38
|
||||
|
||||
## 0.0.47-alpha.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.37
|
||||
- cojson@0.7.0-alpha.37
|
||||
- jazz-tools@0.7.0-alpha.37
|
||||
|
||||
## 0.0.47-alpha.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [1a35307]
|
||||
- cojson@0.7.0-alpha.36
|
||||
- jazz-tools@0.7.0-alpha.36
|
||||
- jazz-react@0.7.0-alpha.36
|
||||
|
||||
## 0.0.47-alpha.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- cojson@0.7.0-alpha.35
|
||||
- jazz-tools@0.7.0-alpha.35
|
||||
- jazz-react@0.7.0-alpha.35
|
||||
|
||||
## 0.0.47-alpha.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.34
|
||||
- jazz-react@0.7.0-alpha.34
|
||||
|
||||
## 0.0.47-alpha.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.33
|
||||
|
||||
## 0.0.47-alpha.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.3
|
||||
- jazz-tools@0.7.0-alpha.32
|
||||
- jazz-react@0.7.0-alpha.32
|
||||
|
||||
## 0.0.47-alpha.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.31
|
||||
- jazz-react@0.7.0-alpha.31
|
||||
|
||||
## 0.0.47-alpha.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.30
|
||||
- jazz-react@0.7.0-alpha.30
|
||||
|
||||
## 0.0.47-alpha.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.29
|
||||
- cojson@0.7.0-alpha.29
|
||||
- jazz-react@0.7.0-alpha.29
|
||||
|
||||
## 0.0.47-alpha.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.28
|
||||
- cojson@0.7.0-alpha.28
|
||||
- jazz-react@0.7.0-alpha.28
|
||||
|
||||
## 0.0.47-alpha.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.27
|
||||
- cojson@0.7.0-alpha.27
|
||||
- jazz-react@0.7.0-alpha.27
|
||||
|
||||
## 0.0.47-alpha.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.26
|
||||
- jazz-react@0.7.0-alpha.26
|
||||
|
||||
## 0.0.47-alpha.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.25
|
||||
- jazz-react@0.7.0-alpha.25
|
||||
|
||||
## 0.0.47-alpha.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.24
|
||||
- cojson@0.7.0-alpha.24
|
||||
- jazz-react@0.7.0-alpha.24
|
||||
|
||||
## 0.0.47-alpha.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.23
|
||||
- jazz-react@0.7.0-alpha.23
|
||||
|
||||
## 0.0.47-alpha.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.22
|
||||
- jazz-react@0.7.0-alpha.22
|
||||
|
||||
## 0.0.47-alpha.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.21
|
||||
- jazz-tools@0.7.0-alpha.21
|
||||
|
||||
## 0.0.47-alpha.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.20
|
||||
- jazz-tools@0.7.0-alpha.20
|
||||
|
||||
## 0.0.47-alpha.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.19
|
||||
- jazz-react@0.7.0-alpha.19
|
||||
|
||||
## 0.0.47-alpha.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.0-alpha.18
|
||||
|
||||
## 0.0.47-alpha.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.17
|
||||
- jazz-react@0.7.0-alpha.17
|
||||
|
||||
## 0.0.47-alpha.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.16
|
||||
- jazz-react@0.7.0-alpha.16
|
||||
|
||||
## 0.0.47-alpha.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.15
|
||||
- jazz-react@0.7.0-alpha.15
|
||||
|
||||
## 0.0.47-alpha.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.14
|
||||
- jazz-react@0.7.0-alpha.14
|
||||
|
||||
## 0.0.47-alpha.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.13
|
||||
- jazz-react@0.7.0-alpha.13
|
||||
|
||||
## 0.0.47-alpha.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.12
|
||||
- jazz-tools@0.7.0-alpha.12
|
||||
|
||||
## 0.0.47-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.11
|
||||
- jazz-tools@0.7.0-alpha.11
|
||||
- cojson@0.7.0-alpha.11
|
||||
|
||||
## 0.0.47-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.10
|
||||
- jazz-tools@0.7.0-alpha.10
|
||||
- cojson@0.7.0-alpha.10
|
||||
|
||||
## 0.0.47-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.9
|
||||
- jazz-tools@0.7.0-alpha.9
|
||||
|
||||
## 0.0.47-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.8
|
||||
- jazz-tools@0.7.0-alpha.8
|
||||
|
||||
## 0.0.47-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.7
|
||||
- jazz-tools@0.7.0-alpha.7
|
||||
- cojson@0.7.0-alpha.7
|
||||
|
||||
## 0.0.47-alpha.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.6
|
||||
- jazz-tools@0.7.0-alpha.6
|
||||
|
||||
## 0.0.47-alpha.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.5
|
||||
- jazz-tools@0.7.0-alpha.5
|
||||
- cojson@0.7.0-alpha.5
|
||||
|
||||
## 0.0.47-alpha.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.4
|
||||
- jazz-react@0.7.0-alpha.4
|
||||
|
||||
## 0.0.47-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.3
|
||||
- jazz-react@0.7.0-alpha.3
|
||||
|
||||
## 0.0.47-alpha.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.2
|
||||
- jazz-react@0.7.0-alpha.2
|
||||
- jazz-tools@0.7.0-alpha.2
|
||||
|
||||
## 0.0.47-alpha.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.1
|
||||
- jazz-react@0.7.0-alpha.1
|
||||
- jazz-tools@0.7.0-alpha.1
|
||||
- cojson@0.7.0-alpha.1
|
||||
|
||||
## 0.0.47-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.0
|
||||
- jazz-react@0.7.0-alpha.0
|
||||
- jazz-tools@0.7.0-alpha.0
|
||||
- cojson@0.7.0-alpha.0
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.5.0
|
||||
- jazz-react-auth-local@0.4.16
|
||||
4
examples/chat/Dockerfile
Normal file
4
examples/chat/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM caddy:2.7.3-alpine
|
||||
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||
|
||||
COPY ./dist /usr/share/caddy/
|
||||
42
examples/chat/README.md
Normal file
42
examples/chat/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Jazz Chat Example
|
||||
|
||||
Live version: [https://chat.jazz.tools](https://chat.jazz.tools)
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by checking out `jazz`
|
||||
|
||||
```bash
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
cd jazz/examples/chat
|
||||
pnpm pack --pack-destination /tmp
|
||||
mkdir -p ~/jazz-examples/chat # or any other directory
|
||||
tar -xf /tmp/jazz-example-chat-* --strip-components 1 -C ~/jazz-examples/chat
|
||||
cd ~/jazz-examples/chat
|
||||
```
|
||||
|
||||
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Questions / problems / feedback
|
||||
|
||||
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||
|
||||
## Configuration: sync server
|
||||
|
||||
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
|
||||
14
examples/chat/index.html
Normal file
14
examples/chat/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/jazz-logo.png" />
|
||||
<link rel="stylesheet" href="/src/index.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz Chat Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
56
examples/chat/job-template.nomad
Normal file
56
examples/chat/job-template.nomad
Normal file
@@ -0,0 +1,56 @@
|
||||
job "chat$BRANCH_SUFFIX" {
|
||||
region = "global"
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
to = 80
|
||||
}
|
||||
}
|
||||
|
||||
constraint {
|
||||
attribute = "${node.class}"
|
||||
operator = "="
|
||||
value = "mesh"
|
||||
}
|
||||
|
||||
spread {
|
||||
attribute = "${node.datacenter}"
|
||||
weight = 100
|
||||
}
|
||||
|
||||
constraint {
|
||||
distinct_hosts = true
|
||||
}
|
||||
|
||||
task "server" {
|
||||
driver = "docker"
|
||||
|
||||
config {
|
||||
image = "$DOCKER_TAG"
|
||||
ports = ["http"]
|
||||
|
||||
auth = {
|
||||
username = "$DOCKER_USER"
|
||||
password = "$DOCKER_PASSWORD"
|
||||
}
|
||||
}
|
||||
|
||||
service {
|
||||
tags = ["public"]
|
||||
name = "chat$BRANCH_SUFFIX"
|
||||
port = "http"
|
||||
provider = "consul"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 50 # MHz
|
||||
memory = 50 # MB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# deploy bump 4
|
||||
54
examples/chat/package.json
Normal file
54
examples/chat/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.80-hotfixes.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"format": "echo 'chat example is codegolfed'",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --fix",
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"hash-slash": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"cojson": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-use": "^17.4.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uniqolor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^5.0.10"
|
||||
}
|
||||
}
|
||||
6
examples/chat/postcss.config.js
Normal file
6
examples/chat/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
examples/chat/public/jazz-logo.png
Normal file
BIN
examples/chat/public/jazz-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
43
examples/chat/src/app.tsx
Normal file
43
examples/chat/src/app.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { CoMap, CoList, co, Group, ID } from "jazz-tools";
|
||||
import { createJazzReactContext, DemoAuth } from "jazz-react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { useIframeHashRouter } from "hash-slash";
|
||||
import { ChatScreen } from "./chatScreen.tsx";
|
||||
import { StrictMode } from "react";
|
||||
|
||||
export class Message extends CoMap {
|
||||
text = co.string;
|
||||
}
|
||||
|
||||
export class Chat extends CoList.Of(co.ref(Message)) {}
|
||||
|
||||
const Jazz = createJazzReactContext({
|
||||
auth: DemoAuth({ appName: "Jazz Chat" }),
|
||||
peer: `wss://mesh.jazz.tools/?key=you@example.com`
|
||||
});
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
location.hash = "/chat/" + chat.id;
|
||||
};
|
||||
|
||||
return <div className="flex flex-col items-center justify-between w-screen h-screen p-2 dark:bg-black dark:text-white">
|
||||
<div className="rounded mb-5 px-2 py-1 text-sm self-end">
|
||||
{me.profile?.name} · <button onClick={logOut}>Log Out</button>
|
||||
</div>
|
||||
{useIframeHashRouter().route({
|
||||
'/': () => createChat() as never,
|
||||
'/chat/:id': (id) => <ChatScreen chatID={id as ID<Chat>} />
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!)
|
||||
.render(<StrictMode><Jazz.Provider><App/></Jazz.Provider></StrictMode>);
|
||||
42
examples/chat/src/chatScreen.tsx
Normal file
42
examples/chat/src/chatScreen.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ID } from 'jazz-tools';
|
||||
import { Chat, Message, useCoState } from './app.tsx';
|
||||
|
||||
export function ChatScreen(props: { chatID: ID<Chat> }) {
|
||||
const chat = useCoState(Chat, props.chatID, [{}]);
|
||||
|
||||
return chat ? <div className='w-full max-w-xl h-full flex flex-col items-stretch'>
|
||||
{chat.length > 0
|
||||
? chat.map((msg) => <ChatBubble msg={msg} key={msg.id} />)
|
||||
: <div className='m-auto text-sm'>(Empty chat)</div>}
|
||||
<ChatInput onSubmit={(text) => {
|
||||
chat.push(
|
||||
Message.create({ text }, { owner: chat._owner })
|
||||
);
|
||||
}} />
|
||||
</div> : <div>Loading...</div>;
|
||||
}
|
||||
|
||||
function ChatBubble(props: { msg: Message }) {
|
||||
const lastEdit = props.msg._edits.text;
|
||||
const align = lastEdit.by?.isMe ? 'items-end' : 'items-start';
|
||||
|
||||
return <div className={`${align} flex flex-col`}>
|
||||
<div className='rounded-xl bg-stone-100 dark:bg-stone-700 dark:text-white py-2 px-4 mt-2 min-w-[5rem]'>
|
||||
{ props.msg.text }
|
||||
</div>
|
||||
<div className='text-xs text-neutral-500 ml-2'>
|
||||
{ lastEdit.by?.profile?.name }{' '}
|
||||
{ lastEdit.madeAt?.toLocaleTimeString() }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function ChatInput(props: { onSubmit: (text: string) => void }) {
|
||||
return <input className='rounded p-2 border mt-auto dark:bg-black dark:text-white border-stone-300 dark:border-stone-700'
|
||||
placeholder='Type a message and press Enter'
|
||||
onKeyDown={({ key, currentTarget: input }) => {
|
||||
if (key !== 'Enter' || !input.value) return;
|
||||
props.onSubmit(input.value);
|
||||
input.value = '';
|
||||
}} />;
|
||||
}
|
||||
78
examples/chat/src/index.css
Normal file
78
examples/chat/src/index.css
Normal file
@@ -0,0 +1,78 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 20 14.3% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--primary: 24 9.8% 10%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--secondary: 60 4.8% 95.9%;
|
||||
--secondary-foreground: 24 9.8% 10%;
|
||||
|
||||
--muted: 60 4.8% 95.9%;
|
||||
--muted-foreground: 25 5.3% 44.7%;
|
||||
|
||||
--accent: 60 4.8% 95.9%;
|
||||
--accent-foreground: 24 9.8% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 20 5.9% 90%;
|
||||
--input: 20 5.9% 90%;
|
||||
--ring: 20 14.3% 4.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
|
||||
--card: 20 14.3% 4.1%;
|
||||
--card-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--popover: 20 14.3% 4.1%;
|
||||
--popover-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--primary: 60 9.1% 97.8%;
|
||||
--primary-foreground: 24 9.8% 10%;
|
||||
|
||||
--secondary: 12 6.5% 15.1%;
|
||||
--secondary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--muted: 12 6.5% 15.1%;
|
||||
--muted-foreground: 24 5.4% 63.9%;
|
||||
|
||||
--accent: 12 6.5% 15.1%;
|
||||
--accent-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 12 6.5% 15.1%;
|
||||
--input: 12 6.5% 15.1%;
|
||||
--ring: 24 5.7% 82.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
1
examples/chat/src/vite-env.d.ts
vendored
Normal file
1
examples/chat/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
75
examples/chat/tailwind.config.js
Normal file
75
examples/chat/tailwind.config.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
29
examples/chat/tsconfig.json
Normal file
29
examples/chat/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
examples/chat/tsconfig.node.json
Normal file
10
examples/chat/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
16
examples/chat/vite.config.ts
Normal file
16
examples/chat/vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import path from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false
|
||||
}
|
||||
})
|
||||
18
examples/inspector/.eslintrc.cjs
Normal file
18
examples/inspector/.eslintrc.cjs
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
24
examples/inspector/.gitignore
vendored
Normal file
24
examples/inspector/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
142
examples/inspector/CHANGELOG.md
Normal file
142
examples/inspector/CHANGELOG.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.58-hotfixes.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.5
|
||||
- cojson-transport-ws@0.7.33-hotfixes.5
|
||||
|
||||
## 0.0.58-hotfixes.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.4
|
||||
- cojson-transport-ws@0.7.33-hotfixes.4
|
||||
|
||||
## 0.0.58-hotfixes.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.33-hotfixes.3
|
||||
- cojson@0.7.33-hotfixes.3
|
||||
|
||||
## 0.0.58-hotfixes.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.33-hotfixes.2
|
||||
|
||||
## 0.0.58-hotfixes.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.33-hotfixes.1
|
||||
|
||||
## 0.0.58-hotfixes.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.33-hotfixes.0
|
||||
- cojson-transport-ws@0.7.33-hotfixes.0
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.31
|
||||
- cojson@0.7.31
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.30
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.29
|
||||
- cojson-transport-ws@0.7.29
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.28
|
||||
- cojson-transport-ws@0.7.28
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.27
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.26
|
||||
- cojson-transport-ws@0.7.26
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.23
|
||||
- cojson-transport-ws@0.7.23
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson-transport-ws@0.7.22
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.18
|
||||
- cojson-transport-ws@0.7.18
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.17
|
||||
- cojson-transport-ws@0.7.17
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.6.7
|
||||
- jazz-react@0.5.5
|
||||
- jazz-react-auth-local@0.4.18
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.5.0
|
||||
- jazz-react-auth-local@0.4.16
|
||||
4
examples/inspector/Dockerfile
Normal file
4
examples/inspector/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM caddy:2.7.3-alpine
|
||||
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||
|
||||
COPY ./dist /usr/share/caddy/
|
||||
42
examples/inspector/README.md
Normal file
42
examples/inspector/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Jazz Chat Example
|
||||
|
||||
Live version: https://example-chat.jazz.tools
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by checking out `jazz`
|
||||
```bash
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
cd jazz/examples/chat
|
||||
pnpm pack --pack-destination /tmp
|
||||
mkdir -p ~/jazz-examples/chat # or any other directory
|
||||
tar -xf /tmp/jazz-example-chat-* --strip-components 1 -C ~/jazz-examples/chat
|
||||
cd ~/jazz-examples/chat
|
||||
```
|
||||
|
||||
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Questions / problems / feedback
|
||||
|
||||
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||
|
||||
|
||||
## Configuration: sync server
|
||||
|
||||
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<WithJazz>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
|
||||
17
examples/inspector/index.html
Normal file
17
examples/inspector/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/jazz-logo.png" />
|
||||
<link rel="stylesheet" href="/src/index.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz Inspector</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/app.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
56
examples/inspector/job-template.nomad
Normal file
56
examples/inspector/job-template.nomad
Normal file
@@ -0,0 +1,56 @@
|
||||
job "inspector$BRANCH_SUFFIX" {
|
||||
region = "global"
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
to = 80
|
||||
}
|
||||
}
|
||||
|
||||
constraint {
|
||||
attribute = "${node.class}"
|
||||
operator = "="
|
||||
value = "mesh"
|
||||
}
|
||||
|
||||
spread {
|
||||
attribute = "${node.datacenter}"
|
||||
weight = 100
|
||||
}
|
||||
|
||||
constraint {
|
||||
distinct_hosts = true
|
||||
}
|
||||
|
||||
task "server" {
|
||||
driver = "docker"
|
||||
|
||||
config {
|
||||
image = "$DOCKER_TAG"
|
||||
ports = ["http"]
|
||||
|
||||
auth = {
|
||||
username = "$DOCKER_USER"
|
||||
password = "$DOCKER_PASSWORD"
|
||||
}
|
||||
}
|
||||
|
||||
service {
|
||||
tags = ["public"]
|
||||
name = "inspector$BRANCH_SUFFIX"
|
||||
port = "http"
|
||||
provider = "consul"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 50 # MHz
|
||||
memory = 50 # MB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# deploy bump 4
|
||||
48
examples/inspector/package.json
Normal file
48
examples/inspector/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "jazz-inspector",
|
||||
"private": true,
|
||||
"version": "0.0.58-hotfixes.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"hash-slash": "workspace:*",
|
||||
"cojson": "workspace:*",
|
||||
"cojson-transport-ws": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-use": "^17.4.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uniqolor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^5.0.10"
|
||||
}
|
||||
}
|
||||
6
examples/inspector/postcss.config.js
Normal file
6
examples/inspector/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
examples/inspector/public/jazz-logo.png
Normal file
BIN
examples/inspector/public/jazz-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
4
examples/inspector/src/app.tsx
Normal file
4
examples/inspector/src/app.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./viewer/new-app";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||
92
examples/inspector/src/index.css
Normal file
92
examples/inspector/src/index.css
Normal file
@@ -0,0 +1,92 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 20 14.3% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--primary: 24 9.8% 10%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--secondary: 60 4.8% 95.9%;
|
||||
--secondary-foreground: 24 9.8% 10%;
|
||||
|
||||
--muted: 60 4.8% 95.9%;
|
||||
--muted-foreground: 25 5.3% 44.7%;
|
||||
|
||||
--accent: 60 4.8% 95.9%;
|
||||
--accent-foreground: 24 9.8% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 20 5.9% 90%;
|
||||
--input: 20 5.9% 90%;
|
||||
--ring: 20 14.3% 4.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
|
||||
--card: 20 14.3% 4.1%;
|
||||
--card-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--popover: 20 14.3% 4.1%;
|
||||
--popover-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--primary: 60 9.1% 97.8%;
|
||||
--primary-foreground: 24 9.8% 10%;
|
||||
|
||||
--secondary: 12 6.5% 15.1%;
|
||||
--secondary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--muted: 12 6.5% 15.1%;
|
||||
--muted-foreground: 24 5.4% 63.9%;
|
||||
|
||||
--accent: 12 6.5% 15.1%;
|
||||
--accent-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 12 6.5% 15.1%;
|
||||
--input: 12 6.5% 15.1%;
|
||||
--ring: 24 5.7% 82.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-in {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateZ(400px) translateY(30px) scale(1.05);
|
||||
opacity: 0.4;
|
||||
}
|
||||
to {
|
||||
transform: translateZ(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
18
examples/inspector/src/link-icon.tsx
Normal file
18
examples/inspector/src/link-icon.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
export function LinkIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-3 h-3"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
42
examples/inspector/src/viewer/breadcrumbs.tsx
Normal file
42
examples/inspector/src/viewer/breadcrumbs.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { PageInfo } from "./types";
|
||||
|
||||
interface BreadcrumbsProps {
|
||||
path: PageInfo[];
|
||||
onBreadcrumbClick: (index: number) => void;
|
||||
}
|
||||
|
||||
export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
||||
path,
|
||||
onBreadcrumbClick,
|
||||
}) => {
|
||||
return (
|
||||
<div className="z-20 relative bg-indigo-400/10 backdrop-blur-sm rounded-lg inline-flex px-2 py-1 whitespace-pre transition-all items-center space-x-1 min-h-10">
|
||||
<button
|
||||
onClick={() => onBreadcrumbClick(-1)}
|
||||
className="flex items-center justify-center p-1 rounded-sm hover:bg-indigo-500/10 transition-colors"
|
||||
aria-label="Go to home"
|
||||
>
|
||||
<img src="jazz-logo.png" alt="Jazz Logo" className="size-5" />
|
||||
</button>
|
||||
{path.map((page, index) => {
|
||||
return (
|
||||
<span
|
||||
key={index}
|
||||
className="inline-block first:pl-1 last:pr-1"
|
||||
>
|
||||
{index === 0 ? null : (
|
||||
<span className="text-indigo-500/30">{" / "}</span>
|
||||
)}
|
||||
<button
|
||||
onClick={() => onBreadcrumbClick(index)}
|
||||
className="text-indigo-700 hover:underline"
|
||||
>
|
||||
{index === 0 ? page.name || "Root" : page.name}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
353
examples/inspector/src/viewer/co-stream-view.tsx
Normal file
353
examples/inspector/src/viewer/co-stream-view.tsx
Normal file
@@ -0,0 +1,353 @@
|
||||
import {
|
||||
CoID,
|
||||
LocalNode,
|
||||
RawBinaryCoStream,
|
||||
RawCoStream,
|
||||
RawCoValue,
|
||||
} from "cojson";
|
||||
import { JsonObject, JsonValue } from "cojson/src/jsonValue";
|
||||
import { PageInfo } from "./types";
|
||||
import { base64URLtoBytes } from "cojson/src/base64url";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowDownToLine } from "lucide-react";
|
||||
import {
|
||||
BinaryStreamItem,
|
||||
BinaryStreamStart,
|
||||
CoStreamItem,
|
||||
} from "cojson/src/coValues/coStream";
|
||||
import { AccountOrGroupPreview } from "./value-renderer";
|
||||
|
||||
// typeguard for BinaryStreamStart
|
||||
function isBinaryStreamStart(item: unknown): item is BinaryStreamStart {
|
||||
return (
|
||||
typeof item === "object" &&
|
||||
item !== null &&
|
||||
"type" in item &&
|
||||
item.type === "start"
|
||||
);
|
||||
}
|
||||
|
||||
function detectCoStreamType(value: RawCoStream | RawBinaryCoStream) {
|
||||
const firstKey = Object.keys(value.items)[0];
|
||||
if (!firstKey)
|
||||
return {
|
||||
type: "unknown",
|
||||
};
|
||||
|
||||
const items = value.items[firstKey as never]?.map((v) => v.value);
|
||||
|
||||
if (!items)
|
||||
return {
|
||||
type: "unknown",
|
||||
};
|
||||
const firstItem = items[0];
|
||||
if (!firstItem)
|
||||
return {
|
||||
type: "unknown",
|
||||
};
|
||||
// This is a binary stream
|
||||
if (isBinaryStreamStart(firstItem)) {
|
||||
return {
|
||||
type: "binary",
|
||||
items: items as BinaryStreamItem[],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: "coStream",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getBlobFromCoStream({
|
||||
items,
|
||||
onlyFirstChunk = false,
|
||||
}: {
|
||||
items: BinaryStreamItem[];
|
||||
onlyFirstChunk?: boolean;
|
||||
}) {
|
||||
if (onlyFirstChunk && items.length > 1) {
|
||||
items = items.slice(0, 2);
|
||||
}
|
||||
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
const binary_U_prefixLength = 8;
|
||||
|
||||
let lastProgressUpdate = Date.now();
|
||||
|
||||
for (const item of items.slice(1)) {
|
||||
if (item.type === "end") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.type !== "chunk") {
|
||||
console.error("Invalid binary stream chunk", item);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const chunk = base64URLtoBytes(item.chunk.slice(binary_U_prefixLength));
|
||||
// totalLength += chunk.length;
|
||||
chunks.push(chunk);
|
||||
|
||||
if (Date.now() - lastProgressUpdate > 100) {
|
||||
lastProgressUpdate = Date.now();
|
||||
}
|
||||
}
|
||||
const defaultMime = "mimeType" in items[0] ? items[0].mimeType : null;
|
||||
|
||||
const blob = new Blob(chunks, defaultMime ? { type: defaultMime } : {});
|
||||
|
||||
const mimeType =
|
||||
defaultMime === "" ? await detectPDFMimeType(blob) : defaultMime;
|
||||
|
||||
return {
|
||||
blob,
|
||||
mimeType: mimeType as string,
|
||||
unfinishedChunks: items.length > 1,
|
||||
totalSize:
|
||||
"totalSizeBytes" in items[0]
|
||||
? (items[0].totalSizeBytes as number)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const detectPDFMimeType = async (blob: Blob): Promise<string> => {
|
||||
const arrayBuffer = await blob.slice(0, 4).arrayBuffer();
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
const header = uint8Array.reduce(
|
||||
(acc, byte) => acc + String.fromCharCode(byte),
|
||||
"",
|
||||
);
|
||||
|
||||
if (header === "%PDF") {
|
||||
return "application/pdf";
|
||||
}
|
||||
return "application/octet-stream";
|
||||
};
|
||||
|
||||
const BinaryDownloadButton = ({
|
||||
pdfBlob,
|
||||
fileName = "document",
|
||||
label,
|
||||
mimeType,
|
||||
}: {
|
||||
pdfBlob: Blob;
|
||||
mimeType?: string;
|
||||
fileName?: string;
|
||||
label: string;
|
||||
}) => {
|
||||
const downloadFile = () => {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([pdfBlob], mimeType ? { type: mimeType } : {}),
|
||||
);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download =
|
||||
mimeType === "application/pdf" ? `${fileName}.pdf` : fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className="flex items-center gap-2 px-2 py-1 text-gray-900 border border-gray-900/10 bg-clip-border shadow-sm transition-colors rounded bg-gray-50 text-sm"
|
||||
onClick={downloadFile}
|
||||
>
|
||||
<ArrowDownToLine size={16} />
|
||||
{label}
|
||||
{/* Download {mimeType === "application/pdf" ? "PDF" : "File"} */}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const LabelContentPair = ({
|
||||
label,
|
||||
content,
|
||||
}: {
|
||||
label: string;
|
||||
content: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5 ">
|
||||
<span className="uppercase text-xs font-medium text-gray-600 tracking-wide">
|
||||
{label}
|
||||
</span>
|
||||
<span>{content}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function RenderCoBinaryStream({
|
||||
value,
|
||||
items,
|
||||
}: {
|
||||
items: BinaryStreamItem[];
|
||||
value: RawBinaryCoStream;
|
||||
}) {
|
||||
const [file, setFile] = useState<
|
||||
| {
|
||||
blob: Blob;
|
||||
mimeType: string;
|
||||
unfinishedChunks: boolean;
|
||||
totalSize: number | undefined;
|
||||
}
|
||||
| undefined
|
||||
| null
|
||||
>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// load only the first chunk to get the mime type and size
|
||||
getBlobFromCoStream({
|
||||
items,
|
||||
onlyFirstChunk: true,
|
||||
})
|
||||
.then((v) => {
|
||||
if (v) {
|
||||
setFile(v);
|
||||
if (v.mimeType.includes("image")) {
|
||||
// If it's an image, load the full blob
|
||||
getBlobFromCoStream({
|
||||
items,
|
||||
}).then((s) => {
|
||||
if (s) setFile(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [items]);
|
||||
|
||||
if (!isLoading && !file) return <div>No blob</div>;
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (!file) return <div>No blob</div>;
|
||||
|
||||
const { blob, mimeType } = file;
|
||||
|
||||
const sizeInKB = (file.totalSize || 0) / 1024;
|
||||
|
||||
return (
|
||||
<div className="space-y-8 mt-4">
|
||||
<div className="grid grid-cols-3 gap-2 max-w-3xl">
|
||||
<LabelContentPair
|
||||
label="Mime Type"
|
||||
content={
|
||||
<span className="font-mono bg-gray-100 rounded px-2 py-1 text-sm">
|
||||
{mimeType || "No mime type"}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<LabelContentPair
|
||||
label="Size"
|
||||
content={<span>{sizeInKB.toFixed(2)} KB</span>}
|
||||
/>
|
||||
<LabelContentPair
|
||||
label="Download"
|
||||
content={
|
||||
<BinaryDownloadButton
|
||||
fileName={value.id.toString()}
|
||||
pdfBlob={blob}
|
||||
mimeType={mimeType}
|
||||
label={
|
||||
mimeType === "application/pdf"
|
||||
? "Download PDF"
|
||||
: "Download File"
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{mimeType === "image/png" || mimeType === "image/jpeg" ? (
|
||||
<LabelContentPair
|
||||
label="Preview"
|
||||
content={
|
||||
<div className="bg-gray-50 p-3 rounded-sm">
|
||||
<RenderBlobImage blob={blob} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderCoStream({
|
||||
value,
|
||||
node,
|
||||
}: {
|
||||
value: RawCoStream;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const streamPerUser = Object.keys(value.items);
|
||||
const userCoIds = streamPerUser.map(
|
||||
(stream) => stream.split("_session")[0],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{userCoIds.map((id, idx) => (
|
||||
<div
|
||||
className="bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
|
||||
key={id}
|
||||
>
|
||||
<AccountOrGroupPreview
|
||||
coId={id as CoID<RawCoValue>}
|
||||
node={node}
|
||||
/>
|
||||
{/* @ts-expect-error - TODO: fix types */}
|
||||
{value.items[streamPerUser[idx]]?.map(
|
||||
(item: CoStreamItem<JsonValue>) => (
|
||||
<div>
|
||||
{new Date(item.madeAt).toLocaleString()}{" "}
|
||||
{JSON.stringify(item.value)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CoStreamView({
|
||||
value,
|
||||
node,
|
||||
}: {
|
||||
data: JsonObject;
|
||||
onNavigate: (pages: PageInfo[]) => void;
|
||||
node: LocalNode;
|
||||
value: RawCoStream;
|
||||
}) {
|
||||
// if (!value) return <div>No value</div>;
|
||||
|
||||
const streamType = detectCoStreamType(value);
|
||||
|
||||
if (streamType.type === "binary") {
|
||||
if (streamType.items === undefined) {
|
||||
return <div>No binary stream</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<RenderCoBinaryStream
|
||||
value={value as RawBinaryCoStream}
|
||||
items={streamType.items}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (streamType.type === "coStream") {
|
||||
return <RenderCoStream value={value} node={node} />;
|
||||
}
|
||||
|
||||
if (streamType.type === "unknown") return <div>Unknown stream type</div>;
|
||||
|
||||
return <div>Unknown stream type</div>;
|
||||
}
|
||||
|
||||
function RenderBlobImage({ blob }: { blob: Blob }) {
|
||||
const urlCreator = window.URL || window.webkitURL;
|
||||
return <img src={urlCreator.createObjectURL(blob)} />;
|
||||
}
|
||||
73
examples/inspector/src/viewer/grid-view.tsx
Normal file
73
examples/inspector/src/viewer/grid-view.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
||||
import { JsonObject } from "cojson/src/jsonValue";
|
||||
import { CoMapPreview, ValueRenderer } from "./value-renderer";
|
||||
import clsx from "clsx";
|
||||
import { PageInfo, isCoId } from "./types";
|
||||
import { ResolveIcon } from "./type-icon";
|
||||
|
||||
export function GridView({
|
||||
data,
|
||||
onNavigate,
|
||||
node,
|
||||
}: {
|
||||
data: JsonObject;
|
||||
onNavigate: (pages: PageInfo[]) => void;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const entries = Object.entries(data);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-2">
|
||||
{entries.map(([key, child], childIndex) => (
|
||||
<div
|
||||
key={childIndex}
|
||||
className={clsx(
|
||||
"bg-gray-100 p-3 rounded-lg transition-colors overflow-hidden",
|
||||
isCoId(child)
|
||||
? "bg-white border hover:bg-gray-100/5 cursor-pointer shadow-sm"
|
||||
: "bg-gray-50",
|
||||
)}
|
||||
onClick={() =>
|
||||
isCoId(child) &&
|
||||
onNavigate([
|
||||
{ coId: child as CoID<RawCoValue>, name: key },
|
||||
])
|
||||
}
|
||||
>
|
||||
<h3 className="truncate">
|
||||
{isCoId(child) ? (
|
||||
<span className="font-medium flex justify-between">
|
||||
{key}
|
||||
|
||||
<div className="px-2 py-1 text-xs bg-gray-100 rounded">
|
||||
<ResolveIcon
|
||||
coId={child as CoID<RawCoValue>}
|
||||
node={node}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
) : (
|
||||
<span>{key}</span>
|
||||
)}
|
||||
</h3>
|
||||
<div className="mt-2 text-sm">
|
||||
{isCoId(child) ? (
|
||||
<CoMapPreview
|
||||
coId={child as CoID<RawCoValue>}
|
||||
node={node}
|
||||
/>
|
||||
) : (
|
||||
<ValueRenderer
|
||||
json={child}
|
||||
onCoIDClick={(coId) => {
|
||||
onNavigate([{ coId, name: key }]);
|
||||
}}
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
examples/inspector/src/viewer/index.tsx
Normal file
27
examples/inspector/src/viewer/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { LocalNode } from "cojson";
|
||||
import { Breadcrumbs } from "./breadcrumbs";
|
||||
import { usePagePath } from "./use-page-path";
|
||||
import { PageInfo } from "./types";
|
||||
import { PageStack } from "./page-stack";
|
||||
|
||||
export default function CoJsonViewer({
|
||||
defaultPath,
|
||||
node,
|
||||
}: {
|
||||
defaultPath?: PageInfo[];
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const { path, addPages, goToIndex, goBack } = usePagePath(defaultPath);
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden">
|
||||
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
|
||||
<PageStack
|
||||
path={path}
|
||||
node={node}
|
||||
goBack={goBack}
|
||||
addPages={addPages}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
310
examples/inspector/src/viewer/new-app.tsx
Normal file
310
examples/inspector/src/viewer/new-app.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
LocalNode,
|
||||
CoID,
|
||||
RawCoValue,
|
||||
RawAccount,
|
||||
AgentSecret,
|
||||
AccountID,
|
||||
cojsonInternals,
|
||||
WasmCrypto,
|
||||
} from "cojson";
|
||||
import { createWebSocketPeer } from "cojson-transport-ws";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Breadcrumbs } from "./breadcrumbs";
|
||||
import { usePagePath } from "./use-page-path";
|
||||
import { PageStack } from "./page-stack";
|
||||
import { resolveCoValue, useResolvedCoValue } from "./use-resolve-covalue";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Account {
|
||||
id: CoID<RawAccount>;
|
||||
secret: AgentSecret;
|
||||
}
|
||||
|
||||
export default function CoJsonViewerApp() {
|
||||
const [accounts, setAccounts] = useState<Account[]>(() => {
|
||||
const storedAccounts = localStorage.getItem("inspectorAccounts");
|
||||
return storedAccounts ? JSON.parse(storedAccounts) : [];
|
||||
});
|
||||
const [currentAccount, setCurrentAccount] = useState<Account | null>(() => {
|
||||
const lastSelectedId = localStorage.getItem("lastSelectedAccountId");
|
||||
if (lastSelectedId) {
|
||||
const lastAccount = accounts.find(
|
||||
(account) => account.id === lastSelectedId,
|
||||
);
|
||||
return lastAccount || null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [localNode, setLocalNode] = useState<LocalNode | null>(null);
|
||||
const [coValueId, setCoValueId] = useState<CoID<RawCoValue> | "">("");
|
||||
const { path, addPages, goToIndex, goBack, setPage } = usePagePath();
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("inspectorAccounts", JSON.stringify(accounts));
|
||||
}, [accounts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentAccount) {
|
||||
localStorage.setItem("lastSelectedAccountId", currentAccount.id);
|
||||
} else {
|
||||
localStorage.removeItem("lastSelectedAccountId");
|
||||
}
|
||||
}, [currentAccount]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAccount) {
|
||||
setLocalNode(null);
|
||||
goToIndex(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
WasmCrypto.create().then(async (crypto) => {
|
||||
const wsPeer = createWebSocketPeer({
|
||||
id: "mesh",
|
||||
websocket: new WebSocket("wss://mesh.jazz.tools"),
|
||||
role: "server",
|
||||
});
|
||||
const node = await LocalNode.withLoadedAccount({
|
||||
accountID: currentAccount.id,
|
||||
accountSecret: currentAccount.secret,
|
||||
sessionID: cojsonInternals.newRandomSessionID(
|
||||
currentAccount.id,
|
||||
),
|
||||
peersToLoadFrom: [wsPeer],
|
||||
crypto,
|
||||
migration: async () => {
|
||||
console.log("Not running any migration in inspector");
|
||||
},
|
||||
});
|
||||
setLocalNode(node);
|
||||
});
|
||||
}, [currentAccount, goToIndex]);
|
||||
|
||||
const addAccount = (id: AccountID, secret: AgentSecret) => {
|
||||
const newAccount = { id, secret };
|
||||
setAccounts([...accounts, newAccount]);
|
||||
setCurrentAccount(newAccount);
|
||||
};
|
||||
|
||||
const deleteCurrentAccount = () => {
|
||||
if (currentAccount) {
|
||||
const updatedAccounts = accounts.filter(
|
||||
(account) => account.id !== currentAccount.id,
|
||||
);
|
||||
setAccounts(updatedAccounts);
|
||||
setCurrentAccount(
|
||||
updatedAccounts.length > 0 ? updatedAccounts[0] : null,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCoValueIdSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (coValueId) {
|
||||
setPage(coValueId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden flex flex-col">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Breadcrumbs path={path} onBreadcrumbClick={goToIndex} />
|
||||
<AccountSwitcher
|
||||
accounts={accounts}
|
||||
currentAccount={currentAccount}
|
||||
setCurrentAccount={setCurrentAccount}
|
||||
deleteCurrentAccount={deleteCurrentAccount}
|
||||
localNode={localNode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PageStack
|
||||
path={path}
|
||||
node={localNode}
|
||||
goBack={goBack}
|
||||
addPages={addPages}
|
||||
>
|
||||
{!currentAccount ? (
|
||||
<AddAccountForm addAccount={addAccount} />
|
||||
) : (
|
||||
<form
|
||||
onSubmit={handleCoValueIdSubmit}
|
||||
aria-hidden={path.length !== 0}
|
||||
className={clsx(
|
||||
"flex flex-col justify-center items-center gap-2 h-full w-full mb-20 ",
|
||||
"transition-all duration-150",
|
||||
path.length > 0
|
||||
? "opacity-0 -translate-y-2 scale-95"
|
||||
: "opacity-100",
|
||||
)}
|
||||
>
|
||||
<fieldset className="flex flex-col gap-2 text-sm">
|
||||
<h2 className="text-3xl font-medium text-gray-950 text-center mb-4">
|
||||
Jazz CoValue Inspector
|
||||
</h2>
|
||||
<input
|
||||
className="border p-4 rounded-lg min-w-[21rem] font-mono"
|
||||
placeholder="co_z1234567890abcdef123456789"
|
||||
value={coValueId}
|
||||
onChange={(e) =>
|
||||
setCoValueId(
|
||||
e.target.value as CoID<RawCoValue>,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-indigo-500 hover:bg-indigo-500/80 text-white px-4 py-2 rounded-md"
|
||||
>
|
||||
Inspect
|
||||
</button>
|
||||
<hr />
|
||||
<button
|
||||
type="button"
|
||||
className="border inline-block px-2 py-1.5 text-black rounded"
|
||||
onClick={() => {
|
||||
setCoValueId(currentAccount.id);
|
||||
setPage(currentAccount.id);
|
||||
}}
|
||||
>
|
||||
Inspect My Account
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
)}
|
||||
</PageStack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountSwitcher({
|
||||
accounts,
|
||||
currentAccount,
|
||||
setCurrentAccount,
|
||||
deleteCurrentAccount,
|
||||
localNode,
|
||||
}: {
|
||||
accounts: Account[];
|
||||
currentAccount: Account | null;
|
||||
setCurrentAccount: (account: Account | null) => void;
|
||||
deleteCurrentAccount: () => void;
|
||||
localNode: LocalNode | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative flex items-center gap-1">
|
||||
<select
|
||||
value={currentAccount?.id || "add-account"}
|
||||
onChange={(e) => {
|
||||
if (e.target.value === "add-account") {
|
||||
setCurrentAccount(null);
|
||||
} else {
|
||||
const account = accounts.find(
|
||||
(a) => a.id === e.target.value,
|
||||
);
|
||||
setCurrentAccount(account || null);
|
||||
}
|
||||
}}
|
||||
className="p-2 px-4 bg-gray-100/50 border border-indigo-500/10 backdrop-blur-sm rounded-md text-indigo-700 appearance-none"
|
||||
>
|
||||
{accounts.map((account) => (
|
||||
<option key={account.id} value={account.id}>
|
||||
{localNode ? (
|
||||
<AccountNameDisplay
|
||||
accountId={account.id}
|
||||
node={localNode}
|
||||
/>
|
||||
) : (
|
||||
account.id
|
||||
)}
|
||||
</option>
|
||||
))}
|
||||
<option value="add-account">Add account</option>
|
||||
</select>
|
||||
{currentAccount && (
|
||||
<button
|
||||
onClick={deleteCurrentAccount}
|
||||
className="p-3 rounded hover:bg-gray-200 transition-colors"
|
||||
title="Delete Account"
|
||||
>
|
||||
<Trash2 size={16} className="text-gray-500" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AddAccountForm({
|
||||
addAccount,
|
||||
}: {
|
||||
addAccount: (id: AccountID, secret: AgentSecret) => void;
|
||||
}) {
|
||||
const [id, setId] = useState("");
|
||||
const [secret, setSecret] = useState("");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
addAccount(id as AccountID, secret as AgentSecret);
|
||||
setId("");
|
||||
setSecret("");
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-col gap-2 max-w-md mx-auto h-full justify-center"
|
||||
>
|
||||
<h2 className="text-2xl font-medium text-gray-900 mb-3">
|
||||
Add an Account to Inspect
|
||||
</h2>
|
||||
<input
|
||||
className="border py-2 px-3 rounded-md"
|
||||
placeholder="Account ID"
|
||||
value={id}
|
||||
onChange={(e) => setId(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
className="border py-2 px-3 rounded-md"
|
||||
placeholder="Account Secret"
|
||||
value={secret}
|
||||
onChange={(e) => setSecret(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-indigo-500 text-white px-4 py-2 rounded-md"
|
||||
>
|
||||
Add Account
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountNameDisplay({
|
||||
accountId,
|
||||
node,
|
||||
}: {
|
||||
accountId: CoID<RawAccount>;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const { snapshot } = useResolvedCoValue(accountId, node);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (snapshot && typeof snapshot === "object" && "profile" in snapshot) {
|
||||
const profileId = snapshot.profile as CoID<RawCoValue>;
|
||||
resolveCoValue(profileId, node).then((profileResult) => {
|
||||
if (
|
||||
profileResult.snapshot &&
|
||||
typeof profileResult.snapshot === "object" &&
|
||||
"name" in profileResult.snapshot
|
||||
) {
|
||||
setName(profileResult.snapshot.name as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [snapshot, node]);
|
||||
|
||||
return name ? `${name} <${accountId}>` : accountId;
|
||||
}
|
||||
55
examples/inspector/src/viewer/page-stack.tsx
Normal file
55
examples/inspector/src/viewer/page-stack.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Page } from "./page"; // Assuming you have a Page component
|
||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
||||
|
||||
// Define the structure of a page in the path
|
||||
interface PageInfo {
|
||||
coId: CoID<RawCoValue>;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// Props for the PageStack component
|
||||
interface PageStackProps {
|
||||
path: PageInfo[];
|
||||
node?: LocalNode | null;
|
||||
goBack: () => void;
|
||||
addPages: (pages: PageInfo[]) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PageStack({
|
||||
path,
|
||||
node,
|
||||
goBack,
|
||||
addPages,
|
||||
children,
|
||||
}: PageStackProps) {
|
||||
return (
|
||||
<div className="relative mt-4 h-[calc(100vh-6rem)]">
|
||||
{children && (
|
||||
<div className="absolute inset-0 pb-20">{children}</div>
|
||||
)}
|
||||
{node &&
|
||||
path.map((page, index) => (
|
||||
<Page
|
||||
key={`${page.coId}-${index}`}
|
||||
coId={page.coId}
|
||||
node={node}
|
||||
name={page.name || page.coId}
|
||||
onHeaderClick={goBack}
|
||||
onNavigate={addPages}
|
||||
isTopLevel={index === path.length - 1}
|
||||
style={{
|
||||
transform: `translateZ(${(index - path.length + 1) * 200}px) scale(${
|
||||
1 - (path.length - index - 1) * 0.05
|
||||
}) translateY(${-(index - path.length + 1) * -4}%)`,
|
||||
opacity: 1 - (path.length - index - 1) * 0.05,
|
||||
zIndex: index,
|
||||
transitionProperty: "transform, opacity",
|
||||
transitionDuration: "0.3s",
|
||||
transitionTimingFunction: "ease-out",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
154
examples/inspector/src/viewer/page.tsx
Normal file
154
examples/inspector/src/viewer/page.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import clsx from "clsx";
|
||||
import { CoID, LocalNode, RawCoStream, RawCoValue } from "cojson";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useResolvedCoValue } from "./use-resolve-covalue";
|
||||
import { GridView } from "./grid-view";
|
||||
import { PageInfo } from "./types";
|
||||
import { TableView } from "./table-viewer";
|
||||
import { TypeIcon } from "./type-icon";
|
||||
import { CoStreamView } from "./co-stream-view";
|
||||
import { AccountOrGroupPreview } from "./value-renderer";
|
||||
|
||||
type PageProps = {
|
||||
coId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
name: string;
|
||||
onNavigate: (newPages: PageInfo[]) => void;
|
||||
onHeaderClick?: () => void;
|
||||
isTopLevel?: boolean;
|
||||
style: React.CSSProperties;
|
||||
};
|
||||
|
||||
export function Page({
|
||||
coId,
|
||||
node,
|
||||
name,
|
||||
onNavigate,
|
||||
onHeaderClick,
|
||||
style,
|
||||
isTopLevel,
|
||||
}: PageProps) {
|
||||
const { value, snapshot, type, extendedType } = useResolvedCoValue(
|
||||
coId,
|
||||
node,
|
||||
);
|
||||
const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
|
||||
|
||||
const supportsTableView = type === "colist" || extendedType === "record";
|
||||
|
||||
// Automatically switch to table view if the page is a CoMap record
|
||||
useEffect(() => {
|
||||
if (supportsTableView) {
|
||||
setViewMode("table");
|
||||
}
|
||||
}, [supportsTableView]);
|
||||
|
||||
if (snapshot === "unavailable") {
|
||||
return <div style={style}>Data unavailable</div>;
|
||||
}
|
||||
|
||||
if (!snapshot) {
|
||||
return <div style={style}></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
className={clsx(
|
||||
"absolute inset-0 border border-gray-900/5 bg-clip-padding bg-white rounded-xl shadow-lg p-6 animate-in",
|
||||
)}
|
||||
>
|
||||
{!isTopLevel && (
|
||||
<div
|
||||
className="absolute inset-x-0 top-0 h-10"
|
||||
aria-label="Back"
|
||||
onClick={() => {
|
||||
onHeaderClick?.();
|
||||
}}
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
)}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-2xl font-bold flex items-start flex-col gap-1">
|
||||
<span>
|
||||
{name}
|
||||
{typeof snapshot === "object" &&
|
||||
"name" in snapshot ? (
|
||||
<span className="text-gray-600 font-medium">
|
||||
{" "}
|
||||
{
|
||||
(
|
||||
snapshot as {
|
||||
name: string;
|
||||
}
|
||||
).name
|
||||
}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
|
||||
{type && (
|
||||
<TypeIcon
|
||||
type={type}
|
||||
extendedType={extendedType}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-xs text-gray-700 font-medium py-0.5 px-1 -ml-0.5 rounded bg-gray-700/5 inline-block font-mono">
|
||||
{coId}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* {supportsTableView && (
|
||||
<button
|
||||
onClick={toggleViewMode}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
{viewMode === "grid" ? "Table View" : "Grid View"}
|
||||
</button>
|
||||
)} */}
|
||||
</div>
|
||||
<div className="overflow-auto max-h-[calc(100%-4rem)]">
|
||||
{type === "costream" ? (
|
||||
<CoStreamView
|
||||
data={snapshot}
|
||||
onNavigate={onNavigate}
|
||||
node={node}
|
||||
value={value as RawCoStream}
|
||||
/>
|
||||
) : viewMode === "grid" ? (
|
||||
<GridView
|
||||
data={snapshot}
|
||||
onNavigate={onNavigate}
|
||||
node={node}
|
||||
/>
|
||||
) : (
|
||||
<TableView
|
||||
data={snapshot}
|
||||
node={node}
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
{/* --- */}
|
||||
{extendedType !== "account" && extendedType !== "group" && (
|
||||
<div className="text-xs text-gray-500 mt-4">
|
||||
Owned by{" "}
|
||||
<AccountOrGroupPreview
|
||||
coId={value.group.id}
|
||||
node={node}
|
||||
showId
|
||||
onClick={() => {
|
||||
onNavigate([
|
||||
{ coId: value.group.id, name: "owner" },
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
142
examples/inspector/src/viewer/table-viewer.tsx
Normal file
142
examples/inspector/src/viewer/table-viewer.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
||||
import { JsonObject } from "cojson/src/jsonValue";
|
||||
import { PageInfo } from "./types";
|
||||
import { useMemo, useState } from "react";
|
||||
import { ValueRenderer } from "./value-renderer";
|
||||
import { LinkIcon } from "../link-icon";
|
||||
import { useResolvedCoValues } from "./use-resolve-covalue";
|
||||
|
||||
export function TableView({
|
||||
data,
|
||||
node,
|
||||
onNavigate,
|
||||
}: {
|
||||
data: JsonObject;
|
||||
node: LocalNode;
|
||||
onNavigate: (pages: PageInfo[]) => void;
|
||||
}) {
|
||||
const [visibleRowsCount, setVisibleRowsCount] = useState(10);
|
||||
const [coIdArray, visibleRows] = useMemo(() => {
|
||||
const coIdArray = Array.isArray(data)
|
||||
? data
|
||||
: Object.values(data).every(
|
||||
(k) => typeof k === "string" && k.startsWith("co_"),
|
||||
)
|
||||
? Object.values(data).map((k) => k as CoID<RawCoValue>)
|
||||
: [];
|
||||
|
||||
const visibleRows = coIdArray.slice(0, visibleRowsCount);
|
||||
|
||||
return [coIdArray, visibleRows];
|
||||
}, [data, visibleRowsCount]);
|
||||
const resolvedRows = useResolvedCoValues(visibleRows, node);
|
||||
|
||||
const hasMore = visibleRowsCount < coIdArray.length;
|
||||
|
||||
if (!coIdArray.length) {
|
||||
return <div>No data to display</div>;
|
||||
}
|
||||
|
||||
if (resolvedRows.length === 0) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const keys = Array.from(
|
||||
new Set(
|
||||
resolvedRows.flatMap((item) => Object.keys(item.snapshot || {})),
|
||||
),
|
||||
);
|
||||
|
||||
const loadMore = () => {
|
||||
setVisibleRowsCount((prevVisibleRows) => prevVisibleRows + 10);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="sticky top-0 border-b">
|
||||
<tr>
|
||||
{["", ...keys].map((key) => (
|
||||
<th
|
||||
key={key}
|
||||
className="px-4 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 rounded"
|
||||
>
|
||||
{key}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{resolvedRows
|
||||
.slice(0, visibleRowsCount)
|
||||
.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-1 py-0">
|
||||
<button
|
||||
onClick={() =>
|
||||
onNavigate([
|
||||
{
|
||||
coId: item.value!.id,
|
||||
name: index.toString(),
|
||||
},
|
||||
])
|
||||
}
|
||||
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500 hover:text-blue-500 hover:bg-gray-100 rounded"
|
||||
>
|
||||
<LinkIcon />
|
||||
</button>
|
||||
</td>
|
||||
{keys.map((key) => (
|
||||
<td
|
||||
key={key}
|
||||
className="px-4 py-4 whitespace-nowrap text-sm text-gray-500"
|
||||
>
|
||||
<ValueRenderer
|
||||
json={
|
||||
(item.snapshot as JsonObject)[
|
||||
key
|
||||
]
|
||||
}
|
||||
onCoIDClick={(coId) => {
|
||||
async function handleClick() {
|
||||
onNavigate([
|
||||
{
|
||||
coId: item.value!
|
||||
.id,
|
||||
name: index.toString(),
|
||||
},
|
||||
{
|
||||
coId: coId,
|
||||
name: key,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
handleClick();
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="py-4 text-gray-500 flex items-center justify-between gap-2">
|
||||
<span>
|
||||
Showing {Math.min(visibleRowsCount, coIdArray.length)} of{" "}
|
||||
{coIdArray.length}
|
||||
</span>
|
||||
{hasMore && (
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={loadMore}
|
||||
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
47
examples/inspector/src/viewer/type-icon.tsx
Normal file
47
examples/inspector/src/viewer/type-icon.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { CoID, LocalNode, RawCoValue } from "cojson";
|
||||
import {
|
||||
CoJsonType,
|
||||
ExtendedCoJsonType,
|
||||
useResolvedCoValue,
|
||||
} from "./use-resolve-covalue";
|
||||
|
||||
export const TypeIcon = ({
|
||||
type,
|
||||
extendedType,
|
||||
}: {
|
||||
type: CoJsonType;
|
||||
extendedType?: ExtendedCoJsonType;
|
||||
}) => {
|
||||
const iconMap: Record<ExtendedCoJsonType | CoJsonType, string> = {
|
||||
record: "{} Record",
|
||||
image: "🖼️ Image",
|
||||
comap: "{} CoMap",
|
||||
costream: "≋ CoStream",
|
||||
colist: "☰ CoList",
|
||||
account: "👤 Account",
|
||||
group: "👥 Group",
|
||||
};
|
||||
|
||||
const iconKey = extendedType || type;
|
||||
const icon = iconMap[iconKey as keyof typeof iconMap];
|
||||
|
||||
return icon ? <span className="font-mono">{icon}</span> : null;
|
||||
};
|
||||
|
||||
export const ResolveIcon = ({
|
||||
coId,
|
||||
node,
|
||||
}: {
|
||||
coId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
}) => {
|
||||
const { type, extendedType, snapshot } = useResolvedCoValue(coId, node);
|
||||
|
||||
if (snapshot === "unavailable" && !type) {
|
||||
return <div className="text-gray-600 font-medium">Unavailable</div>;
|
||||
}
|
||||
|
||||
if (!type) return <div className="whitespace-pre w-14 font-mono"> </div>;
|
||||
|
||||
return <TypeIcon type={type} extendedType={extendedType} />;
|
||||
};
|
||||
9
examples/inspector/src/viewer/types.ts
Normal file
9
examples/inspector/src/viewer/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CoID, RawCoValue } from "cojson";
|
||||
|
||||
export type PageInfo = {
|
||||
coId: CoID<RawCoValue>;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export const isCoId = (coId: unknown): coId is CoID<RawCoValue> =>
|
||||
typeof coId === "string" && coId.startsWith("co_");
|
||||
107
examples/inspector/src/viewer/use-page-path.ts
Normal file
107
examples/inspector/src/viewer/use-page-path.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { PageInfo } from "./types";
|
||||
import { CoID, RawCoValue } from "cojson";
|
||||
|
||||
export function usePagePath(defaultPath?: PageInfo[]) {
|
||||
const [path, setPath] = useState<PageInfo[]>(() => {
|
||||
const hash = window.location.hash.slice(2); // Remove '#/'
|
||||
if (hash) {
|
||||
try {
|
||||
return decodePathFromHash(hash);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse hash:", e);
|
||||
}
|
||||
}
|
||||
return defaultPath || [];
|
||||
});
|
||||
|
||||
const updatePath = useCallback((newPath: PageInfo[]) => {
|
||||
setPath(newPath);
|
||||
const hash = encodePathToHash(newPath);
|
||||
window.location.hash = `#/${hash}`;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleHashChange = () => {
|
||||
const hash = window.location.hash.slice(2);
|
||||
if (hash) {
|
||||
try {
|
||||
const newPath = decodePathFromHash(hash);
|
||||
setPath(newPath);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse hash:", e);
|
||||
}
|
||||
} else if (defaultPath) {
|
||||
setPath(defaultPath);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("hashchange", handleHashChange);
|
||||
return () => window.removeEventListener("hashchange", handleHashChange);
|
||||
}, [defaultPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
defaultPath &&
|
||||
JSON.stringify(path) !== JSON.stringify(defaultPath)
|
||||
) {
|
||||
updatePath(defaultPath);
|
||||
}
|
||||
}, [defaultPath, path, updatePath]);
|
||||
|
||||
const addPages = useCallback(
|
||||
(newPages: PageInfo[]) => {
|
||||
updatePath([...path, ...newPages]);
|
||||
},
|
||||
[path, updatePath],
|
||||
);
|
||||
|
||||
const goToIndex = useCallback(
|
||||
(index: number) => {
|
||||
updatePath(path.slice(0, index + 1));
|
||||
},
|
||||
[path, updatePath],
|
||||
);
|
||||
|
||||
const setPage = useCallback(
|
||||
(coId: CoID<RawCoValue>) => {
|
||||
updatePath([{ coId, name: "Root" }]);
|
||||
},
|
||||
[updatePath],
|
||||
);
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
if (path.length > 1) {
|
||||
updatePath(path.slice(0, path.length - 1));
|
||||
}
|
||||
}, [path, updatePath]);
|
||||
|
||||
return {
|
||||
path,
|
||||
setPage,
|
||||
addPages,
|
||||
goToIndex,
|
||||
goBack,
|
||||
};
|
||||
}
|
||||
|
||||
function encodePathToHash(path: PageInfo[]): string {
|
||||
return path
|
||||
.map((page) => {
|
||||
if (page.name && page.name !== "Root") {
|
||||
return `${page.coId}:${encodeURIComponent(page.name)}`;
|
||||
}
|
||||
return page.coId;
|
||||
})
|
||||
.join("/");
|
||||
}
|
||||
|
||||
function decodePathFromHash(hash: string): PageInfo[] {
|
||||
return hash.split("/").map((segment) => {
|
||||
const [coId, encodedName] = segment.split(":");
|
||||
return {
|
||||
coId,
|
||||
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
||||
} as PageInfo;
|
||||
});
|
||||
}
|
||||
152
examples/inspector/src/viewer/use-resolve-covalue.ts
Normal file
152
examples/inspector/src/viewer/use-resolve-covalue.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { CoID, LocalNode, RawBinaryCoStream, RawCoValue } from "cojson";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type CoJsonType = "comap" | "costream" | "colist";
|
||||
export type ExtendedCoJsonType = "image" | "record" | "account" | "group";
|
||||
|
||||
type JSON = string | number | boolean | null | JSON[] | { [key: string]: JSON };
|
||||
type JSONObject = { [key: string]: JSON };
|
||||
|
||||
type ResolvedImageDefinition = {
|
||||
originalSize: [number, number];
|
||||
placeholderDataURL?: string;
|
||||
[res: `${number}x${number}`]: RawBinaryCoStream["id"];
|
||||
};
|
||||
|
||||
// Type guard for browser image
|
||||
export const isBrowserImage = (
|
||||
coValue: JSONObject,
|
||||
): coValue is ResolvedImageDefinition => {
|
||||
return "originalSize" in coValue && "placeholderDataURL" in coValue;
|
||||
};
|
||||
|
||||
export type ResolvedGroup = {
|
||||
readKey: string;
|
||||
[key: string]: JSON;
|
||||
};
|
||||
|
||||
export const isGroup = (coValue: JSONObject): coValue is ResolvedGroup => {
|
||||
return "readKey" in coValue;
|
||||
};
|
||||
|
||||
export type ResolvedAccount = {
|
||||
profile: {
|
||||
name: string;
|
||||
};
|
||||
[key: string]: JSON;
|
||||
};
|
||||
|
||||
export const isAccount = (coValue: JSONObject): coValue is ResolvedAccount => {
|
||||
return isGroup(coValue) && "profile" in coValue;
|
||||
};
|
||||
|
||||
export async function resolveCoValue(
|
||||
coValueId: CoID<RawCoValue>,
|
||||
node: LocalNode,
|
||||
): Promise<
|
||||
| {
|
||||
value: RawCoValue;
|
||||
snapshot: JSONObject;
|
||||
type: CoJsonType | null;
|
||||
extendedType: ExtendedCoJsonType | undefined;
|
||||
}
|
||||
| {
|
||||
value: undefined;
|
||||
snapshot: "unavailable";
|
||||
type: null;
|
||||
extendedType: undefined;
|
||||
}
|
||||
> {
|
||||
const value = await node.load(coValueId);
|
||||
|
||||
if (value === "unavailable") {
|
||||
return {
|
||||
value: undefined,
|
||||
snapshot: "unavailable",
|
||||
type: null,
|
||||
extendedType: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const snapshot = value.toJSON() as JSONObject;
|
||||
const type = value.type as CoJsonType;
|
||||
|
||||
// Determine extended type
|
||||
let extendedType: ExtendedCoJsonType | undefined;
|
||||
|
||||
if (type === "comap") {
|
||||
if (isBrowserImage(snapshot)) {
|
||||
extendedType = "image";
|
||||
} else if (isAccount(snapshot)) {
|
||||
extendedType = "account";
|
||||
} else if (isGroup(snapshot)) {
|
||||
extendedType = "group";
|
||||
} else {
|
||||
// This check is a bit of a hack
|
||||
// There might be a better way to do this
|
||||
const children = Object.values(snapshot).slice(0, 10);
|
||||
if (
|
||||
children.every(
|
||||
(c) => typeof c === "string" && c.startsWith("co_"),
|
||||
) &&
|
||||
children.length > 3
|
||||
) {
|
||||
extendedType = "record";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
snapshot,
|
||||
type,
|
||||
extendedType,
|
||||
};
|
||||
}
|
||||
|
||||
export function useResolvedCoValue(
|
||||
coValueId: CoID<RawCoValue>,
|
||||
node: LocalNode,
|
||||
) {
|
||||
const [result, setResult] =
|
||||
useState<Awaited<ReturnType<typeof resolveCoValue>>>();
|
||||
|
||||
useEffect(() => {
|
||||
resolveCoValue(coValueId, node).then(setResult);
|
||||
}, [coValueId, node]);
|
||||
|
||||
return (
|
||||
result || {
|
||||
value: undefined,
|
||||
snapshot: undefined,
|
||||
type: undefined,
|
||||
extendedType: undefined,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function useResolvedCoValues(
|
||||
coValueIds: CoID<RawCoValue>[],
|
||||
node: LocalNode,
|
||||
) {
|
||||
const [results, setResults] = useState<
|
||||
Awaited<ReturnType<typeof resolveCoValue>>[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("RETECHING", coValueIds);
|
||||
const fetchResults = async () => {
|
||||
if (coValueIds.length === 0) return;
|
||||
const resolvedValues = await Promise.all(
|
||||
coValueIds.map((coValueId) => resolveCoValue(coValueId, node)),
|
||||
);
|
||||
|
||||
console.log({ resolvedValues });
|
||||
setResults(resolvedValues);
|
||||
};
|
||||
|
||||
fetchResults();
|
||||
}, [coValueIds, node]);
|
||||
|
||||
return results;
|
||||
}
|
||||
248
examples/inspector/src/viewer/value-renderer.tsx
Normal file
248
examples/inspector/src/viewer/value-renderer.tsx
Normal file
@@ -0,0 +1,248 @@
|
||||
import clsx from "clsx";
|
||||
import { CoID, JsonValue, LocalNode, RawCoValue } from "cojson";
|
||||
import { LinkIcon } from "../link-icon";
|
||||
import {
|
||||
isBrowserImage,
|
||||
resolveCoValue,
|
||||
useResolvedCoValue,
|
||||
} from "./use-resolve-covalue";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
// Is there a chance we can pass the actual CoValue here?
|
||||
export function ValueRenderer({
|
||||
json,
|
||||
compact,
|
||||
onCoIDClick,
|
||||
}: {
|
||||
json: JsonValue | undefined;
|
||||
compact?: boolean;
|
||||
onCoIDClick?: (childNode: CoID<RawCoValue>) => void;
|
||||
}) {
|
||||
if (typeof json === "undefined" || json === undefined) {
|
||||
return <span className="text-gray-400">undefined</span>;
|
||||
}
|
||||
|
||||
if (json === null) {
|
||||
return <span className="text-gray-400">null</span>;
|
||||
}
|
||||
|
||||
if (typeof json === "string" && json.startsWith("co_")) {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
"inline-flex gap-1 items-center",
|
||||
onCoIDClick &&
|
||||
"text-blue-500 cursor-pointer hover:underline",
|
||||
)}
|
||||
onClick={() => {
|
||||
onCoIDClick?.(json as CoID<RawCoValue>);
|
||||
}}
|
||||
>
|
||||
{json}
|
||||
{onCoIDClick && <LinkIcon />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof json === "string") {
|
||||
return (
|
||||
<span className="text-green-900 font-mono">
|
||||
{/* <span className="select-none opacity-70">{'"'}</span> */}
|
||||
{json}
|
||||
{/* <span className="select-none opacity-70">{'"'}</span> */}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof json === "number") {
|
||||
return <span className="text-purple-500">{json}</span>;
|
||||
}
|
||||
|
||||
if (typeof json === "boolean") {
|
||||
return (
|
||||
<span
|
||||
className={clsx(
|
||||
json
|
||||
? "text-green-700 bg-green-700/5"
|
||||
: "text-amber-700 bg-amber-500/5",
|
||||
"font-mono",
|
||||
"inline-block px-1 py-0.5 rounded",
|
||||
)}
|
||||
>
|
||||
{json.toString()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(json)) {
|
||||
return (
|
||||
<span title={JSON.stringify(json)}>
|
||||
Array <span className="text-gray-500">({json.length})</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof json === "object") {
|
||||
return (
|
||||
<span
|
||||
title={JSON.stringify(json, null, 2)}
|
||||
className="inline-block max-w-64 truncate"
|
||||
>
|
||||
{compact ? (
|
||||
<span>
|
||||
Object{" "}
|
||||
<span className="text-gray-500">
|
||||
({Object.keys(json).length})
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
JSON.stringify(json, null, 2)
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <span>{String(json)}</span>;
|
||||
}
|
||||
|
||||
export const CoMapPreview = ({
|
||||
coId,
|
||||
node,
|
||||
limit = 6,
|
||||
}: {
|
||||
coId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
limit?: number;
|
||||
}) => {
|
||||
const { value, snapshot, type, extendedType } = useResolvedCoValue(
|
||||
coId,
|
||||
node,
|
||||
);
|
||||
|
||||
if (!snapshot) {
|
||||
return (
|
||||
<div className="rounded bg-gray-100 animate-pulse whitespace-pre w-24">
|
||||
{" "}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot === "unavailable" && !value) {
|
||||
return <div className="text-gray-500">Unavailable</div>;
|
||||
}
|
||||
|
||||
if (extendedType === "image" && isBrowserImage(snapshot)) {
|
||||
return (
|
||||
<div>
|
||||
<img
|
||||
src={snapshot.placeholderDataURL}
|
||||
className="size-8 border-2 border-white drop-shadow-md my-2"
|
||||
/>
|
||||
<span className="text-gray-500 text-sm">
|
||||
{snapshot.originalSize[0]} x {snapshot.originalSize[1]}
|
||||
</span>
|
||||
|
||||
{/* <CoMapPreview coId={value[]} node={node} /> */}
|
||||
{/* <ProgressiveImg image={value}>
|
||||
{({ src }) => <img src={src} className={clsx("w-full")} />}
|
||||
</ProgressiveImg> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (extendedType === "record") {
|
||||
return (
|
||||
<div>
|
||||
Record{" "}
|
||||
<span className="text-gray-500">
|
||||
({Object.keys(snapshot).length})
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === "colist") {
|
||||
return (
|
||||
<div>
|
||||
List{" "}
|
||||
<span className="text-gray-500">
|
||||
({(snapshot as unknown as []).length})
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-sm flex flex-col gap-2 items-start">
|
||||
<div className="grid grid-cols-[auto_1fr] gap-2">
|
||||
{Object.entries(snapshot)
|
||||
.slice(0, limit)
|
||||
.map(([key, value]) => (
|
||||
<React.Fragment key={key}>
|
||||
<span className="font-medium">{key}: </span>
|
||||
<span>
|
||||
<ValueRenderer json={value} />
|
||||
</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
{Object.entries(snapshot).length > limit && (
|
||||
<div className="text-left text-xs text-gray-500 mt-2">
|
||||
{Object.entries(snapshot).length - limit} more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function AccountOrGroupPreview({
|
||||
coId,
|
||||
node,
|
||||
showId = false,
|
||||
onClick,
|
||||
}: {
|
||||
coId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
showId?: boolean;
|
||||
onClick?: (name?: string) => void;
|
||||
}) {
|
||||
const { snapshot, extendedType } = useResolvedCoValue(coId, node);
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (extendedType === "account") {
|
||||
resolveCoValue(
|
||||
(snapshot as unknown as { profile: CoID<RawCoValue> }).profile,
|
||||
node,
|
||||
).then(({ snapshot }) => {
|
||||
if (
|
||||
typeof snapshot === "object" &&
|
||||
"name" in snapshot &&
|
||||
typeof snapshot.name === "string"
|
||||
) {
|
||||
setName(snapshot.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [snapshot, node, extendedType]);
|
||||
|
||||
if (!snapshot) return <span>Loading...</span>;
|
||||
if (extendedType !== "account" && extendedType !== "group") {
|
||||
return <span>CoID is not an account or group</span>;
|
||||
}
|
||||
|
||||
const displayName =
|
||||
extendedType === "account" ? name || "Account" : "Group";
|
||||
const displayText = showId ? `${displayName} (${coId})` : displayName;
|
||||
|
||||
const props = onClick
|
||||
? {
|
||||
onClick: () => onClick(displayName),
|
||||
className: "text-blue-500 cursor-pointer hover:underline",
|
||||
}
|
||||
: {
|
||||
className: "text-gray-500",
|
||||
};
|
||||
|
||||
return <span {...props}>{displayText}</span>;
|
||||
}
|
||||
1
examples/inspector/src/vite-env.d.ts
vendored
Normal file
1
examples/inspector/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
75
examples/inspector/tailwind.config.js
Normal file
75
examples/inspector/tailwind.config.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
29
examples/inspector/tsconfig.json
Normal file
29
examples/inspector/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
examples/inspector/tsconfig.node.json
Normal file
10
examples/inspector/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
16
examples/inspector/vite.config.ts
Normal file
16
examples/inspector/vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import path from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false
|
||||
}
|
||||
})
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'prettier'
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
9
examples/pets/.prettierrc.js
Normal file
9
examples/pets/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
trailingComma: "all",
|
||||
tabWidth: 4,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
};
|
||||
|
||||
export default config;
|
||||
777
examples/pets/CHANGELOG.md
Normal file
777
examples/pets/CHANGELOG.md
Normal file
@@ -0,0 +1,777 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.98-hotfixes.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.5
|
||||
- jazz-tools@0.7.33-hotfixes.5
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.5
|
||||
|
||||
## 0.0.98-hotfixes.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.4
|
||||
- jazz-tools@0.7.33-hotfixes.4
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.4
|
||||
|
||||
## 0.0.98-hotfixes.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.3
|
||||
- jazz-tools@0.7.33-hotfixes.3
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.3
|
||||
|
||||
## 0.0.98-hotfixes.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.2
|
||||
- jazz-react@0.7.33-hotfixes.2
|
||||
|
||||
## 0.0.98-hotfixes.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.1
|
||||
- jazz-react@0.7.33-hotfixes.1
|
||||
|
||||
## 0.0.98-hotfixes.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.0
|
||||
- jazz-tools@0.7.33-hotfixes.0
|
||||
- jazz-browser-media-images@0.7.33-hotfixes.0
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.32
|
||||
- jazz-browser-media-images@0.7.32
|
||||
- jazz-react@0.7.32
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.31
|
||||
- jazz-tools@0.7.31
|
||||
- jazz-browser-media-images@0.7.31
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.30
|
||||
- jazz-react@0.7.30
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.29
|
||||
- jazz-tools@0.7.29
|
||||
- jazz-browser-media-images@0.7.29
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.28
|
||||
- jazz-tools@0.7.28
|
||||
- jazz-browser-media-images@0.7.28
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.27
|
||||
- jazz-react@0.7.27
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.26
|
||||
- jazz-tools@0.7.26
|
||||
- jazz-browser-media-images@0.7.26
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.25
|
||||
- jazz-browser-media-images@0.7.25
|
||||
- jazz-react@0.7.25
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.24
|
||||
- jazz-browser-media-images@0.7.24
|
||||
- jazz-react@0.7.24
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.23
|
||||
- jazz-tools@0.7.23
|
||||
- jazz-browser-media-images@0.7.23
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.22
|
||||
- jazz-react@0.7.22
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.21
|
||||
- jazz-browser-media-images@0.7.21
|
||||
- jazz-react@0.7.21
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.20
|
||||
- jazz-browser-media-images@0.7.20
|
||||
- jazz-react@0.7.20
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.19
|
||||
- jazz-browser-media-images@0.7.19
|
||||
- jazz-react@0.7.19
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.18
|
||||
- jazz-tools@0.7.18
|
||||
- jazz-browser-media-images@0.7.18
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.17
|
||||
- jazz-tools@0.7.17
|
||||
- jazz-browser-media-images@0.7.17
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.16
|
||||
- jazz-browser-media-images@0.7.16
|
||||
- jazz-react@0.7.16
|
||||
|
||||
## 0.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.15
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.14
|
||||
- jazz-react@0.7.14
|
||||
- jazz-browser-media-images@0.7.14
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.13
|
||||
- jazz-browser-media-images@0.7.13
|
||||
- jazz-react@0.7.13
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.12
|
||||
- jazz-browser-media-images@0.7.12
|
||||
- jazz-react@0.7.12
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.11
|
||||
- jazz-tools@0.7.11
|
||||
- jazz-browser-media-images@0.7.11
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.10
|
||||
- jazz-tools@0.7.10
|
||||
- jazz-browser-media-images@0.7.10
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.9
|
||||
- jazz-tools@0.7.9
|
||||
- jazz-browser-media-images@0.7.9
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.8
|
||||
- jazz-browser-media-images@0.7.8
|
||||
- jazz-react@0.7.8
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9fdc91c]
|
||||
- jazz-react@0.7.7
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.6
|
||||
- jazz-browser-media-images@0.7.6
|
||||
- jazz-react@0.7.6
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.5
|
||||
- jazz-browser-media-images@0.7.5
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.4
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.3
|
||||
- jazz-browser-media-images@0.7.3
|
||||
- jazz-react@0.7.3
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.2
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.1
|
||||
- jazz-browser-media-images@0.7.1
|
||||
- jazz-react@0.7.1
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [96c494f]
|
||||
- Updated dependencies [59c18c3]
|
||||
- Updated dependencies [19f52b7]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [d8fe2b1]
|
||||
- Updated dependencies [19004b4]
|
||||
- Updated dependencies [a78f168]
|
||||
- Updated dependencies [52675c9]
|
||||
- Updated dependencies [129e2c1]
|
||||
- Updated dependencies [6d49e9b]
|
||||
- Updated dependencies [1cfa279]
|
||||
- Updated dependencies [704af7d]
|
||||
- Updated dependencies [e97f730]
|
||||
- Updated dependencies [460478f]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [e299c3e]
|
||||
- Updated dependencies [ed5643a]
|
||||
- Updated dependencies [bde684f]
|
||||
- Updated dependencies [c4151fc]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [01ac646]
|
||||
- Updated dependencies [a5e68a4]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [952982e]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [5fa277c]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [21771c4]
|
||||
- Updated dependencies [77c2b56]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [d2e03ff]
|
||||
- Updated dependencies [354bdcd]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [69ac514]
|
||||
- Updated dependencies [f8a5c46]
|
||||
- Updated dependencies [f0f6f1b]
|
||||
- Updated dependencies [e5eed5b]
|
||||
- Updated dependencies [1a44f87]
|
||||
- Updated dependencies [627d895]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [38d4410]
|
||||
- Updated dependencies [85d2b62]
|
||||
- Updated dependencies [fd86c11]
|
||||
- Updated dependencies [52675c9]
|
||||
- jazz-tools@0.7.0
|
||||
- jazz-browser-media-images@0.7.0
|
||||
- jazz-react@0.7.0
|
||||
|
||||
## 0.0.65-alpha.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.42
|
||||
- jazz-browser-media-images@0.7.0-alpha.40
|
||||
- jazz-react@0.7.0-alpha.42
|
||||
|
||||
## 0.0.65-alpha.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-tools@0.7.0-alpha.41
|
||||
- jazz-browser-media-images@0.7.0-alpha.39
|
||||
- jazz-react@0.7.0-alpha.41
|
||||
|
||||
## 0.0.65-alpha.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.40
|
||||
|
||||
## 0.0.65-alpha.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.0-alpha.39
|
||||
- jazz-tools@0.7.0-alpha.39
|
||||
- jazz-browser-media-images@0.7.0-alpha.38
|
||||
|
||||
## 0.0.65-alpha.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.38
|
||||
- jazz-react@0.7.0-alpha.38
|
||||
- jazz-browser-media-images@0.7.0-alpha.37
|
||||
|
||||
## 0.0.65-alpha.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.37
|
||||
- jazz-browser-media-images@0.7.0-alpha.36
|
||||
- jazz-tools@0.7.0-alpha.37
|
||||
|
||||
## 0.0.65-alpha.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [1a35307]
|
||||
- jazz-tools@0.7.0-alpha.36
|
||||
- jazz-react@0.7.0-alpha.36
|
||||
- jazz-browser-media-images@0.7.0-alpha.35
|
||||
|
||||
## 0.0.65-alpha.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.35
|
||||
- jazz-react@0.7.0-alpha.35
|
||||
- jazz-browser-media-images@0.7.0-alpha.34
|
||||
|
||||
## 0.0.65-alpha.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.34
|
||||
- jazz-browser-media-images@0.7.0-alpha.33
|
||||
- jazz-react@0.7.0-alpha.34
|
||||
|
||||
## 0.0.65-alpha.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.33
|
||||
|
||||
## 0.0.65-alpha.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.32
|
||||
- jazz-react@0.7.0-alpha.32
|
||||
- jazz-browser-media-images@0.7.0-alpha.32
|
||||
|
||||
## 0.0.65-alpha.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.31
|
||||
- jazz-browser-media-images@0.7.0-alpha.31
|
||||
- jazz-react@0.7.0-alpha.31
|
||||
|
||||
## 0.0.65-alpha.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.30
|
||||
- jazz-browser-media-images@0.7.0-alpha.30
|
||||
- jazz-react@0.7.0-alpha.30
|
||||
|
||||
## 0.0.65-alpha.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.29
|
||||
- jazz-browser-media-images@0.7.0-alpha.29
|
||||
- jazz-react@0.7.0-alpha.29
|
||||
|
||||
## 0.0.65-alpha.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.28
|
||||
- jazz-browser-media-images@0.7.0-alpha.28
|
||||
- jazz-react@0.7.0-alpha.28
|
||||
|
||||
## 0.0.65-alpha.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.27
|
||||
- jazz-browser-media-images@0.7.0-alpha.27
|
||||
- jazz-react@0.7.0-alpha.27
|
||||
|
||||
## 0.0.65-alpha.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.26
|
||||
- jazz-browser-media-images@0.7.0-alpha.26
|
||||
- jazz-react@0.7.0-alpha.26
|
||||
|
||||
## 0.0.65-alpha.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.25
|
||||
- jazz-browser-media-images@0.7.0-alpha.25
|
||||
- jazz-react@0.7.0-alpha.25
|
||||
|
||||
## 0.0.65-alpha.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.24
|
||||
- jazz-browser-media-images@0.7.0-alpha.24
|
||||
- jazz-react@0.7.0-alpha.24
|
||||
|
||||
## 0.0.65-alpha.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.23
|
||||
- jazz-browser-media-images@0.7.0-alpha.23
|
||||
- jazz-react@0.7.0-alpha.23
|
||||
|
||||
## 0.0.65-alpha.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.22
|
||||
- jazz-browser-media-images@0.7.0-alpha.22
|
||||
- jazz-react@0.7.0-alpha.22
|
||||
|
||||
## 0.0.65-alpha.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.21
|
||||
- jazz-tools@0.7.0-alpha.21
|
||||
- jazz-browser-media-images@0.7.0-alpha.21
|
||||
|
||||
## 0.0.65-alpha.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.20
|
||||
- jazz-tools@0.7.0-alpha.20
|
||||
- jazz-browser-media-images@0.7.0-alpha.20
|
||||
|
||||
## 0.0.65-alpha.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.19
|
||||
- jazz-browser-media-images@0.7.0-alpha.19
|
||||
- jazz-react@0.7.0-alpha.19
|
||||
|
||||
## 0.0.65-alpha.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.7.0-alpha.18
|
||||
- jazz-react@0.7.0-alpha.18
|
||||
|
||||
## 0.0.65-alpha.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.17
|
||||
- jazz-browser-media-images@0.7.0-alpha.17
|
||||
- jazz-react@0.7.0-alpha.17
|
||||
|
||||
## 0.0.65-alpha.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.16
|
||||
- jazz-browser-media-images@0.7.0-alpha.16
|
||||
- jazz-react@0.7.0-alpha.16
|
||||
|
||||
## 0.0.65-alpha.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.15
|
||||
- jazz-browser-media-images@0.7.0-alpha.15
|
||||
- jazz-react@0.7.0-alpha.15
|
||||
|
||||
## 0.0.65-alpha.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.14
|
||||
- jazz-browser-media-images@0.7.0-alpha.14
|
||||
- jazz-react@0.7.0-alpha.14
|
||||
|
||||
## 0.0.65-alpha.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.13
|
||||
- jazz-browser-media-images@0.7.0-alpha.13
|
||||
- jazz-react@0.7.0-alpha.13
|
||||
|
||||
## 0.0.65-alpha.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.12
|
||||
- jazz-react@0.7.0-alpha.12
|
||||
- jazz-tools@0.7.0-alpha.12
|
||||
|
||||
## 0.0.65-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.11
|
||||
- jazz-react@0.7.0-alpha.11
|
||||
- jazz-tools@0.7.0-alpha.11
|
||||
|
||||
## 0.0.65-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.10
|
||||
- jazz-react@0.7.0-alpha.10
|
||||
- jazz-tools@0.7.0-alpha.10
|
||||
|
||||
## 0.0.65-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.9
|
||||
- jazz-react@0.7.0-alpha.9
|
||||
- jazz-tools@0.7.0-alpha.9
|
||||
|
||||
## 0.0.65-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.8
|
||||
- jazz-react@0.7.0-alpha.8
|
||||
- jazz-tools@0.7.0-alpha.8
|
||||
|
||||
## 0.0.65-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.7
|
||||
- jazz-react@0.7.0-alpha.7
|
||||
- jazz-tools@0.7.0-alpha.7
|
||||
|
||||
## 0.0.65-alpha.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.6
|
||||
- jazz-react@0.7.0-alpha.6
|
||||
- jazz-tools@0.7.0-alpha.6
|
||||
|
||||
## 0.0.65-alpha.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.5
|
||||
- jazz-react@0.7.0-alpha.5
|
||||
- jazz-tools@0.7.0-alpha.5
|
||||
|
||||
## 0.0.65-alpha.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.4
|
||||
- jazz-browser-media-images@0.7.0-alpha.4
|
||||
- jazz-react@0.7.0-alpha.4
|
||||
|
||||
## 0.0.65-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.3
|
||||
- jazz-browser-media-images@0.7.0-alpha.3
|
||||
- jazz-react@0.7.0-alpha.3
|
||||
|
||||
## 0.0.65-alpha.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.2
|
||||
- jazz-react@0.7.0-alpha.2
|
||||
- jazz-tools@0.7.0-alpha.2
|
||||
|
||||
## 0.0.65-alpha.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.1
|
||||
- jazz-react@0.7.0-alpha.1
|
||||
- jazz-tools@0.7.0-alpha.1
|
||||
|
||||
## 0.0.65-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.7.0-alpha.0
|
||||
- jazz-react@0.7.0-alpha.0
|
||||
- jazz-tools@0.7.0-alpha.0
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.6.0
|
||||
|
||||
## 0.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-browser-media-images@0.5.0
|
||||
- jazz-react@0.5.0
|
||||
- jazz-react-auth-local@0.4.16
|
||||
@@ -1,58 +1,35 @@
|
||||
# Jazz Todo List Example
|
||||
# Jazz Rate-My-Pet List Example
|
||||
|
||||
Live version: https://example-todo.jazz.tools
|
||||
Live version: https://example-pets.jazz.tools
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
Start by checking out just the example app to a folder:
|
||||
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by checking out `jazz`
|
||||
```bash
|
||||
npx degit gardencmp/jazz/examples/todo jazz-example-todo
|
||||
cd jazz-example-todo
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
cd jazz/examples/pets
|
||||
pnpm pack --pack-destination /tmp
|
||||
mkdir -p ~/jazz-examples/pets # or any other directory
|
||||
tar -xf /tmp/jazz-example-pets-* --strip-components 1 -C ~/jazz-examples/pets
|
||||
cd ~/jazz-examples/pets
|
||||
```
|
||||
|
||||
(This ensures that you have the example app without git history or our multi-package monorepo)
|
||||
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
- [`src/basicComponents`](./src/basicComponents) contains simple components to build the UI, unrelated to Jazz (powered by [shadcn/ui](https://ui.shadcn.com))
|
||||
- [`src/components`](./src/components/) contains helper components that do contain Jazz-specific logic, but are not super relevant to understand the basics of Jazz and CoJSON
|
||||
- [`src/0_main.tsx`](./src/0_main.tsx), [`src/1_types.ts`](./src/1_types.ts), [`src/2_App.tsx`](./src/2_App.tsx), [`src/3_TodoTable.tsx`](./src/3_TodoTable.tsx), [`src/router.ts`](./src/router.ts) - the main files for this example, see the walkthrough below
|
||||
|
||||
## Walkthrough
|
||||
|
||||
### Main parts
|
||||
|
||||
- The top-level provider `<WithJazz/>`: [`src/0_main.tsx`](./src/0_main.tsx)
|
||||
|
||||
- Defining the data model with CoJSON: [`src/1_types.ts`](./src/1_types.ts)
|
||||
|
||||
- Creating todo projects & routing in `<App/>`: [`src/2_App.tsx`](./src/2_App.tsx)
|
||||
|
||||
- Reactively rendering a todo project as a table, adding and editing tasks: [`src/3_TodoTable.tsx`](./src/3_TodoTable.tsx)
|
||||
|
||||
### Helpers
|
||||
|
||||
- Getting user profiles in `<NameBadge/>`: [`src/components/NameBadge.tsx`](./src/components/NameBadge.tsx)
|
||||
|
||||
- (not yet commented) Creating invite links/QR codes with `<InviteButton/>`: [`src/components/InviteButton.tsx`](./src/components/InviteButton.tsx)
|
||||
|
||||
- (not yet commented) `location.hash`-based routing and accepting invite links with `useSimpleHashRouterThatAcceptsInvites()` in [`src/router.ts`](./src/router.ts)
|
||||
|
||||
This is the whole Todo List app!
|
||||
|
||||
## Questions / problems / feedback
|
||||
|
||||
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||
@@ -62,4 +39,4 @@ If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or
|
||||
|
||||
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<WithJazz>` provider component in [./src/0_main.tsx](./src/0_main.tsx).
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/0_main.tsx"></script>
|
||||
<script type="module" src="/src/2_main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
job "example-todo$BRANCH_SUFFIX" {
|
||||
job "example-pets$BRANCH_SUFFIX" {
|
||||
region = "global"
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 8
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
@@ -41,7 +41,7 @@ job "example-todo$BRANCH_SUFFIX" {
|
||||
|
||||
service {
|
||||
tags = ["public"]
|
||||
name = "example-todo$BRANCH_SUFFIX"
|
||||
name = "example-pets$BRANCH_SUFFIX"
|
||||
port = "http"
|
||||
provider = "consul"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.98-hotfixes.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"format": "prettier --write './src/**/*.{ts,tsx}'",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --fix",
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
@@ -16,16 +21,18 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "^0.1.13",
|
||||
"jazz-react-auth-local": "^0.1.13",
|
||||
"jazz-browser-media-images": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uniqolor": "^1.1.0",
|
||||
"use-debounce": "^9.0.4"
|
||||
"uniqolor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
|
||||
import { WithJazz } from "jazz-react";
|
||||
import { LocalAuth } from "jazz-react-auth-local";
|
||||
|
||||
import { ThemeProvider, TitleAndLogo } from "./basicComponents/index.ts";
|
||||
import { PrettyAuthUI } from "./components/Auth.tsx";
|
||||
import App from "./2_App.tsx";
|
||||
|
||||
/** Walkthrough: The top-level provider `<WithJazz/>`
|
||||
*
|
||||
* This shows how to use the top-level provider `<WithJazz/>`,
|
||||
* which provides the rest of the app with a `LocalNode` (used through `useJazz` later),
|
||||
* based on `LocalAuth` that uses PassKeys (aka WebAuthn) to store a user's account secret
|
||||
* - no backend needed. */
|
||||
|
||||
const appName = "Jazz Rate My Pet Example";
|
||||
|
||||
const auth = LocalAuth({
|
||||
appName,
|
||||
Component: PrettyAuthUI,
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<TitleAndLogo name={appName} />
|
||||
|
||||
<WithJazz auth={auth}>
|
||||
<App />
|
||||
</WithJazz>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
/** Walkthrough: Continue with ./1_types.ts */
|
||||
59
examples/pets/src/1_schema.ts
Normal file
59
examples/pets/src/1_schema.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Account,
|
||||
CoList,
|
||||
CoMap,
|
||||
CoStream,
|
||||
ImageDefinition,
|
||||
Profile,
|
||||
co,
|
||||
} from "jazz-tools";
|
||||
|
||||
/** Walkthrough: Defining the data model with CoJSON
|
||||
*
|
||||
* Here, we define our main data model of TODO
|
||||
*
|
||||
* TODO
|
||||
**/
|
||||
|
||||
export const ReactionTypes = [
|
||||
"aww",
|
||||
"love",
|
||||
"haha",
|
||||
"wow",
|
||||
"tiny",
|
||||
"chonkers",
|
||||
] as const;
|
||||
export type ReactionType = (typeof ReactionTypes)[number];
|
||||
|
||||
export class PetReactions extends CoStream.Of(co.json<ReactionType>()) {}
|
||||
|
||||
export class PetPost extends CoMap {
|
||||
name = co.string;
|
||||
image = co.ref(ImageDefinition);
|
||||
reactions = co.ref(PetReactions);
|
||||
}
|
||||
|
||||
export class ListOfPosts extends CoList.Of(co.ref(PetPost)) {}
|
||||
|
||||
export class PetAccountRoot extends CoMap {
|
||||
posts = co.ref(ListOfPosts);
|
||||
}
|
||||
|
||||
export class PetAccount extends Account {
|
||||
profile = co.ref(Profile);
|
||||
root = co.ref(PetAccountRoot);
|
||||
|
||||
migrate(this: PetAccount, creationProps?: { name: string }) {
|
||||
super.migrate(creationProps);
|
||||
if (!this._refs.root) {
|
||||
this.root = PetAccountRoot.create(
|
||||
{
|
||||
posts: ListOfPosts.create([], { owner: this }),
|
||||
},
|
||||
{ owner: this },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Walkthrough: Continue with ./2_App.tsx */
|
||||
@@ -1,27 +0,0 @@
|
||||
import { CoMap, CoID, BinaryCoStream, CoStream } from "cojson";
|
||||
|
||||
/** Walkthrough: Defining the data model with CoJSON
|
||||
*
|
||||
* Here, we define our main data model of TODO
|
||||
*
|
||||
* TODO
|
||||
**/
|
||||
|
||||
export type PetPost = CoMap<{
|
||||
name: string;
|
||||
image: CoID<BinaryCoStream>;
|
||||
reactions: CoID<PetReactions>;
|
||||
}>;
|
||||
|
||||
export type ReactionType =
|
||||
| "aww"
|
||||
| "love"
|
||||
| "haha"
|
||||
| "wow"
|
||||
| "tiny"
|
||||
| "chonkers"
|
||||
| "good";
|
||||
|
||||
export type PetReactions = CoStream<ReactionType>;
|
||||
|
||||
/** Walkthrough: Continue with ./2_App.tsx */
|
||||
@@ -1,50 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useJazz } from "jazz-react";
|
||||
|
||||
import { PetPost } from "./1_types";
|
||||
|
||||
import { Button } from "./basicComponents";
|
||||
|
||||
import { useSimpleHashRouterThatAcceptsInvites } from "./router";
|
||||
import { PetPostUI } from "./4_PetPostUI";
|
||||
import { CreatePetPostForm } from "./4_CreatePetPostForm";
|
||||
|
||||
/** Walkthrough: Creating pet posts & routing in `<App/>`
|
||||
*
|
||||
* <App> is the main app component, handling client-side routing based
|
||||
* on the CoValue ID (CoID) of our PetPost, stored in the URL hash
|
||||
* - which can also contain invite links.
|
||||
*/
|
||||
|
||||
export default function App() {
|
||||
// A `LocalNode` represents a local view of loaded & created CoValues.
|
||||
// It is associated with a current user account, which will determine
|
||||
// access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
||||
const { localNode, logOut } = useJazz();
|
||||
|
||||
// This sets up routing and accepting invites, skip for now
|
||||
const [currentPetPostID, navigateToPetPostID] =
|
||||
useSimpleHashRouterThatAcceptsInvites<PetPost>(localNode);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 pb-10 px-5">
|
||||
{currentPetPostID ? (
|
||||
<PetPostUI petPostID={currentPetPostID} />
|
||||
) : (
|
||||
<CreatePetPostForm onCreate={navigateToPetPostID} />
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigateToPetPostID(undefined);
|
||||
logOut();
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: continue with ./3_TodoTable.tsx */
|
||||
124
examples/pets/src/2_main.tsx
Normal file
124
examples/pets/src/2_main.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Link, RouterProvider, createHashRouter } from "react-router-dom";
|
||||
import "./index.css";
|
||||
|
||||
import { createJazzReactContext, PasskeyAuth } from "jazz-react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
ThemeProvider,
|
||||
TitleAndLogo,
|
||||
} from "./basicComponents/index.ts";
|
||||
import { PrettyAuthUI } from "./components/Auth.tsx";
|
||||
import { NewPetPostForm } from "./3_NewPetPostForm.tsx";
|
||||
import { RatePetPostUI } from "./4_RatePetPostUI.tsx";
|
||||
import { PetAccount, PetPost } from "./1_schema.ts";
|
||||
|
||||
/** Walkthrough: The top-level provider `<WithJazz/>`
|
||||
*
|
||||
* This shows how to use the top-level provider `<WithJazz/>`,
|
||||
* which provides the rest of the app with a `LocalNode` (used through `useJazz` later),
|
||||
* based on `LocalAuth` that uses PassKeys (aka WebAuthn) to store a user's account secret
|
||||
* - no backend needed. */
|
||||
|
||||
const appName = "Jazz Rate My Pet Example";
|
||||
|
||||
const auth = PasskeyAuth<PetAccount>({
|
||||
appName,
|
||||
Component: PrettyAuthUI,
|
||||
accountSchema: PetAccount,
|
||||
});
|
||||
|
||||
const Jazz = createJazzReactContext({
|
||||
auth,
|
||||
peer: "wss://mesh.jazz.tools/?key=you@example.com",
|
||||
});
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<TitleAndLogo name={appName} />
|
||||
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 pb-10 px-5">
|
||||
<Jazz.Provider loading={<div>Loading</div>}>
|
||||
<App />
|
||||
</Jazz.Provider>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
/** Walkthrough: Creating pet posts & routing in `<App/>`
|
||||
*
|
||||
* <App> is the main app component, handling client-side routing based
|
||||
* on the CoValue ID (CoID) of our PetPost, stored in the URL hash
|
||||
* - which can also contain invite links.
|
||||
*/
|
||||
|
||||
export default function App() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <PostOverview />,
|
||||
},
|
||||
{
|
||||
path: "/new",
|
||||
element: <NewPetPostForm />,
|
||||
},
|
||||
{
|
||||
path: "/pet/:petPostId",
|
||||
element: <RatePetPostUI />,
|
||||
},
|
||||
{
|
||||
path: "/invite/*",
|
||||
element: <p>Accepting invite...</p>,
|
||||
},
|
||||
]);
|
||||
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: PetPost,
|
||||
onAccept: (petPostID) => router.navigate("/pet/" + petPostID),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
|
||||
<Button
|
||||
onClick={() => router.navigate("/").then(logOut)}
|
||||
variant="outline"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function PostOverview() {
|
||||
const { me } = useAccount();
|
||||
|
||||
const myPosts = me.root?.posts;
|
||||
|
||||
return (
|
||||
<>
|
||||
{myPosts?.length ? (
|
||||
<>
|
||||
<h1>My posts</h1>
|
||||
{myPosts.map(
|
||||
(post) =>
|
||||
post && (
|
||||
<Link key={post.id} to={"/pet/" + post.id}>
|
||||
{post.name}
|
||||
</Link>
|
||||
),
|
||||
)}
|
||||
</>
|
||||
) : undefined}
|
||||
<Link to="/new">New post</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
106
examples/pets/src/3_NewPetPostForm.tsx
Normal file
106
examples/pets/src/3_NewPetPostForm.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { ChangeEvent, useCallback, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
|
||||
import { PetPost, PetReactions } from "./1_schema";
|
||||
import { Input, Button } from "./basicComponents";
|
||||
import { useAccount, useCoState } from "./2_main";
|
||||
import { CoMap, Group, ID, ImageDefinition, co } from "jazz-tools";
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
|
||||
/** Walkthrough: TODO
|
||||
*/
|
||||
|
||||
class PartialPetPost extends CoMap {
|
||||
name = co.string;
|
||||
image = co.ref(ImageDefinition, { optional: true });
|
||||
reactions = co.ref(PetReactions);
|
||||
}
|
||||
|
||||
export function NewPetPostForm() {
|
||||
const { me } = useAccount();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [newPostId, setNewPostId] = useState<ID<PartialPetPost> | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const newPetPost = useCoState(PartialPetPost, newPostId);
|
||||
|
||||
const onChangeName = useCallback(
|
||||
(name: string) => {
|
||||
if (newPetPost) {
|
||||
newPetPost.name = name;
|
||||
} else {
|
||||
const petPostGroup = Group.create({ owner: me });
|
||||
const petPost = PartialPetPost.create(
|
||||
{
|
||||
name,
|
||||
reactions: PetReactions.create([], { owner: me }),
|
||||
},
|
||||
{ owner: petPostGroup },
|
||||
);
|
||||
|
||||
setNewPostId(petPost.id);
|
||||
}
|
||||
},
|
||||
[me, newPetPost],
|
||||
);
|
||||
|
||||
const onImageSelected = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!newPetPost || !event.target.files) return;
|
||||
|
||||
const image = await createImage(event.target.files[0], {
|
||||
owner: newPetPost._owner,
|
||||
});
|
||||
|
||||
newPetPost.image = image;
|
||||
},
|
||||
[newPetPost],
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
if (!newPetPost) return;
|
||||
const myPosts = me.root?.posts;
|
||||
|
||||
if (!myPosts) {
|
||||
throw new Error("No posts list found");
|
||||
}
|
||||
|
||||
myPosts.push(newPetPost as PetPost);
|
||||
|
||||
navigate("/pet/" + newPetPost.id);
|
||||
}, [me.root?.posts, newPetPost, navigate]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-10">
|
||||
<p>Share your pet with friends!</p>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Pet Name"
|
||||
className="text-3xl py-6"
|
||||
onChange={(event) => onChangeName(event.target.value)}
|
||||
value={newPetPost?.name || ""}
|
||||
/>
|
||||
|
||||
{newPetPost?.image ? (
|
||||
<ProgressiveImg image={newPetPost.image}>
|
||||
{({ src }) => (
|
||||
<img className="w-80 max-w-full rounded" src={src} />
|
||||
)}
|
||||
</ProgressiveImg>
|
||||
) : (
|
||||
<Input
|
||||
type="file"
|
||||
disabled={!newPetPost?.name}
|
||||
onChange={onImageSelected}
|
||||
/>
|
||||
)}
|
||||
|
||||
{newPetPost?.name && newPetPost?.image && (
|
||||
<Button onClick={onSubmit}>Submit Post</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { BinaryCoStream, CoID } from "cojson";
|
||||
import {
|
||||
useBinaryStream,
|
||||
useJazz,
|
||||
useTelepathicState,
|
||||
} from "jazz-react";
|
||||
|
||||
import { PetPost, PetReactions, ReactionType } from "./1_types";
|
||||
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
} from "./basicComponents";
|
||||
|
||||
import { InviteButton } from "./components/InviteButton";
|
||||
import { NameBadge } from "./components/NameBadge";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { createBinaryStreamHandler } from "jazz-react";
|
||||
|
||||
/** Walkthrough: TODO
|
||||
*/
|
||||
|
||||
export function CreatePetPostForm({
|
||||
onCreate,
|
||||
}: {
|
||||
onCreate: (id: CoID<PetPost>) => void;
|
||||
}) {
|
||||
const { localNode } = useJazz();
|
||||
|
||||
const [creatingPostId, setCreatingPostId] = useState<
|
||||
CoID<PetPost> | undefined
|
||||
>(undefined);
|
||||
|
||||
const creatingPetPost = useTelepathicState(creatingPostId);
|
||||
|
||||
const onChangeName = useDebouncedCallback((name: string) => {
|
||||
let petPost = creatingPetPost;
|
||||
if (!petPost) {
|
||||
const petPostGroup = localNode.createGroup();
|
||||
petPost = petPostGroup.createMap<PetPost>();
|
||||
const reactions = petPostGroup.createStream<PetReactions>();
|
||||
|
||||
petPost = petPost.edit((petPost) => {
|
||||
petPost.set("reactions", reactions.id);
|
||||
});
|
||||
|
||||
setCreatingPostId(petPost.id);
|
||||
}
|
||||
|
||||
petPost.edit((petPost) => {
|
||||
petPost.set("name", name);
|
||||
});
|
||||
}, 200);
|
||||
|
||||
const onImageCreated = useCallback(
|
||||
(image: BinaryCoStream) => {
|
||||
if (!creatingPetPost) throw new Error("Never get here");
|
||||
creatingPetPost.edit((petPost) => {
|
||||
petPost.set("image", image.id);
|
||||
});
|
||||
},
|
||||
[creatingPetPost]
|
||||
);
|
||||
|
||||
const image = useBinaryStream(creatingPetPost?.get("image"));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Pet Name"
|
||||
onChange={event => onChangeName(event.target.value)}
|
||||
value={creatingPetPost?.get("name")}
|
||||
/>
|
||||
|
||||
{image ? (
|
||||
<img src={image.blobURL} />
|
||||
) : (
|
||||
creatingPetPost && (
|
||||
<Input
|
||||
type="file"
|
||||
onChange={createBinaryStreamHandler(
|
||||
onImageCreated,
|
||||
creatingPetPost.group
|
||||
)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
{creatingPetPost?.get("name") && creatingPetPost?.get("image") && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCreate(creatingPetPost.id);
|
||||
}}
|
||||
>
|
||||
Submit Post
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { CoID } from "cojson";
|
||||
import { useTelepathicState } from "jazz-react";
|
||||
|
||||
import { PetPost } from "./1_types";
|
||||
|
||||
import { InviteButton } from "./components/InviteButton";
|
||||
import { NameBadge } from "./components/NameBadge";
|
||||
|
||||
/** Walkthrough: TODO
|
||||
*/
|
||||
|
||||
export function PetPostUI({ petPostID }: { petPostID: CoID<PetPost> }) {
|
||||
|
||||
|
||||
return (<div>TODO</div>);
|
||||
}
|
||||
120
examples/pets/src/4_RatePetPostUI.tsx
Normal file
120
examples/pets/src/4_RatePetPostUI.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import { PetPost, PetReactions, ReactionTypes } from "./1_schema";
|
||||
|
||||
import { ShareButton } from "./components/ShareButton";
|
||||
import { Button, Skeleton } from "./basicComponents";
|
||||
import uniqolor from "uniqolor";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useCoState } from "./2_main";
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
|
||||
/** Walkthrough: TODO
|
||||
*/
|
||||
|
||||
const reactionEmojiMap: {
|
||||
[reaction in (typeof ReactionTypes)[number]]: string;
|
||||
} = {
|
||||
aww: "😍",
|
||||
love: "❤️",
|
||||
haha: "😂",
|
||||
wow: "😮",
|
||||
tiny: "🐥",
|
||||
chonkers: "🐘",
|
||||
};
|
||||
|
||||
export function RatePetPostUI() {
|
||||
const petPostID = useParams<{ petPostId: ID<PetPost> }>().petPostId;
|
||||
|
||||
const petPost = useCoState(PetPost, petPostID);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex justify-between">
|
||||
<h1 className="text-3xl font-bold">{petPost?.name}</h1>
|
||||
<ShareButton petPost={petPost} />
|
||||
</div>
|
||||
|
||||
<ProgressiveImg image={petPost?.image}>
|
||||
{({ src }) => (
|
||||
<img className="w-80 max-w-full rounded" src={src} />
|
||||
)}
|
||||
</ProgressiveImg>
|
||||
|
||||
<div className="flex justify-between max-w-xs flex-wrap">
|
||||
{ReactionTypes.map((reactionType) => (
|
||||
<Button
|
||||
key={reactionType}
|
||||
variant={
|
||||
petPost?.reactions?.byMe?.value === reactionType
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
onClick={() => {
|
||||
petPost?.reactions?.push(reactionType);
|
||||
}}
|
||||
title={`React with ${reactionType}`}
|
||||
className="text-2xl px-2"
|
||||
>
|
||||
{reactionEmojiMap[reactionType]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{petPost?._owner.myRole() === "admin" && petPost.reactions && (
|
||||
<ReactionOverview petReactions={petPost.reactions} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ReactionOverview({ petReactions }: { petReactions: PetReactions }) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Reactions</h2>
|
||||
<div className="flex flex-col gap-1">
|
||||
{ReactionTypes.map((reactionType) => {
|
||||
const reactionsOfThisType = Object.values(
|
||||
petReactions,
|
||||
).filter((entry) => entry.value === reactionType);
|
||||
|
||||
if (reactionsOfThisType.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex gap-2 items-center"
|
||||
key={reactionType}
|
||||
>
|
||||
{reactionEmojiMap[reactionType]}{" "}
|
||||
{reactionsOfThisType.map((reaction, idx) =>
|
||||
reaction.by?.profile?.name ? (
|
||||
<span
|
||||
className="rounded-full py-0.5 px-2 text-xs"
|
||||
style={uniqueColoring(reaction.by.id)}
|
||||
key={reaction.by.id}
|
||||
>
|
||||
{reaction.by.profile.name}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton
|
||||
className="mt-1 w-[50px] h-[1em] rounded-full"
|
||||
key={idx}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function uniqueColoring(seed: string) {
|
||||
const darkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
return {
|
||||
color: uniqolor(seed, { lightness: darkMode ? 80 : 20 }).color,
|
||||
background: uniqolor(seed, { lightness: darkMode ? 20 : 80 }).color,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
export function TitleAndLogo({name}: {name: string}) {
|
||||
return <>
|
||||
<div className="flex items-center gap-2 justify-center mt-5">
|
||||
import { Toaster } from ".";
|
||||
|
||||
export function TitleAndLogo({ name }: { name: string }) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2 justify-center mt-5">
|
||||
<img src="jazz-logo.png" className="h-5" /> {name}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export { Button } from "./ui/button";
|
||||
export { Input } from "./ui/input";
|
||||
export { Toaster } from "./ui/toaster";
|
||||
export { useToast } from "./ui/use-toast";
|
||||
export { Skeleton } from "./ui/skeleton";
|
||||
export { TitleAndLogo } from "./TitleAndLogo";
|
||||
export { ThemeProvider } from "./themeProvider";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ThemeProvider({
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setTheme] = useState(
|
||||
() => localStorage.getItem(storageKey) || defaultTheme
|
||||
() => localStorage.getItem(storageKey) || defaultTheme,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -35,7 +35,7 @@ export function ThemeProvider({
|
||||
|
||||
if (theme === "system") {
|
||||
const systemTheme = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)"
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches
|
||||
? "dark"
|
||||
: "light";
|
||||
@@ -62,6 +62,7 @@ export function ThemeProvider({
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeProviderContext);
|
||||
|
||||
|
||||
@@ -1,56 +1,58 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants }
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { Button, buttonVariants };
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input }
|
||||
export { Input };
|
||||
|
||||
15
examples/pets/src/basicComponents/ui/skeleton.tsx
Normal file
15
examples/pets/src/basicComponents/ui/skeleton.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton };
|
||||
127
examples/pets/src/basicComponents/ui/toast.tsx
Normal file
127
examples/pets/src/basicComponents/ui/toast.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import * as React from "react";
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider;
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
||||
|
||||
const toastVariants = cva(
|
||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border bg-background text-foreground",
|
||||
destructive:
|
||||
"destructive group border-destructive bg-destructive text-destructive-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
ref={ref}
|
||||
className={cn(toastVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Toast.displayName = ToastPrimitives.Root.displayName;
|
||||
|
||||
const ToastAction = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
||||
|
||||
const ToastClose = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||
className,
|
||||
)}
|
||||
toast-close=""
|
||||
{...props}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</ToastPrimitives.Close>
|
||||
));
|
||||
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
||||
|
||||
const ToastTitle = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
||||
|
||||
const ToastDescription = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
};
|
||||
41
examples/pets/src/basicComponents/ui/toaster.tsx
Normal file
41
examples/pets/src/basicComponents/ui/toaster.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
import { useToast } from "@/basicComponents/ui/use-toast";
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast();
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && (
|
||||
<ToastDescription>
|
||||
{description}
|
||||
</ToastDescription>
|
||||
)}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
);
|
||||
})}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
193
examples/pets/src/basicComponents/ui/use-toast.ts
Normal file
193
examples/pets/src/basicComponents/ui/use-toast.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
|
||||
const TOAST_LIMIT = 1;
|
||||
const TOAST_REMOVE_DELAY = 1000000;
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string;
|
||||
title?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
action?: ToastActionElement;
|
||||
};
|
||||
|
||||
const actionTypes = {
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
} as const;
|
||||
|
||||
let count = 0;
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_VALUE;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes;
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: ActionType["ADD_TOAST"];
|
||||
toast: ToasterToast;
|
||||
}
|
||||
| {
|
||||
type: ActionType["UPDATE_TOAST"];
|
||||
toast: Partial<ToasterToast>;
|
||||
}
|
||||
| {
|
||||
type: ActionType["DISMISS_TOAST"];
|
||||
toastId?: ToasterToast["id"];
|
||||
}
|
||||
| {
|
||||
type: ActionType["REMOVE_TOAST"];
|
||||
toastId?: ToasterToast["id"];
|
||||
};
|
||||
|
||||
interface State {
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId);
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
});
|
||||
}, TOAST_REMOVE_DELAY);
|
||||
|
||||
toastTimeouts.set(toastId, timeout);
|
||||
};
|
||||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "ADD_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||
};
|
||||
|
||||
case "UPDATE_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||
),
|
||||
};
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action;
|
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
// but I'll keep it here for simplicity
|
||||
if (toastId) {
|
||||
addToRemoveQueue(toastId);
|
||||
} else {
|
||||
state.toasts.forEach((toast) => {
|
||||
addToRemoveQueue(toast.id);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t,
|
||||
),
|
||||
};
|
||||
}
|
||||
case "REMOVE_TOAST":
|
||||
if (action.toastId === undefined) {
|
||||
return {
|
||||
...state,
|
||||
toasts: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action);
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState);
|
||||
});
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">;
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
});
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
const [state, setState] = React.useState<State>(memoryState);
|
||||
|
||||
React.useEffect(() => {
|
||||
listeners.push(setState);
|
||||
return () => {
|
||||
const index = listeners.indexOf(setState);
|
||||
if (index > -1) {
|
||||
listeners.splice(index, 1);
|
||||
}
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) =>
|
||||
dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast };
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { LocalAuthComponent } from "jazz-react-auth-local";
|
||||
import { PasskeyAuth } from "jazz-react";
|
||||
|
||||
import { Input, Button } from "../basicComponents";
|
||||
|
||||
export const PrettyAuthUI: LocalAuthComponent = ({
|
||||
export const PrettyAuthUI: PasskeyAuth.Component = ({
|
||||
loading,
|
||||
logIn,
|
||||
signUp,
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { AccountID } from "cojson";
|
||||
import { useProfile } from "jazz-react";
|
||||
|
||||
import { Skeleton } from "@/basicComponents";
|
||||
import uniqolor from "uniqolor";
|
||||
|
||||
/** Walkthrough: Getting user profiles in `<NameBadge/>`
|
||||
*
|
||||
* `<NameBadge/>` uses `useProfile(accountID)`, which is a shorthand for
|
||||
* useTelepathicState on an account's profile.
|
||||
*
|
||||
* Profiles are always a `CoMap<{name: string}>`, but they might have app-specific
|
||||
* additional properties).
|
||||
*
|
||||
* In our case, we just display the profile name (which is set by the LocalAuth
|
||||
* provider when we first create an account).
|
||||
*/
|
||||
|
||||
export function NameBadge({ accountID }: { accountID?: AccountID }) {
|
||||
const profile = useProfile(accountID);
|
||||
|
||||
return accountID && profile?.get("name") ? (
|
||||
<span
|
||||
className="rounded-full py-0.5 px-2 text-xs"
|
||||
style={randomUserColor(accountID)}
|
||||
>
|
||||
{profile.get("name")}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton className="mt-1 w-[50px] h-[1em] rounded-full" />
|
||||
);
|
||||
}
|
||||
|
||||
function randomUserColor(accountID: AccountID) {
|
||||
const theme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
|
||||
const brightColor = uniqolor(accountID || "", { lightness: 80 }).color;
|
||||
const darkColor = uniqolor(accountID || "", { lightness: 20 }).color;
|
||||
|
||||
return {
|
||||
color: theme == "light" ? darkColor : brightColor,
|
||||
background: theme == "light" ? brightColor : darkColor,
|
||||
};
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { TodoProject } from "../1_types";
|
||||
import { PetPost } from "../1_schema";
|
||||
|
||||
import { createInviteLink } from "jazz-react";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
import { useToast, Button } from "../basicComponents";
|
||||
|
||||
export function InviteButton({ list }: { list?: TodoProject }) {
|
||||
export function ShareButton({ petPost }: { petPost?: PetPost }) {
|
||||
const [existingInviteLink, setExistingInviteLink] = useState<string>();
|
||||
const { toast } = useToast();
|
||||
|
||||
return (
|
||||
list?.group.myRole() === "admin" && (
|
||||
petPost?._owner.myRole() === "admin" && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="py-0"
|
||||
disabled={!list}
|
||||
disabled={!petPost}
|
||||
variant="outline"
|
||||
onClick={async () => {
|
||||
let inviteLink = existingInviteLink;
|
||||
if (list && !inviteLink) {
|
||||
inviteLink = createInviteLink(list, "writer");
|
||||
if (petPost && !inviteLink) {
|
||||
inviteLink = createInviteLink(petPost, "writer");
|
||||
setExistingInviteLink(inviteLink);
|
||||
}
|
||||
if (inviteLink) {
|
||||
@@ -34,12 +34,12 @@ export function InviteButton({ list }: { list?: TodoProject }) {
|
||||
description: (
|
||||
<img src={qr} className="w-20 h-20" />
|
||||
),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Invite
|
||||
Share
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { CoID, LocalNode, CoValueImpl } from "cojson";
|
||||
import { consumeInviteLinkFromWindowLocation } from "jazz-react";
|
||||
|
||||
export function useSimpleHashRouterThatAcceptsInvites<C extends CoValueImpl>(
|
||||
localNode: LocalNode
|
||||
) {
|
||||
const [currentValueId, setCurrentValueId] = useState<CoID<C>>();
|
||||
|
||||
useEffect(() => {
|
||||
const listener = async () => {
|
||||
const acceptedInvitation = await consumeInviteLinkFromWindowLocation<C>(localNode);
|
||||
|
||||
if (acceptedInvitation) {
|
||||
setCurrentValueId(acceptedInvitation.valueID);
|
||||
window.location.hash = acceptedInvitation.valueID;
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentValueId(
|
||||
(window.location.hash.slice(1) as CoID<C>) || undefined
|
||||
);
|
||||
};
|
||||
window.addEventListener("hashchange", listener);
|
||||
listener();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("hashchange", listener);
|
||||
};
|
||||
}, [localNode]);
|
||||
|
||||
const navigateToValue = useCallback((id: CoID<C> | undefined) => {
|
||||
window.location.hash = id || "";
|
||||
}, []);
|
||||
|
||||
return [currentValueId, navigateToValue] as const;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'prettier'
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
9
examples/todo/.prettierrc.js
Normal file
9
examples/todo/.prettierrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
trailingComma: "all",
|
||||
tabWidth: 4,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
};
|
||||
|
||||
export default config;
|
||||
693
examples/todo/CHANGELOG.md
Normal file
693
examples/todo/CHANGELOG.md
Normal file
@@ -0,0 +1,693 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.97-hotfixes.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.5
|
||||
- jazz-tools@0.7.33-hotfixes.5
|
||||
|
||||
## 0.0.97-hotfixes.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.4
|
||||
- jazz-tools@0.7.33-hotfixes.4
|
||||
|
||||
## 0.0.97-hotfixes.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.3
|
||||
- jazz-tools@0.7.33-hotfixes.3
|
||||
|
||||
## 0.0.97-hotfixes.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.2
|
||||
|
||||
## 0.0.97-hotfixes.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.1
|
||||
|
||||
## 0.0.97-hotfixes.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.33-hotfixes.0
|
||||
- jazz-tools@0.7.33-hotfixes.0
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.32
|
||||
- jazz-react@0.7.32
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.31
|
||||
- jazz-tools@0.7.31
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.30
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.29
|
||||
- jazz-tools@0.7.29
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.28
|
||||
- jazz-tools@0.7.28
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.27
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.26
|
||||
- jazz-tools@0.7.26
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.25
|
||||
- jazz-react@0.7.25
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.24
|
||||
- jazz-react@0.7.24
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.23
|
||||
- jazz-tools@0.7.23
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.22
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.21
|
||||
- jazz-react@0.7.21
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.20
|
||||
- jazz-react@0.7.20
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.19
|
||||
- jazz-react@0.7.19
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.18
|
||||
- jazz-tools@0.7.18
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.17
|
||||
- jazz-tools@0.7.17
|
||||
|
||||
## 0.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.16
|
||||
- jazz-react@0.7.16
|
||||
|
||||
## 0.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.15
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.14
|
||||
- jazz-react@0.7.14
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.13
|
||||
- jazz-react@0.7.13
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.12
|
||||
- jazz-react@0.7.12
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.11
|
||||
- jazz-tools@0.7.11
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.10
|
||||
- jazz-tools@0.7.10
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.9
|
||||
- jazz-tools@0.7.9
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.8
|
||||
- jazz-react@0.7.8
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9fdc91c]
|
||||
- jazz-react@0.7.7
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.6
|
||||
- jazz-react@0.7.6
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.5
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.4
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.3
|
||||
- jazz-react@0.7.3
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.2
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.1
|
||||
- jazz-react@0.7.1
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [96c494f]
|
||||
- Updated dependencies [59c18c3]
|
||||
- Updated dependencies [19f52b7]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [d8fe2b1]
|
||||
- Updated dependencies [19004b4]
|
||||
- Updated dependencies [a78f168]
|
||||
- Updated dependencies [52675c9]
|
||||
- Updated dependencies [129e2c1]
|
||||
- Updated dependencies [6d49e9b]
|
||||
- Updated dependencies [1cfa279]
|
||||
- Updated dependencies [704af7d]
|
||||
- Updated dependencies [e97f730]
|
||||
- Updated dependencies [460478f]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [e299c3e]
|
||||
- Updated dependencies [ed5643a]
|
||||
- Updated dependencies [bde684f]
|
||||
- Updated dependencies [c4151fc]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [01ac646]
|
||||
- Updated dependencies [a5e68a4]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [952982e]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [5fa277c]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [21771c4]
|
||||
- Updated dependencies [77c2b56]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [d2e03ff]
|
||||
- Updated dependencies [354bdcd]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [69ac514]
|
||||
- Updated dependencies [f8a5c46]
|
||||
- Updated dependencies [f0f6f1b]
|
||||
- Updated dependencies [e5eed5b]
|
||||
- Updated dependencies [1a44f87]
|
||||
- Updated dependencies [627d895]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [38d4410]
|
||||
- Updated dependencies [85d2b62]
|
||||
- Updated dependencies [fd86c11]
|
||||
- Updated dependencies [52675c9]
|
||||
- jazz-tools@0.7.0
|
||||
- jazz-react@0.7.0
|
||||
|
||||
## 0.0.64-alpha.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.42
|
||||
- jazz-react@0.7.0-alpha.42
|
||||
|
||||
## 0.0.64-alpha.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-tools@0.7.0-alpha.41
|
||||
- jazz-react@0.7.0-alpha.41
|
||||
|
||||
## 0.0.64-alpha.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.40
|
||||
|
||||
## 0.0.64-alpha.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.0-alpha.39
|
||||
- jazz-tools@0.7.0-alpha.39
|
||||
|
||||
## 0.0.64-alpha.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.38
|
||||
- jazz-react@0.7.0-alpha.38
|
||||
|
||||
## 0.0.64-alpha.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.37
|
||||
- jazz-tools@0.7.0-alpha.37
|
||||
|
||||
## 0.0.64-alpha.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [1a35307]
|
||||
- jazz-tools@0.7.0-alpha.36
|
||||
- jazz-react@0.7.0-alpha.36
|
||||
|
||||
## 0.0.64-alpha.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.35
|
||||
- jazz-react@0.7.0-alpha.35
|
||||
|
||||
## 0.0.64-alpha.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.34
|
||||
- jazz-react@0.7.0-alpha.34
|
||||
|
||||
## 0.0.64-alpha.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.33
|
||||
|
||||
## 0.0.64-alpha.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.32
|
||||
- jazz-react@0.7.0-alpha.32
|
||||
|
||||
## 0.0.64-alpha.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.31
|
||||
- jazz-react@0.7.0-alpha.31
|
||||
|
||||
## 0.0.64-alpha.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.30
|
||||
- jazz-react@0.7.0-alpha.30
|
||||
|
||||
## 0.0.64-alpha.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.29
|
||||
- jazz-react@0.7.0-alpha.29
|
||||
|
||||
## 0.0.64-alpha.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.28
|
||||
- jazz-react@0.7.0-alpha.28
|
||||
|
||||
## 0.0.64-alpha.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.27
|
||||
- jazz-react@0.7.0-alpha.27
|
||||
|
||||
## 0.0.64-alpha.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.26
|
||||
- jazz-react@0.7.0-alpha.26
|
||||
|
||||
## 0.0.64-alpha.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.25
|
||||
- jazz-react@0.7.0-alpha.25
|
||||
|
||||
## 0.0.64-alpha.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.24
|
||||
- jazz-react@0.7.0-alpha.24
|
||||
|
||||
## 0.0.64-alpha.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.23
|
||||
- jazz-react@0.7.0-alpha.23
|
||||
|
||||
## 0.0.64-alpha.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.22
|
||||
- jazz-react@0.7.0-alpha.22
|
||||
|
||||
## 0.0.64-alpha.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.21
|
||||
- jazz-tools@0.7.0-alpha.21
|
||||
|
||||
## 0.0.64-alpha.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.20
|
||||
- jazz-tools@0.7.0-alpha.20
|
||||
|
||||
## 0.0.64-alpha.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.19
|
||||
- jazz-react@0.7.0-alpha.19
|
||||
|
||||
## 0.0.64-alpha.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.0-alpha.18
|
||||
|
||||
## 0.0.64-alpha.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.17
|
||||
- jazz-react@0.7.0-alpha.17
|
||||
|
||||
## 0.0.64-alpha.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.16
|
||||
- jazz-react@0.7.0-alpha.16
|
||||
|
||||
## 0.0.64-alpha.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.15
|
||||
- jazz-react@0.7.0-alpha.15
|
||||
|
||||
## 0.0.64-alpha.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.14
|
||||
- jazz-react@0.7.0-alpha.14
|
||||
|
||||
## 0.0.64-alpha.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.13
|
||||
- jazz-react@0.7.0-alpha.13
|
||||
|
||||
## 0.0.64-alpha.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.12
|
||||
- jazz-tools@0.7.0-alpha.12
|
||||
|
||||
## 0.0.64-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.11
|
||||
- jazz-tools@0.7.0-alpha.11
|
||||
|
||||
## 0.0.64-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.10
|
||||
- jazz-tools@0.7.0-alpha.10
|
||||
|
||||
## 0.0.64-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.9
|
||||
- jazz-tools@0.7.0-alpha.9
|
||||
|
||||
## 0.0.64-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.8
|
||||
- jazz-tools@0.7.0-alpha.8
|
||||
|
||||
## 0.0.64-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.7
|
||||
- jazz-tools@0.7.0-alpha.7
|
||||
|
||||
## 0.0.64-alpha.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.6
|
||||
- jazz-tools@0.7.0-alpha.6
|
||||
|
||||
## 0.0.64-alpha.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.5
|
||||
- jazz-tools@0.7.0-alpha.5
|
||||
|
||||
## 0.0.64-alpha.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.4
|
||||
- jazz-react@0.7.0-alpha.4
|
||||
|
||||
## 0.0.64-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.3
|
||||
- jazz-react@0.7.0-alpha.3
|
||||
|
||||
## 0.0.64-alpha.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.2
|
||||
- jazz-tools@0.7.0-alpha.2
|
||||
|
||||
## 0.0.64-alpha.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.1
|
||||
- jazz-tools@0.7.0-alpha.1
|
||||
|
||||
## 0.0.64-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.0
|
||||
- cojson@0.7.0-alpha.0
|
||||
|
||||
## 0.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.5.0
|
||||
- jazz-react-auth-local@0.4.16
|
||||
@@ -4,52 +4,56 @@ Live version: https://example-todo.jazz.tools
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
Start by checking out just the example app to a folder:
|
||||
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by checking out `jazz`
|
||||
```bash
|
||||
npx degit gardencmp/jazz/examples/todo jazz-example-todo
|
||||
cd jazz-example-todo
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
cd jazz/examples/todo
|
||||
pnpm pack --pack-destination /tmp
|
||||
mkdir -p ~/jazz-examples/todo # or any other directory
|
||||
tar -xf /tmp/jazz-example-todo-* --strip-components 1 -C ~/jazz-examples/todo
|
||||
cd ~/jazz-examples/todo
|
||||
```
|
||||
|
||||
(This ensures that you have the example app without git history or our multi-package monorepo)
|
||||
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
- [`src/basicComponents`](./src/basicComponents) contains simple components to build the UI, unrelated to Jazz (powered by [shadcn/ui](https://ui.shadcn.com))
|
||||
- [`src/components`](./src/components/) contains helper components that do contain Jazz-specific logic, but are not super relevant to understand the basics of Jazz and CoJSON
|
||||
- [`src/0_main.tsx`](./src/0_main.tsx), [`src/1_types.ts`](./src/1_types.ts), [`src/2_App.tsx`](./src/2_App.tsx), [`src/3_TodoTable.tsx`](./src/3_TodoTable.tsx), [`src/router.ts`](./src/router.ts) - the main files for this example, see the walkthrough below
|
||||
- [`src/basicComponents`](./src/basicComponents): simple components to build the UI, unrelated to Jazz (uses [shadcn/ui](https://ui.shadcn.com))
|
||||
- [`src/components`](./src/components/): helper components that do contain Jazz-specific logic, but aren't very relevant to understand the basics of Jazz and CoJSON
|
||||
- [`src/1_schema.ts`](./src/1_schema.ts),
|
||||
[`src/2_main.tsx`](./src/2_main.tsx),
|
||||
[`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx),
|
||||
[`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx): the main files for this example, see the walkthrough below
|
||||
|
||||
## Walkthrough
|
||||
|
||||
### Main parts
|
||||
|
||||
- The top-level provider `<WithJazz/>`: [`src/0_main.tsx`](./src/0_main.tsx)
|
||||
1. Defining the data model with CoJSON: [`src/1_schema.ts`](./src/1_schema.ts)
|
||||
|
||||
- Defining the data model with CoJSON: [`src/1_types.ts`](./src/1_types.ts)
|
||||
2. The top-level provider `<WithJazz/>` and routing: [`src/2_main.tsx`](./src/2_main.tsx)
|
||||
|
||||
- Creating todo projects & routing in `<App/>`: [`src/2_App.tsx`](./src/2_App.tsx)
|
||||
3. Creating a new todo project: [`src/3_NewProjectForm.tsx`](./src/3_NewProjectForm.tsx)
|
||||
|
||||
- Reactively rendering a todo project as a table, adding and editing tasks: [`src/3_TodoTable.tsx`](./src/3_TodoTable.tsx)
|
||||
4. Reactively rendering a todo project as a table, adding and editing tasks: [`src/4_ProjectTodoTable.tsx`](./src/4_ProjectTodoTable.tsx)
|
||||
|
||||
### Helpers
|
||||
|
||||
- Getting user profiles in `<NameBadge/>`: [`src/components/NameBadge.tsx`](./src/components/NameBadge.tsx)
|
||||
|
||||
- (not yet commented) Creating invite links/QR codes with `<InviteButton/>`: [`src/components/InviteButton.tsx`](./src/components/InviteButton.tsx)
|
||||
|
||||
- (not yet commented) `location.hash`-based routing and accepting invite links with `useSimpleHashRouterThatAcceptsInvites()` in [`src/router.ts`](./src/router.ts)
|
||||
- (not yet explained) Creating Invite Links/QR codes with `<InviteButton/>`: [`src/components/InviteButton.tsx`](./src/components/InviteButton.tsx)
|
||||
|
||||
This is the whole Todo List app!
|
||||
|
||||
@@ -62,4 +66,4 @@ If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or
|
||||
|
||||
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<WithJazz>` provider component in [./src/0_main.tsx](./src/0_main.tsx).
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/0_main.tsx"></script>
|
||||
<script type="module" src="/src/2_main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@ job "example-todo$BRANCH_SUFFIX" {
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 8
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user