Compare commits
529 Commits
jazz-react
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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)
|
||||
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": []
|
||||
}
|
||||
31
.github/workflows/build-and-deploy.yaml
vendored
31
.github/workflows/build-and-deploy.yaml
vendored
@@ -7,22 +7,26 @@ on:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["todo", "pets"]
|
||||
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: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: yarn.lock
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
@@ -34,14 +38,10 @@ jobs:
|
||||
username: gardencmp
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Nuke Workspace
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
rm package.json yarn.lock;
|
||||
|
||||
- name: Yarn Build
|
||||
run: |
|
||||
yarn install --frozen-lockfile;
|
||||
yarn build;
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
|
||||
- name: Docker Build & Push
|
||||
@@ -53,12 +53,13 @@ jobs:
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
deploy:
|
||||
deploy-examples:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
needs: build-examples
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["todo", "pets"]
|
||||
example: ["chat", "pets", "todo", "inspector"]
|
||||
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -86,5 +87,5 @@ jobs:
|
||||
|
||||
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;
|
||||
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?
|
||||
625
examples/chat/CHANGELOG.md
Normal file
625
examples/chat/CHANGELOG.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 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://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 `<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.69",
|
||||
"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 |
42
examples/chat/src/app.tsx
Normal file
42
examples/chat/src/app.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
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";
|
||||
|
||||
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(<Jazz.Provider><App/></Jazz.Provider>);
|
||||
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?
|
||||
41
examples/inspector/CHANGELOG.md
Normal file
41
examples/inspector/CHANGELOG.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 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).
|
||||
14
examples/inspector/index.html
Normal file
14
examples/inspector/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/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
|
||||
49
examples/inspector/package.json
Normal file
49
examples/inspector/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "jazz-inspector",
|
||||
"private": true,
|
||||
"version": "0.0.50",
|
||||
"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:*",
|
||||
"effect": "^3.5.2",
|
||||
"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 |
309
examples/inspector/src/app.tsx
Normal file
309
examples/inspector/src/app.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {
|
||||
RawAccount,
|
||||
CoID,
|
||||
RawCoValue,
|
||||
SessionID,
|
||||
LocalNode,
|
||||
AgentSecret,
|
||||
AccountID,
|
||||
cojsonInternals,
|
||||
WasmCrypto,
|
||||
} from "cojson";
|
||||
import { clsx } from "clsx";
|
||||
import { AccountInfo, CoJsonTree, Tag } from "./cojson-tree";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createWebSocketPeer } from "cojson-transport-ws";
|
||||
import { Effect } from "effect";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
||||
function App() {
|
||||
const [accountID, setAccountID] = useState<CoID<RawAccount>>(
|
||||
localStorage["inspectorAccountID"]
|
||||
);
|
||||
const [accountSecret, setAccountSecret] = useState<AgentSecret>(
|
||||
localStorage["inspectorAccountSecret"]
|
||||
);
|
||||
|
||||
const [coValueId, setCoValueId] = useState<CoID<RawCoValue>>(
|
||||
window.location.hash.slice(2) as CoID<RawCoValue>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("hashchange", () => {
|
||||
setCoValueId(window.location.hash.slice(2) as CoID<RawCoValue>);
|
||||
});
|
||||
});
|
||||
|
||||
const [localNode, setLocalNode] = useState<LocalNode>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountID || !accountSecret) return;
|
||||
WasmCrypto.create().then(async (crypto) => {
|
||||
const wsPeer = await Effect.runPromise(
|
||||
createWebSocketPeer({
|
||||
id: "mesh",
|
||||
websocket: new WebSocket("wss://mesh.jazz.tools"),
|
||||
role: "server",
|
||||
})
|
||||
);
|
||||
const node = await LocalNode.withLoadedAccount({
|
||||
accountID: accountID,
|
||||
accountSecret: accountSecret,
|
||||
sessionID: cojsonInternals.newRandomSessionID(accountID),
|
||||
peersToLoadFrom: [wsPeer],
|
||||
crypto,
|
||||
migration: async () => {
|
||||
console.log("Not running any migration in inspector");
|
||||
},
|
||||
});
|
||||
setLocalNode(node);
|
||||
});
|
||||
}, [accountID, accountSecret]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-screen h-screen p-2 gap-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
Account
|
||||
<input
|
||||
className="border p-2 rounded"
|
||||
placeholder="Account ID"
|
||||
value={accountID}
|
||||
onChange={(e) => {
|
||||
setAccountID(e.target.value as AccountID);
|
||||
localStorage["inspectorAccountID"] = e.target.value;
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
className="border p-2 rounded"
|
||||
placeholder="Account Secret"
|
||||
value={accountSecret}
|
||||
onChange={(e) => {
|
||||
setAccountSecret(e.target.value as AgentSecret);
|
||||
localStorage["inspectorAccountSecret"] = e.target.value;
|
||||
}}
|
||||
/>
|
||||
{localNode ? (
|
||||
<AccountInfo accountID={accountID} node={localNode} />
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
CoValue ID
|
||||
<input
|
||||
className="border p-2 rounded min-w-[20rem]"
|
||||
placeholder="CoValue ID"
|
||||
value={coValueId}
|
||||
onChange={(e) =>
|
||||
setCoValueId(e.target.value as CoID<RawCoValue>)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{coValueId && localNode ? (
|
||||
<Inspect coValueId={coValueId} node={localNode} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// function ImageCoValue({ value }: { value: ImageDefinition["_shape"] }) {
|
||||
// const keys = Object.keys(value);
|
||||
// const keyIncludingRes = keys.find((key) => key.includes("x"));
|
||||
// const idToResolve = keyIncludingRes
|
||||
// ? value[keyIncludingRes as `${number}x${number}`]
|
||||
// : null;
|
||||
|
||||
// if (!idToResolve) return <div>Can't find image</div>;
|
||||
|
||||
// const [blobURL, setBlobURL] = useState<string>();
|
||||
|
||||
// useEffect(() => {
|
||||
|
||||
// })
|
||||
|
||||
// return (
|
||||
// <img
|
||||
// src={image?.blobURL || value.placeholderDataURL}
|
||||
// alt="placeholder"
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
function Inspect({
|
||||
coValueId,
|
||||
node,
|
||||
}: {
|
||||
coValueId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const [coValue, setCoValue] = useState<RawCoValue | "unavailable">();
|
||||
|
||||
useEffect(() => {
|
||||
return node.subscribe(coValueId, (coValue) => {
|
||||
setCoValue(coValue);
|
||||
});
|
||||
}, [node, coValueId]);
|
||||
|
||||
if (coValue === "unavailable") {
|
||||
return <div>Unavailable</div>;
|
||||
}
|
||||
|
||||
const values = coValue?.toJSON() || {};
|
||||
const isImage =
|
||||
typeof values === "object" && "placeholderDataURL" in values;
|
||||
const isGroup = coValue?.core.header.ruleset?.type === "group";
|
||||
|
||||
const entires = Object.entries(values as any) as [string, string][];
|
||||
const onlyCoValues = entires.filter(([key]) => key.startsWith("co_"));
|
||||
|
||||
let title = "";
|
||||
if (isImage) {
|
||||
title = "Image";
|
||||
} else if (isGroup) {
|
||||
title = "Group";
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-auto">
|
||||
<h1 className="text-xl font-bold mb-2">
|
||||
Inspecting {title}{" "}
|
||||
<span className="text-gray-500 text-sm">{coValueId}</span>
|
||||
</h1>
|
||||
|
||||
{isGroup ? (
|
||||
<p>
|
||||
{onlyCoValues.length > 0 ? <h3>Permissions</h3> : ""}
|
||||
<div className="flex gap-2 flex-col">
|
||||
{onlyCoValues?.map(([key, value]) => (
|
||||
<div className="flex gap-1 items-center">
|
||||
<span className="bg-gray-200 text-xs px-2 py-0.5 rounded">
|
||||
{value}
|
||||
</span>
|
||||
<AccountInfo
|
||||
accountID={key as CoID<RawAccount>}
|
||||
node={node}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</p>
|
||||
) : (
|
||||
<span className="">
|
||||
Group{" "}
|
||||
<Tag href={`#/${coValue?.group.id}`}>
|
||||
{coValue?.group.id}
|
||||
</Tag>
|
||||
</span>
|
||||
)}
|
||||
{/* {isImage ? (
|
||||
<div className="my-2">
|
||||
<ImageCoValue value={values as any} />
|
||||
</div>
|
||||
) : null} */}
|
||||
<pre className="max-w-[80vw] overflow-scroll text-sm mt-4">
|
||||
<CoJsonTree coValueId={coValueId} node={node} />
|
||||
</pre>
|
||||
<h2 className="text-lg font-semibold mt-10 mb-4">Sessions</h2>
|
||||
{coValue && <Sessions coValue={coValue} node={node} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Sessions({ coValue, node }: { coValue: RawCoValue; node: LocalNode }) {
|
||||
const validTx = coValue.core.getValidSortedTransactions();
|
||||
return (
|
||||
<div className="max-w-[80vw] border rounded">
|
||||
{[...coValue.core.sessionLogs.entries()].map(
|
||||
([sessionID, session]) => (
|
||||
<div
|
||||
key={sessionID}
|
||||
className="mv-10 flex gap-2 border-b p-5 flex-wrap flex-col"
|
||||
>
|
||||
<div className="flex gap-2 flex-row">
|
||||
<SessionInfo
|
||||
sessionID={sessionID}
|
||||
transactionCount={session.transactions.length}
|
||||
node={node}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-1 flex-wrap max-h-64 overflow-y-auto p-1 bg-gray-50 rounded">
|
||||
{session.transactions.map((tx, txIdx) => {
|
||||
const correspondingValidTx = validTx.find(
|
||||
(validTx) =>
|
||||
validTx.txID.sessionID === sessionID &&
|
||||
validTx.txID.txIndex == txIdx
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={txIdx}
|
||||
className={clsx(
|
||||
"text-xs flex-1 p-2 border rounded min-w-36 max-w-40 overflow-scroll bg-white",
|
||||
!correspondingValidTx &&
|
||||
"bg-red-50 border-red-100"
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
{new Date(
|
||||
tx.madeAt
|
||||
).toLocaleString()}
|
||||
</div>
|
||||
<div>{tx.privacy}</div>
|
||||
<pre>
|
||||
{correspondingValidTx
|
||||
? JSON.stringify(
|
||||
correspondingValidTx.changes,
|
||||
undefined,
|
||||
2
|
||||
)
|
||||
: "invalid/undecryptable"}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{session.lastHash} / {session.lastSignature}{" "}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SessionInfo({
|
||||
sessionID,
|
||||
transactionCount,
|
||||
node,
|
||||
}: {
|
||||
sessionID: SessionID;
|
||||
transactionCount: number;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
let Prefix = sessionID.startsWith("co_") ? (
|
||||
<AccountInfo
|
||||
accountID={sessionID.split("_session_")[0] as CoID<RawAccount>}
|
||||
node={node}
|
||||
/>
|
||||
) : (
|
||||
<pre className="text-xs">{sessionID.split("_session_")[0]}</pre>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Prefix}
|
||||
<div>
|
||||
<span className="text-xs">
|
||||
Session {sessionID.split("_session_")[1]}
|
||||
</span>
|
||||
<span className="text-xs text-gray-600 font-medium">
|
||||
{" "}
|
||||
- {transactionCount} txs
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
249
examples/inspector/src/cojson-tree.tsx
Normal file
249
examples/inspector/src/cojson-tree.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
import clsx from "clsx";
|
||||
import { AccountID, CoID, LocalNode, RawAccount, RawCoMap, RawCoValue } from "cojson";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LinkIcon } from "./link-icon";
|
||||
|
||||
export function CoJsonTree({
|
||||
coValueId,
|
||||
node,
|
||||
}: {
|
||||
coValueId: CoID<RawCoValue>;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const [coValue, setCoValue] = useState<RawCoValue | "unavailable">();
|
||||
|
||||
useEffect(() => {
|
||||
return node.subscribe(coValueId, (value) => {
|
||||
setCoValue(value);
|
||||
});
|
||||
});
|
||||
|
||||
if (coValue === "unavailable") {
|
||||
return <div className="text-red-500">Unavailable</div>;
|
||||
}
|
||||
|
||||
const values = coValue?.toJSON() || {};
|
||||
|
||||
return <RenderCoValueJSON json={values} node={node} />;
|
||||
}
|
||||
|
||||
function RenderObject({
|
||||
json,
|
||||
node,
|
||||
}: {
|
||||
json: Record<string, any>;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const [limit, setLimit] = useState(10);
|
||||
const hasMore = Object.keys(json).length > limit;
|
||||
|
||||
const entries = Object.entries(json).slice(0, limit);
|
||||
return (
|
||||
<div className="flex gap-x-1 flex-col font-mono text-xs overflow-auto">
|
||||
{"{"}
|
||||
{entries.map(([key, value]) => {
|
||||
return (
|
||||
<RenderObjectValue
|
||||
property={key}
|
||||
value={value}
|
||||
node={node}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{hasMore ? (
|
||||
<div
|
||||
className="text-gray-500 cursor-pointer"
|
||||
onClick={() => setLimit((l) => l + 10)}
|
||||
>
|
||||
... {Object.keys(json).length - limit} more
|
||||
</div>
|
||||
) : null}
|
||||
{"}"}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderObjectValue({
|
||||
property,
|
||||
value,
|
||||
node,
|
||||
}: {
|
||||
property: string;
|
||||
value: any;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
const [shouldLoad, setShouldLoad] = useState(false);
|
||||
|
||||
const isCoValue =
|
||||
typeof value === "string" ? value?.startsWith("co_") : false;
|
||||
|
||||
return (
|
||||
<div className={clsx(`flex group`)}>
|
||||
<div className="text-gray-500 flex items-start">
|
||||
<div className="flex items-center">
|
||||
<RenderCoValueJSON json={property} node={node} />:{" "}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCoValue ? (
|
||||
<div className={clsx(shouldLoad && "pb-2")}>
|
||||
<div className="flex items-center ">
|
||||
<div onClick={() => setShouldLoad((s) => !s)}>
|
||||
<div className="w-8 text-center text-gray-700 font-mono px-1 text-xs rounded hover:bg-gray-300 cursor-pointer">
|
||||
{shouldLoad ? `-` : `...`}
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={`#/${value}`}
|
||||
className="ml-2 group-hover:block hidden"
|
||||
>
|
||||
<LinkIcon />
|
||||
</a>
|
||||
</div>
|
||||
<span>
|
||||
{shouldLoad ? (
|
||||
<CoJsonTree coValueId={value} node={node} />
|
||||
) : null}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="">
|
||||
<RenderCoValueJSON json={value} node={node} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderCoValueArray({ json, node }: { json: any[]; node: LocalNode }) {
|
||||
const [limit, setLimit] = useState(10);
|
||||
const hasMore = json.length > limit;
|
||||
|
||||
const entries = json.slice(0, limit);
|
||||
return (
|
||||
<div className="flex gap-x-1 flex-col font-mono text-xs overflow-auto">
|
||||
{entries.map((value, idx) => {
|
||||
return (
|
||||
<div key={idx} className="flex gap-x-1">
|
||||
<RenderCoValueJSON json={value} node={node} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{hasMore ? (
|
||||
<div
|
||||
className="text-gray-500 cursor-pointer"
|
||||
onClick={() => setLimit((l) => l + 10)}
|
||||
>
|
||||
... {json.length - limit} more
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RenderCoValueJSON({
|
||||
json,
|
||||
node,
|
||||
}: {
|
||||
json:
|
||||
| Record<string, any>
|
||||
| any[]
|
||||
| string
|
||||
| null
|
||||
| number
|
||||
| boolean
|
||||
| undefined;
|
||||
node: LocalNode;
|
||||
}) {
|
||||
if (typeof json === "undefined") {
|
||||
return <>"undefined"</>;
|
||||
} else if (Array.isArray(json)) {
|
||||
return (
|
||||
<div className="">
|
||||
<span className="text-gray-500">[</span>
|
||||
<div className="ml-2">
|
||||
<RenderCoValueArray json={json} node={node} />
|
||||
</div>
|
||||
<span className="text-gray-500">]</span>
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
typeof json === "object" &&
|
||||
json &&
|
||||
Object.getPrototypeOf(json) === Object.prototype
|
||||
) {
|
||||
return <RenderObject json={json} node={node} />;
|
||||
} else if (typeof json === "string") {
|
||||
if (json?.startsWith("co_")) {
|
||||
if (json.includes("_session_")) {
|
||||
return (
|
||||
<>
|
||||
<AccountInfo accountID={json.split("_session_")[0] as AccountID} node={node}/>{" "}
|
||||
(sess {json.split("_session_")[1]})
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<a className="underline" href={`#/${json}`}>
|
||||
{'"'}
|
||||
{json}
|
||||
{'"'}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return <div className="truncate max-w-64 ml-1">{json}</div>;
|
||||
}
|
||||
} else {
|
||||
return <div className="truncate max-w-64">{JSON.stringify(json)}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export function AccountInfo({ accountID, node }: { accountID: CoID<RawAccount>, node: LocalNode }) {
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const account = await node.load(accountID);
|
||||
if (account === "unavailable") return;
|
||||
const profileID = account?.get("profile");
|
||||
if (profileID === undefined) return;
|
||||
const profile = await node.load(profileID as CoID<RawCoMap>);
|
||||
if (profile === "unavailable") return;
|
||||
setName(profile?.get("name") as string);
|
||||
})()
|
||||
}, [accountID, node]);
|
||||
|
||||
return name ? (
|
||||
<Tag href={`#/${accountID}`} title={accountID}><h1>{name}</h1></Tag>
|
||||
) : (
|
||||
<Tag href={`#/${accountID}`}>{accountID}</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
export function Tag({
|
||||
children,
|
||||
href,
|
||||
title
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
href?: string;
|
||||
title?: string;
|
||||
}) {
|
||||
if (href) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className="border text-xs px-2 py-0.5 rounded hover:underline"
|
||||
title={title}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="border text-xs px-2 py-0.5 rounded">{children}</span>
|
||||
);
|
||||
}
|
||||
78
examples/inspector/src/index.css
Normal file
78
examples/inspector/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;
|
||||
}
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
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;
|
||||
648
examples/pets/CHANGELOG.md
Normal file
648
examples/pets/CHANGELOG.md
Normal file
@@ -0,0 +1,648 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 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
|
||||
@@ -4,41 +4,32 @@ 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/pets jazz-example-pets
|
||||
cd jazz-example-pets
|
||||
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
|
||||
|
||||
TODO
|
||||
|
||||
## Walkthrough
|
||||
|
||||
### Main parts
|
||||
|
||||
TODO
|
||||
|
||||
### Helpers
|
||||
|
||||
TODO
|
||||
|
||||
## 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.
|
||||
@@ -48,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>
|
||||
|
||||
@@ -3,7 +3,7 @@ job "example-pets$BRANCH_SUFFIX" {
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 8
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.87",
|
||||
"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,13 +21,15 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "^0.2.4",
|
||||
"jazz-react-auth-local": "^0.2.4",
|
||||
"jazz-react-media-images": "^0.2.4",
|
||||
"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"
|
||||
|
||||
@@ -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,29 +0,0 @@
|
||||
import { CoMap, CoID, CoStream, Media } 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<Media.ImageDefinition>;
|
||||
reactions: CoID<PetReactions>;
|
||||
}>;
|
||||
|
||||
export const REACTION_TYPES = [
|
||||
"aww",
|
||||
"love",
|
||||
"haha",
|
||||
"wow",
|
||||
"tiny",
|
||||
"chonkers",
|
||||
] as const;
|
||||
|
||||
export type ReactionType = (typeof REACTION_TYPES)[number];
|
||||
|
||||
export type PetReactions = CoStream<ReactionType>;
|
||||
|
||||
/** Walkthrough: Continue with ./2_App.tsx */
|
||||
@@ -1,48 +0,0 @@
|
||||
import { useJazz } from "jazz-react";
|
||||
|
||||
import { PetPost } from "./1_types";
|
||||
|
||||
import { Button } from "./basicComponents";
|
||||
|
||||
import { useSimpleHashRouterThatAcceptsInvites } from "./router";
|
||||
import { RatePetPostUI } from "./4_RatePetPostUI";
|
||||
import { CreatePetPostForm } from "./3_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 ? (
|
||||
<RatePetPostUI petPostID={currentPetPostID} />
|
||||
) : (
|
||||
<CreatePetPostForm onCreate={navigateToPetPostID} />
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigateToPetPostID(undefined);
|
||||
logOut();
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: continue with ./3_CreatePetPostForm.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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import { ChangeEvent, useCallback, useState } from "react";
|
||||
|
||||
import { CoID } from "cojson";
|
||||
import { useJazz, useTelepathicState } from "jazz-react";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
|
||||
import { PetPost, PetReactions } from "./1_types";
|
||||
|
||||
import { Input, Button } from "./basicComponents";
|
||||
import { useLoadImage } from "jazz-react-media-images";
|
||||
|
||||
/** Walkthrough: TODO
|
||||
*/
|
||||
|
||||
export function CreatePetPostForm({
|
||||
onCreate,
|
||||
}: {
|
||||
onCreate: (id: CoID<PetPost>) => void;
|
||||
}) {
|
||||
const { localNode } = useJazz();
|
||||
|
||||
const [newPostId, setNewPostId] = useState<CoID<PetPost> | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const newPetPost = useTelepathicState(newPostId);
|
||||
|
||||
const onChangeName = useCallback(
|
||||
(name: string) => {
|
||||
let petPost = newPetPost;
|
||||
if (!petPost) {
|
||||
const petPostGroup = localNode.createGroup();
|
||||
petPost = petPostGroup.createMap<PetPost>();
|
||||
const petReactions = petPostGroup.createStream<PetReactions>();
|
||||
|
||||
petPost = petPost.edit((petPost) => {
|
||||
petPost.set("reactions", petReactions.id);
|
||||
});
|
||||
|
||||
setNewPostId(petPost.id);
|
||||
}
|
||||
|
||||
petPost.edit((petPost) => {
|
||||
petPost.set("name", name);
|
||||
});
|
||||
},
|
||||
[localNode, newPetPost]
|
||||
);
|
||||
|
||||
const onImageSelected = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!newPetPost || !event.target.files) return;
|
||||
|
||||
const imageDefinition = await createImage(
|
||||
event.target.files[0],
|
||||
newPetPost.group
|
||||
);
|
||||
|
||||
newPetPost.edit((petPost) => {
|
||||
petPost.set("image", imageDefinition.id);
|
||||
});
|
||||
},
|
||||
[newPetPost]
|
||||
);
|
||||
|
||||
const petImage = useLoadImage(newPetPost?.get("image"));
|
||||
|
||||
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?.get("name") || ""}
|
||||
/>
|
||||
|
||||
{petImage ? (
|
||||
<img
|
||||
className="w-80 max-w-full rounded"
|
||||
src={petImage.highestResSrc || petImage.placeholderDataURL}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
type="file"
|
||||
disabled={!newPetPost?.get("name")}
|
||||
onChange={onImageSelected}
|
||||
/>
|
||||
)}
|
||||
|
||||
{newPetPost?.get("name") && newPetPost?.get("image") && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onCreate(newPetPost.id);
|
||||
}}
|
||||
>
|
||||
Submit Post
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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,17 +1,20 @@
|
||||
import { AccountID, CoID } from "cojson";
|
||||
import { useTelepathicState } from "jazz-react";
|
||||
import { useParams } from "react-router";
|
||||
|
||||
import { PetPost, PetReactions, ReactionType, REACTION_TYPES } from "./1_types";
|
||||
import { PetPost, PetReactions, ReactionTypes } from "./1_schema";
|
||||
|
||||
import { ShareButton } from "./components/ShareButton";
|
||||
import { NameBadge } from "./components/NameBadge";
|
||||
import { Button } from "./basicComponents";
|
||||
import { useLoadImage } from "jazz-react-media-images";
|
||||
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 ReactionType]: string } = {
|
||||
const reactionEmojiMap: {
|
||||
[reaction in (typeof ReactionTypes)[number]]: string;
|
||||
} = {
|
||||
aww: "😍",
|
||||
love: "❤️",
|
||||
haha: "😂",
|
||||
@@ -20,38 +23,35 @@ const reactionEmojiMap: { [reaction in ReactionType]: string } = {
|
||||
chonkers: "🐘",
|
||||
};
|
||||
|
||||
export function RatePetPostUI({ petPostID }: { petPostID: CoID<PetPost> }) {
|
||||
const petPost = useTelepathicState(petPostID);
|
||||
const petReactions = useTelepathicState(petPost?.get("reactions"));
|
||||
const petImage = useLoadImage(petPost?.get("image"));
|
||||
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?.get("name")}</h1>
|
||||
<h1 className="text-3xl font-bold">{petPost?.name}</h1>
|
||||
<ShareButton petPost={petPost} />
|
||||
</div>
|
||||
|
||||
{petImage && (
|
||||
<img
|
||||
className="w-80 max-w-full rounded"
|
||||
src={petImage.highestResSrc || petImage.placeholderDataURL}
|
||||
/>
|
||||
)}
|
||||
<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">
|
||||
{REACTION_TYPES.map((reactionType) => (
|
||||
{ReactionTypes.map((reactionType) => (
|
||||
<Button
|
||||
key={reactionType}
|
||||
variant={
|
||||
petReactions?.getLastItemFromMe() === reactionType
|
||||
petPost?.reactions?.byMe?.value === reactionType
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
onClick={() => {
|
||||
petReactions?.edit((reactions) => {
|
||||
reactions.push(reactionType);
|
||||
});
|
||||
petPost?.reactions?.push(reactionType);
|
||||
}}
|
||||
title={`React with ${reactionType}`}
|
||||
className="text-2xl px-2"
|
||||
@@ -61,8 +61,8 @@ export function RatePetPostUI({ petPostID }: { petPostID: CoID<PetPost> }) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{petPost?.group.myRole() === "admin" && petReactions && (
|
||||
<ReactionOverview petReactions={petReactions} />
|
||||
{petPost?._owner.myRole() === "admin" && petPost.reactions && (
|
||||
<ReactionOverview petReactions={petPost.reactions} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -73,14 +73,12 @@ function ReactionOverview({ petReactions }: { petReactions: PetReactions }) {
|
||||
<div>
|
||||
<h2>Reactions</h2>
|
||||
<div className="flex flex-col gap-1">
|
||||
{REACTION_TYPES.map((reactionType) => {
|
||||
const accountsWithThisReaction = Object.entries(
|
||||
petReactions.getLastItemsPerAccount()
|
||||
).flatMap(([accountID, reaction]) =>
|
||||
reaction === reactionType ? [accountID] : []
|
||||
);
|
||||
{ReactionTypes.map((reactionType) => {
|
||||
const reactionsOfThisType = Object.values(
|
||||
petReactions,
|
||||
).filter((entry) => entry.value === reactionType);
|
||||
|
||||
if (accountsWithThisReaction.length === 0) return null;
|
||||
if (reactionsOfThisType.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -88,12 +86,22 @@ function ReactionOverview({ petReactions }: { petReactions: PetReactions }) {
|
||||
key={reactionType}
|
||||
>
|
||||
{reactionEmojiMap[reactionType]}{" "}
|
||||
{accountsWithThisReaction.map((accountID) => (
|
||||
<NameBadge
|
||||
key={accountID}
|
||||
accountID={accountID as AccountID}
|
||||
/>
|
||||
))}
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
@@ -101,3 +109,12 @@ function ReactionOverview({ petReactions }: { petReactions: PetReactions }) {
|
||||
</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,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 };
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
export { Skeleton };
|
||||
|
||||
@@ -1,127 +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 * 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"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
const ToastProvider = ToastPrimitives.Provider;
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||
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
|
||||
<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",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
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
|
||||
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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
}
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
};
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/basicComponents/ui/toast"
|
||||
import { useToast } from "@/basicComponents/ui/use-toast"
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
import { useToast } from "@/basicComponents/ui/use-toast";
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
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>
|
||||
)
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,192 +1,193 @@
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/basicComponents/ui/toast"
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
const TOAST_LIMIT = 1;
|
||||
const TOAST_REMOVE_DELAY = 1000000;
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
action?: ToastActionElement
|
||||
}
|
||||
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
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
} as const;
|
||||
|
||||
let count = 0
|
||||
let count = 0;
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_VALUE
|
||||
return count.toString()
|
||||
count = (count + 1) % Number.MAX_VALUE;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes
|
||||
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"]
|
||||
}
|
||||
| {
|
||||
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[]
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return
|
||||
}
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
})
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId);
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
});
|
||||
}, TOAST_REMOVE_DELAY);
|
||||
|
||||
toastTimeouts.set(toastId, timeout)
|
||||
}
|
||||
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),
|
||||
}
|
||||
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 "UPDATE_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||
),
|
||||
};
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action
|
||||
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)
|
||||
})
|
||||
}
|
||||
// ! 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.map((t) =>
|
||||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
}
|
||||
}
|
||||
}
|
||||
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> = []
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
|
||||
let memoryState: State = { toasts: [] }
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action)
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState)
|
||||
})
|
||||
memoryState = reducer(memoryState, action);
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState);
|
||||
});
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
type Toast = Omit<ToasterToast, "id">;
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId()
|
||||
const id = genId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
});
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
const [state, setState] = React.useState<State>(memoryState)
|
||||
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])
|
||||
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 }),
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) =>
|
||||
dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast }
|
||||
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,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { PetPost } from "../1_types";
|
||||
import { PetPost } from "../1_schema";
|
||||
|
||||
import { createInviteLink } from "jazz-react";
|
||||
import QRCode from "qrcode";
|
||||
@@ -12,7 +12,7 @@ export function ShareButton({ petPost }: { petPost?: PetPost }) {
|
||||
const { toast } = useToast();
|
||||
|
||||
return (
|
||||
petPost?.group.myRole() === "admin" && (
|
||||
petPost?._owner.myRole() === "admin" && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="py-0"
|
||||
@@ -34,7 +34,7 @@ export function ShareButton({ petPost }: { petPost?: PetPost }) {
|
||||
description: (
|
||||
<img src={qr} className="w-20 h-20" />
|
||||
),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
580
examples/todo/CHANGELOG.md
Normal file
580
examples/todo/CHANGELOG.md
Normal file
@@ -0,0 +1,580 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 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" {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.33",
|
||||
"version": "0.0.86",
|
||||
"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,12 +21,14 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "^0.2.4",
|
||||
"jazz-react-auth-local": "^0.2.4",
|
||||
"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"
|
||||
@@ -34,6 +41,7 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.27",
|
||||
|
||||
@@ -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 Todo List 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 */
|
||||
55
examples/todo/src/1_schema.ts
Normal file
55
examples/todo/src/1_schema.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Account, CoList, CoMap, Profile, co } from "jazz-tools";
|
||||
|
||||
/** Walkthrough: Defining the data model with CoJSON
|
||||
*
|
||||
* Here, we define our main data model of tasks, lists of tasks and projects
|
||||
* using CoJSON's collaborative map and list types, CoMap & CoList.
|
||||
*
|
||||
* CoMap values and CoLists items can contain:
|
||||
* - arbitrary immutable JSON
|
||||
* - other CoValues
|
||||
**/
|
||||
|
||||
/** An individual task which collaborators can tick or rename */
|
||||
export class Task extends CoMap {
|
||||
done = co.boolean;
|
||||
text = co.string;
|
||||
}
|
||||
|
||||
export class ListOfTasks extends CoList.Of(co.ref(Task)) {}
|
||||
|
||||
/** Our top level object: a project with a title, referencing a list of tasks */
|
||||
export class TodoProject extends CoMap {
|
||||
title = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
}
|
||||
|
||||
export class ListOfProjects extends CoList.Of(co.ref(TodoProject)) {}
|
||||
|
||||
/** The account root is an app-specific per-user private `CoMap`
|
||||
* where you can store top-level objects for that user */
|
||||
export class TodoAccountRoot extends CoMap {
|
||||
projects = co.ref(ListOfProjects);
|
||||
}
|
||||
|
||||
export class TodoAccount extends Account {
|
||||
profile = co.ref(Profile);
|
||||
root = co.ref(TodoAccountRoot);
|
||||
|
||||
/** The account migration is run on account creation and on every log-in.
|
||||
* You can use it to set up the account root and any other initial CoValues you need.
|
||||
*/
|
||||
migrate(this: TodoAccount, creationProps?: { name: string }) {
|
||||
super.migrate(creationProps);
|
||||
if (!this._refs.root) {
|
||||
this.root = TodoAccountRoot.create(
|
||||
{
|
||||
projects: ListOfProjects.create([], { owner: this }),
|
||||
},
|
||||
{ owner: this },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Walkthrough: Continue with ./2_main.tsx */
|
||||
@@ -1,28 +0,0 @@
|
||||
import { CoMap, CoList, CoID } from "cojson";
|
||||
|
||||
/** Walkthrough: Defining the data model with CoJSON
|
||||
*
|
||||
* Here, we define our main data model of tasks, lists of tasks and projects
|
||||
* using CoJSON's collaborative map and list types, CoMap & CoList.
|
||||
*
|
||||
* CoMap values and CoLists items can be:
|
||||
* - arbitrary immutable JSON
|
||||
* - references to other CoValues by their CoID
|
||||
* - CoIDs are strings that look like `co_zXPuWmH1D1cKdMpDW6CMzWb3LpY`
|
||||
* - In TypeScript, CoIDs take a generic parameter for the type of the
|
||||
* referenced CoValue, e.g. `CoID<Task>` - to make the references precise
|
||||
**/
|
||||
|
||||
/** An individual task which collaborators can tick or rename */
|
||||
export type Task = CoMap<{ done: boolean; text: string; }>;
|
||||
|
||||
/** A collaborative, ordered list of task references */
|
||||
export type ListOfTasks = CoList<CoID<Task>>;
|
||||
|
||||
/** Our top level object: a project with a title, referencing a list of tasks */
|
||||
export type TodoProject = CoMap<{
|
||||
title: string;
|
||||
tasks: CoID<ListOfTasks>;
|
||||
}>;
|
||||
|
||||
/** Walkthrough: Continue with ./2_App.tsx */
|
||||
@@ -1,78 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useJazz } from "jazz-react";
|
||||
|
||||
import { TodoProject, ListOfTasks } from "./1_types";
|
||||
|
||||
import { SubmittableInput, Button } from "./basicComponents";
|
||||
|
||||
import { useSimpleHashRouterThatAcceptsInvites } from "./router";
|
||||
import { TodoTable } from "./3_TodoTable";
|
||||
|
||||
/** Walkthrough: Creating todo projects & routing in `<App/>`
|
||||
*
|
||||
* <App> is the main app component, handling client-side routing based
|
||||
* on the CoValue ID (CoID) of our TodoProject, 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 [currentProjectId, navigateToProjectId] =
|
||||
useSimpleHashRouterThatAcceptsInvites<TodoProject>(localNode);
|
||||
|
||||
const createProject = useCallback(
|
||||
(title: string) => {
|
||||
if (!title) return;
|
||||
|
||||
// To create a new todo project, we first create a `Group`,
|
||||
// which is a scope for defining access rights (reader/writer/admin)
|
||||
// of its members, which will apply to all CoValues owned by that group.
|
||||
const projectGroup = localNode.createGroup();
|
||||
|
||||
// Then we create an empty todo project and list of tasks within that group.
|
||||
const project = projectGroup.createMap<TodoProject>();
|
||||
const tasks = projectGroup.createList<ListOfTasks>();
|
||||
|
||||
// We edit the todo project to initialise it.
|
||||
// Inside the `.edit` callback we can mutate a CoValue
|
||||
project.edit((project) => {
|
||||
project.set("title", title);
|
||||
project.set("tasks", tasks.id);
|
||||
});
|
||||
|
||||
navigateToProjectId(project.id);
|
||||
},
|
||||
[localNode, navigateToProjectId]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 pb-10 px-5">
|
||||
{currentProjectId ? (
|
||||
<TodoTable projectId={currentProjectId} />
|
||||
) : (
|
||||
<SubmittableInput
|
||||
onSubmit={createProject}
|
||||
label="Create New Project"
|
||||
placeholder="New project title"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigateToProjectId(undefined);
|
||||
logOut();
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: continue with ./3_TodoTable.tsx */
|
||||
132
examples/todo/src/2_main.tsx
Normal file
132
examples/todo/src/2_main.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {
|
||||
RouterProvider,
|
||||
createHashRouter,
|
||||
useNavigate,
|
||||
} 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 { NewProjectForm } from "./3_NewProjectForm.tsx";
|
||||
import { ProjectTodoTable } from "./4_ProjectTodoTable.tsx";
|
||||
import { TodoAccount, TodoProject } from "./1_schema.ts";
|
||||
|
||||
/**
|
||||
* Walkthrough: The top-level provider `<Jazz.Provider/>`
|
||||
*
|
||||
* This shows how to use the top-level provider `<Jazz.Provider/>`,
|
||||
* which provides the rest of the app with a controlled account (used through `useAccount` later).
|
||||
* Here we use `PasskeyAuth`, which uses Passkeys (aka WebAuthn) to store a user's account secret
|
||||
* - no backend needed.
|
||||
*
|
||||
* `<Jazz.Provider/>` also runs our account migration
|
||||
*/
|
||||
|
||||
const appName = "Jazz Todo List Example";
|
||||
|
||||
const auth = PasskeyAuth<TodoAccount>({
|
||||
appName,
|
||||
Component: PrettyAuthUI,
|
||||
accountSchema: TodoAccount,
|
||||
});
|
||||
const Jazz = createJazzReactContext<TodoAccount>({
|
||||
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>
|
||||
<App />
|
||||
</Jazz.Provider>
|
||||
</div>
|
||||
</ThemeProvider>,
|
||||
// </React.StrictMode>
|
||||
);
|
||||
|
||||
/**
|
||||
* Routing in `<App/>`
|
||||
*
|
||||
* <App> is the main app component, handling client-side routing based
|
||||
* on the CoValue ID (CoID) of our TodoProject, stored in the URL hash
|
||||
* - which can also contain invite links.
|
||||
*/
|
||||
export default function App() {
|
||||
// logOut logs out the AuthProvider passed to `<Jazz.Provider/>` above.
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <HomeScreen />,
|
||||
},
|
||||
{
|
||||
path: "/project/:projectId",
|
||||
element: <ProjectTodoTable />,
|
||||
},
|
||||
{
|
||||
path: "/invite/*",
|
||||
element: <p>Accepting invite...</p>,
|
||||
},
|
||||
]);
|
||||
|
||||
// `useAcceptInvite()` is a hook that accepts an invite link from the URL hash,
|
||||
// and on success calls our callback where we navigate to the project that we were just invited to.
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: TodoProject,
|
||||
forValueHint: "project",
|
||||
onAccept: (projectID) => router.navigate("/project/" + projectID),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterProvider router={router} />
|
||||
|
||||
<Button
|
||||
onClick={() => router.navigate("/").then(logOut)}
|
||||
variant="outline"
|
||||
>
|
||||
Log Out
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function HomeScreen() {
|
||||
const { me } = useAccount({
|
||||
root: { projects: [{}] },
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
{me?.root.projects.length ? <h1>My Projects</h1> : null}
|
||||
{me?.root.projects.map((project) => {
|
||||
return (
|
||||
<Button
|
||||
key={project.id}
|
||||
onClick={() => navigate("/project/" + project?.id)}
|
||||
variant="ghost"
|
||||
>
|
||||
{project.title}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
<NewProjectForm />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: Continue with ./3_NewProjectForm.tsx */
|
||||
51
examples/todo/src/3_NewProjectForm.tsx
Normal file
51
examples/todo/src/3_NewProjectForm.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { ListOfTasks, TodoProject } from "./1_schema";
|
||||
|
||||
import { SubmittableInput } from "./basicComponents";
|
||||
|
||||
import { useNavigate } from "react-router";
|
||||
import { useAccount } from "./2_main";
|
||||
import { Group } from "jazz-tools";
|
||||
|
||||
export function NewProjectForm() {
|
||||
// `me` represents the current user account, which will determine
|
||||
// access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
|
||||
const { me } = useAccount();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const createProject = useCallback(
|
||||
(title: string) => {
|
||||
if (!title) return;
|
||||
|
||||
// To create a new todo project, we first create a `Group`,
|
||||
// which is a scope for defining access rights (reader/writer/admin)
|
||||
// of its members, which will apply to all CoValues owned by that group.
|
||||
const projectGroup = Group.create({ owner: me });
|
||||
|
||||
// Then we create an empty todo project within that group
|
||||
const project = TodoProject.create(
|
||||
{
|
||||
title,
|
||||
tasks: ListOfTasks.create([], { owner: projectGroup }),
|
||||
},
|
||||
{ owner: projectGroup },
|
||||
);
|
||||
|
||||
me.root?.projects?.push(project);
|
||||
|
||||
navigate("/project/" + project.id);
|
||||
},
|
||||
[me, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
<SubmittableInput
|
||||
onSubmit={createProject}
|
||||
label="Create New Project"
|
||||
placeholder="New project title"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: continue with ./4_ProjectTodoTable.tsx */
|
||||
@@ -1,162 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { CoID } from "cojson";
|
||||
import { useTelepathicState } from "jazz-react";
|
||||
|
||||
import { TodoProject, Task } from "./1_types";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
SubmittableInput,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./basicComponents";
|
||||
|
||||
import { InviteButton } from "./components/InviteButton";
|
||||
import { NameBadge } from "./components/NameBadge";
|
||||
|
||||
/** Walkthrough: Reactively rendering a todo project as a table,
|
||||
* adding and editing tasks
|
||||
*
|
||||
* Here in `<TodoTable/>`, we use `useTelepathicData()` for the first time,
|
||||
* in this case to load the CoValue for our `TodoProject` as well as
|
||||
* the `ListOfTasks` referenced in it.
|
||||
*/
|
||||
|
||||
export function TodoTable({ projectId }: { projectId: CoID<TodoProject> }) {
|
||||
// `useTelepathicData()` reactively subscribes to updates to a CoValue's
|
||||
// content - whether we create edits locally, load persisted data, or receive
|
||||
// sync updates from other devices or participants!
|
||||
const project = useTelepathicState(projectId);
|
||||
const projectTasks = useTelepathicState(project?.get("tasks"));
|
||||
|
||||
// `createTask` is similar to `createProject` we saw earlier, creating a new CoMap
|
||||
// for a new task (in the same group as the list of tasks/the project), and then
|
||||
// adding it as an item to the project's list of tasks.
|
||||
const createTask = useCallback(
|
||||
(text: string) => {
|
||||
if (!projectTasks || !text) return;
|
||||
const task = projectTasks.group.createMap<Task>();
|
||||
|
||||
task.edit((task) => {
|
||||
task.set("text", text);
|
||||
task.set("done", false);
|
||||
});
|
||||
|
||||
projectTasks.edit((projectTasks) => {
|
||||
projectTasks.push(task.id);
|
||||
});
|
||||
},
|
||||
[projectTasks]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="max-w-full w-4xl">
|
||||
<div className="flex justify-between items-center gap-4 mb-4">
|
||||
<h1>
|
||||
{
|
||||
// This is how we can access properties from the project,
|
||||
// accounting for the fact that it might not be loaded yet
|
||||
project?.get("title") ? (
|
||||
<>
|
||||
{project.get("title")}{" "}
|
||||
<span className="text-sm">({project.id})</span>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
|
||||
)
|
||||
}
|
||||
</h1>
|
||||
<InviteButton list={project} />
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[40px]">Done</TableHead>
|
||||
<TableHead>Task</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{
|
||||
// Here, we iterate over the items of our `ListOfTasks`
|
||||
// and render a `<TaskRow>` for each.
|
||||
|
||||
projectTasks?.map((taskId: CoID<Task>) => (
|
||||
<TaskRow key={taskId} taskId={taskId} />
|
||||
))
|
||||
}
|
||||
<NewTaskInputRow
|
||||
createTask={createTask}
|
||||
disabled={!project}
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TaskRow({ taskId }: { taskId: CoID<Task> }) {
|
||||
// `<TaskRow/>` uses `useTelepathicState()` as well, to granularly load and
|
||||
// subscribe to changes for that particular task.
|
||||
const task = useTelepathicState(taskId);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
className="mt-1"
|
||||
checked={task?.get("done")}
|
||||
onCheckedChange={(checked) => {
|
||||
// (the only thing we let the user change is the "done" status)
|
||||
task?.edit((task) => {
|
||||
task.set("done", !!checked);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-row justify-between items-center gap-2">
|
||||
<span className={task?.get("done") ? "line-through" : ""}>
|
||||
{task?.get("text") || (
|
||||
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
|
||||
)}
|
||||
</span>
|
||||
{/* We also use a `<NameBadge/>` helper component to render the name
|
||||
of the author of the task. We get the author by using the collaboration
|
||||
feature `whoEdited(key)` on our `Task` CoMap, which returns the accountID
|
||||
of the last account that changed a given key in the CoMap. */}
|
||||
<NameBadge accountID={task?.whoEdited("text")} />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
function NewTaskInputRow({
|
||||
createTask,
|
||||
disabled,
|
||||
}: {
|
||||
createTask: (text: string) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Checkbox className="mt-1" disabled />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SubmittableInput
|
||||
onSubmit={(taskText) => createTask(taskText)}
|
||||
label="Add"
|
||||
placeholder="New task"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
177
examples/todo/src/4_ProjectTodoTable.tsx
Normal file
177
examples/todo/src/4_ProjectTodoTable.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { TodoProject, Task } from "./1_schema";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
SubmittableInput,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "./basicComponents";
|
||||
|
||||
import { InviteButton } from "./components/InviteButton";
|
||||
import uniqolor from "uniqolor";
|
||||
import { useParams } from "react-router";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useCoState } from "./2_main";
|
||||
|
||||
/** Walkthrough: Reactively rendering a todo project as a table,
|
||||
* adding and editing tasks
|
||||
*
|
||||
* Here in `<TodoTable/>`, we use `useAutoSub()` for the first time,
|
||||
* in this case to load the CoValue for our `TodoProject` as well as
|
||||
* the `ListOfTasks` referenced in it.
|
||||
*/
|
||||
|
||||
export function ProjectTodoTable() {
|
||||
const projectId = useParams<{ projectId: ID<TodoProject> }>().projectId;
|
||||
|
||||
// `useAutoSub()` reactively subscribes to updates to a CoValue's
|
||||
// content - whether we create edits locally, load persisted data, or receive
|
||||
// sync updates from other devices or participants!
|
||||
// It also recursively resolves and subsribes to all referenced CoValues.
|
||||
const project = useCoState(TodoProject, projectId);
|
||||
|
||||
// `createTask` is similar to `createProject` we saw earlier, creating a new CoMap
|
||||
// for a new task (in the same group as the project), and then
|
||||
// adding that as an item to the project's list of tasks.
|
||||
const createTask = useCallback(
|
||||
(text: string) => {
|
||||
if (!project?.tasks || !text) return;
|
||||
const task = Task.create(
|
||||
{
|
||||
done: false,
|
||||
text,
|
||||
},
|
||||
{ owner: project._owner },
|
||||
);
|
||||
|
||||
// push will cause useCoState to rerender this component, both here and on other devices
|
||||
project.tasks.push(task);
|
||||
},
|
||||
[project?.tasks, project?._owner],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="max-w-full w-4xl">
|
||||
<div className="flex justify-between items-center gap-4 mb-4">
|
||||
<h1>
|
||||
{
|
||||
// This is how we can access properties from the project query,
|
||||
// accounting for the fact that note everything might be loaded yet
|
||||
project?.title ? (
|
||||
<>
|
||||
{project.title}{" "}
|
||||
<span className="text-sm">({project.id})</span>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
|
||||
)
|
||||
}
|
||||
</h1>
|
||||
<InviteButton value={project} valueHint="project" />
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[40px]">Done</TableHead>
|
||||
<TableHead>Task</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{project?.tasks?.map(
|
||||
(task) => task && <TaskRow key={task.id} task={task} />,
|
||||
)}
|
||||
<NewTaskInputRow
|
||||
createTask={createTask}
|
||||
disabled={!project}
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TaskRow({ task }: { task: Task | undefined }) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
className="mt-1"
|
||||
checked={task?.done}
|
||||
onCheckedChange={(checked) => {
|
||||
// Tick or untick the task
|
||||
// Task is also immutable, but this will update all queries
|
||||
// that include this task as a reference
|
||||
if (task) task.done = !!checked;
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-row justify-between items-center gap-2">
|
||||
{task?.text ? (
|
||||
<span className={task?.done ? "line-through" : ""}>
|
||||
{task.text}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton className="mt-1 w-[200px] h-[1em] rounded-full" />
|
||||
)}
|
||||
{
|
||||
// Here we see for the first time how we can access edit history
|
||||
// for a CoValue, and use it to display who created the task.
|
||||
task?._edits.text?.by?.profile?.name ? (
|
||||
<span
|
||||
className="rounded-full py-0.5 px-2 text-xs"
|
||||
style={uniqueColoring(task._edits.text.by.id)}
|
||||
>
|
||||
{task._edits.text.by.profile.name}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton className="mt-1 w-[50px] h-[1em] rounded-full" />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
/** Walkthrough: This is the end of the walkthrough so far! */
|
||||
|
||||
function NewTaskInputRow({
|
||||
createTask,
|
||||
disabled,
|
||||
}: {
|
||||
createTask: (text: string) => void;
|
||||
disabled: boolean;
|
||||
}) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Checkbox className="mt-1" disabled />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SubmittableInput
|
||||
onSubmit={(taskText) => createTask(taskText)}
|
||||
label="Add"
|
||||
placeholder="New task"
|
||||
disabled={disabled}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export function SubmittableInput({
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const textEl = e.currentTarget.elements.namedItem(
|
||||
"text"
|
||||
"text",
|
||||
) as HTMLInputElement;
|
||||
onSubmit(textEl.value);
|
||||
textEl.value = "";
|
||||
@@ -31,7 +31,11 @@ export function SubmittableInput({
|
||||
autoComplete="off"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Button asChild type="submit" className="flex-shrink flex-1 cursor-pointer">
|
||||
<Button
|
||||
asChild
|
||||
type="submit"
|
||||
className="flex-shrink flex-1 cursor-pointer"
|
||||
>
|
||||
<Input type="submit" value={label} disabled={disabled} />
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Toaster } from ".";
|
||||
|
||||
export function TitleAndLogo({name}: {name: string}) {
|
||||
return <>
|
||||
<div className="flex items-center gap-2 justify-center mt-5">
|
||||
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,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,28 +1,28 @@
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { Check } from "lucide-react";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
));
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
||||
|
||||
export { Checkbox }
|
||||
export { Checkbox };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
export { Skeleton };
|
||||
|
||||
@@ -1,114 +1,120 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
Table.displayName = "Table"
|
||||
<div className="w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("bg-primary font-medium text-primary-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"bg-primary font-medium text-primary-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableRow.displayName = "TableRow"
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-4 align-middle [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
};
|
||||
|
||||
@@ -1,127 +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 * 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"
|
||||
import { cn } from "@/basicComponents/lib/utils";
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
const ToastProvider = ToastPrimitives.Provider;
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||
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
|
||||
<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",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
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
|
||||
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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<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>
|
||||
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
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
}
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
};
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/basicComponents/ui/toast"
|
||||
import { useToast } from "@/basicComponents/ui/use-toast"
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
import { useToast } from "@/basicComponents/ui/use-toast";
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
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>
|
||||
)
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,192 +1,193 @@
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/basicComponents/ui/toast"
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/basicComponents/ui/toast";
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
const TOAST_LIMIT = 1;
|
||||
const TOAST_REMOVE_DELAY = 1000000;
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
action?: ToastActionElement
|
||||
}
|
||||
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
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
} as const;
|
||||
|
||||
let count = 0
|
||||
let count = 0;
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_VALUE
|
||||
return count.toString()
|
||||
count = (count + 1) % Number.MAX_VALUE;
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes
|
||||
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"]
|
||||
}
|
||||
| {
|
||||
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[]
|
||||
toasts: ToasterToast[];
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return
|
||||
}
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
})
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId);
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
});
|
||||
}, TOAST_REMOVE_DELAY);
|
||||
|
||||
toastTimeouts.set(toastId, timeout)
|
||||
}
|
||||
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),
|
||||
}
|
||||
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 "UPDATE_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||
),
|
||||
};
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action
|
||||
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)
|
||||
})
|
||||
}
|
||||
// ! 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.map((t) =>
|
||||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
}
|
||||
}
|
||||
}
|
||||
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> = []
|
||||
const listeners: Array<(state: State) => void> = [];
|
||||
|
||||
let memoryState: State = { toasts: [] }
|
||||
let memoryState: State = { toasts: [] };
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action)
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState)
|
||||
})
|
||||
memoryState = reducer(memoryState, action);
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState);
|
||||
});
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
type Toast = Omit<ToasterToast, "id">;
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId()
|
||||
const id = genId();
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
});
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
};
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
const [state, setState] = React.useState<State>(memoryState)
|
||||
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])
|
||||
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 }),
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) =>
|
||||
dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast }
|
||||
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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user