Compare commits
763 Commits
v0.57.0
...
v0.63.3-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4319745c52 | ||
|
|
69d2b36788 | ||
|
|
eebc785e4c | ||
|
|
d109220e0d | ||
|
|
a88a2e601d | ||
|
|
7cefb16086 | ||
|
|
c2c30f6615 | ||
|
|
bf9a1091e3 | ||
|
|
e1f1a706a6 | ||
|
|
75ebaabc2c | ||
|
|
d608331f08 | ||
|
|
448c472ee6 | ||
|
|
370a6f3dbd | ||
|
|
d8685baa47 | ||
|
|
6a07f59d34 | ||
|
|
7981cd45ed | ||
|
|
66b1283c95 | ||
|
|
d275474b23 | ||
|
|
86057ab071 | ||
|
|
aafc3a9584 | ||
|
|
dc657a647e | ||
|
|
e5f0965138 | ||
|
|
b2b25acc4c | ||
|
|
0b79950510 | ||
|
|
d6d1e20f07 | ||
|
|
c58abf1b0b | ||
|
|
88d2e2e277 | ||
|
|
946c92667f | ||
|
|
f54f653d42 | ||
|
|
ef72c75fab | ||
|
|
c6e52dbef7 | ||
|
|
62547e87dd | ||
|
|
eb6b545eeb | ||
|
|
4c4ebbfa19 | ||
|
|
61f31bf010 | ||
|
|
495fd151f5 | ||
|
|
482a5bb02a | ||
|
|
4e35b26365 | ||
|
|
c180137e02 | ||
|
|
ef837232bc | ||
|
|
d0aa9f1c57 | ||
|
|
aa549d1da7 | ||
|
|
259a758849 | ||
|
|
7ac45379eb | ||
|
|
0940482c62 | ||
|
|
9cbb698b96 | ||
|
|
1820be4990 | ||
|
|
37ca232548 | ||
|
|
ea9b009a22 | ||
|
|
dbc804669c | ||
|
|
c8fbc0d348 | ||
|
|
8361b4d47a | ||
|
|
ae2021e073 | ||
|
|
c4b21a0ab5 | ||
|
|
8a095d0a55 | ||
|
|
c0bf8fd6a8 | ||
|
|
14329980e1 | ||
|
|
950626fe9e | ||
|
|
8b7e587467 | ||
|
|
552ebc0f29 | ||
|
|
9148e1d30a | ||
|
|
9156d488ca | ||
|
|
e1ac1675ea | ||
|
|
863250904b | ||
|
|
8a926c4a23 | ||
|
|
760cceb378 | ||
|
|
c5650f9334 | ||
|
|
bf968707df | ||
|
|
31fa7ca90c | ||
|
|
309f401015 | ||
|
|
33d02cedd7 | ||
|
|
daa75b5282 | ||
|
|
36c64045ae | ||
|
|
93e1e30323 | ||
|
|
9b69f9a3ff | ||
|
|
d5fbb59656 | ||
|
|
7ba95d5d6c | ||
|
|
d79aeba2c1 | ||
|
|
672b445676 | ||
|
|
e02199fa2a | ||
|
|
9e55051811 | ||
|
|
7db176a763 | ||
|
|
92886236a2 | ||
|
|
8d94de8eb2 | ||
|
|
eaebec88c0 | ||
|
|
22fa6c07dd | ||
|
|
afe9ab9d8c | ||
|
|
16139cc6b6 | ||
|
|
31a904d370 | ||
|
|
02cf7679a3 | ||
|
|
df708465d1 | ||
|
|
aa9ccf3411 | ||
|
|
6410fdc474 | ||
|
|
499d947e69 | ||
|
|
c093516351 | ||
|
|
41699224ff | ||
|
|
8886cb5786 | ||
|
|
d85d4de218 | ||
|
|
f56f0b7bbb | ||
|
|
ae79b50101 | ||
|
|
fcfc4a4298 | ||
|
|
d355bd3372 | ||
|
|
2bfd46d48c | ||
|
|
f1b41389b3 | ||
|
|
92a4998ddc | ||
|
|
23d7209f82 | ||
|
|
cf3c610eba | ||
|
|
6a010f58be | ||
|
|
0f1b0a4a78 | ||
|
|
a4a8596a29 | ||
|
|
1cdd3c0e28 | ||
|
|
22db5bffe8 | ||
|
|
a61f3b715b | ||
|
|
949a28d49c | ||
|
|
88be4fe77e | ||
|
|
625a62626e | ||
|
|
ee440cf300 | ||
|
|
cf2ec99a4d | ||
|
|
bb0f6e85a8 | ||
|
|
4412217f51 | ||
|
|
1e85361914 | ||
|
|
f611b443c0 | ||
|
|
5984be3d84 | ||
|
|
5a8061ac7b | ||
|
|
509c327b3b | ||
|
|
56a66b348d | ||
|
|
a7d86a164c | ||
|
|
383334633f | ||
|
|
6a2dc444c6 | ||
|
|
e9073310c4 | ||
|
|
3b67602b13 | ||
|
|
04477e9f97 | ||
|
|
990c83eabd | ||
|
|
ddc71653ad | ||
|
|
e5e5cf1314 | ||
|
|
f364a15d89 | ||
|
|
2b4fd53202 | ||
|
|
dfe2fd0386 | ||
|
|
2055f05b09 | ||
|
|
33ebfc3f10 | ||
|
|
6a4f3aaa56 | ||
|
|
c1f7ac0d8c | ||
|
|
19adfdf8bb | ||
|
|
af74d5409a | ||
|
|
2a3773240d | ||
|
|
782676dc67 | ||
|
|
68717d0fe8 | ||
|
|
8bd9577318 | ||
|
|
2ac537393d | ||
|
|
82956b618a | ||
|
|
a725ded95e | ||
|
|
113b7f6f97 | ||
|
|
aed085b168 | ||
|
|
345544646a | ||
|
|
4520227e98 | ||
|
|
f5795ffc6f | ||
|
|
8cde64d3f6 | ||
|
|
d7b8a189e4 | ||
|
|
cfde3e348c | ||
|
|
70e2951e35 | ||
|
|
ba35536664 | ||
|
|
b9f9819637 | ||
|
|
076d353e84 | ||
|
|
64e9b9f893 | ||
|
|
21ad375b42 | ||
|
|
cb9534eae0 | ||
|
|
8b43368bf9 | ||
|
|
c96c8fd782 | ||
|
|
c295f943ba | ||
|
|
e527474dd9 | ||
|
|
73f267167f | ||
|
|
40290a9a42 | ||
|
|
bd35468d18 | ||
|
|
c80395fc18 | ||
|
|
95be2c6070 | ||
|
|
fb7a92242b | ||
|
|
8c2ff69515 | ||
|
|
011085a93f | ||
|
|
dce21900a7 | ||
|
|
2b5ac535b9 | ||
|
|
484c8f7cbe | ||
|
|
7e4d582d1e | ||
|
|
50c4783333 | ||
|
|
9860dbbbea | ||
|
|
874a3605f8 | ||
|
|
088c5bac1f | ||
|
|
e135b982c1 | ||
|
|
a8bd234aa4 | ||
|
|
f99d70500c | ||
|
|
476020ae84 | ||
|
|
2f1ddc0d0f | ||
|
|
ef5844bc79 | ||
|
|
0c9ceb51e6 | ||
|
|
cedc0f64d5 | ||
|
|
9952f08cce | ||
|
|
efa6745035 | ||
|
|
4816a587c3 | ||
|
|
6514eb5209 | ||
|
|
2a38c4938d | ||
|
|
b015761131 | ||
|
|
99e6ecc466 | ||
|
|
7e411ae098 | ||
|
|
1bbb7dd126 | ||
|
|
78969d0938 | ||
|
|
bac3dc1ccd | ||
|
|
ae44a38285 | ||
|
|
77b13b1356 | ||
|
|
2e97e2dbfd | ||
|
|
75ec5c3b1b | ||
|
|
3a456b09cb | ||
|
|
022f70b1de | ||
|
|
c1e23fc6d9 | ||
|
|
a6e9d0d061 | ||
|
|
b700ea84a5 | ||
|
|
0ef62fc334 | ||
|
|
c3900565b9 | ||
|
|
a86756ed20 | ||
|
|
e3ef6d35ab | ||
|
|
038670cc6f | ||
|
|
5d87a04dc3 | ||
|
|
fbfe8a2311 | ||
|
|
bd8509990a | ||
|
|
6bdb08ab9c | ||
|
|
db8b8ef66b | ||
|
|
ac5d5e2451 | ||
|
|
fad6cfef05 | ||
|
|
c83cae60f6 | ||
|
|
9b8e6cce02 | ||
|
|
9858906463 | ||
|
|
be1dc01d9e | ||
|
|
de24b4b4e8 | ||
|
|
629d3d473c | ||
|
|
5dc82d3df8 | ||
|
|
76a1b81e45 | ||
|
|
99aa1219d2 | ||
|
|
69472f7823 | ||
|
|
723fa83909 | ||
|
|
2f064d5ccc | ||
|
|
ae9a0a99ea | ||
|
|
c2b9b08944 | ||
|
|
2aa2e5af7a | ||
|
|
b7c439f4c4 | ||
|
|
e6b29086a9 | ||
|
|
83e4e26989 | ||
|
|
caec9c1f45 | ||
|
|
e3809c267d | ||
|
|
0d9eecd2ed | ||
|
|
d7915840d0 | ||
|
|
8098697847 | ||
|
|
4c2f8406c7 | ||
|
|
e0a477265d | ||
|
|
364c3f2f00 | ||
|
|
75c79d60fe | ||
|
|
5b2dd8e4d0 | ||
|
|
9e8e227b46 | ||
|
|
adf7578007 | ||
|
|
b6e5aa3bb0 | ||
|
|
288c039929 | ||
|
|
fb5c6493cf | ||
|
|
3160d07b9c | ||
|
|
e49fc9f4b1 | ||
|
|
ed6f482e68 | ||
|
|
773f569385 | ||
|
|
219793afcc | ||
|
|
571636c526 | ||
|
|
cbc15b6b58 | ||
|
|
c410935c9c | ||
|
|
79cf5dbd4b | ||
|
|
da5203011c | ||
|
|
84c7aa9cad | ||
|
|
f8e5a08324 | ||
|
|
5e57a33df7 | ||
|
|
38bdf7ad92 | ||
|
|
5447f63e9d | ||
|
|
50ba8bdc9b | ||
|
|
6f279c0239 | ||
|
|
26ccd70e77 | ||
|
|
b0ddbeb0ad | ||
|
|
826eb113e7 | ||
|
|
2661a9cc98 | ||
|
|
b06366ebb7 | ||
|
|
c7a629ba6b | ||
|
|
d155c11729 | ||
|
|
0c3c1e1f68 | ||
|
|
6c322dc835 | ||
|
|
6019e4c37b | ||
|
|
9c8dd66b20 | ||
|
|
0c0e8688ed | ||
|
|
6146923dbb | ||
|
|
2c4f003897 | ||
|
|
0491747eed | ||
|
|
29b9651ebd | ||
|
|
48a1dd1588 | ||
|
|
9cf39b1da6 | ||
|
|
47be340cac | ||
|
|
bf98300547 | ||
|
|
46635956f4 | ||
|
|
8c6de99159 | ||
|
|
a42a703b35 | ||
|
|
59fab0bb2d | ||
|
|
c73e2c2d0f | ||
|
|
8c1c98a0bf | ||
|
|
d99a074bc0 | ||
|
|
05b4b443d9 | ||
|
|
4b09f77950 | ||
|
|
dbea3cf20c | ||
|
|
aa8fa4a6d5 | ||
|
|
dbc03e2668 | ||
|
|
4ef69c8361 | ||
|
|
895aeb033f | ||
|
|
e15cc376b0 | ||
|
|
54428ca6f6 | ||
|
|
54cf6fa838 | ||
|
|
09a0b3eb55 | ||
|
|
40c3e925ad | ||
|
|
5ef5147780 | ||
|
|
318b923bac | ||
|
|
93a30ea940 | ||
|
|
5bb2edca8b | ||
|
|
1789dfb8b1 | ||
|
|
f473eadf2d | ||
|
|
1f161b9aa1 | ||
|
|
354fefe61b | ||
|
|
19c98bb5ad | ||
|
|
2149c17a0a | ||
|
|
1716aff969 | ||
|
|
2a5d7ea2de | ||
|
|
be34c50c72 | ||
|
|
50ae3e03f7 | ||
|
|
499b8f5f55 | ||
|
|
81d83841ab | ||
|
|
cce00526b9 | ||
|
|
c9225bb87c | ||
|
|
75c339851f | ||
|
|
e39c7c62e4 | ||
|
|
b6bb2985f5 | ||
|
|
6bdbab2faf | ||
|
|
f09d6b7b95 | ||
|
|
19a2752674 | ||
|
|
5d433b1666 | ||
|
|
caeae38e3a | ||
|
|
c25acc155d | ||
|
|
4222f86537 | ||
|
|
9569323f93 | ||
|
|
0bbba90f30 | ||
|
|
f1ff557a25 | ||
|
|
23d7143298 | ||
|
|
12eab6551f | ||
|
|
d25c6b15a6 | ||
|
|
b9308ad80d | ||
|
|
6e363e464c | ||
|
|
6e53deb1b2 | ||
|
|
0717c168d9 | ||
|
|
6d020a3ee9 | ||
|
|
9a381c1803 | ||
|
|
3e23d1f48d | ||
|
|
1750fcf833 | ||
|
|
646d344a11 | ||
|
|
bc03592912 | ||
|
|
a4b518ec72 | ||
|
|
b541ac313c | ||
|
|
934474f87e | ||
|
|
3a4e802093 | ||
|
|
b3eb5f7cdf | ||
|
|
c21e0e916c | ||
|
|
d301a215f7 | ||
|
|
8044beffc7 | ||
|
|
8df84e0341 | ||
|
|
137a9cefbd | ||
|
|
55576f879b | ||
|
|
78aee53411 | ||
|
|
864020463f | ||
|
|
2d3d07d4d7 | ||
|
|
ad6f9b2499 | ||
|
|
330968434f | ||
|
|
4b12fb6b3b | ||
|
|
eef086f60f | ||
|
|
6ac0b81778 | ||
|
|
8d82702da2 | ||
|
|
dde3dfdbf6 | ||
|
|
8d609959f1 | ||
|
|
16f854b636 | ||
|
|
9c47325c25 | ||
|
|
cf499abf31 | ||
|
|
86ddbc6d26 | ||
|
|
b8bc5a282e | ||
|
|
f5db02a605 | ||
|
|
9ebd586350 | ||
|
|
1bec8087ee | ||
|
|
a5a60eb854 | ||
|
|
edb61a9c8f | ||
|
|
06dfb74663 | ||
|
|
26b03afa60 | ||
|
|
c4680e66ff | ||
|
|
06e9b8276f | ||
|
|
ad975da8bd | ||
|
|
37a0fd33c5 | ||
|
|
f28cc5ca0c | ||
|
|
0a1aea6cb8 | ||
|
|
a6a7e85894 | ||
|
|
e75dcc853b | ||
|
|
b5786cbf30 | ||
|
|
513c02e67f | ||
|
|
51c0a140c6 | ||
|
|
e73270085b | ||
|
|
dd1320e6d1 | ||
|
|
d42bf8eebe | ||
|
|
2a1dbd6fb5 | ||
|
|
9760eb0081 | ||
|
|
6cdf4e98fc | ||
|
|
2ff6ffff58 | ||
|
|
27a87c3d9e | ||
|
|
1d8717f4de | ||
|
|
fedec68d39 | ||
|
|
490a608663 | ||
|
|
94a5bbc0ab | ||
|
|
89f05ada0b | ||
|
|
3bb1f0097f | ||
|
|
69dcfbb423 | ||
|
|
e744520d90 | ||
|
|
3c3671a193 | ||
|
|
cbf31e6d27 | ||
|
|
b3567a7240 | ||
|
|
296656570e | ||
|
|
aac24938f5 | ||
|
|
47332f97c7 | ||
|
|
1179f8f7be | ||
|
|
bd146306c6 | ||
|
|
c4dde0f4e2 | ||
|
|
ec19f0f8e9 | ||
|
|
cc56fa9ea6 | ||
|
|
a19783919c | ||
|
|
83d3fad80d | ||
|
|
202950aa98 | ||
|
|
9adbab5d99 | ||
|
|
a6910584b6 | ||
|
|
e24a69b838 | ||
|
|
b1f64d9550 | ||
|
|
41590ef64b | ||
|
|
e7b6d1befe | ||
|
|
76a86b7e5e | ||
|
|
7eceff1d7b | ||
|
|
81a3a22379 | ||
|
|
d1f1eb9a29 | ||
|
|
5487f99ac7 | ||
|
|
bc2a6e429c | ||
|
|
0beb97547e | ||
|
|
941f4097fe | ||
|
|
673041d1f5 | ||
|
|
6dfa34fcf8 | ||
|
|
b626ec3bf9 | ||
|
|
5708879b5a | ||
|
|
638e9f9477 | ||
|
|
acc85ad03c | ||
|
|
0a8e2f6bb0 | ||
|
|
9bdcd37f60 | ||
|
|
a833652077 | ||
|
|
7ce758b343 | ||
|
|
cc8ae45012 | ||
|
|
65b8c512fe | ||
|
|
0e695eaae8 | ||
|
|
1f0a9ce418 | ||
|
|
a656047c15 | ||
|
|
f26695ea8c | ||
|
|
f4306d977f | ||
|
|
d93e75bf5f | ||
|
|
67a32de7d4 | ||
|
|
ba6c5441c0 | ||
|
|
e2700ff8c6 | ||
|
|
f83de0a91c | ||
|
|
4c07a0782b | ||
|
|
ee2587d3e5 | ||
|
|
45d118f96f | ||
|
|
eb711cde53 | ||
|
|
4504b36c8f | ||
|
|
29c3b81a0a | ||
|
|
feb17c29ec | ||
|
|
8e7f96cebc | ||
|
|
0a306808da | ||
|
|
1d4bdfc4a1 | ||
|
|
9ec62d4c1f | ||
|
|
bf0a04ab50 | ||
|
|
bf488f2027 | ||
|
|
b229bc69b9 | ||
|
|
7b084199be | ||
|
|
e0b6b0df2a | ||
|
|
6dcf638322 | ||
|
|
b8c2acf0f2 | ||
|
|
eedcc585af | ||
|
|
7528bf8f32 | ||
|
|
0d31ea7cf2 | ||
|
|
6a237deb21 | ||
|
|
95bc18a995 | ||
|
|
d2494822b0 | ||
|
|
61dc703a58 | ||
|
|
a87d9d3578 | ||
|
|
425e540c9a | ||
|
|
3ae96f2c6e | ||
|
|
fc770c6ea5 | ||
|
|
0c68abbe17 | ||
|
|
576581c20d | ||
|
|
1d2495d57b | ||
|
|
7d6690335f | ||
|
|
2f96a09c46 | ||
|
|
94c68d246e | ||
|
|
8dc99d42ff | ||
|
|
04fcd18c75 | ||
|
|
d9d99e5e04 | ||
|
|
5f9cedad23 | ||
|
|
afaacba41f | ||
|
|
3396a98978 | ||
|
|
7cfe435e62 | ||
|
|
9d990ae329 | ||
|
|
25ff5959fb | ||
|
|
d7bac3cea6 | ||
|
|
79748803a9 | ||
|
|
6f4edf6df5 | ||
|
|
1af4b263b2 | ||
|
|
2d25e25ec3 | ||
|
|
c4028ef116 | ||
|
|
393d728769 | ||
|
|
5fec8c8bfd | ||
|
|
f90b693ed5 | ||
|
|
515c1ea123 | ||
|
|
b82db3a254 | ||
|
|
34cb742db1 | ||
|
|
59aaf4ce1b | ||
|
|
d14744d02f | ||
|
|
e96abf1429 | ||
|
|
2758234e03 | ||
|
|
00188511cb | ||
|
|
4456e81163 | ||
|
|
6ecf870c66 | ||
|
|
95cb9ceac9 | ||
|
|
fcf13b44fb | ||
|
|
070c4bc503 | ||
|
|
e15f27106d | ||
|
|
15595a67fa | ||
|
|
bf50a8ad8e | ||
|
|
188b775fa6 | ||
|
|
ec76146a23 | ||
|
|
f9fb3f78b2 | ||
|
|
96c5bb8c39 | ||
|
|
560d8a8004 | ||
|
|
251e06c50f | ||
|
|
6fb5901d69 | ||
|
|
d3cddfdced | ||
|
|
386de03f46 | ||
|
|
4aaf3df8c7 | ||
|
|
d7cea646fc | ||
|
|
e82320cde8 | ||
|
|
669406d5af | ||
|
|
b479c8c8ba | ||
|
|
3d467a9491 | ||
|
|
8fb8fff61b | ||
|
|
d67fad8dca | ||
|
|
431ac1267a | ||
|
|
47a8e4222a | ||
|
|
4508d94a3e | ||
|
|
8411d886ac | ||
|
|
17ed80f74d | ||
|
|
63e1c839fe | ||
|
|
b6525e9164 | ||
|
|
c0ee8dc007 | ||
|
|
fe7a39ba5c | ||
|
|
51fa06cc8d | ||
|
|
771215d254 | ||
|
|
9f81699e01 | ||
|
|
95e08edbb8 | ||
|
|
baf6097b49 | ||
|
|
4cb306fbf3 | ||
|
|
2e84fc6737 | ||
|
|
c43956d70a | ||
|
|
40163da679 | ||
|
|
7763acbdd5 | ||
|
|
55cc142319 | ||
|
|
edf4c3ec00 | ||
|
|
b7e115a6a1 | ||
|
|
7fb5fe036a | ||
|
|
8b86781ad1 | ||
|
|
3f4be5521c | ||
|
|
aa86806408 | ||
|
|
5bc074005c | ||
|
|
fa31c9659b | ||
|
|
5ef342f8c4 | ||
|
|
5b811e4304 | ||
|
|
183ca5da6f | ||
|
|
8f8843711f | ||
|
|
383c21046f | ||
|
|
78e3370c1e | ||
|
|
84eebbe24a | ||
|
|
087760dba0 | ||
|
|
d9fb8c90d8 | ||
|
|
836b536a90 | ||
|
|
2bd947d4d0 | ||
|
|
4a61b1011e | ||
|
|
84847ff181 | ||
|
|
b5d941b10c | ||
|
|
0bbc02e10d | ||
|
|
fceba6814f | ||
|
|
0ed811b81b | ||
|
|
ce2112df43 | ||
|
|
678b013da6 | ||
|
|
ebee2168fc | ||
|
|
41240351d3 | ||
|
|
debedaf004 | ||
|
|
57930cb88a | ||
|
|
de917c4678 | ||
|
|
456dde200c | ||
|
|
218ba81013 | ||
|
|
499e95d16a | ||
|
|
6f7547d28f | ||
|
|
c354b9b959 | ||
|
|
841ba405f0 | ||
|
|
6f6d72890a | ||
|
|
f3d83631ef | ||
|
|
e6487de069 | ||
|
|
a5c2f22bf7 | ||
|
|
7080dc9c23 | ||
|
|
06813be5c8 | ||
|
|
8f4b3c3493 | ||
|
|
4477f95ee6 | ||
|
|
9427bb7553 | ||
|
|
1e45198b9f | ||
|
|
ad323d6e3b | ||
|
|
da6106db8e | ||
|
|
bec6b41448 | ||
|
|
6426037653 | ||
|
|
01176e04b7 | ||
|
|
c237075102 | ||
|
|
0f1d71c38f | ||
|
|
56b4162023 | ||
|
|
fd42811ef1 | ||
|
|
34926abe83 | ||
|
|
1aa554f4c9 | ||
|
|
52dbf2f9b8 | ||
|
|
5769cdc354 | ||
|
|
7f84abaf13 | ||
|
|
512f817e2f | ||
|
|
8c24c858c9 | ||
|
|
a1299d9b68 | ||
|
|
af0974264c | ||
|
|
c95646a298 | ||
|
|
42b7820dbb | ||
|
|
ce7f6dd082 | ||
|
|
6540936970 | ||
|
|
1c5d15b85e | ||
|
|
964a5d2db7 | ||
|
|
bce25918a0 | ||
|
|
074b8f18d1 | ||
|
|
be8990ea78 | ||
|
|
761ae3ae6f | ||
|
|
25bba396ef | ||
|
|
3c62de34f7 | ||
|
|
b35e8f0164 | ||
|
|
a6cccf82f7 | ||
|
|
fcf11b1181 | ||
|
|
e865b85d9c | ||
|
|
9fe6a5e83e | ||
|
|
b395fbb3f2 | ||
|
|
8a2430090b | ||
|
|
113d3b88d0 | ||
|
|
f7714a25d1 | ||
|
|
71b2126eca | ||
|
|
d5fd531743 | ||
|
|
bf3b3da6ed | ||
|
|
7e5d49487b | ||
|
|
759b7f1e07 | ||
|
|
d2b18790a0 | ||
|
|
4251e0f5f1 | ||
|
|
c8e63d76a4 | ||
|
|
6ac9308a03 | ||
|
|
0d1b2a7e46 | ||
|
|
bb8798a844 | ||
|
|
8d2de1074b | ||
|
|
632f47930f | ||
|
|
a679557e40 | ||
|
|
b18dd8fcff | ||
|
|
8edee9b2a8 | ||
|
|
6633c0b328 | ||
|
|
6825b6077a | ||
|
|
9c82954877 | ||
|
|
c4da8c46f7 | ||
|
|
b9d84df127 | ||
|
|
446bf88655 | ||
|
|
03b6f3e0bf | ||
|
|
e72e132ce2 | ||
|
|
c1249a3d84 | ||
|
|
96917a8007 | ||
|
|
2f7283fd13 | ||
|
|
e0ea932fa7 | ||
|
|
4b2040a7ca | ||
|
|
a2e8fc79d9 | ||
|
|
61ff24edc8 | ||
|
|
a86e93d46f | ||
|
|
883d5b7a08 | ||
|
|
5157c71fa9 | ||
|
|
fdda2abb78 | ||
|
|
641daf0a6e | ||
|
|
55ca02351c | ||
|
|
6fa2e62fa4 | ||
|
|
2a14af4cde | ||
|
|
1898e813f5 | ||
|
|
e0db62173a | ||
|
|
1158911560 | ||
|
|
634f9de7e6 | ||
|
|
f8da5ab2e7 | ||
|
|
fbe5f9225c | ||
|
|
4f44375abd | ||
|
|
773423fcf4 | ||
|
|
a62e2a38d7 | ||
|
|
48dcc465f2 | ||
|
|
d0c50b4fbf | ||
|
|
2da32af340 | ||
|
|
2b0794f5ae | ||
|
|
67e188a015 | ||
|
|
a2e57e8d71 | ||
|
|
21fb2b9bf1 | ||
|
|
e4f5e85c3c | ||
|
|
a48995c782 | ||
|
|
04d194924e | ||
|
|
46b61feb9a | ||
|
|
aa3cb8e35e | ||
|
|
8ff4f044b7 | ||
|
|
ab3a6f775e | ||
|
|
815cf44647 | ||
|
|
f5b2d56efd | ||
|
|
1d1bd3975a | ||
|
|
4b73239972 | ||
|
|
0a29e13d4a | ||
|
|
0db6eb2fb8 | ||
|
|
782309f369 | ||
|
|
5a3a85b2c8 | ||
|
|
c8a48e8990 | ||
|
|
80ab144bf3 | ||
|
|
6aa0f0b200 | ||
|
|
f0c45cbceb | ||
|
|
e55e7e4844 | ||
|
|
573086eed2 | ||
|
|
df285def59 | ||
|
|
bb9ce86a29 | ||
|
|
f4697ff4d1 | ||
|
|
55b095cbd3 | ||
|
|
4a9bf8f4fe | ||
|
|
ebb5ffcedc | ||
|
|
0b1e372d11 | ||
|
|
8fec7da799 | ||
|
|
46019f8537 | ||
|
|
0674ca14d9 | ||
|
|
d0b35b5e19 | ||
|
|
01570504ad | ||
|
|
506c28d2b6 | ||
|
|
53f58f72f2 | ||
|
|
c9786fe464 | ||
|
|
c2ffc7086c | ||
|
|
96f9ee784d | ||
|
|
962f087ac2 | ||
|
|
ebe8c952e4 | ||
|
|
eabd687cbc | ||
|
|
593c7a8cd1 | ||
|
|
79b9420017 | ||
|
|
db5c83eb36 | ||
|
|
56f9543a95 |
@@ -1,3 +1,11 @@
|
||||
/target
|
||||
/manifest.yml
|
||||
/migrate.yml
|
||||
**/target
|
||||
zed.xcworkspace
|
||||
.DS_Store
|
||||
plugins/bin
|
||||
script/node_modules
|
||||
styles/node_modules
|
||||
crates/collab/static/styles.css
|
||||
vendor/bin
|
||||
assets/themes/*.json
|
||||
assets/themes/internal/*.json
|
||||
assets/themes/experiments/*.json
|
||||
|
||||
38
.github/workflows/ci.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "v*"
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request:
|
||||
@@ -39,6 +40,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --no-fail-fast
|
||||
@@ -51,12 +53,14 @@ jobs:
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- bundle
|
||||
if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
|
||||
needs: tests
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
ZED_AMPLITUDE_API_KEY: ${{ secrets.ZED_AMPLITUDE_API_KEY }}
|
||||
ZED_MIXPANEL_TOKEN: ${{ secrets.ZED_MIXPANEL_TOKEN }}
|
||||
steps:
|
||||
- name: Install Rust
|
||||
run: |
|
||||
@@ -75,10 +79,32 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Validate version
|
||||
- name: Determine version and release channel
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: script/validate-version
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
version=$(script/get-crate-version zed)
|
||||
channel=$(cat crates/zed/RELEASE_CHANNEL)
|
||||
echo "Publishing version: ${version} on release channel ${channel}"
|
||||
echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
|
||||
|
||||
expected_tag_name=""
|
||||
case ${channel} in
|
||||
stable)
|
||||
expected_tag_name="v${version}";;
|
||||
preview)
|
||||
expected_tag_name="v${version}-pre";;
|
||||
*)
|
||||
echo "can't publish a release on channel ${channel}"
|
||||
exit 1;;
|
||||
esac
|
||||
if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
|
||||
echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create app bundle
|
||||
run: script/bundle
|
||||
@@ -91,12 +117,12 @@ jobs:
|
||||
path: target/release/Zed.dmg
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
name: Upload app bundle to release if release tag
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
name: Upload app bundle to release
|
||||
if: ${{ env.RELEASE_CHANNEL }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
files: target/release/Zed.dmg
|
||||
overwrite: true
|
||||
body: ""
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
46
.github/workflows/publish_collab_image.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Publish Collab Server Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- collab-v*
|
||||
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
DIGITALOCEAN_ACCESS_TOKEN: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish collab server image
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- deploy
|
||||
steps:
|
||||
- name: Add Rust to the PATH
|
||||
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Sign into DigitalOcean docker registry
|
||||
run: doctl registry login
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
clean: false
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Determine version
|
||||
run: |
|
||||
set -eu
|
||||
version=$(script/get-crate-version collab)
|
||||
if [[ $GITHUB_REF_NAME != "collab-v${version}" ]]; then
|
||||
echo "release tag ${GITHUB_REF_NAME} does not match version ${version}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Publishing collab version: ${version}"
|
||||
echo "COLLAB_VERSION=${version}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build docker image
|
||||
run: docker build . --tag registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
|
||||
- name: Publish docker image
|
||||
run: docker push registry.digitalocean.com/zed/collab:v${COLLAB_VERSION}
|
||||
39
.github/workflows/release_actions.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
discord_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@v5.3.0
|
||||
if: ${{ ! github.event.release.prerelease }}
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
content: |
|
||||
📣 Zed ${{ github.event.release.tag_name }} was just released!
|
||||
|
||||
Restart your Zed or head to https://zed.dev/releases to grab it.
|
||||
|
||||
```md
|
||||
### Changelog
|
||||
|
||||
${{ github.event.release.body }}
|
||||
```
|
||||
mixpanel_release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10.5"
|
||||
architecture: "x64"
|
||||
cache: "pip"
|
||||
- run: pip install -r script/mixpanel_release/requirements.txt
|
||||
- run: >
|
||||
python script/mixpanel_release/main.py
|
||||
${{ github.event.release.tag_name }}
|
||||
${{ secrets.MIXPANEL_PROJECT_ID }}
|
||||
${{ secrets.MIXPANEL_SERVICE_ACCOUNT_USERNAME }}
|
||||
${{ secrets.MIXPANEL_SERVICE_ACCOUNT_SECRET }}
|
||||
13
.gitignore
vendored
@@ -7,5 +7,14 @@
|
||||
/crates/collab/static/styles.css
|
||||
/vendor/bin
|
||||
/assets/themes/*.json
|
||||
/assets/themes/internal/*.json
|
||||
/assets/themes/experiments/*.json
|
||||
/assets/themes/Internal/*.json
|
||||
/assets/themes/Experiments/*.json
|
||||
**/venv
|
||||
.build
|
||||
Packages
|
||||
*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "crates/live_kit_server/protocol"]
|
||||
path = crates/live_kit_server/protocol
|
||||
url = https://github.com/livekit/protocol
|
||||
1726
Cargo.lock
generated
66
Cargo.toml
@@ -1,8 +1,69 @@
|
||||
[workspace]
|
||||
members = ["crates/*"]
|
||||
members = [
|
||||
"crates/activity_indicator",
|
||||
"crates/assets",
|
||||
"crates/auto_update",
|
||||
"crates/breadcrumbs",
|
||||
"crates/call",
|
||||
"crates/cli",
|
||||
"crates/client",
|
||||
"crates/clock",
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/command_palette",
|
||||
"crates/context_menu",
|
||||
"crates/db",
|
||||
"crates/diagnostics",
|
||||
"crates/drag_and_drop",
|
||||
"crates/editor",
|
||||
"crates/file_finder",
|
||||
"crates/fs",
|
||||
"crates/fsevent",
|
||||
"crates/fuzzy",
|
||||
"crates/git",
|
||||
"crates/go_to_line",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/journal",
|
||||
"crates/language",
|
||||
"crates/live_kit_client",
|
||||
"crates/live_kit_server",
|
||||
"crates/lsp",
|
||||
"crates/media",
|
||||
"crates/menu",
|
||||
"crates/outline",
|
||||
"crates/picker",
|
||||
"crates/plugin",
|
||||
"crates/plugin_macros",
|
||||
"crates/plugin_runtime",
|
||||
"crates/project",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/search",
|
||||
"crates/settings",
|
||||
"crates/snippet",
|
||||
"crates/sum_tree",
|
||||
"crates/terminal",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_selector",
|
||||
"crates/theme_testbench",
|
||||
"crates/util",
|
||||
"crates/vim",
|
||||
"crates/workspace",
|
||||
"crates/zed",
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||
rand = { version = "0.8" }
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "366210ae925d7ea0891bc7a0c738f60c77c04d7b" }
|
||||
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
||||
@@ -13,11 +74,10 @@ cocoa-foundation = { git = "https://github.com/servo/core-foundation-rs", rev =
|
||||
core-foundation = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" }
|
||||
core-foundation-sys = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" }
|
||||
core-graphics = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" }
|
||||
# TODO - Remove when a new version of RustRocksDB is released
|
||||
rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "39dc822dde743b2a26eb160b660e8fbdab079d49" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.62-bullseye as builder
|
||||
FROM rust:1.64-bullseye as builder
|
||||
WORKDIR app
|
||||
COPY . .
|
||||
|
||||
@@ -19,5 +19,7 @@ FROM debian:bullseye-slim as runtime
|
||||
RUN apt-get update; \
|
||||
apt-get install -y --no-install-recommends libcurl4-openssl-dev ca-certificates
|
||||
WORKDIR app
|
||||
COPY --from=builder /app/collab /app
|
||||
COPY --from=builder /app/collab /app/collab
|
||||
COPY --from=builder /app/crates/collab/migrations /app/migrations
|
||||
ENV MIGRATIONS_PATH=/app/migrations
|
||||
ENTRYPOINT ["/app/collab"]
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# syntax = docker/dockerfile:1.2
|
||||
|
||||
FROM rust:1.62-bullseye as builder
|
||||
WORKDIR app
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||
--mount=type=cache,target=./target \
|
||||
cargo install sqlx-cli --root=/app --target-dir=/app/target --version 0.5.7
|
||||
|
||||
FROM debian:bullseye-slim as runtime
|
||||
RUN apt-get update; \
|
||||
apt-get install -y --no-install-recommends libssl1.1
|
||||
WORKDIR app
|
||||
COPY --from=builder /app/bin/sqlx /app
|
||||
COPY ./crates/collab/migrations /app/migrations
|
||||
ENTRYPOINT ["/app/sqlx", "migrate", "run"]
|
||||
5
Procfile
@@ -1,2 +1,3 @@
|
||||
web: cd ../zed.dev && PORT=3000 npx next dev
|
||||
collab: cd crates/collab && cargo run
|
||||
web: cd ../zed.dev && PORT=3000 npx vercel dev
|
||||
collab: cd crates/collab && cargo run serve
|
||||
livekit: livekit-server --dev
|
||||
45
README.md
@@ -6,36 +6,43 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
|
||||
|
||||
## Development tips
|
||||
|
||||
### Dependencies
|
||||
|
||||
* Install [Postgres.app](https://postgresapp.com) and start it.
|
||||
* Install the `LiveKit` server and the `foreman` process supervisor:
|
||||
|
||||
```
|
||||
brew install livekit
|
||||
brew install foreman
|
||||
```
|
||||
|
||||
* Ensure the Zed.dev website is checked out in a sibling directory:
|
||||
|
||||
```
|
||||
cd ..
|
||||
git clone https://github.com/zed-industries/zed.dev
|
||||
```
|
||||
|
||||
* Set up a local `zed` database and seed it with some initial users:
|
||||
|
||||
```
|
||||
script/bootstrap
|
||||
```
|
||||
|
||||
### Testing against locally-running servers
|
||||
|
||||
Make sure you have `zed.dev` cloned as a sibling to this repo.
|
||||
Start the web and collab servers:
|
||||
|
||||
```
|
||||
cd ..
|
||||
git clone https://github.com/zed-industries/zed.dev
|
||||
```
|
||||
|
||||
Make sure your local database is created, migrated, and seeded with initial data. Install [Postgres](https://postgresapp.com), then from the `zed` repository root, run:
|
||||
|
||||
```
|
||||
script/sqlx database create
|
||||
script/sqlx migrate run
|
||||
script/seed-db
|
||||
```
|
||||
|
||||
Run the web frontend and the collaboration server.
|
||||
|
||||
```
|
||||
brew install foreman
|
||||
foreman start
|
||||
```
|
||||
|
||||
If you want to run Zed pointed at the local servers, you can run:
|
||||
|
||||
```
|
||||
script/zed_with_local_servers
|
||||
script/zed-with-local-servers
|
||||
# or...
|
||||
script/zed_with_local_servers --release
|
||||
script/zed-with-local-servers --release
|
||||
```
|
||||
|
||||
### Dump element JSON
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1271)">
|
||||
<path d="M10.9007 7.45347L6.60649 11.7477C6.44009 11.9168 6.22001 12 5.99993 12C5.77985 12 5.56031 11.9161 5.39283 11.7484L1.09859 7.45414C0.763098 7.11865 0.763098 6.57516 1.09859 6.23967C1.43407 5.90419 1.97756 5.90419 2.31305 6.23967L5.14108 9.06904V0.834694C5.14108 0.359912 5.52568 0 5.97577 0C6.42587 0 6.85878 0.359912 6.85878 0.834694V9.06891L9.68761 6.24008C10.0231 5.90459 10.5666 5.90459 10.9021 6.24008C11.2376 6.57556 11.2362 7.11771 10.9007 7.4532V7.45347Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1271">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 590 B After Width: | Height: | Size: 734 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.0538 9.45347L8.75952 13.7477C8.59312 13.9168 8.37304 14 8.15296 14C7.93288 14 7.71334 13.9161 7.54586 13.7484L3.25162 9.45414C2.91613 9.11865 2.91613 8.57516 3.25162 8.23967C3.5871 7.90419 4.13059 7.90419 4.46608 8.23967L7.29411 11.069V2.83469C7.29411 2.35991 7.67871 2 8.1288 2C8.5789 2 9.01181 2.35991 9.01181 2.83469V11.0689L11.8406 8.24008C12.1761 7.90459 12.7196 7.90459 13.0551 8.24008C13.3906 8.57556 13.3893 9.11771 13.0538 9.4532V9.45347Z" fill="white"/>
|
||||
<path d="M13.0538 9.45347L8.75952 13.7477C8.59312 13.9168 8.37304 14 8.15296 14C7.93288 14 7.71334 13.9161 7.54586 13.7484L3.25162 9.45414C2.91613 9.11865 2.91613 8.57516 3.25162 8.23967C3.5871 7.90418 4.13059 7.90418 4.46608 8.23967L7.29411 11.069V2.83469C7.29411 2.35991 7.67871 2 8.12881 2C8.5789 2 9.01181 2.35991 9.01181 2.83469V11.0689L11.8406 8.24008C12.1761 7.90459 12.7196 7.90459 13.0551 8.24008C13.3906 8.57556 13.3893 9.11771 13.0538 9.4532V9.45347Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 581 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1354)">
|
||||
<path d="M7.26716 4.96898L4.40433 7.83181C4.2934 7.94453 4.14668 8 3.99996 8C3.85324 8 3.70688 7.94409 3.59523 7.83226L0.732395 4.96943C0.508737 4.74577 0.508737 4.38344 0.732395 4.15978C0.956054 3.93612 1.31838 3.93612 1.54204 4.15978L3.42739 6.04603V0.556463C3.42739 0.239941 3.68379 0 3.98385 0C4.28392 0 4.57252 0.239941 4.57252 0.556463V6.04594L6.45841 4.16005C6.68207 3.93639 7.0444 3.93639 7.26806 4.16005C7.49172 4.38371 7.49082 4.74514 7.26716 4.9688V4.96898Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1354">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 726 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1272)">
|
||||
<path d="M12 6.00001C12 6.47507 11.6403 6.85889 11.1653 6.85889H2.93347L5.7624 9.68781C6.0979 10.0233 6.0979 10.5668 5.7624 10.9023C5.59331 11.0701 5.37322 11.1533 5.15313 11.1533C4.93305 11.1533 4.71349 11.0694 4.54601 10.9017L0.251624 6.60726C-0.0838748 6.27176 -0.0838748 5.72825 0.251624 5.39275L4.54601 1.09837C4.88151 0.762866 5.42502 0.762866 5.76052 1.09837C6.09602 1.43386 6.09602 1.97737 5.76052 2.31287L2.93347 5.14113H11.1653C11.6403 5.14113 12 5.52494 12 6.00001Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1272">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 740 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 8.15327C14 8.62833 13.6403 9.01214 13.1653 9.01214H4.93347L7.7624 11.8411C8.0979 12.1766 8.0979 12.7201 7.7624 13.0556C7.59331 13.2233 7.37322 13.3065 7.15313 13.3065C6.93305 13.3065 6.71349 13.2227 6.54601 13.0549L2.25162 8.76052C1.91613 8.42502 1.91613 7.88151 2.25162 7.54601L6.54601 3.25162C6.88151 2.91613 7.42502 2.91613 7.76052 3.25162C8.09602 3.58712 8.09602 4.13063 7.76052 4.46613L4.93347 7.29439H13.1653C13.6403 7.29439 14 7.6782 14 8.15327Z" fill="white"/>
|
||||
<path d="M14 8.15327C14 8.62833 13.6403 9.01215 13.1653 9.01215H4.93347L7.7624 11.8411C8.0979 12.1766 8.0979 12.7201 7.7624 13.0556C7.59331 13.2233 7.37322 13.3065 7.15313 13.3065C6.93305 13.3065 6.71349 13.2227 6.54601 13.0549L2.25162 8.76052C1.91613 8.42502 1.91613 7.88151 2.25162 7.54601L6.54601 3.25162C6.88151 2.91613 7.42502 2.91613 7.76052 3.25162C8.09602 3.58712 8.09602 4.13063 7.76052 4.46613L4.93347 7.29439H13.1653C13.6403 7.29439 14 7.6782 14 8.15327Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 585 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1355)">
|
||||
<path d="M8 4.00003C8 4.31674 7.76023 4.57261 7.44352 4.57261H1.95565L3.8416 6.45856C4.06527 6.68223 4.06527 7.04457 3.8416 7.26823C3.72887 7.38007 3.58215 7.43554 3.43542 7.43554C3.2887 7.43554 3.14233 7.37962 3.03068 7.26779L0.16775 4.40486C-0.0559165 4.1812 -0.0559165 3.81886 0.16775 3.59519L3.03068 0.732264C3.25434 0.508598 3.61668 0.508598 3.84035 0.732264C4.06401 0.95593 4.06401 1.31827 3.84035 1.54194L1.95565 3.42744H7.44352C7.76023 3.42744 8 3.68331 8 4.00003Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1355">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 730 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1273)">
|
||||
<path d="M11.749 6.60576L7.46298 10.8917C7.2969 11.0605 7.07724 11.1436 6.85758 11.1436C6.63793 11.1436 6.41881 11.0598 6.25165 10.8924C5.91681 10.5576 5.91681 10.0151 6.25165 9.68029L9.07558 6.85756H0.857198C0.383864 6.85756 0 6.4745 0 6.00036C0 5.52623 0.383596 5.14317 0.85693 5.14317H9.07531L6.25192 2.31977C5.91708 1.98493 5.91708 1.44248 6.25192 1.10764C6.58676 0.772796 7.12921 0.772796 7.46405 1.10764L11.75 5.39363C12.0835 5.72981 12.0835 6.27092 11.7487 6.60576H11.749Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1273">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 743 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1356)">
|
||||
<path d="M7.83265 4.40382L4.97532 7.26115C4.8646 7.37365 4.71816 7.42901 4.57172 7.42901C4.42528 7.42901 4.2792 7.37321 4.16777 7.26159C3.94454 7.03836 3.94454 6.67673 4.16777 6.4535L6.05039 4.57169H0.571465C0.255909 4.57169 0 4.31631 0 4.00022C0 3.68413 0.255731 3.42876 0.571287 3.42876H6.05021L4.16795 1.54649C3.94472 1.32326 3.94472 0.961634 4.16795 0.738405C4.39117 0.515177 4.75281 0.515177 4.97603 0.738405L7.83336 3.59573C8.0557 3.81985 8.0557 4.18059 7.83247 4.40382H7.83265Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1356">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 742 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1274)">
|
||||
<path d="M10.9009 5.7604C10.7345 5.92948 10.5144 6.01268 10.2943 6.01268C10.0742 6.01268 9.85471 5.92881 9.68724 5.76107L6.85903 2.93408V11.1653C6.85903 11.6401 6.47444 12 6.02437 12C5.57429 12 5.14139 11.6404 5.14139 11.1653V2.93408L2.31346 5.76013C1.97798 6.09561 1.43451 6.09561 1.09903 5.76013C0.763558 5.42466 0.763558 4.88119 1.09903 4.54571L5.39314 0.251607C5.72861 -0.0838692 6.27209 -0.0838692 6.60756 0.251607L10.9017 4.54571C11.2363 4.88253 11.2363 5.42466 10.9009 5.76013V5.7604Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1274">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 755 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1357)">
|
||||
<path d="M7.26724 3.84027C7.15631 3.95299 7.0096 4.00845 6.86288 4.00845C6.71617 4.00845 6.56981 3.95254 6.45816 3.84072L4.57269 1.95605V7.44356C4.57269 7.76007 4.3163 8 4.01625 8C3.7162 8 3.4276 7.76025 3.4276 7.44356V1.95605L1.54231 3.84009C1.31866 4.06374 0.956346 4.06374 0.732695 3.84009C0.509044 3.61644 0.509044 3.25412 0.732695 3.03047L3.59543 0.167738C3.81908 -0.0559128 4.1814 -0.0559128 4.40505 0.167738L7.26778 3.03047C7.49089 3.25502 7.49089 3.61644 7.26724 3.84009V3.84027Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1357">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 603 B After Width: | Height: | Size: 745 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.17314 8L5.89399 3.29329L6.20495 7.94346L8 6.14841L7.67491 0.339222L1.85159 0L0.0565371 1.79505L4.70671 2.12014L0 6.82685L1.17314 8Z" fill="white"/>
|
||||
<path d="M6 2H6.5C6.5 1.86739 6.44732 1.74021 6.35355 1.64645C6.25979 1.55268 6.13261 1.5 6 1.5V2ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2C1.5 2.27614 1.72386 2.5 2 2.5L2 1.5ZM5.5 6C5.5 6.27614 5.72386 6.5 6 6.5C6.27614 6.5 6.5 6.27614 6.5 6H5.5ZM1.64645 5.64645C1.45118 5.84171 1.45118 6.15829 1.64645 6.35355C1.84171 6.54882 2.15829 6.54882 2.35355 6.35355L1.64645 5.64645ZM6 1.5H2L2 2.5H6V1.5ZM5.5 2V6H6.5V2H5.5ZM5.64645 1.64645L1.64645 5.64645L2.35355 6.35355L6.35355 2.35355L5.64645 1.64645Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 608 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.14029 5.25005H9.75348C10.0652 5.25005 10.3464 5.44457 10.4355 5.73518C10.5644 6.02814 10.4824 6.3586 10.248 6.56484L4.24822 11.8146C3.98338 12.0443 3.59598 12.0631 3.31286 11.8568C3.02951 11.6506 2.92639 11.2756 3.06443 10.9545L4.86694 6.74999H2.23267C1.94135 6.74999 1.66152 6.55546 1.5516 6.26485C1.44171 5.97189 1.52465 5.64144 1.75986 5.43519L7.75893 0.185629C8.02376 -0.0449871 8.41046 -0.0625645 8.69405 0.143209C8.97763 0.349123 9.08075 0.723265 8.94248 1.04542L7.1402 5.24972L7.14029 5.25005Z" fill="white"/>
|
||||
<path d="M7.01442 5.33285H9.33914C9.61643 5.33285 9.86663 5.5059 9.94586 5.76443C10.0605 6.02505 9.98756 6.31903 9.77906 6.50251L4.4416 11.1728C4.206 11.3771 3.86136 11.3938 3.6095 11.2103C3.35743 11.0268 3.26569 10.6932 3.38849 10.4076L4.99202 6.66722H2.64855C2.38939 6.66722 2.14044 6.49417 2.04266 6.23563C1.9449 5.97501 2.01868 5.68104 2.22793 5.49756L7.56476 0.827491C7.80036 0.622333 8.14438 0.606695 8.39666 0.789754C8.64894 0.972937 8.74067 1.30578 8.61766 1.59237L7.01434 5.33256L7.01442 5.33285Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 625 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96164 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
|
||||
<path d="M4.76019 3.50003H6.50231C6.71012 3.50003 6.89761 3.62971 6.95698 3.82346C7.04292 4.01876 6.98823 4.23906 6.83199 4.37656L2.83214 7.87643C2.65558 8.02954 2.39731 8.04204 2.20857 7.90455C2.01967 7.76705 1.95092 7.51706 2.04295 7.30301L3.24462 4.49999H1.48844C1.29423 4.49999 1.10767 4.37031 1.0344 4.17657C0.961132 3.98126 1.01643 3.76096 1.17323 3.62346L5.17261 0.123753C5.34917 -0.0299914 5.60697 -0.0417097 5.79603 0.0954726C5.98508 0.232749 6.05383 0.482177 5.96165 0.69695L4.76013 3.49981L4.76019 3.50003Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 633 B After Width: | Height: | Size: 633 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.00788 3.83701L7.62903 0.754503C7.87479 0.546451 8.23364 0.530593 8.4968 0.716233C8.75996 0.901999 8.85565 1.23953 8.72734 1.53017L7.05487 5.3231H9.47984C9.7691 5.3231 10.0301 5.49859 10.1127 5.76077C10.2323 6.02506 10.1562 6.32318 9.93874 6.50925L8.79258 7.48396L11.7998 9.86631C12.026 10.0397 12.0673 10.3589 11.889 10.5788C11.7106 10.7987 11.3822 10.8389 11.156 10.6655L0.200158 2.51988C-0.0268974 2.34693 -0.0666975 2.02808 0.111206 1.80735C0.28911 1.58661 0.616862 1.54792 0.843918 1.72108L4.00788 3.83701ZM2.06138 5.49043L2.40718 5.17751L4.34064 6.67658H2.52027C2.23102 6.67658 1.97003 6.50109 1.86782 6.23891C1.76777 5.97461 1.84389 5.67649 2.06138 5.49043ZM3.27278 10.4697L4.79301 7.02333L7.18318 8.83533L4.37108 11.2457C4.12532 11.4529 3.76647 11.4698 3.50331 11.2837C3.24015 11.0977 3.14446 10.7594 3.27278 10.4697Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1336)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.01443 5.33285H9.33914C9.61644 5.33285 9.86663 5.5059 9.94586 5.76443C10.0605 6.02505 9.98756 6.31903 9.77906 6.50251L9.11489 7.08366L11.4605 8.90799C11.7874 9.16229 11.8463 9.6335 11.592 9.96045C11.3377 10.2874 10.8665 10.3463 10.5395 10.092L1.53955 3.09201C1.21259 2.83771 1.15369 2.3665 1.40799 2.03954C1.66229 1.71258 2.1335 1.65368 2.46046 1.90799L4.50911 3.50138L7.56477 0.827491C7.80037 0.622333 8.14438 0.606695 8.39666 0.789754C8.64894 0.972937 8.74068 1.30578 8.61767 1.59237L7.01434 5.33256L7.01443 5.33285ZM2.88883 4.91923L7.49455 8.50146L4.4416 11.1728C4.206 11.3771 3.86136 11.3938 3.6095 11.2103C3.35743 11.0268 3.26569 10.6932 3.3885 10.4076L4.99203 6.66722H2.64855C2.38939 6.66722 2.14045 6.49416 2.04266 6.23563C1.9449 5.97501 2.01869 5.68104 2.22793 5.49756L2.88883 4.91923Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1336">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 956 B After Width: | Height: | Size: 1.1 KiB |
@@ -1,9 +1,9 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1350)">
|
||||
<path d="M2.8102 2.0687L5.08603 0.503002C5.24987 0.364301 5.4891 0.353729 5.66454 0.477489C5.83998 0.601333 5.90377 0.826356 5.81823 1.02011L4.70325 3.54873H6.3199C6.51274 3.54873 6.68673 3.66573 6.74182 3.84051C6.82157 4.01671 6.77082 4.21546 6.62583 4.3395L6.22432 4.68222L8.0048 6.08823C8.15559 6.20381 8.18314 6.41666 8.06425 6.56325C7.94536 6.70985 7.72642 6.73663 7.57563 6.62104L0.271717 1.19061C0.120346 1.07531 0.093813 0.862748 0.212416 0.715589C0.331018 0.56843 0.54952 0.542635 0.70089 0.658079L2.8102 2.0687ZM1.37426 3.66029L1.60479 3.45167L2.89376 4.45105H1.68019C1.48735 4.45105 1.31336 4.33406 1.24521 4.15927C1.17852 3.98308 1.22927 3.78433 1.37426 3.66029ZM2.18186 6.97981L3.19534 4.68222L4.78879 5.89022L2.91406 7.49712C2.75022 7.63526 2.51099 7.64653 2.33555 7.52249C2.16011 7.39845 2.09631 7.17292 2.18186 6.97981Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_702_132)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.67628 3.55525H6.22609C6.41096 3.55525 6.57775 3.67062 6.63057 3.84298C6.70702 4.01672 6.65837 4.21271 6.51937 4.33502L6.07659 4.72246L7.6403 5.93868C7.85828 6.10821 7.89754 6.42235 7.72801 6.64032C7.55847 6.8583 7.24434 6.89756 7.02636 6.72803L1.02636 2.06136C0.808388 1.89183 0.769121 1.57769 0.938656 1.35972C1.10819 1.14174 1.42233 1.10248 1.6403 1.27201L3.00607 2.33428L5.04318 0.551681C5.20024 0.414909 5.42959 0.404484 5.59777 0.526523C5.76596 0.648645 5.82712 0.870539 5.74511 1.0616L4.67622 3.55506L4.67628 3.55525ZM3.25023 4.62627L4.80474 5.83533L2.96106 7.44854C2.804 7.58476 2.57424 7.59588 2.40633 7.47356C2.23828 7.35125 2.17713 7.12885 2.25899 6.93843L3.25023 4.62627ZM1.73426 3.44719L3.01695 4.44483H1.7657C1.59292 4.44483 1.42696 4.32946 1.36177 4.15711C1.2966 3.98336 1.34579 3.78738 1.48528 3.66506L1.73426 3.44719Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1350">
|
||||
<clipPath id="clip0_702_132">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.78057 2.96296L4.59642 6.16966C4.41855 6.32749 4.21312 6.40515 4.00769 6.40515C3.80226 6.40515 3.59733 6.32686 3.44075 6.17028L0.256599 2.96358C0.00482339 2.73498 -0.06382 2.38913 0.0601891 2.09088C0.184298 1.79276 0.47686 1.59485 0.800787 1.59485H7.19164C7.51582 1.59485 7.80843 1.78976 7.93269 2.08963C8.05695 2.38926 8.01085 2.73473 7.78037 2.96271L7.78057 2.96296Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1359)">
|
||||
<path d="M6.36286 3.35324L4.37276 5.35315C4.26159 5.45158 4.1332 5.50002 4.0048 5.50002C3.87641 5.50002 3.74833 5.45119 3.65047 5.35354L1.66037 3.35363C1.50301 3.21106 1.46011 2.99537 1.53762 2.80936C1.61519 2.62343 1.79804 2.5 2.00049 2.5H5.99478C6.19739 2.5 6.38027 2.62156 6.45793 2.80858C6.53559 2.99545 6.50678 3.2109 6.36273 3.35309L6.36286 3.35324Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1359">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 613 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.03717 7.78065L1.83086 4.59689C1.67355 4.41904 1.5954 4.21363 1.5954 3.98568C1.5954 3.75774 1.67368 3.57538 1.83011 3.41882L5.03641 0.235058C5.26562 0.00560697 5.61029 -0.0630281 5.91013 0.0609657C6.20997 0.18496 6.40461 0.478537 6.40461 0.801672V7.19174C6.40461 7.51588 6.20947 7.80845 5.90988 7.9327C5.61004 8.05694 5.26486 8.01085 5.03692 7.7804L5.03717 7.78065Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1360)">
|
||||
<path d="M4.64677 6.36286L2.64687 4.37276C2.54843 4.26159 2.5 4.1332 2.5 4.0048C2.5 3.87641 2.54882 3.74833 2.64648 3.65047L4.64638 1.66037C4.78895 1.50301 5.00465 1.46011 5.19065 1.53762C5.37658 1.61519 5.50002 1.79804 5.50002 2.00049L5.50002 5.99478C5.50002 6.19739 5.37846 6.38027 5.19144 6.45793C5.00457 6.53559 4.78911 6.50678 4.64693 6.36273L4.64677 6.36286Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1360">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 622 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.95931 0.236879L6.17709 3.43204C6.33546 3.61052 6.4134 3.81666 6.4134 4.00018C6.4134 4.18369 6.33484 4.41195 6.17772 4.56907L2.95993 7.76423C2.72966 7.99425 2.384 8.06338 2.08309 7.93869C1.78217 7.81401 1.58659 7.56463 1.58659 7.21771V0.804768C1.58659 0.47947 1.78217 0.185847 2.08309 0.0611582C2.38375 -0.0635309 2.73042 0.0061038 2.95918 0.236628L2.95931 0.236879Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1361)">
|
||||
<path d="M3.35323 1.63714L5.35313 3.62724C5.45157 3.73841 5.5 3.8668 5.5 3.9952C5.5 4.12359 5.45118 4.25167 5.35352 4.34953L3.35362 6.33963C3.21105 6.49699 2.99535 6.53989 2.80935 6.46238C2.62342 6.38481 2.49998 6.20196 2.49998 5.99951L2.49998 2.00522C2.49998 1.80261 2.62154 1.61973 2.80856 1.54207C2.99543 1.46441 3.21089 1.49322 3.35307 1.63727L3.35323 1.63714Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1361">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 622 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.235788 5.03793L3.42201 1.82915C3.60024 1.67122 3.80581 1.59351 4.01137 1.59351C4.21693 1.59351 4.42199 1.67185 4.57867 1.82852L7.76489 5.0373C7.99427 5.26668 8.06321 5.61163 7.93887 5.9117C7.81453 6.21177 7.54328 6.40655 7.19734 6.40655H0.802589C0.478202 6.40655 0.1854 6.21127 0.0610601 5.91145C-0.0632802 5.61137 0.00590919 5.26593 0.235538 5.03781L0.235788 5.03793Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1362)">
|
||||
<path d="M1.63714 4.64676L3.62724 2.64685C3.73841 2.54842 3.8668 2.49998 3.9952 2.49998C4.12359 2.49998 4.25167 2.54881 4.34953 2.64646L6.33963 4.64637C6.49699 4.78894 6.53989 5.00463 6.46238 5.19064C6.38481 5.37657 6.20196 5.5 5.99951 5.5L2.00522 5.5C1.80261 5.5 1.61973 5.37844 1.54207 5.19142C1.46441 5.00455 1.49322 4.7891 1.63727 4.64691L1.63714 4.64676Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1362">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 617 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.28561 8C2.13936 8 1.99311 7.9442 1.88168 7.83259C1.65846 7.60937 1.65846 7.24776 1.88168 7.02454L4.90707 3.99996L1.88168 0.975457C1.65846 0.75224 1.65846 0.390629 1.88168 0.167413C2.10489 -0.0558042 2.4665 -0.0558042 2.68972 0.167413L6.11833 3.59602C6.34155 3.81924 6.34155 4.18085 6.11833 4.40407L2.68972 7.83268C2.57847 7.94464 2.43204 8 2.28561 8Z" fill="white"/>
|
||||
<path d="M2.28561 8C2.13936 8 1.99311 7.9442 1.88168 7.83259C1.65846 7.60937 1.65846 7.24776 1.88168 7.02454L4.90707 3.99996L1.88168 0.975457C1.65846 0.752241 1.65846 0.390629 1.88168 0.167413C2.10489 -0.0558042 2.4665 -0.0558042 2.68972 0.167413L6.11833 3.59602C6.34155 3.81924 6.34155 4.18085 6.11833 4.40407L2.68972 7.83268C2.57847 7.94464 2.43204 8 2.28561 8Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 479 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1285)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 12C9.31371 12 12 9.31371 12 6C12 2.68629 9.31371 0 6 0C2.68629 0 0 2.68629 0 6C0 9.31371 2.68629 12 6 12ZM9.53033 4.28033L8.46967 3.21967L4.875 6.81434L3.53033 5.46967L2.46967 6.53033L4.875 8.93566L9.53033 4.28033Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1285">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 530 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1370)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM6.35355 2.85355L5.64645 2.14645L3.25 4.54289L2.35355 3.64645L1.64645 4.35355L3.25 5.95711L6.35355 2.85355Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1370">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 373 B After Width: | Height: | Size: 515 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1294)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 6C12 9.31371 9.31371 12 6 12C2.68629 12 0 9.31371 0 6C0 2.68629 2.68629 0 6 0C9.31371 0 12 2.68629 12 6ZM5.25 6H4.5V4.5H6.75V8.25H7.5V9.75H5.25V6ZM6.75 3.75V2.25H5.25V3.75H6.75Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1294">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 494 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1372)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 4C8 6.20914 6.20914 8 4 8C1.79086 8 0 6.20914 0 4C0 1.79086 1.79086 0 4 0C6.20914 0 8 1.79086 8 4ZM3.5 4H3V3H4.5V5.5H5V6.5H3.5V4ZM4.5 2.5V1.5H3.5V2.5H4.5Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1372">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 464 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1287)">
|
||||
<path d="M6 0C2.68594 0 0 2.68594 0 6C0 9.31406 2.68594 12 6 12C9.31406 12 12 9.31406 12 6C12 2.68594 9.31406 0 6 0ZM8.97187 5.76797C8.91328 5.90859 8.77734 6 8.625 6H7.125V8.25C7.125 8.66414 6.78914 9 6.375 9H5.625C5.21086 9 4.875 8.66414 4.875 8.25V6H3.375C3.22266 6 3.08672 5.90859 3.02812 5.76797C2.96953 5.62734 3.00234 5.46797 3.11016 5.36016L5.73516 2.73516C5.88141 2.58867 6.11906 2.58867 6.26531 2.73516L8.89031 5.36016C8.99766 5.46797 9.03047 5.62734 8.97187 5.76797Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1287">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 741 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1376)">
|
||||
<path d="M4 0C1.79063 0 0 1.79063 0 4C0 6.20937 1.79063 8 4 8C6.20937 8 8 6.20937 8 4C8 1.79063 6.20937 0 4 0ZM5.98125 3.84531C5.94219 3.93906 5.85156 4 5.75 4H4.75V5.5C4.75 5.77609 4.52609 6 4.25 6H3.75C3.47391 6 3.25 5.77609 3.25 5.5V4H2.25C2.14844 4 2.05781 3.93906 2.01875 3.84531C1.97969 3.75156 2.00156 3.64531 2.07344 3.57344L3.82344 1.82344C3.92094 1.72578 4.07938 1.72578 4.17688 1.82344L5.92688 3.57344C5.99844 3.64531 6.02031 3.75156 5.98125 3.84531Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1376">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 719 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1292)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 12C9.31371 12 12 9.31371 12 6C12 2.68629 9.31371 0 6 0C2.68629 0 0 2.68629 0 6C0 9.31371 2.68629 12 6 12ZM3.21967 4.28033L4.93934 6L3.21967 7.71967L4.28033 8.78033L6 7.06066L7.71967 8.78033L8.78033 7.71967L7.06066 6L8.78033 4.28033L7.71967 3.21967L6 4.93934L4.28033 3.21967L3.21967 4.28033Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1292">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 606 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1365)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 8C6.20914 8 8 6.20914 8 4C8 1.79086 6.20914 0 4 0C1.79086 0 0 1.79086 0 4C0 6.20914 1.79086 8 4 8ZM2.14645 2.85355L3.29289 4L2.14645 5.14645L2.85355 5.85355L4 4.70711L5.14645 5.85355L5.85355 5.14645L4.70711 4L5.85355 2.85355L5.14645 2.14645L4 3.29289L2.85355 2.14645L2.14645 2.85355Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1365">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 451 B After Width: | Height: | Size: 593 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.59892 2.76222C3.14641 2.17067 3.93013 1.80018 4.80011 1.80018C5.91195 1.80018 6.8813 2.40485 7.40066 3.30389C7.68565 3.09577 8.03064 3.00015 8.4 3.00015C9.39372 3.00015 10.1999 3.7895 10.1999 4.80009C10.1999 5.02884 10.1568 5.24633 10.08 5.44882C11.1749 5.67007 11.9999 6.63941 11.9999 7.8C11.9999 8.48623 11.7112 9.10497 11.233 9.52683L11.8274 9.99557C12.0224 10.1493 12.058 10.4324 11.9043 10.6274C11.7505 10.8224 11.4674 10.858 11.2724 10.7043L0.172556 2.00436C-0.0231882 1.85099 -0.0574997 1.56825 0.0958708 1.37251C0.249241 1.17676 0.531796 1.14245 0.727671 1.29582L2.59868 2.76184L2.59892 2.76222ZM1.82213 4.43635L9.13873 10.1999H2.70017C1.20903 10.1999 0.000248596 8.99059 0.000248596 7.50001C0.000248596 6.32255 0.753414 5.32133 1.80395 4.95196C1.80151 4.90134 1.8002 4.85072 1.8002 4.80009C1.8002 4.67635 1.8077 4.55448 1.82213 4.43635Z" fill="white"/>
|
||||
<path d="M2.59892 2.76222C3.14641 2.17067 3.93013 1.80018 4.80011 1.80018C5.91195 1.80018 6.8813 2.40485 7.40066 3.30389C7.68565 3.09577 8.03064 3.00015 8.4 3.00015C9.39372 3.00015 10.1999 3.7895 10.1999 4.80009C10.1999 5.02884 10.1568 5.24633 10.08 5.44882C11.1749 5.67007 11.9999 6.63941 11.9999 7.80001C11.9999 8.48623 11.7112 9.10497 11.233 9.52683L11.8274 9.99557C12.0224 10.1493 12.058 10.4324 11.9043 10.6274C11.7505 10.8224 11.4674 10.858 11.2724 10.7043L0.172556 2.00436C-0.0231882 1.85099 -0.0574997 1.56825 0.0958708 1.37251C0.249241 1.17676 0.531795 1.14245 0.727671 1.29582L2.59868 2.76184L2.59892 2.76222ZM1.82213 4.43635L9.13873 10.1999H2.70017C1.20903 10.1999 0.000248596 8.99059 0.000248596 7.50001C0.000248596 6.32255 0.753414 5.32133 1.80395 4.95196C1.80151 4.90134 1.8002 4.85072 1.8002 4.80009C1.8002 4.67635 1.8077 4.55448 1.82213 4.43635Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 977 B After Width: | Height: | Size: 981 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.73261 1.8415C2.0976 1.44713 2.62009 1.20014 3.20007 1.20014C3.9413 1.20014 4.58753 1.60325 4.93377 2.20261C5.12376 2.06387 5.35376 2.00012 5.6 2.00012C6.26248 2.00012 6.79996 2.52635 6.79996 3.20008C6.79996 3.35258 6.77122 3.49757 6.71997 3.63257C7.44995 3.78007 7.99993 4.4263 7.99993 5.20002C7.99993 5.65751 7.80744 6.07 7.48869 6.35124L7.88493 6.66373C8.01493 6.76623 8.03868 6.95497 7.93618 7.08497C7.83368 7.21496 7.64494 7.23871 7.51494 7.13622L0.115037 1.33626C-0.0154588 1.23402 -0.0383331 1.04552 0.0639139 0.915025C0.166161 0.784529 0.35453 0.761655 0.485114 0.863902L1.73245 1.84125L1.73261 1.8415ZM1.21475 2.95759L6.09249 6.79998H1.80011C0.806017 6.79998 0.000165731 5.99375 0.000165731 5.00003C0.000165731 4.21505 0.502276 3.54757 1.20263 3.30133C1.20101 3.26758 1.20013 3.23383 1.20013 3.20008C1.20013 3.11759 1.20513 3.03634 1.21475 2.95759Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1352)">
|
||||
<path d="M1.73261 1.8415C2.0976 1.44713 2.62009 1.20014 3.20007 1.20014C3.9413 1.20014 4.58753 1.60325 4.93377 2.20261C5.12376 2.06387 5.35376 2.00012 5.6 2.00012C6.26248 2.00012 6.79997 2.52635 6.79997 3.20008C6.79997 3.35258 6.77122 3.49757 6.71997 3.63257C7.44995 3.78007 7.99993 4.4263 7.99993 5.20002C7.99993 5.65751 7.80744 6.07 7.48869 6.35124L7.88493 6.66373C8.01493 6.76623 8.03868 6.95497 7.93618 7.08497C7.83368 7.21496 7.64494 7.23871 7.51494 7.13622L0.115037 1.33626C-0.0154588 1.23402 -0.0383331 1.04552 0.0639139 0.915025C0.166161 0.784529 0.35453 0.761655 0.485114 0.863902L1.73245 1.84125L1.73261 1.8415ZM1.21475 2.95759L6.09249 6.79998H1.80011C0.806017 6.79998 0.000165731 5.99375 0.000165731 5.00003C0.000165731 4.21505 0.502276 3.54757 1.20263 3.30133C1.20101 3.26758 1.20013 3.23383 1.20013 3.20008C1.20013 3.11759 1.20513 3.03634 1.21475 2.95759Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1352">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 1.1 KiB |
3
assets/icons/disable_screen_sharing_12.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 0.666656H1C0.447917 0.666656 0 1.11457 0 1.66666V8.33332C0 8.88541 0.447917 9.33332 1 9.33332H5L4.66667 10.3333H3.16667C2.89167 10.3333 2.66667 10.5583 2.66667 10.8333C2.66667 11.1083 2.89167 11.3333 3.16667 11.3333H8.83333C9.10938 11.3333 9.33333 11.1094 9.33333 10.8333C9.33333 10.5573 9.10938 10.3333 8.83333 10.3333H7.33333L7 9.33332H11C11.5521 9.33332 12 8.88541 12 8.33332V1.66666C12 1.11457 11.5521 0.666656 11 0.666656ZM10.6667 7.99999H1.33333V1.99999H10.6667V7.99999Z" fill="#979DB4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 611 B |
@@ -1,11 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_616_81)">
|
||||
<path opacity="0.6" d="M11 11L11 7L1 7L1 11L11 11Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1818 1C1.51237 1 1.04544 1.48778 1.04544 2V10C1.04544 10.5122 1.51237 11 2.1818 11H9.81817C10.4876 11 10.9545 10.5122 10.9545 10V2C10.9545 1.48778 10.4876 1 9.81817 1H2.1818ZM0.0454407 2C0.0454407 0.855367 1.04377 0 2.1818 0H9.81817C10.9562 0 11.9545 0.855367 11.9545 2V10C11.9545 11.1446 10.9562 12 9.81817 12H2.1818C1.04377 12 0.0454407 11.1446 0.0454407 10V2Z" fill="white"/>
|
||||
<path d="M11 8L11 7L1 7L1 8L11 8Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_702_214)">
|
||||
<rect x="0.5" y="0.5" width="11" height="11" rx="1" stroke="white" stroke-opacity="0.4"/>
|
||||
<path d="M12 7.5L12 10.5C12 11.3284 11.3284 12 10.5 12L1.5 12C0.671573 12 -1.67346e-07 11.3284 -1.31134e-07 10.5L0 7.5L12 7.5Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_616_81">
|
||||
<clipPath id="clip0_702_214">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 478 B |
@@ -1,7 +1,7 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_610_112)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H1V7H7V1ZM1 0C0.447715 0 4.37114e-08 0.447715 4.37114e-08 1V7C4.37114e-08 7.55228 0.447715 8 1 8H7C7.55228 8 8 7.55228 8 7V1C8 0.447715 7.55228 0 7 0H1Z" fill="white"/>
|
||||
<path d="M7 4C7.55228 4 8 4.44772 8 5V7C8 7.55228 7.55228 8 7 8H1C0.447715 8 4.37114e-08 7.55228 4.37114e-08 7L0 5C2.41411e-08 4.44771 0.447715 4 1 4L7 4Z" fill="white"/>
|
||||
<rect x="0.5" y="0.5" width="7" height="7" rx="0.5" stroke="white" stroke-opacity="0.4"/>
|
||||
<path d="M8 5L8 7C8 7.55228 7.55228 8 7 8L1 8C0.447715 8 -1.11564e-07 7.55228 -8.74228e-08 7L0 5L8 5Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_610_112">
|
||||
|
||||
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 447 B |
@@ -1,11 +0,0 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_616_82)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1818 1C1.51237 1 1.04544 1.48778 1.04544 2V10C1.04544 10.5122 1.51237 11 2.1818 11H9.81817C10.4876 11 10.9545 10.5122 10.9545 10V2C10.9545 1.48778 10.4876 1 9.81817 1H2.1818ZM0.0454407 2C0.0454407 0.855367 1.04377 0 2.1818 0H9.81817C10.9562 0 11.9545 0.855367 11.9545 2V10C11.9545 11.1446 10.9562 12 9.81817 12H2.1818C1.04377 12 0.0454407 11.1446 0.0454407 10V2Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 4C5.5 3.72386 5.72386 3.5 6 3.5H8C8.27614 3.5 8.5 3.72386 8.5 4V6C8.5 6.27614 8.27614 6.5 8 6.5C7.72386 6.5 7.5 6.27614 7.5 6V5.20711L5.20711 7.5H6C6.27614 7.5 6.5 7.72386 6.5 8C6.5 8.27614 6.27614 8.5 6 8.5H4C3.72386 8.5 3.5 8.27614 3.5 8V6C3.5 5.72386 3.72386 5.5 4 5.5C4.27614 5.5 4.5 5.72386 4.5 6V6.79289L6.79289 4.5H6C5.72386 4.5 5.5 4.27614 5.5 4Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_616_82">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,13 +0,0 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_610_113)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H1V7H7V1ZM1 0C0.447715 0 0 0.447715 0 1V7C0 7.55228 0.447715 8 1 8H7C7.55228 8 8 7.55228 8 7V1C8 0.447715 7.55228 0 7 0H1Z" fill="white"/>
|
||||
<path d="M6 2H3L6 5V2Z" fill="white"/>
|
||||
<path d="M2 6L5 6L2 3L2 6Z" fill="white"/>
|
||||
<path d="M5.5 2.5L2.5 5.5" stroke="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_610_113">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 557 B |
@@ -1,11 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_620_198)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1818 1C1.51237 1 1.04544 1.48778 1.04544 2V10C1.04544 10.5122 1.51237 11 2.1818 11H9.81817C10.4876 11 10.9545 10.5122 10.9545 10V2C10.9545 1.48778 10.4876 1 9.81817 1H2.1818ZM0.0454407 2C0.0454407 0.855367 1.04377 0 2.1818 0H9.81817C10.9562 0 11.9545 0.855367 11.9545 2V10C11.9545 11.1446 10.9562 12 9.81817 12H2.1818C1.04377 12 0.0454407 11.1446 0.0454407 10V2Z" fill="white"/>
|
||||
<rect opacity="0.6" x="4" y="4" width="4" height="4" fill="white"/>
|
||||
<rect x="3.5" y="3.5" width="5" height="5" rx="0.5" stroke="white"/>
|
||||
<g clip-path="url(#clip0_702_213)">
|
||||
<rect x="2" y="3" width="8" height="6" rx="1" fill="white"/>
|
||||
<rect x="0.5" y="0.5" width="11" height="11" rx="1" stroke="white" stroke-opacity="0.4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_620_198">
|
||||
<clipPath id="clip0_702_213">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 813 B After Width: | Height: | Size: 396 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_620_186)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H1V7H7V1ZM1 0C0.447715 0 0 0.447715 0 1V7C0 7.55228 0.447715 8 1 8H7C7.55228 8 8 7.55228 8 7V1C8 0.447715 7.55228 0 7 0H1Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H1V7H7V1ZM1 0C0.447715 0 0 0.447715 0 1V7C0 7.55228 0.447715 8 1 8H7C7.55228 8 8 7.55228 8 7V1C8 0.447715 7.55228 0 7 0H1Z" fill="white" fill-opacity="0.4"/>
|
||||
<rect x="2" y="2" width="4" height="4" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 504 B |
@@ -1,11 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_616_83)">
|
||||
<path opacity="0.6" d="M11 1H7V11H11V1Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1818 1C1.51237 1 1.04544 1.48778 1.04544 2V10C1.04544 10.5122 1.51237 11 2.1818 11H9.81817C10.4876 11 10.9545 10.5122 10.9545 10V2C10.9545 1.48778 10.4876 1 9.81817 1H2.1818ZM0.0454407 2C0.0454407 0.855367 1.04377 0 2.1818 0H9.81817C10.9562 0 11.9545 0.855367 11.9545 2V10C11.9545 11.1446 10.9562 12 9.81817 12H2.1818C1.04377 12 0.0454407 11.1446 0.0454407 10V2Z" fill="white"/>
|
||||
<path d="M8 1H7V11H8V1Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_702_215)">
|
||||
<rect x="0.5" y="0.5" width="11" height="11" rx="1" stroke="white" stroke-opacity="0.4"/>
|
||||
<path d="M7.5 0H10.5C11.3284 0 12 0.671573 12 1.5V10.5C12 11.3284 11.3284 12 10.5 12H7.5V0Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_616_83">
|
||||
<clipPath id="clip0_702_215">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 443 B |
@@ -1,7 +1,7 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_610_114)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 1H1V7H7V1ZM1 0C0.447715 0 0 0.447715 0 1V7C0 7.55228 0.447715 8 1 8H7C7.55228 8 8 7.55228 8 7V1C8 0.447715 7.55228 0 7 0H1Z" fill="white"/>
|
||||
<path d="M4 1C4 0.447715 4.44772 0 5 0H7C7.55228 0 8 0.447715 8 1V7C8 7.55228 7.55228 8 7 8H5C4.44772 8 4 7.55228 4 7V1Z" fill="white"/>
|
||||
<rect x="0.5" y="0.5" width="7" height="7" rx="0.5" stroke="white" stroke-opacity="0.4"/>
|
||||
<path d="M5 0H7C7.55228 0 8 0.447715 8 1V7C8 7.55228 7.55228 8 7 8H5V0Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_610_114">
|
||||
|
||||
|
Before Width: | Height: | Size: 568 B After Width: | Height: | Size: 417 B |
3
assets/icons/enable_screen_sharing_12.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.53324 9.90014H7.18324L6.88324 9.00014H7.63305L6.10211 7.80014H1.78324V4.41577L0.583236 3.47452V8.10014C0.583217 8.59702 0.986361 9.00014 1.46636 9.00014H5.04949L4.74949 9.90014H3.43324C3.1848 9.90014 2.98324 10.1017 2.98324 10.3501C2.98324 10.5986 3.1848 10.8001 3.43324 10.8001H8.51637C8.7648 10.8001 8.96637 10.5986 8.96637 10.3501C8.96637 10.1017 8.79762 9.90014 8.53324 9.90014ZM11.8276 9.99577L10.5507 8.99489C11.0234 8.96789 11.3999 8.57939 11.3999 8.09996V2.09995C11.3999 1.60308 10.9968 1.19995 10.4999 1.19995H1.5168C1.28617 1.19995 1.07786 1.28939 0.918674 1.43208L0.727799 1.29595C0.645299 1.23145 0.547423 1.19995 0.450673 1.19995C0.316986 1.19995 0.184611 1.2592 0.0961106 1.37226C-0.057452 1.56801 -0.023327 1.85095 0.172236 2.00414L11.2724 10.7041C11.4693 10.8579 11.7519 10.8226 11.9041 10.6276C12.0581 10.4321 12.0224 10.149 11.8274 9.99521L11.8276 9.99577ZM10.1832 7.80014H9.00968L2.11905 2.40014H10.1816L10.1832 7.80014Z" fill="#93A1A1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1286)">
|
||||
<path d="M11.3333 1.33335H9L8.33333 0.666687H6.66667C6.29958 0.666687 6 0.96627 6 1.33335V4.66669C6 5.03377 6.29958 5.33335 6.66667 5.33335H11.3333C11.7004 5.33335 12 5.03377 12 4.66669V2.00002C12 1.63294 11.7 1.33335 11.3333 1.33335ZM11.3333 7.33335H9L8.33333 6.66669H6.66667C6.29958 6.66669 6 6.96627 6 7.33335V10.6667C6 11.0338 6.29958 11.3334 6.66667 11.3334H11.3333C11.7004 11.3334 12 11.0338 12 10.6667V8.00002C12 7.63335 11.7 7.33335 11.3333 7.33335ZM1.33333 1.00002C1.33333 0.815125 1.185 0.666687 1 0.666687H0.333333C0.148438 0.666687 0 0.81502 0 1.00002V9.33335C0 9.70044 0.299583 10 0.666667 10H5.33333V8.66669H1.33333V4.00002H5.33333V2.66669H1.33333V1.00002Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1286">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 934 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1367)">
|
||||
<path d="M7.55556 0.888902H6L5.55556 0.444458H4.44444C4.19972 0.444458 4 0.64418 4 0.888902V3.11112C4 3.35585 4.19972 3.55557 4.44444 3.55557H7.55556C7.80028 3.55557 8 3.35585 8 3.11112V1.33335C8 1.08862 7.8 0.888902 7.55556 0.888902ZM7.55556 4.8889H6L5.55556 4.44446H4.44444C4.19972 4.44446 4 4.64418 4 4.8889V7.11112C4 7.35585 4.19972 7.55557 4.44444 7.55557H7.55556C7.80028 7.55557 8 7.35585 8 7.11112V5.33335C8 5.0889 7.8 4.8889 7.55556 4.8889ZM0.888889 0.66668C0.888889 0.543416 0.79 0.444458 0.666667 0.444458H0.222222C0.0989583 0.444458 0 0.543347 0 0.66668V6.22224C0 6.46696 0.199722 6.66668 0.444444 6.66668H3.55556V5.77779H0.888889V2.66668H3.55556V1.77779H0.888889V0.66668Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1367">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 941 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_614_97)">
|
||||
<path d="M5 7.62515C5 7.77359 4.9125 7.90796 4.77812 7.96734C4.64219 8.02828 4.47031 8.00327 4.37344 7.90328L3.12344 6.77828C3.04531 6.70796 3 6.6064 3 6.50015C3 6.3939 3.04531 6.29234 3.12344 6.22203L4.37344 5.09703C4.47031 4.99703 4.64219 4.97203 4.77812 5.03296C4.9125 5.09234 5 5.22671 5 5.37515V6.00015H5.25C5.80156 6.00015 6.25 5.55171 6.25 5.00015V2.39546C5.80781 2.18921 5.5 1.76265 5.5 1.25015C5.5 0.559838 6.05937 0.000150232 6.75 0.000150232C7.44063 0.000134607 8 0.559838 8 1.25015C8 1.76265 7.69219 2.18921 7.25 2.39546V5.00015C7.25 6.10484 6.35469 7.00015 5.25 7.00015H5V7.62515ZM7.125 1.23609C7.125 1.04296 6.95781 0.861088 6.75 0.861088C6.54219 0.861088 6.375 1.04296 6.375 1.23609C6.375 1.45718 6.54219 1.61109 6.75 1.61109C6.95781 1.61109 7.125 1.45718 7.125 1.23609ZM3 0.37515C3 0.227025 3.0875 0.0928065 3.22187 0.032494C3.35781 -0.0278185 3.51562 -0.00281852 3.62656 0.096244L4.87656 1.22123C4.95469 1.29232 5 1.39373 5 1.49998C5 1.60623 4.95469 1.70779 4.87656 1.7781L3.62656 2.9031C3.51562 3.0031 3.35781 3.0281 3.22187 2.96717C3.0875 2.90779 3 2.77342 3 2.62498V1.99998H2.75C2.19844 1.99998 1.75 2.44842 1.75 2.99998V5.60467C2.19219 5.79685 2.5 6.23748 2.5 6.74998C2.5 7.4406 1.94062 7.99998 1.25 7.99998C0.559687 7.99998 0 7.4406 0 6.74998C0 6.23748 0.308594 5.79685 0.75 5.60467V2.99998C0.75 1.89529 1.64531 0.999978 2.75 0.999978H3V0.374978V0.37515ZM0.875 6.75015C0.875 6.95796 1.04297 7.12515 1.25 7.12515C1.45703 7.12515 1.625 6.95796 1.625 6.75015C1.625 6.54234 1.45703 6.37515 1.25 6.37515C1.04297 6.37515 0.875 6.54234 0.875 6.75015Z" fill="white"/>
|
||||
<path d="M5 7.62515C5 7.77359 4.9125 7.90796 4.77812 7.96734C4.64219 8.02828 4.47031 8.00328 4.37344 7.90328L3.12344 6.77828C3.04531 6.70796 3 6.6064 3 6.50015C3 6.3939 3.04531 6.29234 3.12344 6.22203L4.37344 5.09703C4.47031 4.99703 4.64219 4.97203 4.77812 5.03296C4.9125 5.09234 5 5.22671 5 5.37515V6.00015H5.25C5.80156 6.00015 6.25 5.55171 6.25 5.00015V2.39546C5.80781 2.18921 5.5 1.76265 5.5 1.25015C5.5 0.559838 6.05937 0.000150232 6.75 0.000150232C7.44063 0.000134607 8 0.559838 8 1.25015C8 1.76265 7.69219 2.18921 7.25 2.39546V5.00015C7.25 6.10484 6.35469 7.00015 5.25 7.00015H5V7.62515ZM7.125 1.23609C7.125 1.04296 6.95781 0.861088 6.75 0.861088C6.54219 0.861088 6.375 1.04296 6.375 1.23609C6.375 1.45718 6.54219 1.61109 6.75 1.61109C6.95781 1.61109 7.125 1.45718 7.125 1.23609ZM3 0.37515C3 0.227025 3.0875 0.0928065 3.22187 0.032494C3.35781 -0.0278185 3.51563 -0.00281852 3.62656 0.096244L4.87656 1.22123C4.95469 1.29232 5 1.39373 5 1.49998C5 1.60623 4.95469 1.70779 4.87656 1.7781L3.62656 2.9031C3.51563 3.0031 3.35781 3.0281 3.22187 2.96717C3.0875 2.90779 3 2.77342 3 2.62498V1.99998H2.75C2.19844 1.99998 1.75 2.44842 1.75 2.99998V5.60467C2.19219 5.79685 2.5 6.23748 2.5 6.74998C2.5 7.4406 1.94063 7.99998 1.25 7.99998C0.559687 7.99998 0 7.4406 0 6.74998C0 6.23748 0.308594 5.79685 0.75 5.60467V2.99998C0.75 1.89529 1.64531 0.999978 2.75 0.999978H3V0.374978V0.37515ZM0.875 6.75015C0.875 6.95796 1.04297 7.12515 1.25 7.12515C1.45703 7.12515 1.625 6.95796 1.625 6.75015C1.625 6.54234 1.45703 6.37515 1.25 6.37515C1.04297 6.37515 0.875 6.54234 0.875 6.75015Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_614_97">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8167 11.0391L8.67607 7.89844C9.35576 7.06641 9.73076 6.01875 9.73076 4.875C9.73076 2.18203 7.54802 0 4.85576 0C2.16349 0 0.00161743 2.18273 0.00161743 4.875C0.00161743 7.56727 2.18412 9.75 4.85552 9.75C5.99904 9.75 7.0481 9.35367 7.87896 8.69438L11.0196 11.835C11.1508 11.9461 11.2961 12 11.4391 12C11.5821 12 11.7269 11.9449 11.8369 11.835C12.0555 11.6154 12.0555 11.2591 11.8165 11.0388L11.8167 11.0391ZM1.12685 4.875C1.12685 2.80734 2.8092 1.125 4.87685 1.125C6.94451 1.125 8.62685 2.80734 8.62685 4.875C8.62685 6.94266 6.94451 8.625 4.87685 8.625C2.8092 8.625 1.12685 6.94219 1.12685 4.875Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1288)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.40624 7.54167C6.77901 8.13568 5.93205 8.5 5 8.5C3.067 8.5 1.5 6.933 1.5 5C1.5 3.067 3.067 1.5 5 1.5C6.933 1.5 8.5 3.067 8.5 5C8.5 5.93205 8.13568 6.77901 7.54167 7.40624C7.51667 7.42558 7.49261 7.44673 7.46967 7.46967C7.44673 7.49261 7.42558 7.51667 7.40624 7.54167ZM7.96544 9.0261C7.13578 9.63821 6.11014 10 5 10C2.23858 10 0 7.76142 0 5C0 2.23858 2.23858 0 5 0C7.76142 0 10 2.23858 10 5C10 6.11014 9.63821 7.13578 9.0261 7.96544L11.5303 10.4697C11.8232 10.7626 11.8232 11.2374 11.5303 11.5303C11.2374 11.8232 10.7626 11.8232 10.4697 11.5303L7.96544 9.0261Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1288">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 727 B After Width: | Height: | Size: 874 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.87779 7.35938L5.78404 5.26562C6.23716 4.71094 6.48716 4.0125 6.48716 3.25C6.48716 1.45469 5.03201 0 3.23716 0C1.44232 0 0.00106812 1.45516 0.00106812 3.25C0.00106812 5.04484 1.45607 6.5 3.23701 6.5C3.99935 6.5 4.69873 6.23578 5.25263 5.79625L7.34638 7.89C7.43388 7.96406 7.53076 8 7.62607 8C7.72138 8 7.81794 7.96328 7.89123 7.89C8.03701 7.74359 8.03701 7.50609 7.87763 7.35922L7.87779 7.35938ZM0.751224 3.25C0.751224 1.87156 1.87279 0.75 3.25122 0.75C4.62966 0.75 5.75122 1.87156 5.75122 3.25C5.75122 4.62844 4.62966 5.75 3.25122 5.75C1.87279 5.75 0.751224 4.62813 0.751224 3.25Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1369)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.16734 5.87445C4.62987 6.26778 3.96705 6.5 3.25 6.5C1.45507 6.5 0 5.04493 0 3.25C0 1.45507 1.45507 0 3.25 0C5.04493 0 6.5 1.45507 6.5 3.25C6.5 3.96705 6.26778 4.62987 5.87445 5.16734L7.85355 7.14645C8.04882 7.34171 8.04882 7.65829 7.85355 7.85355C7.65829 8.04882 7.34171 8.04882 7.14645 7.85355L5.16734 5.87445ZM5.5 3.25C5.5 4.49264 4.49264 5.5 3.25 5.5C2.00736 5.5 1 4.49264 1 3.25C1 2.00736 2.00736 1 3.25 1C4.49264 1 5.5 2.00736 5.5 3.25Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1369">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 708 B After Width: | Height: | Size: 750 B |
@@ -1,6 +1,6 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_609_69)">
|
||||
<path d="M2.77187 4.52188L0.999998 6.29375V5.50001C0.999998 5.2236 0.776405 5.00001 0.499999 5.00001C0.223593 5.00001 0 5.2236 0 5.50001V7.5C0 7.565 0.013125 7.62985 0.0385155 7.69094C0.0891405 7.81328 0.186484 7.91078 0.308984 7.96141C0.370077 7.9875 0.434921 8 0.499921 8H2.49992C2.77632 8 2.99992 7.77641 2.99992 7.5C2.99992 7.2236 2.77632 7 2.49992 7H1.70773L3.4796 5.22813C3.67492 5.03282 3.67492 4.71641 3.4796 4.5211C3.28429 4.32579 2.9671 4.32657 2.77179 4.52188H2.77187ZM7.96092 0.309078C7.91014 0.186578 7.8128 0.089078 7.6903 0.038453C7.62967 0.0131406 7.56561 0 7.49999 0H5.49999C5.22358 0 4.99999 0.223593 4.99999 0.499999C4.99999 0.776405 5.22358 0.999998 5.49999 0.999998H6.29296L4.52109 2.77187C4.32577 2.96718 4.32577 3.28359 4.52109 3.4789C4.71624 3.67406 5.03265 3.67437 5.22812 3.4789L6.99999 1.70782V2.50001C6.99999 2.77642 7.22358 3.00001 7.49999 3.00001C7.77639 3.00001 7.99998 2.77642 7.99998 2.50001V0.500015C7.99998 0.435015 7.98748 0.370171 7.96092 0.309078Z" fill="white"/>
|
||||
<path d="M2.77188 4.52188L1.00001 6.29375V5.50001C1.00001 5.2236 0.776412 5.00001 0.500007 5.00001C0.223601 5.00001 7.62939e-06 5.2236 7.62939e-06 5.50001V7.5C7.62939e-06 7.565 0.0131326 7.62984 0.0385232 7.69094C0.0891481 7.81328 0.186492 7.91078 0.308991 7.96141C0.370085 7.9875 0.434929 8 0.499929 8H2.49992C2.77633 8 2.99992 7.77641 2.99992 7.5C2.99992 7.2236 2.77633 7 2.49992 7H1.70774L3.47961 5.22813C3.67492 5.03282 3.67492 4.71641 3.47961 4.5211C3.2843 4.32579 2.96711 4.32657 2.7718 4.52188H2.77188ZM7.96093 0.309078C7.91015 0.186578 7.81281 0.089078 7.69031 0.0384531C7.62968 0.0131406 7.56562 0 7.49999 0H5.5C5.22359 0 5 0.223593 5 0.499999C5 0.776405 5.22359 0.999998 5.5 0.999998H6.29296L4.52109 2.77187C4.32578 2.96718 4.32578 3.28359 4.52109 3.4789C4.71625 3.67406 5.03265 3.67437 5.22812 3.4789L6.99999 1.70782V2.50001C6.99999 2.77642 7.22359 3.00001 7.49999 3.00001C7.7764 3.00001 7.99999 2.77642 7.99999 2.50001V0.500015C7.99999 0.435015 7.98749 0.370171 7.96093 0.309078Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_609_69">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,6 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_609_75)">
|
||||
<path d="M0.854041 7.85284L2.75022 5.95775V6.74984C2.75022 7.02622 2.97379 7.24978 3.25017 7.24978C3.52654 7.24978 3.75011 7.02622 3.75011 6.74984V4.75008C3.75011 4.68493 3.73683 4.62009 3.71152 4.55916C3.6609 4.43605 3.56404 4.33919 3.44061 4.28919C3.37968 4.26264 3.31563 4.25014 3.25001 4.25014H1.25024C0.97387 4.25014 0.750303 4.4737 0.750303 4.75008C0.750303 5.02645 0.97387 5.25002 1.25024 5.25002H2.04312L0.146467 7.14667C-0.0488224 7.34196 -0.0488224 7.65833 0.146467 7.85362C0.341757 8.04891 0.658595 8.04813 0.853884 7.85284H0.854041ZM4.28911 3.44086C4.33973 3.56334 4.43706 3.66083 4.5597 3.71145C4.62032 3.7377 4.68437 3.7502 4.74999 3.7502H6.74976C7.02613 3.7502 7.2497 3.52663 7.2497 3.25025C7.2497 2.97388 7.02613 2.75031 6.74976 2.75031H5.95688L7.85353 0.85366C8.04882 0.65837 8.04882 0.342001 7.85353 0.146711C7.6584 -0.048422 7.34203 -0.0487345 7.14658 0.146711L5.24993 2.04415V1.25049C5.24993 0.974114 5.02636 0.750547 4.74999 0.750547C4.47362 0.750547 4.25005 0.974114 4.25005 1.25049V3.23619C4.25005 3.31587 4.26255 3.37993 4.28911 3.44086Z" fill="white"/>
|
||||
<path d="M0.854041 7.85284L2.75022 5.95775V6.74984C2.75022 7.02622 2.97379 7.24979 3.25017 7.24979C3.52654 7.24979 3.75011 7.02622 3.75011 6.74984V4.75008C3.75011 4.68493 3.73683 4.62009 3.71152 4.55916C3.6609 4.43605 3.56404 4.33919 3.44061 4.28919C3.37968 4.26264 3.31563 4.25014 3.25001 4.25014H1.25024C0.97387 4.25014 0.750303 4.4737 0.750303 4.75008C0.750303 5.02645 0.97387 5.25002 1.25024 5.25002H2.04312L0.146467 7.14667C-0.0488224 7.34196 -0.0488224 7.65833 0.146467 7.85362C0.341757 8.04891 0.658595 8.04813 0.853884 7.85284H0.854041ZM4.28911 3.44086C4.33973 3.56334 4.43706 3.66083 4.5597 3.71145C4.62032 3.7377 4.68437 3.7502 4.74999 3.7502H6.74976C7.02613 3.7502 7.2497 3.52663 7.2497 3.25025C7.2497 2.97388 7.02613 2.75031 6.74976 2.75031H5.95688L7.85353 0.85366C8.04882 0.65837 8.04882 0.342001 7.85353 0.146711C7.6584 -0.048422 7.34203 -0.0487345 7.14658 0.146711L5.24993 2.04415V1.25049C5.24993 0.974114 5.02636 0.750547 4.74999 0.750547C4.47362 0.750547 4.25005 0.974114 4.25005 1.25049V3.23619C4.25005 3.31587 4.26255 3.37993 4.28911 3.44086Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_609_75">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 2.5V9.5M2.5 6H9.5" stroke="white" stroke-linecap="round"/>
|
||||
<g clip-path="url(#clip0_519_280)">
|
||||
<path d="M6 2.5V9.5M2.5 6H9.5" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_519_280">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 335 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 2V14M2 8H14" stroke="white" stroke-width="1.71429" stroke-linecap="round"/>
|
||||
<g clip-path="url(#clip0_702_228)">
|
||||
<path d="M8.00001 3.33331V12.6666M3.33334 7.99998H12.6667" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_702_228">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 363 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 1.5V6.5M1.5 4H6.5" stroke="white" stroke-linecap="round"/>
|
||||
<g clip-path="url(#clip0_519_287)">
|
||||
<path d="M4 1V7M1 4H7" stroke="white" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_519_287">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 302 B |
@@ -1,5 +1,12 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1301)">
|
||||
<path d="M11 1H6V11H11V1Z" fill="white" fill-opacity="0.2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.1818 1C1.51237 1 1.04544 1.48778 1.04544 2V10C1.04544 10.5122 1.51237 11 2.1818 11H9.81817C10.4876 11 10.9545 10.5122 10.9545 10V2C10.9545 1.48778 10.4876 1 9.81817 1H2.1818ZM0.0454407 2C0.0454407 0.855367 1.04377 0 2.1818 0H9.81817C10.9562 0 11.9545 0.855367 11.9545 2V10C11.9545 11.1446 10.9562 12 9.81817 12H2.1818C1.04377 12 0.0454407 11.1446 0.0454407 10V2Z" fill="white"/>
|
||||
<path d="M6.09091 1H5V11H6.09091V1Z" fill="white"/>
|
||||
<path d="M6 1H5V11H6V1Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1301">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 779 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1295)">
|
||||
<path d="M0 2.25C0 1.42148 0.67148 0.75 1.5 0.75H10.5C11.3273 0.75 12 1.42148 12 2.25V9.75C12 10.5773 11.3273 11.25 10.5 11.25H1.5C0.67148 11.25 0 10.5773 0 9.75V2.25ZM2.39766 3.55781C2.18789 3.7875 2.20336 4.14141 2.43281 4.35234L4.23047 6L2.43281 7.64766C2.20336 7.85859 2.18789 8.2125 2.39766 8.44219C2.60859 8.65078 2.9625 8.68594 3.19219 8.47734L5.44219 6.41484C5.55937 6.30703 5.625 6.15703 5.625 5.97891C5.625 5.84297 5.55937 5.69297 5.44219 5.58516L3.19219 3.52266C2.9625 3.31406 2.60859 3.32813 2.39766 3.55781ZM5.8125 7.875C5.50078 7.875 5.25 8.12578 5.25 8.4375C5.25 8.74922 5.50078 9 5.8125 9H9.1875C9.4992 9 9.75 8.74922 9.75 8.4375C9.75 8.12578 9.4992 7.875 9.1875 7.875H5.8125Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1295">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 812 B After Width: | Height: | Size: 956 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1381)">
|
||||
<path d="M0 1.5C0 0.947653 0.447653 0.5 1 0.5H7C7.55153 0.5 8 0.947653 8 1.5V6.5C8 7.05153 7.55153 7.5 7 7.5H1C0.447653 7.5 0 7.05153 0 6.5V1.5ZM1.59844 2.37187C1.45859 2.525 1.46891 2.76094 1.62187 2.90156L2.82031 4L1.62187 5.09844C1.46891 5.23906 1.45859 5.475 1.59844 5.62813C1.73906 5.76719 1.975 5.79063 2.12813 5.65156L3.62813 4.27656C3.70625 4.20469 3.75 4.10469 3.75 3.98594C3.75 3.89531 3.70625 3.79531 3.62813 3.72344L2.12813 2.34844C1.975 2.20937 1.73906 2.21875 1.59844 2.37187ZM3.875 5.25C3.66719 5.25 3.5 5.41719 3.5 5.625C3.5 5.83281 3.66719 6 3.875 6H6.125C6.3328 6 6.5 5.83281 6.5 5.625C6.5 5.41719 6.3328 5.25 6.125 5.25H3.875Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1381">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 761 B After Width: | Height: | Size: 903 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1297)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 11.625H0V9.375L5.25 0.375H6.75L12 9.375V11.625ZM5.25 3.375H6.75V7.125H5.25V3.375ZM5.25 8.625H6.75V10.125H5.25V8.625Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1297">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 433 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1371)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 7.75H0V6.25L3.5 0.25H4.5L8 6.25V7.75ZM3.5 2.25H4.5V4.75H3.5V2.25ZM3.5 5.75H4.5V6.75H3.5V5.75Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1371">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 403 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1383)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.75 1C5.33579 1 5 1.33579 5 1.75V3H6.5V8H0.5V3H4V1.75C4 0.783502 4.7835 0 5.75 0C6.7165 0 7.5 0.783502 7.5 1.75V2H6.5V1.75C6.5 1.33579 6.16421 1 5.75 1ZM4.5 5H2.5V6H4.5V5Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1383">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 338 B After Width: | Height: | Size: 480 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1299)">
|
||||
<path d="M6 0C2.68594 0 0 2.68594 0 6C0 9.31406 2.68594 12 6 12C9.31406 12 12 9.31406 12 6C12 2.68594 9.31406 0 6 0ZM6 3C6.93211 3 7.6875 3.75563 7.6875 4.6875C7.6875 5.61937 6.93281 6.375 6 6.375C5.06813 6.375 4.3125 5.61937 4.3125 4.6875C4.3125 3.75563 5.06719 3 6 3ZM6 10.5C4.75945 10.5 3.63516 9.99539 2.81953 9.1807C3.19922 8.20078 4.13672 7.5 5.25 7.5H6.75C7.86422 7.5 8.80172 8.20031 9.18047 9.1807C8.36484 9.99609 7.23984 10.5 6 10.5Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1299">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 706 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 0C1.79063 0 0 1.79063 0 4C0 6.20937 1.79063 8 4 8C6.20937 8 8 6.20937 8 4C8 1.79063 6.20937 0 4 0ZM4 2C4.62141 2 5.125 2.50375 5.125 3.125C5.125 3.74625 4.62187 4.25 4 4.25C3.37875 4.25 2.875 3.74625 2.875 3.125C2.875 2.50375 3.37812 2 4 2ZM4 7C3.17297 7 2.42344 6.66359 1.87969 6.12047C2.13281 5.46719 2.75781 5 3.5 5H4.5C5.24281 5 5.86781 5.46688 6.12031 6.12047C5.57656 6.66406 4.82656 7 4 7Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1382)">
|
||||
<path d="M4 0C1.79063 0 0 1.79063 0 4C0 6.20937 1.79063 8 4 8C6.20937 8 8 6.20937 8 4C8 1.79063 6.20937 0 4 0ZM4 2C4.62141 2 5.125 2.50375 5.125 3.125C5.125 3.74625 4.62187 4.25 4 4.25C3.37875 4.25 2.875 3.74625 2.875 3.125C2.875 2.50375 3.37813 2 4 2ZM4 7C3.17297 7 2.42344 6.66359 1.87969 6.12047C2.13281 5.46719 2.75781 5 3.5 5H4.5C5.24281 5 5.86781 5.46687 6.12031 6.12047C5.57656 6.66406 4.82656 7 4 7Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1382">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 665 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.2 6.00001C5.52563 6.00001 6.6 4.92545 6.6 3.60001C6.6 2.27457 5.52563 1.20001 4.2 1.20001C2.87438 1.20001 1.8 2.27457 1.8 3.60001C1.8 4.92545 2.87438 6.00001 4.2 6.00001ZM5.15063 6.90001H3.24938C1.45444 6.90001 0 8.35501 0 10.1494C0 10.5094 0.291 10.8 0.649875 10.8H7.74937C8.10938 10.8 8.4 10.5094 8.4 10.1494C8.4 8.35501 6.945 6.90001 5.15063 6.90001ZM8.98313 7.20001H7.59844C8.46 7.90689 9 8.96439 9 10.1494C9 10.3894 8.92875 10.6106 8.8125 10.8H11.4C11.7319 10.8 12 10.53 12 10.1831C12 8.54251 10.6575 7.20001 8.98313 7.20001ZM8.1 6.00001C9.26063 6.00001 10.2 5.06064 10.2 3.90001C10.2 2.73939 9.26063 1.80001 8.1 1.80001C7.62919 1.80001 7.19925 1.96042 6.849 2.22207C7.065 2.63682 7.2 3.10126 7.2 3.60001C7.2 4.26601 6.97631 4.87764 6.60769 5.37582C6.98813 5.76001 7.515 6.00001 8.1 6.00001Z" fill="white"/>
|
||||
<path d="M4.2 6.00001C5.52563 6.00001 6.6 4.92545 6.6 3.60001C6.6 2.27457 5.52563 1.20001 4.2 1.20001C2.87438 1.20001 1.8 2.27457 1.8 3.60001C1.8 4.92545 2.87438 6.00001 4.2 6.00001ZM5.15063 6.90001H3.24938C1.45444 6.90001 0 8.35501 0 10.1494C0 10.5094 0.291 10.8 0.649875 10.8H7.74938C8.10938 10.8 8.4 10.5094 8.4 10.1494C8.4 8.35501 6.945 6.90001 5.15063 6.90001ZM8.98313 7.20001H7.59844C8.46 7.90689 9 8.96439 9 10.1494C9 10.3894 8.92875 10.6106 8.8125 10.8H11.4C11.7319 10.8 12 10.53 12 10.1831C12 8.54251 10.6575 7.20001 8.98313 7.20001ZM8.1 6.00001C9.26063 6.00001 10.2 5.06064 10.2 3.90001C10.2 2.73939 9.26063 1.80001 8.1 1.80001C7.62919 1.80001 7.19925 1.96042 6.849 2.22207C7.065 2.63682 7.2 3.10126 7.2 3.60001C7.2 4.26601 6.97631 4.87764 6.60769 5.37582C6.98813 5.76001 7.515 6.00001 8.1 6.00001Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 928 B After Width: | Height: | Size: 928 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.8 3.99999C3.68375 3.99999 4.4 3.28361 4.4 2.39999C4.4 1.51636 3.68375 0.799988 2.8 0.799988C1.91625 0.799988 1.2 1.51636 1.2 2.39999C1.2 3.28361 1.91625 3.99999 2.8 3.99999ZM3.43375 4.59999H2.16625C0.969625 4.59999 0 5.56999 0 6.76624C0 7.00624 0.194 7.19999 0.43325 7.19999H5.16625C5.40625 7.19999 5.6 7.00624 5.6 6.76624C5.6 5.56999 4.63 4.59999 3.43375 4.59999ZM5.98875 4.79999H5.06563C5.64 5.27124 6 5.97624 6 6.76624C6 6.92624 5.9525 7.07374 5.875 7.19999H7.6C7.82125 7.19999 8 7.01999 8 6.78874C8 5.69499 7.105 4.79999 5.98875 4.79999ZM5.4 3.99999C6.17375 3.99999 6.8 3.37374 6.8 2.59999C6.8 1.82624 6.17375 1.19999 5.4 1.19999C5.08612 1.19999 4.7995 1.30693 4.566 1.48136C4.71 1.75786 4.8 2.06749 4.8 2.39999C4.8 2.84399 4.65088 3.25174 4.40513 3.58386C4.65875 3.83999 5.01 3.99999 5.4 3.99999Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1375)">
|
||||
<path d="M2.8 3.99999C3.68375 3.99999 4.4 3.28361 4.4 2.39999C4.4 1.51636 3.68375 0.799988 2.8 0.799988C1.91625 0.799988 1.2 1.51636 1.2 2.39999C1.2 3.28361 1.91625 3.99999 2.8 3.99999ZM3.43375 4.59999H2.16625C0.969625 4.59999 0 5.56999 0 6.76624C0 7.00624 0.194 7.19999 0.43325 7.19999H5.16625C5.40625 7.19999 5.6 7.00624 5.6 6.76624C5.6 5.56999 4.63 4.59999 3.43375 4.59999ZM5.98875 4.79999H5.06563C5.64 5.27124 6 5.97624 6 6.76624C6 6.92624 5.9525 7.07374 5.875 7.19999H7.6C7.82125 7.19999 8 7.01999 8 6.78874C8 5.69499 7.105 4.79999 5.98875 4.79999ZM5.4 3.99999C6.17375 3.99999 6.8 3.37374 6.8 2.59999C6.8 1.82624 6.17375 1.19999 5.4 1.19999C5.08613 1.19999 4.7995 1.30693 4.566 1.48136C4.71 1.75786 4.8 2.06749 4.8 2.39999C4.8 2.84399 4.65088 3.25174 4.40513 3.58386C4.65875 3.83999 5.01 3.99999 5.4 3.99999Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1375">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_430_1373)">
|
||||
<path d="M2.8 3.99999C3.68375 3.99999 4.4 3.28361 4.4 2.39999C4.4 1.51636 3.68375 0.799988 2.8 0.799988C1.91625 0.799988 1.2 1.51636 1.2 2.39999C1.2 3.28361 1.91625 3.99999 2.8 3.99999ZM3.43375 4.59999H2.16625C0.970125 4.59999 0 5.56999 0 6.76624C0 7.00624 0.194 7.19999 0.43325 7.19999H5.167C5.40625 7.19999 5.6 7.00624 5.6 6.76624C5.6 5.56999 4.63 4.59999 3.43375 4.59999ZM7.7 3.29999H7.1V2.69999C7.1 2.53499 6.96625 2.39999 6.8 2.39999C6.63375 2.39999 6.5 2.53436 6.5 2.69999V3.29999H5.9C5.735 3.29999 5.6 3.43499 5.6 3.59999C5.6 3.76499 5.73438 3.89999 5.9 3.89999H6.5V4.49999C6.5 4.66624 6.635 4.79999 6.8 4.79999C6.965 4.79999 7.1 4.66561 7.1 4.49999V3.89999H7.7C7.86625 3.89999 8 3.76624 8 3.59999C8 3.43374 7.86625 3.29999 7.7 3.29999Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1373">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 1001 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.71108 6L0 2.28892L2.28892 0L6 3.71108L9.71108 0L12 2.28892L8.28892 6L12 9.71108L9.71108 12L6 8.28892L2.28892 12L0 9.71108L3.71108 6Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1296)">
|
||||
<path d="M9.5 2.5L6 6M2.5 9.5L6 6M2.5 2.5L6 6M6 6L9.5 9.5" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1296">
|
||||
<rect width="12" height="12" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 365 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.32959 8L1 3.67041L3.67041 1L8 5.32959L12.3296 1L15 3.67041L10.6704 8L15 12.3296L12.3296 15L8 10.6704L3.67041 15L1 12.3296L5.32959 8Z" fill="white"/>
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_702_229)">
|
||||
<path d="M11.875 3.125L7.5 7.5M3.125 11.875L7.5 7.5M3.125 3.125L7.5 7.5M7.5 7.5L11.875 11.875" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_702_229">
|
||||
<rect width="15" height="15" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 399 B |
@@ -1,3 +1,10 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.47405 4L0 1.52595L1.52595 0L4 2.47405L6.47405 0L8 1.52595L5.52595 4L8 6.47405L6.47405 8L4 5.52595L1.52595 8L0 6.47405L2.47405 4Z" fill="white"/>
|
||||
<g clip-path="url(#clip0_430_1379)">
|
||||
<path d="M6.49999 1.49999L1.49997 6.5M1.49998 1.50002L6.49999 6.50004" stroke="white" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_430_1379">
|
||||
<rect width="8" height="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 352 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.76433 6.63513C8.0768 6.9476 8.0768 7.4538 7.76433 7.76627C7.60935 7.92251 7.40437 8 7.19939 8C6.99441 8 6.78992 7.92188 6.63394 7.76565L3.99969 5.13264L1.36568 7.7649C1.20945 7.92238 1.00472 7.99988 0.799986 7.99988C0.595255 7.99988 0.390774 7.92238 0.234414 7.7649C-0.0780566 7.45243 -0.0780566 6.94622 0.234414 6.63375L2.86917 3.999L0.234414 1.3655C-0.0780566 1.05303 -0.0780566 0.546824 0.234414 0.234353C0.546885 -0.0781177 1.05309 -0.0781177 1.36556 0.234353L3.99969 2.87023L6.63444 0.235478C6.94691 -0.0769928 7.45311 -0.0769928 7.76558 0.235478C8.07805 0.547949 8.07805 1.05415 7.76558 1.36662L5.13083 4.00138L7.76433 6.63488V6.63513Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 769 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 11C5 14.3137 7.68629 17 11 17C14.3137 17 17 14.3137 17 11C17 7.68629 14.3137 5 11 5C7.68629 5 5 7.68629 5 11ZM11 3C6.58172 3 3 6.58172 3 11C3 15.4183 6.58172 19 11 19C15.4183 19 19 15.4183 19 11C19 6.58172 15.4183 3 11 3Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.09092 8.09088H14.6364L10.5511 12.4545H12.4546L13.9091 13.9091H7.36365L11.7273 9.54543H9.54547L8.09092 8.09088Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 571 B |
@@ -3,8 +3,12 @@
|
||||
{
|
||||
"bindings": {
|
||||
"up": "menu::SelectPrev",
|
||||
"pageup": "menu::SelectFirst",
|
||||
"shift-pageup": "menu::SelectFirst",
|
||||
"ctrl-p": "menu::SelectPrev",
|
||||
"down": "menu::SelectNext",
|
||||
"pagedown": "menu::SelectLast",
|
||||
"shift-pagedown": "menu::SelectFirst",
|
||||
"ctrl-n": "menu::SelectNext",
|
||||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
@@ -60,13 +64,18 @@
|
||||
"cmd-z": "editor::Undo",
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
"ctrl-p": "editor::MoveUp",
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-l": "editor::CenterScreen",
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-b": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
@@ -118,8 +127,18 @@
|
||||
"stop_at_soft_wraps": true
|
||||
}
|
||||
],
|
||||
"pageup": "editor::PageUp",
|
||||
"pagedown": "editor::PageDown",
|
||||
"ctrl-v": [
|
||||
"editor::MovePageDown",
|
||||
{
|
||||
"center_cursor": true
|
||||
}
|
||||
],
|
||||
"alt-v": [
|
||||
"editor::MovePageUp",
|
||||
{
|
||||
"center_cursor": true
|
||||
}
|
||||
],
|
||||
"ctrl-cmd-space": "editor::ShowCharacterPalette"
|
||||
}
|
||||
},
|
||||
@@ -376,13 +395,16 @@
|
||||
{
|
||||
"bindings": {
|
||||
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
|
||||
"cmd-shift-c": "collab::ToggleCollaborationMenu",
|
||||
"cmd-alt-i": "zed::DebugElements"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts"
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPrevHunk"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -395,7 +417,6 @@
|
||||
"context": "Workspace",
|
||||
"bindings": {
|
||||
"shift-escape": "dock::FocusDock",
|
||||
"cmd-shift-c": "contacts_panel::ToggleFocus",
|
||||
"cmd-shift-b": "workspace::ToggleRightSidebar"
|
||||
}
|
||||
},
|
||||
@@ -412,6 +433,12 @@
|
||||
"shift-escape": "dock::HideDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
"cmd-escape": "dock::MoveActiveItemToDock"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
@@ -451,10 +478,18 @@
|
||||
"terminal::SendKeystroke",
|
||||
"up"
|
||||
],
|
||||
"pageup": [
|
||||
"terminal::SendKeystroke",
|
||||
"pageup"
|
||||
],
|
||||
"down": [
|
||||
"terminal::SendKeystroke",
|
||||
"down"
|
||||
],
|
||||
"pagedown": [
|
||||
"terminal::SendKeystroke",
|
||||
"pagedown"
|
||||
],
|
||||
"escape": [
|
||||
"terminal::SendKeystroke",
|
||||
"escape"
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
}
|
||||
],
|
||||
"h": "vim::Left",
|
||||
"backspace": "vim::Left",
|
||||
"backspace": "vim::Backspace",
|
||||
"j": "vim::Down",
|
||||
"k": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
"0": "vim::StartOfLine",
|
||||
"$": "vim::EndOfLine",
|
||||
"shift-g": "vim::EndOfDocument",
|
||||
"w": "vim::NextWordStart",
|
||||
@@ -38,7 +37,60 @@
|
||||
}
|
||||
],
|
||||
"%": "vim::Matching",
|
||||
"escape": "editor::Cancel"
|
||||
"escape": "editor::Cancel",
|
||||
"i": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"a": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Object": {
|
||||
"around": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
"1": [
|
||||
"vim::Number",
|
||||
1
|
||||
],
|
||||
"2": [
|
||||
"vim::Number",
|
||||
2
|
||||
],
|
||||
"3": [
|
||||
"vim::Number",
|
||||
3
|
||||
],
|
||||
"4": [
|
||||
"vim::Number",
|
||||
4
|
||||
],
|
||||
"5": [
|
||||
"vim::Number",
|
||||
5
|
||||
],
|
||||
"6": [
|
||||
"vim::Number",
|
||||
6
|
||||
],
|
||||
"7": [
|
||||
"vim::Number",
|
||||
7
|
||||
],
|
||||
"8": [
|
||||
"vim::Number",
|
||||
8
|
||||
],
|
||||
"9": [
|
||||
"vim::Number",
|
||||
9
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -98,6 +150,15 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == n",
|
||||
"bindings": {
|
||||
"0": [
|
||||
"vim::Number",
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == g",
|
||||
"bindings": {
|
||||
@@ -112,13 +173,6 @@
|
||||
{
|
||||
"context": "Editor && vim_operator == c",
|
||||
"bindings": {
|
||||
"w": "vim::ChangeWord",
|
||||
"shift-w": [
|
||||
"vim::ChangeWord",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"c": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
@@ -134,9 +188,34 @@
|
||||
"y": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimObject",
|
||||
"bindings": {
|
||||
"w": "vim::Word",
|
||||
"shift-w": [
|
||||
"vim::Word",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"s": "vim::Sentence",
|
||||
"'": "vim::Quotes",
|
||||
"`": "vim::BackQuotes",
|
||||
"\"": "vim::DoubleQuotes",
|
||||
"(": "vim::Parentheses",
|
||||
")": "vim::Parentheses",
|
||||
"[": "vim::SquareBrackets",
|
||||
"]": "vim::SquareBrackets",
|
||||
"{": "vim::CurlyBrackets",
|
||||
"}": "vim::CurlyBrackets",
|
||||
"<": "vim::AngleBrackets",
|
||||
">": "vim::AngleBrackets"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == visual",
|
||||
"bindings": {
|
||||
"u": "editor::Undo",
|
||||
"c": "vim::VisualChange",
|
||||
"d": "vim::VisualDelete",
|
||||
"x": "vim::VisualDelete",
|
||||
|
||||
@@ -1,206 +1,230 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI
|
||||
"theme": "one-dark",
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The default font size for text in the editor
|
||||
"buffer_font_size": 15,
|
||||
// Whether to enable vim modes and key bindings
|
||||
"vim_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether new projects should start out 'online'. Online projects
|
||||
// appear in the contacts panel under your name, so that your contacts
|
||||
// can see which projects you are working on. Regardless of this
|
||||
// setting, projects keep their last online status when you reopen them.
|
||||
"projects_online_by_default": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
"enable_language_server": true,
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
// 1. Never automatically save:
|
||||
// "autosave": "off",
|
||||
// 2. Save when changing focus away from the Zed window:
|
||||
// "autosave": "on_window_change",
|
||||
// 3. Save when changing focus away from a specific buffer:
|
||||
// "autosave": "on_focus_change",
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Where to place the dock by default. This setting can take three
|
||||
// values:
|
||||
//
|
||||
// 1. Position the dock attached to the bottom of the workspace
|
||||
// "default_dock_anchor": "bottom"
|
||||
// 2. Position the dock to the right of the workspace like a side panel
|
||||
// "default_dock_anchor": "right"
|
||||
// 3. Position the dock full screen over the entire workspace"
|
||||
// "default_dock_anchor": "expanded"
|
||||
"default_dock_anchor": "right",
|
||||
// Whether or not to perform a buffer format before saving
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take two values:
|
||||
//
|
||||
// 1. Format code using the current language server:
|
||||
// "format_on_save": "language_server"
|
||||
// 2. Format code using an external command:
|
||||
// "format_on_save": {
|
||||
// "external": {
|
||||
// "command": "prettier",
|
||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||
// }
|
||||
// The name of the Zed theme to use for the UI
|
||||
"theme": "One Dark",
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The default font size for text in the editor
|
||||
"buffer_font_size": 15,
|
||||
// Whether to enable vim modes and key bindings
|
||||
"vim_mode": false,
|
||||
// Whether to show the informational hover box when moving the mouse
|
||||
// over symbols in the editor.
|
||||
"hover_popover_enabled": true,
|
||||
// Whether the cursor blinks in the editor.
|
||||
"cursor_blink": true,
|
||||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether new projects should start out 'online'. Online projects
|
||||
// appear in the contacts panel under your name, so that your contacts
|
||||
// can see which projects you are working on. Regardless of this
|
||||
// setting, projects keep their last online status when you reopen them.
|
||||
"projects_online_by_default": true,
|
||||
// Whether to use language servers to provide code intelligence.
|
||||
"enable_language_server": true,
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
// 1. Never automatically save:
|
||||
// "autosave": "off",
|
||||
// 2. Save when changing focus away from the Zed window:
|
||||
// "autosave": "on_window_change",
|
||||
// 3. Save when changing focus away from a specific buffer:
|
||||
// "autosave": "on_focus_change",
|
||||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Where to place the dock by default. This setting can take three
|
||||
// values:
|
||||
//
|
||||
// 1. Position the dock attached to the bottom of the workspace
|
||||
// "default_dock_anchor": "bottom"
|
||||
// 2. Position the dock to the right of the workspace like a side panel
|
||||
// "default_dock_anchor": "right"
|
||||
// 3. Position the dock full screen over the entire workspace"
|
||||
// "default_dock_anchor": "expanded"
|
||||
"default_dock_anchor": "right",
|
||||
// Whether or not to perform a buffer format before saving
|
||||
"format_on_save": "on",
|
||||
// How to perform a buffer format. This setting can take two values:
|
||||
//
|
||||
// 1. Format code using the current language server:
|
||||
// "format_on_save": "language_server"
|
||||
// 2. Format code using an external command:
|
||||
// "format_on_save": {
|
||||
// "external": {
|
||||
// "command": "prettier",
|
||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||
// }
|
||||
// }
|
||||
"formatter": "language_server",
|
||||
// How to soft-wrap long lines of text. This setting can take
|
||||
// three values:
|
||||
//
|
||||
// 1. Do not soft wrap.
|
||||
// "soft_wrap": "none",
|
||||
// 2. Soft wrap lines that overflow the editor:
|
||||
// "soft_wrap": "editor_width",
|
||||
// 3. Soft wrap lines at the preferred line length
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
"soft_wrap": "none",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
"preferred_line_length": 80,
|
||||
// Whether to indent lines using tab characters, as opposed to multiple
|
||||
// spaces.
|
||||
"hard_tabs": false,
|
||||
// How many columns a tab should occupy.
|
||||
"tab_size": 4,
|
||||
// Git gutter behavior configuration.
|
||||
"git": {
|
||||
// Control whether the git gutter is shown. May take 2 values:
|
||||
// 1. Show the gutter
|
||||
// "git_gutter": "tracked_files"
|
||||
// 2. Hide the gutter
|
||||
// "git_gutter": "hide"
|
||||
"git_gutter": "tracked_files"
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
// The path of the directory where journal entries are stored
|
||||
"path": "~",
|
||||
// What format to display the hours in
|
||||
// May take 2 values:
|
||||
// 1. hour12
|
||||
// 2. hour24
|
||||
"hour_format": "hour12"
|
||||
},
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
// What shell to use when opening a terminal. May take 3 values:
|
||||
// 1. Use the system's default terminal configuration (e.g. $TERM).
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"formatter": "language_server",
|
||||
// How to soft-wrap long lines of text. This setting can take
|
||||
// three values:
|
||||
"shell": "system",
|
||||
// What working directory to use when launching the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Use the current file's project directory. Will Fallback to the
|
||||
// first project directory strategy if unsuccessful
|
||||
// "working_directory": "current_project_directory"
|
||||
// 2. Use the first project in this workspace's directory
|
||||
// "working_directory": "first_project_directory"
|
||||
// 3. Always use this platform's home directory (if we can find it)
|
||||
// "working_directory": "always_home"
|
||||
// 4. Always use a specific directory. This value will be shell expanded.
|
||||
// If this path is not a valid directory the terminal will default to
|
||||
// this platform's home directory (if we can find it)
|
||||
// "working_directory": {
|
||||
// "always": {
|
||||
// "directory": "~/zed/projects/"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 1. Do not soft wrap.
|
||||
// "soft_wrap": "none",
|
||||
// 2. Soft wrap lines that overflow the editor:
|
||||
// "soft_wrap": "editor_width",
|
||||
// 3. Soft wrap lines at the preferred line length
|
||||
// "soft_wrap": "preferred_line_length",
|
||||
"soft_wrap": "none",
|
||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
// is enabled.
|
||||
"preferred_line_length": 80,
|
||||
// Whether to indent lines using tab characters, as opposed to multiple
|
||||
// spaces.
|
||||
"hard_tabs": false,
|
||||
// How many columns a tab should occupy.
|
||||
"tab_size": 4,
|
||||
// Settings specific to the terminal
|
||||
"terminal": {
|
||||
// What shell to use when opening a terminal. May take 3 values:
|
||||
// 1. Use the system's default terminal configuration (e.g. $TERM).
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system",
|
||||
// What working directory to use when launching the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Use the current file's project directory. Will Fallback to the
|
||||
// first project directory strategy if unsuccessful
|
||||
// "working_directory": "current_project_directory"
|
||||
// 2. Use the first project in this workspace's directory
|
||||
// "working_directory": "first_project_directory"
|
||||
// 3. Always use this platform's home directory (if we can find it)
|
||||
// "working_directory": "always_home"
|
||||
// 4. Always use a specific directory. This value will be shell expanded.
|
||||
// If this path is not a valid directory the terminal will default to
|
||||
// this platform's home directory (if we can find it)
|
||||
// "working_directory": {
|
||||
// "always": {
|
||||
// "directory": "~/zed/projects/"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
"working_directory": "current_project_directory",
|
||||
// Set the cursor blinking behavior in the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
"blinking": "terminal_controlled",
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
// like vim or less). The terminal can still set and unset this mode.
|
||||
// May take 2 values:
|
||||
// 1. Default alternate scroll mode to on
|
||||
// "alternate_scroll": "on",
|
||||
// 2. Default alternate scroll mode to off
|
||||
// "alternate_scroll": "off",
|
||||
"alternate_scroll": "off",
|
||||
// Set whether the option key behaves as the meta key.
|
||||
// May take 2 values:
|
||||
// 1. Rely on default platform handling of option key, on macOS
|
||||
// this means generating certain unicode characters
|
||||
// "option_to_meta": false,
|
||||
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
||||
// "option_to_meta": true,
|
||||
"option_as_meta": false,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// enviroment. Use `:` to seperate multiple values.
|
||||
"env": {
|
||||
// "KEY": "value1:value2"
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15"
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"C": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"C++": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Elixir": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
},
|
||||
"Markdown": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"Rust": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"JavaScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TypeScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TSX": {
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust_analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "checkOnSave": {
|
||||
// "command": "clippy"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
"working_directory": "current_project_directory",
|
||||
// Set the cursor blinking behavior in the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
"blinking": "terminal_controlled",
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
// like vim or less). The terminal can still set and unset this mode.
|
||||
// May take 2 values:
|
||||
// 1. Default alternate scroll mode to on
|
||||
// "alternate_scroll": "on",
|
||||
// 2. Default alternate scroll mode to off
|
||||
// "alternate_scroll": "off",
|
||||
"alternate_scroll": "off",
|
||||
// Set whether the option key behaves as the meta key.
|
||||
// May take 2 values:
|
||||
// 1. Rely on default platform handling of option key, on macOS
|
||||
// this means generating certain unicode characters
|
||||
// "option_to_meta": false,
|
||||
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
||||
// "option_to_meta": true,
|
||||
"option_as_meta": false,
|
||||
// Whether or not selecting text in the terminal will automatically
|
||||
// copy to the system clipboard.
|
||||
"copy_on_select": false,
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// enviroment. Use `:` to seperate multiple values.
|
||||
"env": {
|
||||
// "KEY": "value1:value2"
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15"
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
"Plain Text": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"C": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"C++": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Elixir": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"Go": {
|
||||
"tab_size": 4,
|
||||
"hard_tabs": true
|
||||
},
|
||||
"Markdown": {
|
||||
"soft_wrap": "preferred_line_length"
|
||||
},
|
||||
"Rust": {
|
||||
"tab_size": 4
|
||||
},
|
||||
"JavaScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TypeScript": {
|
||||
"tab_size": 2
|
||||
},
|
||||
"TSX": {
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust_analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
// "checkOnSave": {
|
||||
// "command": "clippy"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ impl ActivityIndicator {
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> ViewHandle<ActivityIndicator> {
|
||||
let project = workspace.project().clone();
|
||||
let auto_updater = AutoUpdater::get(cx);
|
||||
let this = cx.add_view(|cx: &mut ViewContext<Self>| {
|
||||
let mut status_events = languages.language_server_binary_statuses();
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
@@ -66,11 +67,14 @@ impl ActivityIndicator {
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||
if let Some(auto_updater) = auto_updater.as_ref() {
|
||||
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
|
||||
Self {
|
||||
statuses: Default::default(),
|
||||
project: project.clone(),
|
||||
auto_updater: AutoUpdater::get(cx),
|
||||
auto_updater,
|
||||
}
|
||||
});
|
||||
cx.subscribe(&this, move |workspace, _, event, cx| match event {
|
||||
@@ -285,7 +289,7 @@ impl View for ActivityIndicator {
|
||||
.workspace
|
||||
.status_bar
|
||||
.lsp_status;
|
||||
let style = if state.hovered && action.is_some() {
|
||||
let style = if state.hovered() && action.is_some() {
|
||||
theme.hover.as_ref().unwrap_or(&theme.default)
|
||||
} else {
|
||||
&theme.default
|
||||
|
||||
29
crates/assets/build.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
let output = Command::new("npm")
|
||||
.current_dir("../../styles")
|
||||
.args(["install", "--no-save"])
|
||||
.output()
|
||||
.expect("failed to run npm");
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"failed to install theme dependencies {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
let output = Command::new("npm")
|
||||
.current_dir("../../styles")
|
||||
.args(["run", "build"])
|
||||
.output()
|
||||
.expect("failed to run npm");
|
||||
if !output.status.success() {
|
||||
panic!(
|
||||
"build script failed {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=../../styles/src");
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
mod update_notification;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN};
|
||||
use client::{http::HttpClient, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||
use gpui::{
|
||||
actions, platform::AppVersion, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
MutableAppContext, Task, WeakViewHandle,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use settings::ReleaseChannel;
|
||||
use smol::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use std::{env, ffi::OsString, path::PathBuf, sync::Arc, time::Duration};
|
||||
use update_notification::UpdateNotification;
|
||||
@@ -40,7 +41,7 @@ pub struct AutoUpdater {
|
||||
current_version: AppVersion,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
pending_poll: Option<Task<()>>,
|
||||
db: Arc<project::Db>,
|
||||
db: project::Db,
|
||||
server_url: String,
|
||||
}
|
||||
|
||||
@@ -54,13 +55,9 @@ impl Entity for AutoUpdater {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
db: Arc<project::Db>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
server_url: String,
|
||||
cx: &mut MutableAppContext,
|
||||
) {
|
||||
pub fn init(db: project::Db, http_client: Arc<dyn HttpClient>, cx: &mut MutableAppContext) {
|
||||
if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
|
||||
let server_url = ZED_SERVER_URL.to_string();
|
||||
let auto_updater = cx.add_model(|cx| {
|
||||
let updater = AutoUpdater::new(version, db.clone(), http_client, server_url.clone());
|
||||
updater.start_polling(cx).detach();
|
||||
@@ -116,7 +113,7 @@ impl AutoUpdater {
|
||||
|
||||
fn new(
|
||||
current_version: AppVersion,
|
||||
db: Arc<project::Db>,
|
||||
db: project::Db,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
server_url: String,
|
||||
) -> Self {
|
||||
@@ -177,9 +174,19 @@ impl AutoUpdater {
|
||||
this.current_version,
|
||||
)
|
||||
});
|
||||
|
||||
let preview_param = cx.read(|cx| {
|
||||
if cx.has_global::<ReleaseChannel>() {
|
||||
if *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview {
|
||||
return "&preview=1";
|
||||
}
|
||||
}
|
||||
""
|
||||
});
|
||||
|
||||
let mut response = client
|
||||
.get(
|
||||
&format!("{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"),
|
||||
&format!("{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg{preview_param}"),
|
||||
Default::default(),
|
||||
true,
|
||||
)
|
||||
@@ -211,11 +218,14 @@ impl AutoUpdater {
|
||||
let temp_dir = tempdir::TempDir::new("zed-auto-update")?;
|
||||
let dmg_path = temp_dir.path().join("Zed.dmg");
|
||||
let mount_path = temp_dir.path().join("Zed");
|
||||
let mut mounted_app_path: OsString = mount_path.join("Zed.app").into();
|
||||
mounted_app_path.push("/");
|
||||
let running_app_path = ZED_APP_PATH
|
||||
.clone()
|
||||
.map_or_else(|| cx.platform().app_path(), Ok)?;
|
||||
let running_app_filename = running_app_path
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("invalid running app path"))?;
|
||||
let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into();
|
||||
mounted_app_path.push("/");
|
||||
|
||||
let mut dmg_file = File::create(&dmg_path).await?;
|
||||
let mut response = client.get(&release.url, Default::default(), true).await?;
|
||||
@@ -283,9 +293,9 @@ impl AutoUpdater {
|
||||
let db = self.db.clone();
|
||||
cx.background().spawn(async move {
|
||||
if should_show {
|
||||
db.write([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY, "")])?;
|
||||
db.write_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY, "")?;
|
||||
} else {
|
||||
db.delete([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)])?;
|
||||
db.delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@@ -293,8 +303,7 @@ impl AutoUpdater {
|
||||
|
||||
fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
|
||||
let db = self.db.clone();
|
||||
cx.background().spawn(async move {
|
||||
Ok(db.read([(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)])?[0].is_some())
|
||||
})
|
||||
cx.background()
|
||||
.spawn(async move { Ok(db.read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?.is_some()) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use gpui::{
|
||||
Element, Entity, MouseButton, View, ViewContext,
|
||||
};
|
||||
use menu::Cancel;
|
||||
use settings::Settings;
|
||||
use settings::{ReleaseChannel, Settings};
|
||||
use workspace::Notification;
|
||||
|
||||
pub struct UpdateNotification {
|
||||
@@ -29,13 +29,15 @@ impl View for UpdateNotification {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let theme = &theme.update_notification;
|
||||
|
||||
let app_name = cx.global::<ReleaseChannel>().name();
|
||||
|
||||
MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| {
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Text::new(
|
||||
format!("Updated to Zed {}", self.version),
|
||||
format!("Updated to {app_name} {}", self.version),
|
||||
theme.message.text.clone(),
|
||||
)
|
||||
.contained()
|
||||
@@ -49,7 +51,7 @@ impl View for UpdateNotification {
|
||||
.with_child(
|
||||
MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
|
||||
let style = theme.dismiss_button.style_for(state, false);
|
||||
Svg::new("icons/x_mark_thin_8.svg")
|
||||
Svg::new("icons/x_mark_8.svg")
|
||||
.with_color(style.color)
|
||||
.constrained()
|
||||
.with_width(style.icon_width)
|
||||
|
||||
40
crates/call/Cargo.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "call"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/call.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
"client/test-support",
|
||||
"collections/test-support",
|
||||
"gpui/test-support",
|
||||
"live_kit_client/test-support",
|
||||
"project/test-support",
|
||||
"util/test-support"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
client = { path = "../client" }
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
live_kit_client = { path = "../live_kit_client" }
|
||||
media = { path = "../media" }
|
||||
project = { path = "../project" }
|
||||
util = { path = "../util" }
|
||||
|
||||
anyhow = "1.0.38"
|
||||
async-broadcast = "0.4"
|
||||
futures = "0.3"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
||||
project = { path = "../project", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
300
crates/call/src/call.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
pub mod participant;
|
||||
pub mod room;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{proto, Client, TypedEnvelope, User, UserStore};
|
||||
use collections::HashSet;
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Subscription, Task, WeakModelHandle,
|
||||
};
|
||||
pub use participant::ParticipantLocation;
|
||||
use postage::watch;
|
||||
use project::Project;
|
||||
pub use room::Room;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut MutableAppContext) {
|
||||
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(active_call);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IncomingCall {
|
||||
pub room_id: u64,
|
||||
pub caller: Arc<User>,
|
||||
pub participants: Vec<Arc<User>>,
|
||||
pub initial_project: Option<proto::ParticipantProject>,
|
||||
}
|
||||
|
||||
pub struct ActiveCall {
|
||||
room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
|
||||
location: Option<WeakModelHandle<Project>>,
|
||||
pending_invites: HashSet<u64>,
|
||||
incoming_call: (
|
||||
watch::Sender<Option<IncomingCall>>,
|
||||
watch::Receiver<Option<IncomingCall>>,
|
||||
),
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
||||
impl Entity for ActiveCall {
|
||||
type Event = room::Event;
|
||||
}
|
||||
|
||||
impl ActiveCall {
|
||||
fn new(
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
Self {
|
||||
room: None,
|
||||
location: None,
|
||||
pending_invites: Default::default(),
|
||||
incoming_call: watch::channel(),
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(cx.handle(), Self::handle_incoming_call),
|
||||
client.add_message_handler(cx.handle(), Self::handle_call_canceled),
|
||||
],
|
||||
client,
|
||||
user_store,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_incoming_call(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
|
||||
let call = IncomingCall {
|
||||
room_id: envelope.payload.room_id,
|
||||
participants: user_store
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
||||
})
|
||||
.await?,
|
||||
caller: user_store
|
||||
.update(&mut cx, |user_store, cx| {
|
||||
user_store.get_user(envelope.payload.caller_user_id, cx)
|
||||
})
|
||||
.await?,
|
||||
initial_project: envelope.payload.initial_project,
|
||||
};
|
||||
this.update(&mut cx, |this, _| {
|
||||
*this.incoming_call.0.borrow_mut() = Some(call);
|
||||
});
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_call_canceled(
|
||||
this: ModelHandle<Self>,
|
||||
_: TypedEnvelope<proto::CallCanceled>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, _| {
|
||||
*this.incoming_call.0.borrow_mut() = None;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn global(cx: &AppContext) -> ModelHandle<Self> {
|
||||
cx.global::<ModelHandle<Self>>().clone()
|
||||
}
|
||||
|
||||
pub fn invite(
|
||||
&mut self,
|
||||
recipient_user_id: u64,
|
||||
initial_project: Option<ModelHandle<Project>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
if !self.pending_invites.insert(recipient_user_id) {
|
||||
return Task::ready(Err(anyhow!("user was already invited")));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let invite = async {
|
||||
if let Some(room) = this.read_with(&cx, |this, _| this.room().cloned()) {
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
Some(
|
||||
room.update(&mut cx, |room, cx| {
|
||||
room.share_project(initial_project, cx)
|
||||
})
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
room.update(&mut cx, |room, cx| {
|
||||
room.call(recipient_user_id, initial_project_id, cx)
|
||||
})
|
||||
.await?;
|
||||
} else {
|
||||
let room = cx
|
||||
.update(|cx| {
|
||||
Room::create(recipient_user_id, initial_project, client, user_store, cx)
|
||||
})
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.set_room(Some(room), cx))
|
||||
.await?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let result = invite.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_invites.remove(&recipient_user_id);
|
||||
cx.notify();
|
||||
});
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cancel_invite(
|
||||
&mut self,
|
||||
recipient_user_id: u64,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let room_id = if let Some(room) = self.room() {
|
||||
room.read(cx).id()
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("no active call")));
|
||||
};
|
||||
|
||||
let client = self.client.clone();
|
||||
cx.foreground().spawn(async move {
|
||||
client
|
||||
.request(proto::CancelCall {
|
||||
room_id,
|
||||
recipient_user_id,
|
||||
})
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn incoming(&self) -> watch::Receiver<Option<IncomingCall>> {
|
||||
self.incoming_call.1.clone()
|
||||
}
|
||||
|
||||
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.room.is_some() {
|
||||
return Task::ready(Err(anyhow!("cannot join while on another call")));
|
||||
}
|
||||
|
||||
let call = if let Some(call) = self.incoming_call.1.borrow().clone() {
|
||||
call
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("no incoming call")));
|
||||
};
|
||||
|
||||
let join = Room::join(&call, self.client.clone(), self.user_store.clone(), cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decline_incoming(&mut self) -> Result<()> {
|
||||
let call = self
|
||||
.incoming_call
|
||||
.0
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.ok_or_else(|| anyhow!("no incoming call"))?;
|
||||
self.client.send(proto::DeclineCall {
|
||||
room_id: call.room_id,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if let Some((room, _)) = self.room.take() {
|
||||
room.update(cx, |room, cx| room.leave(cx))?;
|
||||
cx.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("no active call")))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_location(
|
||||
&mut self,
|
||||
project: Option<&ModelHandle<Project>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
self.location = project.map(|project| project.downgrade());
|
||||
if let Some((room, _)) = self.room.as_ref() {
|
||||
room.update(cx, |room, cx| room.set_location(project, cx))
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn set_room(
|
||||
&mut self,
|
||||
room: Option<ModelHandle<Room>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
|
||||
cx.notify();
|
||||
if let Some(room) = room {
|
||||
if room.read(cx).status().is_offline() {
|
||||
self.room = None;
|
||||
Task::ready(Ok(()))
|
||||
} else {
|
||||
let subscriptions = vec![
|
||||
cx.observe(&room, |this, room, cx| {
|
||||
if room.read(cx).status().is_offline() {
|
||||
this.set_room(None, cx).detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}),
|
||||
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
||||
];
|
||||
self.room = Some((room.clone(), subscriptions));
|
||||
let location = self.location.and_then(|location| location.upgrade(cx));
|
||||
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
||||
}
|
||||
} else {
|
||||
self.room = None;
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room(&self) -> Option<&ModelHandle<Room>> {
|
||||
self.room.as_ref().map(|(room, _)| room)
|
||||
}
|
||||
|
||||
pub fn pending_invites(&self) -> &HashSet<u64> {
|
||||
&self.pending_invites
|
||||
}
|
||||
}
|
||||
56
crates/call/src/participant.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{proto, User};
|
||||
use collections::HashMap;
|
||||
use gpui::WeakModelHandle;
|
||||
pub use live_kit_client::Frame;
|
||||
use project::Project;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParticipantLocation {
|
||||
SharedProject { project_id: u64 },
|
||||
UnsharedProject,
|
||||
External,
|
||||
}
|
||||
|
||||
impl ParticipantLocation {
|
||||
pub fn from_proto(location: Option<proto::ParticipantLocation>) -> Result<Self> {
|
||||
match location.and_then(|l| l.variant) {
|
||||
Some(proto::participant_location::Variant::SharedProject(project)) => {
|
||||
Ok(Self::SharedProject {
|
||||
project_id: project.id,
|
||||
})
|
||||
}
|
||||
Some(proto::participant_location::Variant::UnsharedProject(_)) => {
|
||||
Ok(Self::UnsharedProject)
|
||||
}
|
||||
Some(proto::participant_location::Variant::External(_)) => Ok(Self::External),
|
||||
None => Err(anyhow!("participant location was not provided")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct LocalParticipant {
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub active_project: Option<WeakModelHandle<Project>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RemoteParticipant {
|
||||
pub user: Arc<User>,
|
||||
pub projects: Vec<proto::ParticipantProject>,
|
||||
pub location: ParticipantLocation,
|
||||
pub tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RemoteVideoTrack {
|
||||
pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
||||
}
|
||||
|
||||
impl RemoteVideoTrack {
|
||||
pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
|
||||
self.live_kit_track.frames()
|
||||
}
|
||||
}
|
||||
760
crates/call/src/room.rs
Normal file
@@ -0,0 +1,760 @@
|
||||
use crate::{
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
|
||||
IncomingCall,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||
use collections::{BTreeMap, HashSet};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
|
||||
use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate};
|
||||
use postage::stream::Stream;
|
||||
use project::Project;
|
||||
use std::{mem, os::unix::prelude::OsStrExt, sync::Arc};
|
||||
use util::{post_inc, ResultExt};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
ParticipantLocationChanged {
|
||||
participant_id: PeerId,
|
||||
},
|
||||
RemoteVideoTracksChanged {
|
||||
participant_id: PeerId,
|
||||
},
|
||||
RemoteProjectShared {
|
||||
owner: Arc<User>,
|
||||
project_id: u64,
|
||||
worktree_root_names: Vec<String>,
|
||||
},
|
||||
RemoteProjectUnshared {
|
||||
project_id: u64,
|
||||
},
|
||||
Left,
|
||||
}
|
||||
|
||||
pub struct Room {
|
||||
id: u64,
|
||||
live_kit: Option<LiveKitRoom>,
|
||||
status: RoomStatus,
|
||||
local_participant: LocalParticipant,
|
||||
remote_participants: BTreeMap<PeerId, RemoteParticipant>,
|
||||
pending_participants: Vec<Arc<User>>,
|
||||
participant_user_ids: HashSet<u64>,
|
||||
pending_call_count: usize,
|
||||
leave_when_empty: bool,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
subscriptions: Vec<client::Subscription>,
|
||||
pending_room_update: Option<Task<()>>,
|
||||
}
|
||||
|
||||
impl Entity for Room {
|
||||
type Event = Event;
|
||||
|
||||
fn release(&mut self, _: &mut MutableAppContext) {
|
||||
if self.status.is_online() {
|
||||
self.client.send(proto::LeaveRoom { id: self.id }).log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Room {
|
||||
fn new(
|
||||
id: u64,
|
||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let mut client_status = client.status();
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let is_connected = client_status
|
||||
.next()
|
||||
.await
|
||||
.map_or(false, |s| s.is_connected());
|
||||
// Even if we're initially connected, any future change of the status means we momentarily disconnected.
|
||||
if !is_connected || client_status.next().await.is_some() {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
let _ = this.update(&mut cx, |this, cx| this.leave(cx));
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||
let room = live_kit_client::Room::new();
|
||||
let mut status = room.status();
|
||||
// Consume the initial status of the room.
|
||||
let _ = status.try_recv();
|
||||
let _maintain_room = cx.spawn_weak(|this, mut cx| async move {
|
||||
while let Some(status) = status.next().await {
|
||||
let this = if let Some(this) = this.upgrade(&cx) {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
if status == live_kit_client::ConnectionState::Disconnected {
|
||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err());
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut track_changes = room.remote_video_track_updates();
|
||||
let _maintain_tracks = cx.spawn_weak(|this, mut cx| async move {
|
||||
while let Some(track_change) = track_changes.next().await {
|
||||
let this = if let Some(this) = this.upgrade(&cx) {
|
||||
this
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.remote_video_track_updated(track_change, cx).log_err()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cx.foreground()
|
||||
.spawn(room.connect(&connection_info.server_url, &connection_info.token))
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
Some(LiveKitRoom {
|
||||
room,
|
||||
screen_track: ScreenTrack::None,
|
||||
next_publish_id: 0,
|
||||
_maintain_room,
|
||||
_maintain_tracks,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
id,
|
||||
live_kit: live_kit_room,
|
||||
status: RoomStatus::Online,
|
||||
participant_user_ids: Default::default(),
|
||||
local_participant: Default::default(),
|
||||
remote_participants: Default::default(),
|
||||
pending_participants: Default::default(),
|
||||
pending_call_count: 0,
|
||||
subscriptions: vec![client.add_message_handler(cx.handle(), Self::handle_room_updated)],
|
||||
leave_when_empty: false,
|
||||
pending_room_update: None,
|
||||
client,
|
||||
user_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create(
|
||||
recipient_user_id: u64,
|
||||
initial_project: Option<ModelHandle<Project>>,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Task<Result<ModelHandle<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let response = client.request(proto::CreateRoom {}).await?;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.add_model(|cx| {
|
||||
Self::new(
|
||||
room_proto.id,
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||
let initial_project_id = room
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.share_project(initial_project.clone(), cx)
|
||||
})
|
||||
.await?;
|
||||
Some(initial_project_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match room
|
||||
.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.call(recipient_user_id, initial_project_id, cx)
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(room),
|
||||
Err(error) => Err(anyhow!("room creation failed: {:?}", error)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn join(
|
||||
call: &IncomingCall,
|
||||
client: Arc<Client>,
|
||||
user_store: ModelHandle<UserStore>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Task<Result<ModelHandle<Self>>> {
|
||||
let room_id = call.room_id;
|
||||
cx.spawn(|mut cx| async move {
|
||||
let response = client.request(proto::JoinRoom { id: room_id }).await?;
|
||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
let room = cx.add_model(|cx| {
|
||||
Self::new(
|
||||
room_id,
|
||||
response.live_kit_connection_info,
|
||||
client,
|
||||
user_store,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
room.update(&mut cx, |room, cx| {
|
||||
room.leave_when_empty = true;
|
||||
room.apply_room_update(room_proto, cx)?;
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
Ok(room)
|
||||
})
|
||||
}
|
||||
|
||||
fn should_leave(&self) -> bool {
|
||||
self.leave_when_empty
|
||||
&& self.pending_room_update.is_none()
|
||||
&& self.pending_participants.is_empty()
|
||||
&& self.remote_participants.is_empty()
|
||||
&& self.pending_call_count == 0
|
||||
}
|
||||
|
||||
pub(crate) fn leave(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if self.status.is_offline() {
|
||||
return Err(anyhow!("room is offline"));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
cx.emit(Event::Left);
|
||||
self.status = RoomStatus::Offline;
|
||||
self.remote_participants.clear();
|
||||
self.pending_participants.clear();
|
||||
self.participant_user_ids.clear();
|
||||
self.subscriptions.clear();
|
||||
self.live_kit.take();
|
||||
self.client.send(proto::LeaveRoom { id: self.id })?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn status(&self) -> RoomStatus {
|
||||
self.status
|
||||
}
|
||||
|
||||
pub fn local_participant(&self) -> &LocalParticipant {
|
||||
&self.local_participant
|
||||
}
|
||||
|
||||
pub fn remote_participants(&self) -> &BTreeMap<PeerId, RemoteParticipant> {
|
||||
&self.remote_participants
|
||||
}
|
||||
|
||||
pub fn pending_participants(&self) -> &[Arc<User>] {
|
||||
&self.pending_participants
|
||||
}
|
||||
|
||||
pub fn contains_participant(&self, user_id: u64) -> bool {
|
||||
self.participant_user_ids.contains(&user_id)
|
||||
}
|
||||
|
||||
async fn handle_room_updated(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::RoomUpdated>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let room = envelope
|
||||
.payload
|
||||
.room
|
||||
.ok_or_else(|| anyhow!("invalid room"))?;
|
||||
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))
|
||||
}
|
||||
|
||||
fn apply_room_update(
|
||||
&mut self,
|
||||
mut room: proto::Room,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
// Filter ourselves out from the room's participants.
|
||||
let local_participant_ix = room
|
||||
.participants
|
||||
.iter()
|
||||
.position(|participant| Some(participant.user_id) == self.client.user_id());
|
||||
let local_participant = local_participant_ix.map(|ix| room.participants.swap_remove(ix));
|
||||
|
||||
let remote_participant_user_ids = room
|
||||
.participants
|
||||
.iter()
|
||||
.map(|p| p.user_id)
|
||||
.collect::<Vec<_>>();
|
||||
let (remote_participants, pending_participants) =
|
||||
self.user_store.update(cx, move |user_store, cx| {
|
||||
(
|
||||
user_store.get_users(remote_participant_user_ids, cx),
|
||||
user_store.get_users(room.pending_participant_user_ids, cx),
|
||||
)
|
||||
});
|
||||
self.pending_room_update = Some(cx.spawn(|this, mut cx| async move {
|
||||
let (remote_participants, pending_participants) =
|
||||
futures::join!(remote_participants, pending_participants);
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.participant_user_ids.clear();
|
||||
|
||||
if let Some(participant) = local_participant {
|
||||
this.local_participant.projects = participant.projects;
|
||||
} else {
|
||||
this.local_participant.projects.clear();
|
||||
}
|
||||
|
||||
if let Some(participants) = remote_participants.log_err() {
|
||||
for (participant, user) in room.participants.into_iter().zip(participants) {
|
||||
let peer_id = PeerId(participant.peer_id);
|
||||
this.participant_user_ids.insert(participant.user_id);
|
||||
|
||||
let old_projects = this
|
||||
.remote_participants
|
||||
.get(&peer_id)
|
||||
.into_iter()
|
||||
.flat_map(|existing| &existing.projects)
|
||||
.map(|project| project.id)
|
||||
.collect::<HashSet<_>>();
|
||||
let new_projects = participant
|
||||
.projects
|
||||
.iter()
|
||||
.map(|project| project.id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for project in &participant.projects {
|
||||
if !old_projects.contains(&project.id) {
|
||||
cx.emit(Event::RemoteProjectShared {
|
||||
owner: user.clone(),
|
||||
project_id: project.id,
|
||||
worktree_root_names: project.worktree_root_names.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for unshared_project_id in old_projects.difference(&new_projects) {
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: *unshared_project_id,
|
||||
});
|
||||
}
|
||||
|
||||
let location = ParticipantLocation::from_proto(participant.location)
|
||||
.unwrap_or(ParticipantLocation::External);
|
||||
if let Some(remote_participant) = this.remote_participants.get_mut(&peer_id)
|
||||
{
|
||||
remote_participant.projects = participant.projects;
|
||||
if location != remote_participant.location {
|
||||
remote_participant.location = location;
|
||||
cx.emit(Event::ParticipantLocationChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.remote_participants.insert(
|
||||
peer_id,
|
||||
RemoteParticipant {
|
||||
user: user.clone(),
|
||||
projects: participant.projects,
|
||||
location,
|
||||
tracks: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(live_kit) = this.live_kit.as_ref() {
|
||||
let tracks =
|
||||
live_kit.room.remote_video_tracks(&peer_id.0.to_string());
|
||||
for track in tracks {
|
||||
this.remote_video_track_updated(
|
||||
RemoteVideoTrackUpdate::Subscribed(track),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.remote_participants.retain(|_, participant| {
|
||||
if this.participant_user_ids.contains(&participant.user.id) {
|
||||
true
|
||||
} else {
|
||||
for project in &participant.projects {
|
||||
cx.emit(Event::RemoteProjectUnshared {
|
||||
project_id: project.id,
|
||||
});
|
||||
}
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pending_participants) = pending_participants.log_err() {
|
||||
this.pending_participants = pending_participants;
|
||||
for participant in &this.pending_participants {
|
||||
this.participant_user_ids.insert(participant.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.pending_room_update.take();
|
||||
if this.should_leave() {
|
||||
let _ = this.leave(cx);
|
||||
}
|
||||
|
||||
this.check_invariants();
|
||||
cx.notify();
|
||||
});
|
||||
}));
|
||||
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remote_video_track_updated(
|
||||
&mut self,
|
||||
change: RemoteVideoTrackUpdate,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
match change {
|
||||
RemoteVideoTrackUpdate::Subscribed(track) => {
|
||||
let peer_id = PeerId(track.publisher_id().parse()?);
|
||||
let track_id = track.sid().to_string();
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&peer_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
participant.tracks.insert(
|
||||
track_id.clone(),
|
||||
Arc::new(RemoteVideoTrack {
|
||||
live_kit_track: track,
|
||||
}),
|
||||
);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
}
|
||||
RemoteVideoTrackUpdate::Unsubscribed {
|
||||
publisher_id,
|
||||
track_id,
|
||||
} => {
|
||||
let peer_id = PeerId(publisher_id.parse()?);
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&peer_id)
|
||||
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
|
||||
participant.tracks.remove(&track_id);
|
||||
cx.emit(Event::RemoteVideoTracksChanged {
|
||||
participant_id: peer_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_invariants(&self) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
for participant in self.remote_participants.values() {
|
||||
assert!(self.participant_user_ids.contains(&participant.user.id));
|
||||
}
|
||||
|
||||
for participant in &self.pending_participants {
|
||||
assert!(self.participant_user_ids.contains(&participant.id));
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
self.participant_user_ids.len(),
|
||||
self.remote_participants.len() + self.pending_participants.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn call(
|
||||
&mut self,
|
||||
recipient_user_id: u64,
|
||||
initial_project_id: Option<u64>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
self.pending_call_count += 1;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let result = client
|
||||
.request(proto::Call {
|
||||
room_id,
|
||||
recipient_user_id,
|
||||
initial_project_id,
|
||||
})
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_call_count -= 1;
|
||||
if this.should_leave() {
|
||||
this.leave(cx)?;
|
||||
}
|
||||
result
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn share_project(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
return Task::ready(Ok(project_id));
|
||||
}
|
||||
|
||||
let request = self.client.request(proto::ShareProject {
|
||||
room_id: self.id(),
|
||||
worktrees: project
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
proto::WorktreeMetadata {
|
||||
id: worktree.id().to_proto(),
|
||||
root_name: worktree.root_name().into(),
|
||||
visible: worktree.is_visible(),
|
||||
abs_path: worktree.abs_path().as_os_str().as_bytes().to_vec(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project
|
||||
.shared(response.project_id, cx)
|
||||
.detach_and_log_err(cx)
|
||||
});
|
||||
|
||||
// If the user's location is in this project, it changes from UnsharedProject to SharedProject.
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let active_project = this.local_participant.active_project.as_ref();
|
||||
if active_project.map_or(false, |location| *location == project) {
|
||||
this.set_location(Some(&project), cx)
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(response.project_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_location(
|
||||
&mut self,
|
||||
project: Option<&ModelHandle<Project>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
}
|
||||
|
||||
let client = self.client.clone();
|
||||
let room_id = self.id;
|
||||
let location = if let Some(project) = project {
|
||||
self.local_participant.active_project = Some(project.downgrade());
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
proto::participant_location::Variant::SharedProject(
|
||||
proto::participant_location::SharedProject { id: project_id },
|
||||
)
|
||||
} else {
|
||||
proto::participant_location::Variant::UnsharedProject(
|
||||
proto::participant_location::UnsharedProject {},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
self.local_participant.active_project = None;
|
||||
proto::participant_location::Variant::External(proto::participant_location::External {})
|
||||
};
|
||||
|
||||
cx.notify();
|
||||
cx.foreground().spawn(async move {
|
||||
client
|
||||
.request(proto::UpdateParticipantLocation {
|
||||
room_id,
|
||||
location: Some(proto::ParticipantLocation {
|
||||
variant: Some(location),
|
||||
}),
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_screen_sharing(&self) -> bool {
|
||||
self.live_kit.as_ref().map_or(false, |live_kit| {
|
||||
!matches!(live_kit.screen_track, ScreenTrack::None)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
} else if self.is_screen_sharing() {
|
||||
return Task::ready(Err(anyhow!("screen was already shared")));
|
||||
}
|
||||
|
||||
let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
let publish_id = post_inc(&mut live_kit.next_publish_id);
|
||||
live_kit.screen_track = ScreenTrack::Pending { publish_id };
|
||||
cx.notify();
|
||||
(live_kit.room.display_sources(), publish_id)
|
||||
} else {
|
||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||
};
|
||||
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
let publish_track = async {
|
||||
let displays = displays.await?;
|
||||
let display = displays
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("no display found"))?;
|
||||
let track = LocalVideoTrack::screen_share_for_display(&display);
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.read_with(&cx, |this, _| {
|
||||
this.live_kit
|
||||
.as_ref()
|
||||
.map(|live_kit| live_kit.room.publish_video_track(&track))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
||||
.await
|
||||
};
|
||||
|
||||
let publication = publish_track.await;
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
let live_kit = this
|
||||
.live_kit
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||
|
||||
let canceled = if let ScreenTrack::Pending {
|
||||
publish_id: cur_publish_id,
|
||||
} = &live_kit.screen_track
|
||||
{
|
||||
*cur_publish_id != publish_id
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
match publication {
|
||||
Ok(publication) => {
|
||||
if canceled {
|
||||
live_kit.room.unpublish_track(publication);
|
||||
} else {
|
||||
live_kit.screen_track = ScreenTrack::Published(publication);
|
||||
cx.notify();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
if canceled {
|
||||
Ok(())
|
||||
} else {
|
||||
live_kit.screen_track = ScreenTrack::None;
|
||||
cx.notify();
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unshare_screen(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if self.status.is_offline() {
|
||||
return Err(anyhow!("room is offline"));
|
||||
}
|
||||
|
||||
let live_kit = self
|
||||
.live_kit
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?;
|
||||
match mem::take(&mut live_kit.screen_track) {
|
||||
ScreenTrack::None => Err(anyhow!("screen was not shared")),
|
||||
ScreenTrack::Pending { .. } => {
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
ScreenTrack::Published(track) => {
|
||||
live_kit.room.unpublish_track(track);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_display_sources(&self, sources: Vec<live_kit_client::MacOSDisplay>) {
|
||||
self.live_kit
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.room
|
||||
.set_display_sources(sources);
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveKitRoom {
|
||||
room: Arc<live_kit_client::Room>,
|
||||
screen_track: ScreenTrack,
|
||||
next_publish_id: usize,
|
||||
_maintain_room: Task<()>,
|
||||
_maintain_tracks: Task<()>,
|
||||
}
|
||||
|
||||
enum ScreenTrack {
|
||||
None,
|
||||
Pending { publish_id: usize },
|
||||
Published(LocalTrackPublication),
|
||||
}
|
||||
|
||||
impl Default for ScreenTrack {
|
||||
fn default() -> Self {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum RoomStatus {
|
||||
Online,
|
||||
Offline,
|
||||
}
|
||||
|
||||
impl RoomStatus {
|
||||
pub fn is_offline(&self) -> bool {
|
||||
matches!(self, RoomStatus::Offline)
|
||||
}
|
||||
|
||||
pub fn is_online(&self) -> bool {
|
||||
matches!(self, RoomStatus::Online)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
[package]
|
||||
name = "capture"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "An example of screen capture"
|
||||
|
||||
[dependencies]
|
||||
gpui = { path = "../gpui" }
|
||||
live_kit = { path = "../live_kit" }
|
||||
media = { path = "../media" }
|
||||
|
||||
anyhow = "1.0.38"
|
||||
block = "0.1"
|
||||
bytes = "1.2"
|
||||
byteorder = "1.4"
|
||||
cocoa = "0.24"
|
||||
core-foundation = "0.9.3"
|
||||
core-graphics = "0.22.3"
|
||||
foreign-types = "0.3"
|
||||
futures = "0.3"
|
||||
hmac = "0.12"
|
||||
jwt = "0.16"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||
objc = "0.2"
|
||||
parking_lot = "0.11.1"
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
sha2 = "0.10"
|
||||
simplelog = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.59.2"
|
||||
@@ -1,7 +0,0 @@
|
||||
fn main() {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
|
||||
// Register exported Objective-C selectors, protocols, etc
|
||||
println!("cargo:rustc-link-arg=-Wl,-ObjC");
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use hmac::{Hmac, Mac};
|
||||
use jwt::SignWithKey;
|
||||
use serde::Serialize;
|
||||
use sha2::Sha256;
|
||||
use std::{
|
||||
ops::Add,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ClaimGrants<'a> {
|
||||
iss: &'a str,
|
||||
sub: &'a str,
|
||||
iat: u64,
|
||||
exp: u64,
|
||||
nbf: u64,
|
||||
jwtid: &'a str,
|
||||
video: VideoGrant<'a>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct VideoGrant<'a> {
|
||||
room_create: Option<bool>,
|
||||
room_join: Option<bool>,
|
||||
room_list: Option<bool>,
|
||||
room_record: Option<bool>,
|
||||
room_admin: Option<bool>,
|
||||
room: Option<&'a str>,
|
||||
can_publish: Option<bool>,
|
||||
can_subscribe: Option<bool>,
|
||||
can_publish_data: Option<bool>,
|
||||
hidden: Option<bool>,
|
||||
recorder: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn create_token(
|
||||
api_key: &str,
|
||||
secret_key: &str,
|
||||
room_name: &str,
|
||||
participant_name: &str,
|
||||
) -> Result<String> {
|
||||
let secret_key: Hmac<Sha256> = Hmac::new_from_slice(secret_key.as_bytes())?;
|
||||
|
||||
let now = SystemTime::now();
|
||||
|
||||
let claims = ClaimGrants {
|
||||
iss: api_key,
|
||||
sub: participant_name,
|
||||
iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||
exp: now
|
||||
.add(DEFAULT_TTL)
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
nbf: 0,
|
||||
jwtid: participant_name,
|
||||
video: VideoGrant {
|
||||
room: Some(room_name),
|
||||
room_join: Some(true),
|
||||
can_publish: Some(true),
|
||||
can_subscribe: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
Ok(claims.sign_with_key(&secret_key)?)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
mod live_kit_token;
|
||||
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::{Canvas, *},
|
||||
keymap::Binding,
|
||||
platform::current::Surface,
|
||||
Menu, MenuItem, ViewContext,
|
||||
};
|
||||
use live_kit::{LocalVideoTrack, Room};
|
||||
use log::LevelFilter;
|
||||
use media::core_video::CVImageBuffer;
|
||||
use postage::watch;
|
||||
use simplelog::SimpleLogger;
|
||||
use std::sync::Arc;
|
||||
|
||||
actions!(capture, [Quit]);
|
||||
|
||||
fn main() {
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
gpui::App::new(()).unwrap().run(|cx| {
|
||||
cx.platform().activate(true);
|
||||
cx.add_global_action(quit);
|
||||
|
||||
cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
|
||||
cx.set_menus(vec![Menu {
|
||||
name: "Zed",
|
||||
items: vec![MenuItem::Action {
|
||||
name: "Quit",
|
||||
action: Box::new(Quit),
|
||||
}],
|
||||
}]);
|
||||
|
||||
let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap();
|
||||
let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap();
|
||||
let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap();
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let user1_token = live_kit_token::create_token(
|
||||
&live_kit_key,
|
||||
&live_kit_secret,
|
||||
"test-room",
|
||||
"test-participant-1",
|
||||
)
|
||||
.unwrap();
|
||||
let room1 = Room::new();
|
||||
room1.connect(&live_kit_url, &user1_token).await.unwrap();
|
||||
|
||||
let user2_token = live_kit_token::create_token(
|
||||
&live_kit_key,
|
||||
&live_kit_secret,
|
||||
"test-room",
|
||||
"test-participant-2",
|
||||
)
|
||||
.unwrap();
|
||||
let room2 = Room::new();
|
||||
room2.connect(&live_kit_url, &user2_token).await.unwrap();
|
||||
cx.add_window(Default::default(), |cx| ScreenCaptureView::new(room2, cx));
|
||||
|
||||
let windows = live_kit::list_windows();
|
||||
let window = windows
|
||||
.iter()
|
||||
.find(|w| w.owner_name.as_deref() == Some("Safari"))
|
||||
.unwrap();
|
||||
let track = LocalVideoTrack::screen_share_for_window(window.id);
|
||||
room1.publish_video_track(&track).await.unwrap();
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
}
|
||||
|
||||
struct ScreenCaptureView {
|
||||
image_buffer: Option<CVImageBuffer>,
|
||||
_room: Arc<Room>,
|
||||
}
|
||||
|
||||
impl gpui::Entity for ScreenCaptureView {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl ScreenCaptureView {
|
||||
pub fn new(room: Arc<Room>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let mut remote_video_tracks = room.remote_video_tracks();
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
if let Some(video_track) = remote_video_tracks.next().await {
|
||||
let (mut frames_tx, mut frames_rx) = watch::channel_with(None);
|
||||
video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame));
|
||||
|
||||
while let Some(frame) = frames_rx.next().await {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.image_buffer = frame;
|
||||
cx.notify();
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
image_buffer: None,
|
||||
_room: room,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl gpui::View for ScreenCaptureView {
|
||||
fn ui_name() -> &'static str {
|
||||
"View"
|
||||
}
|
||||
|
||||
fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
|
||||
let image_buffer = self.image_buffer.clone();
|
||||
let canvas = Canvas::new(move |bounds, _, cx| {
|
||||
if let Some(image_buffer) = image_buffer.clone() {
|
||||
cx.scene.push_surface(Surface {
|
||||
bounds,
|
||||
image_buffer,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(image_buffer) = self.image_buffer.as_ref() {
|
||||
canvas
|
||||
.constrained()
|
||||
.with_width(image_buffer.width() as f32)
|
||||
.with_height(image_buffer.height() as f32)
|
||||
.aligned()
|
||||
.boxed()
|
||||
} else {
|
||||
canvas.boxed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
|
||||
cx.platform().quit();
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "chat_panel"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/chat_panel.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
client = { path = "../client" }
|
||||
editor = { path = "../editor" }
|
||||
gpui = { path = "../gpui" }
|
||||
menu = { path = "../menu" }
|
||||
settings = { path = "../settings" }
|
||||
theme = { path = "../theme" }
|
||||
util = { path = "../util" }
|
||||
workspace = { path = "../workspace" }
|
||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
@@ -1,433 +0,0 @@
|
||||
use client::{
|
||||
channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
|
||||
Client,
|
||||
};
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
platform::CursorStyle,
|
||||
views::{ItemType, Select, SelectStyle},
|
||||
AnyViewHandle, AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
|
||||
Subscription, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::Confirm;
|
||||
use postage::prelude::Stream;
|
||||
use settings::{Settings, SoftWrap};
|
||||
use std::sync::Arc;
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
const MESSAGE_LOADING_THRESHOLD: usize = 50;
|
||||
|
||||
pub struct ChatPanel {
|
||||
rpc: Arc<Client>,
|
||||
channel_list: ModelHandle<ChannelList>,
|
||||
active_channel: Option<(ModelHandle<Channel>, Subscription)>,
|
||||
message_list: ListState,
|
||||
input_editor: ViewHandle<Editor>,
|
||||
channel_select: ViewHandle<Select>,
|
||||
local_timezone: UtcOffset,
|
||||
_observe_status: Task<()>,
|
||||
}
|
||||
|
||||
pub enum Event {}
|
||||
|
||||
actions!(chat_panel, [LoadMoreMessages]);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(ChatPanel::send);
|
||||
cx.add_action(ChatPanel::load_more_messages);
|
||||
}
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(
|
||||
rpc: Arc<Client>,
|
||||
channel_list: ModelHandle<ChannelList>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let input_editor = cx.add_view(|cx| {
|
||||
let mut editor =
|
||||
Editor::auto_height(4, Some(|theme| theme.chat_panel.input_editor.clone()), cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor
|
||||
});
|
||||
let channel_select = cx.add_view(|cx| {
|
||||
let channel_list = channel_list.clone();
|
||||
Select::new(0, cx, {
|
||||
move |ix, item_type, is_hovered, cx| {
|
||||
Self::render_channel_name(
|
||||
&channel_list,
|
||||
ix,
|
||||
item_type,
|
||||
is_hovered,
|
||||
&cx.global::<Settings>().theme.chat_panel.channel_select,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.with_style(move |cx| {
|
||||
let theme = &cx.global::<Settings>().theme.chat_panel.channel_select;
|
||||
SelectStyle {
|
||||
header: theme.header.container,
|
||||
menu: theme.menu,
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let mut message_list = ListState::new(0, Orientation::Bottom, 1000., cx, {
|
||||
let this = cx.weak_handle();
|
||||
move |_, ix, cx| {
|
||||
let this = this.upgrade(cx).unwrap().read(cx);
|
||||
let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix);
|
||||
this.render_message(message, cx)
|
||||
}
|
||||
});
|
||||
message_list.set_scroll_handler(|visible_range, cx| {
|
||||
if visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
||||
cx.dispatch_action(LoadMoreMessages);
|
||||
}
|
||||
});
|
||||
let _observe_status = cx.spawn_weak(|this, mut cx| {
|
||||
let mut status = rpc.status();
|
||||
async move {
|
||||
while (status.recv().await).is_some() {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |_, cx| cx.notify());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut this = Self {
|
||||
rpc,
|
||||
channel_list,
|
||||
active_channel: Default::default(),
|
||||
message_list,
|
||||
input_editor,
|
||||
channel_select,
|
||||
local_timezone: cx.platform().local_timezone(),
|
||||
_observe_status,
|
||||
};
|
||||
|
||||
this.init_active_channel(cx);
|
||||
cx.observe(&this.channel_list, |this, _, cx| {
|
||||
this.init_active_channel(cx);
|
||||
})
|
||||
.detach();
|
||||
cx.observe(&this.channel_select, |this, channel_select, cx| {
|
||||
let selected_ix = channel_select.read(cx).selected_index();
|
||||
let selected_channel = this.channel_list.update(cx, |channel_list, cx| {
|
||||
let available_channels = channel_list.available_channels()?;
|
||||
let channel_id = available_channels.get(selected_ix)?.id;
|
||||
channel_list.get_channel(channel_id, cx)
|
||||
});
|
||||
if let Some(selected_channel) = selected_channel {
|
||||
this.set_active_channel(selected_channel, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let (active_channel, channel_count) = self.channel_list.update(cx, |list, cx| {
|
||||
let channel_count;
|
||||
let mut active_channel = None;
|
||||
|
||||
if let Some(available_channels) = list.available_channels() {
|
||||
channel_count = available_channels.len();
|
||||
if self.active_channel.is_none() {
|
||||
if let Some(channel_id) = available_channels.first().map(|channel| channel.id) {
|
||||
active_channel = list.get_channel(channel_id, cx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel_count = 0;
|
||||
}
|
||||
|
||||
(active_channel, channel_count)
|
||||
});
|
||||
|
||||
if let Some(active_channel) = active_channel {
|
||||
self.set_active_channel(active_channel, cx);
|
||||
} else {
|
||||
self.message_list.reset(0);
|
||||
self.active_channel = None;
|
||||
}
|
||||
|
||||
self.channel_select.update(cx, |select, cx| {
|
||||
select.set_item_count(channel_count, cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn set_active_channel(&mut self, channel: ModelHandle<Channel>, cx: &mut ViewContext<Self>) {
|
||||
if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) {
|
||||
{
|
||||
let channel = channel.read(cx);
|
||||
self.message_list.reset(channel.message_count());
|
||||
let placeholder = format!("Message #{}", channel.name());
|
||||
self.input_editor.update(cx, move |editor, cx| {
|
||||
editor.set_placeholder_text(placeholder, cx);
|
||||
});
|
||||
}
|
||||
let subscription = cx.subscribe(&channel, Self::channel_did_change);
|
||||
self.active_channel = Some((channel, subscription));
|
||||
}
|
||||
}
|
||||
|
||||
fn channel_did_change(
|
||||
&mut self,
|
||||
_: ModelHandle<Channel>,
|
||||
event: &ChannelEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
ChannelEvent::MessagesUpdated {
|
||||
old_range,
|
||||
new_count,
|
||||
} => {
|
||||
self.message_list.splice(old_range.clone(), *new_count);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_channel(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Container::new(ChildView::new(&self.channel_select).boxed())
|
||||
.with_style(theme.chat_panel.channel_select.container)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(self.render_active_channel_messages())
|
||||
.with_child(self.render_input_box(cx))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_active_channel_messages(&self) -> ElementBox {
|
||||
let messages = if self.active_channel.is_some() {
|
||||
List::new(self.message_list.clone()).boxed()
|
||||
} else {
|
||||
Empty::new().boxed()
|
||||
};
|
||||
|
||||
FlexItem::new(messages).flex(1., true).boxed()
|
||||
}
|
||||
|
||||
fn render_message(&self, message: &ChannelMessage, cx: &AppContext) -> ElementBox {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let settings = cx.global::<Settings>();
|
||||
let theme = if message.is_pending() {
|
||||
&settings.theme.chat_panel.pending_message
|
||||
} else {
|
||||
&settings.theme.chat_panel.message
|
||||
};
|
||||
|
||||
Container::new(
|
||||
Flex::column()
|
||||
.with_child(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Container::new(
|
||||
Label::new(
|
||||
message.sender.github_login.clone(),
|
||||
theme.sender.text.clone(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.sender.container)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
Container::new(
|
||||
Label::new(
|
||||
format_timestamp(message.timestamp, now, self.local_timezone),
|
||||
theme.timestamp.text.clone(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.timestamp.container)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(Text::new(message.body.clone(), theme.body.clone()).boxed())
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.container)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_input_box(&self, cx: &AppContext) -> ElementBox {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
Container::new(ChildView::new(&self.input_editor).boxed())
|
||||
.with_style(theme.chat_panel.input_editor.container)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_channel_name(
|
||||
channel_list: &ModelHandle<ChannelList>,
|
||||
ix: usize,
|
||||
item_type: ItemType,
|
||||
is_hovered: bool,
|
||||
theme: &theme::ChannelSelect,
|
||||
cx: &AppContext,
|
||||
) -> ElementBox {
|
||||
let channel = &channel_list.read(cx).available_channels().unwrap()[ix];
|
||||
let theme = match (item_type, is_hovered) {
|
||||
(ItemType::Header, _) => &theme.header,
|
||||
(ItemType::Selected, false) => &theme.active_item,
|
||||
(ItemType::Selected, true) => &theme.hovered_active_item,
|
||||
(ItemType::Unselected, false) => &theme.item,
|
||||
(ItemType::Unselected, true) => &theme.hovered_item,
|
||||
};
|
||||
Container::new(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Container::new(Label::new("#".to_string(), theme.hash.text.clone()).boxed())
|
||||
.with_style(theme.hash.container)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(Label::new(channel.name.clone(), theme.name.clone()).boxed())
|
||||
.boxed(),
|
||||
)
|
||||
.with_style(theme.container)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn render_sign_in_prompt(&self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let theme = cx.global::<Settings>().theme.clone();
|
||||
let rpc = self.rpc.clone();
|
||||
let this = cx.handle();
|
||||
|
||||
enum SignInPromptLabel {}
|
||||
|
||||
Align::new(
|
||||
MouseEventHandler::<SignInPromptLabel>::new(0, cx, |mouse_state, _| {
|
||||
Label::new(
|
||||
"Sign in to use chat".to_string(),
|
||||
if mouse_state.hovered {
|
||||
theme.chat_panel.hovered_sign_in_prompt.clone()
|
||||
} else {
|
||||
theme.chat_panel.sign_in_prompt.clone()
|
||||
},
|
||||
)
|
||||
.boxed()
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
let rpc = rpc.clone();
|
||||
let this = this.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
if rpc
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.log_err()
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
cx.update(|cx| {
|
||||
if let Some(this) = this.upgrade(cx) {
|
||||
if this.is_focused(cx) {
|
||||
this.update(cx, |this, cx| cx.focus(&this.input_editor));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
if let Some((channel, _)) = self.active_channel.as_ref() {
|
||||
let body = self.input_editor.update(cx, |editor, cx| {
|
||||
let body = editor.text(cx);
|
||||
editor.clear(cx);
|
||||
body
|
||||
});
|
||||
|
||||
if let Some(task) = channel
|
||||
.update(cx, |channel, cx| channel.send_message(body, cx))
|
||||
.log_err()
|
||||
{
|
||||
task.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext<Self>) {
|
||||
if let Some((channel, _)) = self.active_channel.as_ref() {
|
||||
channel.update(cx, |channel, cx| {
|
||||
channel.load_more_messages(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ChatPanel {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
impl View for ChatPanel {
|
||||
fn ui_name() -> &'static str {
|
||||
"ChatPanel"
|
||||
}
|
||||
|
||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||
let element = if self.rpc.user_id().is_some() {
|
||||
self.render_channel(cx)
|
||||
} else {
|
||||
self.render_sign_in_prompt(cx)
|
||||
};
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
ConstrainedBox::new(
|
||||
Container::new(element)
|
||||
.with_style(theme.chat_panel.container)
|
||||
.boxed(),
|
||||
)
|
||||
.with_min_width(150.)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
if matches!(
|
||||
*self.rpc.status().borrow(),
|
||||
client::Status::Connected { .. }
|
||||
) {
|
||||
cx.focus(&self.input_editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_timestamp(
|
||||
mut timestamp: OffsetDateTime,
|
||||
mut now: OffsetDateTime,
|
||||
local_timezone: UtcOffset,
|
||||
) -> String {
|
||||
timestamp = timestamp.to_offset(local_timezone);
|
||||
now = now.to_offset(local_timezone);
|
||||
|
||||
let today = now.date();
|
||||
let date = timestamp.date();
|
||||
let mut hour = timestamp.hour();
|
||||
let mut part = "am";
|
||||
if hour > 12 {
|
||||
hour -= 12;
|
||||
part = "pm";
|
||||
}
|
||||
if date == today {
|
||||
format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
} else if date.next_day() == Some(today) {
|
||||
format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
|
||||
} else {
|
||||
format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,11 @@ tiny_http = "0.8"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
url = "2.2"
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
settings = { path = "../settings" }
|
||||
tempfile = "3"
|
||||
|
||||
[dev-dependencies]
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
|
||||
@@ -530,7 +530,7 @@ impl ChannelMessage {
|
||||
) -> Result<Self> {
|
||||
let sender = user_store
|
||||
.update(cx, |user_store, cx| {
|
||||
user_store.fetch_user(message.sender_id, cx)
|
||||
user_store.get_user(message.sender_id, cx)
|
||||
})
|
||||
.await?;
|
||||
Ok(ChannelMessage {
|
||||
|
||||
@@ -13,11 +13,13 @@ use async_tungstenite::tungstenite::{
|
||||
http::{Request, StatusCode},
|
||||
};
|
||||
use db::Db;
|
||||
use futures::{future::LocalBoxFuture, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use futures::{future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryStreamExt};
|
||||
use gpui::{
|
||||
actions, serde_json::Value, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle,
|
||||
AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
|
||||
MutableAppContext, Task, View, ViewContext, ViewHandle,
|
||||
actions,
|
||||
serde_json::{self, Value},
|
||||
AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
|
||||
AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
};
|
||||
use http::HttpClient;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -25,6 +27,8 @@ use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, RequestMessage};
|
||||
use serde::Deserialize;
|
||||
use settings::ReleaseChannel;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::HashMap,
|
||||
@@ -50,9 +54,14 @@ lazy_static! {
|
||||
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
}
|
||||
|
||||
pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
|
||||
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
|
||||
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
actions!(client, [Authenticate]);
|
||||
|
||||
@@ -141,11 +150,16 @@ pub enum Status {
|
||||
Authenticating,
|
||||
Connecting,
|
||||
ConnectionError,
|
||||
Connected { connection_id: ConnectionId },
|
||||
Connected {
|
||||
peer_id: PeerId,
|
||||
connection_id: ConnectionId,
|
||||
},
|
||||
ConnectionLost,
|
||||
Reauthenticating,
|
||||
Reconnecting,
|
||||
ReconnectionError { next_reconnection: Instant },
|
||||
ReconnectionError {
|
||||
next_reconnection: Instant,
|
||||
},
|
||||
}
|
||||
|
||||
impl Status {
|
||||
@@ -312,6 +326,14 @@ impl Client {
|
||||
.map(|credentials| credentials.user_id)
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> Option<PeerId> {
|
||||
if let Status::Connected { peer_id, .. } = &*self.status().borrow() {
|
||||
Some(*peer_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> watch::Receiver<Status> {
|
||||
self.state.read().status.1.clone()
|
||||
}
|
||||
@@ -330,7 +352,7 @@ impl Client {
|
||||
let reconnect_interval = state.reconnect_interval;
|
||||
state._reconnect_task = Some(cx.spawn(|cx| async move {
|
||||
let mut rng = StdRng::from_entropy();
|
||||
let mut delay = Duration::from_millis(100);
|
||||
let mut delay = INITIAL_RECONNECTION_DELAY;
|
||||
while let Err(error) = this.authenticate_and_connect(true, &cx).await {
|
||||
log::error!("failed to connect {}", error);
|
||||
if matches!(*this.status().borrow(), Status::ConnectionError) {
|
||||
@@ -351,7 +373,7 @@ impl Client {
|
||||
}));
|
||||
}
|
||||
Status::SignedOut | Status::UpgradeRequired => {
|
||||
self.telemetry.set_metrics_id(None);
|
||||
self.telemetry.set_authenticated_user_info(None, false);
|
||||
state._reconnect_task.take();
|
||||
}
|
||||
_ => {}
|
||||
@@ -434,6 +456,29 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_request_handler<M, E, H, F>(
|
||||
self: &Arc<Self>,
|
||||
model: ModelHandle<E>,
|
||||
handler: H,
|
||||
) -> Subscription
|
||||
where
|
||||
M: RequestMessage,
|
||||
E: Entity,
|
||||
H: 'static
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Fn(ModelHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
|
||||
F: 'static + Future<Output = Result<M::Response>>,
|
||||
{
|
||||
self.add_message_handler(model, move |handle, envelope, this, cx| {
|
||||
Self::respond_to_request(
|
||||
envelope.receipt(),
|
||||
handler(handle, envelope, this.clone(), cx),
|
||||
this,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_view_message_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
|
||||
where
|
||||
M: EntityMessage,
|
||||
@@ -638,46 +683,104 @@ impl Client {
|
||||
self.set_status(Status::Reconnecting, cx);
|
||||
}
|
||||
|
||||
match self.establish_connection(&credentials, cx).await {
|
||||
Ok(conn) => {
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
||||
write_credentials_to_keychain(&credentials, cx).log_err();
|
||||
}
|
||||
self.set_connection(conn, cx).await;
|
||||
Ok(())
|
||||
}
|
||||
Err(EstablishConnectionError::Unauthorized) => {
|
||||
self.state.write().credentials.take();
|
||||
if read_from_keychain {
|
||||
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
||||
self.set_status(Status::SignedOut, cx);
|
||||
self.authenticate_and_connect(false, cx).await
|
||||
} else {
|
||||
self.set_status(Status::ConnectionError, cx);
|
||||
Err(EstablishConnectionError::Unauthorized)?
|
||||
let mut timeout = cx.background().timer(CONNECTION_TIMEOUT).fuse();
|
||||
futures::select_biased! {
|
||||
connection = self.establish_connection(&credentials, cx).fuse() => {
|
||||
match connection {
|
||||
Ok(conn) => {
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
||||
write_credentials_to_keychain(&credentials, cx).log_err();
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
result = self.set_connection(conn, cx).fuse() => result,
|
||||
_ = timeout => {
|
||||
self.set_status(Status::ConnectionError, cx);
|
||||
Err(anyhow!("timed out waiting on hello message from server"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(EstablishConnectionError::Unauthorized) => {
|
||||
self.state.write().credentials.take();
|
||||
if read_from_keychain {
|
||||
cx.platform().delete_credentials(&ZED_SERVER_URL).log_err();
|
||||
self.set_status(Status::SignedOut, cx);
|
||||
self.authenticate_and_connect(false, cx).await
|
||||
} else {
|
||||
self.set_status(Status::ConnectionError, cx);
|
||||
Err(EstablishConnectionError::Unauthorized)?
|
||||
}
|
||||
}
|
||||
Err(EstablishConnectionError::UpgradeRequired) => {
|
||||
self.set_status(Status::UpgradeRequired, cx);
|
||||
Err(EstablishConnectionError::UpgradeRequired)?
|
||||
}
|
||||
Err(error) => {
|
||||
self.set_status(Status::ConnectionError, cx);
|
||||
Err(error)?
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(EstablishConnectionError::UpgradeRequired) => {
|
||||
self.set_status(Status::UpgradeRequired, cx);
|
||||
Err(EstablishConnectionError::UpgradeRequired)?
|
||||
}
|
||||
Err(error) => {
|
||||
_ = &mut timeout => {
|
||||
self.set_status(Status::ConnectionError, cx);
|
||||
Err(error)?
|
||||
Err(anyhow!("timed out trying to establish connection"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_connection(self: &Arc<Self>, conn: Connection, cx: &AsyncAppContext) {
|
||||
async fn set_connection(
|
||||
self: &Arc<Self>,
|
||||
conn: Connection,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let executor = cx.background();
|
||||
log::info!("add connection to peer");
|
||||
let (connection_id, handle_io, mut incoming) = self
|
||||
.peer
|
||||
.add_connection(conn, move |duration| executor.timer(duration))
|
||||
.await;
|
||||
log::info!("set status to connected {}", connection_id);
|
||||
self.set_status(Status::Connected { connection_id }, cx);
|
||||
.add_connection(conn, move |duration| executor.timer(duration));
|
||||
let handle_io = cx.background().spawn(handle_io);
|
||||
|
||||
let peer_id = async {
|
||||
log::info!("waiting for server hello");
|
||||
let message = incoming
|
||||
.next()
|
||||
.await
|
||||
.ok_or_else(|| anyhow!("no hello message received"))?;
|
||||
log::info!("got server hello");
|
||||
let hello_message_type_name = message.payload_type_name().to_string();
|
||||
let hello = message
|
||||
.into_any()
|
||||
.downcast::<TypedEnvelope<proto::Hello>>()
|
||||
.map_err(|_| {
|
||||
anyhow!(
|
||||
"invalid hello message received: {:?}",
|
||||
hello_message_type_name
|
||||
)
|
||||
})?;
|
||||
Ok(PeerId(hello.payload.peer_id))
|
||||
};
|
||||
|
||||
let peer_id = match peer_id.await {
|
||||
Ok(peer_id) => peer_id,
|
||||
Err(error) => {
|
||||
self.peer.disconnect(connection_id);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"set status to connected (connection id: {}, peer id: {})",
|
||||
connection_id,
|
||||
peer_id
|
||||
);
|
||||
self.set_status(
|
||||
Status::Connected {
|
||||
peer_id,
|
||||
connection_id,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
cx.foreground()
|
||||
.spawn({
|
||||
let cx = cx.clone();
|
||||
@@ -726,11 +829,14 @@ impl Client {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(handler) = state.message_handlers.get(&payload_type_id).cloned()
|
||||
{
|
||||
drop(state); // Avoid deadlocks if the handler interacts with rpc::Client
|
||||
let future = handler(model, message, &this, cx.clone());
|
||||
let handler = state.message_handlers.get(&payload_type_id).cloned();
|
||||
// Dropping the state prevents deadlocks if the handler interacts with rpc::Client.
|
||||
// It also ensures we don't hold the lock while yielding back to the executor, as
|
||||
// that might cause the executor thread driving this future to block indefinitely.
|
||||
drop(state);
|
||||
|
||||
if let Some(handler) = handler {
|
||||
let future = handler(model, message, &this, cx.clone());
|
||||
let client_id = this.id;
|
||||
log::debug!(
|
||||
"rpc message received. client_id:{}, message_id:{}, sender_id:{:?}, type:{}",
|
||||
@@ -775,14 +881,18 @@ impl Client {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let handle_io = cx.background().spawn(handle_io);
|
||||
let this = self.clone();
|
||||
let cx = cx.clone();
|
||||
cx.foreground()
|
||||
.spawn(async move {
|
||||
match handle_io.await {
|
||||
Ok(()) => {
|
||||
if *this.status().borrow() == (Status::Connected { connection_id }) {
|
||||
if *this.status().borrow()
|
||||
== (Status::Connected {
|
||||
connection_id,
|
||||
peer_id,
|
||||
})
|
||||
{
|
||||
this.set_status(Status::SignedOut, &cx);
|
||||
}
|
||||
}
|
||||
@@ -793,6 +903,8 @@ impl Client {
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn authenticate(self: &Arc<Self>, cx: &AsyncAppContext) -> Task<Result<Credentials>> {
|
||||
@@ -817,11 +929,51 @@ impl Client {
|
||||
self.establish_websocket_connection(credentials, cx)
|
||||
}
|
||||
|
||||
async fn get_rpc_url(http: Arc<dyn HttpClient>, is_preview: bool) -> Result<Url> {
|
||||
let preview_param = if is_preview { "?preview=1" } else { "" };
|
||||
let url = format!("{}/rpc{preview_param}", *ZED_SERVER_URL);
|
||||
let response = http.get(&url, Default::default(), false).await?;
|
||||
|
||||
// Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
|
||||
// The website's /rpc endpoint redirects to a collab server's /rpc endpoint,
|
||||
// which requires authorization via an HTTP header.
|
||||
//
|
||||
// For testing purposes, ZED_SERVER_URL can also set to the direct URL of
|
||||
// of a collab server. In that case, a request to the /rpc endpoint will
|
||||
// return an 'unauthorized' response.
|
||||
let collab_url = if response.status().is_redirection() {
|
||||
response
|
||||
.headers()
|
||||
.get("Location")
|
||||
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
||||
.to_str()
|
||||
.map_err(EstablishConnectionError::other)?
|
||||
.to_string()
|
||||
} else if response.status() == StatusCode::UNAUTHORIZED {
|
||||
url
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"unexpected /rpc response status {}",
|
||||
response.status()
|
||||
))?
|
||||
};
|
||||
|
||||
Url::parse(&collab_url).context("invalid rpc url")
|
||||
}
|
||||
|
||||
fn establish_websocket_connection(
|
||||
self: &Arc<Self>,
|
||||
credentials: &Credentials,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Task<Result<Connection, EstablishConnectionError>> {
|
||||
let is_preview = cx.read(|cx| {
|
||||
if cx.has_global::<ReleaseChannel>() {
|
||||
*cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
let request = Request::builder()
|
||||
.header(
|
||||
"Authorization",
|
||||
@@ -831,28 +983,7 @@ impl Client {
|
||||
|
||||
let http = self.http.clone();
|
||||
cx.background().spawn(async move {
|
||||
let mut rpc_url = format!("{}/rpc", *ZED_SERVER_URL);
|
||||
let rpc_response = http.get(&rpc_url, Default::default(), false).await?;
|
||||
if rpc_response.status().is_redirection() {
|
||||
rpc_url = rpc_response
|
||||
.headers()
|
||||
.get("Location")
|
||||
.ok_or_else(|| anyhow!("missing location header in /rpc response"))?
|
||||
.to_str()
|
||||
.map_err(EstablishConnectionError::other)?
|
||||
.to_string();
|
||||
}
|
||||
// Until we switch the zed.dev domain to point to the new Next.js app, there
|
||||
// will be no redirect required, and the app will connect directly to
|
||||
// wss://zed.dev/rpc.
|
||||
else if rpc_response.status() != StatusCode::UPGRADE_REQUIRED {
|
||||
Err(anyhow!(
|
||||
"unexpected /rpc response status {}",
|
||||
rpc_response.status()
|
||||
))?
|
||||
}
|
||||
|
||||
let mut rpc_url = Url::parse(&rpc_url).context("invalid rpc url")?;
|
||||
let mut rpc_url = Self::get_rpc_url(http, is_preview).await?;
|
||||
let rpc_host = rpc_url
|
||||
.host_str()
|
||||
.zip(rpc_url.port_or_known_default())
|
||||
@@ -895,6 +1026,7 @@ impl Client {
|
||||
let platform = cx.platform();
|
||||
let executor = cx.background();
|
||||
let telemetry = self.telemetry.clone();
|
||||
let http = self.http.clone();
|
||||
executor.clone().spawn(async move {
|
||||
// Generate a pair of asymmetric encryption keys. The public key will be used by the
|
||||
// zed server to encrypt the user's access token, so that it can'be intercepted by
|
||||
@@ -904,6 +1036,10 @@ impl Client {
|
||||
let public_key_string =
|
||||
String::try_from(public_key).expect("failed to serialize public key for auth");
|
||||
|
||||
if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) {
|
||||
return Self::authenticate_as_admin(http, login.clone(), token.clone()).await;
|
||||
}
|
||||
|
||||
// Start an HTTP server to receive the redirect from Zed's sign-in page.
|
||||
let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port");
|
||||
let port = server.server_addr().port();
|
||||
@@ -982,6 +1118,50 @@ impl Client {
|
||||
})
|
||||
}
|
||||
|
||||
async fn authenticate_as_admin(
|
||||
http: Arc<dyn HttpClient>,
|
||||
login: String,
|
||||
mut api_token: String,
|
||||
) -> Result<Credentials> {
|
||||
#[derive(Deserialize)]
|
||||
struct AuthenticatedUserResponse {
|
||||
user: User,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct User {
|
||||
id: u64,
|
||||
}
|
||||
|
||||
// Use the collab server's admin API to retrieve the id
|
||||
// of the impersonated user.
|
||||
let mut url = Self::get_rpc_url(http.clone(), false).await?;
|
||||
url.set_path("/user");
|
||||
url.set_query(Some(&format!("github_login={login}")));
|
||||
let request = Request::get(url.as_str())
|
||||
.header("Authorization", format!("token {api_token}"))
|
||||
.body("".into())?;
|
||||
|
||||
let mut response = http.send(request).await?;
|
||||
let mut body = String::new();
|
||||
response.body_mut().read_to_string(&mut body).await?;
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"admin user request failed {} - {}",
|
||||
response.status().as_u16(),
|
||||
body,
|
||||
))?;
|
||||
}
|
||||
let response: AuthenticatedUserResponse = serde_json::from_str(&body)?;
|
||||
|
||||
// Use the admin API token to authenticate as the impersonated user.
|
||||
api_token.insert_str(0, "ADMIN_TOKEN:");
|
||||
Ok(Credentials {
|
||||
user_id: response.user.id,
|
||||
access_token: api_token,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
|
||||
let conn_id = self.connection_id()?;
|
||||
self.peer.disconnect(conn_id);
|
||||
@@ -1040,12 +1220,12 @@ impl Client {
|
||||
self.peer.respond_with_error(receipt, error)
|
||||
}
|
||||
|
||||
pub fn start_telemetry(&self, db: Arc<Db>) {
|
||||
self.telemetry.start(db);
|
||||
pub fn start_telemetry(&self, db: Db) {
|
||||
self.telemetry.start(db.clone());
|
||||
}
|
||||
|
||||
pub fn report_event(&self, kind: &str, properties: Value) {
|
||||
self.telemetry.report_event(kind, properties)
|
||||
self.telemetry.report_event(kind, properties.clone());
|
||||
}
|
||||
|
||||
pub fn telemetry_log_file_path(&self) -> Option<PathBuf> {
|
||||
@@ -1146,6 +1326,76 @@ mod tests {
|
||||
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
deterministic.forbid_parking();
|
||||
|
||||
let user_id = 5;
|
||||
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
|
||||
let mut status = client.status();
|
||||
|
||||
// Time out when client tries to connect.
|
||||
client.override_authenticate(move |cx| {
|
||||
cx.foreground().spawn(async move {
|
||||
Ok(Credentials {
|
||||
user_id,
|
||||
access_token: "token".into(),
|
||||
})
|
||||
})
|
||||
});
|
||||
client.override_establish_connection(|_, cx| {
|
||||
cx.foreground().spawn(async move {
|
||||
future::pending::<()>().await;
|
||||
unreachable!()
|
||||
})
|
||||
});
|
||||
let auth_and_connect = cx.spawn({
|
||||
let client = client.clone();
|
||||
|cx| async move { client.authenticate_and_connect(false, &cx).await }
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
assert!(matches!(status.next().await, Some(Status::Connecting)));
|
||||
|
||||
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||
assert!(matches!(
|
||||
status.next().await,
|
||||
Some(Status::ConnectionError { .. })
|
||||
));
|
||||
auth_and_connect.await.unwrap_err();
|
||||
|
||||
// Allow the connection to be established.
|
||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||
assert!(matches!(
|
||||
status.next().await,
|
||||
Some(Status::Connected { .. })
|
||||
));
|
||||
|
||||
// Disconnect client.
|
||||
server.forbid_connections();
|
||||
server.disconnect();
|
||||
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
|
||||
|
||||
// Time out when re-establishing the connection.
|
||||
server.allow_connections();
|
||||
client.override_establish_connection(|_, cx| {
|
||||
cx.foreground().spawn(async move {
|
||||
future::pending::<()>().await;
|
||||
unreachable!()
|
||||
})
|
||||
});
|
||||
deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
|
||||
assert!(matches!(
|
||||
status.next().await,
|
||||
Some(Status::Reconnecting { .. })
|
||||
));
|
||||
|
||||
deterministic.advance_clock(CONNECTION_TIMEOUT);
|
||||
assert!(matches!(
|
||||
status.next().await,
|
||||
Some(Status::ReconnectionError { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_authenticating_more_than_once(
|
||||
cx: &mut TestAppContext,
|
||||
|
||||
@@ -9,6 +9,8 @@ use isahc::Request;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use settings::ReleaseChannel;
|
||||
use std::{
|
||||
io::Write,
|
||||
mem,
|
||||
@@ -23,7 +25,6 @@ use uuid::Uuid;
|
||||
pub struct Telemetry {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
executor: Arc<Background>,
|
||||
session_id: u128,
|
||||
state: Mutex<TelemetryState>,
|
||||
}
|
||||
|
||||
@@ -32,50 +33,62 @@ struct TelemetryState {
|
||||
metrics_id: Option<Arc<str>>,
|
||||
device_id: Option<Arc<str>>,
|
||||
app_version: Option<Arc<str>>,
|
||||
release_channel: Option<&'static str>,
|
||||
os_version: Option<Arc<str>>,
|
||||
os_name: &'static str,
|
||||
queue: Vec<AmplitudeEvent>,
|
||||
queue: Vec<MixpanelEvent>,
|
||||
next_event_id: usize,
|
||||
flush_task: Option<Task<()>>,
|
||||
log_file: Option<NamedTempFile>,
|
||||
}
|
||||
|
||||
const AMPLITUDE_EVENTS_URL: &'static str = "https://api2.amplitude.com/batch";
|
||||
const MIXPANEL_EVENTS_URL: &'static str = "https://api.mixpanel.com/track";
|
||||
const MIXPANEL_ENGAGE_URL: &'static str = "https://api.mixpanel.com/engage#profile-set";
|
||||
|
||||
lazy_static! {
|
||||
static ref AMPLITUDE_API_KEY: Option<String> = std::env::var("ZED_AMPLITUDE_API_KEY")
|
||||
static ref MIXPANEL_TOKEN: Option<String> = std::env::var("ZED_MIXPANEL_TOKEN")
|
||||
.ok()
|
||||
.or_else(|| option_env!("ZED_AMPLITUDE_API_KEY").map(|key| key.to_string()));
|
||||
.or_else(|| option_env!("ZED_MIXPANEL_TOKEN").map(|key| key.to_string()));
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AmplitudeEventBatch {
|
||||
api_key: &'static str,
|
||||
events: Vec<AmplitudeEvent>,
|
||||
options: AmplitudeEventBatchOptions,
|
||||
#[derive(Serialize, Debug)]
|
||||
struct MixpanelEvent {
|
||||
event: String,
|
||||
properties: MixpanelEventProperties,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AmplitudeEventBatchOptions {
|
||||
min_id_length: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AmplitudeEvent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
user_id: Option<Arc<str>>,
|
||||
device_id: Option<Arc<str>>,
|
||||
event_type: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
event_properties: Option<Map<String, Value>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
user_properties: Option<Map<String, Value>>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
app_version: Option<Arc<str>>,
|
||||
event_id: usize,
|
||||
session_id: u128,
|
||||
#[derive(Serialize, Debug)]
|
||||
struct MixpanelEventProperties {
|
||||
// Mixpanel required fields
|
||||
#[serde(skip_serializing_if = "str::is_empty")]
|
||||
token: &'static str,
|
||||
time: u128,
|
||||
distinct_id: Option<Arc<str>>,
|
||||
#[serde(rename = "$insert_id")]
|
||||
insert_id: usize,
|
||||
// Custom fields
|
||||
#[serde(skip_serializing_if = "Option::is_none", flatten)]
|
||||
event_properties: Option<Map<String, Value>>,
|
||||
#[serde(rename = "OS Name")]
|
||||
os_name: &'static str,
|
||||
#[serde(rename = "OS Version")]
|
||||
os_version: Option<Arc<str>>,
|
||||
#[serde(rename = "Release Channel")]
|
||||
release_channel: Option<&'static str>,
|
||||
#[serde(rename = "App Version")]
|
||||
app_version: Option<Arc<str>>,
|
||||
#[serde(rename = "Signed In")]
|
||||
signed_in: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MixpanelEngageRequest {
|
||||
#[serde(rename = "$token")]
|
||||
token: &'static str,
|
||||
#[serde(rename = "$distinct_id")]
|
||||
distinct_id: Arc<str>,
|
||||
#[serde(rename = "$set")]
|
||||
set: Value,
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -93,33 +106,29 @@ const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
impl Telemetry {
|
||||
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
||||
let platform = cx.platform();
|
||||
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
||||
Some(cx.global::<ReleaseChannel>().name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let this = Arc::new(Self {
|
||||
http_client: client,
|
||||
executor: cx.background().clone(),
|
||||
session_id: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
state: Mutex::new(TelemetryState {
|
||||
os_version: platform
|
||||
.os_version()
|
||||
.log_err()
|
||||
.map(|v| v.to_string().into()),
|
||||
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
|
||||
os_name: platform.os_name().into(),
|
||||
app_version: platform
|
||||
.app_version()
|
||||
.log_err()
|
||||
.map(|v| v.to_string().into()),
|
||||
app_version: platform.app_version().ok().map(|v| v.to_string().into()),
|
||||
release_channel,
|
||||
device_id: None,
|
||||
metrics_id: None,
|
||||
queue: Default::default(),
|
||||
flush_task: Default::default(),
|
||||
next_event_id: 0,
|
||||
log_file: None,
|
||||
metrics_id: None,
|
||||
}),
|
||||
});
|
||||
|
||||
if AMPLITUDE_API_KEY.is_some() {
|
||||
if MIXPANEL_TOKEN.is_some() {
|
||||
this.executor
|
||||
.spawn({
|
||||
let this = this.clone();
|
||||
@@ -139,30 +148,27 @@ impl Telemetry {
|
||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||
}
|
||||
|
||||
pub fn start(self: &Arc<Self>, db: Arc<Db>) {
|
||||
pub fn start(self: &Arc<Self>, db: Db) {
|
||||
let this = self.clone();
|
||||
self.executor
|
||||
.spawn(
|
||||
async move {
|
||||
let device_id = if let Some(device_id) = db
|
||||
.read(["device_id"])?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.next()
|
||||
.and_then(|bytes| String::from_utf8(bytes).ok())
|
||||
{
|
||||
let device_id = if let Ok(Some(device_id)) = db.read_kvp("device_id") {
|
||||
device_id
|
||||
} else {
|
||||
let device_id = Uuid::new_v4().to_string();
|
||||
db.write([("device_id", device_id.as_bytes())])?;
|
||||
db.write_kvp("device_id", &device_id)?;
|
||||
device_id
|
||||
};
|
||||
|
||||
let device_id = Some(Arc::from(device_id));
|
||||
let device_id: Arc<str> = device_id.into();
|
||||
let mut state = this.state.lock();
|
||||
state.device_id = device_id.clone();
|
||||
state.device_id = Some(device_id.clone());
|
||||
for event in &mut state.queue {
|
||||
event.device_id = device_id.clone();
|
||||
event
|
||||
.properties
|
||||
.distinct_id
|
||||
.get_or_insert_with(|| device_id.clone());
|
||||
}
|
||||
if !state.queue.is_empty() {
|
||||
drop(state);
|
||||
@@ -176,35 +182,66 @@ impl Telemetry {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn set_metrics_id(&self, metrics_id: Option<String>) {
|
||||
self.state.lock().metrics_id = metrics_id.map(|s| s.into());
|
||||
pub fn set_authenticated_user_info(
|
||||
self: &Arc<Self>,
|
||||
metrics_id: Option<String>,
|
||||
is_staff: bool,
|
||||
) {
|
||||
let this = self.clone();
|
||||
let mut state = self.state.lock();
|
||||
let device_id = state.device_id.clone();
|
||||
let metrics_id: Option<Arc<str>> = metrics_id.map(|id| id.into());
|
||||
state.metrics_id = metrics_id.clone();
|
||||
drop(state);
|
||||
|
||||
if let Some((token, device_id)) = MIXPANEL_TOKEN.as_ref().zip(device_id) {
|
||||
self.executor
|
||||
.spawn(
|
||||
async move {
|
||||
let json_bytes = serde_json::to_vec(&[MixpanelEngageRequest {
|
||||
token,
|
||||
distinct_id: device_id,
|
||||
set: json!({
|
||||
"Staff": is_staff,
|
||||
"ID": metrics_id,
|
||||
"App": true
|
||||
}),
|
||||
}])?;
|
||||
let request = Request::post(MIXPANEL_ENGAGE_URL)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json_bytes.into())?;
|
||||
this.http_client.send(request).await?;
|
||||
Ok(())
|
||||
}
|
||||
.log_err(),
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_event(self: &Arc<Self>, kind: &str, properties: Value) {
|
||||
if AMPLITUDE_API_KEY.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut state = self.state.lock();
|
||||
let event = AmplitudeEvent {
|
||||
event_type: kind.to_string(),
|
||||
time: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
session_id: self.session_id,
|
||||
event_properties: if let Value::Object(properties) = properties {
|
||||
Some(properties)
|
||||
} else {
|
||||
None
|
||||
let event = MixpanelEvent {
|
||||
event: kind.to_string(),
|
||||
properties: MixpanelEventProperties {
|
||||
token: "",
|
||||
time: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
distinct_id: state.device_id.clone(),
|
||||
insert_id: post_inc(&mut state.next_event_id),
|
||||
event_properties: if let Value::Object(properties) = properties {
|
||||
Some(properties)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
os_name: state.os_name,
|
||||
os_version: state.os_version.clone(),
|
||||
release_channel: state.release_channel,
|
||||
app_version: state.app_version.clone(),
|
||||
signed_in: state.metrics_id.is_some(),
|
||||
},
|
||||
user_properties: None,
|
||||
user_id: state.metrics_id.clone(),
|
||||
device_id: state.device_id.clone(),
|
||||
os_name: state.os_name,
|
||||
os_version: state.os_version.clone(),
|
||||
app_version: state.app_version.clone(),
|
||||
event_id: post_inc(&mut state.next_event_id),
|
||||
};
|
||||
state.queue.push(event);
|
||||
if state.device_id.is_some() {
|
||||
@@ -224,11 +261,11 @@ impl Telemetry {
|
||||
|
||||
fn flush(self: &Arc<Self>) {
|
||||
let mut state = self.state.lock();
|
||||
let events = mem::take(&mut state.queue);
|
||||
let mut events = mem::take(&mut state.queue);
|
||||
state.flush_task.take();
|
||||
drop(state);
|
||||
|
||||
if let Some(api_key) = AMPLITUDE_API_KEY.as_ref() {
|
||||
if let Some(token) = MIXPANEL_TOKEN.as_ref() {
|
||||
let this = self.clone();
|
||||
self.executor
|
||||
.spawn(
|
||||
@@ -237,23 +274,21 @@ impl Telemetry {
|
||||
|
||||
if let Some(file) = &mut this.state.lock().log_file {
|
||||
let file = file.as_file_mut();
|
||||
for event in &events {
|
||||
for event in &mut events {
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, event)?;
|
||||
file.write_all(&json_bytes)?;
|
||||
file.write(b"\n")?;
|
||||
|
||||
event.properties.token = token;
|
||||
}
|
||||
}
|
||||
|
||||
let batch = AmplitudeEventBatch {
|
||||
api_key,
|
||||
events,
|
||||
options: AmplitudeEventBatchOptions { min_id_length: 1 },
|
||||
};
|
||||
json_bytes.clear();
|
||||
serde_json::to_writer(&mut json_bytes, &batch)?;
|
||||
let request =
|
||||
Request::post(AMPLITUDE_EVENTS_URL).body(json_bytes.into())?;
|
||||
serde_json::to_writer(&mut json_bytes, &events)?;
|
||||
let request = Request::post(MIXPANEL_EVENTS_URL)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json_bytes.into())?;
|
||||
this.http_client.send(request).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||