Compare commits
2030 Commits
react-perf
...
briscola
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba7e20c0a | ||
|
|
d4c45fe2bd | ||
|
|
3a45c18cf5 | ||
|
|
79e3611897 | ||
|
|
9c50120110 | ||
|
|
447c7ace39 | ||
|
|
ffa53f2ab6 | ||
|
|
81bf8785cc | ||
|
|
304cb1bd15 | ||
|
|
eb3063b855 | ||
|
|
f0db269b0d | ||
|
|
48a8bd2c92 | ||
|
|
ff96a490f4 | ||
|
|
7834f8a672 | ||
|
|
969d3d5026 | ||
|
|
98cc5e1294 | ||
|
|
269673c15b | ||
|
|
51d8fb7e77 | ||
|
|
315a00bcb5 | ||
|
|
ed4ee686b5 | ||
|
|
b4dc466526 | ||
|
|
af9ea3dc4b | ||
|
|
67abc0e437 | ||
|
|
2034ef2dcb | ||
|
|
343b4c0c80 | ||
|
|
41e4083f97 | ||
|
|
15f81e889f | ||
|
|
8d7fb18a08 | ||
|
|
43378ef2b7 | ||
|
|
1ca1d15639 | ||
|
|
bdd67ef34d | ||
|
|
dc62b9569b | ||
|
|
6fd920face | ||
|
|
0689ed3767 | ||
|
|
a4e1aa7f56 | ||
|
|
ca5d7cb2ee | ||
|
|
25dfd90c5a | ||
|
|
66badc061e | ||
|
|
eecdd5215e | ||
|
|
99b9605579 | ||
|
|
830f0c00ca | ||
|
|
635e824eb0 | ||
|
|
c2a55c09e0 | ||
|
|
33b64743ed | ||
|
|
74a419e2df | ||
|
|
170e135c79 | ||
|
|
305917236e | ||
|
|
71e881afcb | ||
|
|
f32af158f0 | ||
|
|
e0d9bb3db3 | ||
|
|
93f73b3b86 | ||
|
|
7e96af0602 | ||
|
|
3a5d8d1ff4 | ||
|
|
d558c085e6 | ||
|
|
07e52f50b3 | ||
|
|
9a775c67d5 | ||
|
|
a64d05f9a5 | ||
|
|
e43a6d4418 | ||
|
|
70182c5b28 | ||
|
|
1de26f8be9 | ||
|
|
af6c0878c3 | ||
|
|
579384a6df | ||
|
|
3376a02fde | ||
|
|
8440699193 | ||
|
|
d7f4ba5b9e | ||
|
|
58a785db40 | ||
|
|
7546e8bffc | ||
|
|
ad57ac21e0 | ||
|
|
f8b9854286 | ||
|
|
10ea7332e6 | ||
|
|
0a859821bc | ||
|
|
b9408c6119 | ||
|
|
c6c26b0ad1 | ||
|
|
9527c8a019 | ||
|
|
69bdcc0273 | ||
|
|
62cfd3bce4 | ||
|
|
edf6328905 | ||
|
|
1d2b8d92d7 | ||
|
|
697dd370ee | ||
|
|
33ef9c43d0 | ||
|
|
f74134164b | ||
|
|
50939729e5 | ||
|
|
dcf945b514 | ||
|
|
0b8425d9b4 | ||
|
|
986805dcba | ||
|
|
89dae1d889 | ||
|
|
c3418f6bed | ||
|
|
f5a63d13c1 | ||
|
|
e9778cc4ca | ||
|
|
7d955d053e | ||
|
|
ae6d2c6daf | ||
|
|
44aa86978f | ||
|
|
eedb208e4c | ||
|
|
454fbbfc17 | ||
|
|
686e3e998c | ||
|
|
7a2f249614 | ||
|
|
59a0c86dd1 | ||
|
|
7701307ab7 | ||
|
|
1e7dadd31b | ||
|
|
b57bb88328 | ||
|
|
afd25d766f | ||
|
|
222f8c5063 | ||
|
|
857e0d5c48 | ||
|
|
be903ede57 | ||
|
|
9b654ad1fb | ||
|
|
a7bc15fdf2 | ||
|
|
e14a2c0a5d | ||
|
|
6a7ef2ba60 | ||
|
|
94d95ceeb6 | ||
|
|
ab4ffbd130 | ||
|
|
e322e6034b | ||
|
|
bbf49ae7cd | ||
|
|
04bcf94ae4 | ||
|
|
22ad297e4b | ||
|
|
2ee58780a5 | ||
|
|
fca6a0bfc2 | ||
|
|
6f0bd7f5ea | ||
|
|
4dea285879 | ||
|
|
0c68349a27 | ||
|
|
e761a8e5d5 | ||
|
|
45c1e113ea | ||
|
|
88d7d9add0 | ||
|
|
36d3be25a1 | ||
|
|
99e731d6f8 | ||
|
|
0027bc73b5 | ||
|
|
2304412c20 | ||
|
|
b697b91f1d | ||
|
|
6694dbc53f | ||
|
|
af9eb02d46 | ||
|
|
4e8c70f8d9 | ||
|
|
fa41f8eec2 | ||
|
|
60e35eab71 | ||
|
|
80f563e4f0 | ||
|
|
c321d99ec6 | ||
|
|
135fa2d164 | ||
|
|
5d20c81ed5 | ||
|
|
8ee6db5102 | ||
|
|
a8e7f52700 | ||
|
|
1e6f6fce53 | ||
|
|
025dfaedc4 | ||
|
|
d681aa242a | ||
|
|
a4f0a0737e | ||
|
|
4583401502 | ||
|
|
99a614038d | ||
|
|
762d1fac23 | ||
|
|
9d7fabb1cf | ||
|
|
7c9a872f50 | ||
|
|
9ed8dd509b | ||
|
|
0d771b9acc | ||
|
|
3e191685e4 | ||
|
|
7b2fd31a75 | ||
|
|
dda7c243d7 | ||
|
|
cce6a2f243 | ||
|
|
63001da65a | ||
|
|
b094605109 | ||
|
|
e4bc39ed21 | ||
|
|
67d4f538ec | ||
|
|
f5b527886f | ||
|
|
dba767ae7b | ||
|
|
bb39c0b7fa | ||
|
|
57c846a904 | ||
|
|
4390d4b266 | ||
|
|
1427f071d4 | ||
|
|
2d31e8edce | ||
|
|
09cad40565 | ||
|
|
3accbc06e2 | ||
|
|
39ae4fc8c1 | ||
|
|
ae60eb0d01 | ||
|
|
05eb330173 | ||
|
|
5d1e192245 | ||
|
|
c5e059b3a9 | ||
|
|
d1785c0178 | ||
|
|
9b95c32edb | ||
|
|
d87781d33f | ||
|
|
ad50ec2d9d | ||
|
|
fef055b9fb | ||
|
|
cdc7f9f841 | ||
|
|
ff6940de56 | ||
|
|
cad54e4018 | ||
|
|
c657880a22 | ||
|
|
79b02dee3c | ||
|
|
ee29cb300c | ||
|
|
36f0ab7571 | ||
|
|
829be3cafa | ||
|
|
7c322796aa | ||
|
|
36d50d96dd | ||
|
|
1fd26b9d05 | ||
|
|
40f161d4d0 | ||
|
|
655a601a8e | ||
|
|
c7a28e5003 | ||
|
|
ae429d0a1c | ||
|
|
26c390be66 | ||
|
|
a0a0962f28 | ||
|
|
32834ef9e3 | ||
|
|
13be2a3235 | ||
|
|
7fa6e4333d | ||
|
|
093f08b5e0 | ||
|
|
f374545692 | ||
|
|
9a8f2747fe | ||
|
|
84b7c9ab89 | ||
|
|
3863d834ce | ||
|
|
4c860075ee | ||
|
|
79480ddf0c | ||
|
|
8d447b8725 | ||
|
|
238fd47089 | ||
|
|
29ac1fbe88 | ||
|
|
95fd27d3e3 | ||
|
|
533d7684c9 | ||
|
|
798403a57d | ||
|
|
ddf060e64c | ||
|
|
51515f629c | ||
|
|
65e012f401 | ||
|
|
a1ba29fd47 | ||
|
|
b6b76ab2b9 | ||
|
|
77dbd4e205 | ||
|
|
baa8aff33f | ||
|
|
63703489ab | ||
|
|
325250272b | ||
|
|
219ba975a5 | ||
|
|
f5a3394d40 | ||
|
|
4a5209237f | ||
|
|
d7eb50abc1 | ||
|
|
4908d7cfd3 | ||
|
|
38b19ab980 | ||
|
|
9e2486c332 | ||
|
|
e905c84a34 | ||
|
|
b097c38617 | ||
|
|
385dfc89ff | ||
|
|
d89e4c3412 | ||
|
|
bbeee086ce | ||
|
|
718e9418e2 | ||
|
|
507a00fe6a | ||
|
|
8e940130d2 | ||
|
|
ac216b9f2e | ||
|
|
93230df2cb | ||
|
|
c13daa140b | ||
|
|
3c9439d0cc | ||
|
|
d38d4928c7 | ||
|
|
596901cba6 | ||
|
|
2a1fda3758 | ||
|
|
f3581d1086 | ||
|
|
4ac2b75194 | ||
|
|
cfa78ed628 | ||
|
|
94095aff28 | ||
|
|
a70f7a4c49 | ||
|
|
2270ae113e | ||
|
|
92b64e05ca | ||
|
|
cb6b9af57d | ||
|
|
fc00369d87 | ||
|
|
0b260f339d | ||
|
|
8f50dd48f6 | ||
|
|
d3af4fecd1 | ||
|
|
4ec13a9515 | ||
|
|
79262351fa | ||
|
|
01292230e8 | ||
|
|
9b31d3241d | ||
|
|
77dfd4f883 | ||
|
|
9150c238e4 | ||
|
|
67bd5ab07f | ||
|
|
2678c83a99 | ||
|
|
4c1364a5cb | ||
|
|
f546811a23 | ||
|
|
eb9164633c | ||
|
|
db5e5d8392 | ||
|
|
a18d870faa | ||
|
|
d434b23a34 | ||
|
|
dd363d613f | ||
|
|
9d41f7a16a | ||
|
|
dfddc3603a | ||
|
|
5c09f0f41a | ||
|
|
139fcbe172 | ||
|
|
507b3b890a | ||
|
|
06ad39c418 | ||
|
|
7077a41c2d | ||
|
|
9572729cc9 | ||
|
|
9f08ca8cd8 | ||
|
|
8e0bf8a8d8 | ||
|
|
5b72d1f89b | ||
|
|
e10d09e6ab | ||
|
|
2007815745 | ||
|
|
1f826734d4 | ||
|
|
80383dac3b | ||
|
|
b9ed9e3f13 | ||
|
|
96f844af5e | ||
|
|
994fbf3524 | ||
|
|
6eba301599 | ||
|
|
322438f361 | ||
|
|
f2f3481773 | ||
|
|
b96bab59d0 | ||
|
|
aa21072ceb | ||
|
|
01442948d0 | ||
|
|
6c26902a73 | ||
|
|
b12f9dc675 | ||
|
|
bb6d9791dc | ||
|
|
024b24f5fa | ||
|
|
facfc51e84 | ||
|
|
6e4a7411dd | ||
|
|
a02b53cbe3 | ||
|
|
a0548ecb25 | ||
|
|
970414c8c3 | ||
|
|
d62a5c74f9 | ||
|
|
5d35469b91 | ||
|
|
6cd79a0631 | ||
|
|
5122cd027b | ||
|
|
9d9bb97da3 | ||
|
|
9db3f2120b | ||
|
|
cb4ccd5d33 | ||
|
|
3121551e8a | ||
|
|
1a65ffd913 | ||
|
|
80301e51bb | ||
|
|
5686db9b89 | ||
|
|
21a56fc96b | ||
|
|
eb22a24f17 | ||
|
|
4f3de09154 | ||
|
|
f2f865da50 | ||
|
|
c0d12dba26 | ||
|
|
cd0717f35d | ||
|
|
4515395bb9 | ||
|
|
7cee49f2b2 | ||
|
|
9414fbfb23 | ||
|
|
9c6ea58ef5 | ||
|
|
0685436b0c | ||
|
|
daa11aa915 | ||
|
|
bbdb1ef429 | ||
|
|
508537928f | ||
|
|
eaeeedc3a4 | ||
|
|
15cdfd406c | ||
|
|
76262c1a03 | ||
|
|
fc43a2384b | ||
|
|
d1698eed6f | ||
|
|
6d68dc1bbf | ||
|
|
1fb49b266a | ||
|
|
49f0ab1545 | ||
|
|
641bbddd64 | ||
|
|
7e22f53718 | ||
|
|
4c44468129 | ||
|
|
b0c5ecd7b6 | ||
|
|
5bf9d69e9f | ||
|
|
3ecf11ded9 | ||
|
|
709d60b365 | ||
|
|
f135babcb8 | ||
|
|
36a85d4313 | ||
|
|
7316379247 | ||
|
|
0cf59b6549 | ||
|
|
bcaff2f24a | ||
|
|
d2a5087e65 | ||
|
|
8627d47afc | ||
|
|
47f625cbd4 | ||
|
|
34b65ef67a | ||
|
|
51209d5314 | ||
|
|
309dffcc67 | ||
|
|
716b87da0a | ||
|
|
3c58879a10 | ||
|
|
ed16031f12 | ||
|
|
90d9e1ca91 | ||
|
|
95828596f8 | ||
|
|
d794a555dc | ||
|
|
a6c143f33f | ||
|
|
1db615d1b1 | ||
|
|
9b45c44b29 | ||
|
|
ecc7c9672d | ||
|
|
d645970cb0 | ||
|
|
e9864cda9d | ||
|
|
d4e9afc4e2 | ||
|
|
4fc4f029a2 | ||
|
|
fb06319dae | ||
|
|
a4886fede8 | ||
|
|
b2ca4ec52e | ||
|
|
b2c8a1a27d | ||
|
|
5469c1f347 | ||
|
|
d36e7cd55a | ||
|
|
03ccdb93af | ||
|
|
eb8bd20931 | ||
|
|
9819811c91 | ||
|
|
350e59b187 | ||
|
|
c2cad4b68c | ||
|
|
0c6b0f37ed | ||
|
|
a3541f24ee | ||
|
|
f8be4e8bc0 | ||
|
|
c8015ddee1 | ||
|
|
44227bd6e8 | ||
|
|
d582429488 | ||
|
|
3db0639636 | ||
|
|
c75cd9da37 | ||
|
|
da13ecac5c | ||
|
|
c4af72e1ca | ||
|
|
e43ca2bc98 | ||
|
|
bcdd8e9125 | ||
|
|
2d6352a2dc | ||
|
|
749350cb59 | ||
|
|
07e9a73c52 | ||
|
|
1ef9998b23 | ||
|
|
b00ee914c3 | ||
|
|
f488c09545 | ||
|
|
0d6ea25e56 | ||
|
|
9ffe6d0c63 | ||
|
|
249eecb68f | ||
|
|
cdbde2970d | ||
|
|
24a1b045d3 | ||
|
|
a21c3068cd | ||
|
|
55ab4844d6 | ||
|
|
6e131eb0dc | ||
|
|
8a0cb69c72 | ||
|
|
d01286b723 | ||
|
|
0b636d26f3 | ||
|
|
f41a1b0d1d | ||
|
|
28930ee1d5 | ||
|
|
0a8753dfe0 | ||
|
|
3ca8b13f6c | ||
|
|
e06c18150a | ||
|
|
4c4499ce86 | ||
|
|
afa4c825ca | ||
|
|
60e04cb193 | ||
|
|
843a012f33 | ||
|
|
bd510a13ce | ||
|
|
0d6853bbdd | ||
|
|
a238dde78a | ||
|
|
59997fbf71 | ||
|
|
b56d7e3e7c | ||
|
|
2f5bd74206 | ||
|
|
a4864d93d2 | ||
|
|
19376e6abd | ||
|
|
22f6db8141 | ||
|
|
3d9f12e9ea | ||
|
|
68620a3df9 | ||
|
|
1767f024d9 | ||
|
|
c55924a04a | ||
|
|
31e00a96ae | ||
|
|
7bb834f399 | ||
|
|
667f36e1cf | ||
|
|
07669923ad | ||
|
|
9082a099ee | ||
|
|
aba059db28 | ||
|
|
ae6b9c8dd2 | ||
|
|
7b9f96bf1a | ||
|
|
8df49546fe | ||
|
|
bb4460f422 | ||
|
|
2099099afc | ||
|
|
06b0758d7c | ||
|
|
6135250e57 | ||
|
|
aacd03bbdd | ||
|
|
ca7f250d47 | ||
|
|
83ad506b94 | ||
|
|
e9751f5b69 | ||
|
|
c84764acd5 | ||
|
|
c2a805bffa | ||
|
|
8728dde42b | ||
|
|
9dd9366734 | ||
|
|
48dd00f453 | ||
|
|
cc361aefe5 | ||
|
|
9e4438cb54 | ||
|
|
08706d557a | ||
|
|
02c1ec63cc | ||
|
|
df797dedcd | ||
|
|
74cb08e7d4 | ||
|
|
73720a8cc4 | ||
|
|
50c77fa788 | ||
|
|
9977a4ee85 | ||
|
|
441fe27802 | ||
|
|
1afbd2c7cc | ||
|
|
4955e39af5 | ||
|
|
ace151696c | ||
|
|
db9560ebc5 | ||
|
|
80b572710e | ||
|
|
bbb9d45969 | ||
|
|
83e9a3eaa8 | ||
|
|
9ff7e68f7d | ||
|
|
06740e840a | ||
|
|
b0e2c4fd4b | ||
|
|
4c1922c10e | ||
|
|
76bad9b5c3 | ||
|
|
7f16f2705e | ||
|
|
d12594e521 | ||
|
|
cf96350b01 | ||
|
|
947030433f | ||
|
|
b4cebd732e | ||
|
|
d0d95e6d5d | ||
|
|
a75383ac6a | ||
|
|
073d8992f5 | ||
|
|
7b400835a2 | ||
|
|
de503b6120 | ||
|
|
bf287d0709 | ||
|
|
f7ae41254f | ||
|
|
8f348b28c6 | ||
|
|
b0072e63e3 | ||
|
|
a479ece032 | ||
|
|
e0dbe46d64 | ||
|
|
1f1fc56720 | ||
|
|
3b185b4cd3 | ||
|
|
64fa74a6d9 | ||
|
|
df5dc513bf | ||
|
|
8e6783ad88 | ||
|
|
79bf6f478f | ||
|
|
7fd93a5a61 | ||
|
|
8f9687323f | ||
|
|
47ee25786f | ||
|
|
0009aa19b2 | ||
|
|
741b9cbada | ||
|
|
259ade3099 | ||
|
|
b6f2da2221 | ||
|
|
44157945a0 | ||
|
|
4ff7bb500a | ||
|
|
66538fdcf5 | ||
|
|
543f91277d | ||
|
|
42112ec46c | ||
|
|
db5ea54338 | ||
|
|
7c7880a9b2 | ||
|
|
f4170eb879 | ||
|
|
8278055e33 | ||
|
|
65c236571e | ||
|
|
b53cc9930e | ||
|
|
9c5b34d91c | ||
|
|
49082a5aad | ||
|
|
ab7c899a9e | ||
|
|
a6c119b98d | ||
|
|
6e3565d20f | ||
|
|
e5e21718f9 | ||
|
|
b6653555f5 | ||
|
|
1b49bd8be4 | ||
|
|
c9fd16ce21 | ||
|
|
70a93ab093 | ||
|
|
e60f34d9e6 | ||
|
|
dacaa02a01 | ||
|
|
55cc248d91 | ||
|
|
534fce6796 | ||
|
|
a223a3a5ab | ||
|
|
a04c7dca7a | ||
|
|
e067c29d81 | ||
|
|
81c470e9fa | ||
|
|
52d5a14fdf | ||
|
|
1357306d1b | ||
|
|
fca6be8da0 | ||
|
|
b0df041a24 | ||
|
|
3c6d9b20c1 | ||
|
|
357698f4fb | ||
|
|
a6e5a72b97 | ||
|
|
e386f2ba8d | ||
|
|
c2d292effc | ||
|
|
b4dd2add45 | ||
|
|
f3824dfb76 | ||
|
|
ac7b388ca5 | ||
|
|
ad3641861b | ||
|
|
3d601deaa0 | ||
|
|
7824e6b36a | ||
|
|
f597316267 | ||
|
|
9b80278b71 | ||
|
|
46f2ab801d | ||
|
|
a6b6ccf814 | ||
|
|
8928e9e10d | ||
|
|
0e1c38f2d1 | ||
|
|
08ad01d6b5 | ||
|
|
e9eec78ce5 | ||
|
|
5bab5091c3 | ||
|
|
1663b4aa59 | ||
|
|
7f6637e235 | ||
|
|
f320e6821a | ||
|
|
b491a3c638 | ||
|
|
35d03f2f2c | ||
|
|
43c19307cd | ||
|
|
1e642d4454 | ||
|
|
2c931dd57e | ||
|
|
8eec42814a | ||
|
|
0e0590d25b | ||
|
|
17f8bc25c3 | ||
|
|
c1d652cf7f | ||
|
|
58a3ad2951 | ||
|
|
10f3e4aabd | ||
|
|
9f834eba97 | ||
|
|
4a02dbed28 | ||
|
|
ad920158d6 | ||
|
|
63f5574003 | ||
|
|
26b54bf81b | ||
|
|
151c9c923d | ||
|
|
7f9c125074 | ||
|
|
0e59e65430 | ||
|
|
24fc4dd75d | ||
|
|
943283db76 | ||
|
|
19ffeb8505 | ||
|
|
efe9855a9e | ||
|
|
f93d6ae64e | ||
|
|
4b194b218a | ||
|
|
b62a37229c | ||
|
|
d9c9231304 | ||
|
|
74631f25a9 | ||
|
|
b39a9f4921 | ||
|
|
0aeabf2480 | ||
|
|
57d6e7c590 | ||
|
|
9bb5c4ca5f | ||
|
|
80afb04f26 | ||
|
|
0e521ed8df | ||
|
|
692b3e207a | ||
|
|
3102523cb1 | ||
|
|
a939a9b7f7 | ||
|
|
33aee5f79f | ||
|
|
c80086c33e | ||
|
|
36900c6462 | ||
|
|
b02f439db9 | ||
|
|
833fe2dac4 | ||
|
|
c11d7d0103 | ||
|
|
d92413a4ef | ||
|
|
b51c519187 | ||
|
|
4f048b4b6e | ||
|
|
6a8bc02a60 | ||
|
|
687e7aca2c | ||
|
|
d00f57c2a7 | ||
|
|
58f2713218 | ||
|
|
b3a0fbb5cd | ||
|
|
696347881b | ||
|
|
4a0299a73f | ||
|
|
6883c5212f | ||
|
|
22d1402b71 | ||
|
|
6e92baa365 | ||
|
|
03541d856f | ||
|
|
27829e96e8 | ||
|
|
65b7930b1b | ||
|
|
b909e48532 | ||
|
|
1f13002663 | ||
|
|
de1469a22f | ||
|
|
ef240c207b | ||
|
|
202b32a35a | ||
|
|
f13b305644 | ||
|
|
bc3c79ea5d | ||
|
|
55333cf910 | ||
|
|
25c4e234d6 | ||
|
|
3f15a23219 | ||
|
|
1b60fc7095 | ||
|
|
9db577def9 | ||
|
|
ab0760f11b | ||
|
|
7e75be6941 | ||
|
|
67d106e02a | ||
|
|
c255dffe0c | ||
|
|
158f95289e | ||
|
|
a0e560eae6 | ||
|
|
10e71ad840 | ||
|
|
eeb7d41219 | ||
|
|
1f475e568f | ||
|
|
f6a9c33809 | ||
|
|
8dfe64b3d7 | ||
|
|
9b834aa8f8 | ||
|
|
554e9e5d73 | ||
|
|
dd43df6bd3 | ||
|
|
b4876ff82b | ||
|
|
fb48171b22 | ||
|
|
c4202fd21a | ||
|
|
db73a765e4 | ||
|
|
6ae9df74bc | ||
|
|
786d185b24 | ||
|
|
4c48722e65 | ||
|
|
676f2195ab | ||
|
|
45ba926cff | ||
|
|
a909f2212f | ||
|
|
d55f944238 | ||
|
|
7b557f188b | ||
|
|
9ca25d1cdf | ||
|
|
4339e485e2 | ||
|
|
e6dcec2317 | ||
|
|
057286a8df | ||
|
|
b0fa35e11e | ||
|
|
ae6f3a39c7 | ||
|
|
e4f110fa9f | ||
|
|
641483f40b | ||
|
|
9212ab89ec | ||
|
|
915fa5ea4e | ||
|
|
5d72d0a5a0 | ||
|
|
bb874490c5 | ||
|
|
a2c6d0cb16 | ||
|
|
fe6648b3a5 | ||
|
|
c1ab97d976 | ||
|
|
3cb27e11dd | ||
|
|
50f94c2b81 | ||
|
|
3937781246 | ||
|
|
44bcf6e411 | ||
|
|
b66206f1a6 | ||
|
|
973824795e | ||
|
|
f4974e5975 | ||
|
|
9099bbf75f | ||
|
|
e1a40b41eb | ||
|
|
9683e24237 | ||
|
|
50e122d299 | ||
|
|
0ff381b8d5 | ||
|
|
ef7931761a | ||
|
|
da30641856 | ||
|
|
01435dc20b | ||
|
|
506256e291 | ||
|
|
6bb0d38b26 | ||
|
|
397ff9ce09 | ||
|
|
a107e17c44 | ||
|
|
d2d2801537 | ||
|
|
df42b2b295 | ||
|
|
aae280b7da | ||
|
|
d1e4ee0fe4 | ||
|
|
7cfd47b7f3 | ||
|
|
c34ce695ea | ||
|
|
7666dc6da2 | ||
|
|
be3ac8aa63 | ||
|
|
b759668022 | ||
|
|
1ada04f6f4 | ||
|
|
f3455bc691 | ||
|
|
044f61e3d7 | ||
|
|
6d63f4cf2e | ||
|
|
b828f41cf0 | ||
|
|
14d24888e1 | ||
|
|
29bdb0a120 | ||
|
|
c7ec6baba8 | ||
|
|
f8cf198a27 | ||
|
|
720435e2f2 | ||
|
|
2dcf6925b4 | ||
|
|
55d49a3435 | ||
|
|
33d1c11909 | ||
|
|
02ee69672d | ||
|
|
743f7d068b | ||
|
|
c0a1bb272e | ||
|
|
a9f08ebbbe | ||
|
|
0d86b132ea | ||
|
|
5114bbec88 | ||
|
|
29deedb163 | ||
|
|
a322a0e3b5 | ||
|
|
4fea86141f | ||
|
|
d923d45a82 | ||
|
|
ff52fb3275 | ||
|
|
c65a8806f6 | ||
|
|
8c8a5b242a | ||
|
|
d020ee2825 | ||
|
|
2e2a13f22d | ||
|
|
b1056c2ed3 | ||
|
|
e00489d8ee | ||
|
|
1e08581049 | ||
|
|
5a5f886bdf | ||
|
|
e5c575ec10 | ||
|
|
68c2b2598e | ||
|
|
c0ba15cca8 | ||
|
|
3e0493f88f | ||
|
|
e511d6d56a | ||
|
|
53f4b23c0a | ||
|
|
749a424cf1 | ||
|
|
33c4ef902d | ||
|
|
e2e3751172 | ||
|
|
6c62f4be5d | ||
|
|
0a2fae3830 | ||
|
|
4ab533af6b | ||
|
|
0fa017d148 | ||
|
|
99cda2f33d | ||
|
|
7c4f519ca1 | ||
|
|
e4fcafd576 | ||
|
|
a9cff03bcb | ||
|
|
26324d4b1e | ||
|
|
bd022ffd5e | ||
|
|
94e3922efc | ||
|
|
dcc9c2e376 | ||
|
|
48edcaff37 | ||
|
|
a18ed077ca | ||
|
|
7780346208 | ||
|
|
699553fe9f | ||
|
|
bdd5ad7eb1 | ||
|
|
28fea37446 | ||
|
|
61915fe3ed | ||
|
|
a6942030d4 | ||
|
|
905c6b312b | ||
|
|
d5af8df158 | ||
|
|
1a4bda09ca | ||
|
|
815f54fafb | ||
|
|
fc845cc930 | ||
|
|
0e6ea3f762 | ||
|
|
f0060e0355 | ||
|
|
9e9788395c | ||
|
|
6371c7689f | ||
|
|
605734c71c | ||
|
|
db5227b463 | ||
|
|
7ed748a0f6 | ||
|
|
8492dd9501 | ||
|
|
b957085e02 | ||
|
|
75fdff431a | ||
|
|
a5559529ae | ||
|
|
2c74fdaa71 | ||
|
|
2b16a97f7a | ||
|
|
e5f135b82a | ||
|
|
0fa0cb2338 | ||
|
|
7e7b6b981a | ||
|
|
f762afd361 | ||
|
|
63d46c985c | ||
|
|
e5b89e2f96 | ||
|
|
eae724a9f3 | ||
|
|
b14eb2106a | ||
|
|
379e56efe9 | ||
|
|
caf682fecd | ||
|
|
2f6d3ac8e6 | ||
|
|
ed80b52ad3 | ||
|
|
a64690e5f3 | ||
|
|
3bcacecbad | ||
|
|
01e3e8a954 | ||
|
|
9adcfc2cd9 | ||
|
|
738bf3a1bb | ||
|
|
59d37df8a2 | ||
|
|
8b63994048 | ||
|
|
f1a7478331 | ||
|
|
a713d4ad51 | ||
|
|
f33aa5c28a | ||
|
|
2ec7e5f287 | ||
|
|
e7d96c9c76 | ||
|
|
c1e49fe727 | ||
|
|
812b13ecbd | ||
|
|
0e6eb7a592 | ||
|
|
9f8506fe76 | ||
|
|
f62f227e14 | ||
|
|
3e17c0e221 | ||
|
|
1af61c8895 | ||
|
|
0c781d28f7 | ||
|
|
a93db84530 | ||
|
|
b529ae3c55 | ||
|
|
a6780401b5 | ||
|
|
b7ef6db1f6 | ||
|
|
9d089ab94f | ||
|
|
3b7a5c0e9d | ||
|
|
7dc2a2828e | ||
|
|
ea93cacd45 | ||
|
|
162da453ca | ||
|
|
e17e959307 | ||
|
|
0d43b2edbc | ||
|
|
9b220b17de | ||
|
|
d4a1aab5d5 | ||
|
|
32b258a966 | ||
|
|
6846f74bc5 | ||
|
|
5fc3e7b700 | ||
|
|
6a799b0d65 | ||
|
|
0a88aeb5a9 | ||
|
|
28ae36391b | ||
|
|
cf5c636fdd | ||
|
|
3f04b75418 | ||
|
|
6a7047f862 | ||
|
|
640559c56f | ||
|
|
9ef6264ff3 | ||
|
|
2ac09180a5 | ||
|
|
588129b311 | ||
|
|
0c95d4af0b | ||
|
|
807846ae79 | ||
|
|
b92b17340e | ||
|
|
0f3d239500 | ||
|
|
eec30fbcdf | ||
|
|
f382656d77 | ||
|
|
4d8a6400e0 | ||
|
|
cf5bd53dbb | ||
|
|
66a30e8041 | ||
|
|
a4d2f2dd26 | ||
|
|
5724735a95 | ||
|
|
5f9c018106 | ||
|
|
24c018f1f2 | ||
|
|
6f745be39a | ||
|
|
29703797cf | ||
|
|
2241ad4089 | ||
|
|
aed769f7b3 | ||
|
|
d348c2d021 | ||
|
|
e5f68904ed | ||
|
|
0aebbdc2fc | ||
|
|
665a099013 | ||
|
|
c70131aece | ||
|
|
d78f590712 | ||
|
|
7033ae968f | ||
|
|
124bf670a2 | ||
|
|
f4acb91d51 | ||
|
|
47a5047ec8 | ||
|
|
877596f389 | ||
|
|
6902b5bb91 | ||
|
|
e914f11d9e | ||
|
|
1a0cd3df7f | ||
|
|
d9a78739a5 | ||
|
|
756528e18e | ||
|
|
466e587fba | ||
|
|
22caae870d | ||
|
|
435fc0c917 | ||
|
|
3dfce294d4 | ||
|
|
90be185094 | ||
|
|
9eac8633b5 | ||
|
|
007c462362 | ||
|
|
e442bb820b | ||
|
|
3f7235c6f7 | ||
|
|
56d94bd8cd | ||
|
|
cb937c4335 | ||
|
|
b4e31657a0 | ||
|
|
f6bf75890a | ||
|
|
5bb1ad4c42 | ||
|
|
3ed74b6b1d | ||
|
|
823a343fe1 | ||
|
|
5b3c3fff7b | ||
|
|
e3f1c49fdd | ||
|
|
42765020e5 | ||
|
|
6cc7a905f8 | ||
|
|
75753f1d37 | ||
|
|
6c113e7bf0 | ||
|
|
53a74e1b7d | ||
|
|
c63b538251 | ||
|
|
f8c3b495e0 | ||
|
|
a05d90dc6b | ||
|
|
307e7fcd3c | ||
|
|
d0ff589447 | ||
|
|
f6cb4f1225 | ||
|
|
1141a23a37 | ||
|
|
05497d5f77 | ||
|
|
bb126009c8 | ||
|
|
cee7beb02a | ||
|
|
5f12e31a80 | ||
|
|
5f96a6fef2 | ||
|
|
08db39ef2f | ||
|
|
67029e00c7 | ||
|
|
2b520e938e | ||
|
|
63568273db | ||
|
|
16702702c8 | ||
|
|
cab4f1f89c | ||
|
|
45df82ed16 | ||
|
|
6610aea708 | ||
|
|
f6b6c01852 | ||
|
|
63f9aa6b97 | ||
|
|
822eb22da6 | ||
|
|
fdf3c6e93d | ||
|
|
de38161023 | ||
|
|
59cc64d697 | ||
|
|
55c249834a | ||
|
|
dd189abd4a | ||
|
|
df1a8e9c99 | ||
|
|
259467ac3f | ||
|
|
9a93ab2476 | ||
|
|
337272bbdc | ||
|
|
464ef7d9c3 | ||
|
|
8727089654 | ||
|
|
753bceddb3 | ||
|
|
8202996108 | ||
|
|
1385c8a66f | ||
|
|
2a72942f8e | ||
|
|
e16836becc | ||
|
|
134a101a52 | ||
|
|
3fc6030776 | ||
|
|
8aebe61bf9 | ||
|
|
8c9e807c19 | ||
|
|
e25e4080c7 | ||
|
|
70a8353fe6 | ||
|
|
185b0d866a | ||
|
|
717667ac4c | ||
|
|
a0151caf02 | ||
|
|
c259331f57 | ||
|
|
f6bc8afa06 | ||
|
|
cee0aea5e6 | ||
|
|
c0e11f543d | ||
|
|
6b4cb357ce | ||
|
|
b38f902d70 | ||
|
|
a6085094a7 | ||
|
|
df359ab6dd | ||
|
|
d89da07d42 | ||
|
|
94098c1f1f | ||
|
|
e71e83a217 | ||
|
|
df0b21caed | ||
|
|
6921e621d7 | ||
|
|
ec9e03c266 | ||
|
|
1b0ef401fb | ||
|
|
1833983b8d | ||
|
|
149ca97c48 | ||
|
|
f01a7621b0 | ||
|
|
e6a1e2f169 | ||
|
|
6413059709 | ||
|
|
3c6fd37aba | ||
|
|
8dc90a3554 | ||
|
|
d6638742b0 | ||
|
|
51b1818de6 | ||
|
|
ec7c416097 | ||
|
|
0f30eeaec6 | ||
|
|
ddb158d5fa | ||
|
|
8b87117e0f | ||
|
|
e5000c2b6b | ||
|
|
0347e52118 | ||
|
|
bb9ba33e73 | ||
|
|
92c63d94b9 | ||
|
|
5a3cf04ba7 | ||
|
|
1fe74be8af | ||
|
|
376032f54c | ||
|
|
d6e744d948 | ||
|
|
23b3acb58c | ||
|
|
f32d0c1fad | ||
|
|
a69ed0b7cd | ||
|
|
b4d7024b98 | ||
|
|
222a2ce2e6 | ||
|
|
34a50a9173 | ||
|
|
72c6198ef7 | ||
|
|
e4df1048c8 | ||
|
|
e8abd06406 | ||
|
|
968c2bd699 | ||
|
|
12b6a3b291 | ||
|
|
c9f89e9c32 | ||
|
|
d27cff5c67 | ||
|
|
a734530cc3 | ||
|
|
cc51926d14 | ||
|
|
c0395dd0a3 | ||
|
|
51d7ca09d9 | ||
|
|
fce808cc48 | ||
|
|
4a9217eb25 | ||
|
|
69df98f4fa | ||
|
|
75339c0939 | ||
|
|
cf0a38d6bf | ||
|
|
dd9b13fbaa | ||
|
|
6a982a29cb | ||
|
|
ebc1b03158 | ||
|
|
adaf01f7fa | ||
|
|
c048f4eda9 | ||
|
|
b06272ff17 | ||
|
|
ac37bfab2d | ||
|
|
d8e50824cb | ||
|
|
29b27291a3 | ||
|
|
efa1b60585 | ||
|
|
9d0b39c5cb | ||
|
|
2a02a5b212 | ||
|
|
c6931b82a0 | ||
|
|
279e2202ba | ||
|
|
ec2324519e | ||
|
|
5932f50c68 | ||
|
|
00906e5d08 | ||
|
|
7bc5fca440 | ||
|
|
e30eb224ae | ||
|
|
983ea7cf03 | ||
|
|
ad2d453e33 | ||
|
|
15d711f6de | ||
|
|
7441a7d3d8 | ||
|
|
0d756209e9 | ||
|
|
b20c2ca173 | ||
|
|
987a186db3 | ||
|
|
f4e0b59fa1 | ||
|
|
0d2112d8d0 | ||
|
|
6fdab780a9 | ||
|
|
159e2eb7f6 | ||
|
|
9daa50dd7d | ||
|
|
9c2aadb7d5 | ||
|
|
7d0d81b16e | ||
|
|
22102deabc | ||
|
|
760a1c10c5 | ||
|
|
fac73f0e44 | ||
|
|
80b04e78c9 | ||
|
|
b23c0d75fe | ||
|
|
d4319d8a0e | ||
|
|
52cd4a9a0f | ||
|
|
3ef3ff3db9 | ||
|
|
549ec2047f | ||
|
|
ac241981f6 | ||
|
|
b3504dce9c | ||
|
|
f859d9f1c5 | ||
|
|
d433cf473f | ||
|
|
c6cd6cba20 | ||
|
|
9c7333d4f0 | ||
|
|
970f870245 | ||
|
|
a2dbaf86d2 | ||
|
|
b6162f0fc4 | ||
|
|
91cfa6aa8f | ||
|
|
cd152b83ae | ||
|
|
bac3ddddf5 | ||
|
|
b934fab89e | ||
|
|
dc4ea86819 | ||
|
|
4fd6bbd41d | ||
|
|
e4c1f0eefd | ||
|
|
e8a509e41c | ||
|
|
53560b7ce1 | ||
|
|
2aa2304d4d | ||
|
|
d4c8443c4a | ||
|
|
f9faf719d4 | ||
|
|
1a32e0c73f | ||
|
|
0ea3ef31ac | ||
|
|
d02613e2ec | ||
|
|
8ff036e5ff | ||
|
|
d0733e2a3b | ||
|
|
ad4f1b74f6 | ||
|
|
04d97e4a2d | ||
|
|
3f9e749122 | ||
|
|
aa1dd3e10a | ||
|
|
9f0deeb31c | ||
|
|
4f21ba9a5f | ||
|
|
bdc541e35f | ||
|
|
7350915006 | ||
|
|
d678ddbe11 | ||
|
|
7e92f85117 | ||
|
|
ffbeb0af43 | ||
|
|
1eba2a66af | ||
|
|
854ade8c06 | ||
|
|
ccb060262f | ||
|
|
981ad88fa1 | ||
|
|
8caaca1267 | ||
|
|
043e2acae4 | ||
|
|
1b7ef1c2c0 | ||
|
|
2af107c676 | ||
|
|
751a7394ff | ||
|
|
b1c0937bd2 | ||
|
|
d5f9c57ed3 | ||
|
|
f689a5871b | ||
|
|
092359f37f | ||
|
|
0fb71c1938 | ||
|
|
60d150c8d0 | ||
|
|
2dbfaf3ee0 | ||
|
|
a48f357928 | ||
|
|
a6e264d37b | ||
|
|
be0a09a222 | ||
|
|
4230cbf957 | ||
|
|
12ae46d10a | ||
|
|
4e6a65de9d | ||
|
|
4006544d11 | ||
|
|
67c311b458 | ||
|
|
e796a6f2ba | ||
|
|
f6cecfaaf9 | ||
|
|
572007139b | ||
|
|
47f3b72977 | ||
|
|
4d9c5331d8 | ||
|
|
d5482a32db | ||
|
|
61b346a948 | ||
|
|
c2dfe6a0dd | ||
|
|
81e8910519 | ||
|
|
577d5c2066 | ||
|
|
cce679b031 | ||
|
|
c2dbf45f26 | ||
|
|
d9036bc47b | ||
|
|
04d3a8c677 | ||
|
|
91019f3f85 | ||
|
|
4f61da553e | ||
|
|
ae11cbfded | ||
|
|
06278c3d97 | ||
|
|
36273b3114 | ||
|
|
51a0a9f23f | ||
|
|
fc24a65264 | ||
|
|
fa0b9003dc | ||
|
|
8a0da655e8 | ||
|
|
fff11daca7 | ||
|
|
ef83947eb5 | ||
|
|
750cfa1b82 | ||
|
|
fd011d76f4 | ||
|
|
0681a8301e | ||
|
|
221c58f1f7 | ||
|
|
244e5ee582 | ||
|
|
ebca75bf70 | ||
|
|
ea5bd59528 | ||
|
|
5977fa3244 | ||
|
|
81179a4c34 | ||
|
|
b5e516f974 | ||
|
|
996092c26f | ||
|
|
8fc5a49ca8 | ||
|
|
3c794bba0a | ||
|
|
2d3d53d144 | ||
|
|
00054aa9a5 | ||
|
|
6f3c98f704 | ||
|
|
69d05c8c15 | ||
|
|
8823957c6e | ||
|
|
47ec0cd8fe | ||
|
|
685031c39c | ||
|
|
e4fd89dbbb | ||
|
|
d11aeee083 | ||
|
|
4968e194dc | ||
|
|
36e53ee462 | ||
|
|
7e2e89be6d | ||
|
|
232801efdc | ||
|
|
e0dd0065fb | ||
|
|
98d7d402eb | ||
|
|
ba5dfd34f2 | ||
|
|
2701cd7e4c | ||
|
|
5429590797 | ||
|
|
c1dfda1321 | ||
|
|
57deec451a | ||
|
|
a7f38c946d | ||
|
|
8a4204e262 | ||
|
|
77090f4368 | ||
|
|
d655bb5638 | ||
|
|
bc8ea23e6d | ||
|
|
0a30b719f1 | ||
|
|
82c7b5ff85 | ||
|
|
faab6c02df | ||
|
|
de7631c5b3 | ||
|
|
3cdf823f74 | ||
|
|
ec91b6b3c2 | ||
|
|
a725119e0f | ||
|
|
520a8e736f | ||
|
|
4d5848161d | ||
|
|
8ee456d4e4 | ||
|
|
7b2c2e6084 | ||
|
|
133f75d34e | ||
|
|
68c9114896 | ||
|
|
a1a0b203f0 | ||
|
|
7059196ca2 | ||
|
|
d2ad2db930 | ||
|
|
b53d40dad8 | ||
|
|
84ca2f7af1 | ||
|
|
61655d3b53 | ||
|
|
b1263b85e9 | ||
|
|
49a7fa2d9a | ||
|
|
f1b0abc377 | ||
|
|
a62f0bf852 | ||
|
|
17165523f4 | ||
|
|
5395f0bb69 | ||
|
|
3bb5421a37 | ||
|
|
dd48e52a75 | ||
|
|
e18537eb67 | ||
|
|
82d81975d3 | ||
|
|
44497fd058 | ||
|
|
ff2b5ce0a0 | ||
|
|
a18c187818 | ||
|
|
f49949cc25 | ||
|
|
cbc723f129 | ||
|
|
3cc6aee7e6 | ||
|
|
624442b1c1 | ||
|
|
74e672b38b | ||
|
|
a0d169fea8 | ||
|
|
c80763100e | ||
|
|
6ed75ebb35 | ||
|
|
30d7734124 | ||
|
|
bca8b44189 | ||
|
|
36a069c90f | ||
|
|
757b37e8ed | ||
|
|
46b1163058 | ||
|
|
8c261b0409 | ||
|
|
fdacde57dd | ||
|
|
817dd7dde1 | ||
|
|
6279dd1467 | ||
|
|
abd4b94392 | ||
|
|
68c6ee77c0 | ||
|
|
d2b2812428 | ||
|
|
fe214cc3c2 | ||
|
|
2cca9506ad | ||
|
|
17e69aff8f | ||
|
|
c45331e645 | ||
|
|
4337001526 | ||
|
|
9eef1ec031 | ||
|
|
9d31bbc3aa | ||
|
|
911add3dea | ||
|
|
eed9ad99d8 | ||
|
|
a1d0793e95 | ||
|
|
ac9aad2ff5 | ||
|
|
1ed4ab5a8e | ||
|
|
b03aed69b6 | ||
|
|
1120d84c89 | ||
|
|
4e4671cba2 | ||
|
|
cdca330f1e | ||
|
|
bf8840cfbc | ||
|
|
89d4da8d50 | ||
|
|
74e441d233 | ||
|
|
58e5682e3d | ||
|
|
4915422646 | ||
|
|
2337d66ec5 | ||
|
|
e2c73d3c3d | ||
|
|
e4c0314368 | ||
|
|
228cd42b23 | ||
|
|
1c80492482 | ||
|
|
bfbd5c514b | ||
|
|
520bcfd72a | ||
|
|
566fde6ecc | ||
|
|
c44b5db09a | ||
|
|
477d0aa899 | ||
|
|
60171b477f | ||
|
|
9d4fb4d32b | ||
|
|
d5b9544f6b | ||
|
|
357e6ba59f | ||
|
|
0aca58af1e | ||
|
|
790caf6f0d | ||
|
|
d4fbae2bf3 | ||
|
|
da3bc74fe5 | ||
|
|
a1bd53100b | ||
|
|
46d8b19918 | ||
|
|
8ed31452b9 | ||
|
|
dfb8c3f329 | ||
|
|
de0045e2ee | ||
|
|
7dfb485558 | ||
|
|
81ac9d0dfd | ||
|
|
1430ef6158 | ||
|
|
8759bccc02 | ||
|
|
5dd0e31e2a | ||
|
|
a2c1586d26 | ||
|
|
7000698629 | ||
|
|
f52511721e | ||
|
|
94e84dd5bd | ||
|
|
ee0b1695fd | ||
|
|
e338e9005b | ||
|
|
00f2dea6ab | ||
|
|
4fe3ae89da | ||
|
|
195d703e1f | ||
|
|
a49e3492ec | ||
|
|
a81846241c | ||
|
|
e0d62096af | ||
|
|
77ee9bc529 | ||
|
|
e20c4a2d76 | ||
|
|
675e72aec3 | ||
|
|
8a45e6d642 | ||
|
|
171565a88c | ||
|
|
f8945e99a9 | ||
|
|
830a007a08 | ||
|
|
3072072c60 | ||
|
|
2bb672ea20 | ||
|
|
b6f7c61445 | ||
|
|
a9af4fa0ff | ||
|
|
e37369027e | ||
|
|
47eedc4433 | ||
|
|
3d4c151260 | ||
|
|
eadbb2b8e5 | ||
|
|
12ab0b9ac1 | ||
|
|
a723ad5e92 | ||
|
|
a459193bbd | ||
|
|
65c0eecc62 | ||
|
|
7f63675ee5 | ||
|
|
35f236f17e | ||
|
|
4131754c32 | ||
|
|
8ead3a7ce5 | ||
|
|
2aa055e164 | ||
|
|
2e2e0b8dce | ||
|
|
42897dc211 | ||
|
|
d80a8f35ec | ||
|
|
7a6dcc28b2 | ||
|
|
3f78c011df | ||
|
|
55aa159b56 | ||
|
|
6225427363 | ||
|
|
81d922d490 | ||
|
|
0ba357dab7 | ||
|
|
03b4c3cd12 | ||
|
|
265b2aaf83 | ||
|
|
fbb22f1e9d | ||
|
|
280e1e25e3 | ||
|
|
c7306a0c57 | ||
|
|
4af29dd034 | ||
|
|
4d70862344 | ||
|
|
d40ef934e8 | ||
|
|
7af0b2b938 | ||
|
|
49ee543b5e | ||
|
|
f68be96d86 | ||
|
|
99721a2e56 | ||
|
|
e4b6678538 | ||
|
|
bb443269c3 | ||
|
|
2e97deb894 | ||
|
|
cabec79dde | ||
|
|
6f9afdc2d3 | ||
|
|
3e8d4fdd53 | ||
|
|
35d5e4b39e | ||
|
|
aeb521e6d9 | ||
|
|
5228dc43c9 | ||
|
|
4ba7731dce | ||
|
|
1afe45d2f2 | ||
|
|
7e574191eb | ||
|
|
4ac35b5b6d | ||
|
|
88a6cc132f | ||
|
|
574509e83a | ||
|
|
f9ce03113e | ||
|
|
b0eda8350a | ||
|
|
9cb5ea332a | ||
|
|
1f409493de | ||
|
|
5ac310ff9f | ||
|
|
9a96203f8d | ||
|
|
5239a05734 | ||
|
|
4bed591002 | ||
|
|
aede27f986 | ||
|
|
8aeb89e878 | ||
|
|
31cec1986b | ||
|
|
d7ae755ce4 | ||
|
|
d425c18b9a | ||
|
|
57ff9e2d1f | ||
|
|
1cac820ec6 | ||
|
|
91d94abaf2 | ||
|
|
eb4646beca | ||
|
|
5447d6f10b | ||
|
|
dbb040eb07 | ||
|
|
9960320645 | ||
|
|
5730c933ee | ||
|
|
68be1274bf | ||
|
|
e4180308ea | ||
|
|
14fec451ec | ||
|
|
916073ee2a | ||
|
|
2b48954693 | ||
|
|
d789ee031f | ||
|
|
a58a73b9f2 | ||
|
|
4172e9f160 | ||
|
|
04baa537b9 | ||
|
|
4a2649c2d1 | ||
|
|
0845853e42 | ||
|
|
933be40123 | ||
|
|
c385b9a501 | ||
|
|
2c1774dc46 | ||
|
|
77d7b4b99d | ||
|
|
3c6dca4779 | ||
|
|
1eba3a3e92 | ||
|
|
ef30e47b4b | ||
|
|
384e6dd3be | ||
|
|
5b61efc2cc | ||
|
|
2623fa465c | ||
|
|
b5c8ea8e1c | ||
|
|
352026e8ab | ||
|
|
4a56365495 | ||
|
|
dfed6883f8 | ||
|
|
4473463f4a | ||
|
|
b79619dcb3 | ||
|
|
fe54274ac1 | ||
|
|
61c1a38ba4 | ||
|
|
76c1341549 | ||
|
|
9d1bd08b77 | ||
|
|
6cf7b974c1 | ||
|
|
edb030e348 | ||
|
|
f644fc6610 | ||
|
|
8c7b721903 | ||
|
|
6b16f7a0c3 | ||
|
|
f3a6c752b4 | ||
|
|
0e053b2d47 | ||
|
|
f462139327 | ||
|
|
8e32fd8688 | ||
|
|
2066543754 | ||
|
|
a251aaabea | ||
|
|
da95d6de08 | ||
|
|
cb367c38c8 | ||
|
|
4acefe350c | ||
|
|
db8fa169d9 | ||
|
|
d998fdf6f6 | ||
|
|
6cdedd63d9 | ||
|
|
1703bc6c74 | ||
|
|
49d8287b24 | ||
|
|
b6af85c91e | ||
|
|
308ab80736 | ||
|
|
73630b4d94 | ||
|
|
cb8dfcadbc | ||
|
|
b759f29ab8 | ||
|
|
b826adabbf | ||
|
|
7dd3d57800 | ||
|
|
e0670a8742 | ||
|
|
61f62660ca | ||
|
|
acca37fc4b | ||
|
|
88b01a14a6 | ||
|
|
30917c9b2e | ||
|
|
4c22c75b45 | ||
|
|
e9baca6781 | ||
|
|
46983ca49b | ||
|
|
c3e9790436 | ||
|
|
2b4e839926 | ||
|
|
c7aae14e03 | ||
|
|
2444344929 | ||
|
|
d247183892 | ||
|
|
a81fe66e8f | ||
|
|
6d43977b12 | ||
|
|
14c00e5dd4 | ||
|
|
be492a8184 | ||
|
|
90149ac411 | ||
|
|
7bae6e43b7 | ||
|
|
32c2435cb1 | ||
|
|
64feacd2c1 | ||
|
|
263fe7a4b3 | ||
|
|
8049f1d972 | ||
|
|
f3263e60ff | ||
|
|
79dd3eecb6 | ||
|
|
62bbe9a09b | ||
|
|
4dd80a65da | ||
|
|
52dd1d37ab | ||
|
|
b75df6fa5c | ||
|
|
b7639cffd3 | ||
|
|
21575f2e79 | ||
|
|
6d6849707e | ||
|
|
11ac24e3f4 | ||
|
|
7a30f3c025 | ||
|
|
c84f96c436 | ||
|
|
0ecf666c9f | ||
|
|
42e4969f92 | ||
|
|
35587aaacc | ||
|
|
2398dedbae | ||
|
|
d545a3b8d6 | ||
|
|
dce673ff7a | ||
|
|
d18f6bf371 | ||
|
|
32b05b69a3 | ||
|
|
c1a5c3cad7 | ||
|
|
e9abaee255 | ||
|
|
ba998255c8 | ||
|
|
d7c1ce70e2 | ||
|
|
e82cf3dd71 | ||
|
|
5542fdbbcb | ||
|
|
c715587a54 | ||
|
|
d9ac4d8dc6 | ||
|
|
f35a2cca8a | ||
|
|
6dd6e225c9 | ||
|
|
b84b682a4e | ||
|
|
3f3c0da7e3 | ||
|
|
1ad823b52e | ||
|
|
7dd958e920 | ||
|
|
41d977d3b0 | ||
|
|
a9a3ad6e67 | ||
|
|
ca2bde4748 | ||
|
|
9c3945b82a | ||
|
|
55d95f9469 | ||
|
|
dbdb5820d6 | ||
|
|
bd9dc51744 | ||
|
|
984cd34822 | ||
|
|
e7b8c95e21 | ||
|
|
2fbebfb60f | ||
|
|
86b2d98ba8 | ||
|
|
bd8efcdb4d | ||
|
|
77b25e55c6 | ||
|
|
9df131fd8d | ||
|
|
2aca9bee03 | ||
|
|
fe279ea94c | ||
|
|
8481b3bf16 | ||
|
|
301334c1ff | ||
|
|
3311828432 | ||
|
|
480d9edb6a | ||
|
|
07b62cc292 | ||
|
|
f03f8dfeaa | ||
|
|
05e0b3d795 | ||
|
|
7ce08753f1 | ||
|
|
01e9255d3b | ||
|
|
0e918f60e3 | ||
|
|
b08c5ec1d2 | ||
|
|
87f24fa985 | ||
|
|
86df2f0132 | ||
|
|
0e1654a474 | ||
|
|
05f447baae | ||
|
|
85e2bb230e | ||
|
|
e21f87f920 | ||
|
|
052041d134 | ||
|
|
d0e6ff8d72 | ||
|
|
bf8ab931ad | ||
|
|
5532271f14 | ||
|
|
123a57169a | ||
|
|
7d84191693 | ||
|
|
b0101f8157 | ||
|
|
8865450d28 | ||
|
|
0fb12fc437 | ||
|
|
b462a380af | ||
|
|
1e94004364 | ||
|
|
76e7a81eef | ||
|
|
1445e4ab80 | ||
|
|
6c8534a709 | ||
|
|
99565ed03a | ||
|
|
1d3b9f9152 | ||
|
|
c3f4e6baec | ||
|
|
7f0d24b113 | ||
|
|
52a649a352 | ||
|
|
d4a89c4328 | ||
|
|
508720745f | ||
|
|
2fde6a47aa | ||
|
|
22709e6fe0 | ||
|
|
4ee3f7229c | ||
|
|
09e0e17559 | ||
|
|
7b6656e99e | ||
|
|
b15a295a73 | ||
|
|
fe384dfff6 | ||
|
|
943baffcf8 | ||
|
|
2fb83514cc | ||
|
|
863eb73ad1 | ||
|
|
5a41a9e7b9 | ||
|
|
f89e2772bc | ||
|
|
7ca45c4a24 | ||
|
|
b4449b92bb | ||
|
|
10738a7c3d | ||
|
|
08fdb0316a | ||
|
|
6ebc5967f4 | ||
|
|
11a3da61e1 | ||
|
|
ef1592dbee | ||
|
|
d9152ed556 | ||
|
|
509c8fad3f | ||
|
|
19b4f6d21d | ||
|
|
d705ef7e14 | ||
|
|
b99cc2a665 | ||
|
|
4e6e87aadd | ||
|
|
561924f18f | ||
|
|
80d5c32405 | ||
|
|
4b33a13758 | ||
|
|
8878c1eebd | ||
|
|
c10ad968f9 | ||
|
|
a19b817818 | ||
|
|
7192b142a3 | ||
|
|
fc34a18934 | ||
|
|
d7ce6fa254 | ||
|
|
ed94e2ca2c | ||
|
|
0c6e05e7ab | ||
|
|
a6e966438b | ||
|
|
f668bc05f2 | ||
|
|
4d95f8ea8e | ||
|
|
9172558481 | ||
|
|
fbdf091dd8 | ||
|
|
16c4fad514 | ||
|
|
6e7591ab38 | ||
|
|
a85875bc53 | ||
|
|
e1b25750b9 | ||
|
|
eae6c27566 | ||
|
|
f66079ea22 | ||
|
|
2a35761401 | ||
|
|
103a82075d | ||
|
|
e85ab79026 | ||
|
|
5f3680e749 | ||
|
|
5956217b41 | ||
|
|
03c0d81ca1 | ||
|
|
0d53f378a0 | ||
|
|
428ec8c170 | ||
|
|
4cc442babc | ||
|
|
1f9b94c0c7 | ||
|
|
4c9d2b5a41 | ||
|
|
183e384819 | ||
|
|
7295abeac5 | ||
|
|
77f1351000 | ||
|
|
ba3e09a2a0 | ||
|
|
21890eb99d | ||
|
|
489de02696 | ||
|
|
4c3be94376 | ||
|
|
37180974a8 | ||
|
|
8cf1aa5ac4 | ||
|
|
7aee91eb83 | ||
|
|
8890c84d39 | ||
|
|
2b301c723b | ||
|
|
6e7f4facbd | ||
|
|
a2ec72eab2 | ||
|
|
7ac660820f | ||
|
|
a8d33d14c8 | ||
|
|
72c33640f6 | ||
|
|
fd21db18fc | ||
|
|
92e9ea50d4 | ||
|
|
d1c4f16df0 | ||
|
|
738940a634 | ||
|
|
a0d3dcafdc | ||
|
|
9bae5569de | ||
|
|
6f6fcf25e7 | ||
|
|
430cf732a6 | ||
|
|
9851227258 | ||
|
|
3c302cb675 | ||
|
|
0fb11b4f98 | ||
|
|
cee3750f2e | ||
|
|
140c5fc928 | ||
|
|
fd331a5692 | ||
|
|
7c12a16ad4 | ||
|
|
079963fb47 | ||
|
|
82f0ebaf6f | ||
|
|
3772ef9645 | ||
|
|
0e67bdccfc | ||
|
|
39e33f4fac | ||
|
|
358dec2e09 | ||
|
|
b76b9aa267 | ||
|
|
f2f10c58d7 | ||
|
|
3d77fff140 | ||
|
|
9cb111373b | ||
|
|
e07fb36c26 | ||
|
|
79bb915b43 | ||
|
|
9926c485e7 | ||
|
|
abbda85c16 | ||
|
|
d77fb9db0c | ||
|
|
595b1c71b4 | ||
|
|
625ddd5db9 | ||
|
|
9e1fa1adc6 | ||
|
|
d87c081055 | ||
|
|
f224b2b4ea | ||
|
|
f70d34fb0b | ||
|
|
a075f90890 | ||
|
|
66686e4f71 | ||
|
|
e8a6f9d123 | ||
|
|
9246c009b8 | ||
|
|
afa43dc248 | ||
|
|
effa15082e | ||
|
|
958f534a16 | ||
|
|
2a648a620c | ||
|
|
f10d13be3b | ||
|
|
e8f7e90220 | ||
|
|
6bf16fd52c | ||
|
|
f290f7716e | ||
|
|
3a83fdf005 | ||
|
|
88ca9fe406 | ||
|
|
00e5847fbe | ||
|
|
7d54bc8cdd | ||
|
|
b684ca1fd6 | ||
|
|
3c934e97ac | ||
|
|
f143a4aa4d | ||
|
|
0c07fcee1c | ||
|
|
eca9698bbc | ||
|
|
de66d90b85 | ||
|
|
4bc815a576 | ||
|
|
0bd4fea0dd | ||
|
|
35c310dc47 | ||
|
|
f80442793a | ||
|
|
98be05f697 | ||
|
|
0b6e9e6c4d | ||
|
|
45046f571f | ||
|
|
4ccad8ac0b | ||
|
|
e4d68bb56b | ||
|
|
ab7c560fbb | ||
|
|
32c820be56 | ||
|
|
f5d7c9fd6b | ||
|
|
6b90c6048b | ||
|
|
80bb793e3a | ||
|
|
b275ffbe01 | ||
|
|
49f60bda67 | ||
|
|
8b52f180af | ||
|
|
46365a35fe | ||
|
|
f3232b5361 | ||
|
|
2b8f5ed3ca | ||
|
|
fcf0baee2e | ||
|
|
07183ffe5b | ||
|
|
ac9dab0b3b | ||
|
|
11c36460ac | ||
|
|
e0d7b266e7 | ||
|
|
3706360ffc | ||
|
|
3d7a20ca64 | ||
|
|
9b1227915e | ||
|
|
dbb024e4da | ||
|
|
381ecaaf96 | ||
|
|
25435ba597 | ||
|
|
c56fd81bff | ||
|
|
707bf332d0 | ||
|
|
40225635f1 | ||
|
|
e4ff701488 | ||
|
|
314cdee815 | ||
|
|
fa549f49b7 | ||
|
|
c38460afd2 | ||
|
|
7657c8469e | ||
|
|
97abe86d7e | ||
|
|
a339dfaeb0 | ||
|
|
15356960e9 | ||
|
|
3710776d09 | ||
|
|
dffc2ee2fe | ||
|
|
cd47928252 | ||
|
|
4b84ede0b5 | ||
|
|
0de4d684d8 | ||
|
|
d78a5364f6 | ||
|
|
228dfa1799 | ||
|
|
74a66eaa94 | ||
|
|
96670db342 | ||
|
|
e4655c3705 | ||
|
|
5a5f767b3d | ||
|
|
9c8038ffb6 | ||
|
|
a524cd38a1 | ||
|
|
70960e917d | ||
|
|
a28d899e5c | ||
|
|
20ef6474d4 | ||
|
|
6947593414 | ||
|
|
23369dce80 | ||
|
|
a7cd61ce75 | ||
|
|
c702d632a2 | ||
|
|
9b90ae195c | ||
|
|
7820232282 | ||
|
|
32d588451a | ||
|
|
ae56765efa | ||
|
|
467decfcce | ||
|
|
2ab17d0569 | ||
|
|
543517f80a | ||
|
|
9ef0be665e | ||
|
|
87ef6d5064 | ||
|
|
e2ec51d3db | ||
|
|
566be392da | ||
|
|
7497ed2a7c | ||
|
|
13b0dfc38f | ||
|
|
1debe44cf1 | ||
|
|
b737e14069 | ||
|
|
82fd148370 | ||
|
|
d0fc6dbc13 | ||
|
|
f588c3f265 | ||
|
|
659e80bbf4 | ||
|
|
3585e2acde | ||
|
|
98554523c8 | ||
|
|
dbeaa35110 | ||
|
|
61c719c728 | ||
|
|
48fddecd30 | ||
|
|
f5779077df | ||
|
|
5149fb6fbf | ||
|
|
dcc6134180 | ||
|
|
ecb336829d | ||
|
|
2778f919a6 | ||
|
|
ab6fcc7a7a | ||
|
|
aa284802ab | ||
|
|
a6af56bc2e | ||
|
|
751f0b4d10 | ||
|
|
a233eb6396 | ||
|
|
c91d9b99be | ||
|
|
a5b798a257 | ||
|
|
18b71442ca | ||
|
|
f350e902a8 | ||
|
|
38a6447887 | ||
|
|
39da70558b | ||
|
|
73f62966ed | ||
|
|
c41e035b67 | ||
|
|
f4c466ea30 | ||
|
|
91e9a267f0 | ||
|
|
d1ec24a05b | ||
|
|
657159e034 | ||
|
|
0327913e53 | ||
|
|
5ad9d652be | ||
|
|
3cde94a94b | ||
|
|
a7f036c71f | ||
|
|
20b47ad8af | ||
|
|
6f2eac383d | ||
|
|
5d40e83297 | ||
|
|
7721ccffaf | ||
|
|
aa7edc8e3f | ||
|
|
bba1728988 | ||
|
|
f1dff3e3fd | ||
|
|
d5604b9eb5 | ||
|
|
3acf61e2c3 | ||
|
|
3a0d25d048 | ||
|
|
28720043a4 | ||
|
|
79491353da | ||
|
|
35eb8829ea | ||
|
|
5cd38d5974 | ||
|
|
30f1fa1557 | ||
|
|
fe035c3b3f | ||
|
|
1efe8ee6c7 | ||
|
|
f410a8540b | ||
|
|
acb6249292 | ||
|
|
77c38a1c4b | ||
|
|
6e1d56a9b0 | ||
|
|
d10a7b9c76 | ||
|
|
209f295399 | ||
|
|
cac2ec9aa1 | ||
|
|
30a9e7a94f | ||
|
|
af5adc37ca | ||
|
|
af12226998 | ||
|
|
6a147c2d28 | ||
|
|
b8bf440f84 | ||
|
|
c2b62a0fee | ||
|
|
301458e713 | ||
|
|
5198a249d1 | ||
|
|
918322bc4a | ||
|
|
b66791d81a | ||
|
|
a2fbd9ed22 | ||
|
|
89111c5bf3 | ||
|
|
1a979b64b3 | ||
|
|
d4a358d0f9 | ||
|
|
daf24d65f6 | ||
|
|
9517cef183 | ||
|
|
f70950cc82 | ||
|
|
ec3dffa3e4 | ||
|
|
207bea3c1c | ||
|
|
fcae20d77c | ||
|
|
0a542941ad | ||
|
|
5dceef8c03 | ||
|
|
fb01eb42f1 | ||
|
|
e1e3a8352e | ||
|
|
09678c4277 | ||
|
|
5b83669368 | ||
|
|
30c8c922a3 | ||
|
|
e05b327eb5 | ||
|
|
06665cbbf7 | ||
|
|
c0425df2ee | ||
|
|
42cbef8063 | ||
|
|
d1abf06621 | ||
|
|
1c00020859 | ||
|
|
00dd9cea99 | ||
|
|
607cd0ab97 | ||
|
|
9f65e56b21 | ||
|
|
82c5d1bf5c | ||
|
|
deda3e571f | ||
|
|
2d4039c5d2 | ||
|
|
63dd9190a2 | ||
|
|
3dfa630923 | ||
|
|
63daf6af09 | ||
|
|
c85ccd96aa | ||
|
|
8b7b57fe2c | ||
|
|
403b430ee4 | ||
|
|
f58845ef74 | ||
|
|
38fad35945 | ||
|
|
2533740a7c | ||
|
|
109a7d6128 | ||
|
|
ec1906e262 | ||
|
|
ad40b883eb | ||
|
|
1c64ae1bba | ||
|
|
6f8028253d | ||
|
|
0704a76006 | ||
|
|
ab93a0b679 | ||
|
|
ed85560547 | ||
|
|
a242adf3b3 | ||
|
|
1a9c7ecefd | ||
|
|
aded528a27 | ||
|
|
152ae9865e | ||
|
|
8bd92b44f5 | ||
|
|
8d15e04fd0 | ||
|
|
b7f3ece0f1 | ||
|
|
4c94ae3da2 | ||
|
|
cd23ce0a51 | ||
|
|
8538036af7 | ||
|
|
6ee7133924 | ||
|
|
4fa19ece52 | ||
|
|
875ac4b84f | ||
|
|
346e797447 | ||
|
|
6ce20517bc | ||
|
|
9509ceb975 | ||
|
|
bcec3be423 | ||
|
|
e3958d1609 | ||
|
|
7f6d778301 | ||
|
|
40287a5682 | ||
|
|
bcfe4b794e | ||
|
|
7a27193ceb | ||
|
|
6db14cefa0 | ||
|
|
b57e1a64ac | ||
|
|
a1b83cceea | ||
|
|
0fc7291178 | ||
|
|
cd29438304 | ||
|
|
7695a817ca | ||
|
|
51d1cc677b | ||
|
|
0cf7a7aa85 | ||
|
|
2ddce1a2de | ||
|
|
49a8b54a8a | ||
|
|
04b15c7d4a | ||
|
|
5909e7e894 | ||
|
|
7a3d519970 | ||
|
|
35bbcd94e6 | ||
|
|
f6629b2b58 | ||
|
|
b4891db9fa | ||
|
|
4e16575f97 | ||
|
|
12ab20ecd9 | ||
|
|
2c3a40c94b | ||
|
|
0d8175ba1c | ||
|
|
311ed74709 | ||
|
|
c86301fcfd | ||
|
|
032f69f692 | ||
|
|
6dc52b2a6d | ||
|
|
1232c0240a | ||
|
|
55fa74f44a | ||
|
|
627e04151d | ||
|
|
5d91f9f8dc | ||
|
|
08f1f77834 | ||
|
|
ea882aba63 | ||
|
|
513a78ab9b | ||
|
|
406ab9b0da | ||
|
|
140f6616cb | ||
|
|
b09589b15e | ||
|
|
00638897f4 | ||
|
|
4cd68c1930 | ||
|
|
f1f3607e28 | ||
|
|
7b9f503b9e | ||
|
|
a18f44399c | ||
|
|
5094e6d536 | ||
|
|
39242e7f68 | ||
|
|
be7e208b1c | ||
|
|
c3bffbf4de | ||
|
|
d46467f318 | ||
|
|
6d21400803 | ||
|
|
db53161296 | ||
|
|
cb4a116cec | ||
|
|
013199b9b2 | ||
|
|
a8b74ff703 | ||
|
|
b1985a9161 | ||
|
|
3bf512719f | ||
|
|
d83ed69d41 | ||
|
|
fdde8db664 | ||
|
|
dd5581ba2d | ||
|
|
07fe2b9dcf | ||
|
|
b297c93b80 | ||
|
|
d2e62e5b44 | ||
|
|
fe73ce7514 | ||
|
|
0fed16cea4 | ||
|
|
0503479fcf | ||
|
|
08804ad435 | ||
|
|
79fa7724ad | ||
|
|
4604c2184a | ||
|
|
11bac697fb | ||
|
|
96cec27f89 | ||
|
|
bcd412b8f9 | ||
|
|
6b456e2841 | ||
|
|
1df72b3dc8 | ||
|
|
402d692739 | ||
|
|
fad46b2fb5 | ||
|
|
0153c80cf2 | ||
|
|
4f75dc8d97 | ||
|
|
e2c79cccb5 | ||
|
|
c14a0e05be | ||
|
|
016dd3a5dd | ||
|
|
5c4ca9103c | ||
|
|
b4aad92907 | ||
|
|
56d1e095a1 | ||
|
|
6dee9aae49 | ||
|
|
a10bff981e | ||
|
|
e333f7884a | ||
|
|
8ea7bf237b | ||
|
|
5e8409fa08 | ||
|
|
23354c1767 | ||
|
|
0efb69d0db | ||
|
|
0462c4e41b | ||
|
|
70a5673197 | ||
|
|
9ec3203485 | ||
|
|
1a46f9b2e1 | ||
|
|
77bb26a8d7 | ||
|
|
2a36dcf592 | ||
|
|
fc2bcadbe2 | ||
|
|
46b0cc1adb | ||
|
|
d75d1c6a3f | ||
|
|
13b236aeed | ||
|
|
1c0a61b0b2 | ||
|
|
ceb92438f4 | ||
|
|
9bdd62ed4c | ||
|
|
3f5ef7e799 | ||
|
|
e7a573fa94 | ||
|
|
364060eaa7 | ||
|
|
a3ddc3d5e0 | ||
|
|
185f747adb | ||
|
|
895d281088 | ||
|
|
b44e4354f7 | ||
|
|
3fcb0665ec | ||
|
|
be49d33ce5 | ||
|
|
c7dae1608b | ||
|
|
b020c5868b | ||
|
|
eae42d3afe | ||
|
|
a816e2436e | ||
|
|
b09e35e372 | ||
|
|
d2c8121c9c | ||
|
|
380bb88ffa | ||
|
|
e0e3726b3c | ||
|
|
c2253a7979 | ||
|
|
9d244226ec | ||
|
|
71df5e3a59 | ||
|
|
3a738dad88 | ||
|
|
56d301cfde | ||
|
|
5efec6d5ea | ||
|
|
32769b24f1 | ||
|
|
6ab53c263d | ||
|
|
e7f3e4e242 | ||
|
|
8bb5201647 | ||
|
|
a9fc94f53d | ||
|
|
ca7c0510d1 | ||
|
|
1bf16f0859 | ||
|
|
21b503c188 | ||
|
|
0053e9796c | ||
|
|
e84941b1b1 | ||
|
|
57f6f8d67e | ||
|
|
5b8e69d973 | ||
|
|
7213b1bfa3 | ||
|
|
11f0770f08 | ||
|
|
44e6dc3ae8 | ||
|
|
b5d20d2488 | ||
|
|
0185545838 | ||
|
|
8c8f85859c | ||
|
|
104384409e | ||
|
|
179827ae56 | ||
|
|
6645829876 | ||
|
|
68cb302722 | ||
|
|
8dc33f2790 | ||
|
|
5f64ba326c | ||
|
|
7ccb15107c | ||
|
|
b102964743 | ||
|
|
216d50a09c | ||
|
|
07ea59fdcb | ||
|
|
932a84a47f | ||
|
|
34dda7bdbd | ||
|
|
49fa153581 | ||
|
|
c80b827775 | ||
|
|
a2bf9f988a | ||
|
|
ac27b2d5c2 | ||
|
|
c813518fdc | ||
|
|
d5034ed5c3 | ||
|
|
cf2c29a365 | ||
|
|
d948823db6 | ||
|
|
060ad4630d |
@@ -1,24 +1,35 @@
|
||||
{
|
||||
"$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": []
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [
|
||||
[
|
||||
"cojson",
|
||||
"cojson-storage",
|
||||
"cojson-storage-indexeddb",
|
||||
"cojson-storage-sqlite",
|
||||
"cojson-transport-ws",
|
||||
"jazz-browser",
|
||||
"jazz-browser-auth-clerk",
|
||||
"jazz-browser-media-images",
|
||||
"jazz-nodejs",
|
||||
"jazz-react",
|
||||
"jazz-react-auth-clerk",
|
||||
"jazz-react-native",
|
||||
"jazz-react-native-auth-clerk",
|
||||
"jazz-react-native-media-images",
|
||||
"jazz-run",
|
||||
"jazz-svelte",
|
||||
"jazz-tools",
|
||||
"jazz-vue"
|
||||
]
|
||||
],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
}
|
||||
}
|
||||
|
||||
5
.changeset/healthy-pianos-rest.md
Normal file
5
.changeset/healthy-pianos-rest.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Return field name on \_edits
|
||||
5
.changeset/loud-llamas-compare.md
Normal file
5
.changeset/loud-llamas-compare.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Simplify the .create calls by accepting directly "Account | Group" as second param
|
||||
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# EditorConfig is awesome: https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Formatted workspace with biome
|
||||
be0a09a22295cd5d2ee3ef323e2d999da8a14110
|
||||
171
.github/workflows/build-and-deploy.yaml
vendored
171
.github/workflows/build-and-deploy.yaml
vendored
@@ -1,171 +0,0 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["chat", "pets", "todo"]
|
||||
# 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: 'pnpm'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: gardencmp
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
|
||||
- name: Docker Build & Push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ./examples/${{ matrix.example }}
|
||||
push: true
|
||||
tags: ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build-homepage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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: 'pnpm'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: gardencmp
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Pnpm Install (root)
|
||||
run: |
|
||||
pnpm install
|
||||
working-directory: .
|
||||
|
||||
- name: Pnpm Install & Build (homepage)
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm build;
|
||||
working-directory: ./homepage/homepage
|
||||
|
||||
- name: Docker Build & Push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: ./homepage
|
||||
push: true
|
||||
tags: ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
deploy-examples:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-examples
|
||||
strategy:
|
||||
matrix:
|
||||
example: ["chat", "pets", "todo"]
|
||||
# example: ["twit", "chat", "counter-js-auth0", "pets", "twit", "file-drop", "inspector"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: gacts/install-nomad@v1
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v1
|
||||
with:
|
||||
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
|
||||
|
||||
- name: Deploy on Nomad
|
||||
run: |
|
||||
if [ "${{github.ref_name}}" == "main" ]; then
|
||||
export BRANCH_SUFFIX="";
|
||||
export BRANCH_SUBDOMAIN="";
|
||||
else
|
||||
export BRANCH_SUFFIX=-${{github.head_ref || github.ref_name}};
|
||||
export BRANCH_SUBDOMAIN=${{github.head_ref || github.ref_name}}.;
|
||||
fi
|
||||
|
||||
export DOCKER_USER=gardencmp;
|
||||
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
|
||||
export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-example-${{ matrix.example }}:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
|
||||
|
||||
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
|
||||
cat job-instance.nomad;
|
||||
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
|
||||
deploy-homepage:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-homepage
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: gacts/install-nomad@v1
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v1
|
||||
with:
|
||||
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
|
||||
|
||||
- name: Deploy on Nomad
|
||||
run: |
|
||||
if [ "${{github.ref_name}}" == "main" ]; then
|
||||
export BRANCH_SUFFIX="";
|
||||
export BRANCH_SUBDOMAIN="";
|
||||
else
|
||||
export BRANCH_SUFFIX=-${{github.head_ref || github.ref_name}};
|
||||
export BRANCH_SUBDOMAIN=${{github.head_ref || github.ref_name}}.;
|
||||
fi
|
||||
|
||||
export DOCKER_USER=gardencmp;
|
||||
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
|
||||
export DOCKER_TAG=ghcr.io/gardencmp/${{github.event.repository.name}}-homepage-jazz:${{github.head_ref || github.ref_name}}-${{github.sha}}-${{github.run_number}}-${{github.run_attempt}};
|
||||
|
||||
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN}' < job-template.nomad > job-instance.nomad;
|
||||
cat job-instance.nomad;
|
||||
NOMAD_ADDR=${{ secrets.NOMAD_ADDR }} nomad job run job-instance.nomad;
|
||||
working-directory: ./homepage
|
||||
61
.github/workflows/build-examples.yaml
vendored
Normal file
61
.github/workflows/build-examples.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build-examples:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
example: [
|
||||
"chat",
|
||||
"clerk",
|
||||
"passkey",
|
||||
"inspector",
|
||||
"music-player",
|
||||
"password-manager",
|
||||
"pets",
|
||||
"reactions",
|
||||
"todo",
|
||||
"onboarding",
|
||||
]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
18
.github/workflows/code-quality.yml
vendored
Normal file
18
.github/workflows/code-quality.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Code quality
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Biome
|
||||
uses: biomejs/setup-biome@v2
|
||||
with:
|
||||
version: latest
|
||||
- name: Run Biome
|
||||
run: biome ci .
|
||||
51
.github/workflows/jazz-run.yml
vendored
Normal file
51
.github/workflows/jazz-run.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Jazz Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build jazz-run
|
||||
run: pnpm exec turbo build && chmod +x dist/index.js;
|
||||
working-directory: ./packages/jazz-run
|
||||
|
||||
- name: Run create account
|
||||
run: ./dist/index.js account create --name "Jazz Run CI test"
|
||||
working-directory: ./packages/jazz-run
|
||||
|
||||
65
.github/workflows/playwright.yml
vendored
Normal file
65
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Playwright Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "examples/onboarding"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build
|
||||
working-directory: ./${{ matrix.project }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install
|
||||
working-directory: ./${{ matrix.project }}
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm exec playwright test
|
||||
working-directory: ./${{ matrix.project }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: ${{ hashFiles(format('{0}/package.json', matrix.project)) }}-playwright-report
|
||||
path: ./${{ matrix.project }}/playwright-report/
|
||||
retention-days: 30
|
||||
65
.github/workflows/release.yml
vendored
Normal file
65
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug_enabled:
|
||||
type: boolean
|
||||
description: "Run tmate session for debugging"
|
||||
required: false
|
||||
default: false
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm changeset-version
|
||||
publish: pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
# Enable tmate debugging only if the workflow is manually triggered, debug_enabled is true, and the workflow failed
|
||||
- name: Setup tmate session for debugging
|
||||
if: ${{ failure() && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
with:
|
||||
timeout-minutes: 15
|
||||
47
.github/workflows/unit-test.yml
vendored
Normal file
47
.github/workflows/unit-test.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Unit Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build --filter="./packages/*"
|
||||
|
||||
- name: Unit Tests
|
||||
run: pnpm test:ci
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -3,4 +3,19 @@ yarn-error.log
|
||||
lerna-debug.log
|
||||
docsTmp
|
||||
.DS_Store
|
||||
.turbo
|
||||
.turbo
|
||||
coverage
|
||||
.direnv
|
||||
|
||||
# Next.js
|
||||
**/.next
|
||||
|
||||
# Vite output
|
||||
**/dist
|
||||
|
||||
# Playwright
|
||||
test-results
|
||||
|
||||
.husky
|
||||
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
1
.node-version
Normal file
1
.node-version
Normal file
@@ -0,0 +1 @@
|
||||
22
|
||||
131
CODE_OF_CONDUCT.md
Normal file
131
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to [the community leaders responsible for enforcement](mailto:hello@garden.co).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
81
CONTRIBUTING.md
Normal file
81
CONTRIBUTING.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Contribution Guide
|
||||
|
||||
Thank you for considering contributing to Jazz! Jazz is an open-source framework for building local-first apps. We value your time and effort and are excited to collaborate with you. This guide will help you get started with contributing.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
### 1. Reporting Bugs
|
||||
|
||||
If you find a bug, please [open an issue with as much detail as possible](https://github.com/garden-co/jazz/issues). Include:
|
||||
|
||||
- A clear and descriptive title.
|
||||
- Steps to reproduce the issue.
|
||||
- What you expected to happen.
|
||||
- What actually happened.
|
||||
|
||||
### 2. Suggesting Enhancements
|
||||
|
||||
We welcome all ideas! If you have suggestions, feel free to open an issue marked with the "enhancement" label. Please provide context on why the enhancement would be beneficial and how it could be implemented.
|
||||
|
||||
### 3. Pull Requests
|
||||
|
||||
1. **Fork the repository** and create your feature branch (see [GitHub's guide on forking a repository](https://docs.github.com/en/get-started/quickstart/fork-a-repo) if you're unfamiliar with the process):
|
||||
|
||||
2. **Make your changes**, ensuring that you follow our coding standards (`pnpm format` (prettier) and `pnpm lint` (eslint) will automatically let you know there are issues).
|
||||
|
||||
3. **Commit your changes** with a descriptive commit message.
|
||||
|
||||
4. **Push to your fork** and submit a pull request.
|
||||
|
||||
5. **Describe your pull request**, explaining the problem it solves or the enhancement it adds.
|
||||
|
||||
### 4. Code Style Guidelines
|
||||
|
||||
- We use [Prettier](https://prettier.io/) for formatting. Please ensure your code is formatted before submitting.
|
||||
- Write descriptive comments where necessary.
|
||||
|
||||
### 5. Local Setup
|
||||
|
||||
You'll need Node.js 20.x or 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone https://github.com/garden-co/jazz.git
|
||||
```
|
||||
|
||||
2. **Install dependencies**:
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
3. **Run tests** to verify everything is working:
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### 6. Testing
|
||||
|
||||
Please write tests for any new features or bug fixes. We use Vitest for unit tests, and Playwright for e2e tests. Make sure all tests pass before submitting a pull request.
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
NB: You'll need to run `pnpm exec playwright install` to install the Playwright browsers before first run.
|
||||
|
||||
### 7. Communication
|
||||
|
||||
- If you're unsure about anything, feel free to ask questions by opening a discussion, reaching out via issues, or on our [Discord](https://discord.gg/utDMjHYg42).
|
||||
- Be respectful and constructive, this is a welcoming community for everyone.
|
||||
- Please be mindful of GitHub’s [Community Guidelines](https://docs.github.com/en/site-policy/github-terms/github-community-guidelines), which include being kind, avoiding disruptive behavior, and respecting others.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please read and adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md) to ensure a positive experience for all contributors.
|
||||
|
||||
---
|
||||
|
||||
Thank you again for your interest in contributing to Jazz. Your help makes this project better for everyone!
|
||||
|
||||
If you have any questions, don't hesitate to reach out. Let's make something great together!
|
||||
|
||||
18
README.md
18
README.md
@@ -1,12 +1,20 @@
|
||||
# Jazz - Instant sync
|
||||
|
||||
|
||||
# Jazz - Build local-first apps
|
||||
|
||||
**Jazz is an open-source toolkit for building apps with *distributed state.***
|
||||
|
||||
## Getting started
|
||||
|
||||
We recommend reading the [homepage](https://jazz.tools) and [docs](https://jazz.tools/docs) to get an overview of what Jazz is and how it works.
|
||||
|
||||
If you're interested in contributing, please read [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
For community and support, please join our [Discord](https://discord.gg/utDMjHYg42).
|
||||
|
||||
---
|
||||
|
||||
- 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)
|
||||
- Updates: [X](https://x.com/jazz_tools) & [Email](https://garden.co/news)
|
||||
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
|
||||
78
biome.json
Normal file
78
biome.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": [
|
||||
"jazz-tools.json",
|
||||
"**/ios/**",
|
||||
"**/android/**",
|
||||
"packages/jazz-svelte/**",
|
||||
"examples/*svelte*/**",
|
||||
"examples/briscola/src/routeTree.gen.ts"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useImportExtensions": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"suggestedExtensions": {
|
||||
"ts": {
|
||||
"module": "js",
|
||||
"component": "jsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["packages/**/src/**"],
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["packages/**/src/tests/**", "packages/**/src/test/**"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"useImportExtensions": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["packages/cojson-storage-indexeddb/**"],
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExplicitAny": "info"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
36
examples/book-shelf/.gitignore
vendored
Normal file
36
examples/book-shelf/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
323
examples/book-shelf/CHANGELOG.md
Normal file
323
examples/book-shelf/CHANGELOG.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# jazz-example-book-shelf
|
||||
|
||||
## 0.1.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.50
|
||||
- jazz-tools@0.8.50
|
||||
- jazz-browser-media-images@0.8.50
|
||||
|
||||
## 0.1.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.49
|
||||
- jazz-tools@0.8.49
|
||||
- jazz-browser-media-images@0.8.49
|
||||
|
||||
## 0.1.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [635e824]
|
||||
- Updated dependencies [0a85982]
|
||||
- jazz-tools@0.8.48
|
||||
- jazz-browser-media-images@0.8.48
|
||||
- jazz-react@0.8.48
|
||||
|
||||
## 0.1.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fa41f8e]
|
||||
- Updated dependencies [88d7d9a]
|
||||
- Updated dependencies [60e35ea]
|
||||
- jazz-tools@0.8.45
|
||||
- jazz-react@0.8.45
|
||||
- jazz-browser-media-images@0.8.45
|
||||
|
||||
## 0.1.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.44
|
||||
- jazz-tools@0.8.44
|
||||
- jazz-browser-media-images@0.8.44
|
||||
|
||||
## 0.1.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.41
|
||||
- jazz-tools@0.8.41
|
||||
- jazz-browser-media-images@0.8.41
|
||||
|
||||
## 0.1.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.8.40
|
||||
- jazz-react@0.8.40
|
||||
|
||||
## 0.1.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [249eecb]
|
||||
- jazz-tools@0.8.39
|
||||
- jazz-browser-media-images@0.8.39
|
||||
- jazz-react@0.8.39
|
||||
|
||||
## 0.1.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.38
|
||||
- jazz-tools@0.8.38
|
||||
- jazz-browser-media-images@0.8.38
|
||||
|
||||
## 0.1.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.37
|
||||
- jazz-tools@0.8.37
|
||||
- jazz-browser-media-images@0.8.37
|
||||
|
||||
## 0.1.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [441fe27]
|
||||
- jazz-tools@0.8.36
|
||||
- jazz-react@0.8.36
|
||||
- jazz-browser-media-images@0.8.36
|
||||
|
||||
## 0.1.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9212ab8]
|
||||
- Updated dependencies [8b87117]
|
||||
- jazz-react@0.8.35
|
||||
- jazz-tools@0.8.35
|
||||
- jazz-browser-media-images@0.8.35
|
||||
|
||||
## 0.1.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.34
|
||||
- jazz-tools@0.8.34
|
||||
- jazz-browser-media-images@0.8.34
|
||||
|
||||
## 0.1.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.8.33
|
||||
- jazz-react@0.8.33
|
||||
|
||||
## 0.1.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [df42b2b]
|
||||
- jazz-tools@0.8.32
|
||||
- jazz-react@0.8.32
|
||||
- jazz-browser-media-images@0.8.32
|
||||
|
||||
## 0.1.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.31
|
||||
- jazz-tools@0.8.31
|
||||
- jazz-browser-media-images@0.8.31
|
||||
|
||||
## 0.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.30
|
||||
- jazz-tools@0.8.30
|
||||
- jazz-browser-media-images@0.8.30
|
||||
|
||||
## 0.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.29
|
||||
- jazz-tools@0.8.29
|
||||
- jazz-browser-media-images@0.8.29
|
||||
|
||||
## 0.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.28
|
||||
- jazz-tools@0.8.28
|
||||
- jazz-browser-media-images@0.8.28
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.27
|
||||
- jazz-tools@0.8.27
|
||||
- jazz-browser-media-images@0.8.27
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59d37df]
|
||||
- jazz-react@0.8.26
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.8.24
|
||||
- jazz-react@0.8.24
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d348c2d]
|
||||
- Updated dependencies [6902b5b]
|
||||
- Updated dependencies [1a0cd3d]
|
||||
- jazz-tools@0.8.23
|
||||
- jazz-react@0.8.23
|
||||
- jazz-browser-media-images@0.8.23
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
- jazz-browser-media-images@0.8.22
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-browser-media-images@0.8.21
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-browser-media-images@0.8.20
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-browser-media-images@0.8.19
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
- jazz-browser-media-images@0.8.18
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
- jazz-browser-media-images@0.8.17
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2af107c]
|
||||
- jazz-react@0.8.16
|
||||
- jazz-browser-media-images@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-browser-media-images@0.8.15
|
||||
- jazz-react@0.8.15
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-browser-media-images@0.8.14
|
||||
- jazz-react@0.8.14
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-browser-media-images@0.8.13
|
||||
- jazz-react@0.8.13
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3cc6aee]
|
||||
- jazz-react@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- jazz-browser-media-images@0.8.12
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-browser-media-images@0.8.11
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.8.7
|
||||
- jazz-react@0.8.7
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.8.6
|
||||
- jazz-react@0.8.6
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-tools@0.8.5
|
||||
- jazz-browser-media-images@0.8.5
|
||||
- jazz-react@0.8.5
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM caddy:2.7.3-alpine
|
||||
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||
LABEL org.opencontainers.image.source="https://github.com/garden-co/jazz"
|
||||
|
||||
COPY ./dist /usr/share/caddy/
|
||||
41
examples/book-shelf/README.md
Normal file
41
examples/book-shelf/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Book shelf example with Jazz and Next.js
|
||||
|
||||
This is an example of a book shelf where you can add and review books.
|
||||
It shows you how to create and edit data, upload images, and share data publicly.
|
||||
|
||||
Live version: https://books-demo.jazz.tools
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
(This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by downloading the [jazz repository](https://github.com/garden-co/jazz):
|
||||
```bash
|
||||
npx degit gardencmp/jazz jazz
|
||||
```
|
||||
|
||||
Go to the book-shelf example directory:
|
||||
```bash
|
||||
cd jazz/examples/book-shelf
|
||||
```
|
||||
|
||||
Install and build dependencies:
|
||||
```bash
|
||||
pnpm i && npx turbo build
|
||||
```
|
||||
|
||||
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 Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx jazz-run sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?peer=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/components/JazzAndAuth.tsx](./src/components/JazzAndAuth.tsx).
|
||||
@@ -1,4 +1,4 @@
|
||||
job "example-file-drop$BRANCH_SUFFIX" {
|
||||
job "example-bookShelf$BRANCH_SUFFIX" {
|
||||
region = "global"
|
||||
datacenters = ["*"]
|
||||
|
||||
@@ -14,7 +14,7 @@ job "example-file-drop$BRANCH_SUFFIX" {
|
||||
constraint {
|
||||
attribute = "${node.class}"
|
||||
operator = "="
|
||||
value = "mesh"
|
||||
value = "cloud"
|
||||
}
|
||||
|
||||
spread {
|
||||
@@ -41,7 +41,7 @@ job "example-file-drop$BRANCH_SUFFIX" {
|
||||
|
||||
service {
|
||||
tags = ["public"]
|
||||
name = "example-file-drop$BRANCH_SUFFIX"
|
||||
name = "example-bookShelf$BRANCH_SUFFIX"
|
||||
port = "http"
|
||||
provider = "consul"
|
||||
}
|
||||
@@ -53,4 +53,4 @@ job "example-file-drop$BRANCH_SUFFIX" {
|
||||
}
|
||||
}
|
||||
}
|
||||
# deploy bump 4
|
||||
# deploy bump 4
|
||||
5
examples/book-shelf/next-env.d.ts
vendored
Normal file
5
examples/book-shelf/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
4
examples/book-shelf/next.config.mjs
Normal file
4
examples/book-shelf/next.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {};
|
||||
|
||||
export default nextConfig;
|
||||
29
examples/book-shelf/package.json
Normal file
29
examples/book-shelf/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "jazz-example-book-shelf",
|
||||
"version": "0.1.38",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.8.50",
|
||||
"jazz-react": "workspace:0.8.50",
|
||||
"jazz-tools": "workspace:0.8.50",
|
||||
"next": "14.2.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"typescript": "~5.6.2"
|
||||
}
|
||||
}
|
||||
8
examples/book-shelf/postcss.config.mjs
Normal file
8
examples/book-shelf/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
139
examples/book-shelf/src/app/add/page.tsx
Normal file
139
examples/book-shelf/src/app/add/page.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview, ListOfBookReviews } from "@/schema";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { Group, ImageDefinition } from "jazz-tools";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
export default function AddBookReview() {
|
||||
const { me } = useAccount();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const [author, setAuthor] = useState("");
|
||||
const [review, setReview] = useState("");
|
||||
const [rating, setRating] = useState(0);
|
||||
const [dateRead, setDateRead] = useState(
|
||||
new Date().toISOString().split("T")[0],
|
||||
);
|
||||
const [coverImage, setCoverImage] = useState<ImageDefinition | undefined>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const onDateChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const date = new Date(event.currentTarget.value)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
setDateRead(date);
|
||||
};
|
||||
|
||||
const onImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "reader");
|
||||
|
||||
if (file) {
|
||||
createImage(file, { owner: group }).then((image) => {
|
||||
setCoverImage(image);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!me?.profile) return;
|
||||
|
||||
const bookReview = BookReview.create(
|
||||
{
|
||||
title,
|
||||
author,
|
||||
review,
|
||||
rating,
|
||||
dateRead: new Date(dateRead),
|
||||
cover: coverImage,
|
||||
},
|
||||
{
|
||||
owner: me.profile._owner,
|
||||
},
|
||||
);
|
||||
|
||||
if (!me.profile.bookReviews) {
|
||||
me.profile.bookReviews = ListOfBookReviews.create([], {
|
||||
owner: me.profile._owner,
|
||||
});
|
||||
}
|
||||
|
||||
me.profile.bookReviews.push(bookReview);
|
||||
router.push("/");
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="grid max-w-lg gap-8 py-8">
|
||||
<h1 className="text-lg font-semibold text-black">Add book review</h1>
|
||||
<form action="" className="grid gap-4" onSubmit={onSubmit}>
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
Cover
|
||||
<input type="file" onChange={onImageChange} />
|
||||
</label>
|
||||
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
Title
|
||||
<input
|
||||
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||
type="text"
|
||||
value={title}
|
||||
required
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
></input>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
Author
|
||||
<input
|
||||
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||
type="text"
|
||||
value={author}
|
||||
required
|
||||
onChange={(e) => setAuthor(e.target.value)}
|
||||
></input>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
Date read
|
||||
<input
|
||||
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||
type="date"
|
||||
value={dateRead}
|
||||
required
|
||||
onChange={onDateChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="grid gap-1 text-sm text-gray-600">
|
||||
Rating
|
||||
<RatingInput
|
||||
value={rating}
|
||||
onChange={(rating) => setRating(rating)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
Review
|
||||
<textarea
|
||||
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||
value={review}
|
||||
onChange={(e) => setReview(e.target.value)}
|
||||
></textarea>
|
||||
</label>
|
||||
<Button variant="primary" type="submit">
|
||||
Add
|
||||
</Button>
|
||||
</form>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
172
examples/book-shelf/src/app/book/[slug]/page.tsx
Normal file
172
examples/book-shelf/src/app/book/[slug]/page.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
"use client";
|
||||
|
||||
import { BookCover } from "@/components/BookCover";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import Rating from "@/components/Rating";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview } from "@/schema";
|
||||
import clsx from "clsx";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
|
||||
const BookReviewTitle = ({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly: boolean;
|
||||
}) => {
|
||||
const className = "font-serif text-2xl font-bold lg:text-4xl";
|
||||
|
||||
if (readOnly) {
|
||||
return <h1 className={className}>{bookReview.title}</h1>;
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
value={bookReview.title}
|
||||
placeholder="Book title"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={(e) => (bookReview.title = e.target.value)}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
|
||||
const BookReviewAuthor = ({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly: boolean;
|
||||
}) => {
|
||||
const className = "text-gray-700";
|
||||
|
||||
if (readOnly) {
|
||||
return <p className={className}>by {bookReview.author}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
value={bookReview.author}
|
||||
placeholder="Author"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={(e) => (bookReview.author = e.target.value)}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
|
||||
const BookReviewDateRead = ({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly: boolean;
|
||||
}) => {
|
||||
const className = "text-gray-700 max-w-[10rem]";
|
||||
|
||||
if (readOnly) {
|
||||
return (
|
||||
bookReview.dateRead && (
|
||||
<p className={className}>{bookReview.dateRead.toLocaleDateString()}</p>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="date"
|
||||
value={bookReview.dateRead?.toISOString().split("T")[0]}
|
||||
placeholder="Date read"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={(e) => {
|
||||
const date = new Date(e.target.value);
|
||||
bookReview.dateRead = date;
|
||||
}}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
|
||||
const BookReviewReview = ({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly: boolean;
|
||||
}) => {
|
||||
const className = "text-sm leading-relaxed text-gray-600";
|
||||
|
||||
if (readOnly) {
|
||||
if (bookReview.review) {
|
||||
return <p className={className}>{bookReview.review}</p>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<textarea
|
||||
value={bookReview.review}
|
||||
placeholder="Write your review here..."
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={(e) => (bookReview.review = e.target.value)}
|
||||
></textarea>
|
||||
);
|
||||
};
|
||||
|
||||
const BookReviewRating = ({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly: boolean;
|
||||
}) => {
|
||||
const className = "text-2xl sm:mx-0";
|
||||
|
||||
if (readOnly) {
|
||||
return <Rating className={className} rating={bookReview.rating} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RatingInput
|
||||
className={clsx(className, "p-2")}
|
||||
onChange={(rating) => (bookReview.rating = rating)}
|
||||
value={bookReview.rating}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Page({ params }: { params: { slug: string } }) {
|
||||
const bookReview = useCoState(BookReview, params.slug as ID<BookReview>);
|
||||
|
||||
if (!bookReview) return <></>;
|
||||
|
||||
const readOnly = !(bookReview._owner.castAs(Group).myRole() === "admin");
|
||||
|
||||
return (
|
||||
<Container className="grid gap-12 py-8">
|
||||
<div className="flex flex-col gap-6 sm:flex-row md:gap-10">
|
||||
<div className="w-[180px]">
|
||||
<BookCover bookReview={bookReview} readOnly={readOnly} />
|
||||
</div>
|
||||
|
||||
<div className="-mx-2 grid max-w-lg flex-1 gap-3 sm:mx-0">
|
||||
<BookReviewTitle bookReview={bookReview} readOnly={readOnly} />
|
||||
<BookReviewAuthor bookReview={bookReview} readOnly={readOnly} />
|
||||
<BookReviewDateRead bookReview={bookReview} readOnly={readOnly} />
|
||||
<BookReviewRating bookReview={bookReview} readOnly={readOnly} />
|
||||
<BookReviewReview bookReview={bookReview} readOnly={readOnly} />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
BIN
examples/book-shelf/src/app/favicon.ico
Normal file
BIN
examples/book-shelf/src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
13
examples/book-shelf/src/app/globals.css
Normal file
13
examples/book-shelf/src/app/globals.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:focus {
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
@apply ring-2 ring-purple-600/75 dark:ring-2 dark:ring-white/75;
|
||||
}
|
||||
}
|
||||
39
examples/book-shelf/src/app/layout.tsx
Normal file
39
examples/book-shelf/src/app/layout.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { JazzAndAuth } from "@/components/JazzAndAuth";
|
||||
import { Nav } from "@/components/Nav";
|
||||
import clsx from "clsx";
|
||||
import { Fraunces } from "next/font/google";
|
||||
|
||||
const fraunces = Fraunces({
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-fraunces",
|
||||
});
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Jazz Book Shelf",
|
||||
description: "Jazz Book Shelf",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={clsx(inter.className, fraunces.variable)}>
|
||||
<JazzAndAuth>
|
||||
<header>
|
||||
<Nav />
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
</JazzAndAuth>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
27
examples/book-shelf/src/app/page.tsx
Normal file
27
examples/book-shelf/src/app/page.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import UserProfile from "@/components/UserProfile";
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { ID } from "jazz-tools";
|
||||
|
||||
export default function Home() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
<Container className="grid gap-12 py-8">
|
||||
<UserProfile id={me?.id as ID<JazzAccount>} />
|
||||
|
||||
<label className="flex flex-wrap items-center gap-3">
|
||||
Share your profile:
|
||||
<input
|
||||
type="text"
|
||||
className="w-full rounded border p-1"
|
||||
value={`${window.location.origin}/user/${me?.id}`}
|
||||
readOnly
|
||||
/>
|
||||
</label>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
14
examples/book-shelf/src/app/user/[slug]/page.tsx
Normal file
14
examples/book-shelf/src/app/user/[slug]/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import UserProfile from "@/components/UserProfile";
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { ID } from "jazz-tools";
|
||||
|
||||
export default function Page({ params }: { params: { slug: string } }) {
|
||||
const { slug } = params;
|
||||
|
||||
return (
|
||||
<Container className="py-8">
|
||||
<UserProfile id={slug as ID<JazzAccount>} />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
134
examples/book-shelf/src/components/BookCover.tsx
Normal file
134
examples/book-shelf/src/components/BookCover.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Button } from "@/components/Button";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import PlusIcon from "@/components/icons/PlusIcon";
|
||||
import { BookReview } from "@/schema";
|
||||
import clsx from "clsx";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
import { Group, ImageDefinition } from "jazz-tools";
|
||||
import { ChangeEvent, useRef, useState } from "react";
|
||||
|
||||
const BookCoverContainer = ({
|
||||
children,
|
||||
className = "",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={clsx("h-[240px] lg:h-[260px]", className)}>{children}</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MockCover = ({ bookReview }: { bookReview: BookReview }) => {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-l-sm rounded-r-md bg-gray-100 px-3 text-center shadow-lg">
|
||||
<p className="font-medium">{bookReview.title}</p>
|
||||
<p className="text-xs">{bookReview.author}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function BookCoverReadOnly({
|
||||
bookReview,
|
||||
className,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
className?: string;
|
||||
}) {
|
||||
if (bookReview.cover) {
|
||||
return (
|
||||
<BookCoverContainer className={className}>
|
||||
<ProgressiveImg image={bookReview.cover}>
|
||||
{({ src }) => (
|
||||
<img
|
||||
className="max-h-full max-w-full rounded-l-sm rounded-r-md shadow-lg"
|
||||
src={src}
|
||||
/>
|
||||
)}
|
||||
</ProgressiveImg>
|
||||
</BookCoverContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BookCoverContainer className={className}>
|
||||
<MockCover bookReview={bookReview} />
|
||||
</BookCoverContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookCoverInput({ bookReview }: { bookReview: BookReview }) {
|
||||
const { me } = useAccount();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!me?.profile) return;
|
||||
|
||||
const file = event.currentTarget.files?.[0];
|
||||
|
||||
if (file) {
|
||||
createImage(file, { owner: me.profile._owner }).then((image) => {
|
||||
bookReview.cover = image;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onUploadClick = () => {
|
||||
inputRef.current?.click();
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
bookReview.cover = null;
|
||||
};
|
||||
|
||||
if (bookReview.cover) {
|
||||
return (
|
||||
<div className="group relative inline-block">
|
||||
<BookCoverReadOnly
|
||||
className="transition-opacity group-hover:opacity-40"
|
||||
bookReview={bookReview}
|
||||
/>
|
||||
<div className="absolute left-0 top-0 hidden h-full w-full items-center justify-center rounded-l-sm rounded-r-md group-hover:flex">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
type="button"
|
||||
className="shadow"
|
||||
onClick={remove}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BookCoverContainer className="flex w-[180px] flex-col justify-center rounded-l-sm rounded-r-md bg-gray-100 p-3 shadow-lg">
|
||||
<button
|
||||
className="flex h-full w-full flex-col items-center justify-center gap-3 text-gray-500 transition-colors hover:text-gray-600"
|
||||
type="button"
|
||||
onClick={onUploadClick}
|
||||
>
|
||||
<PlusIcon className="h-10 w-10" />
|
||||
Upload book cover
|
||||
</button>
|
||||
<label className="sr-only">
|
||||
Cover
|
||||
<input ref={inputRef} type="file" onChange={onImageChange} />
|
||||
</label>
|
||||
</BookCoverContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookCover({
|
||||
bookReview,
|
||||
readOnly,
|
||||
}: {
|
||||
bookReview: BookReview;
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
if (readOnly) return <BookCoverReadOnly bookReview={bookReview} />;
|
||||
|
||||
return <BookCoverInput bookReview={bookReview} />;
|
||||
}
|
||||
30
examples/book-shelf/src/components/BookReviewHeader.tsx
Normal file
30
examples/book-shelf/src/components/BookReviewHeader.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { BookCover } from "@/components/BookCover";
|
||||
import Rating from "@/components/Rating";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview } from "@/schema";
|
||||
import { Group } from "jazz-tools";
|
||||
|
||||
export function BookReviewHeader({ bookReview }: { bookReview: BookReview }) {
|
||||
const { title, author, rating, review, dateRead } = bookReview;
|
||||
|
||||
return (
|
||||
<div className="grid gap-5">
|
||||
<div>
|
||||
<h1 className="mb-1 font-serif text-2xl font-bold md:mb-3 lg:text-4xl">
|
||||
{title}
|
||||
</h1>
|
||||
|
||||
<p className="text-gray-500">by {author}</p>
|
||||
</div>
|
||||
|
||||
{bookReview._owner.castAs(Group).myRole() === "admin" ? (
|
||||
<RatingInput
|
||||
onChange={(rating) => (bookReview.rating = rating)}
|
||||
value={rating}
|
||||
/>
|
||||
) : (
|
||||
<Rating className="mx-auto text-2xl sm:mx-0" rating={rating} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
examples/book-shelf/src/components/BookReviewThumbnail.tsx
Normal file
35
examples/book-shelf/src/components/BookReviewThumbnail.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { BookCover, BookCoverReadOnly } from "@/components/BookCover";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import StarIcon from "@/components/icons/StarIcon";
|
||||
import { ID } from "jazz-tools";
|
||||
import Link from "next/link";
|
||||
import { BookReview } from "../schema";
|
||||
|
||||
export function BookReviewThumbnail({ id }: { id: ID<BookReview> }) {
|
||||
const bookReview = useCoState(BookReview, id);
|
||||
|
||||
if (!bookReview) return <></>;
|
||||
|
||||
return (
|
||||
<div className="inline-flex shrink-0 gap-4 rounded border p-4 sm:block sm:space-y-6 sm:border-0 sm:p-0 md:w-[200px]">
|
||||
<Link href={`/book/${bookReview.id}`} className="sm:block sm:flex-1">
|
||||
<BookCoverReadOnly bookReview={bookReview} />
|
||||
</Link>
|
||||
|
||||
<div className="flex-1">
|
||||
<Link href={`/book/${bookReview.id}`}>
|
||||
<h2 className="mb-1 text-sm font-medium">{bookReview.title}</h2>
|
||||
</Link>
|
||||
<div className="mb-2 flex flex-col gap-2 text-sm text-gray-500 sm:flex-row sm:items-center">
|
||||
<p>{bookReview.author}</p>
|
||||
<div className="flex items-center gap-0.5 text-xs font-semibold leading-none">
|
||||
<StarIcon className="-mt-px text-base text-yellow-400" />
|
||||
{bookReview.rating}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
65
examples/book-shelf/src/components/Button.tsx
Normal file
65
examples/book-shelf/src/components/Button.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
interface Props {
|
||||
variant?: "primary" | "secondary" | "tertiary";
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}
|
||||
|
||||
interface AnchorProps
|
||||
extends Props,
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
href: string;
|
||||
}
|
||||
|
||||
type ButtonProps = ComponentProps<"button"> & Props;
|
||||
|
||||
export function Button(props: AnchorProps | ButtonProps) {
|
||||
const {
|
||||
className: customClassName = "",
|
||||
variant = "primary",
|
||||
children,
|
||||
size = "md",
|
||||
} = props;
|
||||
|
||||
const variantClassNames = {
|
||||
base: "inline-flex gap-2 items-center justify-center rounded-full overflow-hidden transition-colors",
|
||||
primary:
|
||||
"bg-purple-300 font-medium text-purple-950 px-4 py-2 rounded-full hover:bg-purple-200",
|
||||
secondary:
|
||||
"rounded-full bg-slate-100 font-medium text-slate-600 hover:bg-slate-200",
|
||||
tertiary: "rounded-full bg-white text-purple-950 font-medium",
|
||||
};
|
||||
|
||||
const sizeClassNames = {
|
||||
sm: "py-1.5 px-3 text-sm",
|
||||
md: "py-2 px-5",
|
||||
lg: "py-2 md:py-2.5 px-6 md:text-lg",
|
||||
};
|
||||
|
||||
const className = clsx(
|
||||
customClassName,
|
||||
variantClassNames.base,
|
||||
variantClassNames[variant],
|
||||
sizeClassNames[size],
|
||||
);
|
||||
|
||||
if (!!(props as AnchorProps).href) {
|
||||
const anchorProps = props as AnchorProps;
|
||||
return (
|
||||
<Link href={anchorProps.href} className={className}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const buttonProps = props as ButtonProps;
|
||||
|
||||
return (
|
||||
<button {...buttonProps} className={className}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
13
examples/book-shelf/src/components/Container.tsx
Normal file
13
examples/book-shelf/src/components/Container.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
export function Container({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={clsx("mx-auto max-w-4xl px-4", className)}>{children}</div>
|
||||
);
|
||||
}
|
||||
29
examples/book-shelf/src/components/JazzAndAuth.tsx
Normal file
29
examples/book-shelf/src/components/JazzAndAuth.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
|
||||
const Jazz = createJazzReactApp({
|
||||
AccountSchema: JazzAccount,
|
||||
});
|
||||
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, authState] = useDemoAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
{authState.state !== "signedIn" && (
|
||||
<DemoAuthBasicUI appName="Jazz Book Shelf" state={authState} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
26
examples/book-shelf/src/components/Nav.tsx
Normal file
26
examples/book-shelf/src/components/Nav.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import Link from "next/link";
|
||||
|
||||
export function Nav() {
|
||||
const { me, logOut } = useAccount();
|
||||
|
||||
return (
|
||||
<nav className="border-b py-3">
|
||||
<Container className="flex items-center justify-between gap-12 text-sm">
|
||||
<Link href="/" className="font-serif text-lg font-semibold">
|
||||
Jazz Book Shelf
|
||||
</Link>
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<p>{me?.profile?.name}</p>
|
||||
<Button variant="secondary" onClick={logOut}>
|
||||
Log out
|
||||
</Button>
|
||||
</div>
|
||||
</Container>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
21
examples/book-shelf/src/components/Rating.tsx
Normal file
21
examples/book-shelf/src/components/Rating.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import StarIcon from "@/components/icons/StarIcon";
|
||||
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||
|
||||
export default function Rating({
|
||||
rating,
|
||||
className = "",
|
||||
}: {
|
||||
rating?: number;
|
||||
className?: string;
|
||||
}) {
|
||||
const max = 5;
|
||||
const outline = max - (rating || 0);
|
||||
return (
|
||||
<div className={`inline-flex gap-0.5 text-yellow-400 ${className}`}>
|
||||
{rating ? [...Array(rating)].map((x, i) => <StarIcon key={i} />) : <></>}
|
||||
{[...Array(outline)].map((x, i) => (
|
||||
<StarOutlineIcon key={i} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
47
examples/book-shelf/src/components/RatingInput.tsx
Normal file
47
examples/book-shelf/src/components/RatingInput.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import StarIcon from "@/components/icons/StarIcon";
|
||||
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface RatingInputProps {
|
||||
value?: number;
|
||||
onChange: (value: number) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function RatingInput({
|
||||
value = 0,
|
||||
onChange,
|
||||
className,
|
||||
}: RatingInputProps) {
|
||||
const handleChange = (newRating: number) => {
|
||||
onChange(newRating > 5 ? 5 : newRating);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(className, "flex gap-0.5 text-2xl text-yellow-400")}>
|
||||
{[...Array(5)].map((_, i) => {
|
||||
return i < value ? (
|
||||
<button
|
||||
type="button"
|
||||
className="focus:outline-none"
|
||||
onClick={() => handleChange(i + 1)}
|
||||
key={i}
|
||||
>
|
||||
<StarIcon />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="focus:outline-none"
|
||||
onClick={() => handleChange(i + 1)}
|
||||
key={i}
|
||||
>
|
||||
<StarOutlineIcon />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
39
examples/book-shelf/src/components/UserProfile.tsx
Normal file
39
examples/book-shelf/src/components/UserProfile.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { BookReviewThumbnail } from "@/components/BookReviewThumbnail";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import { JazzAccount, JazzProfile, ListOfBookReviews } from "@/schema";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
|
||||
export default function UserProfile({ id }: { id: ID<JazzAccount> }) {
|
||||
const user = useCoState(JazzAccount, id);
|
||||
const profile = useCoState(JazzProfile, user?.profile?.id);
|
||||
|
||||
const bookReviews = useCoState(
|
||||
ListOfBookReviews,
|
||||
user?.profile?._refs.bookReviews?.id,
|
||||
[{}],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="font-serif text-lg font-medium sm:text-2xl">
|
||||
{profile?.name}'s book shelf
|
||||
</h1>
|
||||
{profile?._owner.castAs(Group).myRole() === "admin" && (
|
||||
<Button href="/add" variant="primary">
|
||||
Add book
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
{bookReviews?.map((bookReview) => (
|
||||
<BookReviewThumbnail key={bookReview.id} id={bookReview.id} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
examples/book-shelf/src/components/icons/PlusIcon.tsx
Normal file
20
examples/book-shelf/src/components/icons/PlusIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function PlusIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 4.5v15m7.5-7.5h-15"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
18
examples/book-shelf/src/components/icons/StarIcon.tsx
Normal file
18
examples/book-shelf/src/components/icons/StarIcon.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
export default function StarIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={className}
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
20
examples/book-shelf/src/components/icons/StarOutlineIcon.tsx
Normal file
20
examples/book-shelf/src/components/icons/StarOutlineIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function StarOutlineIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className={className}
|
||||
width="1em"
|
||||
height="1em"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
35
examples/book-shelf/src/schema.ts
Normal file
35
examples/book-shelf/src/schema.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Account,
|
||||
CoList,
|
||||
CoMap,
|
||||
Encoders,
|
||||
ImageDefinition,
|
||||
Profile,
|
||||
co,
|
||||
} from "jazz-tools";
|
||||
|
||||
export class BookReview extends CoMap {
|
||||
title = co.string;
|
||||
author = co.string;
|
||||
rating = co.number;
|
||||
dateRead? = co.encoded(Encoders.Date);
|
||||
review? = co.string;
|
||||
cover? = co.ref(ImageDefinition, { optional: true });
|
||||
}
|
||||
|
||||
export class ListOfBookReviews extends CoList.Of(co.ref(BookReview)) {}
|
||||
|
||||
/** The profile is an app-specific per-user public `CoMap`
|
||||
* where you can store top-level objects for that user */
|
||||
export class JazzProfile extends Profile {
|
||||
bookReviews = co.ref(ListOfBookReviews);
|
||||
}
|
||||
|
||||
export class JazzAccount extends Account {
|
||||
profile = co.ref(JazzProfile);
|
||||
|
||||
/** 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() {}
|
||||
}
|
||||
25
examples/book-shelf/tailwind.config.ts
Normal file
25
examples/book-shelf/tailwind.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
const colors = require("tailwindcss/colors");
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
serif: ["var(--font-fraunces)"],
|
||||
},
|
||||
colors: {
|
||||
purple: {
|
||||
...colors.purple,
|
||||
950: "#211f5a",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
export default config;
|
||||
28
examples/book-shelf/tsconfig.json
Normal file
28
examples/book-shelf/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
8
examples/book-shelf/vercel.json
Normal file
8
examples/book-shelf/vercel.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"build": {
|
||||
"env": {
|
||||
"APP_NAME": "books-demo"
|
||||
}
|
||||
},
|
||||
"ignoreCommand": "node ../../ignore-vercel-build.js"
|
||||
}
|
||||
2
examples/briscola/.env.example
Normal file
2
examples/briscola/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_JAZZ_WORKER_ACCOUNT=
|
||||
JAZZ_WORKER_SECRET=
|
||||
21
examples/briscola/.gitignore
vendored
Normal file
21
examples/briscola/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Local
|
||||
.DS_Store
|
||||
*.local
|
||||
*.log*
|
||||
|
||||
# Dist
|
||||
node_modules
|
||||
dist/
|
||||
.vinxi
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
|
||||
# IDE
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
|
||||
|
||||
.env
|
||||
38
examples/briscola/README.md
Normal file
38
examples/briscola/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Jazz Briscola
|
||||
|
||||
This is a simplified implementation of the Italian card game [Briscola](https://en.wikipedia.org/wiki/Briscola), written using Jazz.
|
||||
|
||||
While most Jazz apps don't need workers, in this game players must not be able to see each other's cards. This is a good example of when a worker is useful. In this case, the worker acts as a dealer, revealing the cards to each player as needed.
|
||||
|
||||
In general this showcases how workers can be used to moderate access to coValues.
|
||||
|
||||
The communication between the dealer and the players is done using the [Inbox API](#), which is an abstraction over the Jazz API that allows for easy communication between workers and clients.
|
||||
|
||||
## Setup
|
||||
|
||||
First of we need to create a new account for the dealer:
|
||||
|
||||
```bash
|
||||
pnpx jazz-run account create --name "Dealer"
|
||||
```
|
||||
|
||||
This will print an account ID and a secret key:
|
||||
|
||||
```
|
||||
# Credentials for Jazz account "Dealer":
|
||||
JAZZ_WORKER_ACCOUNT=co_xxxx
|
||||
JAZZ_WORKER_SECRET=sealerSecret_xxx
|
||||
```
|
||||
use these to create a `.env` file based on the `.env.example` file and fill in the `VITE_JAZZ_WORKER_ACCOUNT` and `JAZZ_WORKER_SECRET` fields.
|
||||
|
||||
We can then start the dealer worker:
|
||||
|
||||
```bash
|
||||
pnpm dev:worker
|
||||
```
|
||||
|
||||
and the client:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
21
examples/briscola/components.json
Normal file
21
examples/briscola/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
12
examples/briscola/index.html
Normal file
12
examples/briscola/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="bg-green-800">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz Briscola</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="min-h-screen"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
47
examples/briscola/package.json
Normal file
47
examples/briscola/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "jazz-example-briscola",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "vite --port=3001",
|
||||
"dev:worker": "tsx --env-file=.env ./src/worker.ts",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"start": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/router-plugin": "^1.87.13",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-label": "^2.1.1",
|
||||
"@radix-ui/react-radio-group": "^1.2.2",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@tanstack/react-router": "^1.87.12",
|
||||
"@tanstack/router-devtools": "^1.87.12",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-nodejs": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.468.0",
|
||||
"motion": "^11.14.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tsx": "^4.19.2"
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
119
examples/briscola/src/components/how-to-play-content.tsx
Normal file
119
examples/briscola/src/components/how-to-play-content.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
export function HowToPlayContent() {
|
||||
return (
|
||||
<div className="">
|
||||
<h3 className="font-semibold text-lg">Objective:</h3>
|
||||
<p className="mt-2">
|
||||
The goal is to score the most points by winning tricks containing
|
||||
high-value cards. The game is played until all card are played.
|
||||
</p>
|
||||
|
||||
<h3 className="font-semibold text-lg mt-6">The deck</h3>
|
||||
<p className="mt-2">
|
||||
A deck with 40 cards is used, split into four suits:
|
||||
<ul className="list-disc list-inside">
|
||||
<li className="list-item">Coins (Denari)</li>
|
||||
<li className="list-item">Cups (Coppe)</li>
|
||||
<li className="list-item">Swords (Spade)</li>
|
||||
<li className="list-item">Clubs (Bastoni)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p className="mt-2">Each suit has cards numbered from 1 to 10.</p>
|
||||
|
||||
<h3 className="font-semibold text-lg mt-6">Card values</h3>
|
||||
<p className="mt-2">
|
||||
Each card has a point value:
|
||||
<ul className="list-disc list-inside">
|
||||
<li className="list-item">Ace (1): 11 points</li>
|
||||
<li className="list-item">Three (3): 10 points</li>
|
||||
<li className="list-item">Eight (8): 2 points</li>
|
||||
<li className="list-item">Nine (9): 3 points</li>
|
||||
<li className="list-item">Ten (10): 4 points</li>
|
||||
<li className="list-item">All others (2, 4-7): 0 points</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
There are <span className="font-semibold">120 total points</span> in the
|
||||
deck.
|
||||
</p>
|
||||
|
||||
<h3 className="font-semibold text-lg mt-6">Gameplay</h3>
|
||||
<p className="mt-2">
|
||||
<ol className="list-inside list-decimal">
|
||||
<li>
|
||||
<span className="font-semibold">Starting the game:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
3 cards are dealt to each player.
|
||||
</li>
|
||||
<li className="list-item ml-4">
|
||||
1 card is placed face-up on the table, on the bottom of the draw
|
||||
pile, indicating the trump suit. (Briscola)
|
||||
</li>
|
||||
<li className="list-item ml-4">
|
||||
One player is randomly chosen to start the game.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span className="font-semibold">Starting a trick:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
Players play one card each in turn, trying to win the trick.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span className="font-semibold">Winning a trick:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
The highest card of the trump suit wins the trick.
|
||||
</li>
|
||||
<li className="list-item ml-4">
|
||||
If no trump cards are played, the highest card of the leading
|
||||
suit wins.
|
||||
</li>
|
||||
<li className="list-item ml-4">
|
||||
The leading suit is the suit of the first card played in the
|
||||
current trick.
|
||||
</li>
|
||||
<li className="list-item ml-4">
|
||||
The winner collects the cards, which are placed face-up in their
|
||||
scoring pile.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span className="font-semibold">Drawing cards:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
After each trick, a new card is dealt to each player (starting
|
||||
with the trick winner).
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span className="font-semibold">Continuing play:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
The winner of the previous trick leads the next round.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<span className="font-semibold">End of the game:</span>
|
||||
<ul className="list-inside list-disc">
|
||||
<li className="list-item ml-4">
|
||||
Play continues until all cards are played.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
75
examples/briscola/src/components/playing-card.tsx
Normal file
75
examples/briscola/src/components/playing-card.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Card, Suit } from "@/schema";
|
||||
import type { co } from "jazz-tools";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
import bastoni from "../img/bastoni.svg?url";
|
||||
import coppe from "../img/coppe.svg?url";
|
||||
import denari from "../img/denari.svg?url";
|
||||
import spade from "../img/spade.svg?url";
|
||||
|
||||
interface Props {
|
||||
card: co<Card>;
|
||||
faceDown?: boolean;
|
||||
className?: string;
|
||||
layoutId?: string;
|
||||
}
|
||||
export function PlayingCard({
|
||||
card,
|
||||
className,
|
||||
faceDown = false,
|
||||
layoutId,
|
||||
}: Props) {
|
||||
const cardImage = getCardImage(card.data?.suit!);
|
||||
if (!faceDown && card.data?.value === undefined && card.data?.suit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={cn(
|
||||
"block aspect-card w-[150px] bg-white touch-none rounded-lg shadow-lg p-2 border",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...(faceDown && {
|
||||
backgroundImage: `url(https://placecats.com/150/243)`,
|
||||
backgroundSize: "cover",
|
||||
}),
|
||||
}}
|
||||
layoutId={layoutId}
|
||||
>
|
||||
<div className="border-zinc-400 border rounded-lg h-full px-1 flex flex-col ">
|
||||
{!faceDown && (
|
||||
<>
|
||||
<div className="text-4xl font-bold text-black self-start">
|
||||
{card.data?.value}
|
||||
</div>
|
||||
<div className="grow flex justify-center items-center">
|
||||
<img
|
||||
src={cardImage}
|
||||
className="pointer-events-none max-h-[140px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-4xl font-bold text-black rotate-180 transform self-end">
|
||||
{card.data?.value}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
function getCardImage(suit: typeof Suit) {
|
||||
switch (suit) {
|
||||
case "C":
|
||||
return coppe;
|
||||
case "D":
|
||||
return denari;
|
||||
case "S":
|
||||
return spade;
|
||||
case "B":
|
||||
return bastoni;
|
||||
}
|
||||
}
|
||||
82
examples/briscola/src/components/ui/button.tsx
Normal file
82
examples/briscola/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Loader2Icon } from "lucide-react";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
loadingText?: string;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
disabled,
|
||||
asChild = false,
|
||||
loading = false,
|
||||
loadingText,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
disabled={loading || disabled}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
|
||||
{loadingText}
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/basicComponents/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -10,12 +10,12 @@ const Card = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -26,43 +26,43 @@ const CardHeader = React.forwardRef<
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
));
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -73,7 +73,14 @@ const CardFooter = React.forwardRef<
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
));
|
||||
CardFooter.displayName = "CardFooter";
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
120
examples/briscola/src/components/ui/dialog.tsx
Normal file
120
examples/briscola/src/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
DialogFooter.displayName = "DialogFooter";
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
};
|
||||
22
examples/briscola/src/components/ui/input.tsx
Normal file
22
examples/briscola/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"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-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground 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 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
24
examples/briscola/src/components/ui/label.tsx
Normal file
24
examples/briscola/src/components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
);
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label };
|
||||
29
examples/briscola/src/components/ui/separator.tsx
Normal file
29
examples/briscola/src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref,
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
||||
1
examples/briscola/src/constants.ts
Normal file
1
examples/briscola/src/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const WORKER_ID = import.meta.env.VITE_JAZZ_WORKER_ACCOUNT;
|
||||
7
examples/briscola/src/img/bastoni.svg
Normal file
7
examples/briscola/src/img/bastoni.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 66 KiB |
144
examples/briscola/src/img/coppe.svg
Normal file
144
examples/briscola/src/img/coppe.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 109 KiB |
95
examples/briscola/src/img/denari.svg
Normal file
95
examples/briscola/src/img/denari.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 22 KiB |
86
examples/briscola/src/img/spade.svg
Normal file
86
examples/briscola/src/img/spade.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 69 KiB |
66
examples/briscola/src/index.css
Normal file
66
examples/briscola/src/index.css
Normal file
@@ -0,0 +1,66 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 0 0% 9%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 0 0% 96.1%;
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 0 0% 96.1%;
|
||||
--muted-foreground: 0 0% 45.1%;
|
||||
--accent: 0 0% 96.1%;
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 0 0% 3.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 0 0% 14.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 0 0% 14.9%;
|
||||
--muted-foreground: 0 0% 63.9%;
|
||||
--accent: 0 0% 14.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 0 0% 83.1%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply text-foreground;
|
||||
}
|
||||
}
|
||||
25
examples/briscola/src/jazz.tsx
Normal file
25
examples/briscola/src/jazz.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
|
||||
const Jazz = createJazzReactApp();
|
||||
export const {
|
||||
useAccount,
|
||||
useCoState,
|
||||
experimental: { useInboxSender },
|
||||
} = Jazz;
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, authState] = useDemoAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
<DemoAuthBasicUI appName="Briscola" state={authState} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
6
examples/briscola/src/lib/utils.ts
Normal file
6
examples/briscola/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
27
examples/briscola/src/main.tsx
Normal file
27
examples/briscola/src/main.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { RouterProvider } from "@tanstack/react-router";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { JazzAndAuth, useAccount } from "./jazz";
|
||||
import { router } from "./router";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
const rootElement = document.getElementById("app")!;
|
||||
|
||||
if (!rootElement.innerHTML) {
|
||||
const root = createRoot(rootElement);
|
||||
root.render(<App />);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<JazzAndAuth>
|
||||
<Router />
|
||||
</JazzAndAuth>
|
||||
);
|
||||
}
|
||||
|
||||
function Router() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return <RouterProvider router={router} context={{ me }} />;
|
||||
}
|
||||
207
examples/briscola/src/routeTree.gen.ts
Normal file
207
examples/briscola/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as HowToPlayImport } from './routes/how-to-play'
|
||||
import { Route as GameImport } from './routes/game'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as WaitingRoomWaitingRoomIdImport } from './routes/waiting-room.$waitingRoomId'
|
||||
import { Route as GameGameIdImport } from './routes/game/$gameId'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const HowToPlayRoute = HowToPlayImport.update({
|
||||
id: '/how-to-play',
|
||||
path: '/how-to-play',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const GameRoute = GameImport.update({
|
||||
id: '/game',
|
||||
path: '/game',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const WaitingRoomWaitingRoomIdRoute = WaitingRoomWaitingRoomIdImport.update({
|
||||
id: '/waiting-room/$waitingRoomId',
|
||||
path: '/waiting-room/$waitingRoomId',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const GameGameIdRoute = GameGameIdImport.update({
|
||||
id: '/$gameId',
|
||||
path: '/$gameId',
|
||||
getParentRoute: () => GameRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/game': {
|
||||
id: '/game'
|
||||
path: '/game'
|
||||
fullPath: '/game'
|
||||
preLoaderRoute: typeof GameImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/how-to-play': {
|
||||
id: '/how-to-play'
|
||||
path: '/how-to-play'
|
||||
fullPath: '/how-to-play'
|
||||
preLoaderRoute: typeof HowToPlayImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/game/$gameId': {
|
||||
id: '/game/$gameId'
|
||||
path: '/$gameId'
|
||||
fullPath: '/game/$gameId'
|
||||
preLoaderRoute: typeof GameGameIdImport
|
||||
parentRoute: typeof GameImport
|
||||
}
|
||||
'/waiting-room/$waitingRoomId': {
|
||||
id: '/waiting-room/$waitingRoomId'
|
||||
path: '/waiting-room/$waitingRoomId'
|
||||
fullPath: '/waiting-room/$waitingRoomId'
|
||||
preLoaderRoute: typeof WaitingRoomWaitingRoomIdImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
interface GameRouteChildren {
|
||||
GameGameIdRoute: typeof GameGameIdRoute
|
||||
}
|
||||
|
||||
const GameRouteChildren: GameRouteChildren = {
|
||||
GameGameIdRoute: GameGameIdRoute,
|
||||
}
|
||||
|
||||
const GameRouteWithChildren = GameRoute._addFileChildren(GameRouteChildren)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/game': typeof GameRouteWithChildren
|
||||
'/how-to-play': typeof HowToPlayRoute
|
||||
'/game/$gameId': typeof GameGameIdRoute
|
||||
'/waiting-room/$waitingRoomId': typeof WaitingRoomWaitingRoomIdRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/game': typeof GameRouteWithChildren
|
||||
'/how-to-play': typeof HowToPlayRoute
|
||||
'/game/$gameId': typeof GameGameIdRoute
|
||||
'/waiting-room/$waitingRoomId': typeof WaitingRoomWaitingRoomIdRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
'/game': typeof GameRouteWithChildren
|
||||
'/how-to-play': typeof HowToPlayRoute
|
||||
'/game/$gameId': typeof GameGameIdRoute
|
||||
'/waiting-room/$waitingRoomId': typeof WaitingRoomWaitingRoomIdRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/game'
|
||||
| '/how-to-play'
|
||||
| '/game/$gameId'
|
||||
| '/waiting-room/$waitingRoomId'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/'
|
||||
| '/game'
|
||||
| '/how-to-play'
|
||||
| '/game/$gameId'
|
||||
| '/waiting-room/$waitingRoomId'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/game'
|
||||
| '/how-to-play'
|
||||
| '/game/$gameId'
|
||||
| '/waiting-room/$waitingRoomId'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
GameRoute: typeof GameRouteWithChildren
|
||||
HowToPlayRoute: typeof HowToPlayRoute
|
||||
WaitingRoomWaitingRoomIdRoute: typeof WaitingRoomWaitingRoomIdRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
GameRoute: GameRouteWithChildren,
|
||||
HowToPlayRoute: HowToPlayRoute,
|
||||
WaitingRoomWaitingRoomIdRoute: WaitingRoomWaitingRoomIdRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/game",
|
||||
"/how-to-play",
|
||||
"/waiting-room/$waitingRoomId"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
},
|
||||
"/game": {
|
||||
"filePath": "game.tsx",
|
||||
"children": [
|
||||
"/game/$gameId"
|
||||
]
|
||||
},
|
||||
"/how-to-play": {
|
||||
"filePath": "how-to-play.tsx"
|
||||
},
|
||||
"/game/$gameId": {
|
||||
"filePath": "game/$gameId.tsx",
|
||||
"parent": "/game"
|
||||
},
|
||||
"/waiting-room/$waitingRoomId": {
|
||||
"filePath": "waiting-room.$waitingRoomId.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
18
examples/briscola/src/router.tsx
Normal file
18
examples/briscola/src/router.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { createRouter } from "@tanstack/react-router";
|
||||
|
||||
import { JazzAndAuth } from "./jazz";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
|
||||
export const router = createRouter({
|
||||
routeTree,
|
||||
context: {
|
||||
me: undefined!,
|
||||
},
|
||||
Wrap: JazzAndAuth,
|
||||
});
|
||||
20
examples/briscola/src/routes/__root.tsx
Normal file
20
examples/briscola/src/routes/__root.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { useAccount } from "@/jazz";
|
||||
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
|
||||
|
||||
interface RouterContext {
|
||||
me: ReturnType<typeof useAccount>["me"];
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
11
examples/briscola/src/routes/game.tsx
Normal file
11
examples/briscola/src/routes/game.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/game")({
|
||||
beforeLoad: async ({ context: { me } }) => {
|
||||
if (!me) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
317
examples/briscola/src/routes/game/$gameId.tsx
Normal file
317
examples/briscola/src/routes/game/$gameId.tsx
Normal file
@@ -0,0 +1,317 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { type Card, Game, PlayIntent, type Player } from "@/schema";
|
||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||
import { createFileRoute, notFound, redirect } from "@tanstack/react-router";
|
||||
import { Group, type ID } from "jazz-tools";
|
||||
import { AnimatePresence, LayoutGroup, Reorder, motion } from "motion/react";
|
||||
import type { FormEventHandler, ReactNode } from "react";
|
||||
|
||||
import { HowToPlayContent } from "@/components/how-to-play-content";
|
||||
import { PlayingCard } from "@/components/playing-card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { WORKER_ID } from "@/constants";
|
||||
import { useCoState, useInboxSender } from "@/jazz";
|
||||
import { InfoIcon } from "lucide-react";
|
||||
|
||||
export const Route = createFileRoute("/game/$gameId")({
|
||||
component: RouteComponent,
|
||||
loader: async ({ params: { gameId }, context: { me } }) => {
|
||||
// !FIXME: this is useless, the layout takes care of this
|
||||
if (!me) {
|
||||
throw redirect({
|
||||
to: "/",
|
||||
});
|
||||
}
|
||||
const game = await Game.load(gameId as ID<Game>, me, {});
|
||||
|
||||
if (!game) {
|
||||
throw notFound();
|
||||
}
|
||||
},
|
||||
// TODO: better loading screen
|
||||
pendingComponent: () => <div>...</div>,
|
||||
// TODO: better not found page
|
||||
notFoundComponent: () => <div>Game not found</div>,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { gameId } = Route.useParams();
|
||||
const playCard = useInboxSender(WORKER_ID);
|
||||
|
||||
const game = useCoState(Game, gameId as ID<Game>, {
|
||||
// TODO: load intent only for current user
|
||||
deck: [{}],
|
||||
playedCard: { data: {} },
|
||||
player1: {
|
||||
hand: [{ data: {}, meta: {} }],
|
||||
scoredCards: [{ data: {} }],
|
||||
account: {},
|
||||
},
|
||||
player2: {
|
||||
hand: [{ data: {}, meta: {} }],
|
||||
scoredCards: [{ data: {} }],
|
||||
account: {},
|
||||
},
|
||||
activePlayer: { account: {} },
|
||||
});
|
||||
|
||||
// TODO: loading
|
||||
if (!game) return null;
|
||||
|
||||
const [opponent, me] = game.player1.account.isMe
|
||||
? [game.player2, game.player1]
|
||||
: [game.player1, game.player2];
|
||||
|
||||
const handleSubmit: FormEventHandler<HTMLFormElement> = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!game.activePlayer.account.isMe) {
|
||||
alert("not your turn!");
|
||||
return;
|
||||
}
|
||||
|
||||
const playedCard = new FormData(e.target as HTMLFormElement).get(
|
||||
"play",
|
||||
) as string;
|
||||
|
||||
const playedCardValue: number = Number.parseInt(playedCard?.slice(1));
|
||||
const playedCardSuit: string = playedCard?.[0];
|
||||
|
||||
const pc = me.hand.find(
|
||||
(card) =>
|
||||
card.data?.value === playedCardValue &&
|
||||
card.data.suit === playedCardSuit,
|
||||
);
|
||||
if (!pc) {
|
||||
return;
|
||||
}
|
||||
|
||||
playCard(
|
||||
PlayIntent.create(
|
||||
{ type: "play", card: pc, game },
|
||||
{ owner: Group.create({ owner: me.account }) },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<LayoutGroup>
|
||||
<div className="flex flex-col h-screen p-2 ">
|
||||
<PlayerArea player={opponent}>
|
||||
<ul className="flex gap-2 flex-row-reverse place-content-center ">
|
||||
<AnimatePresence>
|
||||
{opponent.hand.getSorted().map((card) => {
|
||||
if (!card) return null;
|
||||
|
||||
return (
|
||||
<motion.li key={card?.id} layout>
|
||||
<PlayingCard card={card} faceDown layoutId={card?.id} />
|
||||
</motion.li>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</ul>
|
||||
</PlayerArea>
|
||||
|
||||
<div className="container mx-auto grow items-center justify-center grid grid-cols-2">
|
||||
<div className="relative flex justify-center items-center">
|
||||
{game.deck[0] && (
|
||||
<PlayingCard
|
||||
className="rotate-[88deg] left-1/2 absolute"
|
||||
card={game.deck[0]}
|
||||
layoutId={`${game.deck[0]?.id}`}
|
||||
/>
|
||||
)}
|
||||
<CardStack cards={game.deck.slice(1)} faceDown />
|
||||
</div>
|
||||
|
||||
<div className="relative h-full items-center flex justify-center">
|
||||
<AnimatePresence>
|
||||
{game.playedCard && (
|
||||
<motion.div
|
||||
className="absolute"
|
||||
key={game.playedCard.id}
|
||||
animate={{
|
||||
rotate: 0,
|
||||
}}
|
||||
>
|
||||
<PlayingCard
|
||||
card={game.playedCard}
|
||||
layoutId={`${game.playedCard?.id}`}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<PlayerArea player={me}>
|
||||
<div className="">
|
||||
<RadioGroup.Root
|
||||
className="flex place-content-center"
|
||||
aria-label="Play card"
|
||||
orientation="horizontal"
|
||||
loop
|
||||
name="play"
|
||||
required
|
||||
asChild
|
||||
>
|
||||
<Reorder.Group
|
||||
axis="x"
|
||||
values={me.hand.getSorted()}
|
||||
onReorder={(cards) => {
|
||||
cards.forEach((card, i) => {
|
||||
if (!card?.meta) return;
|
||||
card.meta.index = i;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{me.hand
|
||||
.getSorted()
|
||||
.filter(
|
||||
(card) =>
|
||||
card?.data?.value !== undefined &&
|
||||
card.data.suit !== undefined,
|
||||
)
|
||||
.map((card, i, cards) => {
|
||||
if (!card) return null;
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
key={`${card?.data?.suit}${card?.data?.value}`}
|
||||
value={card}
|
||||
initial={{
|
||||
translateY: 800,
|
||||
}}
|
||||
animate={{
|
||||
rotate: i * 15 - (15 * (cards.length - 1)) / 2,
|
||||
translateY: 0,
|
||||
}}
|
||||
whileDrag={{ scale: 1.1 }}
|
||||
exit={{
|
||||
scale: 1.1,
|
||||
}}
|
||||
layout
|
||||
layoutId={`${card?.id}`}
|
||||
>
|
||||
<RadioGroup.Item
|
||||
value={`${card?.data?.suit}${card?.data?.value}`}
|
||||
className="relative data-[state=checked]:translate-y-[-10px] data-[state=checked]:scale-110 data-[state=unchecked]:translate-y-0 transition-transform"
|
||||
asChild
|
||||
>
|
||||
<motion.button>
|
||||
<PlayingCard card={card} />
|
||||
</motion.button>
|
||||
</RadioGroup.Item>
|
||||
</Reorder.Item>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</Reorder.Group>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
</PlayerArea>
|
||||
</form>
|
||||
</div>
|
||||
</LayoutGroup>
|
||||
);
|
||||
}
|
||||
|
||||
interface CardStackProps {
|
||||
cards: Card[];
|
||||
className?: string;
|
||||
faceDown?: boolean;
|
||||
}
|
||||
|
||||
function CardStack({ cards, className, faceDown = false }: CardStackProps) {
|
||||
return (
|
||||
<div className={cn("relative w-[150px] h-[245px]", className)}>
|
||||
<AnimatePresence>
|
||||
{cards.map((card) => (
|
||||
<motion.div key={card?.id} className="absolute" animate layout>
|
||||
<PlayingCard card={card} faceDown={faceDown} layoutId={card?.id} />
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface PlayerAreaProps {
|
||||
player: Player;
|
||||
children: ReactNode;
|
||||
}
|
||||
function PlayerArea({ children, player }: PlayerAreaProps) {
|
||||
return (
|
||||
<div className={cn("flex", !player.account?.isMe && "flex-row-reverse")}>
|
||||
<div className="flex items-center w-1/3 justify-around flex-col">
|
||||
{player.account?.isMe && (
|
||||
<>
|
||||
<Button size="lg" type="submit">
|
||||
Play selected card
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Button size="sm" type="button" variant="link">
|
||||
<InfoIcon />
|
||||
How to play?
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>How to play Briscola</DialogTitle>
|
||||
<DialogDescription>
|
||||
<HowToPlayContent />
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-1/3">{children}</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex justify-center flex-col items-center w-1/3 gap-2",
|
||||
!player.account?.isMe && "flex-col-reverse",
|
||||
)}
|
||||
>
|
||||
<span className="text-lg">
|
||||
Score:{" "}
|
||||
<span className="font-semibold">
|
||||
{getScore(player.scoredCards?.map((c) => c!) ?? [])}
|
||||
</span>
|
||||
</span>
|
||||
<CardStack cards={player.scoredCards?.map((c) => c!) ?? []} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getScore(cards: Card[]) {
|
||||
return cards.reduce((acc, card) => {
|
||||
switch (card.data?.value) {
|
||||
case 3:
|
||||
return acc + 10;
|
||||
case 1:
|
||||
return acc + 11;
|
||||
case 10:
|
||||
return acc + 4;
|
||||
case 9:
|
||||
return acc + 3;
|
||||
case 8:
|
||||
return acc + 2;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
38
examples/briscola/src/routes/how-to-play.tsx
Normal file
38
examples/briscola/src/routes/how-to-play.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { HowToPlayContent } from "@/components/how-to-play-content";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Link, createFileRoute } from "@tanstack/react-router";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
|
||||
export const Route = createFileRoute("/how-to-play")({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="flex flex-col w-full place-items-center justify-center p-2 min-h-screen">
|
||||
<Card className="max-w-screen-lg">
|
||||
<CardHeader>
|
||||
<CardTitle>How to Play?</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HowToPlayContent />
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Button asChild variant="link">
|
||||
<Link to="/">
|
||||
<ArrowLeftIcon className="w-5 h-5" />
|
||||
Play
|
||||
</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
81
examples/briscola/src/routes/index.tsx
Normal file
81
examples/briscola/src/routes/index.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { WORKER_ID } from "@/constants";
|
||||
import { useAccount, useInboxSender } from "@/jazz";
|
||||
import { CreateGameRequest } from "@/schema";
|
||||
import { Link, createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { Group } from "jazz-tools";
|
||||
import { ExternalLinkIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: HomeComponent,
|
||||
});
|
||||
|
||||
function HomeComponent() {
|
||||
const createGame = useInboxSender(WORKER_ID);
|
||||
const { me } = useAccount();
|
||||
const navigate = useNavigate({ from: "/" });
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const onNewGameClick = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
const waitingRoomId = await createGame(
|
||||
CreateGameRequest.create(
|
||||
{
|
||||
type: "createGame",
|
||||
},
|
||||
{ owner: Group.create({ owner: me }) },
|
||||
),
|
||||
);
|
||||
|
||||
if (!waitingRoomId) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate({ to: `/waiting-room/${waitingRoomId}` });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col w-full place-items-center justify-center p-2">
|
||||
<Card className="w-[500px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome to Jazz Briscola</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center p-4">
|
||||
<Button
|
||||
onClick={onNewGameClick}
|
||||
loading={isLoading}
|
||||
loadingText="Creating game..."
|
||||
className="w-full"
|
||||
>
|
||||
New Game
|
||||
</Button>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<Button variant="link" asChild>
|
||||
<Link to="/how-to-play">How to play?</Link>
|
||||
</Button>
|
||||
<Button variant="link" asChild className="text-muted-foreground">
|
||||
<Link
|
||||
href="https://en.wikipedia.org/wiki/Briscola"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Briscola on Wikipedia
|
||||
<ExternalLinkIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
103
examples/briscola/src/routes/waiting-room.$waitingRoomId.tsx
Normal file
103
examples/briscola/src/routes/waiting-room.$waitingRoomId.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { WORKER_ID } from "@/constants";
|
||||
import { JoinGameRequest, WaitingRoom } from "@/schema";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { Group, type ID, InboxSender } from "jazz-tools";
|
||||
import { ClipboardCopyIcon, Loader2Icon } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const Route = createFileRoute("/waiting-room/$waitingRoomId")({
|
||||
component: RouteComponent,
|
||||
loader: async ({ context: { me }, params: { waitingRoomId } }) => {
|
||||
if (!me) {
|
||||
throw redirect({ to: "/" });
|
||||
}
|
||||
const waitingRoom = await WaitingRoom.load(
|
||||
waitingRoomId as ID<WaitingRoom>,
|
||||
me,
|
||||
{ account1: {}, account2: {}, game: {} },
|
||||
);
|
||||
|
||||
if (!waitingRoom) {
|
||||
throw redirect({ to: "/" });
|
||||
}
|
||||
|
||||
// If the waiting room already has a game, redirect to the game
|
||||
if (waitingRoom?.game) {
|
||||
throw redirect({ to: `/game/${waitingRoom.game.id}` });
|
||||
}
|
||||
|
||||
if (!waitingRoom?.account1?.isMe) {
|
||||
const sender = await InboxSender.load<JoinGameRequest, WaitingRoom>(
|
||||
WORKER_ID,
|
||||
me,
|
||||
);
|
||||
sender.sendMessage(
|
||||
JoinGameRequest.create(
|
||||
{ type: "joinGame", waitingRoom },
|
||||
{ owner: Group.create({ owner: me }) },
|
||||
),
|
||||
);
|
||||
}
|
||||
return { waitingRoom };
|
||||
},
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { waitingRoom } = Route.useLoaderData();
|
||||
const navigate = Route.useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!waitingRoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
return waitingRoom?.subscribe({ game: {} }, () => {
|
||||
if (waitingRoom.game) {
|
||||
navigate({ to: `/game/${waitingRoom.game.id}` });
|
||||
}
|
||||
});
|
||||
}, [waitingRoom]);
|
||||
|
||||
const onCopyClick = () => {
|
||||
navigator.clipboard.writeText(window.location.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col w-full place-items-center justify-center p-2">
|
||||
<Card className="w-[500px]">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Loader2Icon className="animate-spin inline h-8 w-8 mr-2" />
|
||||
Waiting for opponent to join the game
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Share this link with your friend to join the game. The game will
|
||||
automatically start once they join.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex">
|
||||
<Input
|
||||
className="w-full border bg-muted rounded-e-none"
|
||||
readOnly
|
||||
value={`${window.location}`}
|
||||
/>
|
||||
<Button onClick={onCopyClick} className="rounded-s-none">
|
||||
<ClipboardCopyIcon className="w-5 h-5" />
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
111
examples/briscola/src/schema.ts
Normal file
111
examples/briscola/src/schema.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Account, CoList, CoMap, SchemaUnion, co } from "jazz-tools";
|
||||
|
||||
export const CardValues = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const;
|
||||
|
||||
export const CardValue = co.literal(...CardValues);
|
||||
|
||||
export const Suits = ["S", "B", "C", "D"] as const;
|
||||
|
||||
export const Suit = co.literal(...Suits);
|
||||
|
||||
export class CardMeta extends CoMap {
|
||||
index = co.optional.number;
|
||||
}
|
||||
export class CardData extends CoMap {
|
||||
value = CardValue;
|
||||
suit = Suit;
|
||||
}
|
||||
|
||||
export class Card extends CoMap {
|
||||
data = co.ref(CardData);
|
||||
meta = co.optional.ref(CardMeta);
|
||||
}
|
||||
|
||||
export class CardList extends CoList.Of(co.ref(Card)) {
|
||||
getSorted() {
|
||||
return this.toSorted(
|
||||
(a, b) => (a?.meta?.index ?? 0) - (b?.meta?.index ?? 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Player extends CoMap {
|
||||
account = co.ref(Account);
|
||||
hand = co.ref(CardList); // write Tavolo - read me - quando il Game mi da le carte le scrive qui, quando valida la giocata la toglie da qui
|
||||
scoredCards = co.ref(CardList); // write Tavolo - read everyone -
|
||||
}
|
||||
|
||||
export class Game extends CoMap {
|
||||
deck = co.ref(CardList);
|
||||
|
||||
briscola = co.literal(...Suits);
|
||||
/**
|
||||
* The card that was played in the current turn.
|
||||
*/
|
||||
playedCard = co.optional.ref(Card);
|
||||
|
||||
activePlayer = co.ref(Player);
|
||||
player1 = co.ref(Player);
|
||||
player2 = co.ref(Player);
|
||||
|
||||
/**
|
||||
* Given a player, returns the opponent in the current game.
|
||||
*/
|
||||
getOpponent(player: Player) {
|
||||
// TODO: player may be unrelated to this game
|
||||
const opponent =
|
||||
player.account?.id === this.player1?.account?.id
|
||||
? this.player2
|
||||
: this.player1;
|
||||
|
||||
if (!opponent) {
|
||||
throw new Error("Opponent not found");
|
||||
}
|
||||
|
||||
return opponent.ensureLoaded({
|
||||
account: {},
|
||||
hand: [{}],
|
||||
scoredCards: [{}],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class GameList extends CoList.Of(co.ref(Game)) {}
|
||||
|
||||
export class WaitingRoom extends CoMap {
|
||||
account1 = co.ref(Account);
|
||||
account2 = co.optional.ref(Account);
|
||||
game = co.optional.ref(Game);
|
||||
}
|
||||
|
||||
class BaseInboxMessage extends CoMap {
|
||||
type = co.literal("play", "createGame", "joinGame");
|
||||
}
|
||||
|
||||
export class PlayIntent extends BaseInboxMessage {
|
||||
type = co.literal("play");
|
||||
card = co.ref(Card);
|
||||
game = co.ref(Game);
|
||||
}
|
||||
|
||||
export class CreateGameRequest extends BaseInboxMessage {
|
||||
type = co.literal("createGame");
|
||||
}
|
||||
|
||||
export class JoinGameRequest extends BaseInboxMessage {
|
||||
type = co.literal("joinGame");
|
||||
waitingRoom = co.ref(WaitingRoom);
|
||||
}
|
||||
|
||||
export const InboxMessage = SchemaUnion.Of<BaseInboxMessage>((raw) => {
|
||||
switch (raw.get("type")) {
|
||||
case "play":
|
||||
return PlayIntent;
|
||||
case "createGame":
|
||||
return CreateGameRequest;
|
||||
case "joinGame":
|
||||
return JoinGameRequest;
|
||||
default:
|
||||
throw new Error("Unknown request type");
|
||||
}
|
||||
});
|
||||
331
examples/briscola/src/worker.ts
Normal file
331
examples/briscola/src/worker.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
import {
|
||||
Card,
|
||||
CardData,
|
||||
CardList,
|
||||
CardMeta,
|
||||
CardValues,
|
||||
Game,
|
||||
InboxMessage,
|
||||
JoinGameRequest,
|
||||
PlayIntent,
|
||||
Player,
|
||||
Suits,
|
||||
WaitingRoom,
|
||||
} from "@/schema";
|
||||
import { startWorker } from "jazz-nodejs";
|
||||
import { Account, Group, type ID } from "jazz-tools";
|
||||
|
||||
const {
|
||||
worker,
|
||||
experimental: { inbox },
|
||||
} = await startWorker({
|
||||
accountID: process.env.VITE_JAZZ_WORKER_ACCOUNT,
|
||||
syncServer: "wss://cloud.jazz.tools/?key=you@example.com",
|
||||
});
|
||||
|
||||
inbox.subscribe(
|
||||
InboxMessage,
|
||||
async (message, senderID) => {
|
||||
const playerAccount = await Account.load(senderID, worker, {});
|
||||
if (!playerAccount) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case "play":
|
||||
console.log("play message from", senderID);
|
||||
handlePlayIntent(senderID, message.castAs(PlayIntent));
|
||||
break;
|
||||
case "createGame":
|
||||
console.log("create game message from", senderID);
|
||||
|
||||
const waitingRoomGroup = Group.create({ owner: worker });
|
||||
waitingRoomGroup.addMember("everyone", "reader");
|
||||
const waitingRoom = WaitingRoom.create(
|
||||
{ account1: playerAccount },
|
||||
{ owner: waitingRoomGroup },
|
||||
);
|
||||
|
||||
console.log("waiting room created with id:", waitingRoom.id);
|
||||
|
||||
return waitingRoom;
|
||||
case "joinGame":
|
||||
console.log("join game message from", senderID);
|
||||
const joinGameRequest = message.castAs(JoinGameRequest);
|
||||
if (
|
||||
!joinGameRequest.waitingRoom ||
|
||||
!joinGameRequest.waitingRoom.account1
|
||||
) {
|
||||
console.error("No waiting room in join game request");
|
||||
return;
|
||||
}
|
||||
joinGameRequest.waitingRoom.account2 = playerAccount;
|
||||
|
||||
const game = await createGame({
|
||||
account1: joinGameRequest.waitingRoom.account1,
|
||||
account2: joinGameRequest.waitingRoom.account2,
|
||||
});
|
||||
console.log("game created with id:", game.id);
|
||||
|
||||
joinGameRequest.waitingRoom.game = game;
|
||||
return joinGameRequest.waitingRoom;
|
||||
}
|
||||
},
|
||||
{ retries: 3 },
|
||||
);
|
||||
|
||||
interface CreateGameParams {
|
||||
account1: Account;
|
||||
account2: Account;
|
||||
}
|
||||
async function createGame({ account1, account2 }: CreateGameParams) {
|
||||
const publicReadOnly = Group.create({ owner: worker });
|
||||
publicReadOnly.addMember(account1, "reader");
|
||||
publicReadOnly.addMember(account2, "reader");
|
||||
|
||||
const player1 = createPlayer({ account: account1 });
|
||||
const player2 = createPlayer({ account: account2 });
|
||||
|
||||
const deck = createDeck({ publicReadOnlyGroup: publicReadOnly });
|
||||
|
||||
while (player1.hand && player1.hand?.length < 3) {
|
||||
await drawCard(player1, deck);
|
||||
}
|
||||
|
||||
while (player2.hand && player2.hand?.length < 3) {
|
||||
await drawCard(player2, deck);
|
||||
}
|
||||
|
||||
const game = Game.create(
|
||||
{
|
||||
deck,
|
||||
briscola: deck[0]?.data?.suit!,
|
||||
activePlayer: player1,
|
||||
player1: player1,
|
||||
player2: player2,
|
||||
},
|
||||
{ owner: publicReadOnly },
|
||||
);
|
||||
|
||||
await game.waitForSync();
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
interface CreatePlayerParams {
|
||||
account: Account;
|
||||
}
|
||||
function createPlayer({ account }: CreatePlayerParams) {
|
||||
const publicRead = Group.create({ owner: worker });
|
||||
publicRead.addMember("everyone", "reader");
|
||||
|
||||
const player = Player.create(
|
||||
{
|
||||
scoredCards: CardList.create([], {
|
||||
owner: publicRead,
|
||||
}),
|
||||
account,
|
||||
hand: CardList.create([], { owner: publicRead }),
|
||||
},
|
||||
{ owner: publicRead },
|
||||
);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
interface CreateDeckParams {
|
||||
publicReadOnlyGroup: Group;
|
||||
}
|
||||
function createDeck({ publicReadOnlyGroup }: CreateDeckParams) {
|
||||
const allCards = Suits.flatMap((suit) => {
|
||||
return CardValues.map((value) => {
|
||||
return { value, suit };
|
||||
});
|
||||
});
|
||||
shuffle(allCards);
|
||||
|
||||
const deck = CardList.create(
|
||||
allCards.map((card, i) => {
|
||||
const cardDataGroup = Group.create({ owner: worker });
|
||||
|
||||
return Card.create(
|
||||
{
|
||||
// The first card is the briscola, it should be visible by everyone,
|
||||
// so we make its owner the public read-only group.
|
||||
data: CardData.create(card, {
|
||||
owner: i === 0 ? publicReadOnlyGroup : cardDataGroup,
|
||||
}),
|
||||
},
|
||||
{ owner: publicReadOnlyGroup },
|
||||
);
|
||||
}),
|
||||
{ owner: publicReadOnlyGroup },
|
||||
);
|
||||
|
||||
return deck;
|
||||
}
|
||||
|
||||
async function drawCard(player: Player, deck: CardList) {
|
||||
const card = deck.pop();
|
||||
|
||||
const playerAccount = (await player.ensureLoaded({ account: {} }))?.account;
|
||||
|
||||
if (!playerAccount) {
|
||||
console.error("failed to load player account");
|
||||
return;
|
||||
}
|
||||
|
||||
if (card) {
|
||||
const metaGroup = Group.create({ owner: worker });
|
||||
metaGroup.addMember("everyone", "reader");
|
||||
metaGroup.addMember(playerAccount, "writer");
|
||||
// Create the card meta. This is visible to everyone.
|
||||
// It's used to sort the cards in the UI.
|
||||
card.meta = CardMeta.create({}, { owner: metaGroup });
|
||||
// Add the player to the card's data group so that
|
||||
// the player can read the card data.
|
||||
card.data?._owner.castAs(Group).addMember(playerAccount, "reader");
|
||||
player.hand?.push(card);
|
||||
}
|
||||
}
|
||||
|
||||
// Fisher–Yates shuffle
|
||||
function shuffle(array: unknown[]) {
|
||||
let currentIndex = array.length;
|
||||
|
||||
while (currentIndex) {
|
||||
const randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex--;
|
||||
|
||||
[array[currentIndex], array[randomIndex]] = [
|
||||
array[randomIndex],
|
||||
array[currentIndex],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function getCardValue(card: CardData) {
|
||||
switch (card.value) {
|
||||
case 1:
|
||||
return 20;
|
||||
case 3:
|
||||
return 15;
|
||||
default:
|
||||
return card.value;
|
||||
}
|
||||
}
|
||||
|
||||
async function handlePlayIntent(senderId: ID<Account>, playIntent: PlayIntent) {
|
||||
const playedCard = await playIntent.ensureLoaded({ card: { data: {} } });
|
||||
if (!playedCard) {
|
||||
console.log("No card in play intent");
|
||||
return;
|
||||
}
|
||||
|
||||
const state = await playIntent.ensureLoaded({
|
||||
game: {
|
||||
playedCard: { data: {} },
|
||||
deck: [{ data: {} }],
|
||||
activePlayer: { account: {} },
|
||||
player1: { hand: [{ data: {} }], scoredCards: [{}] },
|
||||
player2: { hand: [{ data: {} }], scoredCards: [{}] },
|
||||
},
|
||||
});
|
||||
|
||||
if (!state?.game) {
|
||||
console.log("No game found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.game.activePlayer.account.id !== senderId) {
|
||||
console.log("Not player's turn");
|
||||
return;
|
||||
}
|
||||
|
||||
const publicReadOnly = state.game?.deck._owner.castAs(Group);
|
||||
|
||||
const player = state.game.activePlayer;
|
||||
const opponent = await state.game.getOpponent(player);
|
||||
|
||||
if (!opponent) {
|
||||
console.error("failed to get opponent");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"player",
|
||||
player.account?.id,
|
||||
"played",
|
||||
playedCard.card.data?.value,
|
||||
playedCard.card.data?.suit,
|
||||
);
|
||||
|
||||
const cardIndex =
|
||||
player.hand?.findIndex((card) => {
|
||||
return card?.id === playedCard.card.id;
|
||||
}) ?? -1;
|
||||
|
||||
if (cardIndex === -1) {
|
||||
console.log("Card not found in player's hand");
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the card from player's hand
|
||||
player.hand?.splice(cardIndex, 1);
|
||||
|
||||
// make the newly played card's data visible to everyone by extending its group with
|
||||
// the public read-only group so that everyone can see the card.
|
||||
const group = await playedCard.card.data._owner
|
||||
.castAs(Group)
|
||||
.ensureLoaded({});
|
||||
group?.extend(publicReadOnly);
|
||||
|
||||
// If there's already a card on the table, it means both players have played.
|
||||
if (state.game.playedCard) {
|
||||
// Check who's this turn's winner
|
||||
let winner: Player;
|
||||
// If both cards have the same suit, the one with the highest value wins
|
||||
if (state.game.playedCard.data?.suit === playedCard.card.data?.suit) {
|
||||
winner =
|
||||
getCardValue(playedCard.card.data) >
|
||||
getCardValue(state.game.playedCard.data)
|
||||
? player
|
||||
: opponent;
|
||||
} else {
|
||||
// else the active player wins only if they played a briscola.
|
||||
// (we already know the other player didn't)
|
||||
if (playedCard.card.data.suit === state.game.briscola) {
|
||||
winner = player;
|
||||
} else {
|
||||
winner = opponent;
|
||||
}
|
||||
}
|
||||
|
||||
// Put the cards in the winner's scored cards pile.
|
||||
winner.scoredCards?.push(state.game.playedCard, playedCard.card);
|
||||
|
||||
// The winner of the round always draws first.
|
||||
if (state.game.deck.length > 0) {
|
||||
drawCard(winner, state.game.deck);
|
||||
|
||||
const opponent = await state.game.getOpponent(winner);
|
||||
if (!opponent) {
|
||||
console.error("failed to get opponent");
|
||||
return;
|
||||
}
|
||||
drawCard(opponent, state.game.deck);
|
||||
}
|
||||
|
||||
// @ts-expect-error types are wonky
|
||||
state.game.activePlayer = winner;
|
||||
// And finally, remove the played card from the table.
|
||||
delete state.game.playedCard;
|
||||
|
||||
// TODO: if there are no more cards in the deck and both players have played all their cards, end the game.
|
||||
} else {
|
||||
// else, just put the card on the table and switch the active player.
|
||||
state.game.playedCard = playedCard.card;
|
||||
|
||||
state.game.activePlayer = opponent;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
@@ -30,10 +28,6 @@ module.exports = {
|
||||
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))",
|
||||
@@ -42,35 +36,26 @@ module.exports = {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
chart: {
|
||||
1: "hsl(var(--chart-1))",
|
||||
2: "hsl(var(--chart-2))",
|
||||
3: "hsl(var(--chart-3))",
|
||||
4: "hsl(var(--chart-4))",
|
||||
5: "hsl(var(--chart-5))",
|
||||
},
|
||||
},
|
||||
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",
|
||||
|
||||
aspectRatio: {
|
||||
card: "2584/4181",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
};
|
||||
14
examples/briscola/tsconfig.json
Normal file
14
examples/briscola/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
14
examples/briscola/vite.config.ts
Normal file
14
examples/briscola/vite.config.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { resolve } from "path";
|
||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite({}), react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
1
examples/chat-rn-clerk/.env
Normal file
1
examples/chat-rn-clerk/.env
Normal file
@@ -0,0 +1 @@
|
||||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
17
examples/chat-rn-clerk/.gitignore
vendored
Normal file
17
examples/chat-rn-clerk/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
ios
|
||||
android
|
||||
366
examples/chat-rn-clerk/CHANGELOG.md
Normal file
366
examples/chat-rn-clerk/CHANGELOG.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.50
|
||||
- jazz-react-native-auth-clerk@0.8.50
|
||||
- jazz-tools@0.8.50
|
||||
- jazz-react-native-media-images@0.8.50
|
||||
|
||||
## 1.0.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.49
|
||||
- jazz-react-native-auth-clerk@0.8.49
|
||||
- jazz-tools@0.8.49
|
||||
- jazz-react-native-media-images@0.8.49
|
||||
|
||||
## 1.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [635e824]
|
||||
- Updated dependencies [0a85982]
|
||||
- jazz-tools@0.8.48
|
||||
- jazz-react-native@0.8.48
|
||||
- jazz-react-native-auth-clerk@0.8.48
|
||||
- jazz-react-native-media-images@0.8.48
|
||||
|
||||
## 1.0.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [33ef9c4]
|
||||
- jazz-react-native@0.8.47
|
||||
- jazz-react-native-auth-clerk@0.8.47
|
||||
|
||||
## 1.0.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ab4ffbd]
|
||||
- jazz-react-native@0.8.46
|
||||
- jazz-react-native-auth-clerk@0.8.46
|
||||
|
||||
## 1.0.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7701307]
|
||||
- Updated dependencies [fa41f8e]
|
||||
- Updated dependencies [88d7d9a]
|
||||
- Updated dependencies [60e35ea]
|
||||
- jazz-react-native@0.8.45
|
||||
- jazz-tools@0.8.45
|
||||
- jazz-react-native-auth-clerk@0.8.45
|
||||
- jazz-react-native-media-images@0.8.45
|
||||
|
||||
## 1.0.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.44
|
||||
- jazz-react-native-auth-clerk@0.8.44
|
||||
- jazz-tools@0.8.44
|
||||
- jazz-react-native-media-images@0.8.44
|
||||
|
||||
## 1.0.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cdc7f9f: Fixing react-native examples
|
||||
- Updated dependencies [cdc7f9f]
|
||||
- jazz-react-native-auth-clerk@0.8.43
|
||||
|
||||
## 1.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.41
|
||||
- jazz-react-native-auth-clerk@0.8.41
|
||||
- jazz-tools@0.8.41
|
||||
- jazz-react-native-media-images@0.8.41
|
||||
|
||||
## 1.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0c6b0f3]
|
||||
- Updated dependencies [249eecb]
|
||||
- jazz-react-native@0.8.39
|
||||
- jazz-tools@0.8.39
|
||||
- jazz-react-native-auth-clerk@0.8.39
|
||||
- jazz-react-native-media-images@0.8.39
|
||||
|
||||
## 1.0.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.38
|
||||
- jazz-react-native-auth-clerk@0.8.38
|
||||
- jazz-tools@0.8.38
|
||||
- jazz-react-native-media-images@0.8.38
|
||||
|
||||
## 1.0.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.37
|
||||
- jazz-react-native-auth-clerk@0.8.37
|
||||
- jazz-tools@0.8.37
|
||||
- jazz-react-native-media-images@0.8.28
|
||||
|
||||
## 1.0.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c84764a: feat: added jazz-react-native-auth-clerk package
|
||||
- Updated dependencies [c84764a]
|
||||
- Updated dependencies [441fe27]
|
||||
- jazz-react-native-auth-clerk@0.8.36
|
||||
- jazz-react-native@0.8.36
|
||||
- jazz-tools@0.8.36
|
||||
- jazz-react-native-media-images@0.8.27
|
||||
|
||||
## 1.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8b87117]
|
||||
- jazz-tools@0.8.35
|
||||
- jazz-react-auth-clerk@0.8.35
|
||||
- jazz-react-native@0.8.35
|
||||
- jazz-react-native-media-images@0.8.26
|
||||
|
||||
## 1.0.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9ca25d1]
|
||||
- jazz-react-auth-clerk@0.8.34
|
||||
- jazz-react-native@0.8.34
|
||||
- jazz-tools@0.8.34
|
||||
- jazz-react-native-media-images@0.8.25
|
||||
|
||||
## 1.0.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.33
|
||||
|
||||
## 1.0.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1a4bda0]
|
||||
- Updated dependencies [df42b2b]
|
||||
- jazz-react-auth-clerk@0.8.32
|
||||
- jazz-tools@0.8.32
|
||||
- jazz-react-native@0.8.32
|
||||
- jazz-react-native-media-images@0.8.24
|
||||
|
||||
## 1.0.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.31
|
||||
- jazz-react-native@0.8.31
|
||||
- jazz-tools@0.8.31
|
||||
- jazz-react-native-media-images@0.8.23
|
||||
|
||||
## 1.0.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.30
|
||||
- jazz-react-native@0.8.30
|
||||
- jazz-tools@0.8.30
|
||||
- jazz-react-native-media-images@0.8.22
|
||||
|
||||
## 1.0.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.29
|
||||
- jazz-react-auth-clerk@0.8.29
|
||||
- jazz-tools@0.8.29
|
||||
- jazz-react-native-media-images@0.8.21
|
||||
|
||||
## 1.0.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.28
|
||||
- jazz-react-native@0.8.28
|
||||
- jazz-tools@0.8.28
|
||||
- jazz-react-native-media-images@0.8.20
|
||||
|
||||
## 1.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.27
|
||||
- jazz-react-native@0.8.27
|
||||
- jazz-tools@0.8.27
|
||||
- jazz-react-native-media-images@0.8.19
|
||||
|
||||
## 1.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.26
|
||||
|
||||
## 1.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.24
|
||||
|
||||
## 1.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d348c2d]
|
||||
- Updated dependencies [6902b5b]
|
||||
- Updated dependencies [1a0cd3d]
|
||||
- jazz-tools@0.8.23
|
||||
- jazz-react-auth-clerk@0.8.23
|
||||
- jazz-react-native@0.8.23
|
||||
- jazz-react-native-media-images@0.8.18
|
||||
|
||||
## 1.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.22
|
||||
|
||||
## 1.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react-auth-clerk@0.8.21
|
||||
- jazz-react-native@0.8.21
|
||||
- jazz-react-native-media-images@0.8.17
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- jazz-react-native-media-images@0.8.16
|
||||
- jazz-react-native@0.8.20
|
||||
- jazz-react-auth-clerk@0.8.20
|
||||
|
||||
## 1.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.19
|
||||
- jazz-react-native@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-react-native-media-images@0.8.15
|
||||
|
||||
## 1.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.18
|
||||
- jazz-react-native@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
- jazz-react-native-media-images@0.8.14
|
||||
|
||||
## 1.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.17
|
||||
- jazz-react-native@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
- jazz-react-native-media-images@0.8.13
|
||||
|
||||
## 1.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.16
|
||||
- jazz-react-native@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
- jazz-react-native-media-images@0.8.12
|
||||
|
||||
## 1.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- Updated dependencies [221c58f]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react-auth-clerk@0.8.15
|
||||
- jazz-react-native@0.8.15
|
||||
- jazz-react-native-media-images@0.8.11
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react-auth-clerk@0.8.14
|
||||
- jazz-react-native@0.8.14
|
||||
- jazz-react-native-media-images@0.8.10
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react-auth-clerk@0.8.13
|
||||
- jazz-react-native@0.8.13
|
||||
- jazz-react-native-media-images@0.8.9
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.12
|
||||
- jazz-react-native@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- jazz-react-native-media-images@0.8.8
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.11
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-react-native-media-images@0.8.7
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||
- Updated dependencies [b7639cf]
|
||||
- jazz-react-native@0.8.8
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [32b05b6]
|
||||
- jazz-react-native-media-images@0.8.6
|
||||
- jazz-react-native@0.8.7
|
||||
- jazz-react-auth-clerk@0.8.7
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.6
|
||||
- jazz-react-auth-clerk@0.8.6
|
||||
24
examples/chat-rn-clerk/README.md
Normal file
24
examples/chat-rn-clerk/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 🎷 Jazz + Expo + `expo-router` + Clerk Auth
|
||||
|
||||
## 🚀 How to Run
|
||||
|
||||
### 1. Inside the Workspace Root
|
||||
|
||||
First, install dependencies and build the project:
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
### 2. Inside the `examples/chat-rn-clerk` Directory
|
||||
|
||||
Next, navigate to the specific example project and run the following commands:
|
||||
|
||||
```bash
|
||||
pnpm expo prebuild
|
||||
npx pod-install
|
||||
pnpm expo run:ios
|
||||
```
|
||||
|
||||
This will set up and launch the app on iOS. For Android, you can replace the last command with `pnpm expo run:android`.
|
||||
48
examples/chat-rn-clerk/app.json
Normal file
48
examples/chat-rn-clerk/app.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "jazz-chat-rn-clerk",
|
||||
"scheme": "jazz-chat-rn-clerk",
|
||||
"slug": "jazz-chat-rn-clerk",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.jazz.chatrnclerk"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.jazz.chatrnclerk"
|
||||
},
|
||||
"plugins": [
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"newArchEnabled": true
|
||||
},
|
||||
"android": {
|
||||
"newArchEnabled": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"expo-secure-store",
|
||||
"expo-font",
|
||||
"expo-router"
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "ca3d46e5-a10a-47ec-9d77-3b841e1c62d4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
examples/chat-rn-clerk/app/(app)/_layout.tsx
Normal file
15
examples/chat-rn-clerk/app/(app)/_layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
import React from "react";
|
||||
import { useAuth } from "../../src/auth-context";
|
||||
|
||||
export default function HomeLayout() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false, headerBackVisible: true }} />
|
||||
);
|
||||
}
|
||||
33
examples/chat-rn-clerk/app/(app)/index.tsx
Normal file
33
examples/chat-rn-clerk/app/(app)/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { SignedOut } from "@clerk/clerk-expo";
|
||||
import { Link } from "expo-router";
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-100 p-6">
|
||||
<SignedOut>
|
||||
<View className="bg-white p-6 rounded-lg shadow-lg w-11/12 max-w-md">
|
||||
<Text className="text-2xl font-bold text-center text-gray-900 mb-4">
|
||||
Jazz 🤝 Clerk 🤝 Expo
|
||||
</Text>
|
||||
<Link href="/sign-in" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-in-oauth" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In OAuth
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign Up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</SignedOut>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user