Compare commits
1282 Commits
v0.5.2
...
issue-temp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75f9fa5daf | ||
|
|
c9d34feab7 | ||
|
|
bf16d360af | ||
|
|
38d95b4faf | ||
|
|
b06e7d3cf4 | ||
|
|
9e7ea82f94 | ||
|
|
d74fc8a5d5 | ||
|
|
4a43b45a99 | ||
|
|
d6a72fbfc4 | ||
|
|
396f737612 | ||
|
|
2dd8d5a119 | ||
|
|
656c5b61cc | ||
|
|
9fd0f6834a | ||
|
|
58dac27186 | ||
|
|
c8befb6843 | ||
|
|
fafd47fd90 | ||
|
|
e0990910f6 | ||
|
|
599f23c8aa | ||
|
|
a0bd011b80 | ||
|
|
157c27c20d | ||
|
|
4d0407a3b3 | ||
|
|
f73ed1213f | ||
|
|
daabe11d11 | ||
|
|
aaf125cfca | ||
|
|
161fc6bb68 | ||
|
|
1afed9f256 | ||
|
|
e8d6c15fee | ||
|
|
8686b28c28 | ||
|
|
d68251ad4b | ||
|
|
a02b4ae08f | ||
|
|
9ed1dd714b | ||
|
|
95dc6c66d9 | ||
|
|
e91c76c10c | ||
|
|
9deddbbe7c | ||
|
|
2d416d491c | ||
|
|
a19ce0458a | ||
|
|
3fca24e6fd | ||
|
|
6f956e0423 | ||
|
|
bb3ce9fbc6 | ||
|
|
06f2340569 | ||
|
|
510c29cc71 | ||
|
|
807aa04807 | ||
|
|
c84e2e0537 | ||
|
|
910e219c0e | ||
|
|
3a4843fb46 | ||
|
|
1b3744baa2 | ||
|
|
c7d8c8b104 | ||
|
|
a380d39865 | ||
|
|
34d3310eae | ||
|
|
f328eec26d | ||
|
|
7684a54678 | ||
|
|
51f3174609 | ||
|
|
4fcd518b0a | ||
|
|
1e0d7c7e28 | ||
|
|
984da93a7b | ||
|
|
c2c7eda799 | ||
|
|
04b85630b9 | ||
|
|
ae4f3759c2 | ||
|
|
a238dcc5e6 | ||
|
|
57ba84107c | ||
|
|
ca913cafbc | ||
|
|
7032421096 | ||
|
|
06ac959496 | ||
|
|
41c15f8c96 | ||
|
|
5cad3dfbae | ||
|
|
49a20a303a | ||
|
|
90f74427d9 | ||
|
|
ba9dc6ce26 | ||
|
|
3a73994ad4 | ||
|
|
797e1f1f21 | ||
|
|
f1713647cf | ||
|
|
8704d3acb3 | ||
|
|
ce4250281f | ||
|
|
aecbc714c4 | ||
|
|
c20242cf1c | ||
|
|
04ba76aac8 | ||
|
|
d1bc65c266 | ||
|
|
dab284f339 | ||
|
|
df76d9a50a | ||
|
|
abec2e48d4 | ||
|
|
0f2ddb452c | ||
|
|
6fa1acc461 | ||
|
|
9502639447 | ||
|
|
ef4acb1b66 | ||
|
|
8cb72d306b | ||
|
|
1ef8bf4690 | ||
|
|
22779c9dbc | ||
|
|
b52467b6d6 | ||
|
|
d33a0cd589 | ||
|
|
a04cb54d86 | ||
|
|
10785f156f | ||
|
|
4736fb8e32 | ||
|
|
531e7ec73d | ||
|
|
ff946b4f2f | ||
|
|
68f0e68343 | ||
|
|
12a3520563 | ||
|
|
e84426a4f9 | ||
|
|
8b80934f77 | ||
|
|
b6618f81f8 | ||
|
|
8140db32dd | ||
|
|
13db5f4423 | ||
|
|
bd49d6bd89 | ||
|
|
61fd6f1741 | ||
|
|
05d45d9aab | ||
|
|
612eb64a83 | ||
|
|
e0a9e62d84 | ||
|
|
5db5f2251f | ||
|
|
34fd9d29c8 | ||
|
|
c6d8476963 | ||
|
|
1d9fe0c03d | ||
|
|
5b2d36e976 | ||
|
|
547135f7d2 | ||
|
|
15f1416f52 | ||
|
|
b2709aa816 | ||
|
|
9775d4e74d | ||
|
|
f5661af1f4 | ||
|
|
7394fdc162 | ||
|
|
a4f133eb46 | ||
|
|
53007465cd | ||
|
|
e1a748016a | ||
|
|
611bec38b4 | ||
|
|
89c7adfbcd | ||
|
|
4de64d9a43 | ||
|
|
deedf4fc8b | ||
|
|
e9e99956ad | ||
|
|
08b26f9d5d | ||
|
|
99ea06e22a | ||
|
|
49cecf837b | ||
|
|
5a166d2455 | ||
|
|
6808d270a7 | ||
|
|
9dc68611a1 | ||
|
|
2877919639 | ||
|
|
76447019c0 | ||
|
|
e932f38160 | ||
|
|
9014835880 | ||
|
|
00328d04cd | ||
|
|
5b1850e161 | ||
|
|
60727656c9 | ||
|
|
372d021062 | ||
|
|
9c4421bfe0 | ||
|
|
5f85af76cd | ||
|
|
4c2565826f | ||
|
|
3e7fd80824 | ||
|
|
6699b4cb33 | ||
|
|
ae2d83c8aa | ||
|
|
3b0216ca7e | ||
|
|
1abfb175b5 | ||
|
|
a771695348 | ||
|
|
fe3e4edb04 | ||
|
|
364a4faa75 | ||
|
|
331787fe43 | ||
|
|
9484a7237c | ||
|
|
96063969ae | ||
|
|
cb1c8be870 | ||
|
|
5ea71da181 | ||
|
|
3ff0ff238a | ||
|
|
dda469d10e | ||
|
|
9f6e033c53 | ||
|
|
15f44adf3b | ||
|
|
e9483cb242 | ||
|
|
5243d8bedf | ||
|
|
ed731fd7a6 | ||
|
|
07f5422132 | ||
|
|
5aa1764848 | ||
|
|
4973ee555b | ||
|
|
4bfccba4c0 | ||
|
|
5ddad8ebc2 | ||
|
|
b5921897d6 | ||
|
|
53a46b88b8 | ||
|
|
da977ccace | ||
|
|
7b9a2167ad | ||
|
|
fc16771355 | ||
|
|
3ef3fbfd0e | ||
|
|
415a0aaf75 | ||
|
|
5a4a9b570e | ||
|
|
2df60d6387 | ||
|
|
9c176013ab | ||
|
|
036d251e15 | ||
|
|
925060689a | ||
|
|
f0631d734e | ||
|
|
52b299315d | ||
|
|
0195e94aa7 | ||
|
|
75f0b2b82c | ||
|
|
f26f283c1f | ||
|
|
7c387b2aee | ||
|
|
071ecb284e | ||
|
|
03242942c2 | ||
|
|
a476927595 | ||
|
|
ec6e1704be | ||
|
|
d2f189607e | ||
|
|
c13777e68d | ||
|
|
2fb63dcfb1 | ||
|
|
0adb7685f6 | ||
|
|
1cbe99a0d8 | ||
|
|
fa83da6085 | ||
|
|
1392c16f12 | ||
|
|
d6393653e1 | ||
|
|
a75de51198 | ||
|
|
a21869ec9b | ||
|
|
f534e4ee37 | ||
|
|
9add661a5b | ||
|
|
81948a5c29 | ||
|
|
5924154a62 | ||
|
|
354bfe58cd | ||
|
|
c5974a8575 | ||
|
|
c0275addb0 | ||
|
|
c85baf7fd0 | ||
|
|
39e5778bd0 | ||
|
|
a3b77bb37f | ||
|
|
660cf0c1e4 | ||
|
|
11020c4545 | ||
|
|
e93bf02e3a | ||
|
|
a629d7f048 | ||
|
|
683b0f5c48 | ||
|
|
42a80a41ca | ||
|
|
9dcb90a9cf | ||
|
|
1af0aa3f57 | ||
|
|
35c9338220 | ||
|
|
2e8fa0f6c5 | ||
|
|
931aeda17f | ||
|
|
a037d24b0f | ||
|
|
1fa556cd00 | ||
|
|
82e9ad8a20 | ||
|
|
d893765b24 | ||
|
|
0d3f007dd4 | ||
|
|
9702dae19d | ||
|
|
1108b0264e | ||
|
|
4f060579ca | ||
|
|
1940c6e1ba | ||
|
|
f6a697780b | ||
|
|
e754f6c234 | ||
|
|
d19dbbfe7e | ||
|
|
3a735ea0ce | ||
|
|
e319d19a54 | ||
|
|
5f075e4936 | ||
|
|
ef8b525376 | ||
|
|
e39635c75b | ||
|
|
260744c8af | ||
|
|
54114e6e94 | ||
|
|
1bd721d523 | ||
|
|
d6ba3f3adf | ||
|
|
b453e2cf1a | ||
|
|
2d14e5682d | ||
|
|
1f58aebb2b | ||
|
|
6d70ced93a | ||
|
|
accc53c5dd | ||
|
|
c1518c52f3 | ||
|
|
531b38e562 | ||
|
|
fd299aabe8 | ||
|
|
4b1a3eb41e | ||
|
|
a5739fa7e2 | ||
|
|
e4cb8b71dd | ||
|
|
f6eff090e7 | ||
|
|
25662d1402 | ||
|
|
61a2c362b9 | ||
|
|
cbcee037a7 | ||
|
|
de2e4a9c7f | ||
|
|
0ba0a5bcd8 | ||
|
|
70cc409402 | ||
|
|
160ca610ae | ||
|
|
2f1aa95f4c | ||
|
|
01ab507e3a | ||
|
|
4491d50935 | ||
|
|
3c2889714a | ||
|
|
eb6e7d1491 | ||
|
|
a8eb561774 | ||
|
|
6152eeefe3 | ||
|
|
b8b1668fee | ||
|
|
aaf808477e | ||
|
|
8e3ddbcfaf | ||
|
|
3eead128a6 | ||
|
|
4c22a73ff3 | ||
|
|
a78f58de92 | ||
|
|
fcda48903b | ||
|
|
3f9805ac56 | ||
|
|
2131fed0bb | ||
|
|
baa5d2fcac | ||
|
|
7cf9b76eb5 | ||
|
|
200648275e | ||
|
|
a7584d62c7 | ||
|
|
74e6caf633 | ||
|
|
f1f531cbfa | ||
|
|
b5a275ad07 | ||
|
|
5051ad91ff | ||
|
|
06d6cb6d19 | ||
|
|
92116273b0 | ||
|
|
6169ff99a2 | ||
|
|
67f77d027a | ||
|
|
39e1a803a1 | ||
|
|
ba159e1a3e | ||
|
|
a60b77121a | ||
|
|
810f0104be | ||
|
|
07489fb405 | ||
|
|
82f0bdfc18 | ||
|
|
66154b30c1 | ||
|
|
bf5d391d53 | ||
|
|
591ce0894e | ||
|
|
1c18563dfb | ||
|
|
b60e781767 | ||
|
|
ca54544b82 | ||
|
|
9c3ff71ce4 | ||
|
|
fd82ec7659 | ||
|
|
a17e096d94 | ||
|
|
23ccaacdee | ||
|
|
f5c7f90d19 | ||
|
|
39a4297168 | ||
|
|
544111846c | ||
|
|
79bdea0441 | ||
|
|
c056c4e79e | ||
|
|
19828e9070 | ||
|
|
8231c736ca | ||
|
|
85f639b1f0 | ||
|
|
c471b6d66c | ||
|
|
a363efccfb | ||
|
|
871a17a108 | ||
|
|
bd18dbdbe8 | ||
|
|
16a39b364c | ||
|
|
696000546b | ||
|
|
dc5785c874 | ||
|
|
afaec4b1bf | ||
|
|
91f79d2be0 | ||
|
|
9932fe5055 | ||
|
|
c296597427 | ||
|
|
97cb5bc12c | ||
|
|
7c596d6bf1 | ||
|
|
b68799e2d1 | ||
|
|
a3d4db9fc1 | ||
|
|
8369330053 | ||
|
|
46a6fee81d | ||
|
|
b2c75130ce | ||
|
|
ef444f2a6f | ||
|
|
36f0871f69 | ||
|
|
5adf27d345 | ||
|
|
5e4c261332 | ||
|
|
6fb04e23ad | ||
|
|
7fb8850071 | ||
|
|
2c850a612d | ||
|
|
e555c4d083 | ||
|
|
cb32cca6aa | ||
|
|
8ccc7dfcd2 | ||
|
|
ffb491a61f | ||
|
|
4ecba93a25 | ||
|
|
52ec1d09b8 | ||
|
|
3d2e2e2278 | ||
|
|
f9a9ece320 | ||
|
|
ffc46d6a48 | ||
|
|
ce458642af | ||
|
|
820d3ef426 | ||
|
|
145386b6a7 | ||
|
|
a708cf7f69 | ||
|
|
c70ebb6cce | ||
|
|
a260b6eac4 | ||
|
|
0ab17e1ec8 | ||
|
|
d1a0cd1173 | ||
|
|
da07d7328d | ||
|
|
772597ce2a | ||
|
|
3955b973ef | ||
|
|
e76a7d57c0 | ||
|
|
ebc4da6c29 | ||
|
|
f73c112e07 | ||
|
|
7fbc9fa357 | ||
|
|
6f8ffcaf35 | ||
|
|
fcfa508cbc | ||
|
|
a6766ef1c0 | ||
|
|
606b5ac5ab | ||
|
|
9acc463c7f | ||
|
|
c4ddd08b0a | ||
|
|
5c7dd10769 | ||
|
|
3fa86cb441 | ||
|
|
4a74ee0d72 | ||
|
|
8a8759b0d4 | ||
|
|
f259072136 | ||
|
|
3fd74292ef | ||
|
|
f37c4293df | ||
|
|
b9248553be | ||
|
|
475d434e98 | ||
|
|
05d568d1c1 | ||
|
|
28b63bac38 | ||
|
|
fc64719bb8 | ||
|
|
323a74eef3 | ||
|
|
bd4da4f365 | ||
|
|
ae7f7e9bd6 | ||
|
|
1e8852b540 | ||
|
|
bc02192d80 | ||
|
|
b88e63d133 | ||
|
|
a85255df5a | ||
|
|
15c02e9e91 | ||
|
|
ec35392cd1 | ||
|
|
b14b977bca | ||
|
|
405f77e466 | ||
|
|
54f55d44dc | ||
|
|
568dc1625e | ||
|
|
259b239e16 | ||
|
|
3033387ffc | ||
|
|
d500c29493 | ||
|
|
799d8003eb | ||
|
|
5d34af6ac5 | ||
|
|
dca5b9f197 | ||
|
|
75e075ef8e | ||
|
|
9895976128 | ||
|
|
133367ddb5 | ||
|
|
57964282ee | ||
|
|
51b07fc0c3 | ||
|
|
d8f33641a5 | ||
|
|
a7c7234589 | ||
|
|
f22f1971cb | ||
|
|
28a5806492 | ||
|
|
2fad02df23 | ||
|
|
df98167706 | ||
|
|
06cc40055a | ||
|
|
66b47c08e6 | ||
|
|
40cb8db1fc | ||
|
|
2454a03c2b | ||
|
|
1eb8998296 | ||
|
|
0a59a06121 | ||
|
|
2d3baff031 | ||
|
|
a7191e8efb | ||
|
|
e1f4d791ae | ||
|
|
806fcb6ed2 | ||
|
|
467b0fa988 | ||
|
|
a420d5b203 | ||
|
|
5be3aec7d4 | ||
|
|
5e25a4a00e | ||
|
|
810cf9ab2c | ||
|
|
2a11de7571 | ||
|
|
12543be913 | ||
|
|
cfbf08dbf5 | ||
|
|
3649573ba1 | ||
|
|
0fdc02d5e9 | ||
|
|
7bea362503 | ||
|
|
abb02ad624 | ||
|
|
f7f73b8163 | ||
|
|
0ffe8ef102 | ||
|
|
ce995812d4 | ||
|
|
cd22e1779d | ||
|
|
fbb50d8fb6 | ||
|
|
27172c2a19 | ||
|
|
f453dff438 | ||
|
|
a437a40eee | ||
|
|
70a173a16d | ||
|
|
7724fc74e5 | ||
|
|
6023c08554 | ||
|
|
2de56065af | ||
|
|
f5cf1ca08c | ||
|
|
37a1e67579 | ||
|
|
a372cbbf52 | ||
|
|
a67cf44453 | ||
|
|
a4bead1bde | ||
|
|
0d15024bd2 | ||
|
|
6509d726b3 | ||
|
|
a132f0f41d | ||
|
|
7c2ea3dcfd | ||
|
|
706c6d46da | ||
|
|
04915cc65a | ||
|
|
e2fb8827a7 | ||
|
|
05fda903c6 | ||
|
|
91e1eff763 | ||
|
|
2c4ffcfa7c | ||
|
|
34d63f3557 | ||
|
|
a5613ebb10 | ||
|
|
1a2b45eebd | ||
|
|
65791a58e9 | ||
|
|
6e0f22323b | ||
|
|
4c8c35a25a | ||
|
|
a149f06f58 | ||
|
|
42f97f63d5 | ||
|
|
227652de2e | ||
|
|
dd205e849c | ||
|
|
05d994889d | ||
|
|
4b381049c3 | ||
|
|
33f3479569 | ||
|
|
6b940ed675 | ||
|
|
247be0c11f | ||
|
|
14b0e8875d | ||
|
|
bd51741b01 | ||
|
|
afd60b8a16 | ||
|
|
6e0659af4c | ||
|
|
6de1fa0878 | ||
|
|
61e00f1620 | ||
|
|
c8c75b38c3 | ||
|
|
2bdd0a0e8d | ||
|
|
4131ad5016 | ||
|
|
4f74b3fafe | ||
|
|
177a487ead | ||
|
|
99a4966786 | ||
|
|
db9279a6e5 | ||
|
|
3036f43b75 | ||
|
|
f4560bd7d8 | ||
|
|
97b9ec5cc7 | ||
|
|
cdab3d0eff | ||
|
|
de991041c4 | ||
|
|
776c16cd49 | ||
|
|
d9f8746438 | ||
|
|
20d1c4d242 | ||
|
|
6edac37031 | ||
|
|
cea5a18a7a | ||
|
|
bf615303f1 | ||
|
|
063585f563 | ||
|
|
01304e807a | ||
|
|
d275b3fd08 | ||
|
|
66364354da | ||
|
|
48203aedf9 | ||
|
|
a6ca893ec5 | ||
|
|
d55f1163a2 | ||
|
|
80530012b8 | ||
|
|
d98a6adfb3 | ||
|
|
ae3a35816e | ||
|
|
8ca4be5ac0 | ||
|
|
1c814946d5 | ||
|
|
1db59fc052 | ||
|
|
273a69258a | ||
|
|
3b0dc2195d | ||
|
|
bbb2539ace | ||
|
|
03cfe9bf62 | ||
|
|
e29a4d79ba | ||
|
|
4243bf9ba8 | ||
|
|
7ae9627a79 | ||
|
|
097a1cc799 | ||
|
|
625511d28b | ||
|
|
b6a58ee845 | ||
|
|
a349aed94c | ||
|
|
82b96d1a14 | ||
|
|
b85ddd1a53 | ||
|
|
732f88ff51 | ||
|
|
c08139b812 | ||
|
|
a6a6e73b04 | ||
|
|
9971201004 | ||
|
|
e99b0f8052 | ||
|
|
7a59b2b25d | ||
|
|
521e21072e | ||
|
|
f34845fd7d | ||
|
|
29ccadbd43 | ||
|
|
ffb4b395d0 | ||
|
|
19eba9526a | ||
|
|
09ed76904b | ||
|
|
22932241bd | ||
|
|
afc95d99cb | ||
|
|
5fde3c53b8 | ||
|
|
1135852f25 | ||
|
|
e892f0cb45 | ||
|
|
de56374c4d | ||
|
|
9d92b8cc6d | ||
|
|
d9b3ba0ff2 | ||
|
|
4be6b11c1e | ||
|
|
6794da0738 | ||
|
|
5527170fab | ||
|
|
e732eed98a | ||
|
|
5244642dee | ||
|
|
b329a44fef | ||
|
|
c93632421d | ||
|
|
97e0fd9f43 | ||
|
|
ea536c5537 | ||
|
|
970c906c97 | ||
|
|
fe738da633 | ||
|
|
5e3da71ce4 | ||
|
|
a0cccb299e | ||
|
|
e56974f4b2 | ||
|
|
fd28da2a3b | ||
|
|
257b35e4ae | ||
|
|
4398563b85 | ||
|
|
e23ace1bb0 | ||
|
|
2ef333c66d | ||
|
|
c6ee44010c | ||
|
|
71d770e679 | ||
|
|
cf17b93201 | ||
|
|
08f284ce29 | ||
|
|
4336aeb0ad | ||
|
|
5fd3a45c64 | ||
|
|
77f8bfcf25 | ||
|
|
509a97e532 | ||
|
|
604683efd5 | ||
|
|
c731515b94 | ||
|
|
8d85158f91 | ||
|
|
0736f372dc | ||
|
|
a37210b22e | ||
|
|
ccf81ff7b9 | ||
|
|
a4c1a65f10 | ||
|
|
6bdea219bf | ||
|
|
02a82e1e59 | ||
|
|
d238b0b9f2 | ||
|
|
40026fbf53 | ||
|
|
4193818259 | ||
|
|
78840157ef | ||
|
|
f3d69a29b9 | ||
|
|
1ae6709b26 | ||
|
|
8cc44b3958 | ||
|
|
70e894c268 | ||
|
|
9ff3ab1a2b | ||
|
|
8167f6f232 | ||
|
|
de844e9b23 | ||
|
|
7214b71c29 | ||
|
|
be5df46803 | ||
|
|
25bd61558b | ||
|
|
41961de619 | ||
|
|
1ca3f6ef8a | ||
|
|
f97c94832a | ||
|
|
4ae7786362 | ||
|
|
d3d553ad5a | ||
|
|
4faab006e3 | ||
|
|
80621f598e | ||
|
|
eaf689fe2f | ||
|
|
211e3304f3 | ||
|
|
d4c3b70f15 | ||
|
|
f874941e4d | ||
|
|
437b2ab30a | ||
|
|
dcc2a5afe0 | ||
|
|
078e952f30 | ||
|
|
2d2fe4983c | ||
|
|
29ddc04f7b | ||
|
|
aac2da70cf | ||
|
|
f5cbe0c4fe | ||
|
|
ab28d0a9b3 | ||
|
|
6aaf4ed9d9 | ||
|
|
1b81a1f4a6 | ||
|
|
e838fe30de | ||
|
|
6d9779a497 | ||
|
|
130ece3d2e | ||
|
|
d63a794848 | ||
|
|
ba220abbbf | ||
|
|
3825575f70 | ||
|
|
8d69961130 | ||
|
|
740357d120 | ||
|
|
5638f0f24b | ||
|
|
c8a30e793a | ||
|
|
2264281e4d | ||
|
|
ce38d4ff6b | ||
|
|
74773ade62 | ||
|
|
3faf777db1 | ||
|
|
09e880a90d | ||
|
|
d5785e87e8 | ||
|
|
d4a43bf051 | ||
|
|
76c0432f96 | ||
|
|
4434796aab | ||
|
|
0faa24d5d3 | ||
|
|
6c3b87c9b9 | ||
|
|
2fb31f5944 | ||
|
|
8be42e78de | ||
|
|
6d242ee6c3 | ||
|
|
6c910d2b5f | ||
|
|
0caa56e618 | ||
|
|
e5dc843d6a | ||
|
|
3efd1ee325 | ||
|
|
56a1367322 | ||
|
|
544aba53dc | ||
|
|
d68bbbf9a1 | ||
|
|
489737274a | ||
|
|
4c1fabffc3 | ||
|
|
6a52d03329 | ||
|
|
17f00e6fea | ||
|
|
c67bc33c23 | ||
|
|
bb80dcb4e4 | ||
|
|
8fff3e5389 | ||
|
|
233109d23e | ||
|
|
a6aa78e3b6 | ||
|
|
7536322e53 | ||
|
|
b690e3d149 | ||
|
|
aabf270144 | ||
|
|
61a4f15e53 | ||
|
|
de212322e2 | ||
|
|
cda18fb957 | ||
|
|
011ba5056a | ||
|
|
44c871270b | ||
|
|
9f6b0e1018 | ||
|
|
2d9293732b | ||
|
|
d4a131ad31 | ||
|
|
fa2316fb28 | ||
|
|
6e5d1fb613 | ||
|
|
e45f0f0299 | ||
|
|
62125d0284 | ||
|
|
d5ffede5ea | ||
|
|
212bb33142 | ||
|
|
1d8c80c062 | ||
|
|
e8e11257d2 | ||
|
|
b4236b9a95 | ||
|
|
d2b1404907 | ||
|
|
f11aab6c19 | ||
|
|
1f133dbcd0 | ||
|
|
879576b613 | ||
|
|
7a105a1538 | ||
|
|
7408d7bc95 | ||
|
|
1d9820a7c9 | ||
|
|
c536f7d342 | ||
|
|
6d9dfb5deb | ||
|
|
a3d469a101 | ||
|
|
97ecb0a5d6 | ||
|
|
6f58e9e7bb | ||
|
|
0de19a09ad | ||
|
|
17a56189dc | ||
|
|
520f9801be | ||
|
|
03a6b0311f | ||
|
|
1a922d41df | ||
|
|
050f8f3c81 | ||
|
|
5fc32c7b85 | ||
|
|
964fa69fb4 | ||
|
|
f1a33db800 | ||
|
|
2e3b56c52f | ||
|
|
8f137cf49d | ||
|
|
de170ebdd2 | ||
|
|
e5ae1db49c | ||
|
|
63035196af | ||
|
|
e08d803a33 | ||
|
|
df1a939798 | ||
|
|
848f0db3d1 | ||
|
|
9e2d47eab5 | ||
|
|
88fa103acb | ||
|
|
9849bc055c | ||
|
|
1cf75a7169 | ||
|
|
e297dff0d4 | ||
|
|
aafd7ed21f | ||
|
|
ab2c31aa8a | ||
|
|
47111819e7 | ||
|
|
ddd3f7c196 | ||
|
|
1738f92ce2 | ||
|
|
074695b50c | ||
|
|
95ac136720 | ||
|
|
ecced27853 | ||
|
|
e86aa6b541 | ||
|
|
91f74b44a0 | ||
|
|
35d8e28b8c | ||
|
|
26a6af4ab2 | ||
|
|
20785926e2 | ||
|
|
66d0ee8156 | ||
|
|
6138b060d7 | ||
|
|
308d661d7e | ||
|
|
4e106c2355 | ||
|
|
456160fa29 | ||
|
|
b553ac58d3 | ||
|
|
2c26f43035 | ||
|
|
1b49844012 | ||
|
|
75d45e8907 | ||
|
|
b78bd5d6fd | ||
|
|
5d53344b75 | ||
|
|
7cd58bd534 | ||
|
|
9826bf67c2 | ||
|
|
350fe124d0 | ||
|
|
20aac83e5e | ||
|
|
9d1c822ba1 | ||
|
|
b8d46e2863 | ||
|
|
cd2f2d42a3 | ||
|
|
4bfab89292 | ||
|
|
1d3e8aedcd | ||
|
|
75c41644c6 | ||
|
|
3e221f3b6a | ||
|
|
535abdc481 | ||
|
|
3684126250 | ||
|
|
d3fc34c824 | ||
|
|
8769b5d1b5 | ||
|
|
59bb132dd7 | ||
|
|
8a53fa0aab | ||
|
|
71f42e405d | ||
|
|
64b89dd226 | ||
|
|
27d6b855b8 | ||
|
|
20c4f0dc10 | ||
|
|
7c166d85e1 | ||
|
|
093080894b | ||
|
|
9a2243da9f | ||
|
|
0f5aa9f8d9 | ||
|
|
673f7a82d1 | ||
|
|
87251878a5 | ||
|
|
3a14f363d9 | ||
|
|
84921f04cd | ||
|
|
b16475b8bd | ||
|
|
848e26eec6 | ||
|
|
62e86f9507 | ||
|
|
5b306f365e | ||
|
|
ce8950da43 | ||
|
|
0e1c7cd8ed | ||
|
|
fba6dd750d | ||
|
|
e73b6bc39e | ||
|
|
fa3cbbee67 | ||
|
|
029e52eb5f | ||
|
|
13914507e8 | ||
|
|
533f0213e3 | ||
|
|
95d6dac055 | ||
|
|
2001d7f02a | ||
|
|
bae5b70023 | ||
|
|
8341d343d3 | ||
|
|
278a56e580 | ||
|
|
6ab44ccf0c | ||
|
|
918898d6fe | ||
|
|
566e5dea57 | ||
|
|
5d1bfa18b1 | ||
|
|
800540ce7f | ||
|
|
74ee74365e | ||
|
|
75f6235111 | ||
|
|
fe415cbd77 | ||
|
|
dc69c0d3e5 | ||
|
|
40b3f39249 | ||
|
|
a9acc55735 | ||
|
|
9c07612274 | ||
|
|
3e87d88825 | ||
|
|
e280c0c5ca | ||
|
|
64f750a097 | ||
|
|
48bc18017e | ||
|
|
035fa3be3f | ||
|
|
f62723c274 | ||
|
|
aca59019c1 | ||
|
|
2186ea9a90 | ||
|
|
928f6e7009 | ||
|
|
64ed5fca3b | ||
|
|
4c7fc79843 | ||
|
|
153d77359f | ||
|
|
c8a2fb92d2 | ||
|
|
dcc252b371 | ||
|
|
846aadd7c6 | ||
|
|
3c604db234 | ||
|
|
cb3efb1c1d | ||
|
|
1423db35eb | ||
|
|
cb2a7a88fc | ||
|
|
e530111283 | ||
|
|
332c4a1f98 | ||
|
|
8541b95d8b | ||
|
|
e4584e3f04 | ||
|
|
168ec9092a | ||
|
|
914b6c757d | ||
|
|
48d10d701e | ||
|
|
6479e94c4f | ||
|
|
fb3a9f1f29 | ||
|
|
e9d7df8862 | ||
|
|
123de56f38 | ||
|
|
8627132d6a | ||
|
|
3ef9c4c626 | ||
|
|
aec03294d6 | ||
|
|
2e6e1f4060 | ||
|
|
07f1103784 | ||
|
|
ffc8f133c9 | ||
|
|
507f2a4c7c | ||
|
|
1c9e9b9367 | ||
|
|
4d81775f8d | ||
|
|
cf042931b6 | ||
|
|
f6a931599a | ||
|
|
1b17049e94 | ||
|
|
81c039d645 | ||
|
|
e245edc843 | ||
|
|
8494ed8ac3 | ||
|
|
371f14d2e4 | ||
|
|
1109fbfcc3 | ||
|
|
ff88f242fa | ||
|
|
bd3dd69ae1 | ||
|
|
06780c2805 | ||
|
|
e44e227af6 | ||
|
|
4ca06a9218 | ||
|
|
9e29761581 | ||
|
|
110c389d85 | ||
|
|
d856ed89ff | ||
|
|
fa747515e9 | ||
|
|
b15c9d7655 | ||
|
|
15c3ee4af2 | ||
|
|
748248deab | ||
|
|
70671ac672 | ||
|
|
f70f670ed3 | ||
|
|
4b685716d5 | ||
|
|
252c812125 | ||
|
|
69a437a1a8 | ||
|
|
b8b36b6248 | ||
|
|
28e24cb9d6 | ||
|
|
2a0609fdb8 | ||
|
|
b3233cab71 | ||
|
|
405a3eda60 | ||
|
|
49acfc496b | ||
|
|
a1932fa3dc | ||
|
|
87a8706299 | ||
|
|
92ec46147b | ||
|
|
2bda7566b4 | ||
|
|
43488cb57b | ||
|
|
137fbb638b | ||
|
|
76ed8d58fa | ||
|
|
8204139df8 | ||
|
|
7926684930 | ||
|
|
73946a4ede | ||
|
|
aa88115d45 | ||
|
|
496c08b075 | ||
|
|
2388d1782f | ||
|
|
e0e1c59d41 | ||
|
|
f2cb6ce972 | ||
|
|
ed3535e9c9 | ||
|
|
62f1830197 | ||
|
|
2fb62fe8be | ||
|
|
3243cbf039 | ||
|
|
eb171ecb21 | ||
|
|
43b8941f7a | ||
|
|
0b16839d7f | ||
|
|
164ba46259 | ||
|
|
b174f7a023 | ||
|
|
c9ec9f14de | ||
|
|
597c071c3d | ||
|
|
91d05434ca | ||
|
|
5b0aba99bb | ||
|
|
87c7d1a5f4 | ||
|
|
d841d69bd4 | ||
|
|
512bf1d013 | ||
|
|
adbd58a949 | ||
|
|
a0959d2730 | ||
|
|
f455722bed | ||
|
|
735d23c9eb | ||
|
|
0fd6a23c78 | ||
|
|
fd73d8d67e | ||
|
|
347366e8d7 | ||
|
|
eb8d4abcbe | ||
|
|
86acccebb0 | ||
|
|
8f184f76fa | ||
|
|
1daabfa3e0 | ||
|
|
90b49106f0 | ||
|
|
1a5638426c | ||
|
|
8d4da009af | ||
|
|
89571d57bd | ||
|
|
5d4e06074a | ||
|
|
98522cf6e4 | ||
|
|
7cdb751606 | ||
|
|
4ffc90f963 | ||
|
|
8da6572074 | ||
|
|
ee64aad670 | ||
|
|
92cee2321f | ||
|
|
7f969b1252 | ||
|
|
a02e2e1fa1 | ||
|
|
d690e1b37a | ||
|
|
3300c2302c | ||
|
|
685470cf23 | ||
|
|
c545a959ac | ||
|
|
a7832ef40f | ||
|
|
3e941999d5 | ||
|
|
bebdfc259c | ||
|
|
d31a920eb7 | ||
|
|
ec87821bcd | ||
|
|
21df0e32c4 | ||
|
|
afaa235c75 | ||
|
|
8d97e7136f | ||
|
|
89ac422929 | ||
|
|
d8d55f032e | ||
|
|
c83d694c7a | ||
|
|
ea55650d45 | ||
|
|
ec1aee60a1 | ||
|
|
3a7a0918d3 | ||
|
|
8bf90714da | ||
|
|
d8b59925b0 | ||
|
|
d37fd6bea0 | ||
|
|
0f6263d82e | ||
|
|
e7c70945e6 | ||
|
|
095a936891 | ||
|
|
c49ce91fac | ||
|
|
dd52727055 | ||
|
|
b48068c63c | ||
|
|
c39d04e8a5 | ||
|
|
0eff8e4bc8 | ||
|
|
178551a467 | ||
|
|
720739e50a | ||
|
|
46d1537d8f | ||
|
|
63b2bb55a1 | ||
|
|
06c210f5c8 | ||
|
|
a545c42047 | ||
|
|
da4b4de292 | ||
|
|
fd936e5bf3 | ||
|
|
dc11340faf | ||
|
|
06d921d855 | ||
|
|
91b602341b | ||
|
|
ad853ca342 | ||
|
|
14fa3481b0 | ||
|
|
2b6fee0455 | ||
|
|
a48e6f82d1 | ||
|
|
1020904fe0 | ||
|
|
6aaced0c04 | ||
|
|
3646c0d0a3 | ||
|
|
44c8574f1b | ||
|
|
ea1ff65db7 | ||
|
|
85b7147927 | ||
|
|
6b54c342aa | ||
|
|
60720b04db | ||
|
|
323a593159 | ||
|
|
baef94f14a | ||
|
|
59b3cfee66 | ||
|
|
cbc85ca98f | ||
|
|
425c8cd1d3 | ||
|
|
1d7949ded6 | ||
|
|
2507170816 | ||
|
|
2c83425b42 | ||
|
|
1da281de8a | ||
|
|
98baab4d03 | ||
|
|
441069aa76 | ||
|
|
8c5bf55b51 | ||
|
|
b6d6955b39 | ||
|
|
0d68b1a73e | ||
|
|
15b7c241ff | ||
|
|
de9c62617b | ||
|
|
a0e05115a3 | ||
|
|
aaf58272f1 | ||
|
|
5ebaf5663d | ||
|
|
7dc3e6a08b | ||
|
|
5a5f817e59 | ||
|
|
b650320a34 | ||
|
|
424fd8e4d3 | ||
|
|
cfbf80fdfe | ||
|
|
9b8ced2803 | ||
|
|
630a1d42b8 | ||
|
|
b69ff6ca8a | ||
|
|
1a23b6178b | ||
|
|
de536d7305 | ||
|
|
6dcd33bbc5 | ||
|
|
c6df78eb81 | ||
|
|
d1babe27ec | ||
|
|
7596f62ca5 | ||
|
|
139e57f1eb | ||
|
|
e87d85b088 | ||
|
|
91b79b703c | ||
|
|
bdefa4a967 | ||
|
|
b82efb6e3c | ||
|
|
a50e109043 | ||
|
|
8b5db12e1d | ||
|
|
f1f8163bb7 | ||
|
|
c0fac90b52 | ||
|
|
15717ddca0 | ||
|
|
a7ce9aaca5 | ||
|
|
d8df8e1033 | ||
|
|
440a35e491 | ||
|
|
dbbc6ed0bb | ||
|
|
86fbb383fd | ||
|
|
1c07a74bef | ||
|
|
2df93c2ceb | ||
|
|
6ca40afac0 | ||
|
|
36a8f044ae | ||
|
|
276e5453f1 | ||
|
|
0f81986684 | ||
|
|
bc54e536af | ||
|
|
acb57b3e9c | ||
|
|
de0e30de57 | ||
|
|
b8b942cdae | ||
|
|
0146a077e8 | ||
|
|
a0fcf515cd | ||
|
|
1719ad81a4 | ||
|
|
51a9a7b75d | ||
|
|
e91b0500c5 | ||
|
|
2a17361a90 | ||
|
|
1eca3a8603 | ||
|
|
86f37863a7 | ||
|
|
643cd87706 | ||
|
|
98c85441f8 | ||
|
|
1345f996da | ||
|
|
ff48f08241 | ||
|
|
b5f438aa0c | ||
|
|
a2266adb3f | ||
|
|
c518f373df | ||
|
|
f4636fdca2 | ||
|
|
4108d7827a | ||
|
|
5ccd40f530 | ||
|
|
8798340d48 | ||
|
|
e8d2959350 | ||
|
|
f1d838de6b | ||
|
|
ec92a4cd34 | ||
|
|
b3b9788d37 | ||
|
|
67739e8e39 | ||
|
|
be3ed7d380 | ||
|
|
9b4d58cfe8 | ||
|
|
fb6a8d999c | ||
|
|
b2f6a06a1f | ||
|
|
f7da32702f | ||
|
|
219d2246fa | ||
|
|
979b5eb89c | ||
|
|
a58811b1bf | ||
|
|
be0e18d4b0 | ||
|
|
fea6f0ce81 | ||
|
|
5859ac7a58 | ||
|
|
566833da6e | ||
|
|
d174602cc0 | ||
|
|
46bc04f69f | ||
|
|
bb8fd5500d | ||
|
|
d0df105dda | ||
|
|
a061fb5421 | ||
|
|
fd17e5a4c0 | ||
|
|
cd8bc7a7a1 | ||
|
|
e22a586a41 | ||
|
|
5017c5a5f1 | ||
|
|
03cbab5267 | ||
|
|
2c22837d9f | ||
|
|
4d64df37f5 | ||
|
|
b224f1d78d | ||
|
|
52e018989d | ||
|
|
eb14193b23 | ||
|
|
001c2c88cd | ||
|
|
98ab83976e | ||
|
|
9cd5bf2992 | ||
|
|
e876712af5 | ||
|
|
9fc8d66601 | ||
|
|
12d86493bf | ||
|
|
c97cba69b9 | ||
|
|
63ed7a734b | ||
|
|
0e338ef453 | ||
|
|
d914b38f9f | ||
|
|
3ee00a4824 | ||
|
|
5198c69f6b | ||
|
|
40704c4549 | ||
|
|
83df59e5f1 | ||
|
|
246602e275 | ||
|
|
d3449609da | ||
|
|
a872058704 | ||
|
|
3033376028 | ||
|
|
0bda636113 | ||
|
|
28092d6862 | ||
|
|
cecafb726c | ||
|
|
8418c21096 | ||
|
|
c4ace1972a | ||
|
|
862df1d2df | ||
|
|
487c1a58d6 | ||
|
|
c06c3d48d2 | ||
|
|
7d3ad5a639 | ||
|
|
03b2d02742 | ||
|
|
103ce44ba9 | ||
|
|
22219af133 | ||
|
|
7d7377ab15 | ||
|
|
af8fc53704 | ||
|
|
e80a5d82a4 | ||
|
|
2b73e745da | ||
|
|
e61a1080f7 | ||
|
|
464f336790 | ||
|
|
1dc6e085cc | ||
|
|
8594ff0c23 | ||
|
|
e58963410e | ||
|
|
1e0d118930 | ||
|
|
723e6dd13f | ||
|
|
e941af0905 | ||
|
|
24c8e29691 | ||
|
|
8911bdf966 | ||
|
|
0834759e1a | ||
|
|
bb8c0d264e | ||
|
|
c7da8c5fd3 | ||
|
|
30789ff6e2 | ||
|
|
e6ece10716 | ||
|
|
ea7a255006 | ||
|
|
98df1f5312 | ||
|
|
2bf08787d8 | ||
|
|
b8f7e4211c | ||
|
|
b581d0c44f | ||
|
|
0b613812f7 | ||
|
|
4d066a762f | ||
|
|
8a30a65818 | ||
|
|
01703aff43 | ||
|
|
9d62931af9 | ||
|
|
e53d8fd4fa | ||
|
|
0dbde32ed4 | ||
|
|
d506fdb68c | ||
|
|
9c10186158 | ||
|
|
3eed51dbac | ||
|
|
1c1855f0c3 | ||
|
|
e0a277fa45 | ||
|
|
3d6c7171d2 | ||
|
|
0fee90e4df | ||
|
|
0cced44491 | ||
|
|
f21f758591 | ||
|
|
51e9527d53 | ||
|
|
268cd131dc | ||
|
|
26997ad5c3 | ||
|
|
ce1786f128 | ||
|
|
888057f64a | ||
|
|
3c6c4d9601 | ||
|
|
5e8bcf8faf | ||
|
|
0d6f2f93bf | ||
|
|
937b00cfcc | ||
|
|
0d4432720c | ||
|
|
0ec49643ee | ||
|
|
0d80ea4f4c | ||
|
|
d2b26124e4 | ||
|
|
343116ea24 | ||
|
|
10dc699b21 | ||
|
|
015c5f07bc | ||
|
|
a0b6a3bed6 | ||
|
|
9de5950f4d | ||
|
|
26488b793a | ||
|
|
5a6c2dfcd5 | ||
|
|
7e891ecb79 | ||
|
|
12840f2428 | ||
|
|
0b160a6741 | ||
|
|
dff34a8ae2 | ||
|
|
8f780ad89e | ||
|
|
4624ef393d | ||
|
|
2ce8a890e2 | ||
|
|
90e0e9000e | ||
|
|
a6bb1c156d | ||
|
|
31db8161b5 | ||
|
|
c0d1448080 | ||
|
|
4e4f4e60b6 | ||
|
|
dcc83271af | ||
|
|
a38458c9be | ||
|
|
7934f65ae8 | ||
|
|
aea0cec061 | ||
|
|
7ac56b1a0c | ||
|
|
f5ed0b700f | ||
|
|
1ff3b83766 | ||
|
|
40eca1727e | ||
|
|
9505e68ef7 | ||
|
|
5847806a16 | ||
|
|
e427ea44aa | ||
|
|
8df8bf33a4 | ||
|
|
29d2b942e8 | ||
|
|
992cdb992a | ||
|
|
db0dc71319 | ||
|
|
2c871d1339 | ||
|
|
9673922ca1 | ||
|
|
d311870b61 | ||
|
|
8dd1ff593a | ||
|
|
0f64a769c2 | ||
|
|
a8d150db5c | ||
|
|
d7e6367ff3 | ||
|
|
7b84d8c3d5 | ||
|
|
e025df0824 | ||
|
|
c3392d5386 | ||
|
|
2c09e6a7c0 | ||
|
|
2a0740e135 | ||
|
|
ac65357c8a | ||
|
|
07c255f7f8 | ||
|
|
198b3c9f1b | ||
|
|
f5c567eb90 | ||
|
|
8ca09f6384 | ||
|
|
386dfe42d0 | ||
|
|
7445f43b1b | ||
|
|
5b77e78662 | ||
|
|
4b0b8d4de2 | ||
|
|
b7de47b6fb | ||
|
|
3434907f52 | ||
|
|
099adb3853 | ||
|
|
dd9b1c047c | ||
|
|
6084bb15cf | ||
|
|
42b568952e | ||
|
|
d3bac307bb | ||
|
|
7e488b0fa3 | ||
|
|
7d7a6191ce | ||
|
|
5c644b31ca | ||
|
|
7b4398dfee | ||
|
|
663418e943 | ||
|
|
969bec248a | ||
|
|
8f487d7d4e | ||
|
|
89ebce79b2 | ||
|
|
34ce510fbd | ||
|
|
39f29399e7 | ||
|
|
8cd4cc7d0d | ||
|
|
608ab8f4ad | ||
|
|
e95643f198 | ||
|
|
3b1712345b | ||
|
|
6effe47bcc | ||
|
|
205080a210 | ||
|
|
bc998988c2 | ||
|
|
80b470cfd3 | ||
|
|
3df2b4b7f8 | ||
|
|
6141b60294 | ||
|
|
e1cfb0cab6 | ||
|
|
9b9782ce94 | ||
|
|
5d9eb25e38 | ||
|
|
f4a518ce6b | ||
|
|
3656e6ef54 | ||
|
|
a545d7d8b1 | ||
|
|
3127f7a31b | ||
|
|
f561de200d | ||
|
|
d85a063db8 | ||
|
|
d6bf935ceb | ||
|
|
0d6ff822e7 | ||
|
|
1c0cc8e91c | ||
|
|
fb38573b7e | ||
|
|
8a2f45ec23 | ||
|
|
fee2eb9251 | ||
|
|
24035c4ae8 | ||
|
|
aa8215df34 | ||
|
|
eb78e3d640 | ||
|
|
f8201cd2bc | ||
|
|
f37b3ecd26 | ||
|
|
e6dba59ae6 | ||
|
|
a7bc6b4f36 | ||
|
|
cacc5ac803 | ||
|
|
46dc0f144b | ||
|
|
3cc5275592 | ||
|
|
a3955a1ba8 | ||
|
|
89dfaf1d5d | ||
|
|
fc6246c5cb | ||
|
|
ce8a0cab2a | ||
|
|
b36b6c78a2 | ||
|
|
5232dc6485 | ||
|
|
b1d9fc6c9a | ||
|
|
80ded84f7a | ||
|
|
8394535725 | ||
|
|
adf780d25b | ||
|
|
749ae3deb3 | ||
|
|
2d7760e67c | ||
|
|
c915926e0f | ||
|
|
25203934e6 | ||
|
|
4b1b2878ea | ||
|
|
b11d5514b9 |
@@ -1,3 +1,3 @@
|
||||
# Windows has stack overflows when calling from Tauri, so we increase compiler size
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
||||
59
.github/ISSUE_TEMPLATE/1-app-bug.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: 🎮 Modrinth App bug
|
||||
description: Report an issue in the Modrinth Launcher.
|
||||
labels: [bug, app]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- label: I have ensured my Modrinth App installation is up to date
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What version of the Modrinth App are you using?
|
||||
description: Find this in ⚙️ Settings (bottom right) -> About -> App version.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: oses
|
||||
attributes:
|
||||
label: What operating systems are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- MacOS
|
||||
- Linux
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
52
.github/ISSUE_TEMPLATE/2-web-bug.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: 🌐 Website bug (modrinth.com)
|
||||
description: Report an issue on the Modrinth website.
|
||||
labels: [bug, web]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Chrome (including Arc, Brave, Opera, Vivaldi)
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Safari
|
||||
- Other (please specify)
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,11 +1,19 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve Modrinth
|
||||
labels: [bug]
|
||||
name: 🛠️ API issue (api.modrinth.com)
|
||||
description: Report an issue regarding the Modrinth API.
|
||||
labels: [bug, api]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
@@ -30,4 +38,4 @@ body:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
48
.github/ISSUE_TEMPLATE/4-feature-request.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest an idea
|
||||
labels: [enhancement]
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
|
||||
required: true
|
||||
- label: I checked the [existing discussions](https://github.com/orgs/modrinth/discussions) for duplicate feature requests
|
||||
required: true
|
||||
- label: I have checked that this feature request is not on our [roadmap](https://roadmap.modrinth.com)
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: projects
|
||||
attributes:
|
||||
label: What parts of Modrinth is your feature request related too?
|
||||
multiple: true
|
||||
options:
|
||||
- App
|
||||
- Website
|
||||
- API
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your suggested feature related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the suggested enhancement here.
|
||||
validations:
|
||||
required: false
|
||||
22
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,16 +1,14 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Discord
|
||||
about: Ask questions on our Discord Server.
|
||||
url: https://discord.gg/modrinth-734077874708938864
|
||||
- name: Roadmap
|
||||
- name: 🫶 Support Portal
|
||||
about: Get support using through our portal.
|
||||
url: https://support.modrinth.com
|
||||
- name: 💬 Chat
|
||||
about: Join our Discord server to chat about Modrinth.
|
||||
url: https://discord.modrinth.com
|
||||
- name: 🛣️ Roadmap
|
||||
about: View our Roadmap. Please do not open issues for items on our roadmap.
|
||||
url: https://roadmap.modrinth.com
|
||||
- name: Discussions (Feature requests)
|
||||
about: |
|
||||
Please use Issues for reporting bugs and suggesting enhancements to existing features.
|
||||
Suggestions for new features should be made as a Discussion.
|
||||
url: https://github.com/orgs/modrinth/discussions/categories/feature-requests
|
||||
- name: Documentation
|
||||
about: Useful documentation about Modrinth, its API, and how you can contribute.
|
||||
url: https://docs.modrinth.com
|
||||
- name: 📚 Documentation
|
||||
about: Useful documentation about Modrinth's API
|
||||
url: https://docs.modrinth.com
|
||||
28
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Enhancement
|
||||
description: Suggest an enhancement for an existing Modrinth feature
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your suggested enhancement related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the suggested enhancement here.
|
||||
validations:
|
||||
required: false
|
||||
BIN
.github/assets/app_cover.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
.github/assets/monorepo_cover.png
vendored
Normal file
|
After Width: | Height: | Size: 417 KiB |
BIN
.github/assets/web_cover.png
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
131
.github/workflows/app-release.yml
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
name: 'Modrinth App build'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- .github/workflows/app-release.yml
|
||||
- 'apps/app/**'
|
||||
- 'apps/app-frontend/**'
|
||||
- 'packages/app-lib/**'
|
||||
- 'packages/app-macros/**'
|
||||
- 'packages/assets/**'
|
||||
- 'packages/ui/**'
|
||||
- 'packages/utils/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Rust setup (mac)
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
|
||||
- name: Rust setup
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Setup rust cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-target-
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev sqlite3
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config "tauri-release.conf.json"
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: build app
|
||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
||||
id: build_os
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: |
|
||||
target/*/release/bundle/*/*.dmg
|
||||
target/*/release/bundle/*/*.app.tar.gz
|
||||
target/*/release/bundle/*/*.app.tar.gz.sig
|
||||
target/release/bundle/*/*.dmg
|
||||
target/release/bundle/*/*.app.tar.gz
|
||||
target/release/bundle/*/*.app.tar.gz.sig
|
||||
|
||||
target/release/bundle/*/*.AppImage
|
||||
target/release/bundle/*/*.AppImage.tar.gz
|
||||
target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
target/release/bundle/*/*.deb
|
||||
target/release/bundle/*/*.rpm
|
||||
|
||||
target/release/bundle/*/*.msi
|
||||
target/release/bundle/*/*.msi.zip
|
||||
target/release/bundle/*/*.msi.zip.sig
|
||||
|
||||
70
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
types: [ checks_requested ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build, Test, and Lint
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Cache turbo build setup
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: ${{ runner.os }}-turbo-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-turbo-
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Setup Node.JS environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
44
.github/workflows/cli-build.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: CLI Build + Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_cli
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Rust setup
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Build program
|
||||
with:
|
||||
command: build
|
||||
args: --bin theseus_cli
|
||||
|
||||
- name: Run Lint
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --bin theseus_cli
|
||||
28
.github/workflows/frontend-pages.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Deploy frontend
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
wait:
|
||||
if: github.repository_owner == 'modrinth'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
- name: Cloudflare Pages deployment
|
||||
uses: WalshyDev/cf-pages-await@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: '9ddae624c98677d68d93df6e524a6061'
|
||||
project: 'frontend'
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
commitHash: ${{ steps.push-changes.outputs.commit-hash }}
|
||||
- name: Purge cache
|
||||
if: github.ref == 'refs/heads/prod'
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"hosts": ["modrinth.com", "www.modrinth.com"]}' \
|
||||
https://api.cloudflare.com/client/v4/zones/e39df17b9c4ef44cbce2646346ee6d33/purge_cache
|
||||
42
.github/workflows/gui-build.yml
vendored
@@ -1,42 +0,0 @@
|
||||
name: GUI Build + Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_gui
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run Lint
|
||||
run: pnpm lint
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
108
.github/workflows/tauri-build.yml
vendored
@@ -1,108 +0,0 @@
|
||||
name: 'Tauri GUI Build'
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
jobs:
|
||||
test-tauri:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest, ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_gui
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Rust setup (mac)
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
targets: aarch64-apple-darwin
|
||||
|
||||
- name: Rust setup
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os_mac
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
args: --target universal-apple-darwin
|
||||
|
||||
- name: build app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v3
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: "${{ join(fromJSON(steps.build_os_mac.outputs.artifactPaths), '\n') }}"
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: "${{ join(fromJSON(steps.build_os.outputs.artifactPaths), '\n') }}"
|
||||
161
.gitignore
vendored
@@ -1,114 +1,57 @@
|
||||
/target
|
||||
node_modules/
|
||||
.svelte-kit/
|
||||
theseus_gui/build/
|
||||
theseus_gui/generated/
|
||||
WixTools
|
||||
.direnv/
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
dist
|
||||
tmp
|
||||
/out-tsc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
.pnpm-debug.log
|
||||
Thumbs.db
|
||||
|
||||
minecraft
|
||||
config
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
|
||||
[#]*[#]
|
||||
# frontend generated files
|
||||
apps/frontend/src/generated
|
||||
|
||||
# TEMPORARY: ignore my test instance and metadata
|
||||
theseus_cli/foo
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Intellij+all Patch ###
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
theseus.iml
|
||||
.turbo
|
||||
target
|
||||
generated
|
||||
.env
|
||||
|
||||
# app testing dir
|
||||
app-playground-data/*
|
||||
|
||||
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
13
.vscode/extensions.json
vendored
@@ -1,12 +1,3 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"tauri-apps.tauri-vscode",
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
"stylelint.vscode-stylelint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"pivaszbs.svelte-autoimport",
|
||||
"svelte.svelte-vscode",
|
||||
"ardenivanov.svelte-intellisense"
|
||||
]
|
||||
}
|
||||
"recommendations": ["esbenp.prettier-vscode", "Vue.volar", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
|
||||
160
.vscode/launch.json
vendored
@@ -1,160 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in library 'theseus'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=theseus"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_cli'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_cli",
|
||||
"--package=theseus_cli"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_cli",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_cli'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_cli",
|
||||
"--package=theseus_cli"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_cli",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_playground'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_playground",
|
||||
"--package=theseus_playground"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_playground",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_playground'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_playground",
|
||||
"--package=theseus_playground"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_playground",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_gui'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_gui",
|
||||
"--package=theseus_gui"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_gui",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_gui'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_gui",
|
||||
"--package=theseus_gui"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_gui",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Tauri Development Debug",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path=./theseus_gui/src-tauri/Cargo.toml",
|
||||
"--no-default-features"
|
||||
]
|
||||
},
|
||||
"preLaunchTask": "ui:dev"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Tauri Production Debug",
|
||||
"cargo": {
|
||||
"args": ["build", "--release", "--manifest-path=.theseus_gui/src-tauri/Cargo.toml"]
|
||||
},
|
||||
"preLaunchTask": "ui:build"
|
||||
}
|
||||
]
|
||||
}
|
||||
69
.vscode/settings.json
vendored
@@ -1,60 +1,13 @@
|
||||
{
|
||||
"cssVariables.lookupFiles": [
|
||||
"**/*.postcss",
|
||||
"**/node_modules/omorphia/**/*.postcss"
|
||||
"prettier.endOfLine": "lf",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"cssVariables.blacklistFolders": [
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/.git",
|
||||
"**/bower_components",
|
||||
"**/tmp",
|
||||
"**/dist",
|
||||
"**/tests"
|
||||
],
|
||||
"gitlens.showWelcomeOnInstall": false,
|
||||
"gitlens.showWhatsNewAfterUpgrades": false,
|
||||
"gitlens.plusFeatures.enabled": false,
|
||||
"gitlens.currentLine.enabled": false,
|
||||
"gitlens.currentLine.pullRequests.enabled": false,
|
||||
"gitlens.currentLine.scrollable": true,
|
||||
"gitlens.codeLens.enabled": false,
|
||||
"gitlens.hovers.enabled": false,
|
||||
"CSSNavigation.activeCSSFileExtensions": [
|
||||
"css",
|
||||
"postcss"
|
||||
],
|
||||
"CSSNavigation.activeHTMLFileExtensions": [
|
||||
"html",
|
||||
"svelte",
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
"CSSNavigation.excludeGlobPatterns": [
|
||||
"**/bower_components/**",
|
||||
"**/vendor/**",
|
||||
"**/coverage/**"
|
||||
],
|
||||
"CSSNavigation.alwaysIncludeGlobPatterns": [
|
||||
"./theseus_gui/node_modules/omorphia/**/*.postcss"
|
||||
],
|
||||
"html-css-class-completion.HTMLLanguages": [
|
||||
"html",
|
||||
"svelte"
|
||||
],
|
||||
"html-css-class-completion.includeGlobPattern": "**/*.{postcss,svelte}",
|
||||
"html-css-class-completion.CSSLanguages": [
|
||||
"postcss",
|
||||
],
|
||||
"svelte.enable-ts-plugin": true,
|
||||
"svelte.ask-to-enable-ts-plugin": false,
|
||||
"svelte.plugin.css.diagnostics.enable": false,
|
||||
"svelte.plugin.svelte.diagnostics.enable": false,
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./theseus/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
}
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
|
||||
32
.vscode/tasks.json
vendored
@@ -1,32 +0,0 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "ui:dev",
|
||||
"type": "shell",
|
||||
// `dev` keeps running in the background
|
||||
// ideally you should also configure a `problemMatcher`
|
||||
// see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
|
||||
"isBackground": true,
|
||||
// change this to your `beforeDevCommand`:
|
||||
"command": "yarn",
|
||||
"args": ["dev"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/theseus_gui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ui:build",
|
||||
"type": "shell",
|
||||
// change this to your `beforeBuildCommand`:
|
||||
"command": "yarn",
|
||||
"args": ["build"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/theseus_gui"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
14
COPYING.md
@@ -1,13 +1,13 @@
|
||||
# Copying
|
||||
# Copying Guidelines
|
||||
|
||||
The source code of the theseus repository is licensed under the GNU General Public License, Version 3 only, which is provided in the file [LICENSE](./LICENSE). However, some files listed below are licensed under a different license.
|
||||
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
|
||||
|
||||
## Modrinth logo
|
||||
For detailed information, consult each package's COPYING.md file, if available.
|
||||
|
||||
Any files depicting the Modrinth branding, including the wrench-in-labyrinth logo, the landing image, and variations thereof, are licensed as follows:
|
||||
## Modrinth Branding
|
||||
|
||||
> All rights reserved. © 2020-2023 Rinth, Inc.
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
All rights reserved. © 2020-2024 Rinth, Inc.
|
||||
|
||||
- theseus_gui/src-tauri/icons/*
|
||||
If you fork this repository, you must remove all Modrinth branding assets from your fork.
|
||||
|
||||
5379
Cargo.lock
generated
24
Cargo.toml
@@ -1,24 +1,11 @@
|
||||
[workspace]
|
||||
|
||||
resolver = '2'
|
||||
members = [
|
||||
"theseus",
|
||||
"theseus_cli",
|
||||
"theseus_playground",
|
||||
"theseus_gui/src-tauri",
|
||||
"theseus_macros"
|
||||
'./packages/app-lib',
|
||||
'./apps/app-playground',
|
||||
'./apps/app'
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
debug = true
|
||||
debug-assertions = true
|
||||
overflow-checks = true
|
||||
lto = false
|
||||
panic = 'unwind'
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
rpath = false
|
||||
|
||||
# Optimize for speed and reduce size on release builds
|
||||
[profile.release]
|
||||
panic = "abort" # Strip expensive panic clean-up logic
|
||||
@@ -26,3 +13,6 @@ codegen-units = 1 # Compile crates one after another so the compiler can optimiz
|
||||
lto = true # Enables link to optimizations
|
||||
opt-level = "s" # Optimize for binary size
|
||||
strip = true # Remove debug symbols
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
44
README.md
@@ -1,9 +1,39 @@
|
||||
# theseus
|
||||
A game launcher which can be used as a CLI, GUI, and a library for creating and playing modrinth projects
|
||||
# 
|
||||
|
||||
Theseus aims to provide three components:
|
||||
- A library (theseus)
|
||||
- A CLI (theseus-cli)
|
||||
- A GUI (theseus-gui)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Feel free to contribute!
|
||||
## Modrinth Monorepo
|
||||
|
||||
Welcome to the Modrinth Monorepo, the primary codebase for the Modrinth web interface and app. It contains  lines of code and has  contributors!
|
||||
|
||||
If you're not a developer and you've stumbled upon this repository, you can access the web interface on the [Modrinth website](https://modrinth.com) and download the latest release of the app [here](https://modrinth.com/app).
|
||||
|
||||
## Development
|
||||
|
||||
This repository contains two primary packages. For detailed development information, please refer to their respective READMEs:
|
||||
|
||||
- [Web Interface](apps/frontend/README.md)
|
||||
- [Desktop App](apps/app/README.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://support.modrinth.com/en/articles/8802215-contributing-to-modrinth).
|
||||
|
||||
If you plan to fork this repository for your own purposes, please review our [copying guidelines](COPYING.md).
|
||||
|
||||
## Security
|
||||
|
||||
If you discover a security vulnerability within our codebase, please follow our [responsible disclosure guidelines](https://modrinth.com/legal/security).
|
||||
|
||||
## Support
|
||||
|
||||
If you need help with the Modrinth web interface or app, please visit our [support page](https://support.modrinth.com). For general inquiries, you can also join our [Discord server](https://discord.modrinth.com).
|
||||
|
||||
## License
|
||||
|
||||
All packages in this repository are licensed under their respective licenses. Refer to the LICENSE file in each package for more information.
|
||||
|
||||
4
apps/app-frontend/.eslintrc.cjs
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/vue'],
|
||||
}
|
||||
1
apps/app-frontend/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
**/dist
|
||||
13
apps/app-frontend/COPYING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copying
|
||||
|
||||
The source code of the theseus repository is licensed under the GNU General Public License, Version 3 only, which is provided in the file [LICENSE](./LICENSE). However, some files listed below are licensed under a different license.
|
||||
|
||||
## Modrinth logo
|
||||
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
|
||||
> All rights reserved. © 2020-2023 Rinth, Inc.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
- theseus_gui/src-tauri/icons/\*
|
||||
@@ -1,10 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark-mode">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Modrinth App</title>
|
||||
|
||||
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
48
apps/app-frontend/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@modrinth/app-frontend",
|
||||
"private": true,
|
||||
"version": "0.8.3-1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@tauri-apps/api": "^2.0.0-rc.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@vintl/vintl": "^4.4.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"floating-vue": "^5.2.2",
|
||||
"ofetch": "^1.3.4",
|
||||
"pinia": "^2.1.7",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-multiselect": "3.0.0",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8",
|
||||
"posthog-js": "^1.158.2",
|
||||
"@sentry/vue": "^8.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0-rc",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsconfig": "workspace:*",
|
||||
"vite": "^5.2.8"
|
||||
},
|
||||
"packageManager": "pnpm@9.4.0"
|
||||
}
|
||||
6
apps/app-frontend/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
HomeIcon,
|
||||
SearchIcon,
|
||||
LibraryIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
Button,
|
||||
Notifications,
|
||||
XIcon,
|
||||
} from 'omorphia'
|
||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, XIcon } from '@modrinth/assets'
|
||||
import { Button, Notifications } from '@modrinth/ui'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
@@ -18,137 +10,156 @@ import { get } from '@/helpers/settings'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||
import { useNotifications } from '@/store/notifications.js'
|
||||
import { offline_listener, command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { handleError, useNotifications } from '@/store/notifications.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import { type } from '@tauri-apps/api/os'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { isDev, getOS, isOffline } from '@/helpers/utils.js'
|
||||
import {
|
||||
mixpanel_track,
|
||||
mixpanel_init,
|
||||
mixpanel_opt_out_tracking,
|
||||
mixpanel_is_loaded,
|
||||
} from '@/helpers/mixpanel'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { isDev, getOS } from '@/helpers/utils.js'
|
||||
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { window as TauriWindow } from '@tauri-apps/api'
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
|
||||
import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue'
|
||||
import { install_from_file } from './helpers/pack'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
||||
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
|
||||
import { useInstall } from '@/store/install.js'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-shell'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const urlModal = ref(null)
|
||||
const isLoading = ref(true)
|
||||
|
||||
const videoPlaying = ref(false)
|
||||
const offline = ref(false)
|
||||
const showOnboarding = ref(false)
|
||||
|
||||
const onboardingVideo = ref()
|
||||
|
||||
defineExpose({
|
||||
initialize: async () => {
|
||||
isLoading.value = false
|
||||
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering, fully_onboarded } =
|
||||
await get()
|
||||
const os = await getOS()
|
||||
// video should play if the user is not on linux, and has not onboarded
|
||||
videoPlaying.value = !fully_onboarded && os !== 'Linux'
|
||||
const dev = await isDev()
|
||||
const version = await getVersion()
|
||||
showOnboarding.value = !fully_onboarded
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
|
||||
mixpanel_init('014c7d6a336d0efaefe3aca91063748d', { debug: dev, persistence: 'localStorage' })
|
||||
if (opt_out_analytics) {
|
||||
mixpanel_opt_out_tracking()
|
||||
}
|
||||
mixpanel_track('Launched', { version, dev, fully_onboarded })
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
if ((await type()) === 'Darwin') {
|
||||
document.getElementsByTagName('html')[0].classList.add('mac')
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||
}
|
||||
|
||||
offline.value = await isOffline()
|
||||
await offline_listener((b) => {
|
||||
offline.value = b
|
||||
})
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
})
|
||||
)
|
||||
|
||||
if (showOnboarding.value) {
|
||||
onboardingVideo.value.play()
|
||||
}
|
||||
},
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const confirmClose = async () => {
|
||||
const confirmed = await confirm(
|
||||
'An action is currently in progress. Are you sure you want to exit?',
|
||||
{
|
||||
title: 'Modrinth',
|
||||
type: 'warning',
|
||||
}
|
||||
const showOnboarding = ref(false)
|
||||
const nativeDecorations = ref(false)
|
||||
|
||||
const os = ref('')
|
||||
|
||||
const stateInitialized = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
await useCheckDisableMouseover()
|
||||
})
|
||||
|
||||
async function setupApp() {
|
||||
stateInitialized.value = true
|
||||
const {
|
||||
native_decorations,
|
||||
theme,
|
||||
telemetry,
|
||||
collapsed_navigation,
|
||||
advanced_rendering,
|
||||
onboarded,
|
||||
default_page,
|
||||
} = await get()
|
||||
|
||||
if (default_page && default_page !== 'Home') {
|
||||
await router.push({ name: default_page })
|
||||
}
|
||||
|
||||
os.value = await getOS()
|
||||
const dev = await isDev()
|
||||
const version = await getVersion()
|
||||
showOnboarding.value = !onboarded
|
||||
|
||||
nativeDecorations.value = native_decorations
|
||||
if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations)
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
|
||||
initAnalytics()
|
||||
if (!telemetry) {
|
||||
optOutAnalytics()
|
||||
}
|
||||
if (dev) debugAnalytics()
|
||||
trackEvent('Launched', { version, dev, onboarded })
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
const osType = await type()
|
||||
if (osType === 'macos') {
|
||||
document.getElementsByTagName('html')[0].classList.add('mac')
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||
}
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
}),
|
||||
)
|
||||
return confirmed
|
||||
|
||||
get_opening_command().then(handleCommand)
|
||||
}
|
||||
|
||||
const stateFailed = ref(false)
|
||||
initialize_state()
|
||||
.then(() => {
|
||||
setupApp().catch((err) => {
|
||||
stateFailed.value = true
|
||||
console.error(err)
|
||||
error.showError(err, null, false, 'state_init')
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
stateFailed.value = true
|
||||
console.error('Failed to initialize app', err)
|
||||
error.showError(err, null, false, 'state_init')
|
||||
})
|
||||
|
||||
const handleClose = async () => {
|
||||
// State should respond immeiately if it's safe to close
|
||||
// If not, code is deadlocked or worse, so wait 2 seconds and then ask the user to confirm closing
|
||||
// (Exception: if the user is changing config directory, which takes control of the state, and it's taking a significant amount of time for some reason)
|
||||
const isSafe = await Promise.race([
|
||||
check_safe_loading_bars_complete(),
|
||||
new Promise((r) => setTimeout(r, 2000)),
|
||||
])
|
||||
if (!isSafe) {
|
||||
const response = await confirmClose()
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
}
|
||||
await await_sync()
|
||||
await TauriWindow.getCurrent().close()
|
||||
await getCurrentWindow().close()
|
||||
}
|
||||
|
||||
TauriWindow.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
|
||||
await handleClose()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
router.afterEach((to, from, failure) => {
|
||||
if (mixpanel_is_loaded()) {
|
||||
mixpanel_track('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
}
|
||||
trackEvent('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
})
|
||||
const route = useRoute()
|
||||
const isOnBrowse = computed(() => route.path.startsWith('/browse'))
|
||||
|
||||
const loading = useLoading()
|
||||
loading.setEnabled(false)
|
||||
|
||||
const notifications = useNotifications()
|
||||
const notificationsWrapper = ref(null)
|
||||
const notificationsWrapper = ref()
|
||||
|
||||
const error = useError()
|
||||
const errorModal = ref()
|
||||
|
||||
const install = useInstall()
|
||||
const modInstallModal = ref()
|
||||
const installConfirmModal = ref()
|
||||
const incompatibilityWarningModal = ref()
|
||||
|
||||
onMounted(() => {
|
||||
invoke('show_window')
|
||||
|
||||
watch(notificationsWrapper, () => {
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
|
||||
error.setErrorModal(errorModal.value)
|
||||
|
||||
install.setIncompatibilityWarningModal(incompatibilityWarningModal)
|
||||
install.setInstallConfirmModal(installConfirmModal)
|
||||
install.setModInstallModal(modInstallModal)
|
||||
})
|
||||
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
@@ -162,13 +173,7 @@ document.querySelector('body').addEventListener('click', function (e) {
|
||||
!target.href.startsWith('http://localhost') &&
|
||||
!target.href.startsWith('https://tauri.localhost')
|
||||
) {
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: target.href,
|
||||
},
|
||||
})
|
||||
open(target.href)
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
@@ -193,25 +198,28 @@ document.querySelector('body').addEventListener('auxclick', function (e) {
|
||||
|
||||
const accounts = ref(null)
|
||||
|
||||
command_listener((e) => {
|
||||
console.log(e)
|
||||
urlModal.value.show(e)
|
||||
})
|
||||
command_listener(handleCommand)
|
||||
async function handleCommand(e) {
|
||||
if (!e) return
|
||||
|
||||
if (e.event === 'RunMRPack') {
|
||||
// RunMRPack should directly install a local mrpack given a path
|
||||
if (e.path.endsWith('.mrpack')) {
|
||||
await install_from_file(e.path).catch(handleError)
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Other commands are URL-based (deep linking)
|
||||
urlModal.value.show(e)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<StickyTitleBar v-if="videoPlaying" />
|
||||
<video
|
||||
v-if="videoPlaying"
|
||||
ref="onboardingVideo"
|
||||
class="video"
|
||||
src="@/assets/video.mp4"
|
||||
autoplay
|
||||
@ended="videoPlaying = false"
|
||||
/>
|
||||
<SplashScreen v-else-if="!videoPlaying && isLoading" app-loading />
|
||||
<OnboardingScreen v-else-if="showOnboarding" :finish="() => (showOnboarding = false)" />
|
||||
<div v-else class="container">
|
||||
<SplashScreen v-if="!stateFailed" ref="splashScreen" data-tauri-drag-region />
|
||||
<div v-if="stateInitialized" class="app-container">
|
||||
<div class="nav-container">
|
||||
<div class="nav-section">
|
||||
<suspense>
|
||||
@@ -266,23 +274,18 @@ command_listener((e) => {
|
||||
</Suspense>
|
||||
</section>
|
||||
</div>
|
||||
<section class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.minimize()">
|
||||
<section v-if="!nativeDecorations" class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
|
||||
<MinimizeIcon />
|
||||
</Button>
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
||||
<Button
|
||||
class="titlebar-button"
|
||||
icon-only
|
||||
@click="() => getCurrentWindow().toggleMaximize()"
|
||||
>
|
||||
<MaximizeIcon />
|
||||
</Button>
|
||||
<Button
|
||||
class="titlebar-button close"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
saveWindowState(StateFlags.ALL)
|
||||
handleClose()
|
||||
}
|
||||
"
|
||||
>
|
||||
<Button class="titlebar-button close" icon-only @click="handleClose">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</section>
|
||||
@@ -304,6 +307,10 @@ command_listener((e) => {
|
||||
</div>
|
||||
<URLConfirmModal ref="urlModal" />
|
||||
<Notifications ref="notificationsWrapper" />
|
||||
<ErrorModal ref="errorModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
<InstallConfirmModal ref="installConfirmModal" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -355,7 +362,7 @@ command_listener((e) => {
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
.app-container {
|
||||
--appbar-height: 3.25rem;
|
||||
--sidebar-width: 4.5rem;
|
||||
|
||||
@@ -366,13 +373,13 @@ command_listener((e) => {
|
||||
|
||||
.view {
|
||||
width: calc(100% - var(--sidebar-width));
|
||||
background-color: var(--color-raised-bg);
|
||||
|
||||
.appbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
background: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
text-align: center;
|
||||
padding: var(--gap-md);
|
||||
height: 3.25rem;
|
||||
@@ -388,6 +395,7 @@ command_listener((e) => {
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--color-bg);
|
||||
border-top-left-radius: var(--radius-xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,55 +463,6 @@ command_listener((e) => {
|
||||
}
|
||||
}
|
||||
|
||||
.instance-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 70%;
|
||||
margin: 0.4rem;
|
||||
|
||||
p:nth-child(1) {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
& > p {
|
||||
color: var(--color-base);
|
||||
margin: 0.8rem 0;
|
||||
font-size: 0.7rem;
|
||||
line-height: 0.8125rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.user-section {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 4.375rem;
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.username {
|
||||
margin-bottom: 0.3rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 400;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -514,11 +473,44 @@ command_listener((e) => {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.video {
|
||||
margin-top: 2.25rem;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 2.25rem);
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-md);
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-sm) 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.mac {
|
||||
.nav-container {
|
||||
padding-top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
|
||||
.account-card,
|
||||
.card-section {
|
||||
top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
.windows {
|
||||
.fake-appbar {
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
right: 8rem;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
right: 8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 952 B After Width: | Height: | Size: 952 B |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 901 B After Width: | Height: | Size: 901 B |
|
Before Width: | Height: | Size: 757 B After Width: | Height: | Size: 757 B |
22
apps/app-frontend/src/assets/external/google.svg
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg
|
||||
data-v-8c2610d6=""
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 100 100"
|
||||
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2;"><circle cx="50" cy="50" r="50" style="fill:#fff;"
|
||||
/>
|
||||
<g transform="translate(14.39 14.302) scale(.09916)">
|
||||
<path
|
||||
d="M-4117.16-2597.44v139.42h193.74c-8.51 44.84-34.04 82.8-72.33 108.33l116.84 90.66c68.07-62.84 107.35-155.13 107.35-264.77 0-25.53-2.29-50.07-6.55-73.63l-339.05-.01Z"
|
||||
style="fill:#4285f4;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="m-4318.92-2463.46-26.35 20.17-93.28 72.65c59.24 117.49 180.65 198.66 321.38 198.66 97.2 0 178.69-32.07 238.25-87.05l-116.83-90.66c-32.08 21.6-72.99 34.69-121.42 34.69-93.6 0-173.13-63.16-201.6-148.25l-.15-.21Z"
|
||||
style="fill:#34a853;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="M-4438.55-2693.33c-24.54 48.44-38.61 103.09-38.61 161.34 0 58.26 14.07 112.91 38.61 161.35 0 .32 119.79-92.95 119.79-92.95-7.2-21.6-11.46-44.5-11.46-68.4 0-23.89 4.26-46.8 11.46-68.4l-119.79-92.94Z"
|
||||
style="fill:#fbbc05;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="M-4117.16-2748.64c53.02 0 100.14 18.33 137.78 53.67l103.09-103.09c-62.51-58.25-143.67-93.93-240.87-93.93-140.73 0-262.15 80.84-321.39 198.66l119.79 92.95c28.47-85.09 108-148.26 201.6-148.26Z"
|
||||
style="fill:#ea4335;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 718 B After Width: | Height: | Size: 718 B |
|
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 415 B |
|
Before Width: | Height: | Size: 274 B After Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 924 B After Width: | Height: | Size: 924 B |
|
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 881 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 427 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 253 B After Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 461 B After Width: | Height: | Size: 461 B |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 309 B |
BIN
apps/app-frontend/src/assets/loading/cube.png
Normal file
|
After Width: | Height: | Size: 937 KiB |
@@ -1,3 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: var(--font-standard);
|
||||
color-scheme: dark;
|
||||
@@ -69,44 +73,15 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
.mac {
|
||||
.nav-container {
|
||||
padding-top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
|
||||
.account-card,
|
||||
.card-section {
|
||||
top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
.windows {
|
||||
.fake-appbar {
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
right: 8rem;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
right: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: auto;
|
||||
scrollbar-color: var(--color-button-bg) var(--color-bg);
|
||||
scrollbar-color: var(--color-scrollbar) var(--color-bg);
|
||||
}
|
||||
|
||||
/* Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: var(--gap-md);
|
||||
border: 3px solid var(--color-bg);
|
||||
border: 3px solid var(--color-scrollbar);
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@@ -115,7 +90,7 @@ input {
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--color-button-bg);
|
||||
background-color: var(--color-scrollbar);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 3px solid var(--color-bg);
|
||||
}
|
||||
@@ -135,3 +110,5 @@ img {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
@import '@modrinth/assets/omorphia.scss';
|
||||
@@ -9,18 +9,15 @@ import {
|
||||
TrashIcon,
|
||||
StopCircleIcon,
|
||||
EyeIcon,
|
||||
Card,
|
||||
DropdownSelect,
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
Button,
|
||||
formatCategoryHeader,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
} from '@modrinth/assets'
|
||||
import { ConfirmModal, Button, Card, DropdownSelect } from '@modrinth/ui'
|
||||
import { formatCategoryHeader } from '@modrinth/utils'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { remove } from '@/helpers/profile.js'
|
||||
import { duplicate, remove } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -45,17 +42,23 @@ const confirmModal = ref(null)
|
||||
async function deleteProfile() {
|
||||
if (currentDeleteInstance.value) {
|
||||
instanceComponents.value = instanceComponents.value.filter(
|
||||
(x) => x.instance.path !== currentDeleteInstance.value
|
||||
(x) => x.instance.path !== currentDeleteInstance.value,
|
||||
)
|
||||
await remove(currentDeleteInstance.value).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRightClick = (event, item) => {
|
||||
async function duplicateProfile(p) {
|
||||
await duplicate(p).catch(handleError)
|
||||
}
|
||||
|
||||
const handleRightClick = (event, profilePathId) => {
|
||||
const item = instanceComponents.value.find((x) => x.instance.path === profilePathId)
|
||||
const baseOptions = [
|
||||
{ name: 'add_content' },
|
||||
{ type: 'divider' },
|
||||
{ name: 'edit' },
|
||||
{ name: 'duplicate' },
|
||||
{ name: 'open' },
|
||||
{ name: 'copy' },
|
||||
{ type: 'divider' },
|
||||
@@ -82,7 +85,7 @@ const handleRightClick = (event, item) => {
|
||||
color: 'primary',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -100,6 +103,10 @@ const handleOptionsClick = async (args) => {
|
||||
case 'edit':
|
||||
await args.item.seeInstance()
|
||||
break
|
||||
case 'duplicate':
|
||||
if (args.item.instance.install_stage == 'installed')
|
||||
await duplicateProfile(args.item.instance.path)
|
||||
break
|
||||
case 'open':
|
||||
await args.item.openFolder()
|
||||
break
|
||||
@@ -120,46 +127,46 @@ const sortBy = ref('Name')
|
||||
|
||||
const filteredResults = computed(() => {
|
||||
let instances = props.instances.filter((instance) => {
|
||||
return instance.metadata.name.toLowerCase().includes(search.value.toLowerCase())
|
||||
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
||||
})
|
||||
|
||||
if (sortBy.value === 'Name') {
|
||||
instances.sort((a, b) => {
|
||||
return a.metadata.name.localeCompare(b.metadata.name)
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Game version') {
|
||||
instances.sort((a, b) => {
|
||||
return a.metadata.name.localeCompare(b.metadata.game_version)
|
||||
return a.game_version.localeCompare(b.game_version)
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Last played') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
|
||||
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Date created') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.date_created).diff(dayjs(a.metadata.date_created))
|
||||
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Date modified') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.date_modified).diff(dayjs(a.metadata.date_modified))
|
||||
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
||||
})
|
||||
}
|
||||
|
||||
if (filters.value === 'Custom instances') {
|
||||
instances = instances.filter((instance) => {
|
||||
return !instance.metadata?.linked_data
|
||||
return !instance.linked_data
|
||||
})
|
||||
} else if (filters.value === 'Downloaded modpacks') {
|
||||
instances = instances.filter((instance) => {
|
||||
return instance.metadata?.linked_data
|
||||
return instance.linked_data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,7 +174,7 @@ const filteredResults = computed(() => {
|
||||
|
||||
if (group.value === 'Loader') {
|
||||
instances.forEach((instance) => {
|
||||
const loader = formatCategoryHeader(instance.metadata.loader)
|
||||
const loader = formatCategoryHeader(instance.loader)
|
||||
if (!instanceMap.has(loader)) {
|
||||
instanceMap.set(loader, [])
|
||||
}
|
||||
@@ -176,19 +183,19 @@ const filteredResults = computed(() => {
|
||||
})
|
||||
} else if (group.value === 'Game version') {
|
||||
instances.forEach((instance) => {
|
||||
if (!instanceMap.has(instance.metadata.game_version)) {
|
||||
instanceMap.set(instance.metadata.game_version, [])
|
||||
if (!instanceMap.has(instance.game_version)) {
|
||||
instanceMap.set(instance.game_version, [])
|
||||
}
|
||||
|
||||
instanceMap.get(instance.metadata.game_version).push(instance)
|
||||
instanceMap.get(instance.game_version).push(instance)
|
||||
})
|
||||
} else if (group.value === 'Category') {
|
||||
instances.forEach((instance) => {
|
||||
if (instance.metadata.groups.length === 0) {
|
||||
instance.metadata.groups.push('None')
|
||||
if (instance.groups.length === 0) {
|
||||
instance.groups.push('None')
|
||||
}
|
||||
|
||||
for (const category of instance.metadata.groups) {
|
||||
for (const category of instance.groups) {
|
||||
if (!instanceMap.has(category)) {
|
||||
instanceMap.set(category, [])
|
||||
}
|
||||
@@ -223,7 +230,7 @@ const filteredResults = computed(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<ModalConfirm
|
||||
<ConfirmModal
|
||||
ref="confirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
@@ -236,7 +243,7 @@ const filteredResults = computed(() => {
|
||||
<div class="iconified-input">
|
||||
<SearchIcon />
|
||||
<input v-model="search" type="text" placeholder="Search" class="search-input" />
|
||||
<Button @click="() => (search = '')">
|
||||
<Button class="r-btn" @click="() => (search = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -285,11 +292,11 @@ const filteredResults = computed(() => {
|
||||
</div>
|
||||
<section class="instances">
|
||||
<Instance
|
||||
v-for="(instance, index) in instanceSection.value"
|
||||
v-for="instance in instanceSection.value"
|
||||
ref="instanceComponents"
|
||||
:key="instance.path"
|
||||
:key="instance.path + instance.install_stage"
|
||||
:instance="instance"
|
||||
@contextmenu.prevent.stop="(event) => handleRightClick(event, instanceComponents[index])"
|
||||
@contextmenu.prevent.stop="(event) => handleRightClick(event, instance.path)"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
@@ -298,6 +305,7 @@ const filteredResults = computed(() => {
|
||||
<template #stop> <StopCircleIcon /> Stop </template>
|
||||
<template #add_content> <PlusIcon /> Add content </template>
|
||||
<template #edit> <EyeIcon /> View instance </template>
|
||||
<template #duplicate> <ClipboardCopyIcon /> Duplicate instance</template>
|
||||
<template #delete> <TrashIcon /> Delete </template>
|
||||
<template #open> <FolderOpenIcon /> Open folder </template>
|
||||
<template #copy> <ClipboardCopyIcon /> Copy path </template>
|
||||
@@ -11,27 +11,21 @@ import {
|
||||
ExternalIcon,
|
||||
EyeIcon,
|
||||
ChevronRightIcon,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
} from '@modrinth/assets'
|
||||
import { ConfirmModal } from '@modrinth/ui'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
kill_by_uuid,
|
||||
} from '@/helpers/process.js'
|
||||
import { get_by_profile_path } from '@/helpers/process.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { remove, run } from '@/helpers/profile.js'
|
||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { install as pack_install } from '@/helpers/pack.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -50,16 +44,14 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const actualInstances = computed(() =>
|
||||
props.instances.filter((x) => x && x.instances && x.instances[0])
|
||||
props.instances.filter((x) => x && x.instances && x.instances[0]),
|
||||
)
|
||||
|
||||
const modsRow = ref(null)
|
||||
const instanceOptions = ref(null)
|
||||
const instanceComponents = ref(null)
|
||||
const rows = ref(null)
|
||||
const confirmModal = ref(null)
|
||||
const deleteConfirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
@@ -70,11 +62,16 @@ async function deleteProfile() {
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateProfile(p) {
|
||||
await duplicate(p).catch(handleError)
|
||||
}
|
||||
|
||||
const handleInstanceRightClick = async (event, passedInstance) => {
|
||||
const baseOptions = [
|
||||
{ name: 'add_content' },
|
||||
{ type: 'divider' },
|
||||
{ name: 'edit' },
|
||||
{ name: 'duplicate' },
|
||||
{ name: 'open_folder' },
|
||||
{ name: 'copy_path' },
|
||||
{ type: 'divider' },
|
||||
@@ -84,23 +81,24 @@ const handleInstanceRightClick = async (event, passedInstance) => {
|
||||
},
|
||||
]
|
||||
|
||||
const running = await get_all_running_profile_paths().catch(handleError)
|
||||
const runningProcesses = await get_by_profile_path(passedInstance.path).catch(handleError)
|
||||
|
||||
const options = running.includes(passedInstance.path)
|
||||
? [
|
||||
{
|
||||
name: 'stop',
|
||||
color: 'danger',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'play',
|
||||
color: 'primary',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
const options =
|
||||
runningProcesses.length > 0
|
||||
? [
|
||||
{
|
||||
name: 'stop',
|
||||
color: 'danger',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'play',
|
||||
color: 'primary',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
|
||||
instanceOptions.value.showMenu(event, passedInstance, options)
|
||||
}
|
||||
@@ -124,24 +122,24 @@ const handleProjectClick = (event, passedInstance) => {
|
||||
const handleOptionsClick = async (args) => {
|
||||
switch (args.option) {
|
||||
case 'play':
|
||||
await run(args.item.path).catch(handleError)
|
||||
mixpanel_track('InstanceStart', {
|
||||
loader: args.item.metadata.loader,
|
||||
game_version: args.item.metadata.game_version,
|
||||
await run(args.item.path).catch((err) =>
|
||||
handleSevereError(err, { profilePath: args.item.path }),
|
||||
)
|
||||
trackEvent('InstanceStart', {
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
break
|
||||
case 'stop':
|
||||
for (const u of await get_uuids_by_profile_path(args.item.path).catch(handleError)) {
|
||||
await kill_by_uuid(u).catch(handleError)
|
||||
}
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: args.item.metadata.loader,
|
||||
game_version: args.item.metadata.game_version,
|
||||
await kill(args.item.path).catch(handleError)
|
||||
trackEvent('InstanceStop', {
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
break
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
path: `/browse/${args.item.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
path: `/browse/${args.item.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: args.item.path },
|
||||
})
|
||||
break
|
||||
@@ -150,6 +148,9 @@ const handleOptionsClick = async (args) => {
|
||||
path: `/instance/${encodeURIComponent(args.item.path)}/`,
|
||||
})
|
||||
break
|
||||
case 'duplicate':
|
||||
if (args.item.install_stage == 'installed') await duplicateProfile(args.item.path)
|
||||
break
|
||||
case 'delete':
|
||||
currentDeleteInstance.value = args.item.path
|
||||
deleteConfirmModal.value.show()
|
||||
@@ -161,21 +162,8 @@ const handleOptionsClick = async (args) => {
|
||||
await navigator.clipboard.writeText(args.item.path)
|
||||
break
|
||||
case 'install': {
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${args.item.project_id}/version`,
|
||||
'project versions'
|
||||
)
|
||||
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu')
|
||||
|
||||
if (args.item.project_type === 'modpack') {
|
||||
await pack_install(
|
||||
args.item.project_id,
|
||||
versions[0].id,
|
||||
args.item.title,
|
||||
args.item.icon_url
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(args.item.project_id, versions)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'open_link':
|
||||
@@ -189,14 +177,14 @@ const handleOptionsClick = async (args) => {
|
||||
break
|
||||
case 'copy_link':
|
||||
await navigator.clipboard.writeText(
|
||||
`https://modrinth.com/${args.item.project_type}/${args.item.slug}`
|
||||
`https://modrinth.com/${args.item.project_type}/${args.item.slug}`,
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const maxInstancesPerRow = ref(0)
|
||||
const maxProjectsPerRow = ref(0)
|
||||
const maxInstancesPerRow = ref(1)
|
||||
const maxProjectsPerRow = ref(1)
|
||||
|
||||
const calculateCardsPerRow = () => {
|
||||
// Calculate how many cards fit in one row
|
||||
@@ -219,7 +207,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalConfirm
|
||||
<ConfirmModal
|
||||
ref="deleteConfirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
@@ -234,10 +222,10 @@ onUnmounted(() => {
|
||||
<router-link :to="row.route">{{ row.label }}</router-link>
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
<section v-if="row.instances[0].metadata" ref="modsRow" class="instances">
|
||||
<section v-if="row.instance" ref="modsRow" class="instances">
|
||||
<Instance
|
||||
v-for="instance in row.instances.slice(0, maxInstancesPerRow)"
|
||||
:key="instance?.project_id || instance?.id"
|
||||
:key="(instance?.project_id || instance?.id) + instance.install_stage"
|
||||
:instance="instance"
|
||||
@contextmenu.prevent.stop="(event) => handleInstanceRightClick(event, instance)"
|
||||
/>
|
||||
@@ -249,8 +237,6 @@ onUnmounted(() => {
|
||||
ref="instanceComponents"
|
||||
class="item"
|
||||
:project="project"
|
||||
:confirm-modal="confirmModal"
|
||||
:mod-install-modal="modInstallModal"
|
||||
@contextmenu.prevent.stop="(event) => handleProjectClick(event, project)"
|
||||
/>
|
||||
</section>
|
||||
@@ -263,13 +249,12 @@ onUnmounted(() => {
|
||||
<template #edit> <EyeIcon /> View instance </template>
|
||||
<template #delete> <TrashIcon /> Delete </template>
|
||||
<template #open_folder> <FolderOpenIcon /> Open folder </template>
|
||||
<template #duplicate> <ClipboardCopyIcon /> Duplicate instance</template>
|
||||
<template #copy_path> <ClipboardCopyIcon /> Copy path </template>
|
||||
<template #install> <DownloadIcon /> Install </template>
|
||||
<template #open_link> <GlobeIcon /> Open in Modrinth <ExternalIcon /> </template>
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||
</ContextMenu>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
@@ -40,10 +40,12 @@ export default defineComponent({
|
||||
const loading = useLoading()
|
||||
|
||||
watch(loading, (newValue) => {
|
||||
if (newValue.loading) {
|
||||
indicator.start()
|
||||
} else {
|
||||
indicator.finish()
|
||||
if (newValue.barEnabled) {
|
||||
if (newValue.loading) {
|
||||
indicator.start()
|
||||
} else {
|
||||
indicator.finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -66,7 +68,7 @@ export default defineComponent({
|
||||
zIndex: 6,
|
||||
},
|
||||
},
|
||||
slots
|
||||
slots,
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
v-if="mode !== 'isolated'"
|
||||
ref="button"
|
||||
v-tooltip="'Minecraft accounts'"
|
||||
v-tooltip.right="'Minecraft accounts'"
|
||||
class="button-base avatar-button"
|
||||
:class="{ expanded: mode === 'expanded' }"
|
||||
@click="showCard = !showCard"
|
||||
@@ -56,66 +56,23 @@
|
||||
</Button>
|
||||
</Card>
|
||||
</transition>
|
||||
<Modal ref="loginModal" class="modal" header="Signing in">
|
||||
<div class="modal-body">
|
||||
<QrcodeVue :value="loginUrl" class="qr-code" margin="3" size="160" />
|
||||
<div class="modal-text">
|
||||
<div class="label">Copy this code</div>
|
||||
<div class="code-text">
|
||||
<div class="code">
|
||||
{{ loginCode }}
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip="'Copy code'"
|
||||
icon-only
|
||||
large
|
||||
color="raised"
|
||||
@click="() => clipboardWrite(loginCode)"
|
||||
>
|
||||
<ClipboardCopyIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div>And enter it on Microsoft's website to sign in.</div>
|
||||
<div class="iconified-input">
|
||||
<LogInIcon />
|
||||
<input type="text" :value="loginUrl" readonly />
|
||||
<Button
|
||||
v-tooltip="'Open link'"
|
||||
icon-only
|
||||
color="raised"
|
||||
@click="() => clipboardWrite(loginUrl)"
|
||||
>
|
||||
<GlobeIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
PlusIcon,
|
||||
TrashIcon,
|
||||
LogInIcon,
|
||||
Modal,
|
||||
GlobeIcon,
|
||||
ClipboardCopyIcon,
|
||||
} from 'omorphia'
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { PlusIcon, TrashIcon, LogInIcon } from '@modrinth/assets'
|
||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
||||
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
||||
import {
|
||||
users,
|
||||
remove_user,
|
||||
authenticate_begin_flow,
|
||||
authenticate_await_completion,
|
||||
set_default_user,
|
||||
login as login_flow,
|
||||
get_default_user,
|
||||
} from '@/helpers/auth'
|
||||
import { get, set } from '@/helpers/settings'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
defineProps({
|
||||
mode: {
|
||||
@@ -127,15 +84,11 @@ defineProps({
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const loginCode = ref(null)
|
||||
|
||||
const settings = ref({})
|
||||
const accounts = ref([])
|
||||
const loginUrl = ref('')
|
||||
const loginModal = ref(null)
|
||||
const accounts = ref({})
|
||||
const defaultUser = ref()
|
||||
|
||||
async function refreshValues() {
|
||||
settings.value = await get().catch(handleError)
|
||||
defaultUser.value = await get_default_user().catch(handleError)
|
||||
accounts.value = await users().catch(handleError)
|
||||
}
|
||||
defineExpose({
|
||||
@@ -144,47 +97,28 @@ defineExpose({
|
||||
await refreshValues()
|
||||
|
||||
const displayAccounts = computed(() =>
|
||||
accounts.value.filter((account) => settings.value.default_user !== account.id)
|
||||
accounts.value.filter((account) => defaultUser.value !== account.id),
|
||||
)
|
||||
|
||||
const selectedAccount = computed(() =>
|
||||
accounts.value.find((account) => account.id === settings.value.default_user)
|
||||
accounts.value.find((account) => account.id === defaultUser.value),
|
||||
)
|
||||
|
||||
async function setAccount(account) {
|
||||
settings.value.default_user = account.id
|
||||
await set(settings.value).catch(handleError)
|
||||
defaultUser.value = account.id
|
||||
await set_default_user(account.id).catch(handleError)
|
||||
emit('change')
|
||||
}
|
||||
|
||||
const clipboardWrite = async (a) => {
|
||||
navigator.clipboard.writeText(a)
|
||||
}
|
||||
|
||||
async function login() {
|
||||
const loginSuccess = await authenticate_begin_flow().catch(handleError)
|
||||
|
||||
loginModal.value.show()
|
||||
loginCode.value = loginSuccess.user_code
|
||||
loginUrl.value = loginSuccess.verification_uri
|
||||
await window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: loginSuccess.verification_uri,
|
||||
},
|
||||
})
|
||||
|
||||
const loggedIn = await authenticate_await_completion().catch(handleError)
|
||||
loginModal.value.hide()
|
||||
const loggedIn = await login_flow().catch(handleSevereError)
|
||||
|
||||
if (loggedIn) {
|
||||
await setAccount(loggedIn)
|
||||
await refreshValues()
|
||||
}
|
||||
|
||||
loginModal.value.hide()
|
||||
mixpanel_track('AccountLogIn')
|
||||
trackEvent('AccountLogIn')
|
||||
}
|
||||
|
||||
const logout = async (id) => {
|
||||
@@ -196,7 +130,7 @@ const logout = async (id) => {
|
||||
} else {
|
||||
emit('change')
|
||||
}
|
||||
mixpanel_track('AccountLogOut')
|
||||
trackEvent('AccountLogOut')
|
||||
}
|
||||
|
||||
let showCard = ref(false)
|
||||
@@ -214,6 +148,12 @@ const handleClickOutside = (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = await process_listener(async (e) => {
|
||||
if (e.event === 'launched') {
|
||||
await refreshValues()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
@@ -221,6 +161,10 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlisten()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
63
apps/app-frontend/src/components/ui/AddContentButton.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { Button, OverflowMenu } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleAddContentFromFile = async () => {
|
||||
const newProject = await open({ multiple: true })
|
||||
if (!newProject) return
|
||||
|
||||
for (const project of newProject) {
|
||||
await add_project_from_path(props.instance.path, project).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="joined-buttons">
|
||||
<Button color="primary" @click="handleSearchContent"><SearchIcon /> Add content </Button>
|
||||
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
{
|
||||
id: 'search',
|
||||
action: handleSearchContent,
|
||||
},
|
||||
{
|
||||
id: 'from_file',
|
||||
action: handleAddContentFromFile,
|
||||
},
|
||||
]"
|
||||
class="btn btn-primary btn-dropdown-animation icon-only"
|
||||
>
|
||||
<DropdownIcon />
|
||||
<template #search>
|
||||
<SearchIcon />
|
||||
<span class="no-wrap"> Search </span>
|
||||
</template>
|
||||
<template #from_file>
|
||||
<FolderOpenIcon />
|
||||
<span class="no-wrap"> Add from file </span>
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -31,7 +31,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ChevronRightIcon, Button, ChevronLeftIcon } from 'omorphia'
|
||||
import { ChevronRightIcon, ChevronLeftIcon } from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
@@ -44,8 +45,8 @@ const breadcrumbs = computed(() => {
|
||||
route.meta.useContext === true
|
||||
? breadcrumbData.context
|
||||
: route.meta.useRootContext === true
|
||||
? breadcrumbData.rootContext
|
||||
: null
|
||||
? breadcrumbData.rootContext
|
||||
: null
|
||||
return additionalContext ? [additionalContext, ...route.meta.breadcrumb] : route.meta.breadcrumb
|
||||
})
|
||||
</script>
|
||||
@@ -11,7 +11,11 @@
|
||||
>
|
||||
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
|
||||
<hr v-if="option.type === 'divider'" class="divider" />
|
||||
<div v-else class="item clickable" :class="[option.color ?? 'base']">
|
||||
<div
|
||||
v-else-if="!(isLinkedData(item) && option.name === `add_content`)"
|
||||
class="item clickable"
|
||||
:class="[option.color ?? 'base']"
|
||||
>
|
||||
<slot :name="option.name" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,6 +59,15 @@ defineExpose({
|
||||
},
|
||||
})
|
||||
|
||||
const isLinkedData = (item) => {
|
||||
if (item.instance != undefined && item.instance.linked_data) {
|
||||
return true
|
||||
} else if (item != undefined && item.linked_data) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const hideContextMenu = () => {
|
||||
shown.value = false
|
||||
emit('menu-closed')
|
||||
332
apps/app-frontend/src/components/ui/ErrorModal.vue
Normal file
@@ -0,0 +1,332 @@
|
||||
<script setup>
|
||||
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import { Modal } from '@modrinth/ui'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.js'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const errorModal = ref()
|
||||
const error = ref()
|
||||
const closable = ref(true)
|
||||
|
||||
const title = ref('An error occurred')
|
||||
const errorType = ref('unknown')
|
||||
const supportLink = ref('https://support.modrinth.com')
|
||||
const metadata = ref({})
|
||||
|
||||
defineExpose({
|
||||
async show(errorVal, context, canClose = true, source = null) {
|
||||
closable.value = canClose
|
||||
|
||||
if (errorVal.message && errorVal.message.includes('Minecraft authentication error:')) {
|
||||
title.value = 'Unable to sign in to Minecraft'
|
||||
errorType.value = 'minecraft_auth'
|
||||
supportLink.value =
|
||||
'https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues'
|
||||
|
||||
if (
|
||||
errorVal.message.includes('existing connection was forcibly closed') ||
|
||||
errorVal.message.includes('error sending request for url')
|
||||
) {
|
||||
metadata.value.network = true
|
||||
}
|
||||
if (errorVal.message.includes('because the target machine actively refused it')) {
|
||||
metadata.value.hostsFile = true
|
||||
}
|
||||
} else if (errorVal.message && errorVal.message.includes('User is not logged in')) {
|
||||
title.value = 'Sign in to Minecraft'
|
||||
errorType.value = 'minecraft_sign_in'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else if (errorVal.message && errorVal.message.includes('Move directory error:')) {
|
||||
title.value = 'Could not change app directory'
|
||||
errorType.value = 'directory_move'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
|
||||
if (errorVal.message.includes('directory is not writeable')) {
|
||||
metadata.value.readOnly = true
|
||||
}
|
||||
|
||||
if (errorVal.message.includes('Not enough space')) {
|
||||
metadata.value.notEnoughSpace = true
|
||||
}
|
||||
} else if (errorVal.message && errorVal.message.includes('No loader version selected for')) {
|
||||
title.value = 'No loader selected'
|
||||
errorType.value = 'no_loader_version'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
metadata.value.profilePath = context.profilePath
|
||||
} else if (source === 'state_init') {
|
||||
title.value = 'Error initializing Modrinth App'
|
||||
errorType.value = 'state_init'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else {
|
||||
title.value = 'An error occurred'
|
||||
errorType.value = 'unknown'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
metadata.value = {}
|
||||
}
|
||||
|
||||
error.value = errorVal
|
||||
errorModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
const loadingMinecraft = ref(false)
|
||||
async function loginMinecraft() {
|
||||
try {
|
||||
loadingMinecraft.value = true
|
||||
const loggedIn = await login_flow()
|
||||
|
||||
if (loggedIn) {
|
||||
await set_default_user(loggedIn.id).catch(handleError)
|
||||
}
|
||||
|
||||
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
|
||||
loadingMinecraft.value = false
|
||||
errorModal.value.hide()
|
||||
} catch (err) {
|
||||
loadingMinecraft.value = false
|
||||
handleSevereError(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelDirectoryChange() {
|
||||
try {
|
||||
await cancel_directory_change()
|
||||
window.location.reload()
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
function retryDirectoryChange() {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const loadingRepair = ref(false)
|
||||
async function repairInstance() {
|
||||
loadingRepair.value = true
|
||||
try {
|
||||
await install(metadata.value.profilePath, false)
|
||||
errorModal.value.hide()
|
||||
} catch (err) {
|
||||
handleSevereError(err)
|
||||
}
|
||||
loadingRepair.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="errorModal" :header="title" :closable="closable">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<template v-if="errorType === 'minecraft_auth'">
|
||||
<template v-if="metadata.network">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
It looks like there were issues with the Modrinth App connecting to Microsoft's
|
||||
servers. This is often the result of a poor connection, so we recommend trying again
|
||||
to see if it works. If issues continue to persist, follow the steps in
|
||||
<a
|
||||
href="https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues#h_e71a5f805f"
|
||||
>
|
||||
our support article
|
||||
</a>
|
||||
to troubleshoot.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="metadata.hostsFile">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
The Modrinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
||||
remote server rejected the connection. This may indicate that these services are
|
||||
blocked by the hosts file. Please visit
|
||||
<a
|
||||
href="https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues#h_d694a29256"
|
||||
>
|
||||
our support article
|
||||
</a>
|
||||
for steps on how to fix the issue.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h3>Try another Microsoft account</h3>
|
||||
<p>
|
||||
Double check you've signed in with the right account. You may own Minecraft on a
|
||||
different Microsoft account.
|
||||
</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Try another account
|
||||
</button>
|
||||
</div>
|
||||
<h3>Using PC Game Pass, coming from Bedrock, or just bought the game?</h3>
|
||||
<p>
|
||||
Try signing in with the
|
||||
<a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>
|
||||
first. Once you're done, come back here and sign in!
|
||||
</p>
|
||||
</template>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Try signing in again
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="errorType === 'directory_move'">
|
||||
<template v-if="metadata.readOnly">
|
||||
<h3>Change directory permissions</h3>
|
||||
<p>
|
||||
It looks like the Modrinth App is unable to write to the directory you selected.
|
||||
Please adjust the permissions of the directory and try again or cancel the directory
|
||||
change.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="metadata.notEnoughSpace">
|
||||
<h3>Not enough space</h3>
|
||||
<p>
|
||||
It looks like there is not enough space on the disk containing the dirctory you
|
||||
selected Please free up some space and try again or cancel the directory change.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>
|
||||
The Modrinth App is unable to migrate to the new directory you selected. Please
|
||||
contact support for help or cancel the directory change.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="cta-button">
|
||||
<button class="btn" @click="retryDirectoryChange">
|
||||
<UpdatedIcon /> Retry directory change
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="cancelDirectoryChange">
|
||||
<XIcon /> Cancel directory change
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="errorType === 'minecraft_sign_in'">
|
||||
<p>
|
||||
To play this instance, you must sign in through Microsoft below. If you don't have a
|
||||
Minecraft account, you can purchase the game on the
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
|
||||
>Minecraft website</a
|
||||
>.
|
||||
</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Sign in to Minecraft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="errorType === 'state_init'">
|
||||
<p>
|
||||
Modrinth App failed to load correctly. This may be because of a corrupted file, or
|
||||
because the app is missing crucial files.
|
||||
</p>
|
||||
<p>You may be able to fix it through one of the following ways:</p>
|
||||
<ul>
|
||||
<li>Ennsuring you are connected to the internet, then try restarting the app.</li>
|
||||
<li>Redownloading the app.</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template v-else-if="errorType === 'no_loader_version'">
|
||||
<p>The Modrinth App failed to find the loader version for this instance.</p>
|
||||
<p>To resolve this, you need to repair the instance. Click the button below to do so.</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingRepair" @click="repairInstance">
|
||||
<HammerIcon /> Repair instance
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ error.message ?? error }}
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
errorType === 'directory_move' ||
|
||||
errorType === 'minecraft_auth' ||
|
||||
errorType === 'state_init' ||
|
||||
errorType === 'no_loader_version'
|
||||
"
|
||||
>
|
||||
<hr />
|
||||
<p>
|
||||
If nothing is working and you need help, visit
|
||||
<a :href="supportLink">our support page</a>
|
||||
and start a chat using the widget in the bottom right and we will be more than happy to
|
||||
assist! Make sure to provide the following debug information to the agent:
|
||||
</p>
|
||||
<details>
|
||||
<summary>Debug information</summary>
|
||||
{{ error.message ?? error }}
|
||||
</details>
|
||||
</template>
|
||||
</div>
|
||||
<div class="input-group push-right">
|
||||
<a :href="supportLink" class="btn" @click="errorModal.hide()"><ChatIcon /> Get support</a>
|
||||
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.light-mode {
|
||||
--color-orange-bg: rgba(255, 163, 71, 0.2);
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
.oled-mode {
|
||||
--color-orange-bg: rgba(224, 131, 37, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cta-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.warning-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: var(--gap-lg);
|
||||
background-color: var(--color-orange-bg);
|
||||
border: 2px solid var(--color-orange);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-banner__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 700;
|
||||
|
||||
svg {
|
||||
color: var(--color-orange);
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-md);
|
||||
padding: var(--gap-lg);
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import { Button, Checkbox, Modal, SendIcon, XIcon } from 'omorphia'
|
||||
import { XIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { Button, Checkbox, Modal } from '@modrinth/ui'
|
||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { export_profile_mrpack, get_potential_override_folders } from '@/helpers/profile.js'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { useTheming } from '@/store/theme'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -23,23 +23,36 @@ defineExpose({
|
||||
})
|
||||
|
||||
const exportModal = ref(null)
|
||||
const nameInput = ref(props.instance.metadata.name)
|
||||
const nameInput = ref(props.instance.name)
|
||||
const exportDescription = ref('')
|
||||
const versionInput = ref('1.0.0')
|
||||
const files = ref([])
|
||||
const folders = ref([])
|
||||
const showingFiles = ref(false)
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const initFiles = async () => {
|
||||
const newFolders = new Map()
|
||||
const sep = '/'
|
||||
files.value = []
|
||||
await get_potential_override_folders(props.instance.path).then((filePaths) =>
|
||||
await get_pack_export_candidates(props.instance.path).then((filePaths) =>
|
||||
filePaths
|
||||
.map((folder) => ({
|
||||
path: folder,
|
||||
name: folder.split(sep).pop(),
|
||||
selected: false,
|
||||
selected:
|
||||
folder.startsWith('mods') ||
|
||||
folder.startsWith('datapacks') ||
|
||||
folder.startsWith('resourcepacks') ||
|
||||
folder.startsWith('shaderpacks') ||
|
||||
folder.startsWith('config'),
|
||||
disabled:
|
||||
folder === 'profile.json' ||
|
||||
folder.startsWith('modrinth_logs') ||
|
||||
folder.startsWith('.fabric'),
|
||||
}))
|
||||
.filter((pathData) => !pathData.path.includes('.DS_Store'))
|
||||
.forEach((pathData) => {
|
||||
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
|
||||
if (parent !== '') {
|
||||
@@ -51,7 +64,7 @@ const initFiles = async () => {
|
||||
} else {
|
||||
files.value.push(pathData)
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
folders.value = [...newFolders.entries()].map(([name, value]) => [
|
||||
{
|
||||
@@ -83,7 +96,9 @@ const exportPack = async () => {
|
||||
props.instance.path,
|
||||
outputPath + `/${nameInput.value} ${versionInput.value}.mrpack`,
|
||||
filesToExport,
|
||||
versionInput.value
|
||||
versionInput.value,
|
||||
exportDescription.value,
|
||||
nameInput.value,
|
||||
).catch((err) => handleError(err))
|
||||
exportModal.value.hide()
|
||||
}
|
||||
@@ -98,7 +113,7 @@ const exportPack = async () => {
|
||||
<div class="iconified-input">
|
||||
<PackageIcon />
|
||||
<input v-model="nameInput" type="text" placeholder="Modpack name" class="input" />
|
||||
<Button @click="nameInput = ''">
|
||||
<Button class="r-btn" @click="nameInput = ''">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -108,16 +123,36 @@ const exportPack = async () => {
|
||||
<div class="iconified-input">
|
||||
<VersionIcon />
|
||||
<input v-model="versionInput" type="text" placeholder="1.0.0" class="input" />
|
||||
<Button @click="versionInput = ''">
|
||||
<Button class="r-btn" @click="versionInput = ''">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<div class="labeled_input">
|
||||
<p>Description</p>
|
||||
|
||||
<div class="textarea-wrapper">
|
||||
<textarea v-model="exportDescription" placeholder="Enter modpack description..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table">
|
||||
<div class="table-head">
|
||||
<div class="table-cell">Select files as overrides</div>
|
||||
<div class="table-cell row-wise">
|
||||
Select files and folders to include in pack
|
||||
<Button
|
||||
class="sleek-primary collapsed-button"
|
||||
icon-only
|
||||
@click="() => (showingFiles = !showingFiles)"
|
||||
>
|
||||
<PlusIcon v-if="!showingFiles" />
|
||||
<XIcon v-else />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div v-if="showingFiles" class="table-content">
|
||||
<div v-for="[path, children] of folders" :key="path.name" class="table-row">
|
||||
<div class="table-cell file-entry">
|
||||
<div class="file-primary">
|
||||
@@ -125,6 +160,7 @@ const exportPack = async () => {
|
||||
:model-value="children.every((child) => child.selected)"
|
||||
:label="path.name"
|
||||
class="select-checkbox"
|
||||
:disabled="children.every((x) => x.disabled)"
|
||||
@update:model-value="
|
||||
(newValue) => children.forEach((child) => (child.selected = newValue))
|
||||
"
|
||||
@@ -137,7 +173,12 @@ const exportPack = async () => {
|
||||
</div>
|
||||
<div v-if="path.showingMore" class="file-secondary">
|
||||
<div v-for="child in children" :key="child.path" class="file-secondary-row">
|
||||
<Checkbox v-model="child.selected" :label="child.name" class="select-checkbox" />
|
||||
<Checkbox
|
||||
v-model="child.selected"
|
||||
:label="child.name"
|
||||
class="select-checkbox"
|
||||
:disabled="child.disabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +186,12 @@ const exportPack = async () => {
|
||||
<div v-for="file in files" :key="file.path" class="table-row">
|
||||
<div class="table-cell file-entry">
|
||||
<div class="file-primary">
|
||||
<Checkbox v-model="file.selected" :label="file.name" class="select-checkbox" />
|
||||
<Checkbox
|
||||
v-model="file.selected"
|
||||
:label="file.name"
|
||||
:disabled="file.disabled"
|
||||
class="select-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,10 +202,6 @@ const exportPack = async () => {
|
||||
<XIcon />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled>
|
||||
<SendIcon />
|
||||
Share
|
||||
</Button>
|
||||
<Button color="primary" @click="exportPack">
|
||||
<PackageIcon />
|
||||
Export
|
||||
@@ -240,4 +282,24 @@ const exportPack = async () => {
|
||||
gap: var(--gap-sm);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row-wise {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.textarea-wrapper {
|
||||
// margin-top: 1rem;
|
||||
height: 12rem;
|
||||
|
||||
textarea {
|
||||
max-height: 12rem;
|
||||
}
|
||||
|
||||
.preview {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
259
apps/app-frontend/src/components/ui/Instance.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
|
||||
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const playing = ref(false)
|
||||
|
||||
const modLoading = computed(() => props.instance.install_stage !== 'installed')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const seeInstance = async () => {
|
||||
await router.push(`/instance/${encodeURIComponent(props.instance.path)}/`)
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningProcesses = await get_by_profile_path(props.instance.path).catch(handleError)
|
||||
|
||||
playing.value = runningProcesses.length > 0
|
||||
}
|
||||
|
||||
const play = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
modLoading.value = true
|
||||
await run(props.instance.path).catch((err) =>
|
||||
handleSevereError(err, { profilePath: props.instance.path }),
|
||||
)
|
||||
modLoading.value = false
|
||||
|
||||
trackEvent('InstancePlay', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const stop = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
await kill(props.instance.path).catch(handleError)
|
||||
|
||||
trackEvent('InstanceStop', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const openFolder = async () => {
|
||||
await showProfileInFolder(props.instance.path)
|
||||
}
|
||||
|
||||
const addContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
play,
|
||||
stop,
|
||||
seeInstance,
|
||||
openFolder,
|
||||
addContent,
|
||||
instance: props.instance,
|
||||
})
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
if (e.event === 'finished' && e.profile_path_id === props.instance.path) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="instance">
|
||||
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
|
||||
<Avatar
|
||||
size="lg"
|
||||
:src="props.instance.icon_path ? convertFileSrc(props.instance.icon_path) : null"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.name }}</p>
|
||||
<p
|
||||
v-if="
|
||||
props.instance.install_stage === 'installing' ||
|
||||
props.instance.install_stage === 'not_installed' ||
|
||||
props.instance.install_stage === 'pack_installing'
|
||||
"
|
||||
class="description"
|
||||
>
|
||||
Installing...
|
||||
</p>
|
||||
<p v-else class="description">
|
||||
{{ props.instance.loader }}
|
||||
{{ props.instance.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
v-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="(e) => play(e, 'InstanceCard')">
|
||||
<PlayIcon />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.loading-indicator {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
|
||||
svg {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.instance {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: calc(var(--gap-md) + 4.25rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
z-index: 1;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: calc(var(--gap-md) * 2);
|
||||
bottom: 3.25rem;
|
||||
opacity: 0;
|
||||
transition:
|
||||
0.2s ease-in-out bottom,
|
||||
0.2s ease-in-out opacity,
|
||||
0.1s ease-in-out filter !important;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-floating);
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&.install {
|
||||
background: var(--color-brand);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.stop {
|
||||
background: var(--color-red);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.loading-cta {
|
||||
background: hsl(220, 11%, 10%) !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: var(--gap-md);
|
||||
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||
margin-bottom: 0;
|
||||
|
||||
.mod-image {
|
||||
--size: 100%;
|
||||
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
aspect-ratio: 1 / 1 !important;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 110%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-base);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
font-size: 0.775rem;
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,13 @@
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Name</p>
|
||||
<input v-model="profile_name" autocomplete="off" class="text-input" type="text" />
|
||||
<input
|
||||
v-model="profile_name"
|
||||
autocomplete="off"
|
||||
class="text-input"
|
||||
type="text"
|
||||
maxlength="100"
|
||||
/>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
@@ -104,7 +110,7 @@
|
||||
placeholder="Path to launcher"
|
||||
@change="setPath"
|
||||
/>
|
||||
<Button @click="() => (selectedLauncherPath = '')">
|
||||
<Button class="r-btn" @click="() => (selectedLauncherPath = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -171,14 +177,14 @@
|
||||
loading
|
||||
? 'Importing...'
|
||||
: Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.some((e) => e.selected)
|
||||
? `Import ${
|
||||
Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.filter((e) => e.selected).length
|
||||
} profiles`
|
||||
: 'Select profiles to import'
|
||||
.some((e) => e.selected)
|
||||
? `Import ${
|
||||
Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.filter((e) => e.selected).length
|
||||
} profiles`
|
||||
: 'Select profiles to import'
|
||||
}}
|
||||
</Button>
|
||||
<ProgressBar
|
||||
@@ -192,34 +198,25 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Chips,
|
||||
Modal,
|
||||
PlusIcon,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
CodeIcon,
|
||||
Checkbox,
|
||||
FolderOpenIcon,
|
||||
InfoIcon,
|
||||
FolderSearchIcon,
|
||||
UpdatedIcon,
|
||||
} from 'omorphia'
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import {
|
||||
get_game_versions,
|
||||
get_fabric_versions,
|
||||
get_forge_versions,
|
||||
get_quilt_versions,
|
||||
} from '@/helpers/metadata'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
@@ -267,13 +264,13 @@ defineExpose({
|
||||
hide()
|
||||
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
|
||||
await install_from_file(event.payload[0]).catch(handleError)
|
||||
mixpanel_track('InstanceCreate', {
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
mixpanel_track('InstanceCreateStart', { source: 'CreationModal' })
|
||||
trackEvent('InstanceCreateStart', { source: 'CreationModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -293,21 +290,28 @@ onUnmounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] =
|
||||
await Promise.all([
|
||||
get_fabric_versions().then(shallowRef).catch(handleError),
|
||||
get_forge_versions().then(shallowRef).catch(handleError),
|
||||
get_quilt_versions().then(shallowRef).catch(handleError),
|
||||
get_game_versions().then(shallowRef).catch(handleError),
|
||||
get_loaders()
|
||||
.then((value) =>
|
||||
value
|
||||
.filter((item) => item.supported_project_types.includes('modpack'))
|
||||
.map((item) => item.name.toLowerCase())
|
||||
)
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
const [
|
||||
fabric_versions,
|
||||
forge_versions,
|
||||
quilt_versions,
|
||||
neoforge_versions,
|
||||
all_game_versions,
|
||||
loaders,
|
||||
] = await Promise.all([
|
||||
get_loader_versions('fabric').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('forge').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('quilt').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('neo').then(shallowRef).catch(handleError),
|
||||
get_game_versions().then(shallowRef).catch(handleError),
|
||||
get_loaders()
|
||||
.then((value) =>
|
||||
value
|
||||
.filter((item) => item.supported_project_types.includes('modpack'))
|
||||
.map((item) => item.name.toLowerCase()),
|
||||
)
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const game_versions = computed(() => {
|
||||
@@ -320,6 +324,8 @@ const game_versions = computed(() => {
|
||||
defaultVal &= forge_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'quilt') {
|
||||
defaultVal &= quilt_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'neoforge') {
|
||||
defaultVal &= neoforge_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
}
|
||||
|
||||
return defaultVal
|
||||
@@ -351,10 +357,10 @@ const create_instance = async () => {
|
||||
game_version.value,
|
||||
loader.value,
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
icon.value,
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceCreate', {
|
||||
trackEvent('InstanceCreate', {
|
||||
profile_name: profile_name.value,
|
||||
game_version: game_version.value,
|
||||
loader: loader.value,
|
||||
@@ -376,7 +382,7 @@ const upload_icon = async () => {
|
||||
})
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
@@ -394,6 +400,10 @@ const selectable_versions = computed(() => {
|
||||
.loaders.map((item) => item.id)
|
||||
} else if (loader.value === 'quilt') {
|
||||
return quilt_versions.value.gameVersions[0].loaders.map((item) => item.id)
|
||||
} else if (loader.value === 'neoforge') {
|
||||
return neoforge_versions.value.gameVersions
|
||||
.find((item) => item.id === game_version.value)
|
||||
.loaders.map((item) => item.id)
|
||||
}
|
||||
}
|
||||
return []
|
||||
@@ -409,7 +419,7 @@ const openFile = async () => {
|
||||
hide()
|
||||
await install_from_file(newProject).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceCreate', {
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileOpen',
|
||||
})
|
||||
}
|
||||
@@ -421,7 +431,7 @@ const profiles = ref(
|
||||
['ATLauncher', []],
|
||||
['Curseforge', []],
|
||||
['PrismLauncher', []],
|
||||
])
|
||||
]),
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -450,7 +460,7 @@ const promises = profileOptions.value.map(async (option) => {
|
||||
profileOptions.value.find((profile) => profile.name === option.name).path = path
|
||||
profiles.value.set(
|
||||
option.name,
|
||||
instances.map((name) => ({ name, selected: false }))
|
||||
instances.map((name) => ({ name, selected: false })),
|
||||
)
|
||||
} catch (error) {
|
||||
// Allow failure silently
|
||||
@@ -469,12 +479,12 @@ const selectLauncherPath = async () => {
|
||||
const reload = async () => {
|
||||
const instances = await get_importable_instances(
|
||||
selectedProfileType.value.name,
|
||||
selectedProfileType.value.path
|
||||
selectedProfileType.value.path,
|
||||
).catch(handleError)
|
||||
if (instances) {
|
||||
profiles.value.set(
|
||||
selectedProfileType.value.name,
|
||||
instances.map((name) => ({ name, selected: false }))
|
||||
instances.map((name) => ({ name, selected: false })),
|
||||
)
|
||||
} else {
|
||||
profiles.value.set(selectedProfileType.value.name, [])
|
||||
@@ -35,17 +35,13 @@
|
||||
</Modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Modal, PlusIcon, CheckIcon, Button, XIcon } from 'omorphia'
|
||||
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import { Modal, Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
find_jre_17_jres,
|
||||
find_jre_18plus_jres,
|
||||
find_jre_8_jres,
|
||||
get_all_jre,
|
||||
} from '@/helpers/jre.js'
|
||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -55,15 +51,7 @@ const currentSelected = ref({})
|
||||
|
||||
defineExpose({
|
||||
show: async (version, currentSelectedJava) => {
|
||||
if (version <= 8 && !!version) {
|
||||
chosenInstallOptions.value = await find_jre_8_jres().catch(handleError)
|
||||
} else if (version >= 18) {
|
||||
chosenInstallOptions.value = await find_jre_18plus_jres().catch(handleError)
|
||||
} else if (version) {
|
||||
chosenInstallOptions.value = await find_jre_17_jres().catch(handleError)
|
||||
} else {
|
||||
chosenInstallOptions.value = await get_all_jre().catch(handleError)
|
||||
}
|
||||
chosenInstallOptions.value = await find_filtered_jres(version).catch(handleError)
|
||||
|
||||
currentSelected.value = currentSelectedJava
|
||||
if (!currentSelected.value) {
|
||||
@@ -79,7 +67,7 @@ const emit = defineEmits(['submit'])
|
||||
function setJavaInstall(javaInstall) {
|
||||
emit('submit', javaInstall)
|
||||
detectJavaModal.value.hide()
|
||||
mixpanel_track('JavaAutoDetect', {
|
||||
trackEvent('JavaAutoDetect', {
|
||||
path: javaInstall.path,
|
||||
version: javaInstall.version,
|
||||
})
|
||||
@@ -18,6 +18,14 @@
|
||||
"
|
||||
/>
|
||||
<span class="installation-buttons">
|
||||
<Button
|
||||
v-if="props.version"
|
||||
:disabled="props.disabled || installingJava"
|
||||
@click="reinstallJava"
|
||||
>
|
||||
<DownloadIcon />
|
||||
{{ installingJava ? 'Installing...' : 'Install recommended' }}
|
||||
</Button>
|
||||
<Button :disabled="props.disabled" @click="autoDetect">
|
||||
<SearchIcon />
|
||||
Auto detect
|
||||
@@ -44,13 +52,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
|
||||
import { find_jre_17_jres, get_jre } from '@/helpers/jre.js'
|
||||
import {
|
||||
SearchIcon,
|
||||
PlayIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
FolderSearchIcon,
|
||||
DownloadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const props = defineProps({
|
||||
version: {
|
||||
@@ -60,7 +76,10 @@ const props = defineProps({
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
path: '',
|
||||
version: '',
|
||||
}),
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -82,15 +101,21 @@ const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const testingJava = ref(false)
|
||||
const testingJavaSuccess = ref(null)
|
||||
|
||||
const installingJava = ref(false)
|
||||
|
||||
async function testJava() {
|
||||
testingJava.value = true
|
||||
let result = await get_jre(props.modelValue ? props.modelValue.path : '')
|
||||
testingJavaSuccess.value = await test_jre(
|
||||
props.modelValue ? props.modelValue.path : '',
|
||||
1,
|
||||
props.version,
|
||||
)
|
||||
testingJava.value = false
|
||||
testingJavaSuccess.value = !!result
|
||||
|
||||
mixpanel_track('JavaTest', {
|
||||
trackEvent('JavaTest', {
|
||||
path: props.modelValue ? props.modelValue.path : '',
|
||||
success: !!result,
|
||||
success: testingJavaSuccess.value,
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -109,13 +134,13 @@ async function handleJavaFileInput() {
|
||||
version: props.version.toString(),
|
||||
architecture: 'x86',
|
||||
}
|
||||
|
||||
mixpanel_track('JavaManualSelect', {
|
||||
path: filePath,
|
||||
version: props.version,
|
||||
})
|
||||
}
|
||||
|
||||
trackEvent('JavaManualSelect', {
|
||||
path: filePath,
|
||||
version: props.version,
|
||||
})
|
||||
|
||||
emit('update:modelValue', result)
|
||||
}
|
||||
}
|
||||
@@ -125,12 +150,34 @@ async function autoDetect() {
|
||||
if (!props.compact) {
|
||||
detectJavaModal.value.show(props.version, props.modelValue)
|
||||
} else {
|
||||
let versions = await find_jre_17_jres().catch(handleError)
|
||||
let versions = await find_filtered_jres(props.version).catch(handleError)
|
||||
if (versions.length > 0) {
|
||||
emit('update:modelValue', versions[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reinstallJava() {
|
||||
installingJava.value = true
|
||||
const path = await auto_install_java(props.version).catch(handleError)
|
||||
let result = await get_jre(path)
|
||||
|
||||
if (!result) {
|
||||
result = {
|
||||
path: path,
|
||||
version: props.version.toString(),
|
||||
architecture: 'x86',
|
||||
}
|
||||
}
|
||||
|
||||
trackEvent('JavaReInstall', {
|
||||
path: path,
|
||||
version: props.version,
|
||||
})
|
||||
|
||||
emit('update:modelValue', result)
|
||||
installingJava.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -157,6 +204,10 @@ async function autoDetect() {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
.btn {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.test-success {
|
||||
188
apps/app-frontend/src/components/ui/ModpackVersionModal.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup>
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import { Button, Modal, Badge } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useTheming } from '@/store/theme'
|
||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
import { SwapIcon } from '@/assets/icons/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
instance: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
modpackVersionModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
const filteredVersions = computed(() => {
|
||||
return props.versions
|
||||
})
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
const installedVersion = computed(() => props.instance?.linked_data?.version_id)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
const inProgress = ref(false)
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const switchVersion = async (versionId) => {
|
||||
inProgress.value = true
|
||||
await update_managed_modrinth_version(props.instance.path, versionId)
|
||||
inProgress.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
ref="modpackVersionModal"
|
||||
class="modpack-version-modal"
|
||||
header="Change modpack version"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<Card v-if="instance.linked_data" class="mod-card">
|
||||
<div class="table">
|
||||
<div class="table-row with-columns table-head">
|
||||
<div class="table-cell table-text download-cell" />
|
||||
<div class="name-cell table-cell table-text">Name</div>
|
||||
<div class="table-cell table-text">Supports</div>
|
||||
</div>
|
||||
<div class="scrollable">
|
||||
<div
|
||||
v-for="version in filteredVersions"
|
||||
:key="version.id"
|
||||
class="table-row with-columns selectable"
|
||||
@click="$router.push(`/project/${$route.params.id}/version/${version.id}`)"
|
||||
>
|
||||
<div class="table-cell table-text">
|
||||
<Button
|
||||
:color="version.id === installedVersion ? '' : 'primary'"
|
||||
icon-only
|
||||
:disabled="inProgress || installing || version.id === installedVersion"
|
||||
@click.stop="() => switchVersion(version.id)"
|
||||
>
|
||||
<SwapIcon v-if="version.id !== installedVersion" />
|
||||
<CheckIcon v-else />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="name-cell table-cell table-text">
|
||||
<div class="version-link">
|
||||
{{ version.name.charAt(0).toUpperCase() + version.name.slice(1) }}
|
||||
<div class="version-badge">
|
||||
<div class="channel-indicator">
|
||||
<Badge
|
||||
:color="releaseColor(version.version_type)"
|
||||
:type="
|
||||
version.version_type.charAt(0).toUpperCase() +
|
||||
version.version_type.slice(1)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{{ version.version_number }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-cell table-text stacked-text">
|
||||
<span>
|
||||
{{
|
||||
version.loaders
|
||||
.map((str) => str.charAt(0).toUpperCase() + str.slice(1))
|
||||
.join(', ')
|
||||
}}
|
||||
</span>
|
||||
<span>
|
||||
{{ version.game_versions.join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.filter-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.with-columns {
|
||||
grid-template-columns: min-content 1fr 1fr;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow-y: auto;
|
||||
max-height: 25rem;
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--color-raised-bg);
|
||||
}
|
||||
|
||||
.mod-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.version-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
|
||||
.version-badge {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.channel-indicator {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stacked-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.download-cell {
|
||||
width: 4rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: var(--gap-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-md);
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 1px solid var(--color-bg);
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,13 @@
|
||||
<script setup>
|
||||
import {
|
||||
Card,
|
||||
Avatar,
|
||||
Button,
|
||||
formatNumber,
|
||||
formatCategory,
|
||||
DownloadIcon,
|
||||
HeartIcon,
|
||||
CalendarIcon,
|
||||
} from 'omorphia'
|
||||
import { Card, Avatar, Button } from '@modrinth/ui'
|
||||
import { DownloadIcon, HeartIcon, CalendarIcon } from '@modrinth/assets'
|
||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { install as pack_install } from '@/helpers/pack.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const router = useRouter()
|
||||
@@ -29,18 +20,6 @@ const props = defineProps({
|
||||
return {}
|
||||
},
|
||||
},
|
||||
confirmModal: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
modInstallModal: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const toColor = computed(() => {
|
||||
@@ -72,40 +51,15 @@ const toTransparent = computed(() => {
|
||||
const install = async (e) => {
|
||||
e?.stopPropagation()
|
||||
installing.value = true
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`,
|
||||
'project versions'
|
||||
)
|
||||
|
||||
if (props.project.project_type === 'modpack') {
|
||||
const packs = Object.values(await list(true).catch(handleError))
|
||||
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
installing.value = true
|
||||
await pack_install(
|
||||
props.project.project_id,
|
||||
versions[0].id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
).catch(handleError)
|
||||
await installVersion(
|
||||
props.project.project_id,
|
||||
null,
|
||||
props.instance ? props.instance.path : null,
|
||||
'ProjectCard',
|
||||
() => {
|
||||
installing.value = false
|
||||
} else
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
versions[0].id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
)
|
||||
} else {
|
||||
props.modInstallModal.show(props.project.project_id, versions)
|
||||
}
|
||||
|
||||
installing.value = false
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
23
apps/app-frontend/src/components/ui/PromotionWrapper.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Promotion } from '@modrinth/ui'
|
||||
import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
|
||||
const showAd = ref(true)
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
if (creds && creds.user_id) {
|
||||
const user = await get_user(creds.user_id).catch(handleError)
|
||||
|
||||
const MIDAS_BITFLAG = 1 << 0
|
||||
if (user && (user.badges & MIDAS_BITFLAG) === MIDAS_BITFLAG) {
|
||||
showAd.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Promotion v-if="showAd" :external="false" query-param="?r=launcher" />
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="action-groups">
|
||||
<a href="https://discord.gg/modrinth" class="link">
|
||||
<a href="https://support.modrinth.com" class="link">
|
||||
<ChatIcon />
|
||||
<span> Get support </span>
|
||||
</a>
|
||||
@@ -15,15 +15,15 @@
|
||||
</Button>
|
||||
<div v-if="offline" class="status">
|
||||
<span class="circle stopped" />
|
||||
<div class="running-text clickable" @click="refreshInternet()">
|
||||
<div class="running-text">
|
||||
<span> Offline </span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedProfile" class="status">
|
||||
<div v-if="selectedProcess" class="status">
|
||||
<span class="circle running" />
|
||||
<div ref="profileButton" class="running-text">
|
||||
<router-link :to="`/instance/${encodeURIComponent(selectedProfile.path)}`">
|
||||
{{ selectedProfile.metadata.name }}
|
||||
<router-link :to="`/instance/${encodeURIComponent(selectedProcess.profile.path)}`">
|
||||
{{ selectedProcess.profile.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-if="currentProcesses.length > 1"
|
||||
@@ -34,7 +34,12 @@
|
||||
<DropdownIcon />
|
||||
</div>
|
||||
</div>
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop()">
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click="stop(selectedProcess)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||
@@ -75,17 +80,17 @@
|
||||
class="profile-card"
|
||||
>
|
||||
<Button
|
||||
v-for="profile in currentProcesses"
|
||||
:key="profile.id"
|
||||
v-for="process in currentProcesses"
|
||||
:key="process.uuid"
|
||||
class="profile-button"
|
||||
@click="selectProfile(profile)"
|
||||
@click="selectProcess(process)"
|
||||
>
|
||||
<div class="text"><span class="circle running" /> {{ profile.metadata.name }}</div>
|
||||
<div class="text"><span class="circle running" /> {{ process.profile.name }}</div>
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click.stop="stop(profile.path)"
|
||||
@click.stop="stop(process)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
@@ -93,7 +98,7 @@
|
||||
v-tooltip="'View logs'"
|
||||
icon-only
|
||||
class="icon-button"
|
||||
@click.stop="goToTerminal(profile.path)"
|
||||
@click.stop="goToTerminal(process.profile.path)"
|
||||
>
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
@@ -103,28 +108,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
DownloadIcon,
|
||||
Card,
|
||||
StopCircleIcon,
|
||||
TerminalSquareIcon,
|
||||
DropdownIcon,
|
||||
} from 'omorphia'
|
||||
import { DownloadIcon, StopCircleIcon, TerminalSquareIcon, DropdownIcon } from '@modrinth/assets'
|
||||
import { Button, Card } from '@modrinth/ui'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import {
|
||||
get_all_running_profiles as getRunningProfiles,
|
||||
kill_by_uuid as killProfile,
|
||||
get_uuids_by_profile_path as getProfileProcesses,
|
||||
} from '@/helpers/process'
|
||||
import { loading_listener, process_listener, offline_listener } from '@/helpers/events'
|
||||
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
import { refreshOffline, isOffline } from '@/helpers/utils.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -135,38 +130,43 @@ const showCard = ref(false)
|
||||
|
||||
const showProfiles = ref(false)
|
||||
|
||||
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
||||
const selectedProfile = ref(currentProcesses.value[0])
|
||||
const currentProcesses = ref([])
|
||||
const selectedProcess = ref()
|
||||
|
||||
const offline = ref(await isOffline().catch(handleError))
|
||||
const refreshInternet = async () => {
|
||||
offline.value = await refreshOffline().catch(handleError)
|
||||
const refresh = async () => {
|
||||
const processes = await getRunningProcesses().catch(handleError)
|
||||
const profiles = await get_many(processes.map((x) => x.profile_path)).catch(handleError)
|
||||
|
||||
currentProcesses.value = processes.map((x) => ({
|
||||
profile: profiles.find((prof) => x.profile_path === prof.path),
|
||||
...x,
|
||||
}))
|
||||
if (!selectedProcess.value || !currentProcesses.value.includes(selectedProcess.value)) {
|
||||
selectedProcess.value = currentProcesses.value[0]
|
||||
}
|
||||
}
|
||||
|
||||
await refresh()
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const unlistenProcess = await process_listener(async () => {
|
||||
await refresh()
|
||||
})
|
||||
|
||||
const unlistenRefresh = await offline_listener(async (b) => {
|
||||
offline.value = b
|
||||
await refresh()
|
||||
})
|
||||
|
||||
const refresh = async () => {
|
||||
currentProcesses.value = await getRunningProfiles().catch(handleError)
|
||||
if (!currentProcesses.value.includes(selectedProfile.value)) {
|
||||
selectedProfile.value = currentProcesses.value[0]
|
||||
}
|
||||
}
|
||||
|
||||
const stop = async (path) => {
|
||||
const stop = async (process) => {
|
||||
try {
|
||||
const processes = await getProfileProcesses(path ?? selectedProfile.value.path)
|
||||
await killProfile(processes[0])
|
||||
await killProcess(process.uuid).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: currentProcesses.value[0].metadata.loader,
|
||||
game_version: currentProcesses.value[0].metadata.game_version,
|
||||
trackEvent('InstanceStop', {
|
||||
loader: process.profile.loader,
|
||||
game_version: process.profile.game_version,
|
||||
source: 'AppBar',
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -176,7 +176,7 @@ const stop = async (path) => {
|
||||
}
|
||||
|
||||
const goToTerminal = (path) => {
|
||||
router.push(`/instance/${encodeURIComponent(path ?? selectedProfile.value.path)}/logs`)
|
||||
router.push(`/instance/${encodeURIComponent(path ?? selectedProcess.value.profile.path)}/logs`)
|
||||
}
|
||||
|
||||
const currentLoadingBars = ref([])
|
||||
@@ -188,16 +188,27 @@ const refreshInfo = async () => {
|
||||
if (x.bar_type.type === 'java_download') {
|
||||
x.title = 'Downloading Java ' + x.bar_type.version
|
||||
}
|
||||
if (x.bar_type.profile_name) {
|
||||
x.title = x.bar_type.profile_name
|
||||
if (x.bar_type.profile_path) {
|
||||
x.title = x.bar_type.profile_path
|
||||
}
|
||||
if (x.bar_type.pack_name) {
|
||||
x.title = x.bar_type.pack_name
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
currentLoadingBars.value.sort((a, b) => {
|
||||
if (a.loading_bar_uuid < b.loading_bar_uuid) {
|
||||
return -1
|
||||
}
|
||||
if (a.loading_bar_uuid > b.loading_bar_uuid) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
if (currentLoadingBars.value.length === 0) {
|
||||
showCard.value = false
|
||||
} else if (currentLoadingBarCount < currentLoadingBars.value.length) {
|
||||
@@ -210,8 +221,8 @@ const unlistenLoading = await loading_listener(async () => {
|
||||
await refreshInfo()
|
||||
})
|
||||
|
||||
const selectProfile = (profile) => {
|
||||
selectedProfile.value = profile
|
||||
const selectProcess = (process) => {
|
||||
selectedProcess.value = process
|
||||
showProfiles.value = false
|
||||
}
|
||||
|
||||
@@ -262,7 +273,6 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutsideProfile)
|
||||
unlistenProcess()
|
||||
unlistenLoading()
|
||||
unlistenRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -330,8 +340,12 @@ onBeforeUnmount(() => {
|
||||
width: 1.25rem !important;
|
||||
height: 1.25rem !important;
|
||||
|
||||
svg {
|
||||
min-width: 1.25rem;
|
||||
}
|
||||
|
||||
&.stop {
|
||||
--text-color: var(--color-red) !important;
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,29 +63,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Avatar,
|
||||
Card,
|
||||
Categories,
|
||||
EnvironmentIndicator,
|
||||
Button,
|
||||
DownloadIcon,
|
||||
formatNumber,
|
||||
formatCategory,
|
||||
HeartIcon,
|
||||
CalendarIcon,
|
||||
CheckIcon,
|
||||
StarIcon,
|
||||
} from 'omorphia'
|
||||
import { DownloadIcon, HeartIcon, CalendarIcon, CheckIcon, StarIcon } from '@modrinth/assets'
|
||||
import { Avatar, Card, Categories, EnvironmentIndicator, Button } from '@modrinth/ui'
|
||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { ref } from 'vue'
|
||||
import { add_project_from_version as installMod, list } from '@/helpers/profile.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import { installVersionDependencies } from '@/helpers/utils.js'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const props = defineProps({
|
||||
@@ -105,18 +89,6 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
confirmModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
modInstallModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
incompatibilityWarningModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
featured: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -134,92 +106,19 @@ const installed = ref(props.installed)
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`,
|
||||
'project versions'
|
||||
)
|
||||
let queuedVersionData
|
||||
|
||||
if (!props.instance) {
|
||||
queuedVersionData = versions[0]
|
||||
} else {
|
||||
queuedVersionData = versions.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(props.instance.metadata.game_version) &&
|
||||
(props.project.project_type !== 'mod' || v.loaders.includes(props.instance.metadata.loader))
|
||||
)
|
||||
}
|
||||
|
||||
if (props.project.project_type === 'modpack') {
|
||||
const packs = Object.values(await list().catch(handleError))
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
await packInstall(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel_track('PackInstall', {
|
||||
id: props.project.project_id,
|
||||
version_id: queuedVersionData.id,
|
||||
title: props.project.title,
|
||||
source: 'SearchCard',
|
||||
})
|
||||
} else {
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (props.instance) {
|
||||
if (!queuedVersionData) {
|
||||
props.incompatibilityWarningModal.show(
|
||||
props.instance,
|
||||
props.project.title,
|
||||
versions,
|
||||
() => (installed.value = true),
|
||||
props.project.project_id,
|
||||
props.project.project_type
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
} else {
|
||||
await installMod(props.instance.path, queuedVersionData.id).catch(handleError)
|
||||
await installVersionDependencies(props.instance, queuedVersionData)
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
id: props.project.project_id,
|
||||
project_type: props.project.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: props.project.title,
|
||||
source: 'SearchCard',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
props.modInstallModal.show(
|
||||
props.project.project_id,
|
||||
versions,
|
||||
props.project.title,
|
||||
props.project.project_type
|
||||
)
|
||||
await installVersion(
|
||||
props.project.project_id,
|
||||
null,
|
||||
props.instance ? props.instance.path : null,
|
||||
'SearchCard',
|
||||
(version) => {
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
if (props.instance) installed.value = true
|
||||
}
|
||||
|
||||
installing.value = false
|
||||
if (props.instance && version) {
|
||||
installed.value = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
219
apps/app-frontend/src/components/ui/SplashScreen.vue
Normal file
@@ -1,48 +1,34 @@
|
||||
<script setup>
|
||||
import { Modal, Button } from 'omorphia'
|
||||
import { Modal, Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import { get_version, get_project } from '@/helpers/cache.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
const version = ref(null)
|
||||
const categories = ref(null)
|
||||
const installing = ref(false)
|
||||
const modInstallModal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
async show(event) {
|
||||
if (event.event === 'InstallVersion') {
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(event.id)}`,
|
||||
'version'
|
||||
)
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
|
||||
'project'
|
||||
version.value = await get_version(event.id, 'must_revalidate').catch(handleError)
|
||||
project.value = await get_project(version.value.project_id, 'must_revalidate').catch(
|
||||
handleError,
|
||||
)
|
||||
} else {
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(event.id)}`,
|
||||
'project'
|
||||
)
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
|
||||
'version'
|
||||
)
|
||||
project.value = await get_project(event.id, 'must_revalidate').catch(handleError)
|
||||
version.value = await get_version(
|
||||
project.value.versions[project.value.versions.length - 1],
|
||||
'must_revalidate',
|
||||
).catch(handleError)
|
||||
}
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||
)
|
||||
confirmModal.value.show()
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod',
|
||||
)
|
||||
confirmModal.value.show()
|
||||
},
|
||||
@@ -50,28 +36,7 @@ defineExpose({
|
||||
|
||||
async function install() {
|
||||
confirmModal.value.hide()
|
||||
if (project.value.project_type === 'modpack') {
|
||||
await packInstall(
|
||||
project.value.id,
|
||||
version.value.id,
|
||||
project.value.title,
|
||||
project.value.icon_url
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: project.value.id,
|
||||
version_id: version.value.id,
|
||||
title: project.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
modInstallModal.value.show(
|
||||
project.value.id,
|
||||
[version.value],
|
||||
project.value.title,
|
||||
project.value.project_type
|
||||
)
|
||||
}
|
||||
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -96,7 +61,6 @@ async function install() {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -3,6 +3,7 @@
|
||||
ref="incompatibleModal"
|
||||
header="Incompatibility warning"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
@@ -12,13 +13,11 @@
|
||||
</p>
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th>{{ instance?.metadata.name }}</th>
|
||||
<th>{{ projectTitle }}</th>
|
||||
<th>{{ instance?.name }}</th>
|
||||
<th>{{ project.title }}</th>
|
||||
</tr>
|
||||
<tr class="content">
|
||||
<td class="data">
|
||||
{{ instance?.metadata.loader }} {{ instance?.metadata.game_version }}
|
||||
</td>
|
||||
<td class="data">{{ instance?.loader }} {{ instance?.game_version }}</td>
|
||||
<td>
|
||||
<DropdownSelect
|
||||
v-if="versions?.length > 1"
|
||||
@@ -56,46 +55,39 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
|
||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button, Modal, DropdownSelect } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
import { ref } from 'vue'
|
||||
import { handleError, useTheming } from '@/store/state.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const instance = ref(null)
|
||||
const project = ref(null)
|
||||
const projectType = ref(null)
|
||||
const projectTitle = ref(null)
|
||||
const versions = ref(null)
|
||||
const selectedVersion = ref(null)
|
||||
const incompatibleModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let markInstalled = () => {}
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (
|
||||
instanceVal,
|
||||
projectTitleVal,
|
||||
selectedVersions,
|
||||
extMarkInstalled,
|
||||
projectIdVal,
|
||||
projectTypeVal
|
||||
) => {
|
||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
||||
instance.value = instanceVal
|
||||
projectTitle.value = projectTitleVal
|
||||
versions.value = selectedVersions
|
||||
selectedVersion.value = selectedVersions[0]
|
||||
versions.value = projectVersions
|
||||
selectedVersion.value = projectVersions[0]
|
||||
|
||||
project.value = projectIdVal
|
||||
projectType.value = projectTypeVal
|
||||
project.value = projectVal
|
||||
|
||||
onInstall.value = callback
|
||||
installing.value = false
|
||||
|
||||
incompatibleModal.value.show()
|
||||
markInstalled = extMarkInstalled
|
||||
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
trackEvent('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,16 +95,16 @@ const install = async () => {
|
||||
installing.value = true
|
||||
await installMod(instance.value.path, selectedVersion.value.id).catch(handleError)
|
||||
installing.value = false
|
||||
markInstalled()
|
||||
onInstall.value(selectedVersion.value.id)
|
||||
incompatibleModal.value.hide()
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
id: project.value,
|
||||
version_id: selectedVersion.value.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectIncompatibilityWarningModal',
|
||||
})
|
||||
}
|
||||
@@ -1,54 +1,63 @@
|
||||
<script setup>
|
||||
import { Button, Modal, XIcon, DownloadIcon } from 'omorphia'
|
||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button, Modal } from '@modrinth/ui'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { ref } from 'vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const version = ref('')
|
||||
const title = ref('')
|
||||
const projectId = ref('')
|
||||
const icon = ref('')
|
||||
const versionId = ref()
|
||||
const project = ref()
|
||||
const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (projectIdVal, versionId, projectTitle, projectIcon) => {
|
||||
projectId.value = projectIdVal
|
||||
version.value = versionId
|
||||
title.value = projectTitle
|
||||
icon.value = projectIcon
|
||||
show: (projectVal, versionIdVal, callback) => {
|
||||
project.value = projectVal
|
||||
versionId.value = versionIdVal
|
||||
installing.value = false
|
||||
confirmModal.value.show()
|
||||
|
||||
mixpanel_track('PackInstallStart')
|
||||
onInstall.value = callback
|
||||
|
||||
trackEvent('PackInstallStart')
|
||||
},
|
||||
})
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
console.log(`Installing ${projectId.value} ${version.value} ${title.value} ${icon.value}`)
|
||||
confirmModal.value.hide()
|
||||
|
||||
await pack_install(
|
||||
projectId.value,
|
||||
version.value,
|
||||
title.value,
|
||||
icon.value ? icon.value : null
|
||||
project.value.id,
|
||||
versionId.value,
|
||||
project.value.title,
|
||||
project.value.icon_url,
|
||||
).catch(handleError)
|
||||
mixpanel_track('PackInstall', {
|
||||
id: projectId.value,
|
||||
version_id: version.value,
|
||||
title: title.value,
|
||||
trackEvent('PackInstall', {
|
||||
id: project.value.id,
|
||||
version_id: versionId.value,
|
||||
title: project.value.title,
|
||||
source: 'ConfirmModal',
|
||||
})
|
||||
|
||||
onInstall.value(versionId.value)
|
||||
installing.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="confirmModal" header="Are you sure?" :noblur="!themeStore.advancedRendering">
|
||||
<Modal
|
||||
ref="confirmModal"
|
||||
header="Are you sure?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||
<div class="input-group push-right">
|
||||
@@ -1,42 +1,38 @@
|
||||
<script setup>
|
||||
import {
|
||||
Avatar,
|
||||
Modal,
|
||||
Button,
|
||||
DownloadIcon,
|
||||
PlusIcon,
|
||||
Card,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
} from 'omorphia'
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Modal, Button, Card } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
get,
|
||||
list,
|
||||
create,
|
||||
} from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { installVersionDependencies } from '@/store/install.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref([])
|
||||
const project = ref('')
|
||||
const projectTitle = ref('')
|
||||
const projectType = ref('')
|
||||
const versions = ref()
|
||||
const project = ref()
|
||||
|
||||
const installModal = ref(null)
|
||||
const installModal = ref()
|
||||
const searchFilter = ref('')
|
||||
|
||||
const showCreation = ref(false)
|
||||
const icon = ref(null)
|
||||
const name = ref(null)
|
||||
@@ -45,33 +41,65 @@ const loader = ref(null)
|
||||
const gameVersion = ref(null)
|
||||
const creatingInstance = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: async (projectId, selectedVersions, title, type) => {
|
||||
project.value = projectId
|
||||
versions.value = selectedVersions
|
||||
projectTitle.value = title
|
||||
projectType.value = type
|
||||
const profiles = ref([])
|
||||
|
||||
installModal.value.show()
|
||||
const shownProfiles = computed(() =>
|
||||
profiles.value
|
||||
.filter((profile) => {
|
||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
let loaders = versions.value.flatMap((v) => v.loaders)
|
||||
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? loaders.includes(profile.loader) || loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: async (projectVal, versionsVal, callback) => {
|
||||
project.value = projectVal
|
||||
versions.value = versionsVal
|
||||
searchFilter.value = ''
|
||||
|
||||
profiles.value = await getData()
|
||||
showCreation.value = false
|
||||
name.value = null
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
onInstall.value = callback
|
||||
|
||||
const profilesVal = await list().catch(handleError)
|
||||
for (let profile of profilesVal) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
||||
handleError,
|
||||
)
|
||||
}
|
||||
profiles.value = profilesVal
|
||||
|
||||
installModal.value.show()
|
||||
|
||||
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
},
|
||||
})
|
||||
|
||||
const profiles = ref([])
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
const version = versions.value.find((v) => {
|
||||
return (
|
||||
v.game_versions.includes(instance.metadata.game_version) &&
|
||||
(v.loaders.includes(instance.metadata.loader) ||
|
||||
v.loaders.includes('minecraft') ||
|
||||
v.loaders.includes('iris') ||
|
||||
v.loaders.includes('optifine'))
|
||||
v.game_versions.includes(instance.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -87,45 +115,19 @@ async function install(instance) {
|
||||
instance.installedMod = true
|
||||
instance.installing = false
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.metadata.loader,
|
||||
game_version: instance.metadata.game_version,
|
||||
id: project.value,
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.value.id,
|
||||
version_id: version.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(version.id)
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
const projects = await list(true).then(Object.values).catch(handleError)
|
||||
|
||||
const filtered = projects
|
||||
.filter((profile) => {
|
||||
return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) &&
|
||||
versions.value
|
||||
.flatMap((v) => v.loaders)
|
||||
.some(
|
||||
(value) =>
|
||||
value === profile.metadata.loader || ['minecraft', 'iris', 'optifine'].includes(value)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
for (let profile of filtered) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value).catch(handleError)
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
const alreadySentCreation = ref(false)
|
||||
const toggleCreation = () => {
|
||||
showCreation.value = !showCreation.value
|
||||
name.value = null
|
||||
@@ -134,9 +136,8 @@ const toggleCreation = () => {
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
if (!alreadySentCreation.value) {
|
||||
alreadySentCreation.value = false
|
||||
mixpanel_track('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
if (showCreation.value) {
|
||||
trackEvent('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ const upload_icon = async () => {
|
||||
})
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
@@ -175,7 +176,7 @@ const createInstance = async () => {
|
||||
versions.value[0].game_versions[0],
|
||||
loader,
|
||||
'latest',
|
||||
icon.value
|
||||
icon.value,
|
||||
).catch(handleError)
|
||||
|
||||
await installMod(id, versions.value[0].id).catch(handleError)
|
||||
@@ -185,7 +186,7 @@ const createInstance = async () => {
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value[0])
|
||||
|
||||
mixpanel_track('InstanceCreate', {
|
||||
trackEvent('InstanceCreate', {
|
||||
profile_name: name.value,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
loader: loader,
|
||||
@@ -194,23 +195,21 @@ const createInstance = async () => {
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: loader,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
id: project.value,
|
||||
version_id: versions.value[0].id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(versions.value[0].id)
|
||||
|
||||
if (installModal.value) installModal.value.hide()
|
||||
creatingInstance.value = false
|
||||
}
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return name.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -218,6 +217,7 @@ const check_valid = computed(() => {
|
||||
ref="installModal"
|
||||
header="Install project to instance"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<input
|
||||
@@ -228,30 +228,42 @@ const check_valid = computed(() => {
|
||||
placeholder="Search for an instance"
|
||||
/>
|
||||
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
||||
<Button
|
||||
color="raised"
|
||||
class="profile-button"
|
||||
@click="$router.push(`/instance/${encodeURIComponent(profile.path)}`)"
|
||||
<div v-for="profile in shownProfiles" :key="profile.name" class="option">
|
||||
<router-link
|
||||
class="btn btn-transparent profile-button"
|
||||
:to="`/instance/${encodeURIComponent(profile.path)}`"
|
||||
@click="installModal.hide()"
|
||||
>
|
||||
<Avatar
|
||||
:src="
|
||||
!profile.metadata.icon ||
|
||||
(profile.metadata.icon && profile.metadata.icon.startsWith('http'))
|
||||
? profile.metadata.icon
|
||||
: tauri.convertFileSrc(profile.metadata?.icon)
|
||||
"
|
||||
:src="profile.icon_path ? tauri.convertFileSrc(profile.icon_path) : null"
|
||||
class="profile-image"
|
||||
/>
|
||||
{{ profile.metadata.name }}
|
||||
</Button>
|
||||
<Button :disabled="profile.installedMod || profile.installing" @click="install(profile)">
|
||||
<DownloadIcon v-if="!profile.installedMod && !profile.installing" />
|
||||
<CheckIcon v-else-if="profile.installedMod" />
|
||||
{{
|
||||
profile.installing ? 'Installing...' : profile.installedMod ? 'Installed' : 'Install'
|
||||
}}
|
||||
</Button>
|
||||
{{ profile.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-tooltip="
|
||||
profile.linked_data?.locked && !profile.installedMod
|
||||
? 'Unpair or unlock an instance to add mods.'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<Button
|
||||
:disabled="profile.installedMod || profile.installing || profile.linked_data?.locked"
|
||||
@click="install(profile)"
|
||||
>
|
||||
<DownloadIcon v-if="!profile.installedMod && !profile.installing" />
|
||||
<CheckIcon v-else-if="profile.installedMod" />
|
||||
{{
|
||||
profile.installing
|
||||
? 'Installing...'
|
||||
: profile.installedMod
|
||||
? 'Installed'
|
||||
: profile.linked_data && profile.linked_data.locked
|
||||
? 'Paired'
|
||||
: 'Install'
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card v-if="showCreation" class="creation-card">
|
||||
@@ -277,7 +289,7 @@ const check_valid = computed(() => {
|
||||
placeholder="Name"
|
||||
class="creation-input"
|
||||
/>
|
||||
<Button :disabled="creatingInstance === true || !check_valid" @click="createInstance()">
|
||||
<Button :disabled="creatingInstance === true || !name" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
</Button>
|
||||
@@ -0,0 +1,367 @@
|
||||
<script setup>
|
||||
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
||||
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
|
||||
import {
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
MicrosoftIcon,
|
||||
GoogleIcon,
|
||||
SteamIcon,
|
||||
GitLabIcon,
|
||||
} from '@/assets/external'
|
||||
import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.js'
|
||||
import { handleError, useNotifications } from '@/store/state.js'
|
||||
import { ref } from 'vue'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const props = defineProps({
|
||||
callback: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref()
|
||||
const turnstileToken = ref()
|
||||
const widgetId = ref()
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
modal.value.show()
|
||||
|
||||
if (window.turnstile === null || !window.turnstile) {
|
||||
const script = document.createElement('script')
|
||||
script.src =
|
||||
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback'
|
||||
script.async = true
|
||||
script.defer = true
|
||||
document.head.appendChild(script)
|
||||
|
||||
window.onloadTurnstileCallback = loadWidget
|
||||
} else {
|
||||
loadWidget()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function loadWidget() {
|
||||
widgetId.value = window.turnstile.render('#turnstile-container', {
|
||||
sitekey: '0x4AAAAAAAW3guHM6Eunbgwu',
|
||||
callback: (token) => (turnstileToken.value = token),
|
||||
expiredCallback: () => (turnstileToken.value = null),
|
||||
})
|
||||
}
|
||||
|
||||
function removeWidget() {
|
||||
if (widgetId.value) {
|
||||
window.turnstile.remove(widgetId.value)
|
||||
widgetId.value = null
|
||||
turnstileToken.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const loggingIn = ref(true)
|
||||
const twoFactorFlow = ref(null)
|
||||
const twoFactorCode = ref('')
|
||||
|
||||
const email = ref('')
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const subscribe = ref(true)
|
||||
|
||||
async function signInOauth(provider) {
|
||||
const creds = await login(provider).catch(handleSevereError)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn2fa() {
|
||||
const creds = await login_2fa(twoFactorCode.value, twoFactorFlow.value).catch(handleError)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
const creds = await login_pass(username.value, password.value, turnstileToken.value).catch(
|
||||
handleError,
|
||||
)
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function createAccount() {
|
||||
if (password.value !== confirmPassword.value) {
|
||||
const notifs = useNotifications()
|
||||
notifs.addNotification({
|
||||
title: 'An error occurred',
|
||||
text: 'Passwords do not match!',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const creds = await create_account(
|
||||
username.value,
|
||||
email.value,
|
||||
password.value,
|
||||
turnstileToken.value,
|
||||
subscribe.value,
|
||||
).catch(handleError)
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="modal" :on-hide="removeWidget">
|
||||
<Card>
|
||||
<template v-if="twoFactorFlow">
|
||||
<h1>Enter two-factor code</h1>
|
||||
<p>Please enter a two-factor code to proceed.</p>
|
||||
<input v-model="twoFactorCode" maxlength="11" type="text" placeholder="Enter code..." />
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 v-if="loggingIn">Login to Modrinth</h1>
|
||||
<h1 v-else>Create an account</h1>
|
||||
<div class="button-grid">
|
||||
<Button class="discord" large @click="signInOauth('discord')">
|
||||
<DiscordIcon />
|
||||
Discord
|
||||
</Button>
|
||||
<Button class="github" large @click="signInOauth('github')">
|
||||
<GithubIcon />
|
||||
Github
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('microsoft')">
|
||||
<MicrosoftIcon />
|
||||
Microsoft
|
||||
</Button>
|
||||
<Button class="google" large @click="signInOauth('google')">
|
||||
<GoogleIcon />
|
||||
Google
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('steam')">
|
||||
<SteamIcon />
|
||||
Steam
|
||||
</Button>
|
||||
<Button class="gitlab" large @click="signInOauth('gitlab')">
|
||||
<GitLabIcon />
|
||||
GitLab
|
||||
</Button>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<hr />
|
||||
<p>Or</p>
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<MailIcon />
|
||||
<input v-model="email" type="text" placeholder="Email" />
|
||||
</div>
|
||||
<div class="iconified-input username">
|
||||
<UserIcon />
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
:placeholder="loggingIn ? 'Email or username' : 'Username'"
|
||||
/>
|
||||
</div>
|
||||
<div class="iconified-input" :class="{ username: !loggingIn }">
|
||||
<LockIcon />
|
||||
<input v-model="password" type="password" placeholder="Password" />
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<LockIcon />
|
||||
<input v-model="confirmPassword" type="password" placeholder="Confirm password" />
|
||||
</div>
|
||||
<div class="turnstile">
|
||||
<div id="turnstile-container"></div>
|
||||
<div id="turnstile-container-2"></div>
|
||||
</div>
|
||||
<Checkbox
|
||||
v-if="!loggingIn"
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
label="Subscribe to updates about Modrinth"
|
||||
/>
|
||||
<div class="link-row">
|
||||
<a v-if="loggingIn" class="button-base" @click="loggingIn = false"> Create account </a>
|
||||
<a v-else class="button-base" @click="loggingIn = true">Sign in</a>
|
||||
<a class="button-base" href="https://modrinth.com/auth/reset-password">
|
||||
Forgot password?
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" large>Close</Button>
|
||||
<Button v-if="twoFactorCode" color="primary" large @click="signIn2fa"> Login </Button>
|
||||
<Button
|
||||
v-else-if="loggingIn"
|
||||
color="primary"
|
||||
large
|
||||
@click="signIn"
|
||||
:disabled="!turnstileToken"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
|
||||
Create account
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.modal-container) {
|
||||
.modal-body {
|
||||
width: auto;
|
||||
|
||||
.content {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 25rem;
|
||||
}
|
||||
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: var(--gap-md);
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discord {
|
||||
background-color: #5865f2;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.github {
|
||||
background-color: #8740f1;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: var(--color-contrast);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.google {
|
||||
background-color: #4285f4;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.gitlab {
|
||||
background-color: #fc6d26;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--color-raised-bg);
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
width: 100%;
|
||||
border-top: 2px solid var(--color-button-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
margin-bottom: var(--gap-sm);
|
||||
}
|
||||
|
||||
.link-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
a {
|
||||
color: var(--color-blue);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.turnstile {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 2px solid var(--color-button-bg);
|
||||
height: 66px;
|
||||
margin-top: var(--gap-md);
|
||||
|
||||
iframe {
|
||||
margin: -1px;
|
||||
min-width: calc(100% + 2px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
20
apps/app-frontend/src/composables/macCssFix.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import cssContent from '@/assets/stylesheets/macFix.css?inline'
|
||||
|
||||
export async function useCheckDisableMouseover() {
|
||||
try {
|
||||
// Fetch the CSS content from the Rust backend
|
||||
let should_disable_mouseover = await invoke('plugin:utils|should_disable_mouseover')
|
||||
|
||||
if (should_disable_mouseover) {
|
||||
// Create a style element and set its content
|
||||
const styleElement = document.createElement('style')
|
||||
styleElement.innerHTML = cssContent
|
||||
|
||||
// Append the style element to the document's head
|
||||
document.head.appendChild(styleElement)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking OS version from Rust backend', error)
|
||||
}
|
||||
}
|
||||
23
apps/app-frontend/src/helpers/analytics.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { posthog } from 'posthog-js'
|
||||
|
||||
export const initAnalytics = () => {
|
||||
posthog.init('phc_hm2ihMpTAoE86xIm7XzsCB8RPiTRKivViK5biiHedm', {
|
||||
persistence: 'localStorage',
|
||||
})
|
||||
}
|
||||
|
||||
export const debugAnalytics = () => {
|
||||
posthog.debug()
|
||||
}
|
||||
|
||||
export const optOutAnalytics = () => {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
|
||||
export const optInAnalytics = () => {
|
||||
posthog.opt_in_capturing()
|
||||
}
|
||||
|
||||
export const trackEvent = (eventName, properties) => {
|
||||
posthog.capture(eventName, properties)
|
||||
}
|
||||
58
apps/app-frontend/src/helpers/auth.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
// Example function:
|
||||
// User goes to auth_url to complete flow, and when completed, authenticate_await_completion() returns the credentials
|
||||
// export async function authenticate() {
|
||||
// const auth_url = await authenticate_begin_flow()
|
||||
// console.log(auth_url)
|
||||
// await authenticate_await_completion()
|
||||
// }
|
||||
|
||||
/**
|
||||
* Authenticate a user with Hydra - part 1.
|
||||
* This begins the authentication flow quasi-synchronously.
|
||||
*
|
||||
* @returns {Promise<DeviceLoginSuccess>} A DeviceLoginSuccess object with two relevant fields:
|
||||
* @property {string} verification_uri - The URL to go to complete the flow.
|
||||
* @property {string} user_code - The code to enter on the verification_uri page.
|
||||
*/
|
||||
export async function login() {
|
||||
return await invoke('plugin:auth|login')
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default user
|
||||
* @return {Promise<UUID | undefined>}
|
||||
*/
|
||||
export async function get_default_user() {
|
||||
return await invoke('plugin:auth|get_default_user')
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default user
|
||||
* @param {UUID} user
|
||||
*/
|
||||
export async function set_default_user(user) {
|
||||
return await invoke('plugin:auth|set_default_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user account from the database
|
||||
* @param {UUID} user
|
||||
*/
|
||||
export async function remove_user(user) {
|
||||
return await invoke('plugin:auth|remove_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of users
|
||||
* @returns {Promise<Credential[]>}
|
||||
*/
|
||||
export async function users() {
|
||||
return await invoke('plugin:auth|get_users')
|
||||
}
|
||||