You've already forked AstralRinth
forked from didirus/AstralRinth
Compare commits
670 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 19a26942af | |||
| eef238c1bb | |||
| 3e5ef753e0 | |||
| 75754230a9 | |||
| e9bc01b0c7 | |||
| 572800d9ca | |||
|
|
79217e78b4 | ||
|
|
bdd808c279 | ||
|
|
986a7e6216 | ||
|
|
b54fcaa0b1 | ||
|
|
1cf782c298 | ||
|
|
fb1050e409 | ||
|
|
5c29a8c7dd | ||
|
|
09dead50d2 | ||
|
|
772e0ee220 | ||
|
|
86b0de3cee | ||
|
|
d174d96b74 | ||
|
|
1d193ed01b | ||
|
|
adf365d99d | ||
|
|
f3f48c3c6f | ||
|
|
e072f2237b | ||
|
|
306eee3a21 | ||
|
|
ca1d66d070 | ||
|
|
7e1400d111 | ||
|
|
7595e77170 | ||
|
|
08fcc61d35 | ||
|
|
b36801c5ed | ||
|
|
4ed1a1ae7f | ||
|
|
04db01cb55 | ||
|
|
8f5185de1c | ||
|
|
9f6db31785 | ||
|
|
a869086ce9 | ||
|
|
2af6a1b36f | ||
|
|
d4381f513f | ||
|
|
a281f13f15 | ||
|
|
a9641dadff | ||
|
|
c94dde9b47 | ||
|
|
976644d1e6 | ||
|
|
11fe90a69b | ||
|
|
1108086854 | ||
|
|
5aea892a39 | ||
|
|
3f3e6f5199 | ||
|
|
2efcd383bb | ||
|
|
faec9c2965 | ||
|
|
a0e8c7f924 | ||
|
|
6efdfdf17e | ||
|
|
aec268c6e9 | ||
|
|
2c096a85d6 | ||
|
|
240e5455cc | ||
|
|
c538a9ec6d | ||
|
|
72458f5c41 | ||
|
|
82e4eb7b40 | ||
|
|
c9bfc4e9b6 | ||
|
|
1ea96df00e | ||
|
|
75c5316dc3 | ||
|
|
4ee7623837 | ||
|
|
f65479ee15 | ||
|
|
a903e46be9 | ||
|
|
4497131206 | ||
|
|
4871abfb3a | ||
|
|
b0ed808745 | ||
|
|
169224560b | ||
|
|
106edc3a51 | ||
|
|
ede405c650 | ||
|
|
1dd1629884 | ||
|
|
0070c9877b | ||
|
|
eb208eed8b | ||
|
|
c37bf75853 | ||
|
|
7838008396 | ||
|
|
454c708fd6 | ||
|
|
3ffa78aa07 | ||
|
|
7dba9cbe54 | ||
|
|
716c4e9a21 | ||
|
|
f85a2d3ec1 | ||
|
|
d055dc68dc | ||
|
|
50a87ba933 | ||
|
|
6030cb560c | ||
|
|
c498230ebf | ||
|
|
4bbc5905e4 | ||
|
|
40f5db64d8 | ||
|
|
8d72a42be5 | ||
|
|
61c8cd75cd | ||
|
|
b46f6d0141 | ||
|
|
915d8c68bf | ||
|
|
21045142cd | ||
|
|
f171752109 | ||
|
|
82f00f961e | ||
|
|
b55b7fdc1c | ||
|
|
0b60060f65 | ||
|
|
b0f1266a8b | ||
|
|
863ff2e228 | ||
|
|
579aa5967f | ||
|
|
3bf5a6ebec | ||
|
|
ff222aa168 | ||
|
|
ea17534f77 | ||
|
|
b91d581928 | ||
|
|
4f6cb7f26c | ||
|
|
c1da3e7e95 | ||
|
|
040c568fdb | ||
|
|
7a78565c97 | ||
|
|
62e56eb27e | ||
|
|
8175120c4c | ||
|
|
6221fe5e08 | ||
|
|
4a8f882063 | ||
|
|
17db55a0bc | ||
|
|
a1d9268d00 | ||
|
|
14d227a1a3 | ||
|
|
3fd6ce1b6d | ||
|
|
7eb1b38cc7 | ||
|
|
2e9730ea1f | ||
|
|
a6cd4dfc0f | ||
|
|
0cf28c6392 | ||
|
|
7c2327ce16 | ||
|
|
099011a177 | ||
|
|
61d4a34f0f | ||
|
|
5b890dcd8a | ||
|
|
c2bd88377b | ||
|
|
efcc0d87b5 | ||
|
|
f3033956cf | ||
|
|
e26291943c | ||
|
|
3fc18feacf | ||
|
|
09a0b34df3 | ||
|
|
937be840c4 | ||
|
|
fef6df1321 | ||
|
|
daf804947c | ||
|
|
477d77cdc1 | ||
|
|
9bb012a439 | ||
|
|
d1650bb3c4 | ||
|
|
2ce22c18bf | ||
|
|
b48443c65b | ||
|
|
b7e7e5e603 | ||
|
|
fca5b7b544 | ||
|
|
3a40ee8713 | ||
|
|
9e4317a262 | ||
|
|
7fb6401613 | ||
|
|
d332032e53 | ||
|
|
560f21c0fe | ||
|
|
2f99628d94 | ||
|
|
ad3edf541b | ||
|
|
b07a1659b4 | ||
|
|
1a16d61511 | ||
|
|
366a0a6366 | ||
|
|
91b08e7380 | ||
|
|
9924faab84 | ||
|
|
9f356beec3 | ||
|
|
afe5f773e0 | ||
|
|
3e246f12de | ||
|
|
042451bad6 | ||
|
|
30106d5f82 | ||
|
|
e0d159c010 | ||
| 45519f5dbb | |||
| 3843ed6690 | |||
|
|
061c52c274 | ||
|
|
1bbb01bd42 | ||
|
|
3cabc3b967 | ||
|
|
7de4e55bad | ||
|
|
1f21d66140 | ||
|
|
3adee66899 | ||
|
|
67a6cd24cc | ||
|
|
a952318c77 | ||
|
|
336832ec40 | ||
|
|
543bd5acf7 | ||
|
|
6a0bf5858e | ||
|
|
11a75e7657 | ||
|
|
88635d8da8 | ||
|
|
934936eba8 | ||
|
|
53ec2c5306 | ||
|
|
cace1a54cd | ||
|
|
803c17de31 | ||
|
|
537eadef0c | ||
|
|
39f2b0ecb6 | ||
|
|
1e9e13aebb | ||
|
|
67835b04a8 | ||
|
|
3f93041ca2 | ||
|
|
0663b8adb0 | ||
|
|
1f48f5b5af | ||
|
|
0268600044 | ||
|
|
8fb38ba0f2 | ||
|
|
85c65e697d | ||
|
|
563997e060 | ||
|
|
2d5568ecec | ||
|
|
a64c4201bb | ||
|
|
51d5ed771c | ||
|
|
539132a527 | ||
|
|
9958600121 | ||
|
|
9ad01723a2 | ||
|
|
8448bacae7 | ||
|
|
c21e98a2a8 | ||
|
|
5bbc3872f3 | ||
|
|
8d894541e8 | ||
|
|
dc16a65b62 | ||
|
|
514c6f6e34 | ||
|
|
609e3896eb | ||
|
|
fd08dff1e7 | ||
|
|
6425ab8c57 | ||
|
|
e123e51c66 | ||
|
|
21fad12a21 | ||
|
|
924a77eb3f | ||
|
|
7aaf99a0c8 | ||
|
|
91accd5578 | ||
|
|
147f19f11e | ||
|
|
73ff6df73c | ||
|
|
0de780b7c9 | ||
|
|
f49f889536 | ||
|
|
b3f598aa1d | ||
|
|
cd1b5dcd3d | ||
|
|
79b7d269b0 | ||
|
|
40ac726930 | ||
|
|
ddcc14d99f | ||
|
|
3dd2de5f18 | ||
|
|
0a8f489234 | ||
|
|
1d64b2e22a | ||
|
|
251e89fe5a | ||
|
|
4fbbc2b1cf | ||
|
|
d5b7ac3542 | ||
|
|
fec395a4cf | ||
|
|
16c0dadc4a | ||
|
|
779092c0b7 | ||
|
|
9aa06fbc26 | ||
|
|
cfd2977c21 | ||
|
|
27fc0796a4 | ||
|
|
b1438bd460 | ||
|
|
267e0cb636 | ||
|
|
d471ef6763 | ||
|
|
cea5cfa4ab | ||
|
|
56356e8260 | ||
|
|
41e4086973 | ||
|
|
0f1f27d450 | ||
|
|
a558064f9d | ||
|
|
c421249767 | ||
|
|
8eff939039 | ||
|
|
e3444a3456 | ||
|
|
16a6f7b352 | ||
|
|
79c2633011 | ||
|
|
783aaa6553 | ||
| ddf51c9596 | |||
| a63b6b27d5 | |||
|
|
60e0953616 | ||
|
|
f7c86f9fc9 | ||
| cac3b46652 | |||
|
|
fe684ab903 | ||
|
|
8592761493 | ||
|
|
dfe087df20 | ||
| 82119a9fc9 | |||
| b9ec1b42dc | |||
| 7345fa401b | |||
|
|
be3208c5a1 | ||
|
|
b56f39ce07 | ||
|
|
0178fddc38 | ||
|
|
31417a2aa1 | ||
|
|
f333a75221 | ||
|
|
bcf14a4c51 | ||
|
|
130c2863ab | ||
|
|
e59664426b | ||
|
|
2f0ef07944 | ||
|
|
9af19d01e5 | ||
|
|
e837d9fa30 | ||
|
|
93b79759c7 | ||
|
|
4becb2a822 | ||
|
|
134a621d0d | ||
|
|
089cca60ce | ||
|
|
20484ed7aa | ||
|
|
763a38812f | ||
|
|
7ccc32675b | ||
|
|
26feaf753a | ||
|
|
94c0003c19 | ||
|
|
c27f787c91 | ||
|
|
70e2138248 | ||
|
|
590ba3ce55 | ||
|
|
29671347a0 | ||
|
|
386e6e50da | ||
|
|
880ed21bcd | ||
|
|
bbc31ef077 | ||
|
|
9a13e977a0 | ||
|
|
a5602ff18c | ||
|
|
5901c5a535 | ||
|
|
cca1dd7e37 | ||
|
|
127e01cc96 | ||
|
|
1dcb38cb57 | ||
|
|
98b4970680 | ||
|
|
9706f1597b | ||
|
|
f8a5a77daa | ||
|
|
1efdceacfd | ||
|
|
b998c71337 | ||
|
|
6a6adb3480 | ||
|
|
a694aeed32 | ||
|
|
8182b795de | ||
|
|
608ab988f0 | ||
|
|
a261598e89 | ||
|
|
11a1918a2e | ||
|
|
67fb825937 | ||
|
|
4289f8b52d | ||
|
|
fb1ba51a2b | ||
|
|
cb47bc97c7 | ||
|
|
06e1bc9dd6 | ||
|
|
af39a1769c | ||
|
|
60ffa75653 | ||
|
|
7674433f88 | ||
|
|
7437a833ef | ||
|
|
1bad1a57b0 | ||
|
|
3437387885 | ||
|
|
23d098eee5 | ||
|
|
4636372ff4 | ||
|
|
4592786de8 | ||
|
|
f054f39c5d | ||
|
|
6e47de06bb | ||
|
|
c38751a38a | ||
|
|
2d218d79c6 | ||
|
|
5a41a35716 | ||
|
|
644554f1e9 | ||
|
|
3765a6ded8 | ||
|
|
92698e4bb5 | ||
|
|
17f395ee55 | ||
|
|
b11934054d | ||
|
|
40cbe92dbc | ||
| 9139c23469 | |||
| 932f4ce662 | |||
| 1fbd39c920 | |||
| 27abe2b42f | |||
| ece15a97a0 | |||
| 97a9c24768 | |||
|
|
b7f0988399 | ||
|
|
4c1020d2ba | ||
|
|
00f9cf0e2c | ||
|
|
1dd7e3bcdc | ||
|
|
3ac3122b31 | ||
|
|
6b5f8a41e7 | ||
|
|
8b39ba491a | ||
|
|
c74460fffa | ||
|
|
5000c4067b | ||
|
|
af33950bbe | ||
|
|
075331b26c | ||
|
|
f31b74f7fd | ||
|
|
bcc36362be | ||
|
|
632b27dc21 | ||
|
|
cf6f3736eb | ||
|
|
aaaef8f39e | ||
|
|
3f8dd1a79c | ||
|
|
363f47f269 | ||
|
|
a0f23a2bca | ||
|
|
08e316a2b2 | ||
|
|
9aaf5fb87e | ||
|
|
bcca66b12c | ||
|
|
ccb24ce8eb | ||
|
|
5dd6c804d0 | ||
|
|
ab886a5ea8 | ||
|
|
03b0eba695 | ||
|
|
707ff2146b | ||
|
|
8d80433c2c | ||
|
|
a547f7a9b0 | ||
|
|
f78fbe3215 | ||
|
|
f375913c62 | ||
|
|
a4015d9df3 | ||
|
|
977de0e18a | ||
|
|
c379e4b173 | ||
|
|
eeed4e572d | ||
|
|
79502a19d6 | ||
|
|
3dbfd69bdd | ||
|
|
19393a38bb | ||
|
|
859d7f57cf | ||
|
|
24bec6baba | ||
|
|
63d8f70e20 | ||
|
|
8a30b7978d | ||
|
|
4a9f0b8a0e | ||
|
|
0e17427a58 | ||
|
|
ad3b5aec69 | ||
|
|
4b17eb5d35 | ||
|
|
6a70acef25 | ||
|
|
e58456eed4 | ||
|
|
12940fc207 | ||
|
|
7796273529 | ||
| 7cc9d8183d | |||
| 231e95792e | |||
| 905eae8403 | |||
| 868fda1703 | |||
| 4b86c4ee8a | |||
|
|
752f68124c | ||
|
|
699a049c69 | ||
| 8e720ccef5 | |||
| 03b49284e1 | |||
| ac6c26a5f9 | |||
| cc963cfc40 | |||
|
|
fa7d1d7942 | ||
|
|
d1ffed564d | ||
|
|
e719ae2f42 | ||
|
|
5db5bf4c4c | ||
|
|
b23d3e674f | ||
|
|
75e3994c6e | ||
|
|
71e28e1ea5 | ||
|
|
6dbd1e5236 | ||
|
|
77afdb1cc4 | ||
|
|
7fa442fb28 | ||
|
|
2535156dac | ||
|
|
8972c9a198 | ||
|
|
03ed64c99f | ||
|
|
4cd8ccd319 | ||
|
|
ea594ec27c | ||
|
|
2a61916d1e | ||
|
|
e66b131a5d | ||
|
|
0c66fa3f12 | ||
|
|
aec49cff7c | ||
|
|
c88bdda3e6 | ||
|
|
1a72d55e2d | ||
|
|
55eae7ec7e | ||
|
|
351b3da337 | ||
|
|
9ee0626e8b | ||
|
|
e9735bd9ba | ||
|
|
15a7815ec3 | ||
|
|
6919c8dea9 | ||
|
|
f32558cf97 | ||
|
|
ad705fa66f | ||
|
|
87f8773401 | ||
|
|
3c578108de | ||
|
|
cb5600ad45 | ||
|
|
59e48ea2b1 | ||
|
|
7658e1c653 | ||
|
|
9589e23118 | ||
|
|
dbc64afe48 | ||
|
|
7e682c22bb | ||
|
|
fd80f1217d | ||
|
|
d98394d8d5 | ||
|
|
e303655727 | ||
|
|
95de8977d4 | ||
|
|
92e91a0606 | ||
|
|
98269842f3 | ||
|
|
ab6e9dd5d7 | ||
|
|
a13647b9e2 | ||
|
|
2af7ecc077 | ||
|
|
bea0ba017c | ||
|
|
f874856452 | ||
|
|
b96c5cd5ab | ||
|
|
7e84659249 | ||
|
|
24504cb94d | ||
|
|
6fe4235358 | ||
|
|
04f0f53104 | ||
|
|
c169b48228 | ||
|
|
beff2fcaa9 | ||
|
|
9315af9b20 | ||
|
|
4d11dc821b | ||
|
|
8fd40f46c5 | ||
|
|
28e9f017e3 | ||
|
|
beb1bdb31f | ||
|
|
895b040ad7 | ||
|
|
54747aa628 | ||
|
|
53c9699b46 | ||
|
|
671fd22389 | ||
|
|
bddc40e601 | ||
|
|
324ad65d7c | ||
|
|
7eace32d93 | ||
|
|
a5108ecc5d | ||
|
|
a538b99c18 | ||
|
|
f6f66a313f | ||
|
|
d5f756fd86 | ||
|
|
b4eba5a0d5 | ||
|
|
d418eaee12 | ||
|
|
f466470d06 | ||
|
|
3f55711f9e | ||
|
|
bb9ce52c9d | ||
|
|
14af3d0763 | ||
|
|
d43451e398 | ||
|
|
2492b11ec0 | ||
|
|
4228a193e9 | ||
|
|
47020f34b6 | ||
|
|
5c00cb06f1 | ||
|
|
e6edf07eae | ||
|
|
5d7bd3b177 | ||
|
|
71d63fbe17 | ||
|
|
f33efed91b | ||
|
|
d41b31c775 | ||
|
|
20281c4efc | ||
|
|
afcdb1d0a1 | ||
|
|
f3060cd9b4 | ||
|
|
1a1b9f54df | ||
|
|
716f293e8e | ||
|
|
f5825f1065 | ||
|
|
5b44454e18 | ||
|
|
b425c66832 | ||
|
|
0b8762cd0a | ||
|
|
ff50964f25 | ||
|
|
36d0760a3e | ||
|
|
4def0e8407 | ||
|
|
6da190ed01 | ||
|
|
8149618187 | ||
|
|
902d749293 | ||
|
|
1491642209 | ||
|
|
7bc2c1dd4d | ||
|
|
9f11759292 | ||
|
|
cef425b6be | ||
|
|
3fc55184a7 | ||
|
|
67e090565e | ||
|
|
d8d9720495 | ||
|
|
9361acb78e | ||
|
|
58aac642a9 | ||
|
|
af3b829449 | ||
|
|
567e31401d | ||
|
|
b95ece04c4 | ||
|
|
3dfd035b50 | ||
|
|
01b19424cd | ||
|
|
cb3130f998 | ||
|
|
4d3e1ade67 | ||
|
|
1b33a3619f | ||
|
|
ea607c1a04 | ||
|
|
fda06cfc60 | ||
|
|
0d61945956 | ||
|
|
c017038f71 | ||
|
|
a323bf6c25 | ||
|
|
e2f07a7848 | ||
|
|
0511a14bd9 | ||
|
|
8f8a4af9eb | ||
|
|
9ed094a1e7 | ||
|
|
aa6de3cc80 | ||
|
|
f5aece1fb1 | ||
|
|
79aa41fd7a | ||
|
|
bd918c7616 | ||
|
|
d23b925bb9 | ||
|
|
8aaddb9d8a | ||
|
|
f48eaee336 | ||
|
|
749fd32307 | ||
|
|
2e95a8a117 | ||
|
|
2194ae774c | ||
|
|
052637d402 | ||
|
|
c1a092e55c | ||
|
|
bd3342badf | ||
|
|
d832ca1e5a | ||
|
|
5b7f025094 | ||
|
|
d0c67b368a | ||
|
|
c43d359561 | ||
|
|
8b2a89d4e0 | ||
|
|
8aede4e082 | ||
|
|
3d80201112 | ||
|
|
8d14f34994 | ||
|
|
6f34130633 | ||
|
|
5a699eec22 | ||
|
|
9fa490aa6a | ||
|
|
d119b301d0 | ||
|
|
15c31f04a3 | ||
|
|
48e5319134 | ||
|
|
8058993578 | ||
|
|
28337c88f6 | ||
|
|
a6d08e9d50 | ||
|
|
7943f77655 | ||
|
|
dc4ef332f8 | ||
|
|
652f2e241f | ||
|
|
5fd27bcb65 | ||
|
|
8fa01b937d | ||
|
|
8b98087936 | ||
|
|
7afe35a6cd | ||
|
|
debaf1381c | ||
|
|
697468e910 | ||
|
|
46c325f78a | ||
|
|
0ac42344e7 | ||
|
|
df261dad95 | ||
|
|
d30643b5a0 | ||
|
|
ab95dcf951 | ||
|
|
ab539a313f | ||
|
|
a2c07c92f8 | ||
|
|
0925abfd1c | ||
|
|
8cf42471a3 | ||
|
|
006b19e3c9 | ||
|
|
ca36d11570 | ||
|
|
c612c8b009 | ||
|
|
f9cf3d5ef9 | ||
|
|
e7d933411e | ||
|
|
44cbbd9ed7 | ||
|
|
87dbb6dcbc | ||
|
|
3d1cafdcec | ||
|
|
e114c7466e | ||
|
|
20059e6cf0 | ||
|
|
6b10b4d30b | ||
|
|
a47dde972c | ||
|
|
e8b0c9df4c | ||
|
|
b8bc2c4cb6 | ||
|
|
328500d381 | ||
|
|
f56672fb68 | ||
|
|
d3459e4b12 | ||
|
|
07703e49ef | ||
|
|
08011161c8 | ||
|
|
9b29694907 | ||
|
|
805c0b86a5 | ||
|
|
d19bf82cb1 | ||
|
|
2e6cff7efc | ||
|
|
b2ff2d8737 | ||
|
|
eaa4b44a16 | ||
|
|
76d0ef03e7 | ||
|
|
ee8c47adcb | ||
|
|
5d3ca3ba02 | ||
|
|
235717b01c | ||
|
|
518f7adafb | ||
|
|
490b994d7b | ||
|
|
14eac461be | ||
|
|
9af1391e0e | ||
|
|
bcfa6941e4 | ||
|
|
5ffe14f058 | ||
|
|
166d14e7e1 | ||
|
|
b03d754a57 | ||
|
|
674f29959d | ||
|
|
3e735b99eb | ||
|
|
74d2d85cb5 | ||
| 3a92adfb82 | |||
| af4c627a04 | |||
| 1e725e6d03 | |||
|
|
1454e3351e | ||
|
|
6f59f4c110 | ||
|
|
8e0732bf01 | ||
|
|
0cf3c1a88e | ||
|
|
8a3171d7c4 | ||
|
|
e25d726da4 | ||
|
|
11e99cb9d3 | ||
|
|
632b09ff3f | ||
|
|
713571d50e | ||
|
|
4ad6daa45c | ||
|
|
9b5f172170 | ||
|
|
4f789a0ebc | ||
|
|
ee3ac37967 | ||
|
|
2aabcf36ee | ||
|
|
82697278dc | ||
|
|
0bc6502443 | ||
|
|
5ffcc48d75 | ||
|
|
b81e727204 | ||
|
|
9ea43a12fd | ||
|
|
b279c43069 | ||
|
|
9497ba70a4 | ||
|
|
c02b809601 | ||
| 1d000bb238 | |||
|
|
df1499047c | ||
|
|
80eb297284 | ||
|
|
58645b9ba9 | ||
|
|
544f63512a | ||
|
|
3b8cd661bc | ||
|
|
8af65f58d9 | ||
|
|
ab79e84398 | ||
|
|
cf190d86d5 | ||
|
|
ca0c16b1fe | ||
|
|
17c9e4a721 | ||
|
|
d7f1029b54 | ||
|
|
ad208536b0 | ||
| 553db55c7b | |||
|
|
d22c9e24f4 | ||
|
|
e31197f649 | ||
|
|
0dee21814d | ||
|
|
0657e4466f | ||
|
|
13dbb4c57e | ||
| 4c6290ead6 | |||
|
|
99493b9917 | ||
|
|
72a52eb7b1 | ||
|
|
b33e12c71d | ||
|
|
82d86839c7 | ||
|
|
3a20e15340 | ||
|
|
1c89b84314 | ||
| 8d36c14554 | |||
|
|
6387fb21c6 | ||
|
|
c7d0839bfb | ||
| 2b43e26a85 | |||
|
|
175b90be5a | ||
|
|
13103b4950 | ||
|
|
8804478221 | ||
|
|
b8982a6d17 | ||
|
|
ff88724d01 | ||
|
|
7dffb352d5 | ||
|
|
1df6e29aa1 | ||
|
|
5deb4179ad | ||
|
|
358cf31c87 | ||
| 7cea4b21a8 | |||
| 7846fd00aa | |||
| cebc195fe0 | |||
|
|
6db1d66591 | ||
|
|
8052fda840 | ||
| ae58f3844d | |||
| acd4b1696a | |||
| 5ea78b78c2 |
@@ -1,9 +1,6 @@
|
|||||||
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
# Windows has stack overflows when calling from Tauri, so we increase the default stack size used by the compiler
|
||||||
[target.'cfg(windows)']
|
[target.'cfg(windows)']
|
||||||
rustflags = ["-C", "link-args=/STACK:16777220", "--cfg", "tokio_unstable"]
|
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||||
|
|
||||||
[target.x86_64-pc-windows-msvc]
|
[target.x86_64-pc-windows-msvc]
|
||||||
linker = "rust-lld"
|
linker = "rust-lld"
|
||||||
|
|
||||||
[build]
|
|
||||||
rustflags = ["--cfg", "tokio_unstable"]
|
|
||||||
|
|||||||
@@ -3,16 +3,23 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = tab
|
||||||
indent_size = 2
|
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
max_line_length = 100
|
max_line_length = 100
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
trim_trailing_whitespace = false
|
|
||||||
|
|
||||||
[*.{rs,java,kts}]
|
[*.{toml,json}]
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
|
|
||||||
|
# YAML requires space indentation by spec
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_style = space
|
||||||
|
|||||||
63
.github/ISSUE_TEMPLATE/3-hosting-bug.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/3-hosting-bug.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: 👥 Bug with Modrinth Hosting
|
||||||
|
description: For issues with a Modrinth Hosting product.
|
||||||
|
labels: [hosting]
|
||||||
|
type: 'bug'
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Please confirm the following.
|
||||||
|
options:
|
||||||
|
- label: I checked the [existing issues](https://github.com/modrinth/code/issues?q=is%3Aissue) 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: issue-location
|
||||||
|
attributes:
|
||||||
|
label: Is this an issue in the control panel or with the Minecraft server itself?
|
||||||
|
options:
|
||||||
|
- Control panel (on Modrinth.com)
|
||||||
|
- Minecraft server
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on? (if a panel issue)
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- N/A
|
||||||
|
- 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
|
||||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +1,8 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: 🫶 Support Portal
|
- name: 🫶 Support portal
|
||||||
about: Get support using through our portal.
|
about: Get support using through our support website.
|
||||||
url: https://support.modrinth.com
|
url: https://support.modrinth.com
|
||||||
- name: 💬 Chat
|
- name: 💬 Chat on Discord
|
||||||
about: Join our Discord server to chat about Modrinth.
|
about: Join our Discord server to chat about Modrinth.
|
||||||
url: https://discord.modrinth.com
|
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: 📚 Documentation
|
|
||||||
about: Useful documentation about Modrinth's API
|
|
||||||
url: https://docs.modrinth.com
|
|
||||||
|
|||||||
86
.github/instructions/i18n-convert.instructions.md
vendored
Normal file
86
.github/instructions/i18n-convert.instructions.md
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
applyTo: '**/*.vue'
|
||||||
|
---
|
||||||
|
|
||||||
|
You are given a Nuxt/Vue single-file component (.vue). Your task is to convert every hard-coded natural-language string in the <template> into our localization system using vue-i18n with utilities from `@modrinth/ui`.
|
||||||
|
|
||||||
|
Please follow these rules precisely:
|
||||||
|
|
||||||
|
1. Identify translatable strings
|
||||||
|
|
||||||
|
- Scan the <template> for all user-visible strings (inner text, alt attributes, placeholders, button labels, etc.). Do not extract dynamic expressions (like {{ user.name }}) or HTML tags. Only extract static human-readable text.
|
||||||
|
- There may be strings within the <script> block, e.g dropdown option labels, notifications etc.
|
||||||
|
|
||||||
|
2. Create message definitions
|
||||||
|
|
||||||
|
- In the <script setup> block, import `defineMessage` or `defineMessages` from `@modrinth/ui`.
|
||||||
|
- For each extracted string, define a message with a unique `id` (use a descriptive prefix based on the component path, e.g. `auth.welcome.long-title`) and a `defaultMessage` equal to the original English string.
|
||||||
|
Example:
|
||||||
|
const messages = defineMessages({
|
||||||
|
welcomeTitle: { id: 'auth.welcome.title', defaultMessage: 'Welcome' },
|
||||||
|
welcomeDescription: { id: 'auth.welcome.description', defaultMessage: 'You're now part of the community…' },
|
||||||
|
})
|
||||||
|
|
||||||
|
3. Handle variables and ICU formats
|
||||||
|
|
||||||
|
- Replace dynamic parts with ICU placeholders: "Hello, ${user.name}!" → `{name}` and defaultMessage: 'Hello, {name}!'
|
||||||
|
- For numbers/dates/times, use ICU options (e.g., currency): `{price, number, ::currency/USD}`
|
||||||
|
- For plurals/selects, use ICU: `'{count, plural, one {# message} other {# messages}}'`
|
||||||
|
|
||||||
|
4. Rich-text messages (links/markup)
|
||||||
|
|
||||||
|
- In `defaultMessage`, wrap link/markup ranges with tags, e.g.:
|
||||||
|
"By creating an account, you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
|
||||||
|
- Render rich-text messages with `<IntlFormatted>` from `@modrinth/ui` using named slots:
|
||||||
|
<IntlFormatted :message-id="messages.tosLabel">
|
||||||
|
<template #terms-link="{ children }">
|
||||||
|
<NuxtLink to="/terms">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
<template #privacy-link="{ children }">
|
||||||
|
<NuxtLink to="/privacy">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
- For simple emphasis: `'Welcome to <strong>Modrinth</strong>!'` with a slot:
|
||||||
|
<template #strong="{ children }">
|
||||||
|
<strong><component :is="() => children" /></strong>
|
||||||
|
</template>
|
||||||
|
- For more complex child handling, use `normalizeChildren` from `@modrinth/ui`:
|
||||||
|
<template #bold="{ children }">
|
||||||
|
<strong><component :is="() => normalizeChildren(children)" /></strong>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
5. Formatting in templates
|
||||||
|
|
||||||
|
- Import and use `useVIntl()` from `@modrinth/ui`; prefer `formatMessage` for simple strings:
|
||||||
|
`const { formatMessage } = useVIntl()`
|
||||||
|
`<button>{{ formatMessage(messages.welcomeTitle) }}</button>`
|
||||||
|
- Pass variables as a second argument:
|
||||||
|
`{{ formatMessage(messages.greeting, { name: user.name }) }}`
|
||||||
|
|
||||||
|
6. Naming conventions and id stability
|
||||||
|
|
||||||
|
- Make `id`s descriptive and stable (e.g., `error.generic.default.title`). Group related messages with `defineMessages`.
|
||||||
|
|
||||||
|
7. Avoid Vue/ICU delimiter collisions
|
||||||
|
|
||||||
|
- If an ICU placeholder would end right before `}}` in a Vue template, insert a space so it becomes `} }` to avoid parsing issues.
|
||||||
|
|
||||||
|
8. Update imports and remove literals
|
||||||
|
|
||||||
|
- Ensure imports from `@modrinth/ui` are present: `defineMessage`/`defineMessages`, `useVIntl`, `IntlFormatted`, and optionally `normalizeChildren`.
|
||||||
|
- Replace all hard-coded strings with `formatMessage(...)` or `<IntlFormatted>` and remove the literals.
|
||||||
|
|
||||||
|
9. Preserve functionality
|
||||||
|
|
||||||
|
- Do not change logic, layout, reactivity, or bindings—only refactor strings into i18n.
|
||||||
|
|
||||||
|
Use existing patterns from our codebase:
|
||||||
|
|
||||||
|
- Variables/plurals: see `apps/frontend/src/pages/frog.vue`
|
||||||
|
- Rich-text link tags: see `apps/frontend/src/pages/auth/welcome.vue` and `apps/frontend/src/error.vue`
|
||||||
|
|
||||||
|
When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.
|
||||||
4
.github/templates/crowdin-pr.md
vendored
Normal file
4
.github/templates/crowdin-pr.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
This pull request is created according to the `.github/workflows/i18n-pull.yml` file.
|
||||||
|
|
||||||
|
- 🌐 [Contribute to translations on Crowdin](https://translate.modrinth.com/)
|
||||||
|
- 🔄 [Dispatch this workflow again to update this PR](https://github.com/Modrinth/code/actions/workflows/i18n-pull.yml)
|
||||||
3
.github/workflows/COPYING.md
vendored
Normal file
3
.github/workflows/COPYING.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Copying
|
||||||
|
|
||||||
|
Modrinth's Github workflows are licensed under the MIT License, which is provided in the file [LICENSE](./LICENSE).
|
||||||
7
.github/workflows/LICENSE
vendored
Normal file
7
.github/workflows/LICENSE
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2025 Rinth, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
25
.github/workflows/astralrinth-build.yml
vendored
25
.github/workflows/astralrinth-build.yml
vendored
@@ -4,9 +4,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- master
|
||||||
|
- prod
|
||||||
|
- release
|
||||||
|
- beta
|
||||||
- feature*
|
- feature*
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- release-*
|
||||||
|
- beta-*
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/astralrinth-build.yml
|
- .github/workflows/astralrinth-build.yml
|
||||||
- 'apps/app/**'
|
- 'apps/app/**'
|
||||||
@@ -95,7 +100,12 @@ jobs:
|
|||||||
libayatana-appindicator3-dev \
|
libayatana-appindicator3-dev \
|
||||||
librsvg2-dev \
|
librsvg2-dev \
|
||||||
xdg-utils \
|
xdg-utils \
|
||||||
openjdk-11-jdk
|
openjdk-17-jdk
|
||||||
|
|
||||||
|
- name: ⚙️ Set application environment
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cp packages/app-lib/.env.prod packages/app-lib/.env
|
||||||
|
|
||||||
- name: 💨 Setup Turbo cache
|
- name: 💨 Setup Turbo cache
|
||||||
uses: rharkor/caching-for-turbo@v1.8
|
uses: rharkor/caching-for-turbo@v1.8
|
||||||
@@ -133,7 +143,7 @@ jobs:
|
|||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$env:JAVA_HOME = "$env:JAVA_HOME_11_X64"
|
$env:JAVA_HOME = "$env:JAVA_HOME_17_X64"
|
||||||
pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis'
|
pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis'
|
||||||
env:
|
env:
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
@@ -144,10 +154,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: App bundle (${{ matrix.artifact-target-name }})
|
name: App bundle (${{ matrix.artifact-target-name }})
|
||||||
path: |
|
path: |
|
||||||
target/release/bundle/appimage/AstralRinth App_*.AppImage*
|
target/release/bundle/**
|
||||||
target/release/bundle/deb/AstralRinth App_*.deb*
|
target/*/release/bundle/**
|
||||||
target/release/bundle/rpm/AstralRinth App-*.rpm*
|
|
||||||
target/universal-apple-darwin/release/bundle/macos/AstralRinth App.app.tar.gz*
|
|
||||||
target/universal-apple-darwin/release/bundle/dmg/AstralRinth App_*.dmg*
|
|
||||||
target/release/bundle/nsis/AstralRinth App_*-setup.exe*
|
|
||||||
target/release/bundle/nsis/AstralRinth App_*-setup.nsis.zip*
|
|
||||||
|
|||||||
25
.gitignore
vendored
25
.gitignore
vendored
@@ -9,7 +9,6 @@ tmp
|
|||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
/.idea
|
|
||||||
.project
|
.project
|
||||||
.classpath
|
.classpath
|
||||||
.c9/
|
.c9/
|
||||||
@@ -24,6 +23,14 @@ node_modules
|
|||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# IDE - IntelliJ
|
||||||
|
.idea/*
|
||||||
|
!.idea/code.iml
|
||||||
|
!.idea/gradle.xml
|
||||||
|
!.idea/icon.svg
|
||||||
|
!.idea/modules.xml
|
||||||
|
!.idea/vcs.xml
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/.sass-cache
|
/.sass-cache
|
||||||
/connect.lock
|
/connect.lock
|
||||||
@@ -56,8 +63,16 @@ generated
|
|||||||
# app testing dir
|
# app testing dir
|
||||||
app-playground-data/*
|
app-playground-data/*
|
||||||
|
|
||||||
# soley because i need the PORT to be 3002 due to WSL stuff
|
|
||||||
.env
|
|
||||||
apps/frontend/.env
|
|
||||||
|
|
||||||
.astro
|
.astro
|
||||||
|
.claude
|
||||||
|
|
||||||
|
# labrinth demo fixtures
|
||||||
|
apps/labrinth/fixtures/demo
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
storybook-static
|
||||||
|
|
||||||
|
.wrangler/
|
||||||
|
|
||||||
|
# frontend robots.txt
|
||||||
|
apps/frontend/src/public/robots.txt
|
||||||
|
|||||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
7
.idea/code.iml
generated
7
.idea/code.iml
generated
@@ -10,8 +10,13 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/tests" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/apps/labrinth/tests" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/packages/app-lib/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/packages/rust-common/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/packages/ariadne/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/packages/ariadne/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/path-util/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-log/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/examples" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-maxmind/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/modrinth-util/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/packages/muralpay/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="DiscordProjectSettings">
|
|
||||||
<option name="show" value="PROJECT_FILES" />
|
|
||||||
<option name="description" value="" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
17
.idea/gradle.xml
generated
Normal file
17
.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1"/>
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$/packages/app-lib/java"/>
|
||||||
|
<option name="gradleJvm" value="#JAVA_HOME"/>
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$/packages/app-lib/java"/>
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
.idea/icon.svg
generated
Normal file
4
.idea/icon.svg
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="512" height="514" viewBox="0 0 512 514" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M503.16 323.56C514.55 281.47 515.32 235.91 503.2 190.76C466.57 54.2299 326.04 -26.8001 189.33 9.77991C83.8101 38.0199 11.3899 128.07 0.689941 230.47H43.99C54.29 147.33 113.74 74.7298 199.75 51.7098C306.05 23.2598 415.13 80.6699 453.17 181.38L411.03 192.65C391.64 145.8 352.57 111.45 306.3 96.8198L298.56 140.66C335.09 154.13 364.72 184.5 375.56 224.91C391.36 283.8 361.94 344.14 308.56 369.17L320.09 412.16C390.25 383.21 432.4 310.3 422.43 235.14L464.41 223.91C468.91 252.62 467.35 281.16 460.55 308.07L503.16 323.56Z" fill="#00af5c"/>
|
||||||
|
<path d="M321.99 504.22C185.27 540.8 44.7501 459.77 8.11011 323.24C3.84011 307.31 1.17 291.33 0 275.46H43.27C44.36 287.37 46.4699 299.35 49.6799 311.29C53.0399 323.8 57.45 335.75 62.79 347.07L101.38 323.92C98.1299 316.42 95.39 308.6 93.21 300.47C69.17 210.87 122.41 118.77 212.13 94.7601C229.13 90.2101 246.23 88.4401 262.93 89.1501L255.19 133C244.73 133.05 234.11 134.42 223.53 137.25C157.31 154.98 118.01 222.95 135.75 289.09C136.85 293.16 138.13 297.13 139.59 300.99L188.94 271.38L174.07 231.95L220.67 184.08L279.57 171.39L296.62 192.38L269.47 219.88L245.79 227.33L228.87 244.72L237.16 267.79C237.16 267.79 253.95 285.63 253.98 285.64L277.7 279.33L294.58 260.79L331.44 249.12L342.42 273.82L304.39 320.45L240.66 340.63L212.08 308.81L162.26 338.7C187.8 367.78 226.2 383.93 266.01 380.56L277.54 423.55C218.13 431.41 160.1 406.82 124.05 361.64L85.6399 384.68C136.25 451.17 223.84 484.11 309.61 461.16C371.35 444.64 419.4 402.56 445.42 349.38L488.06 364.88C457.17 431.16 398.22 483.82 321.99 504.22Z" fill="#00af5c"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
26
.idea/libraries/KotlinJavaRuntime.xml
generated
26
.idea/libraries/KotlinJavaRuntime.xml
generated
@@ -1,26 +0,0 @@
|
|||||||
<component name="libraryTable">
|
|
||||||
<library name="KotlinJavaRuntime" type="repository">
|
|
||||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0" />
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-javadoc.jar!/" />
|
|
||||||
</JAVADOC>
|
|
||||||
<SOURCES>
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-sources.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-sources.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-sources.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-sources.jar!/" />
|
|
||||||
</SOURCES>
|
|
||||||
</library>
|
|
||||||
</component>
|
|
||||||
9
.idea/modules.xml
generated
9
.idea/modules.xml
generated
@@ -2,7 +2,14 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/code.iml" filepath="$PROJECT_DIR$/.idea/code.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/code.iml"
|
||||||
|
filepath="$PROJECT_DIR$/.idea/code.iml"/>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.iml"
|
||||||
|
filepath="$PROJECT_DIR$/.idea/modules/theseus.iml"/>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.main.iml"
|
||||||
|
filepath="$PROJECT_DIR$/.idea/modules/theseus.main.iml"/>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/modules/theseus.test.iml"
|
||||||
|
filepath="$PROJECT_DIR$/.idea/modules/theseus.test.iml"/>
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
8
.idea/vcs.xml
generated
8
.idea/vcs.xml
generated
@@ -2,11 +2,13 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CommitMessageInspectionProfile">
|
<component name="CommitMessageInspectionProfile">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="CommitFormat" enabled="true" level="WARNING"
|
||||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
enabled_by_default="true"/>
|
||||||
|
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING"
|
||||||
|
enabled_by_default="true"/>
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git"/>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Cargo.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
.github/**/*.png
|
||||||
28
.vscode/settings.json
vendored
28
.vscode/settings.json
vendored
@@ -2,8 +2,32 @@
|
|||||||
"prettier.endOfLine": "lf",
|
"prettier.endOfLine": "lf",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
|
||||||
"editor.detectIndentation": true,
|
"editor.detectIndentation": false,
|
||||||
|
"editor.insertSpaces": false,
|
||||||
|
"files.eol": "\n",
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.organizeImports": "always"
|
||||||
|
},
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[scss]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
CLAUDE.md
Normal file
63
CLAUDE.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
Use TAB instead of spaces.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
There are two similar frontends in the Modrinth monorepo, the website (apps/frontend) and the app frontend (apps/app-frontend).
|
||||||
|
|
||||||
|
Both use Tailwind v3, and their respective configs can be seen at `tailwind.config.ts` and `tailwind.config.js` respectively.
|
||||||
|
|
||||||
|
Both utilize shared and common components from `@modrinth/ui` which can be found at `packages/ui`, and stylings from `@modrinth/assets` which can be found at `packages/assets`.
|
||||||
|
|
||||||
|
Both can utilize icons from `@modrinth/assets`, which are automatically generated based on what's available within the `icons` folder of the `packages/assets` directory. You can see the generated icons list in `generated-icons.ts`.
|
||||||
|
|
||||||
|
Both have access to our dependency injection framework, examples as seen in `packages/ui/src/providers/`. Ideally any state which is shared between a page and it's subpages should be shared using this dependency injection framework.
|
||||||
|
|
||||||
|
### Website (apps/frontend)
|
||||||
|
|
||||||
|
Before a pull request can be opened for the website, run `pnpm prepr:frontend:web` from the root folder, otherwise CI will fail.
|
||||||
|
|
||||||
|
To run a development version of the frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within the `apps/frontend` folder into `apps/frontend/.env`. Then you can run the frontend by running `pnpm web:dev` in the root folder.
|
||||||
|
|
||||||
|
### App Frontend (apps/app-frontend)
|
||||||
|
|
||||||
|
Before a pull request can be opened for the app frontend, run `pnpm prepr:frontend:app` from the root folder, otherwise CI will fail.
|
||||||
|
|
||||||
|
To run a development version of the app frontend, you must first copy over the relevant `.env` template file (prod, staging or local, usually prod) within `packages/app-lib` into `packages/app-lib/.env`. Then you must run the app itself by running `pnpm app:dev` in the root folder.
|
||||||
|
|
||||||
|
### Localization
|
||||||
|
|
||||||
|
Refer to `.github/instructions/i18n-convert.instructions.md` if the user asks you to perform any i18n conversion work on a component, set of components, pages or sets of pages.
|
||||||
|
|
||||||
|
## Labrinth
|
||||||
|
|
||||||
|
Labrinth is the backend API service for Modrinth.
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Before a pull request can be opened, run `cargo clippy -p labrinth --all-targets` and make sure there are ZERO warnings, otherwise CI will fail.
|
||||||
|
|
||||||
|
Use `cargo test -p labrinth --all-targets` to test your changes. All tests must pass, otherwise CI will fail.
|
||||||
|
|
||||||
|
To prepare the sqlx cache, cd into `apps/labrinth` and run `cargo sqlx prepare`. Make sure to NEVER run `cargo sqlx prepare --workspace`.
|
||||||
|
|
||||||
|
Read the root `docker-compose.yml` to see what running services are available while developing. Use `docker exec` to access these services.
|
||||||
|
|
||||||
|
When the user refers to "performing pre-PR checks", do the following:
|
||||||
|
|
||||||
|
- Run clippy as described above
|
||||||
|
- DO NOT run tests unless explicitly requested (they take a long time)
|
||||||
|
- Prepare the sqlx cache
|
||||||
|
|
||||||
|
### Clickhouse
|
||||||
|
|
||||||
|
Use `docker exec labrinth-clickhouse clickhouse-client` to access the Clickhouse instance. We use the `staging_ariadne` database to store data in testing.
|
||||||
|
|
||||||
|
### Postgres
|
||||||
|
|
||||||
|
Use `docker exec labrinth-postgres psql -U labrinth -d labrinth -c "SELECT 1"` to access the PostgreSQL instance, replacing the `SELECT 1` with your query.
|
||||||
|
|
||||||
|
# Guidelines
|
||||||
|
|
||||||
|
- Do not create new non-source code files (e.g. Bash scripts, SQL scripts) unless explicitly prompted to.
|
||||||
12
COPYING.md
12
COPYING.md
@@ -2,12 +2,20 @@
|
|||||||
|
|
||||||
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
|
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
|
||||||
|
|
||||||
For detailed information, consult each package's COPYING.md file, if available.
|
For detailed information, consult each package's COPYING.md, LICENSE.txt, or LICENSE file, if available.
|
||||||
|
|
||||||
## Modrinth Branding
|
## Modrinth Branding
|
||||||
|
|
||||||
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.
|
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-2024 Rinth, Inc.
|
> All rights reserved. © 2020-2025 Rinth, Inc.
|
||||||
|
|
||||||
|
This includes, but may not be limited to, the following files:
|
||||||
|
|
||||||
|
- .idea/icon.svg
|
||||||
|
- .github/api_cover.png
|
||||||
|
- .github/app_cover.png
|
||||||
|
- .github/monorepo_cover.png
|
||||||
|
- .github/web_cover.png
|
||||||
|
|
||||||
If you fork this repository, you must remove all Modrinth branding assets from your fork.
|
If you fork this repository, you must remove all Modrinth branding assets from your fork.
|
||||||
|
|||||||
4411
Cargo.lock
generated
4411
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
197
Cargo.toml
197
Cargo.toml
@@ -8,119 +8,142 @@ members = [
|
|||||||
"packages/app-lib",
|
"packages/app-lib",
|
||||||
"packages/ariadne",
|
"packages/ariadne",
|
||||||
"packages/daedalus",
|
"packages/daedalus",
|
||||||
|
"packages/modrinth-log",
|
||||||
|
"packages/modrinth-maxmind",
|
||||||
|
"packages/modrinth-util",
|
||||||
|
"packages/path-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
rust-version = "1.90.0"
|
||||||
|
repository = "https://github.com/modrinth/code"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
actix-cors = "0.7.1"
|
actix-cors = "0.7.1"
|
||||||
actix-files = "0.6.6"
|
actix-files = "0.6.8"
|
||||||
actix-http = "3.11.0"
|
actix-http = "3.11.2"
|
||||||
actix-multipart = "0.7.2"
|
actix-multipart = "0.7.2"
|
||||||
actix-rt = "2.10.0"
|
actix-rt = "2.11.0"
|
||||||
actix-web = "4.11.0"
|
actix-web = "4.11.0"
|
||||||
actix-web-prom = "0.10.0"
|
actix-web-prom = "0.10.0"
|
||||||
actix-ws = "0.3.0"
|
actix-ws = "0.3.0"
|
||||||
|
arc-swap = "1.7.1"
|
||||||
argon2 = { version = "0.5.3", features = ["std"] }
|
argon2 = { version = "0.5.3", features = ["std"] }
|
||||||
ariadne = { path = "packages/ariadne" }
|
ariadne = { path = "packages/ariadne" }
|
||||||
async_zip = "0.0.17"
|
async-compression = { version = "0.4.32", default-features = false }
|
||||||
async-compression = { version = "0.4.25", default-features = false }
|
|
||||||
async-recursion = "1.1.1"
|
async-recursion = "1.1.1"
|
||||||
async-stripe = { version = "0.41.0", default-features = false, features = [
|
async-stripe = { version = "0.41.0", default-features = false, features = [
|
||||||
"runtime-tokio-hyper-rustls",
|
"runtime-tokio-hyper-rustls",
|
||||||
] }
|
] }
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.89"
|
||||||
async-tungstenite = { version = "0.29.1", default-features = false, features = [
|
async-tungstenite = { version = "0.31.0", default-features = false, features = [
|
||||||
"futures-03-sink",
|
"futures-03-sink"
|
||||||
] }
|
] }
|
||||||
async-walkdir = "2.1.0"
|
async-walkdir = "2.1.0"
|
||||||
|
async_zip = "0.0.18"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bitflags = "2.9.1"
|
bitflags = "2.9.4"
|
||||||
bytemuck = "1.23.0"
|
bytemuck = "1.24.0"
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
censor = "0.3.0"
|
censor = "0.3.0"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.42"
|
||||||
clap = "4.5.40"
|
cidre = { version = "0.11.3", default-features = false, features = [
|
||||||
clickhouse = "0.13.3"
|
"macos_15_0"
|
||||||
|
] }
|
||||||
|
clap = "4.5.48"
|
||||||
|
clickhouse = "0.14.0"
|
||||||
|
color-eyre = "0.6.5"
|
||||||
color-thief = "0.2.2"
|
color-thief = "0.2.2"
|
||||||
console-subscriber = "0.4.1"
|
const_format = "0.2.34"
|
||||||
daedalus = { path = "packages/daedalus" }
|
daedalus = { path = "packages/daedalus" }
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
data-url = "0.3.1"
|
data-url = "0.3.2"
|
||||||
deadpool-redis = "0.21.1"
|
deadpool-redis = "0.22.0"
|
||||||
|
derive_more = "2.0.1"
|
||||||
|
directories = "6.0.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
discord-rich-presence = "0.2.5"
|
discord-rich-presence = "1.0.0"
|
||||||
dotenv-build = "0.1.1"
|
dotenv-build = "0.1.1"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
either = "1.15.0"
|
either = "1.15.0"
|
||||||
encoding_rs = "0.8.35"
|
encoding_rs = "0.8.35"
|
||||||
enumset = "1.1.6"
|
enumset = "1.1.10"
|
||||||
flate2 = "1.1.2"
|
eyre = "0.6.12"
|
||||||
|
flate2 = "1.1.4"
|
||||||
fs4 = { version = "0.13.1", default-features = false }
|
fs4 = { version = "0.13.1", default-features = false }
|
||||||
futures = { version = "0.3.31", default-features = false }
|
futures = "0.3.31"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
hashlink = "0.10.0"
|
|
||||||
heck = "0.5.0"
|
heck = "0.5.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hickory-resolver = "0.25.2"
|
hickory-resolver = "0.25.2"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
hyper = "1.6.0"
|
hyper = "1.7.0"
|
||||||
hyper-rustls = { version = "0.27.7", default-features = false, features = [
|
hyper-rustls = { version = "0.27.7", default-features = false, features = [
|
||||||
|
"aws-lc-rs",
|
||||||
"http1",
|
"http1",
|
||||||
"native-tokio",
|
"native-tokio",
|
||||||
"ring",
|
|
||||||
"tls12",
|
"tls12",
|
||||||
] }
|
] }
|
||||||
hyper-util = "0.1.14"
|
hyper-util = "0.1.17"
|
||||||
iana-time-zone = "0.1.63"
|
iana-time-zone = "0.1.64"
|
||||||
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
|
image = { version = "0.25.8", default-features = false, features = ["rayon"] }
|
||||||
indexmap = "2.9.0"
|
indexmap = "2.11.4"
|
||||||
indicatif = "0.17.11"
|
indicatif = "0.18.0"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
jemalloc_pprof = "0.7.0"
|
jemalloc_pprof = "0.8.1"
|
||||||
json-patch = { version = "4.0.0", default-features = false }
|
json-patch = { version = "4.1.0", default-features = false }
|
||||||
lettre = { version = "0.11.17", default-features = false, features = [
|
lettre = { version = "0.11.19", default-features = false, features = [
|
||||||
|
"aws-lc-rs",
|
||||||
"builder",
|
"builder",
|
||||||
"hostname",
|
"hostname",
|
||||||
"pool",
|
"pool",
|
||||||
"ring",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs",
|
||||||
"smtp-transport",
|
"smtp-transport",
|
||||||
|
"tokio1",
|
||||||
|
"tokio1-rustls",
|
||||||
] }
|
] }
|
||||||
maxminddb = "0.26.0"
|
maxminddb = "0.26.0"
|
||||||
meilisearch-sdk = { version = "0.28.0", default-features = false }
|
meilisearch-sdk = { version = "0.30.0", default-features = false }
|
||||||
|
modrinth-log = { path = "packages/modrinth-log" }
|
||||||
|
modrinth-util = { path = "packages/modrinth-util" }
|
||||||
|
muralpay = { path = "packages/muralpay" }
|
||||||
murmur2 = "0.1.0"
|
murmur2 = "0.1.0"
|
||||||
native-dialog = "0.9.0"
|
native-dialog = "0.9.2"
|
||||||
notify = { version = "8.0.0", default-features = false }
|
notify = { version = "8.2.0", default-features = false }
|
||||||
notify-debouncer-mini = { version = "0.6.0", default-features = false }
|
notify-debouncer-mini = { version = "0.7.0", default-features = false }
|
||||||
p256 = "0.13.2"
|
p256 = "0.13.2"
|
||||||
|
parking_lot = "0.12.5"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
png = "0.17.16"
|
path-util = { path = "packages/path-util" }
|
||||||
|
phf = { version = "0.13.1", features = ["macros"] }
|
||||||
|
png = "0.18.0"
|
||||||
prometheus = "0.14.0"
|
prometheus = "0.14.0"
|
||||||
quartz_nbt = "0.2.9"
|
quartz_nbt = "0.2.9"
|
||||||
quick-xml = "0.37.5"
|
quick-xml = "0.38.3"
|
||||||
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
||||||
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
||||||
redis = "=0.31.0" # Locked on 0.31 until deadpool-redis updates to 0.32
|
redis = "0.32.7"
|
||||||
regex = "1.11.1"
|
regex = "1.12.2"
|
||||||
reqwest = { version = "0.12.20", default-features = false }
|
reqwest = { version = "0.12.24", default-features = false }
|
||||||
rgb = "0.8.50"
|
rgb = "0.8.52"
|
||||||
rust_decimal = { version = "1.37.2", features = [
|
rust_decimal = { version = "1.39.0", features = [
|
||||||
"serde-with-float",
|
"serde-with-float",
|
||||||
"serde-with-str",
|
"serde-with-str"
|
||||||
] }
|
] }
|
||||||
rust_iso3166 = "0.1.14"
|
rust_iso3166 = "0.1.14"
|
||||||
rust-s3 = { version = "0.35.1", default-features = false, features = [
|
rust-s3 = { version = "0.37.0", default-features = false, features = [
|
||||||
"fail-on-err",
|
"fail-on-err",
|
||||||
"tags",
|
"tags",
|
||||||
"tokio-rustls-tls",
|
"tokio-rustls-tls",
|
||||||
] }
|
] }
|
||||||
|
rustls = "0.23.32"
|
||||||
rusty-money = "0.4.1"
|
rusty-money = "0.4.1"
|
||||||
sentry = { version = "0.41.0", default-features = false, features = [
|
secrecy = "0.10.3"
|
||||||
|
sentry = { version = "0.45.0", default-features = false, features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"contexts",
|
"contexts",
|
||||||
"debug-images",
|
"debug-images",
|
||||||
@@ -128,57 +151,67 @@ sentry = { version = "0.41.0", default-features = false, features = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls",
|
"rustls",
|
||||||
] }
|
] }
|
||||||
sentry-actix = "0.41.0"
|
serde = "1.0.228"
|
||||||
serde = "1.0.219"
|
serde_bytes = "0.11.19"
|
||||||
serde_bytes = "0.11.17"
|
|
||||||
serde_cbor = "0.11.2"
|
serde_cbor = "0.11.2"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.145"
|
||||||
serde_with = "3.13.0"
|
serde_with = "3.15.0"
|
||||||
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
sha1_smol = { version = "1.0.1", features = ["std"] }
|
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
spdx = "0.10.8"
|
shlex = "1.3.0"
|
||||||
|
spdx = "0.12.0"
|
||||||
sqlx = { version = "0.8.6", default-features = false }
|
sqlx = { version = "0.8.6", default-features = false }
|
||||||
sysinfo = { version = "0.35.2", default-features = false }
|
strum = "0.27.2"
|
||||||
|
sysinfo = { version = "0.37.2", default-features = false }
|
||||||
tar = "0.4.44"
|
tar = "0.4.44"
|
||||||
tauri = "2.6.1"
|
tauri = "2.8.5"
|
||||||
tauri-build = "2.3.0"
|
tauri-build = "2.4.1"
|
||||||
tauri-plugin-deep-link = "2.4.0"
|
tauri-plugin-deep-link = "2.4.3"
|
||||||
tauri-plugin-dialog = "2.3.0"
|
tauri-plugin-dialog = "2.4.0"
|
||||||
tauri-plugin-http = "2.5.0"
|
tauri-plugin-http = "2.5.2"
|
||||||
tauri-plugin-opener = "2.4.0"
|
tauri-plugin-opener = "2.5.0"
|
||||||
tauri-plugin-os = "2.3.0"
|
tauri-plugin-os = "2.3.1"
|
||||||
tauri-plugin-single-instance = "2.3.0"
|
tauri-plugin-single-instance = "2.3.4"
|
||||||
tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
|
tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"zip",
|
"zip",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-window-state = "2.3.0"
|
tauri-plugin-window-state = "2.4.0"
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.23.0"
|
||||||
theseus = { path = "packages/app-lib" }
|
theseus = { path = "packages/app-lib" }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.17"
|
||||||
tikv-jemalloc-ctl = "0.6.0"
|
tikv-jemalloc-ctl = "0.6.0"
|
||||||
tikv-jemallocator = "0.6.0"
|
tikv-jemallocator = "0.6.0"
|
||||||
tokio = "1.45.1"
|
tokio = "1.47.1"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tokio-util = "0.7.15"
|
tokio-util = "0.7.16"
|
||||||
totp-rs = "5.7.0"
|
totp-rs = "5.7.0"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-actix-web = "0.7.18"
|
tracing-actix-web = { version = "0.7.19", default-features = false }
|
||||||
|
tracing-ecs = "0.5.0"
|
||||||
tracing-error = "0.2.1"
|
tracing-error = "0.2.1"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.20"
|
||||||
url = "2.5.4"
|
typed-path = "0.12.0"
|
||||||
|
url = "2.5.7"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = "1.17.0"
|
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
|
||||||
|
utoipa-actix-web = { version = "0.1.2" }
|
||||||
|
utoipa-swagger-ui = { version = "9.0.2", features = ["actix-web", "vendored"] }
|
||||||
|
uuid = "1.18.1"
|
||||||
validator = "0.20.0"
|
validator = "0.20.0"
|
||||||
webp = { version = "0.3.0", default-features = false }
|
webp = { version = "0.3.1", default-features = false }
|
||||||
whoami = "1.6.0"
|
webview2-com = "0.38.0" # Should be updated in lockstep with wry
|
||||||
|
whoami = "1.6.1"
|
||||||
|
windows = "=0.61.3" # Locked on 0.61 until we can update windows-core to 0.62
|
||||||
|
windows-core = "=0.61.2" # Locked on 0.61 until webview2-com updates to 0.62
|
||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
woothee = "0.13.0"
|
woothee = "0.13.0"
|
||||||
yaserde = "0.12.0"
|
yaserde = "0.12.0"
|
||||||
zip = { version = "4.2.0", default-features = false, features = [
|
zbus = "5.11.0"
|
||||||
|
zip = { version = "6.0.0", default-features = false, features = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"deflate",
|
"deflate",
|
||||||
"deflate64",
|
"deflate64",
|
||||||
@@ -206,7 +239,7 @@ manual_assert = "warn"
|
|||||||
manual_instant_elapsed = "warn"
|
manual_instant_elapsed = "warn"
|
||||||
manual_is_variant_and = "warn"
|
manual_is_variant_and = "warn"
|
||||||
manual_let_else = "warn"
|
manual_let_else = "warn"
|
||||||
map_unwrap_or = "warn"
|
map_unwrap_or = "allow"
|
||||||
match_bool = "warn"
|
match_bool = "warn"
|
||||||
needless_collect = "warn"
|
needless_collect = "warn"
|
||||||
negative_feature_names = "warn"
|
negative_feature_names = "warn"
|
||||||
@@ -217,15 +250,13 @@ redundant_clone = "warn"
|
|||||||
redundant_feature_names = "warn"
|
redundant_feature_names = "warn"
|
||||||
redundant_type_annotations = "warn"
|
redundant_type_annotations = "warn"
|
||||||
todo = "warn"
|
todo = "warn"
|
||||||
|
too_many_arguments = "allow"
|
||||||
|
uninlined_format_args = "warn"
|
||||||
unnested_or_patterns = "warn"
|
unnested_or_patterns = "warn"
|
||||||
wildcard_dependencies = "warn"
|
wildcard_dependencies = "warn"
|
||||||
|
|
||||||
[workspace.lints.rust]
|
[profile.dev.package.sqlx-macros]
|
||||||
# Turn warnings into errors by default
|
opt-level = 3
|
||||||
warnings = "deny"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
wry = { git = "https://github.com/modrinth/wry", rev = "21db186" }
|
|
||||||
|
|
||||||
# Optimize for speed and reduce size on release builds
|
# Optimize for speed and reduce size on release builds
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@@ -235,5 +266,7 @@ lto = true # Enables link to optimizations
|
|||||||
panic = "abort" # Strip expensive panic clean-up logic
|
panic = "abort" # Strip expensive panic clean-up logic
|
||||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||||
|
|
||||||
[profile.dev.package.sqlx-macros]
|
# Specific profile for labrinth production builds
|
||||||
opt-level = 3
|
[profile.release-labrinth]
|
||||||
|
inherits = "release"
|
||||||
|
panic = "unwind" # Don't exit the whole app on panic in production
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -16,19 +16,13 @@
|
|||||||
|
|
||||||
# About Project
|
# About Project
|
||||||
|
|
||||||
## **AstralRinth • Empowering Your Minecraft Adventure**
|
## **AstralRinth • Empowering Your Minecraft Experience**
|
||||||
|
|
||||||
Welcome to **AstralRinth (AR)** — a powerful fork of Modrinth, reimagined to enhance your Minecraft journey. Whether you're a GUI enthusiast or a developer building with Modrinth’s API, **Theseus Core** is your launchpad into a new era of Minecraft gameplay.
|
**AstralRinth** — a powerful fork of Modrinth, reimagined to enhance your Minecraft journey. Whether you're a GUI enthusiast or a developer building with Modrinth’s API, **Theseus Core** is your launchpad into a new era of Minecraft gameplay.
|
||||||
|
|
||||||
- *Recently, improved integration with the Git Astralium API has been added.*
|
|
||||||
|
|
||||||
## **About the Software**
|
## **About the Software**
|
||||||
|
|
||||||
**AstralRinth** is a dedicated branch of the Theseus project, focused on **offline authentication**, offering you more flexibility and control. Play Minecraft without the need for constant online verification — a user-first approach to modern modded gaming.
|
**AstralRinth** is a dedicated branch of the Modrinth (a.k.a Theseus) project, focused on **offline authentication**, offering you more flexibility and control. Play Minecraft without the need for constant online verification — a user-first approach to modern modded gaming.
|
||||||
|
|
||||||
## **AR • Unlocking Minecraft's Boundless Horizon**
|
|
||||||
|
|
||||||
This unique fork introduces a **free trial Minecraft experience**, bypassing license checks while maintaining rich functionality. Currently includes:
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,8 +37,8 @@ To install the launcher:
|
|||||||
|
|
||||||
| Extension | OS | Notes |
|
| Extension | OS | Notes |
|
||||||
| --------- | ------- | --------------------------------------------------------------------- |
|
| --------- | ------- | --------------------------------------------------------------------- |
|
||||||
| `.msi` | Windows | Supported on all recent Windows versions |
|
| `.msi` | Windows | Supported on all recent Windows versions (10/11) |
|
||||||
| `.dmg` | macOS | Works on Ventura, Sonoma, Sequoia _(may also support older versions)_ |
|
| `.dmg` | macOS | Works on Ventura, Sonoma, Sequoia, Tahoe _(may also support older versions)_ |
|
||||||
| `.deb` | Linux | Basic support; compatibility may vary by distribution |
|
| `.deb` | Linux | Basic support; compatibility may vary by distribution |
|
||||||
|
|
||||||
### Installation Warnings
|
### Installation Warnings
|
||||||
@@ -70,7 +64,7 @@ Avoid using builds with these prefixes — they may be unstable or experimental:
|
|||||||
- No ads in the entire launcher.
|
- No ads in the entire launcher.
|
||||||
- Custom `.svg` vector icons for a distinct UI.
|
- Custom `.svg` vector icons for a distinct UI.
|
||||||
- Improved compatibility with both licensed and pirate accounts.
|
- Improved compatibility with both licensed and pirate accounts.
|
||||||
- Use **official microsoft accounts** or **offline/pirate accounts** — login won't break.
|
- Use **official microsoft accounts** or **offline/pirate accounts**.
|
||||||
- Supports license-free access for testing or personal use.
|
- Supports license-free access for testing or personal use.
|
||||||
- No dependence on official authentication services.
|
- No dependence on official authentication services.
|
||||||
- Discord Rich Presence integration:
|
- Discord Rich Presence integration:
|
||||||
@@ -82,7 +76,9 @@ Avoid using builds with these prefixes — they may be unstable or experimental:
|
|||||||
- Built-in update alerts for new versions posted on Git Astralium.
|
- Built-in update alerts for new versions posted on Git Astralium.
|
||||||
- Automatic download and installation capabilities.
|
- Automatic download and installation capabilities.
|
||||||
- Database migration fixes, when error occurred (Interactive Mode) (Modrinth issue)
|
- Database migration fixes, when error occurred (Interactive Mode) (Modrinth issue)
|
||||||
- ElyBy skin system integration (AuthLib / Java)
|
- Ely.by full integration
|
||||||
|
- The official account skin system is managed by ely.by
|
||||||
|
- Offline accounts must install AuthLib through the instance settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -90,15 +86,15 @@ Avoid using builds with these prefixes — they may be unstable or experimental:
|
|||||||
|
|
||||||
To begin using AstralRinth:
|
To begin using AstralRinth:
|
||||||
|
|
||||||
1. **Download Your OS Version**
|
1. **Download Latest Release**
|
||||||
|
|
||||||
- Go to the [releases page](https://git.astralium.su/didirus/AstralRinth/releases)
|
- Go to the [releases page](https://git.astralium.su/didirus/AstralRinth/releases)
|
||||||
- [How to choose a file](#downloadable-file-extensions)
|
- [How to choose a file](#downloadable-file-extensions)
|
||||||
- [How to choose a release](#installation-warnings)
|
- [How to choose a release](#installation-warnings)
|
||||||
|
|
||||||
2. **Log In**
|
2. **Log in or create new offline account**
|
||||||
|
|
||||||
- Use your official Mojang/Microsoft account, or test using a non-licensed account.
|
- Use your official Microsoft account (MSA), or test using a non-licensed account (Offline).
|
||||||
|
|
||||||
3. **Launch Minecraft**
|
3. **Launch Minecraft**
|
||||||
- Start Minecraft from the launcher.
|
- Start Minecraft from the launcher.
|
||||||
@@ -119,5 +115,5 @@ To begin using AstralRinth:
|
|||||||
|
|
||||||
If you'd like to support development, you can donate via the following crypto wallets:
|
If you'd like to support development, you can donate via the following crypto wallets:
|
||||||
|
|
||||||
- BTC (Telegram): 14g6asNYzcUoaQtB8B2QGKabgEvn55wfLj
|
- Toncoin (TON): UQA5pGOJhIz9UAVEOh5t2ur1QVbNr_FC1eq9bOb3GwTgaiqk
|
||||||
- TONCOIN (Telegram): UQAqUJ2_hVBI6k_gPyfp_jd-1K0OS61nIFPZuJWN9BwGAvKe
|
- USDT (TON): UQA5pGOJhIz9UAVEOh5t2ur1QVbNr_FC1eq9bOb3GwTgaiqk
|
||||||
|
|||||||
19
_typos.toml
Normal file
19
_typos.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
"**/src/locales/",
|
||||||
|
"apps/frontend/",
|
||||||
|
"patches/",
|
||||||
|
"packages/utils/",
|
||||||
|
"packages/ui/",
|
||||||
|
"packages/blog/",
|
||||||
|
# contains licenses like `CC-BY-ND-4.0`
|
||||||
|
"packages/moderation/src/data/stages/license.ts",
|
||||||
|
# contains payment card IDs like `IY1VMST1MOXS` which are flagged
|
||||||
|
"apps/labrinth/src/queue/payouts/mod.rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
# Terms Of Use in `tou-link`
|
||||||
|
tou = "tou"
|
||||||
|
# Google Ad Manager
|
||||||
|
gam = "gam"
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
**/dist
|
**/dist
|
||||||
*.gltf
|
*.gltf
|
||||||
|
src/locales/
|
||||||
|
src/assets/**/*.svg
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
# Copying
|
# 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.
|
The source code of Modrinth App's frontend 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
|
## 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.
|
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.
|
> All rights reserved. © 2020-2025 Rinth, Inc.
|
||||||
|
|
||||||
This includes, but may not be limited to, the following files:
|
|
||||||
|
|
||||||
- theseus_gui/src-tauri/icons/\*
|
|
||||||
|
|||||||
@@ -1,22 +1,2 @@
|
|||||||
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
|
import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
|
||||||
import { fixupPluginRules } from '@eslint/compat'
|
export default config
|
||||||
import turboPlugin from 'eslint-plugin-turbo'
|
|
||||||
|
|
||||||
export default createConfigForNuxt().append([
|
|
||||||
{
|
|
||||||
name: 'turbo',
|
|
||||||
plugins: {
|
|
||||||
turbo: fixupPluginRules(turboPlugin),
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'turbo/no-undeclared-env-vars': 'error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'modrinth',
|
|
||||||
rules: {
|
|
||||||
'vue/html-self-closing': 'off',
|
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script src="https://tally.so/widgets/embed.js" async></script>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,15 +9,17 @@
|
|||||||
"tsc:check": "vue-tsc --noEmit",
|
"tsc:check": "vue-tsc --noEmit",
|
||||||
"lint": "eslint . && prettier --check .",
|
"lint": "eslint . && prettier --check .",
|
||||||
"fix": "eslint . --fix && prettier --write .",
|
"fix": "eslint . --fix && prettier --write .",
|
||||||
"intl:extract": "formatjs extract \"{,src/components,src/composables,src/helpers,src/pages,src/store}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore '**/*.d.ts' --ignore 'node_modules' --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
|
"intl:extract": "formatjs extract \"src/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
|
||||||
"test": "vue-tsc --noEmit"
|
"test": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@geometrically/minecraft-motd-parser": "^1.1.4",
|
"@modrinth/api-client": "workspace:^",
|
||||||
"@modrinth/assets": "workspace:*",
|
"@modrinth/assets": "workspace:*",
|
||||||
"@modrinth/ui": "workspace:*",
|
"@modrinth/ui": "workspace:*",
|
||||||
"@modrinth/utils": "workspace:*",
|
"@modrinth/utils": "workspace:*",
|
||||||
"@sentry/vue": "^8.27.0",
|
"@sentry/vue": "^8.27.0",
|
||||||
|
"@sfirew/minecraft-motd-parser": "^1.1.6",
|
||||||
|
"@tanstack/vue-query": "^5.90.7",
|
||||||
"@tauri-apps/api": "^2.5.0",
|
"@tauri-apps/api": "^2.5.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||||
"@tauri-apps/plugin-http": "^2.5.0",
|
"@tauri-apps/plugin-http": "^2.5.0",
|
||||||
@@ -26,37 +28,38 @@
|
|||||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||||
"@tauri-apps/plugin-window-state": "^2.2.2",
|
"@tauri-apps/plugin-window-state": "^2.2.2",
|
||||||
"@types/three": "^0.172.0",
|
"@types/three": "^0.172.0",
|
||||||
"@vintl/vintl": "^4.4.1",
|
"intl-messageformat": "^10.7.7",
|
||||||
|
"vue-i18n": "^10.0.0",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"floating-vue": "^5.2.2",
|
"floating-vue": "^5.2.2",
|
||||||
"ofetch": "^1.3.4",
|
"ofetch": "^1.3.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^3.0.0",
|
||||||
"posthog-js": "^1.158.2",
|
"posthog-js": "^1.158.2",
|
||||||
"three": "^0.172.0",
|
"three": "^0.172.0",
|
||||||
"vite-svg-loader": "^5.1.0",
|
"vite-svg-loader": "^5.1.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-multiselect": "3.0.0",
|
"vue-multiselect": "3.0.0",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "^4.6.0",
|
||||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.1.1",
|
"@eslint/compat": "^1.1.1",
|
||||||
"@formatjs/cli": "^6.2.12",
|
"@formatjs/cli": "^6.2.12",
|
||||||
|
"@modrinth/tooling-config": "workspace:*",
|
||||||
"@nuxt/eslint-config": "^0.5.6",
|
"@nuxt/eslint-config": "^0.5.6",
|
||||||
"@taijased/vue-render-tracker": "^1.0.7",
|
"@taijased/vue-render-tracker": "^1.0.7",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^9.9.1",
|
||||||
"eslint-config-custom": "workspace:*",
|
|
||||||
"eslint-plugin-turbo": "^2.5.4",
|
"eslint-plugin-turbo": "^2.5.4",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.74.1",
|
"sass": "^1.74.1",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"tsconfig": "workspace:*",
|
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"vite": "^5.4.6",
|
"vite": "^6.0.0",
|
||||||
|
"vue-component-type-helpers": "^3.1.8",
|
||||||
"vue-tsc": "^2.1.6"
|
"vue-tsc": "^2.1.6"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.4.0",
|
"packageManager": "pnpm@9.4.0",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
24
apps/app-frontend/src/assets/external/index.js
vendored
24
apps/app-frontend/src/assets/external/index.js
vendored
@@ -1,18 +1,18 @@
|
|||||||
|
export { default as ATLauncherIcon } from './atlauncher.svg'
|
||||||
export { default as BuyMeACoffeeIcon } from './bmac.svg'
|
export { default as BuyMeACoffeeIcon } from './bmac.svg'
|
||||||
export { default as DiscordIcon } from './discord.svg'
|
export { default as DiscordIcon } from './discord.svg'
|
||||||
|
export { default as GDLauncherIcon } from './gdlauncher.png'
|
||||||
|
export { default as GithubIcon } from './github.svg'
|
||||||
|
export { default as GitLabIcon } from './gitlab.svg'
|
||||||
|
export { default as GoogleIcon } from './google.svg'
|
||||||
export { default as KoFiIcon } from './kofi.svg'
|
export { default as KoFiIcon } from './kofi.svg'
|
||||||
|
export { default as MastodonIcon } from './mastodon.svg'
|
||||||
|
export { default as MicrosoftIcon } from './microsoft.svg'
|
||||||
|
export { default as MultiMCIcon } from './multimc.webp'
|
||||||
|
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
||||||
export { default as PatreonIcon } from './patreon.svg'
|
export { default as PatreonIcon } from './patreon.svg'
|
||||||
export { default as PaypalIcon } from './paypal.svg'
|
export { default as PaypalIcon } from './paypal.svg'
|
||||||
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
|
||||||
export { default as TwitterIcon } from './twitter.svg'
|
|
||||||
export { default as GithubIcon } from './github.svg'
|
|
||||||
export { default as MastodonIcon } from './mastodon.svg'
|
|
||||||
export { default as RedditIcon } from './reddit.svg'
|
|
||||||
export { default as GoogleIcon } from './google.svg'
|
|
||||||
export { default as MicrosoftIcon } from './microsoft.svg'
|
|
||||||
export { default as SteamIcon } from './steam.svg'
|
|
||||||
export { default as GitLabIcon } from './gitlab.svg'
|
|
||||||
export { default as ATLauncherIcon } from './atlauncher.svg'
|
|
||||||
export { default as GDLauncherIcon } from './gdlauncher.png'
|
|
||||||
export { default as MultiMCIcon } from './multimc.webp'
|
|
||||||
export { default as PrismIcon } from './prism.svg'
|
export { default as PrismIcon } from './prism.svg'
|
||||||
|
export { default as RedditIcon } from './reddit.svg'
|
||||||
|
export { default as SteamIcon } from './steam.svg'
|
||||||
|
export { default as TwitterIcon } from './twitter.svg'
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export { default as SwapIcon } from './arrow-left-right.svg'
|
|
||||||
export { default as ToggleIcon } from './toggle.svg'
|
|
||||||
export { default as PackageIcon } from './package.svg'
|
|
||||||
export { default as VersionIcon } from './milestone.svg'
|
|
||||||
export { default as TextInputIcon } from './text-cursor-input.svg'
|
|
||||||
export { default as AddProjectImage } from './add-project.svg'
|
export { default as AddProjectImage } from './add-project.svg'
|
||||||
export { default as NewInstanceImage } from './new-instance.svg'
|
export { default as SwapIcon } from './arrow-left-right.svg'
|
||||||
export { default as MenuIcon } from './menu.svg'
|
export { default as MenuIcon } from './menu.svg'
|
||||||
export { default as ChatIcon } from './messages-square.svg'
|
export { default as ChatIcon } from './messages-square.svg'
|
||||||
export { default as Pirate } from './pirate.svg'
|
export { default as Pirate } from './pirate.svg'
|
||||||
export { default as Microsoft } from './microsoft.svg'
|
export { default as Microsoft } from './microsoft.svg'
|
||||||
export { default as PirateShip } from './pirate-ship.svg'
|
export { default as PirateShip } from './pirate-ship.svg'
|
||||||
|
export { default as VersionIcon } from './milestone.svg'
|
||||||
|
export { default as NewInstanceImage } from './new-instance.svg'
|
||||||
|
export { default as PackageIcon } from './package.svg'
|
||||||
|
export { default as TextInputIcon } from './text-cursor-input.svg'
|
||||||
|
export { default as ToggleIcon } from './toggle.svg'
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:serif="http://www.serif.com/" version="1.1" viewBox="0 0 1793 199">
|
|
||||||
<g>
|
|
||||||
<g id="Layer_1">
|
|
||||||
<g id="green" fill="var(--color-brand)">
|
|
||||||
<path d="M1184.1,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"/>
|
|
||||||
<path d="M1291.1,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"/>
|
|
||||||
<path d="M1357.2,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"/>
|
|
||||||
<path d="M1460,165.3l-40.8-95.1h23.2l35.1,83.9h-11.4l36.3-83.9h21.4l-40.8,95.1h-23Z"/>
|
|
||||||
<path d="M1579.6,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"/>
|
|
||||||
<path d="M1645.7,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"/>
|
|
||||||
<path d="M1749.9,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"/>
|
|
||||||
<g>
|
|
||||||
<path d="M9.8,143l63.4-38.1-5.8-15.3,18.1-18.6,22.9-4.9,6.6,8.2-10.6,10.7-9.2,2.9-6.6,6.8,3.2,9,6.5,6.9,9.2-2.5,6.6-7.2,14.3-4.5,4.3,9.6-14.8,18.1-24.8,7.8-11.1-12.4-63.6,38.2c-3-3.9-6.5-9.4-8.8-14.7ZM192.8,65.4l-50.4,13.6c2.8,7.4,3.7,11.7,4.5,16.5l50.3-13.6c-.8-5.4-2.2-10.8-4.4-16.5Z" fill-rule="evenodd"/>
|
|
||||||
<path d="M17.3,106.5c3.6,42.1,38.9,75.2,82,75.2s60.7-18.9,74-46.3l16.4,5.7c-15.8,34.1-50.3,57.9-90.4,57.9S3.6,158.2,0,106.5h17.3ZM.3,89.4C5.3,39.2,47.8,0,99.3,0s99.5,44.6,99.5,99.5-1.1,17.4-3.3,25.5l-16.3-5.7c1.6-6.5,2.4-13.1,2.4-19.8,0-45.4-36.9-82.3-82.3-82.3S22.6,48.7,17.6,89.4H.3Z" fill-rule="evenodd"/>
|
|
||||||
<path d="M99,51.6c-26.4,0-47.9,21.5-47.9,48s21.5,48,48,48,2.7,0,4-.2l4.8,16.8c-2.9.4-5.8.6-8.8.6-36,0-65.2-29.2-65.2-65.2S63.1,34.4,99,34.4s1.8,0,2.7,0l-2.7,17.1ZM118.6,37.4c26.4,8.3,45.6,33,45.6,62.2s-16.4,50.2-39.8,60l-4.8-16.7c16.2-7.7,27.4-24.2,27.4-43.3s-13-38.1-31.1-44.9l2.7-17.2Z" fill-rule="evenodd"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="black" fill="currentColor">
|
|
||||||
<path d="M354.8,69.2c12,0,21.7,3.4,28.6,10.4,7,7.2,10.6,17.5,10.6,31.5v54.8h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,5-6.8,12.2-6.8,21.3v48.5h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,4.8-6.8,12-6.8,21.3v48.5h-22.4v-95.6h21.3v12.2c3.6-4.3,8.1-7.5,13.4-9.8,5.4-2.3,11.3-3.4,17.9-3.4s13.6,1.3,19.2,3.9c5.5,2.9,9.8,6.8,13.1,12,3.9-5,8.9-8.9,15.2-11.8,6.3-2.7,13.1-4.1,20.6-4.1ZM466,167.2c-9.7,0-18.4-2.1-26.1-6.3-7.6-4-13.8-10.1-18.1-17.5-4.5-7.3-6.6-15.7-6.6-25.2s2.1-17.9,6.6-25.2c4.3-7.4,10.6-13.4,18.1-17.4,7.7-4.1,16.5-6.3,26.1-6.3s18.6,2.1,26.3,6.3c7.7,4.1,13.8,10,18.3,17.4,4.3,7.3,6.4,15.7,6.4,25.2s-2.1,17.9-6.4,25.2c-4.5,7.5-10.6,13.4-18.3,17.5-7.7,4.1-16.5,6.3-26.3,6.3h0ZM466,148c8.2,0,15-2.7,20.4-8.2,5.4-5.5,8.1-12.7,8.1-21.7s-2.7-16.1-8.1-21.7c-5.4-5.5-12.2-8.2-20.4-8.2s-15,2.7-20.2,8.2c-5.4,5.5-8.1,12.7-8.1,21.7s2.7,16.1,8.1,21.7c5.2,5.5,12,8.2,20.2,8.2ZM631.5,33.1v132.8h-21.5v-12.3c-3.7,4.4-8.3,7.9-13.6,10.2-5.5,2.3-11.5,3.4-18.1,3.4s-17.4-2-24.7-6.1c-7.3-4.1-13.2-9.8-17.4-17.4-4.1-7.3-6.3-15.9-6.3-25.6s2.1-18.3,6.3-25.6c4.1-7.3,10-13.1,17.4-17.2,7.3-4.1,15.6-6.1,24.7-6.1s12.2,1.1,17.4,3.2c5.2,2.1,9.8,5.4,13.4,9.7v-49h22.4ZM581.1,148c5.4,0,10.2-1.3,14.5-3.8,4.3-2.3,7.7-5.9,10.2-10.4,2.5-4.5,3.8-9.8,3.8-15.7s-1.3-11.3-3.8-15.7c-2.5-4.5-5.9-8.1-10.2-10.6-4.3-2.3-9.1-3.6-14.5-3.6s-10.2,1.3-14.5,3.6c-4.3,2.5-7.7,6.1-10.2,10.6-2.5,4.5-3.8,9.8-3.8,15.7s1.3,11.3,3.8,15.7c2.5,4.5,5.9,8.1,10.2,10.4,4.3,2.5,9.1,3.8,14.5,3.8ZM681.6,84.3c6.4-10,17.7-15,34-15v21.3c-1.7-.3-3.4-.5-5.2-.5-8.8,0-15.6,2.5-20.4,7.5-4.8,5.2-7.3,12.5-7.3,22v46.4h-22.4v-95.6h21.3v14h0ZM734.1,70.3h22.4v95.6h-22.4v-95.6ZM745.4,54.6c-4.1,0-7.5-1.3-10.2-3.9-2.7-2.4-4.2-5.9-4.1-9.5,0-3.8,1.4-7,4.1-9.7,2.7-2.5,6.1-3.8,10.2-3.8s7.5,1.3,10.2,3.6c2.7,2.5,4.1,5.5,4.1,9.3s-1.3,7.2-3.9,9.8c-2.7,2.7-6.3,4.1-10.4,4.1ZM839.5,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4v-95.6h21.3v12.3c3.8-4.5,8.4-7.7,14-10,5.5-2.3,12-3.4,19-3.4ZM964.8,160.7c-2.8,2.2-6,3.9-9.5,4.8-3.9,1.1-7.9,1.6-12,1.6-10.6,0-18.6-2.7-24.3-8.2-5.7-5.5-8.6-13.4-8.6-24v-46h-15.7v-17.9h15.7v-21.8h22.4v21.8h25.6v17.9h-25.6v45.5c0,4.7,1.1,8.2,3.4,10.6,2.3,2.5,5.5,3.8,9.8,3.8s9.1-1.3,12.5-3.9l6.3,15.9ZM1036.9,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4V33.1h22.4v48.3c3.8-3.9,8.2-7,13.8-9.1,5.4-2,11.5-3,18.1-3Z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -2,6 +2,8 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import '@modrinth/ui/src/styles/tailwind-utilities.css';
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'bundled-minecraft-font-mrapp';
|
font-family: 'bundled-minecraft-font-mrapp';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Instance from '@/components/ui/Instance.vue'
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import {
|
import {
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
|
EyeIcon,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
TrashIcon,
|
|
||||||
StopCircleIcon,
|
|
||||||
EyeIcon,
|
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
|
StopCircleIcon,
|
||||||
|
TrashIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, DropdownSelect } from '@modrinth/ui'
|
import { Button, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { formatCategoryHeader } from '@modrinth/utils'
|
import { formatCategoryHeader } from '@modrinth/utils'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import { useStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { duplicate, remove } from '@/helpers/profile.js'
|
import { computed, ref } from 'vue'
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
import Instance from '@/components/ui/Instance.vue'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||||
|
import { duplicate, remove } from '@/helpers/profile.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instances: {
|
instances: {
|
||||||
@@ -119,40 +122,50 @@ const handleOptionsClick = async (args) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state = useStorage(
|
||||||
|
`${props.label}-grid-display-state`,
|
||||||
|
{
|
||||||
|
group: 'Group',
|
||||||
|
sortBy: 'Name',
|
||||||
|
},
|
||||||
|
localStorage,
|
||||||
|
{ mergeDefaults: true },
|
||||||
|
)
|
||||||
|
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const group = ref('Group')
|
|
||||||
const sortBy = ref('Name')
|
|
||||||
|
|
||||||
const filteredResults = computed(() => {
|
const filteredResults = computed(() => {
|
||||||
|
const { group = 'Group', sortBy = 'Name' } = state.value
|
||||||
|
|
||||||
const instances = props.instances.filter((instance) => {
|
const instances = props.instances.filter((instance) => {
|
||||||
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
||||||
})
|
})
|
||||||
|
|
||||||
if (sortBy.value === 'Name') {
|
if (sortBy === 'Name') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Game version') {
|
if (sortBy === 'Game version') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return a.game_version.localeCompare(b.game_version, undefined, { numeric: true })
|
return a.game_version.localeCompare(b.game_version, undefined, { numeric: true })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Last played') {
|
if (sortBy === 'Last played') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Date created') {
|
if (sortBy === 'Date created') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Date modified') {
|
if (sortBy === 'Date modified') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
||||||
})
|
})
|
||||||
@@ -160,7 +173,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
const instanceMap = new Map()
|
const instanceMap = new Map()
|
||||||
|
|
||||||
if (group.value === 'Loader') {
|
if (group === 'Loader') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
const loader = formatCategoryHeader(instance.loader)
|
const loader = formatCategoryHeader(instance.loader)
|
||||||
if (!instanceMap.has(loader)) {
|
if (!instanceMap.has(loader)) {
|
||||||
@@ -169,7 +182,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
instanceMap.get(loader).push(instance)
|
instanceMap.get(loader).push(instance)
|
||||||
})
|
})
|
||||||
} else if (group.value === 'Game version') {
|
} else if (group === 'Game version') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
if (!instanceMap.has(instance.game_version)) {
|
if (!instanceMap.has(instance.game_version)) {
|
||||||
instanceMap.set(instance.game_version, [])
|
instanceMap.set(instance.game_version, [])
|
||||||
@@ -177,7 +190,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
instanceMap.get(instance.game_version).push(instance)
|
instanceMap.get(instance.game_version).push(instance)
|
||||||
})
|
})
|
||||||
} else if (group.value === 'Group') {
|
} else if (group === 'Group') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
if (instance.groups.length === 0) {
|
if (instance.groups.length === 0) {
|
||||||
instance.groups.push('None')
|
instance.groups.push('None')
|
||||||
@@ -197,7 +210,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
// For 'name', we intuitively expect the sorting to apply to the name of the group first, not just the name of the instance
|
// For 'name', we intuitively expect the sorting to apply to the name of the group first, not just the name of the instance
|
||||||
// ie: Category A should come before B, even if the first instance in B comes before the first instance in A
|
// ie: Category A should come before B, even if the first instance in B comes before the first instance in A
|
||||||
if (sortBy.value === 'Name') {
|
if (sortBy === 'Name') {
|
||||||
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
||||||
// None should always be first
|
// None should always be first
|
||||||
if (a[0] === 'None' && b[0] !== 'None') {
|
if (a[0] === 'None' && b[0] !== 'None') {
|
||||||
@@ -215,7 +228,7 @@ const filteredResults = computed(() => {
|
|||||||
}
|
}
|
||||||
// default sorting would do 1.20.4 < 1.8.9 because 2 < 8
|
// default sorting would do 1.20.4 < 1.8.9 because 2 < 8
|
||||||
// localeCompare with numeric=true puts 1.8.9 < 1.20.4 because 8 < 20
|
// localeCompare with numeric=true puts 1.8.9 < 1.20.4 because 8 < 20
|
||||||
if (group.value === 'Game version') {
|
if (group === 'Game version') {
|
||||||
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
||||||
return a[0].localeCompare(b[0], undefined, { numeric: true })
|
return a[0].localeCompare(b[0], undefined, { numeric: true })
|
||||||
})
|
})
|
||||||
@@ -239,7 +252,7 @@ const filteredResults = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-slot="{ selected }"
|
v-slot="{ selected }"
|
||||||
v-model="sortBy"
|
v-model="state.sortBy"
|
||||||
name="Sort Dropdown"
|
name="Sort Dropdown"
|
||||||
class="max-w-[16rem]"
|
class="max-w-[16rem]"
|
||||||
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
||||||
@@ -250,7 +263,7 @@ const filteredResults = computed(() => {
|
|||||||
</DropdownSelect>
|
</DropdownSelect>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-slot="{ selected }"
|
v-slot="{ selected }"
|
||||||
v-model="group"
|
v-model="state.group"
|
||||||
class="max-w-[16rem]"
|
class="max-w-[16rem]"
|
||||||
name="Group Dropdown"
|
name="Group Dropdown"
|
||||||
:options="['Group', 'Loader', 'Game version', 'None']"
|
:options="['Group', 'Loader', 'Game version', 'None']"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { useLoading } from '@/store/state.js'
|
import { useLoading } from '@/store/state.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
FolderOpenIcon,
|
|
||||||
PlayIcon,
|
|
||||||
PlusIcon,
|
|
||||||
TrashIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
GlobeIcon,
|
|
||||||
StopCircleIcon,
|
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
|
FolderOpenIcon,
|
||||||
|
GlobeIcon,
|
||||||
|
PlayIcon,
|
||||||
|
PlusIcon,
|
||||||
|
StopCircleIcon,
|
||||||
|
TrashIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
import { HeadingLink, injectNotificationManager } from '@modrinth/ui'
|
||||||
import Instance from '@/components/ui/Instance.vue'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
|
||||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
|
||||||
import { get_by_profile_path } from '@/helpers/process.js'
|
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
|
||||||
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
import Instance from '@/components/ui/Instance.vue'
|
||||||
|
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||||
|
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { get_by_profile_path } from '@/helpers/process.js'
|
||||||
|
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||||
|
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
import { handleSevereError } from '@/store/error.js'
|
||||||
import { install as installVersion } from '@/store/install.js'
|
import { install as installVersion } from '@/store/install.js'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
|
||||||
import { HeadingLink } from '@modrinth/ui'
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -163,7 +165,14 @@ const handleOptionsClick = async (args) => {
|
|||||||
await navigator.clipboard.writeText(args.item.path)
|
await navigator.clipboard.writeText(args.item.path)
|
||||||
break
|
break
|
||||||
case 'install': {
|
case 'install': {
|
||||||
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu')
|
await installVersion(
|
||||||
|
args.item.project_id,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'ProjectCardContextMenu',
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
).catch(handleError)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="mode !== 'isolated'" ref="button"
|
<div
|
||||||
|
v-if="mode !== 'isolated'"
|
||||||
|
ref="button"
|
||||||
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
class="button-base mt-2 px-3 py-2 bg-button-bg rounded-xl flex items-center gap-2"
|
||||||
:class="{ expanded: mode === 'expanded' }" @click="toggleMenu">
|
:class="{ expanded: mode === 'expanded' }"
|
||||||
<Avatar size="36px" :src="selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
@click="toggleMenu"
|
||||||
" />
|
>
|
||||||
|
<Avatar
|
||||||
|
size="36px"
|
||||||
|
:src="
|
||||||
|
selectedAccount ? avatarUrl : 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||||
|
"
|
||||||
|
/>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<span>
|
<span>
|
||||||
<component :is="getAccountType(selectedAccount)" v-if="selectedAccount" class="vector-icon" />
|
<component
|
||||||
|
:is="getAccountType(selectedAccount)"
|
||||||
|
v-if="selectedAccount"
|
||||||
|
class="vector-icon"
|
||||||
|
/>
|
||||||
{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}
|
{{ selectedAccount ? selectedAccount.profile.name : 'Select account' }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-secondary text-xs">Minecraft account</span>
|
<span class="text-secondary text-xs">Minecraft account</span>
|
||||||
@@ -14,31 +26,46 @@
|
|||||||
<DropdownIcon class="w-5 h-5 shrink-0" />
|
<DropdownIcon class="w-5 h-5 shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<Card v-if="showCard || mode === 'isolated'" ref="card" class="account-card"
|
<Card
|
||||||
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }">
|
v-if="showCard || mode === 'isolated'"
|
||||||
|
ref="card"
|
||||||
|
class="account-card"
|
||||||
|
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
|
||||||
|
>
|
||||||
<div v-if="selectedAccount" class="selected account">
|
<div v-if="selectedAccount" class="selected account">
|
||||||
<Avatar size="xs" :src="avatarUrl" />
|
<Avatar size="xs" :src="avatarUrl" />
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<component :is="getAccountType(selectedAccount)" class="vector-icon" /> {{ selectedAccount.profile.name }}
|
<component :is="getAccountType(selectedAccount)" class="vector-icon" />
|
||||||
|
{{ selectedAccount.profile.name }}
|
||||||
</h4>
|
</h4>
|
||||||
<p>Selected</p>
|
<p>Selected</p>
|
||||||
</div>
|
</div>
|
||||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.profile.id)">
|
<Button
|
||||||
|
v-tooltip="'Log out'"
|
||||||
|
icon-only
|
||||||
|
color="raised"
|
||||||
|
@click="logout(selectedAccount.profile.id)"
|
||||||
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="login-section account">
|
<div v-else class="login-section account">
|
||||||
<h4>Not signed in</h4>
|
<h4>Not signed in</h4>
|
||||||
<Button v-tooltip="'Log via Microsoft'" :disabled="microsoftLoginDisabled" icon-only @click="login()">
|
<Button
|
||||||
|
v-tooltip="'Log via Microsoft'"
|
||||||
|
:disabled="microsoftLoginDisabled"
|
||||||
|
icon-only
|
||||||
|
@click="login()"
|
||||||
|
>
|
||||||
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
<MicrosoftIcon v-if="!microsoftLoginDisabled" />
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||||
<PirateIcon />
|
<OfflineIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElyByLoginModal()">
|
||||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
<ElyByIcon v-if="!elyByLoginDisabled" />
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,23 +89,37 @@
|
|||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
<Button v-tooltip="'Add offline account'" icon-only @click="showOfflineLoginModal()">
|
||||||
<PirateIcon />
|
<OfflineIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElybyLoginModal()">
|
<Button v-tooltip="'Log via Ely.by'" icon-only @click="showElyByLoginModal()">
|
||||||
<ElyByIcon v-if="!elybyLoginDisabled" />
|
<ElyByIcon v-if="!elyByLoginDisabled" />
|
||||||
<SpinnerIcon v-else class="animate-spin" />
|
<SpinnerIcon v-else class="animate-spin" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</transition>
|
</transition>
|
||||||
<ModalWrapper ref="addElybyModal" class="modal" header="Authenticate with Ely.by">
|
<ModalWrapper ref="addElyByModal" class="modal" header="Authenticate with Ely.by">
|
||||||
<ModalWrapper ref="requestElybyTwoFactorCodeModal" class="modal"
|
<ModalWrapper
|
||||||
header="Ely.by requested 2FA code for authentication">
|
ref="requestElyByTwoFactorCodeModal"
|
||||||
|
class="modal"
|
||||||
|
header="Ely.by requested 2FA code for authentication"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="label">Enter your 2FA code</label>
|
<label class="label">Enter your 2FA code</label>
|
||||||
<input v-model="elybyTwoFactorCode" type="text" placeholder="Your 2FA code here..." class="input" />
|
<input
|
||||||
|
v-model="elyByTwoFactorCode"
|
||||||
|
type="text"
|
||||||
|
placeholder="Your 2FA code here..."
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
<Button
|
||||||
|
:disabled="elyByLoginDisabled"
|
||||||
|
icon-only
|
||||||
|
color="primary"
|
||||||
|
class="continue-button"
|
||||||
|
@click="addElyByProfile()"
|
||||||
|
>
|
||||||
Continue
|
Continue
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,11 +127,27 @@
|
|||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="label">Enter your player name or email (preferred)</label>
|
<label class="label">Enter your player name or email (preferred)</label>
|
||||||
<input v-model="elybyLogin" type="text" placeholder="Your player name or email here..." class="input" />
|
<input
|
||||||
|
v-model="elyByLogin"
|
||||||
|
type="text"
|
||||||
|
placeholder="Your player name or email here..."
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
<label class="label">Enter your password</label>
|
<label class="label">Enter your password</label>
|
||||||
<input v-model="elybyPassword" type="password" placeholder="Your password here..." class="input" />
|
<input
|
||||||
|
v-model="elyByPassword"
|
||||||
|
type="password"
|
||||||
|
placeholder="Your password here..."
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button icon-only color="primary" class="continue-button" @click="addElybyProfile()">
|
<Button
|
||||||
|
:disabled="elyByLoginDisabled"
|
||||||
|
icon-only
|
||||||
|
color="primary"
|
||||||
|
class="continue-button"
|
||||||
|
@click="addElyByProfile()"
|
||||||
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +156,12 @@
|
|||||||
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
<ModalWrapper ref="addOfflineModal" class="modal" header="Add new offline account">
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="label">Enter your player name</label>
|
<label class="label">Enter your player name</label>
|
||||||
<input v-model="offlinePlayerName" type="text" placeholder="Your player name here..." class="input" />
|
<input
|
||||||
|
v-model="offlinePlayerName"
|
||||||
|
type="text"
|
||||||
|
placeholder="Your player name here..."
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button icon-only color="primary" class="continue-button" @click="addOfflineProfile()">
|
<Button icon-only color="primary" class="continue-button" @click="addOfflineProfile()">
|
||||||
Login
|
Login
|
||||||
@@ -108,22 +170,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper
|
<ModalWrapper
|
||||||
ref="authenticationElybyErrorModal"
|
ref="authenticationElyByErrorModal"
|
||||||
class="modal"
|
class="modal"
|
||||||
header="Error while proceeding authentication event with Ely.by">
|
header="Error while proceeding authentication event with Ely.by"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="text-base font-medium text-red-700">
|
<label class="text-base font-medium text-red-700">
|
||||||
An error occurred while logging in.
|
An error occurred while logging in.
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
<Button color="primary" class="retry-button" @click="retryAddElyByProfile">
|
||||||
Try again
|
Try again
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="inputElybyErrorModal" class="modal" header="Error while proceeding input event with Ely.by">
|
<ModalWrapper
|
||||||
|
ref="inputElyByErrorModal"
|
||||||
|
class="modal"
|
||||||
|
header="Error while proceeding input event with Ely.by"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="text-base font-medium text-red-700">
|
<label class="text-base font-medium text-red-700">
|
||||||
An error occurred while adding the Ely.by account. Please follow the instructions below.
|
An error occurred while adding the Ely.by account. Please follow the instructions below.
|
||||||
@@ -135,13 +202,17 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
<Button color="primary" class="retry-button" @click="retryAddElybyProfile">
|
<Button color="primary" class="retry-button" @click="retryAddElyByProfile">
|
||||||
Try again
|
Try again
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="inputErrorModal" class="modal" header="Error while proceeding input event with offline account">
|
<ModalWrapper
|
||||||
|
ref="inputOfflineErrorModal"
|
||||||
|
class="modal"
|
||||||
|
header="Error while proceeding input event with offline account"
|
||||||
|
>
|
||||||
<div class="flex flex-col gap-4 px-6 py-5">
|
<div class="flex flex-col gap-4 px-6 py-5">
|
||||||
<label class="text-base font-medium text-red-700">
|
<label class="text-base font-medium text-red-700">
|
||||||
An error occurred while adding the offline account. Please follow the instructions below.
|
An error occurred while adding the offline account. Please follow the instructions below.
|
||||||
@@ -150,9 +221,10 @@
|
|||||||
<ul class="list-disc list-inside text-sm space-y-1">
|
<ul class="list-disc list-inside text-sm space-y-1">
|
||||||
<li>Check that you have entered the correct player name.</li>
|
<li>Check that you have entered the correct player name.</li>
|
||||||
<li>
|
<li>
|
||||||
Player name must be at least {{ minOfflinePlayerNameLength }} characters long and no more than
|
Player name must be at least {{ minOfflinePlayerNameLength }} characters long and no more
|
||||||
{{ maxOfflinePlayerNameLength }} characters.
|
than {{ maxOfflinePlayerNameLength }} characters.
|
||||||
</li>
|
</li>
|
||||||
|
<li>Make sure your name meets the format requirement `{{ nameExp }}`</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="mt-6 ml-auto">
|
<div class="mt-6 ml-auto">
|
||||||
@@ -162,7 +234,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="exceptionErrorModal" class="modal" header="Unexpected error occurred">
|
<ModalWrapper ref="unexpectedErrorModal" class="modal" header="Unexpected error occurred">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<label class="label">An unexpected error has occurred. Please try again later.</label>
|
<label class="label">An unexpected error has occurred. Please try again later.</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,36 +242,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
DropdownIcon,
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
TrashIcon,
|
|
||||||
PirateIcon as Offline,
|
|
||||||
MicrosoftIcon as License,
|
|
||||||
ElyByIcon as Elyby,
|
|
||||||
MicrosoftIcon,
|
|
||||||
PirateIcon,
|
|
||||||
ElyByIcon,
|
|
||||||
SpinnerIcon
|
|
||||||
} from '@modrinth/assets'
|
|
||||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
|
||||||
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
|
|
||||||
import {
|
import {
|
||||||
elyby_auth_authenticate,
|
elyby_auth_authenticate,
|
||||||
elyby_login,
|
elyby_login,
|
||||||
|
get_default_user,
|
||||||
|
login as login_flow,
|
||||||
offline_login,
|
offline_login,
|
||||||
users,
|
|
||||||
remove_user,
|
remove_user,
|
||||||
set_default_user,
|
set_default_user,
|
||||||
login as login_flow,
|
users,
|
||||||
get_default_user,
|
|
||||||
} from '@/helpers/auth'
|
} from '@/helpers/auth'
|
||||||
import { handleError } from '@/store/state.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import { process_listener } from '@/helpers/events'
|
import { process_listener } from '@/helpers/events'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import { get_available_skins } from '@/helpers/skins'
|
|
||||||
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
|
||||||
|
import { get_available_skins } from '@/helpers/skins'
|
||||||
|
import { handleSevereError } from '@/store/error.js'
|
||||||
|
import {
|
||||||
|
DropdownIcon,
|
||||||
|
ElyByIcon,
|
||||||
|
MicrosoftIcon,
|
||||||
|
OfflineIcon,
|
||||||
|
SpinnerIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
|
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
|
||||||
|
import { computed, onBeforeUnmount, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
mode: {
|
mode: {
|
||||||
@@ -213,82 +283,88 @@ const emit = defineEmits(['change'])
|
|||||||
|
|
||||||
const accounts = ref({})
|
const accounts = ref({})
|
||||||
const microsoftLoginDisabled = ref(false)
|
const microsoftLoginDisabled = ref(false)
|
||||||
const elybyLoginDisabled = ref(false)
|
const elyByLoginDisabled = ref(false)
|
||||||
const defaultUser = ref()
|
const defaultUser = ref()
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
const clientToken = "astralrinth"
|
const clientToken = 'astralrinth'
|
||||||
const addOfflineModal = ref(null)
|
const addOfflineModal = ref(null)
|
||||||
const addElybyModal = ref(null)
|
const addElyByModal = ref(null)
|
||||||
const requestElybyTwoFactorCodeModal = ref(null)
|
const requestElyByTwoFactorCodeModal = ref(null)
|
||||||
const authenticationElybyErrorModal = ref(null)
|
const authenticationElyByErrorModal = ref(null)
|
||||||
const inputElybyErrorModal = ref(null)
|
const inputElyByErrorModal = ref(null)
|
||||||
const inputErrorModal = ref(null)
|
const inputOfflineErrorModal = ref(null)
|
||||||
const exceptionErrorModal = ref(null)
|
const unexpectedErrorModal = ref(null)
|
||||||
const offlinePlayerName = ref('')
|
const offlinePlayerName = ref('')
|
||||||
const elybyLogin = ref('')
|
const elyByLogin = ref('')
|
||||||
const elybyPassword = ref('')
|
const elyByPassword = ref('')
|
||||||
const elybyTwoFactorCode = ref('')
|
const elyByTwoFactorCode = ref('')
|
||||||
const minOfflinePlayerNameLength = 2
|
const minOfflinePlayerNameLength = 3
|
||||||
const maxOfflinePlayerNameLength = 20
|
const maxOfflinePlayerNameLength = 20
|
||||||
|
const nameExp = 'a-zA-Z0-9_'
|
||||||
|
const nameRegex = new RegExp(`^[${nameExp}]+$`)
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function getAccountType(account) {
|
function getAccountType(account) {
|
||||||
switch (account.account_type) {
|
switch (account.account_type) {
|
||||||
case 'microsoft':
|
case 'microsoft':
|
||||||
return License
|
return MicrosoftIcon
|
||||||
case 'pirate':
|
case 'pirate':
|
||||||
return Offline
|
return OfflineIcon
|
||||||
case 'elyby':
|
case 'elyby':
|
||||||
return Elyby
|
return ElyByIcon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function showOfflineLoginModal() {
|
function showOfflineLoginModal() {
|
||||||
addOfflineModal.value?.show()
|
addOfflineModal.value?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function showElybyLoginModal() {
|
function showElyByLoginModal() {
|
||||||
addElybyModal.value?.show()
|
addElyByModal.value?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function retryAddOfflineProfile() {
|
function retryAddOfflineProfile() {
|
||||||
inputErrorModal.value?.hide()
|
inputOfflineErrorModal.value?.hide()
|
||||||
clearOfflineFields()
|
clearOfflineFields()
|
||||||
showOfflineLoginModal()
|
showOfflineLoginModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function retryAddElybyProfile() {
|
function retryAddElyByProfile() {
|
||||||
authenticationElybyErrorModal.value?.hide()
|
authenticationElyByErrorModal.value?.hide()
|
||||||
inputElybyErrorModal.value?.hide()
|
inputElyByErrorModal.value?.hide()
|
||||||
clearElybyFields()
|
elyByLoginDisabled.value = false
|
||||||
showElybyLoginModal()
|
clearElyByFields()
|
||||||
|
showElyByLoginModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function clearElybyFields() {
|
function clearElyByFields() {
|
||||||
elybyLogin.value = ''
|
elyByLogin.value = ''
|
||||||
elybyPassword.value = ''
|
elyByPassword.value = ''
|
||||||
elybyTwoFactorCode.value = ''
|
elyByTwoFactorCode.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function clearOfflineFields() {
|
function clearOfflineFields() {
|
||||||
offlinePlayerName.value = ''
|
offlinePlayerName.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
async function addOfflineProfile() {
|
async function addOfflineProfile() {
|
||||||
const name = offlinePlayerName.value.trim()
|
const name = offlinePlayerName.value.trim()
|
||||||
const isValidName = name.length >= minOfflinePlayerNameLength && name.length <= maxOfflinePlayerNameLength
|
const isValidName =
|
||||||
|
nameRegex.test(name) &&
|
||||||
|
name.length >= minOfflinePlayerNameLength &&
|
||||||
|
name.length <= maxOfflinePlayerNameLength
|
||||||
|
|
||||||
if (!isValidName) {
|
if (!isValidName) {
|
||||||
addOfflineModal.value?.hide()
|
addOfflineModal.value?.hide()
|
||||||
inputErrorModal.value?.show()
|
inputOfflineErrorModal.value?.show()
|
||||||
clearOfflineFields()
|
clearOfflineFields()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -302,39 +378,36 @@ async function addOfflineProfile() {
|
|||||||
await setAccount(result)
|
await setAccount(result)
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
} else {
|
} else {
|
||||||
exceptionErrorModal.value?.show()
|
unexpectedErrorModal.value?.show()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error)
|
handleError(error)
|
||||||
exceptionErrorModal.value?.show()
|
unexpectedErrorModal.value?.show()
|
||||||
} finally {
|
} finally {
|
||||||
clearOfflineFields()
|
clearOfflineFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
async function addElybyProfile() {
|
async function addElyByProfile() {
|
||||||
if (!elybyLogin.value || !elybyPassword.value) {
|
elyByLoginDisabled.value = true
|
||||||
addElybyModal.value?.hide()
|
if (!elyByLogin.value || !elyByPassword.value) {
|
||||||
inputElybyErrorModal.value?.show()
|
addElyByModal.value?.hide()
|
||||||
clearElybyFields()
|
inputElyByErrorModal.value?.show()
|
||||||
|
clearElyByFields()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
elybyLoginDisabled.value = true
|
|
||||||
|
|
||||||
const login = elybyLogin.value.trim()
|
// Parse ely.by credential fields
|
||||||
let password = elybyPassword.value.trim()
|
const login = elyByLogin.value.trim()
|
||||||
const twoFactorCode = elybyTwoFactorCode.value.trim()
|
let password = elyByPassword.value.trim()
|
||||||
|
const twoFactorCode = elyByTwoFactorCode.value.trim()
|
||||||
if (password && twoFactorCode) {
|
if (password && twoFactorCode) {
|
||||||
password = `${password}:${twoFactorCode}`
|
password = `${password}:${twoFactorCode}`
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw_result = await elyby_auth_authenticate(
|
const raw_result = await elyby_auth_authenticate(login, password, clientToken)
|
||||||
login,
|
|
||||||
password,
|
|
||||||
clientToken
|
|
||||||
)
|
|
||||||
|
|
||||||
const json_data = JSON.parse(raw_result)
|
const json_data = JSON.parse(raw_result)
|
||||||
|
|
||||||
@@ -346,13 +419,13 @@ async function addElybyProfile() {
|
|||||||
json_data.error === 'ForbiddenOperationException' &&
|
json_data.error === 'ForbiddenOperationException' &&
|
||||||
json_data.errorMessage?.includes('two factor')
|
json_data.errorMessage?.includes('two factor')
|
||||||
) {
|
) {
|
||||||
requestElybyTwoFactorCodeModal.value?.show()
|
requestElyByTwoFactorCodeModal.value?.show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addElybyModal.value?.hide()
|
addElyByModal.value?.hide()
|
||||||
requestElybyTwoFactorCodeModal.value?.hide()
|
requestElyByTwoFactorCodeModal.value?.hide()
|
||||||
authenticationElybyErrorModal.value?.show()
|
authenticationElyByErrorModal.value?.show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,22 +435,22 @@ async function addElybyProfile() {
|
|||||||
|
|
||||||
const result = await elyby_login(selectedProfileId, selectedProfileName, accessToken)
|
const result = await elyby_login(selectedProfileId, selectedProfileName, accessToken)
|
||||||
|
|
||||||
addElybyModal.value?.hide()
|
addElyByModal.value?.hide()
|
||||||
requestElybyTwoFactorCodeModal.value?.hide()
|
requestElyByTwoFactorCodeModal.value?.hide()
|
||||||
|
|
||||||
clearElybyFields()
|
clearElyByFields()
|
||||||
|
|
||||||
await setAccount(result)
|
await setAccount(result)
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err)
|
handleError(err)
|
||||||
exceptionErrorModal.value?.show()
|
unexpectedErrorModal.value?.show()
|
||||||
} finally {
|
} finally {
|
||||||
elybyLoginDisabled.value = false
|
elyByLoginDisabled.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// [AR] • Feature
|
// This code is modified by AstralRinth
|
||||||
function convertRawStringToUUIDv4(rawId) {
|
function convertRawStringToUUIDv4(rawId) {
|
||||||
if (rawId.length !== 32) {
|
if (rawId.length !== 32) {
|
||||||
console.warn('Invalid UUID string:', rawId)
|
console.warn('Invalid UUID string:', rawId)
|
||||||
@@ -417,7 +490,7 @@ function setLoginDisabled(value) {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
refreshValues,
|
refreshValues,
|
||||||
setLoginDisabled,
|
setLoginDisabled,
|
||||||
loginDisabled: microsoftLoginDisabled,
|
microsoftLoginDisabled,
|
||||||
})
|
})
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
|
|
||||||
@@ -543,7 +616,6 @@ onUnmounted(() => {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.vector-icon {
|
.vector-icon {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
@@ -571,12 +643,12 @@ onUnmounted(() => {
|
|||||||
z-index: 11;
|
z-index: 11;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
width: max-content;
|
width: max-content;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
max-height: 98vh;
|
max-height: calc(100vh - 300px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
@@ -673,7 +745,7 @@ onUnmounted(() => {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
&.expanded {
|
&.expanded {
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DropdownIcon, PlusIcon, FolderOpenIcon } from '@modrinth/assets'
|
import { DropdownIcon, FolderOpenIcon, PlusIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, OverflowMenu } from '@modrinth/ui'
|
import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth/ui'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
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'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { add_project_from_path } from '@/helpers/profile.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -42,11 +42,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ChevronRightIcon, ChevronLeftIcon } from '@modrinth/assets'
|
import { ChevronLeftIcon, ChevronRightIcon } from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button } from '@modrinth/ui'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div v-show="shown" ref="contextMenu" class="context-menu" :style="{
|
<div
|
||||||
|
v-show="shown"
|
||||||
|
ref="contextMenu"
|
||||||
|
class="context-menu"
|
||||||
|
:style="{
|
||||||
left: left,
|
left: left,
|
||||||
top: top,
|
top: top,
|
||||||
}">
|
}"
|
||||||
|
>
|
||||||
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
|
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
|
||||||
<hr v-if="option.type === 'divider'" class="divider" />
|
<hr v-if="option.type === 'divider'" class="divider" />
|
||||||
<div v-else-if="!(isLinkedData(item) && option.name === `add_content`)" class="item clickable"
|
<div
|
||||||
:class="[option.color ?? 'base']">
|
v-else-if="!(isLinkedData(item) && option.name === `add_content`)"
|
||||||
|
class="item clickable"
|
||||||
|
:class="[option.color ?? 'base']"
|
||||||
|
>
|
||||||
<slot :name="option.name" />
|
<slot :name="option.name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,7 +24,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
||||||
|
|
||||||
@@ -32,22 +40,27 @@ defineExpose({
|
|||||||
item.value = passedItem
|
item.value = passedItem
|
||||||
options.value = passedOptions
|
options.value = passedOptions
|
||||||
|
|
||||||
const menuWidth = contextMenu.value.clientWidth
|
// show to get dimensions
|
||||||
const menuHeight = contextMenu.value.clientHeight
|
|
||||||
|
|
||||||
if (menuWidth + event.pageX >= window.innerWidth) {
|
|
||||||
left.value = event.pageX - menuWidth + 2 + 'px'
|
|
||||||
} else {
|
|
||||||
left.value = event.pageX - 2 + 'px'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuHeight + event.pageY >= window.innerHeight) {
|
|
||||||
top.value = event.pageY - menuHeight + 2 + 'px'
|
|
||||||
} else {
|
|
||||||
top.value = event.pageY - 2 + 'px'
|
|
||||||
}
|
|
||||||
|
|
||||||
shown.value = true
|
shown.value = true
|
||||||
|
|
||||||
|
// then, adjust position if overflowing
|
||||||
|
nextTick(() => {
|
||||||
|
const menuWidth = contextMenu.value?.clientWidth || 200
|
||||||
|
const menuHeight = contextMenu.value?.clientHeight || 100
|
||||||
|
const minFromEdge = 10
|
||||||
|
|
||||||
|
if (event.pageX + menuWidth + minFromEdge >= window.innerWidth) {
|
||||||
|
left.value = Math.max(minFromEdge, event.pageX - menuWidth - minFromEdge) + 'px'
|
||||||
|
} else {
|
||||||
|
left.value = event.pageX + minFromEdge + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.pageY + menuHeight + minFromEdge >= window.innerHeight) {
|
||||||
|
top.value = Math.max(minFromEdge, event.pageY - menuHeight - minFromEdge) + 'px'
|
||||||
|
} else {
|
||||||
|
top.value = event.pageY + minFromEdge + 'px'
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -106,7 +119,7 @@ onBeforeUnmount(() => {
|
|||||||
background-color: var(--color-raised-bg);
|
background-color: var(--color-raised-bg);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
box-shadow: var(--shadow-floating);
|
box-shadow: var(--shadow-floating);
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000000;
|
z-index: 1000000;
|
||||||
@@ -150,7 +163,7 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
margin: var(--gap-sm);
|
margin: var(--gap-sm);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,9 @@
|
|||||||
<p class="input-label">Profile Code</p>
|
<p class="input-label">Profile Code</p>
|
||||||
<div class="iconified-input">
|
<div class="iconified-input">
|
||||||
<SearchIcon aria-hidden="true" class="text-lg" />
|
<SearchIcon aria-hidden="true" class="text-lg" />
|
||||||
<input
|
<input ref="codeInput" v-model="profileCode" autocomplete="off" class="h-12 card-shadow"
|
||||||
ref="codeInput"
|
spellcheck="false" type="text" placeholder="Enter CurseForge profile code" maxlength="20"
|
||||||
v-model="profileCode"
|
@keyup.enter="importProfile" />
|
||||||
autocomplete="off"
|
|
||||||
class="h-12 card-shadow"
|
|
||||||
spellcheck="false"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter CurseForge profile code"
|
|
||||||
maxlength="20"
|
|
||||||
@keyup.enter="importProfile"
|
|
||||||
/>
|
|
||||||
<Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')">
|
<Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -37,10 +29,7 @@
|
|||||||
<span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span>
|
<span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-bar-container">
|
<div class="progress-bar-container">
|
||||||
<div
|
<div class="progress-bar" :style="{ width: `${importProgress.percentage}%` }"></div>
|
||||||
class="progress-bar"
|
|
||||||
:style="{ width: `${importProgress.percentage}%` }"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,21 +38,12 @@
|
|||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button v-if="!metadata" @click="fetchMetadata" :disabled="!profileCode.trim() || fetching"
|
||||||
v-if="!metadata"
|
color="secondary">
|
||||||
@click="fetchMetadata"
|
|
||||||
:disabled="!profileCode.trim() || fetching"
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
<SearchIcon v-if="!fetching" />
|
<SearchIcon v-if="!fetching" />
|
||||||
{{ fetching ? 'Checking...' : 'Check Profile' }}
|
{{ fetching ? 'Checking...' : 'Check Profile' }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button v-if="metadata" @click="importProfile" :disabled="importing" color="primary">
|
||||||
v-if="metadata"
|
|
||||||
@click="importProfile"
|
|
||||||
:disabled="importing"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<DownloadIcon v-if="!importing" />
|
<DownloadIcon v-if="!importing" />
|
||||||
{{ importing ? 'Importing...' : 'Import Profile' }}
|
{{ importing ? 'Importing...' : 'Import Profile' }}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -86,7 +66,6 @@ import {
|
|||||||
fetch_curseforge_profile_metadata,
|
fetch_curseforge_profile_metadata,
|
||||||
import_curseforge_profile
|
import_curseforge_profile
|
||||||
} from '@/helpers/import.js'
|
} from '@/helpers/import.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { loading_listener } from '@/helpers/events.js'
|
import { loading_listener } from '@/helpers/events.js'
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
CopyIcon,
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
XIcon,
|
|
||||||
HammerIcon,
|
HammerIcon,
|
||||||
LogInIcon,
|
LogInIcon,
|
||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
CopyIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { ChatIcon } from '@/assets/icons'
|
import { ChatIcon } from '@/assets/icons'
|
||||||
import { ButtonStyled, Collapsible } from '@modrinth/ui'
|
|
||||||
import { ref, computed } 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.ts'
|
|
||||||
import { install } from '@/helpers/profile.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||||
|
import { install } from '@/helpers/profile.js'
|
||||||
|
import { cancel_directory_change } from '@/helpers/settings.ts'
|
||||||
|
import { handleSevereError } from '@/store/error.js'
|
||||||
|
|
||||||
|
// This code is modified by AstralRinth
|
||||||
import { applyMigrationFix } from '@/helpers/utils.js'
|
import { applyMigrationFix } from '@/helpers/utils.js'
|
||||||
import { restartApp } from '@/helpers/utils.js'
|
import { restartApp } from '@/helpers/utils.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const errorModal = ref()
|
const errorModal = ref()
|
||||||
const error = ref()
|
const error = ref()
|
||||||
const closable = ref(true)
|
const closable = ref(true)
|
||||||
const errorCollapsed = ref(false)
|
const errorCollapsed = ref(false)
|
||||||
const language = ref('en')
|
|
||||||
const migrationFixSuccess = ref(null) // null | true | false
|
const migrationFixSuccess = ref(null) // null | true | false
|
||||||
const migrationFixCallbackModel = ref()
|
const migrationFixCallbackModel = ref()
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ defineExpose({
|
|||||||
supportLink.value = 'https://support.modrinth.com'
|
supportLink.value = 'https://support.modrinth.com'
|
||||||
metadata.value.profilePath = context.profilePath
|
metadata.value.profilePath = context.profilePath
|
||||||
} else if (source === 'state_init') {
|
} else if (source === 'state_init') {
|
||||||
title.value = 'Error initializing Modrinth App'
|
title.value = 'Error initializing AstralRinth App'
|
||||||
errorType.value = 'state_init'
|
errorType.value = 'state_init'
|
||||||
supportLink.value = 'https://support.modrinth.com'
|
supportLink.value = 'https://support.modrinth.com'
|
||||||
} else {
|
} else {
|
||||||
@@ -154,10 +157,6 @@ async function copyToClipboard(text) {
|
|||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLanguage() {
|
|
||||||
language.value = language.value === 'en' ? 'ru' : 'en'
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onApplyMigrationFix(eol) {
|
async function onApplyMigrationFix(eol) {
|
||||||
console.log(`[AR] • Attempting to apply migration ${eol.toUpperCase()} fix`)
|
console.log(`[AR] • Attempting to apply migration ${eol.toUpperCase()} fix`)
|
||||||
try {
|
try {
|
||||||
@@ -187,7 +186,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
<template v-if="metadata.network">
|
<template v-if="metadata.network">
|
||||||
<h3>Network issues</h3>
|
<h3>Network issues</h3>
|
||||||
<p>
|
<p>
|
||||||
It looks like there were issues with the Modrinth App connecting to Microsoft's
|
It looks like there were issues with the AstralRinth App connecting to Microsoft's
|
||||||
servers. This is often the result of a poor connection, so we recommend trying again
|
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
|
to see if it works. If issues continue to persist, follow the steps in
|
||||||
<a
|
<a
|
||||||
@@ -201,7 +200,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
<template v-else-if="metadata.hostsFile">
|
<template v-else-if="metadata.hostsFile">
|
||||||
<h3>Network issues</h3>
|
<h3>Network issues</h3>
|
||||||
<p>
|
<p>
|
||||||
The Modrinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
The AstralRinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
||||||
remote server rejected the connection. This may indicate that these services are
|
remote server rejected the connection. This may indicate that these services are
|
||||||
blocked by the hosts file. Please visit
|
blocked by the hosts file. Please visit
|
||||||
<a
|
<a
|
||||||
@@ -240,7 +239,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
<template v-if="metadata.readOnly">
|
<template v-if="metadata.readOnly">
|
||||||
<h3>Change directory permissions</h3>
|
<h3>Change directory permissions</h3>
|
||||||
<p>
|
<p>
|
||||||
It looks like the Modrinth App is unable to write to the directory you selected.
|
It looks like the AstralRinth App is unable to write to the directory you selected.
|
||||||
Please adjust the permissions of the directory and try again or cancel the directory
|
Please adjust the permissions of the directory and try again or cancel the directory
|
||||||
change.
|
change.
|
||||||
</p>
|
</p>
|
||||||
@@ -254,7 +253,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<p>
|
<p>
|
||||||
The Modrinth App is unable to migrate to the new directory you selected. Please
|
The AstralRinth App is unable to migrate to the new directory you selected. Please
|
||||||
contact support for help or cancel the directory change.
|
contact support for help or cancel the directory change.
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -284,7 +283,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
</div>
|
</div>
|
||||||
<template v-else-if="errorType === 'state_init'">
|
<template v-else-if="errorType === 'state_init'">
|
||||||
<p>
|
<p>
|
||||||
Modrinth App failed to load correctly. This may be because of a corrupted file, or
|
AstralRinth App failed to load correctly. This may be because of a corrupted file, or
|
||||||
because the app is missing crucial files.
|
because the app is missing crucial files.
|
||||||
</p>
|
</p>
|
||||||
<p>You may be able to fix it through one of the following ways:</p>
|
<p>You may be able to fix it through one of the following ways:</p>
|
||||||
@@ -294,7 +293,7 @@ async function onApplyMigrationFix(eol) {
|
|||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="errorType === 'no_loader_version'">
|
<template v-else-if="errorType === 'no_loader_version'">
|
||||||
<p>The Modrinth App failed to find the loader version for this instance.</p>
|
<p>The AstralRinth 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>
|
<p>To resolve this, you need to repair the instance. Click the button below to do so.</p>
|
||||||
<div class="cta-button">
|
<div class="cta-button">
|
||||||
<button class="btn btn-primary" :disabled="loadingRepair" @click="repairInstance">
|
<button class="btn btn-primary" :disabled="loadingRepair" @click="repairInstance">
|
||||||
@@ -327,20 +326,10 @@ async function onApplyMigrationFix(eol) {
|
|||||||
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
|
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
|
||||||
<template v-else> <CopyIcon /> Copy debug info </template>
|
<template v-else> <CopyIcon /> Copy debug info </template>
|
||||||
</button>
|
</button>
|
||||||
<ButtonStyled class="neon-button neon">
|
|
||||||
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
|
|
||||||
Get AstralRinth support
|
|
||||||
</a>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled class="neon-button neon" >
|
|
||||||
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
|
|
||||||
Checkout latest releases
|
|
||||||
</a>
|
|
||||||
</ButtonStyled>
|
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="hasDebugInfo">
|
<template v-if="hasDebugInfo">
|
||||||
<div class="bg-button-bg rounded-xl mt-2 overflow-hidden">
|
<div class="bg-button-bg rounded-xl mt-2 overflow-clip">
|
||||||
<button
|
<button
|
||||||
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
|
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
|
||||||
@click="errorCollapsed = !errorCollapsed"
|
@click="errorCollapsed = !errorCollapsed"
|
||||||
@@ -352,76 +341,45 @@ async function onApplyMigrationFix(eol) {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<Collapsible :collapsed="errorCollapsed">
|
<Collapsible :collapsed="errorCollapsed">
|
||||||
<pre
|
<pre class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
|
||||||
class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
|
|
||||||
>{{ debugInfo }}</pre>
|
>{{ debugInfo }}</pre>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="errorType === 'state_init'">
|
<template v-if="errorType === 'state_init'">
|
||||||
<div class="notice">
|
<h2>⚠️ Migration Issue • Important Notice</h2>
|
||||||
<div class="flex justify-between items-center">
|
<p>We've detected a problem with our database migration system caused by inconsistent line endings between operating systems (Windows vs. macOS/Linux). This may affect app stability.</p>
|
||||||
<h3 v-if="language === 'en'" class="notice__title">⚠️ Migration Issue • Important Notice ⚠️</h3>
|
<p><strong>What’s happening?</strong> Our migration validator misreads modified migrations when line endings differ (CRLF ↔ LF), which can make the app unusable.</p>
|
||||||
<h3 v-if="language === 'ru'" class="notice__title">⚠️ Проблема миграции • Важное уведомление ⚠️</h3>
|
<p><strong>Why?</strong> Git’s automatic line-ending conversions and OS differences can cause these inconsistencies during builds.</p>
|
||||||
<ButtonStyled>
|
<p><strong>What’s next?</strong> We’re working on a permanent fix. In the meantime, you can apply one of the quick fixes below depending on your system.</p>
|
||||||
<button @click="toggleLanguage">
|
<h3>Do I need to apply a fix now?</h3>
|
||||||
{{ language === 'en' ? '📖 Русский' : '📖 English' }}
|
<div>
|
||||||
</button>
|
<p class="notice__text">
|
||||||
</ButtonStyled>
|
If you're encountering an error while applying migrations, such as "Error while applying migrations: migration XXXXXXXXXX was previously applied but has been modified", or a similar issue with migration, the following actions might help:
|
||||||
</div>
|
|
||||||
<p v-if="language === 'en'" class="notice__text">
|
|
||||||
We're experiencing an issue with our database migration system due to differences in how different operating systems handle line endings. This might cause problems with our app's functionality.
|
|
||||||
</p>
|
</p>
|
||||||
<p v-if="language === 'en'" class="notice__text">
|
<p>If none of the above steps help, you can try saving a copy of the file <code>app.db</code> to a safe location, such as <code>%appdata%\Roaming\AstralRinthApp</code>
|
||||||
<strong>What's happening?</strong> When we build our app, we use a system that checks the integrity of our database migrations. However, this system can get confused when it encounters different line endings (like CRLF vs LF) used by different operating systems. This can lead to errors and make our app unusable.
|
on Windows or <code>~/Library/Application Support/AstralRinthApp</code> on macOS, then deleting the original file and letting the app re-create the database file.
|
||||||
</p>
|
Note that this may cause data loss inside the app, so make sure to back up your launcher data before applying this fixes.
|
||||||
<p v-if="language === 'en'" class="notice__text">
|
|
||||||
<strong>Why is this happening?</strong> This issue is caused by a combination of factors, including different operating systems handling line endings differently, Git's line ending conversion settings, and our app's build process.
|
|
||||||
</p>
|
|
||||||
<p v-if="language === 'en'" class="notice__text">
|
|
||||||
<strong>What are we doing about it?</strong> We're working to resolve this issue and ensure that our app works smoothly for all users. In the meantime, we apologize for any inconvenience this might cause and appreciate your patience and understanding.
|
|
||||||
</p>
|
|
||||||
<p v-if="language === 'ru'" class="notice__text">
|
|
||||||
Мы сталкиваемся с проблемой в нашей системе миграции базы данных из-за различий в том, как разные операционные системы обрабатывают окончания строк. Это может вызвать проблемы с функциональностью нашего приложения.
|
|
||||||
</p>
|
|
||||||
<p v-if="language === 'ru'" class="notice__text">
|
|
||||||
<strong>Что происходит?</strong> Когда мы строим наше приложение, мы используем систему, которая проверяет целостность наших миграций базы данных. Однако эта система может сбиваться, когда сталкивается с различными окончаниями строк (например, CRLF против LF), используемыми разными операционными системами. Это может привести к ошибкам и сделать наше приложение неработоспособным.
|
|
||||||
</p>
|
|
||||||
<p v-if="language === 'ru'" class="notice__text">
|
|
||||||
<strong>Почему это происходит?</strong> Эта проблема вызвана сочетанием факторов, включая различную обработку окончаний строк разными операционными системами, настройки преобразования окончаний строк в Git и процесс сборки нашего приложения.
|
|
||||||
</p>
|
|
||||||
<p v-if="language === 'ru'" class="notice__text">
|
|
||||||
<strong>Что мы с этим делаем?</strong> Мы работаем над решением этой проблемы и обеспечением бесперебойной работы нашего приложения для всех пользователей. В это время мы извиняемся за возможные неудобства и благодарим вас за терпение и понимание.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-lg font-bold text-contrast">
|
|
||||||
<template v-if="language === 'en'">Possible fix in real time:</template>
|
|
||||||
<template v-if="language === 'ru'">Возможное исправление в реальном времени:</template>
|
|
||||||
</h2>
|
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<ol class="flex flex-col gap-3">
|
<ol class="flex flex-col gap-3">
|
||||||
<li>
|
<li>
|
||||||
<ButtonStyled class="neon-button neon">
|
<ButtonStyled class="neon-button neon">
|
||||||
<button
|
<button
|
||||||
:title="language === 'en'
|
title="Convert all line endings in migration files to LF (Unix-style: \\n)"
|
||||||
? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
|
|
||||||
: 'Преобразовать все окончания строк в файлах миграций в LF (Unix-стиль: \\n)'"
|
|
||||||
aria-label="LF"
|
|
||||||
@click="onApplyMigrationFix('lf')"
|
@click="onApplyMigrationFix('lf')"
|
||||||
>
|
>
|
||||||
{{ language === 'en' ? 'Apply LF Migration Fix' : 'Применить исправление миграции LF' }}
|
Apply fix for Unix like systems (Debian, Ubuntu, macOS and others)
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<ButtonStyled class="neon-button neon">
|
<ButtonStyled class="neon-button neon">
|
||||||
<button
|
<button
|
||||||
:title="language === 'en'
|
title="Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)"
|
||||||
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
|
|
||||||
: 'Преобразовать все окончания строк в файлах миграций в CRLF (Windows-стиль: \\r\\n)'"
|
|
||||||
aria-label="CRLF"
|
|
||||||
@click="onApplyMigrationFix('crlf')"
|
@click="onApplyMigrationFix('crlf')"
|
||||||
>
|
>
|
||||||
{{ language === 'en' ? 'Apply CRLF Migration Fix' : 'Применить исправление миграции CRLF' }}
|
Apply fix for Windows
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</li>
|
</li>
|
||||||
@@ -433,37 +391,25 @@ async function onApplyMigrationFix(eol) {
|
|||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper
|
<ModalWrapper
|
||||||
ref="migrationFixCallbackModel"
|
ref="migrationFixCallbackModel"
|
||||||
:header="language === 'en'
|
header="💡 Migration fix report"
|
||||||
? '💡 Migration fix report'
|
|
||||||
: '💡 Отчет об исправлении миграции'"
|
|
||||||
:closable="closable">
|
:closable="closable">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<h2 class="text-lg font-bold text-contrast space-y-2">
|
<h2 class="text-lg font-bold text-contrast space-y-2">
|
||||||
<template v-if="migrationFixSuccess === true">
|
<template v-if="migrationFixSuccess === true">
|
||||||
<p class="flex items-center gap-2 neon-text">
|
<p class="flex items-center gap-2 neon-text">
|
||||||
✅
|
✅ The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)
|
||||||
{{ language === 'en'
|
|
||||||
? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)'
|
|
||||||
: 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm neon-text">
|
<p class="mt-2 text-sm neon-text">
|
||||||
{{ language === 'en'
|
If the problem persists, please try the other fix.
|
||||||
? 'If the problem persists, please try the other fix.'
|
|
||||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="migrationFixSuccess === false">
|
<template v-else-if="migrationFixSuccess === false">
|
||||||
<p class="flex items-center gap-2 neon-text">
|
<p class="flex items-center gap-2 neon-text">
|
||||||
❌
|
❌ The migration fix failed or had no effect.
|
||||||
{{ language === 'en'
|
|
||||||
? 'The migration fix failed or had no effect.'
|
|
||||||
: 'Исправление миграции не было успешно применено или не имело эффекта.' }}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm neon-text">
|
<p class="mt-2 text-sm neon-text">
|
||||||
{{ language === 'en'
|
If the problem persists, please try the other fix.
|
||||||
? 'If the problem persists, please try the other fix.'
|
|
||||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -486,6 +432,13 @@ async function onApplyMigrationFix(eol) {
|
|||||||
@import '../../../../../packages/assets/styles/neon-button.scss';
|
@import '../../../../../packages/assets/styles/neon-button.scss';
|
||||||
@import '../../../../../packages/assets/styles/neon-text.scss';
|
@import '../../../../../packages/assets/styles/neon-text.scss';
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: linear-gradient(90deg, #005eff, #00cfff);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.cta-button {
|
.cta-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, PlusIcon } from '@modrinth/assets'
|
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Button, Checkbox } from '@modrinth/ui'
|
import { Button, Checkbox, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import {
|
import {
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
GameIcon,
|
GameIcon,
|
||||||
@@ -9,17 +7,20 @@ import {
|
|||||||
StopCircleIcon,
|
StopCircleIcon,
|
||||||
TimerIcon,
|
TimerIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled, useRelativeTime } from '@modrinth/ui'
|
import { Avatar, ButtonStyled, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { finish_install, kill, run } from '@/helpers/profile'
|
import dayjs from 'dayjs'
|
||||||
import { get_by_profile_path } from '@/helpers/process'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { process_listener } from '@/helpers/events'
|
import { process_listener } from '@/helpers/events'
|
||||||
import { handleError } from '@/store/state.js'
|
import { get_by_profile_path } from '@/helpers/process'
|
||||||
|
import { finish_install, kill, run } from '@/helpers/profile'
|
||||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
import { handleSevereError } from '@/store/error.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -93,7 +94,7 @@ const stop = async (e, context) => {
|
|||||||
const repair = async (e) => {
|
const repair = async (e) => {
|
||||||
e?.stopPropagation()
|
e?.stopPropagation()
|
||||||
|
|
||||||
await finish_install(props.instance)
|
await finish_install(props.instance).catch(handleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openFolder = async () => {
|
const openFolder = async () => {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<p class="input-label">Game version</p>
|
<p class="input-label">Game version</p>
|
||||||
<div class="versions">
|
<div class="flex gap-4 items-center">
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="game_version"
|
v-model="game_version"
|
||||||
class="selector"
|
class="selector"
|
||||||
@@ -45,19 +45,14 @@
|
|||||||
open-direction="top"
|
open-direction="top"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox v-model="showSnapshots" class="shrink-0" label="Show all versions" />
|
||||||
v-if="showAdvanced"
|
|
||||||
v-model="showSnapshots"
|
|
||||||
class="filter-checkbox"
|
|
||||||
label="Include snapshots"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showAdvanced && loader !== 'vanilla'" class="input-row">
|
<div v-if="loader !== 'vanilla'" class="input-row">
|
||||||
<p class="input-label">Loader version</p>
|
<p class="input-label">Loader version</p>
|
||||||
<Chips v-model="loader_version" :items="['stable', 'latest', 'other']" />
|
<Chips v-model="loader_version" :items="['stable', 'latest', 'other']" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showAdvanced && loader_version === 'other' && loader !== 'vanilla'">
|
<div v-if="loader_version === 'other' && loader !== 'vanilla'">
|
||||||
<div v-if="game_version" class="input-row">
|
<div v-if="game_version" class="input-row">
|
||||||
<p class="input-label">Select version</p>
|
<p class="input-label">Select version</p>
|
||||||
<multiselect
|
<multiselect
|
||||||
@@ -75,10 +70,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
<Button @click="toggle_advanced">
|
|
||||||
<CodeIcon />
|
|
||||||
{{ showAdvanced ? 'Hide advanced' : 'Show advanced' }}
|
|
||||||
</Button>
|
|
||||||
<Button @click="hide()">
|
<Button @click="hide()">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
Cancel
|
||||||
@@ -110,7 +101,7 @@
|
|||||||
placeholder="Path to launcher"
|
placeholder="Path to launcher"
|
||||||
@change="setPath"
|
@change="setPath"
|
||||||
/>
|
/>
|
||||||
<Button class="r-btn" @click="() => (selectedLauncherPath = '')">
|
<Button class="r-btn" @click="() => (selectedProfileType.path = '')">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,10 +197,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
|
|
||||||
import {
|
import {
|
||||||
CodeIcon,
|
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
FolderSearchIcon,
|
FolderSearchIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
@@ -218,24 +206,29 @@ import {
|
|||||||
UploadIcon,
|
UploadIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Button, Checkbox, Chips } from '@modrinth/ui'
|
import { Avatar, Button, Checkbox, Chips, injectNotificationManager } 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/plugin-dialog'
|
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
|
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
|
|
||||||
import {
|
import {
|
||||||
get_default_launcher_path,
|
get_default_launcher_path,
|
||||||
get_importable_instances,
|
get_importable_instances,
|
||||||
import_instance,
|
import_instance,
|
||||||
} from '@/helpers/import.js'
|
} from '@/helpers/import.js'
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
|
||||||
|
import { create } from '@/helpers/profile'
|
||||||
|
import { get_loaders } from '@/helpers/tags'
|
||||||
|
|
||||||
|
import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const profile_name = ref('')
|
const profile_name = ref('')
|
||||||
const game_version = ref('')
|
const game_version = ref('')
|
||||||
@@ -244,7 +237,6 @@ const loader_version = ref('stable')
|
|||||||
const specified_loader_version = ref('')
|
const specified_loader_version = ref('')
|
||||||
const icon = ref(null)
|
const icon = ref(null)
|
||||||
const display_icon = ref(null)
|
const display_icon = ref(null)
|
||||||
const showAdvanced = ref(false)
|
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
const showSnapshots = ref(false)
|
const showSnapshots = ref(false)
|
||||||
const creationType = ref('custom')
|
const creationType = ref('custom')
|
||||||
@@ -256,7 +248,6 @@ defineExpose({
|
|||||||
specified_loader_version.value = ''
|
specified_loader_version.value = ''
|
||||||
profile_name.value = ''
|
profile_name.value = ''
|
||||||
creating.value = false
|
creating.value = false
|
||||||
showAdvanced.value = false
|
|
||||||
showSnapshots.value = false
|
showSnapshots.value = false
|
||||||
loader.value = 'vanilla'
|
loader.value = 'vanilla'
|
||||||
loader_version.value = 'stable'
|
loader_version.value = 'stable'
|
||||||
@@ -367,7 +358,7 @@ const create_instance = async () => {
|
|||||||
creating.value = true
|
creating.value = true
|
||||||
const loader_version_value =
|
const loader_version_value =
|
||||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||||
const loaderVersion = loader.value === 'vanilla' ? null : loader_version_value ?? 'stable'
|
const loaderVersion = loader.value === 'vanilla' ? null : (loader_version_value ?? 'stable')
|
||||||
|
|
||||||
hide()
|
hide()
|
||||||
creating.value = false
|
creating.value = false
|
||||||
@@ -376,7 +367,7 @@ const create_instance = async () => {
|
|||||||
profile_name.value,
|
profile_name.value,
|
||||||
game_version.value,
|
game_version.value,
|
||||||
loader.value,
|
loader.value,
|
||||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
loader.value === 'vanilla' ? null : (loader_version_value ?? 'stable'),
|
||||||
icon.value,
|
icon.value,
|
||||||
).catch(handleError)
|
).catch(handleError)
|
||||||
|
|
||||||
@@ -431,10 +422,6 @@ const selectable_versions = computed(() => {
|
|||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggle_advanced = () => {
|
|
||||||
showAdvanced.value = !showAdvanced.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const openFile = async () => {
|
const openFile = async () => {
|
||||||
const newProject = await open({ multiple: false })
|
const newProject = await open({ multiple: false })
|
||||||
if (!newProject) return
|
if (!newProject) return
|
||||||
@@ -576,12 +563,6 @@ const next = async () => {
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.versions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(button.checkbox) {
|
:deep(button.checkbox) {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
|
||||||
import { formatCategory } from '@modrinth/utils'
|
|
||||||
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
|
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled } from '@modrinth/ui'
|
import { Avatar, ButtonStyled } from '@modrinth/ui'
|
||||||
|
import { formatCategory } from '@modrinth/utils'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
type Instance = {
|
type Instance = {
|
||||||
game_version: string
|
game_version: string
|
||||||
|
|||||||
@@ -35,13 +35,15 @@
|
|||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const chosenInstallOptions = ref([])
|
const chosenInstallOptions = ref([])
|
||||||
const detectJavaModal = ref(null)
|
const detectJavaModal = ref(null)
|
||||||
|
|||||||
@@ -53,20 +53,22 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
SearchIcon,
|
|
||||||
PlayIcon,
|
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
XIcon,
|
|
||||||
FolderSearchIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
FolderSearchIcon,
|
||||||
|
PlayIcon,
|
||||||
|
SearchIcon,
|
||||||
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button, injectNotificationManager } 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/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||||
import { handleError } from '@/store/state.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
version: {
|
version: {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { CheckIcon } from '@modrinth/assets'
|
import { CheckIcon } from '@modrinth/assets'
|
||||||
import { Button, Badge } from '@modrinth/ui'
|
import { Badge, Button } from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
|
||||||
import { releaseColor } from '@/helpers/utils'
|
|
||||||
import { SwapIcon } from '@/assets/icons/index.js'
|
import { SwapIcon } from '@/assets/icons/index.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||||
|
import { releaseColor } from '@/helpers/utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
versions: {
|
versions: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'router-link-active': isPrimary && isPrimary(route),
|
'router-link-active': isPrimary && isPrimary(route),
|
||||||
'subpage-active': isSubpage && isSubpage(route),
|
'subpage-active': isSubpage && isSubpage(route),
|
||||||
|
disabled: disabled,
|
||||||
}"
|
}"
|
||||||
class="w-12 h-12 text-primary rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
class="w-12 h-12 text-primary rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
||||||
>
|
>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
v-else
|
v-else
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
|
||||||
|
:disabled="disabled"
|
||||||
@click="to"
|
@click="to"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
@@ -29,12 +31,18 @@ const route = useRoute()
|
|||||||
|
|
||||||
type RouteFunction = (route: RouteLocationNormalizedLoaded) => boolean
|
type RouteFunction = (route: RouteLocationNormalizedLoaded) => boolean
|
||||||
|
|
||||||
defineProps<{
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
to: (() => void) | string
|
to: (() => void) | string
|
||||||
isPrimary?: RouteFunction
|
isPrimary?: RouteFunction
|
||||||
isSubpage?: RouteFunction
|
isSubpage?: RouteFunction
|
||||||
highlightOverride?: boolean
|
highlightOverride?: boolean
|
||||||
}>()
|
disabled?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
import { useRoute, RouterLink } from 'vue-router'
|
import { RouterLink, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-bar__fill" :style="{ width: `${progress}%` }"></div>
|
<div
|
||||||
|
class="progress-bar__fill"
|
||||||
|
:style="{
|
||||||
|
width: `${progress}%`,
|
||||||
|
'background-color': error ? 'var(--color-red)' : 'var(--color-brand)',
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -13,6 +19,10 @@ defineProps({
|
|||||||
return value >= 0 && value <= 100
|
return value >= 0 && value <= 100
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
error: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -27,7 +37,6 @@ defineProps({
|
|||||||
|
|
||||||
.progress-bar__fill {
|
.progress-bar__fill {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--color-brand);
|
|
||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Avatar, TagItem } from '@modrinth/ui'
|
|
||||||
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
||||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
import { Avatar, TagItem } from '@modrinth/ui'
|
||||||
import { computed } from 'vue'
|
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
@@ -21,14 +21,11 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const featuredCategory = computed(() => {
|
const featuredCategory = computed(() => {
|
||||||
if (props.project.categories.includes('optimization')) {
|
if (props.project.display_categories.includes('optimization')) {
|
||||||
return 'optimization'
|
return 'optimization'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.project.categories.length > 0) {
|
return props.project.display_categories[0] ?? props.project.categories[0]
|
||||||
return props.project.categories[0]
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const toColor = computed(() => {
|
const toColor = computed(() => {
|
||||||
@@ -66,7 +63,7 @@ const toTransparent = computed(() => {
|
|||||||
<div
|
<div
|
||||||
class="w-full aspect-[2/1] bg-cover bg-center bg-no-repeat"
|
class="w-full aspect-[2/1] bg-cover bg-center bg-no-repeat"
|
||||||
:style="{
|
:style="{
|
||||||
'background-color': project.featured_gallery ?? project.gallery[0] ? null : toColor,
|
'background-color': (project.featured_gallery ?? project.gallery[0]) ? null : toColor,
|
||||||
'background-image': `url(${
|
'background-image': `url(${
|
||||||
project.featured_gallery ??
|
project.featured_gallery ??
|
||||||
project.gallery[0] ??
|
project.gallery[0] ??
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { list } from '@/helpers/profile'
|
import { SpinnerIcon } from '@modrinth/assets'
|
||||||
import { handleError } from '@/store/notifications'
|
import { Avatar, injectNotificationManager } from '@modrinth/ui'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { onUnmounted, ref } from 'vue'
|
import { onUnmounted, ref } from 'vue'
|
||||||
import { profile_listener } from '@/helpers/events.js'
|
|
||||||
import NavButton from '@/components/ui/NavButton.vue'
|
import NavButton from '@/components/ui/NavButton.vue'
|
||||||
import { Avatar } from '@modrinth/ui'
|
import { profile_listener } from '@/helpers/events.js'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { list } from '@/helpers/profile'
|
||||||
import { SpinnerIcon } from '@modrinth/assets'
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const recentInstances = ref([])
|
const recentInstances = ref([])
|
||||||
const getInstances = async () => {
|
const getInstances = async () => {
|
||||||
@@ -67,7 +69,7 @@ onUnmounted(() => {
|
|||||||
<SpinnerIcon class="animate-spin w-4 h-4" />
|
<SpinnerIcon class="animate-spin w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
|
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-divider"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
@@ -20,12 +20,21 @@
|
|||||||
>
|
>
|
||||||
{{ selectedProcess.profile.name }}
|
{{ selectedProcess.profile.name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div v-if="currentProcesses.length > 1" class="arrow button-base" :class="{ rotate: showProfiles }"
|
<div
|
||||||
@click="toggleProfiles()">
|
v-if="currentProcesses.length > 1"
|
||||||
|
class="arrow button-base"
|
||||||
|
:class="{ rotate: showProfiles }"
|
||||||
|
@click="toggleProfiles()"
|
||||||
|
>
|
||||||
<DropdownIcon />
|
<DropdownIcon />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop(selectedProcess)">
|
<Button
|
||||||
|
v-tooltip="'Stop instance'"
|
||||||
|
icon-only
|
||||||
|
class="icon-button stop"
|
||||||
|
@click="stop(selectedProcess)"
|
||||||
|
>
|
||||||
<StopCircleIcon />
|
<StopCircleIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||||
@@ -45,20 +54,39 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<ProgressBar :progress="Math.floor((100 * loadingBar.current) / loadingBar.total)" />
|
<ProgressBar :progress="Math.floor((100 * loadingBar.current) / loadingBar.total)" />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}% {{ loadingBar.message }}
|
{{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}%
|
||||||
|
{{ loadingBar.message }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="download">
|
<transition name="download">
|
||||||
<Card v-if="showProfiles === true && currentProcesses.length > 0" ref="profiles" class="profile-card">
|
<Card
|
||||||
<Button v-for="process in currentProcesses" :key="process.uuid" class="profile-button"
|
v-if="showProfiles === true && currentProcesses.length > 0"
|
||||||
@click="selectProcess(process)">
|
ref="profiles"
|
||||||
|
class="profile-card"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
v-for="process in currentProcesses"
|
||||||
|
:key="process.uuid"
|
||||||
|
class="profile-button"
|
||||||
|
@click="selectProcess(process)"
|
||||||
|
>
|
||||||
<div class="text"><span class="circle running" /> {{ process.profile.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(process)">
|
<Button
|
||||||
|
v-tooltip="'Stop instance'"
|
||||||
|
icon-only
|
||||||
|
class="icon-button stop"
|
||||||
|
@click.stop="stop(process)"
|
||||||
|
>
|
||||||
<StopCircleIcon />
|
<StopCircleIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click.stop="goToTerminal(process.profile.path)">
|
<Button
|
||||||
|
v-tooltip="'View logs'"
|
||||||
|
icon-only
|
||||||
|
class="icon-button"
|
||||||
|
@click.stop="goToTerminal(process.profile.path)"
|
||||||
|
>
|
||||||
<TerminalSquareIcon />
|
<TerminalSquareIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -69,21 +97,23 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
DropdownIcon,
|
||||||
StopCircleIcon,
|
StopCircleIcon,
|
||||||
TerminalSquareIcon,
|
TerminalSquareIcon,
|
||||||
DropdownIcon,
|
|
||||||
UnplugIcon,
|
UnplugIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, ButtonStyled, Card } from '@modrinth/ui'
|
import { Button, ButtonStyled, Card, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
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 { useRouter } from 'vue-router'
|
||||||
import { progress_bars_list } from '@/helpers/state.js'
|
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { get_many } from '@/helpers/profile.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { loading_listener, process_listener } from '@/helpers/events'
|
||||||
|
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||||
|
import { get_many } from '@/helpers/profile.js'
|
||||||
|
import { progress_bars_list } from '@/helpers/state.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const card = ref(null)
|
const card = ref(null)
|
||||||
@@ -147,8 +177,8 @@ const currentLoadingBars = ref([])
|
|||||||
|
|
||||||
const refreshInfo = async () => {
|
const refreshInfo = async () => {
|
||||||
const currentLoadingBarCount = currentLoadingBars.value.length
|
const currentLoadingBarCount = currentLoadingBars.value.length
|
||||||
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError)).map(
|
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError))
|
||||||
(x) => {
|
.map((x) => {
|
||||||
if (x.bar_type.type === 'java_download') {
|
if (x.bar_type.type === 'java_download') {
|
||||||
x.title = 'Downloading Java ' + x.bar_type.version
|
x.title = 'Downloading Java ' + x.bar_type.version
|
||||||
}
|
}
|
||||||
@@ -160,8 +190,8 @@ const refreshInfo = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return x
|
return x
|
||||||
},
|
})
|
||||||
)
|
.filter((bar) => bar?.bar_type?.type !== 'launcher_update')
|
||||||
|
|
||||||
currentLoadingBars.value.sort((a, b) => {
|
currentLoadingBars.value.sort((a, b) => {
|
||||||
if (a.loading_bar_uuid < b.loading_bar_uuid) {
|
if (a.loading_bar_uuid < b.loading_bar_uuid) {
|
||||||
@@ -252,7 +282,6 @@ onBeforeUnmount(() => {
|
|||||||
transition: transform 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&.rotate {
|
&.rotate {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
@@ -264,7 +293,7 @@ onBeforeUnmount(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
padding: var(--gap-sm) var(--gap-lg);
|
padding: var(--gap-sm) var(--gap-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,10 +303,8 @@ onBeforeUnmount(() => {
|
|||||||
gap: var(--gap-xs);
|
gap: var(--gap-xs);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none; /* Safari */
|
||||||
/* Safari */
|
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||||
-ms-user-select: none;
|
|
||||||
/* IE 10 and IE 11 */
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.clickable:hover {
|
&.clickable:hover {
|
||||||
@@ -329,7 +356,7 @@ onBeforeUnmount(() => {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
@@ -427,7 +454,7 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
border: 1px solid var(--color-button-bg);
|
border: 1px solid var(--color-divider);
|
||||||
padding: var(--gap-md);
|
padding: var(--gap-md);
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
|
|||||||
@@ -117,16 +117,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { TagsIcon, DownloadIcon, HeartIcon, PlusIcon, CheckIcon } from '@modrinth/assets'
|
import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, Avatar } from '@modrinth/ui'
|
import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { ref, computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { install as installVersion } from '@/store/install.js'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { install as installVersion } from '@/store/install.js'
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -174,7 +177,7 @@ async function install() {
|
|||||||
(profile) => {
|
(profile) => {
|
||||||
router.push(`/instance/${profile}`)
|
router.push(`/instance/${profile}`)
|
||||||
},
|
},
|
||||||
)
|
).catch(handleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
const modpack = computed(() => props.project.project_type === 'modpack')
|
const modpack = computed(() => props.project.project_type === 'modpack')
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }">
|
<div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }">
|
||||||
<div v-if="os !== 'MacOS'" class="app-buttons">
|
<div v-if="os !== 'MacOS'" class="app-buttons">
|
||||||
<button class="btn icon-only transparent" icon-only @click="() => getCurrent().minimize()">
|
<button
|
||||||
|
class="btn icon-only transparent"
|
||||||
|
icon-only
|
||||||
|
@click="() => getCurrentWindow().minimize()"
|
||||||
|
>
|
||||||
<MinimizeIcon />
|
<MinimizeIcon />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn icon-only transparent" @click="() => getCurrent().toggleMaximize()">
|
<button class="btn icon-only transparent" @click="() => getCurrentWindow().toggleMaximize()">
|
||||||
<MaximizeIcon />
|
<MaximizeIcon />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn icon-only transparent" @click="handleClose">
|
<button class="btn icon-only transparent" @click="handleClose">
|
||||||
@@ -82,11 +86,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { MaximizeIcon, MinimizeIcon, XIcon } from '@modrinth/assets'
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
import { loading_listener } from '@/helpers/events.js'
|
import { loading_listener } from '@/helpers/events.js'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { XIcon, MaximizeIcon, MinimizeIcon } from '@modrinth/assets'
|
|
||||||
import { getOS } from '@/helpers/utils.js'
|
import { getOS } from '@/helpers/utils.js'
|
||||||
import { useLoading } from '@/store/loading.js'
|
import { useLoading } from '@/store/loading.js'
|
||||||
|
|
||||||
@@ -109,7 +114,7 @@ watch(loading, (newValue) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
hidden.value = true
|
hidden.value = true
|
||||||
loading.setEnabled(true)
|
loading.setEnabled(true)
|
||||||
}, 250)
|
}, 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -130,9 +135,6 @@ loading_listener(async (e) => {
|
|||||||
if (e.event.type === 'directory_move') {
|
if (e.event.type === 'directory_move') {
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||||
message.value = 'Updating app directory...'
|
message.value = 'Updating app directory...'
|
||||||
} else if (e.event.type === 'launcher_update') {
|
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
|
||||||
message.value = 'Updating Modrinth App...'
|
|
||||||
} else if (e.event.type === 'checking_for_updates') {
|
} else if (e.event.type === 'checking_for_updates') {
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||||
message.value = 'Checking for updates...'
|
message.value = 'Checking for updates...'
|
||||||
@@ -189,7 +191,8 @@ const handleClose = async () => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
background: linear-gradient(180deg, rgba(66, 131, 92, 0.275) 0%, rgba(17, 35, 43, 0.5) 97.29%),
|
background:
|
||||||
|
linear-gradient(180deg, rgba(66, 131, 92, 0.275) 0%, rgba(17, 35, 43, 0.5) 97.29%),
|
||||||
linear-gradient(0deg, rgba(22, 24, 28, 0.64), rgba(22, 24, 28, 0.64));
|
linear-gradient(0deg, rgba(22, 24, 28, 0.64), rgba(22, 24, 28, 0.64));
|
||||||
z-index: 9997;
|
z-index: 9997;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
|
||||||
import { get_categories } from '@/helpers/tags.js'
|
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { get_version, get_project } from '@/helpers/cache.js'
|
|
||||||
import { install as installVersion } from '@/store/install.js'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||||
|
import { get_project, get_version } from '@/helpers/cache.js'
|
||||||
|
import { get_categories } from '@/helpers/tags.js'
|
||||||
|
import { install as installVersion } from '@/store/install.js'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const project = ref(null)
|
const project = ref(null)
|
||||||
@@ -37,7 +39,14 @@ defineExpose({
|
|||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
confirmModal.value.hide()
|
confirmModal.value.hide()
|
||||||
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal')
|
await installVersion(
|
||||||
|
project.value.id,
|
||||||
|
version.value.id,
|
||||||
|
null,
|
||||||
|
'URLConfirmModal',
|
||||||
|
() => {},
|
||||||
|
() => {},
|
||||||
|
).catch(handleError)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
130
apps/app-frontend/src/components/ui/UpdateToast.vue
Normal file
130
apps/app-frontend/src/components/ui/UpdateToast.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { DownloadIcon, ExternalIcon, RefreshCwIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||||
|
import { ButtonStyled, commonMessages, defineMessages, ProgressBar, useVIntl } from '@modrinth/ui'
|
||||||
|
import { formatBytes } from '@modrinth/utils'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close' | 'restart' | 'download'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
version: string
|
||||||
|
size: number | null
|
||||||
|
metered: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const downloading = ref(false)
|
||||||
|
const { progress } = injectAppUpdateDownloadProgress()
|
||||||
|
|
||||||
|
function download() {
|
||||||
|
emit('download')
|
||||||
|
downloading.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: {
|
||||||
|
id: 'app.update-toast.title',
|
||||||
|
defaultMessage: 'Update available',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
id: 'app.update-toast.body',
|
||||||
|
defaultMessage:
|
||||||
|
'Modrinth App v{version} is ready to install! Reload to update now, or automatically when you close Modrinth App.',
|
||||||
|
},
|
||||||
|
reload: {
|
||||||
|
id: 'app.update-toast.reload',
|
||||||
|
defaultMessage: 'Reload',
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
id: 'app.update-toast.download',
|
||||||
|
defaultMessage: 'Download ({size})',
|
||||||
|
},
|
||||||
|
downloading: {
|
||||||
|
id: 'app.update-toast.downloading',
|
||||||
|
defaultMessage: 'Downloading...',
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
id: 'app.update-toast.changelog',
|
||||||
|
defaultMessage: 'Changelog',
|
||||||
|
},
|
||||||
|
meteredBody: {
|
||||||
|
id: 'app.update-toast.body.metered',
|
||||||
|
defaultMessage: `Modrinth App v{version} is available now! Since you're on a metered network, we didn't automatically download it.`,
|
||||||
|
},
|
||||||
|
downloadCompleteTitle: {
|
||||||
|
id: 'app.update-toast.title.download-complete',
|
||||||
|
defaultMessage: 'Download complete',
|
||||||
|
},
|
||||||
|
downloadedBody: {
|
||||||
|
id: 'app.update-toast.body.download-complete',
|
||||||
|
defaultMessage: `Modrinth App v{version} has finished downloading. Reload to update now, or automatically when you close Modrinth App.`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-[min-content] fixed card-shadow rounded-2xl top-[--top-bar-height] mt-6 right-6 p-4 z-10 bg-bg-raised border-divider border-solid border-[2px]"
|
||||||
|
:class="{
|
||||||
|
'download-complete': progress === 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-[25rem] gap-4">
|
||||||
|
<h2 class="whitespace-nowrap text-base text-contrast font-semibold m-0 grow">
|
||||||
|
{{
|
||||||
|
formatMessage(metered && progress === 1 ? messages.downloadCompleteTitle : messages.title)
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
|
<ButtonStyled size="small" circular>
|
||||||
|
<button v-tooltip="formatMessage(commonMessages.closeButton)" @click="emit('close')">
|
||||||
|
<XIcon />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm mt-2 mb-0">
|
||||||
|
{{
|
||||||
|
formatMessage(
|
||||||
|
metered
|
||||||
|
? progress === 1
|
||||||
|
? messages.downloadedBody
|
||||||
|
: messages.meteredBody
|
||||||
|
: messages.body,
|
||||||
|
{ version },
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
v-if="metered && progress < 1"
|
||||||
|
class="text-sm text-secondary mt-2 mb-0 flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<template v-if="progress > 0">
|
||||||
|
<ProgressBar :progress="progress" class="max-w-[unset]" />
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2 mt-4">
|
||||||
|
<ButtonStyled color="brand">
|
||||||
|
<button v-if="metered && progress < 1" :disabled="downloading" @click="download">
|
||||||
|
<SpinnerIcon v-if="downloading" class="animate-spin" />
|
||||||
|
<DownloadIcon v-else />
|
||||||
|
{{
|
||||||
|
formatMessage(downloading ? messages.downloading : messages.download, {
|
||||||
|
size: formatBytes(size ?? 0),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
<button v-else @click="emit('restart')">
|
||||||
|
<RefreshCwIcon /> {{ formatMessage(messages.reload) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled>
|
||||||
|
<a href="https://modrinth.com/news/changelog?filter=app">
|
||||||
|
{{ formatMessage(messages.changelog) }} <ExternalIcon />
|
||||||
|
</a>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,34 +1,42 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Avatar, ButtonStyled, OverflowMenu, useRelativeTime } from '@modrinth/ui'
|
import { MailIcon, SendIcon, UserIcon, UserPlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
UserPlusIcon,
|
Avatar,
|
||||||
MoreVerticalIcon,
|
ButtonStyled,
|
||||||
MailIcon,
|
commonMessages,
|
||||||
SettingsIcon,
|
defineMessages,
|
||||||
TrashIcon,
|
injectNotificationManager,
|
||||||
XIcon,
|
IntlFormatted,
|
||||||
} from '@modrinth/assets'
|
useRelativeTime,
|
||||||
import { ref, onUnmounted, watch, computed } from 'vue'
|
useVIntl,
|
||||||
import { friend_listener } from '@/helpers/events'
|
} from '@modrinth/ui'
|
||||||
import { friends, friend_statuses, add_friend, remove_friend } from '@/helpers/friends'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
import { get_user_many } from '@/helpers/cache'
|
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
|
||||||
import type { Dayjs } from 'dayjs'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
|
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { friend_listener } from '@/helpers/events'
|
||||||
|
import {
|
||||||
|
add_friend,
|
||||||
|
friends,
|
||||||
|
type FriendWithUserData,
|
||||||
|
remove_friend,
|
||||||
|
transformFriends,
|
||||||
|
} from '@/helpers/friends.ts'
|
||||||
|
import type { ModrinthCredentials } from '@/helpers/mr_auth'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
credentials: unknown | null
|
credentials: ModrinthCredentials | null
|
||||||
signIn: () => void
|
signIn: () => void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const userCredentials = computed(() => props.credentials)
|
const userCredentials = computed(() => props.credentials)
|
||||||
|
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const manageFriendsModal = ref()
|
|
||||||
const friendInvitesModal = ref()
|
const friendInvitesModal = ref()
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
@@ -40,46 +48,25 @@ async function addFriendFromModal() {
|
|||||||
await loadFriends()
|
await loadFriends()
|
||||||
}
|
}
|
||||||
|
|
||||||
const friendOptions = ref()
|
async function addFriend(friend: FriendWithUserData) {
|
||||||
async function handleFriendOptions(args) {
|
const id = friend.id === userCredentials.value?.user_id ? friend.friend_id : friend.id
|
||||||
switch (args.option) {
|
if (id) {
|
||||||
case 'remove-friend':
|
await add_friend(id).catch(handleError)
|
||||||
await removeFriend(args.item)
|
await loadFriends()
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addFriend(friend: Friend) {
|
async function removeFriend(friend: FriendWithUserData) {
|
||||||
await add_friend(
|
const id = friend.id === userCredentials.value?.user_id ? friend.friend_id : friend.id
|
||||||
friend.id === userCredentials.value.user_id ? friend.friend_id : friend.id,
|
if (id) {
|
||||||
).catch(handleError)
|
await remove_friend(id).catch(handleError)
|
||||||
await loadFriends()
|
await loadFriends()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeFriend(friend: Friend) {
|
const userFriends = ref<FriendWithUserData[]>([])
|
||||||
await remove_friend(
|
const sortedFriends = computed<FriendWithUserData[]>(() =>
|
||||||
friend.id === userCredentials.value.user_id ? friend.friend_id : friend.id,
|
userFriends.value.slice().sort((a, b) => {
|
||||||
).catch(handleError)
|
|
||||||
await loadFriends()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Friend = {
|
|
||||||
id: string
|
|
||||||
friend_id: string | null
|
|
||||||
status: string | null
|
|
||||||
last_updated: Dayjs | null
|
|
||||||
created: Dayjs
|
|
||||||
username: string
|
|
||||||
accepted: boolean
|
|
||||||
online: boolean
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const userFriends = ref<Friend[]>([])
|
|
||||||
const acceptedFriends = computed(() =>
|
|
||||||
userFriends.value
|
|
||||||
.filter((x) => x.accepted)
|
|
||||||
.toSorted((a, b) => {
|
|
||||||
if (a.last_updated === null && b.last_updated === null) {
|
if (a.last_updated === null && b.last_updated === null) {
|
||||||
return 0 // Both are null, equal in sorting
|
return 0 // Both are null, equal in sorting
|
||||||
}
|
}
|
||||||
@@ -93,8 +80,32 @@ const acceptedFriends = computed(() =>
|
|||||||
return b.last_updated.diff(a.last_updated)
|
return b.last_updated.diff(a.last_updated)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
const filteredFriends = computed<FriendWithUserData[]>(() =>
|
||||||
|
sortedFriends.value.filter((x) =>
|
||||||
|
x.username.trim().toLowerCase().includes(search.value.trim().toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const activeFriends = computed<FriendWithUserData[]>(() =>
|
||||||
|
filteredFriends.value.filter((x) => !!x.status && x.online && x.accepted),
|
||||||
|
)
|
||||||
|
const onlineFriends = computed<FriendWithUserData[]>(() =>
|
||||||
|
filteredFriends.value.filter((x) => x.online && !x.status && x.accepted),
|
||||||
|
)
|
||||||
|
const offlineFriends = computed<FriendWithUserData[]>(() =>
|
||||||
|
filteredFriends.value.filter((x) => !x.online && x.accepted),
|
||||||
|
)
|
||||||
const pendingFriends = computed(() =>
|
const pendingFriends = computed(() =>
|
||||||
userFriends.value.filter((x) => !x.accepted).toSorted((a, b) => b.created.diff(a.created)),
|
filteredFriends.value
|
||||||
|
.filter((x) => !x.accepted && x.id !== userCredentials.value?.user_id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => b.created.diff(a.created)),
|
||||||
|
)
|
||||||
|
const incomingRequests = computed(() =>
|
||||||
|
userFriends.value
|
||||||
|
.filter((x) => !x.accepted && x.id === userCredentials.value?.user_id)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => b.created.diff(a.created)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -103,34 +114,7 @@ async function loadFriends(timeout = false) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const friendsList = await friends()
|
const friendsList = await friends()
|
||||||
|
userFriends.value = await transformFriends(friendsList, userCredentials.value)
|
||||||
if (friendsList.length === 0) {
|
|
||||||
userFriends.value = []
|
|
||||||
} else {
|
|
||||||
const friendStatuses = await friend_statuses()
|
|
||||||
const users = await get_user_many(
|
|
||||||
friendsList.map((x) => (x.id === userCredentials.value.user_id ? x.friend_id : x.id)),
|
|
||||||
)
|
|
||||||
|
|
||||||
userFriends.value = friendsList.map((friend) => {
|
|
||||||
const user = users.find((x) => x.id === friend.id || x.id === friend.friend_id)
|
|
||||||
const status = friendStatuses.find(
|
|
||||||
(x) => x.user_id === friend.id || x.user_id === friend.friend_id,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
id: friend.id,
|
|
||||||
friend_id: friend.friend_id,
|
|
||||||
status: status?.profile_name,
|
|
||||||
last_updated: status && status.last_update ? dayjs(status.last_update) : null,
|
|
||||||
created: dayjs(friend.created),
|
|
||||||
avatar: user?.avatar_url,
|
|
||||||
username: user?.username,
|
|
||||||
online: !!status,
|
|
||||||
accepted: friend.accepted,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error loading friends', e)
|
console.error('Error loading friends', e)
|
||||||
@@ -145,6 +129,7 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
if (userCredentials.value === undefined) {
|
if (userCredentials.value === undefined) {
|
||||||
userFriends.value = []
|
userFriends.value = []
|
||||||
|
loading.value = false
|
||||||
} else if (userCredentials.value === null) {
|
} else if (userCredentials.value === null) {
|
||||||
userFriends.value = []
|
userFriends.value = []
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -159,49 +144,87 @@ const unlisten = await friend_listener(() => loadFriends())
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
unlisten()
|
unlisten()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
addFriend: {
|
||||||
|
id: 'friends.action.add-friend',
|
||||||
|
defaultMessage: 'Add a friend',
|
||||||
|
},
|
||||||
|
addingAFriend: {
|
||||||
|
id: 'friends.add-friend.title',
|
||||||
|
defaultMessage: 'Adding a friend',
|
||||||
|
},
|
||||||
|
usernameTitle: {
|
||||||
|
id: 'friends.add-friend.username.title',
|
||||||
|
defaultMessage: "What's your friend's Modrinth username?",
|
||||||
|
},
|
||||||
|
usernameDescription: {
|
||||||
|
id: 'friends.add-friend.username.description',
|
||||||
|
defaultMessage: 'It may be different from their Minecraft username!',
|
||||||
|
},
|
||||||
|
usernamePlaceholder: {
|
||||||
|
id: 'friends.add-friend.username.placeholder',
|
||||||
|
defaultMessage: 'Enter Modrinth username...',
|
||||||
|
},
|
||||||
|
sendFriendRequest: {
|
||||||
|
id: 'friends.add-friend.submit',
|
||||||
|
defaultMessage: 'Send friend request',
|
||||||
|
},
|
||||||
|
viewFriendRequests: {
|
||||||
|
id: 'friends.action.view-friend-requests',
|
||||||
|
defaultMessage: '{count} friend {count, plural, one {request} other {requests}}',
|
||||||
|
},
|
||||||
|
searchFriends: {
|
||||||
|
id: 'friends.search-friends-placeholder',
|
||||||
|
defaultMessage: 'Search friends...',
|
||||||
|
},
|
||||||
|
friends: {
|
||||||
|
id: 'friends.heading',
|
||||||
|
defaultMessage: 'Friends',
|
||||||
|
},
|
||||||
|
pending: {
|
||||||
|
id: 'friends.heading.pending',
|
||||||
|
defaultMessage: 'Pending',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
id: 'friends.heading.active',
|
||||||
|
defaultMessage: 'Active',
|
||||||
|
},
|
||||||
|
online: {
|
||||||
|
id: 'friends.heading.online',
|
||||||
|
defaultMessage: 'Online',
|
||||||
|
},
|
||||||
|
offline: {
|
||||||
|
id: 'friends.heading.offline',
|
||||||
|
defaultMessage: 'Offline',
|
||||||
|
},
|
||||||
|
noFriendsMatch: {
|
||||||
|
id: 'friends.no-friends-match',
|
||||||
|
defaultMessage: `No friends matching ''{query}''`,
|
||||||
|
},
|
||||||
|
signInToAddFriends: {
|
||||||
|
id: 'friends.sign-in-to-add-friends',
|
||||||
|
defaultMessage:
|
||||||
|
"<link>Sign in to a Modrinth account</link> to add friends and see what they're playing!",
|
||||||
|
},
|
||||||
|
addFriendsToShare: {
|
||||||
|
id: 'friends.add-friends-to-share',
|
||||||
|
defaultMessage: "<link>Add friends</link> to see what they're playing!",
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="manageFriendsModal" header="Manage friends">
|
|
||||||
<p v-if="acceptedFriends.length === 0">Add friends to share what you're playing!</p>
|
|
||||||
<div v-else class="flex flex-col gap-4 min-w-[20rem]">
|
|
||||||
<input v-model="search" type="text" placeholder="Search friends..." class="w-full" />
|
|
||||||
<div
|
|
||||||
v-for="friend in acceptedFriends.filter(
|
|
||||||
(x) => !search || x.username.toLowerCase().includes(search),
|
|
||||||
)"
|
|
||||||
:key="friend.username"
|
|
||||||
class="flex gap-2 items-center"
|
|
||||||
>
|
|
||||||
<div class="relative">
|
|
||||||
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
|
|
||||||
<span
|
|
||||||
v-if="friend.online"
|
|
||||||
class="bottom-1 right-0 absolute w-3 h-3 bg-brand border-2 border-black border-solid rounded-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>{{ friend.username }}</div>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<ButtonStyled>
|
|
||||||
<button @click="removeFriend(friend)">
|
|
||||||
<XIcon />
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalWrapper>
|
|
||||||
<ModalWrapper ref="friendInvitesModal" header="View friend requests">
|
<ModalWrapper ref="friendInvitesModal" header="View friend requests">
|
||||||
<p v-if="pendingFriends.length === 0">You have no pending friend requests :C</p>
|
<p v-if="incomingRequests.length === 0">You have no pending friend requests :C</p>
|
||||||
<div v-else class="flex flex-col gap-4">
|
<div v-else class="flex flex-col gap-4 min-w-[40rem]">
|
||||||
<div v-for="friend in pendingFriends" :key="friend.username" class="flex gap-2">
|
<div v-for="friend in incomingRequests" :key="friend.username" class="flex gap-2">
|
||||||
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
|
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="grid grid-cols-[1fr_auto] w-full gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
<template v-if="friend.id === userCredentials.user_id">
|
<template v-if="friend.id === userCredentials?.user_id">
|
||||||
<span class="font-bold">{{ friend.username }}</span> sent you a friend request
|
<span class="text-contrast">{{ friend.username }}</span> sent you a friend request
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
You sent <span class="font-bold">{{ friend.username }}</span> a friend request
|
You sent <span class="font-bold">{{ friend.username }}</span> a friend request
|
||||||
@@ -212,7 +235,7 @@ onUnmounted(() => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<template v-if="friend.id === userCredentials.user_id">
|
<template v-if="friend.id === userCredentials?.user_id">
|
||||||
<ButtonStyled color="brand">
|
<ButtonStyled color="brand">
|
||||||
<button @click="addFriend(friend)">
|
<button @click="addFriend(friend)">
|
||||||
<UserPlusIcon />
|
<UserPlusIcon />
|
||||||
@@ -239,72 +262,89 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<ModalWrapper ref="addFriendModal" header="Add a friend">
|
<ModalWrapper ref="addFriendModal" :header="formatMessage(messages.addingAFriend)">
|
||||||
<div class="mb-4">
|
<div class="min-w-[30rem]">
|
||||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Username</h2>
|
<h2 class="m-0 text-base font-medium text-primary">
|
||||||
<p class="m-0 mt-1 leading-tight">You can add friends with their Modrinth username.</p>
|
{{ formatMessage(messages.usernameTitle) }}
|
||||||
<input v-model="username" class="mt-2 w-full" type="text" placeholder="Enter username..." />
|
</h2>
|
||||||
|
<p class="m-0 mt-1 text-sm text-secondary leading-tight">
|
||||||
|
{{ formatMessage(messages.usernameDescription) }}
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center gap-2 mt-4">
|
||||||
|
<div class="iconified-input flex-1">
|
||||||
|
<UserIcon aria-hidden="true" />
|
||||||
|
<input
|
||||||
|
v-model="username"
|
||||||
|
type="text"
|
||||||
|
:placeholder="formatMessage(messages.usernamePlaceholder)"
|
||||||
|
@keyup.enter="addFriendFromModal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ButtonStyled color="brand">
|
<ButtonStyled color="brand">
|
||||||
<button :disabled="username.length === 0" @click="addFriendFromModal">
|
<button :disabled="username.length === 0" @click="addFriendFromModal">
|
||||||
<UserPlusIcon />
|
<SendIcon />
|
||||||
Add friend
|
{{ formatMessage(messages.sendFriendRequest) }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<div class="flex justify-between items-center">
|
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 ml-2 mr-1">
|
||||||
<h3 class="text-lg m-0">Friends</h3>
|
<template v-if="sortedFriends.length > 0">
|
||||||
<ButtonStyled v-if="userCredentials" type="transparent" circular>
|
<ButtonStyled circular type="transparent">
|
||||||
<OverflowMenu
|
<button
|
||||||
:options="[
|
v-tooltip="formatMessage(messages.addFriend)"
|
||||||
{
|
:aria-label="formatMessage(messages.addFriend)"
|
||||||
id: 'add-friend',
|
@click="addFriendModal.show"
|
||||||
action: () => addFriendModal.show(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'manage-friends',
|
|
||||||
action: () => manageFriendsModal.show(),
|
|
||||||
shown: acceptedFriends.length > 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'view-requests',
|
|
||||||
action: () => friendInvitesModal.show(),
|
|
||||||
shown: pendingFriends.length > 0,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
aria-label="More options"
|
|
||||||
>
|
>
|
||||||
<MoreVerticalIcon aria-hidden="true" />
|
<UserPlusIcon />
|
||||||
<template #add-friend>
|
</button>
|
||||||
<UserPlusIcon aria-hidden="true" />
|
</ButtonStyled>
|
||||||
Add friend
|
<div class="iconified-input flex-1">
|
||||||
</template>
|
<input
|
||||||
<template #manage-friends>
|
v-model="search"
|
||||||
<SettingsIcon aria-hidden="true" />
|
type="text"
|
||||||
Manage friends
|
class="friends-search-bar flex w-full"
|
||||||
<div
|
:placeholder="formatMessage(messages.searchFriends)"
|
||||||
v-if="acceptedFriends.length > 0"
|
@keyup.esc="search = ''"
|
||||||
class="bg-button-bg w-6 h-6 rounded-full flex items-center justify-center"
|
/>
|
||||||
|
<button
|
||||||
|
v-if="search"
|
||||||
|
v-tooltip="formatMessage(commonMessages.clearButton)"
|
||||||
|
class="r-btn flex items-center justify-center bg-transparent button-animation p-2 cursor-pointer appearance-none border-none"
|
||||||
|
@click="search = ''"
|
||||||
>
|
>
|
||||||
{{ acceptedFriends.length }}
|
<XIcon />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #view-requests>
|
<h3 v-else class="ml-2 w-full text-base text-primary font-medium m-0">
|
||||||
<MailIcon aria-hidden="true" />
|
{{ formatMessage(messages.friends) }}
|
||||||
View friend requests
|
</h3>
|
||||||
<div
|
<ButtonStyled v-if="incomingRequests.length > 0" circular type="transparent">
|
||||||
v-if="pendingFriends.length > 0"
|
<button
|
||||||
class="bg-button-bg w-6 h-6 rounded-full flex items-center justify-center"
|
v-tooltip="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
|
||||||
|
class="relative"
|
||||||
|
:aria-label="formatMessage(messages.viewFriendRequests, { count: incomingRequests.length })"
|
||||||
|
@click="friendInvitesModal.show"
|
||||||
>
|
>
|
||||||
{{ pendingFriends.length }}
|
<MailIcon />
|
||||||
</div>
|
<span
|
||||||
</template>
|
v-if="incomingRequests.length > 0"
|
||||||
</OverflowMenu>
|
aria-hidden="true"
|
||||||
|
class="absolute bg-brand text-brand-inverted text-[8px] top-0.5 px-1 right-0.5 min-w-3 h-3 rounded-full flex items-center justify-center font-bold"
|
||||||
|
>
|
||||||
|
{{ incomingRequests.length }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 mt-2">
|
<div class="flex flex-col gap-3">
|
||||||
|
<h3 v-if="loading" class="ml-4 mr-1 text-base text-primary font-medium m-0">
|
||||||
|
{{ formatMessage(messages.friends) }}
|
||||||
|
</h3>
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse">
|
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse ml-4 mr-1">
|
||||||
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
|
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
|
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
|
||||||
@@ -312,50 +352,77 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="acceptedFriends.length === 0">
|
<template v-else-if="sortedFriends.length === 0">
|
||||||
<div class="text-sm">
|
<div class="text-sm ml-4 mr-1">
|
||||||
<div v-if="!userCredentials">
|
<div v-if="!userCredentials">
|
||||||
<span class="text-link cursor-pointer" @click="signIn">Sign in</span> to add friends!
|
<IntlFormatted :message-id="messages.signInToAddFriends">
|
||||||
|
<template #link="{ children }">
|
||||||
|
<span class="font-semibold text-brand cursor-pointer" @click="signIn">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<span class="text-link cursor-pointer" @click="addFriendModal.show()">Add friends</span>
|
<IntlFormatted :message-id="messages.addFriendsToShare">
|
||||||
to share what you're playing!
|
<template #link="{ children }">
|
||||||
|
<span class="font-semibold text-brand cursor-pointer" @click="addFriendModal.show">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ContextMenu ref="friendOptions" @option-clicked="handleFriendOptions">
|
<FriendsSection
|
||||||
<template #remove-friend> <TrashIcon /> Remove friend </template>
|
v-if="activeFriends.length > 0"
|
||||||
</ContextMenu>
|
:is-searching="!!search"
|
||||||
<div
|
open-by-default
|
||||||
v-for="friend in acceptedFriends.slice(0, 5)"
|
:friends="activeFriends"
|
||||||
:key="friend.username"
|
:heading="formatMessage(messages.active)"
|
||||||
class="flex gap-2 items-center"
|
:remove-friend="removeFriend"
|
||||||
:class="{ grayscale: !friend.online }"
|
|
||||||
@contextmenu.prevent.stop="
|
|
||||||
(event) =>
|
|
||||||
friendOptions.showMenu(event, friend, [
|
|
||||||
{
|
|
||||||
name: 'remove-friend',
|
|
||||||
color: 'danger',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="relative">
|
|
||||||
<Avatar :src="friend.avatar" class="w-12 h-12 rounded-full" size="2.25rem" circle />
|
|
||||||
<span
|
|
||||||
v-if="friend.online"
|
|
||||||
class="bottom-1 right-0 absolute w-3 h-3 bg-brand border-2 border-black border-solid rounded-full"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
<FriendsSection
|
||||||
<div class="flex flex-col">
|
v-if="onlineFriends.length > 0"
|
||||||
<span class="text-md m-0 font-medium" :class="{ 'text-secondary': !friend.online }">
|
:is-searching="!!search"
|
||||||
{{ friend.username }}
|
open-by-default
|
||||||
</span>
|
:friends="onlineFriends"
|
||||||
<span v-if="friend.status" class="m-0 text-xs">{{ friend.status }}</span>
|
:heading="formatMessage(messages.online)"
|
||||||
</div>
|
:remove-friend="removeFriend"
|
||||||
</div>
|
/>
|
||||||
|
<FriendsSection
|
||||||
|
v-if="offlineFriends.length > 0"
|
||||||
|
:is-searching="!!search"
|
||||||
|
:open-by-default="activeFriends.length + onlineFriends.length < 3"
|
||||||
|
:friends="offlineFriends"
|
||||||
|
:heading="formatMessage(messages.offline)"
|
||||||
|
:remove-friend="removeFriend"
|
||||||
|
/>
|
||||||
|
<FriendsSection
|
||||||
|
v-if="pendingFriends.length > 0"
|
||||||
|
:is-searching="!!search"
|
||||||
|
open-by-default
|
||||||
|
:friends="pendingFriends"
|
||||||
|
:heading="formatMessage(messages.pending)"
|
||||||
|
:remove-friend="removeFriend"
|
||||||
|
/>
|
||||||
|
<p v-if="filteredFriends.length === 0 && search" class="text-sm text-secondary my-1 mx-4">
|
||||||
|
{{ formatMessage(messages.noFriendsMatch, { query: search }) }}
|
||||||
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.friends-search-bar {
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--color-button-bg) !important;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-search-bar::placeholder {
|
||||||
|
@apply text-sm font-normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
191
apps/app-frontend/src/components/ui/friends/FriendsSection.vue
Normal file
191
apps/app-frontend/src/components/ui/friends/FriendsSection.vue
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { MoreVerticalIcon, TrashIcon, UserIcon, XIcon } from '@modrinth/assets'
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
OverflowMenu,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
import type { FriendWithUserData } from '@/helpers/friends.ts'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
friends: FriendWithUserData[]
|
||||||
|
heading: string
|
||||||
|
removeFriend: (friend: FriendWithUserData) => Promise<void>
|
||||||
|
isSearching?: boolean
|
||||||
|
openByDefault?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
isSearching: false,
|
||||||
|
openByDefault: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function createContextMenuOptions(friend: FriendWithUserData) {
|
||||||
|
if (friend.accepted) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'view-profile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'remove-friend',
|
||||||
|
color: 'danger',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'view-profile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancel-request',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openProfile(username: string) {
|
||||||
|
openUrl('https://modrinth.com/user/' + username)
|
||||||
|
}
|
||||||
|
|
||||||
|
const friendOptions = useTemplateRef('friendOptions')
|
||||||
|
async function handleFriendOptions(args: { item: FriendWithUserData; option: string }) {
|
||||||
|
switch (args.option) {
|
||||||
|
case 'remove-friend':
|
||||||
|
case 'cancel-request':
|
||||||
|
await props.removeFriend(args.item)
|
||||||
|
break
|
||||||
|
case 'view-profile':
|
||||||
|
openProfile(args.item.username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
removeFriend: {
|
||||||
|
id: 'friends.friend.remove-friend',
|
||||||
|
defaultMessage: 'Remove friend',
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
id: 'friends.section.heading',
|
||||||
|
defaultMessage: '{title} - {count}',
|
||||||
|
},
|
||||||
|
friendRequestSent: {
|
||||||
|
id: 'friends.friend.request-sent',
|
||||||
|
defaultMessage: 'Friend request sent',
|
||||||
|
},
|
||||||
|
cancelRequest: {
|
||||||
|
id: 'friends.friend.cancel-request',
|
||||||
|
defaultMessage: 'Cancel request',
|
||||||
|
},
|
||||||
|
viewProfile: {
|
||||||
|
id: 'friends.friend.view-profile',
|
||||||
|
defaultMessage: 'View profile',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ContextMenu ref="friendOptions" @option-clicked="handleFriendOptions">
|
||||||
|
<template #view-profile>
|
||||||
|
<UserIcon />
|
||||||
|
{{ formatMessage(messages.viewProfile) }}
|
||||||
|
</template>
|
||||||
|
<template #remove-friend> <TrashIcon /> {{ formatMessage(messages.removeFriend) }} </template>
|
||||||
|
<template #cancel-request> <XIcon /> {{ formatMessage(messages.cancelRequest) }} </template>
|
||||||
|
</ContextMenu>
|
||||||
|
<Accordion
|
||||||
|
:open-by-default="openByDefault"
|
||||||
|
:force-open="isSearching"
|
||||||
|
:button-class="
|
||||||
|
'pl-4 pr-3 flex w-full items-center bg-transparent border-0 p-0' +
|
||||||
|
(isSearching
|
||||||
|
? ''
|
||||||
|
: ' cursor-pointer hover:brightness-[--hover-brightness] active:scale-[0.98] transition-all')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<h3 class="text-base text-primary font-medium m-0">
|
||||||
|
{{ formatMessage(messages.heading, { title: heading, count: friends.length }) }}
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<div class="pt-3 flex flex-col gap-1">
|
||||||
|
<div
|
||||||
|
v-for="friend in friends"
|
||||||
|
:key="friend.username"
|
||||||
|
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full ml-4 mr-1"
|
||||||
|
@contextmenu.prevent.stop="
|
||||||
|
(event) => friendOptions?.showMenu(event, friend, createContextMenuOptions(friend))
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="relative">
|
||||||
|
<Avatar
|
||||||
|
:src="friend.avatar"
|
||||||
|
:class="{ grayscale: !friend.online && friend.accepted }"
|
||||||
|
class="w-12 h-12 rounded-full"
|
||||||
|
size="32px"
|
||||||
|
circle
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-if="friend.online"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="bottom-[2px] right-[-2px] absolute w-3 h-3 bg-brand border-2 border-black border-solid rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span
|
||||||
|
class="text-sm m-0"
|
||||||
|
:class="friend.online || !friend.accepted ? 'text-contrast' : 'text-primary'"
|
||||||
|
>
|
||||||
|
{{ friend.username }}
|
||||||
|
</span>
|
||||||
|
<span v-if="!friend.accepted" class="m-0 text-xs">
|
||||||
|
{{ formatMessage(messages.friendRequestSent) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="friend.status" class="m-0 text-xs">{{ friend.status }}</span>
|
||||||
|
</div>
|
||||||
|
<ButtonStyled v-if="friend.accepted" circular type="transparent">
|
||||||
|
<OverflowMenu
|
||||||
|
class="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
id: 'view-profile',
|
||||||
|
action: () => openProfile(friend.username),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'remove-friend',
|
||||||
|
action: () => removeFriend(friend),
|
||||||
|
color: 'red',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
<template #view-profile>
|
||||||
|
<UserIcon />
|
||||||
|
{{ formatMessage(messages.viewProfile) }}
|
||||||
|
</template>
|
||||||
|
<template #remove-friend>
|
||||||
|
<TrashIcon />
|
||||||
|
{{ formatMessage(messages.removeFriend) }}
|
||||||
|
</template>
|
||||||
|
</OverflowMenu>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled v-else type="transparent" circular>
|
||||||
|
<button v-tooltip="formatMessage(messages.cancelRequest)" @click="removeFriend(friend)">
|
||||||
|
<XIcon />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Accordion>
|
||||||
|
</template>
|
||||||
@@ -56,16 +56,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { Button } from '@modrinth/ui'
|
|
||||||
import { formatCategory } from '@modrinth/utils'
|
import { formatCategory } from '@modrinth/utils'
|
||||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { handleError } from '@/store/state.js'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
|
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const instance = ref(null)
|
const instance = ref(null)
|
||||||
const project = ref(null)
|
const project = ref(null)
|
||||||
const versions = ref(null)
|
const versions = ref(null)
|
||||||
@@ -76,10 +78,10 @@ const installing = ref(false)
|
|||||||
const onInstall = ref(() => {})
|
const onInstall = ref(() => {})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
show: (instanceVal, projectVal, projectVersions, selected, callback) => {
|
||||||
instance.value = instanceVal
|
instance.value = instanceVal
|
||||||
versions.value = projectVersions
|
versions.value = projectVersions
|
||||||
selectedVersion.value = projectVersions[0]
|
selectedVersion.value = selected ?? projectVersions[0]
|
||||||
|
|
||||||
project.value = projectVal
|
project.value = projectVal
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
import { DownloadIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { create_profile_and_install as pack_install } from '@/helpers/pack'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import { handleError } from '@/store/state.js'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { create_profile_and_install as pack_install } from '@/helpers/pack'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const versionId = ref()
|
const versionId = ref()
|
||||||
const project = ref()
|
const project = ref()
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
|
CheckIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
|
RightArrowIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
RightArrowIcon,
|
|
||||||
CheckIcon,
|
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import {
|
import {
|
||||||
add_project_from_version as installMod,
|
add_project_from_version as installMod,
|
||||||
check_installed,
|
check_installed,
|
||||||
|
create,
|
||||||
get,
|
get,
|
||||||
list,
|
list,
|
||||||
create,
|
|
||||||
} from '@/helpers/profile'
|
} from '@/helpers/profile'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import {
|
||||||
import { installVersionDependencies } from '@/store/install.js'
|
findPreferredVersion,
|
||||||
import { handleError } from '@/store/notifications.js'
|
installVersionDependencies,
|
||||||
import { useRouter } from 'vue-router'
|
isVersionCompatible,
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
} from '@/store/install.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const versions = ref()
|
const versions = ref()
|
||||||
@@ -48,14 +53,11 @@ const shownProfiles = computed(() =>
|
|||||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||||
})
|
})
|
||||||
.filter((profile) => {
|
.filter((profile) => {
|
||||||
const loaders = versions.value.flatMap((v) => v.loaders)
|
const version = {
|
||||||
|
game_versions: versions.value.flatMap((v) => v.game_versions),
|
||||||
return (
|
loaders: versions.value.flatMap((v) => v.loaders),
|
||||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
}
|
||||||
(project.value.project_type === 'mod'
|
return isVersionCompatible(version, project.value, profile)
|
||||||
? loaders.includes(profile.loader) || loaders.includes('minecraft')
|
|
||||||
: true)
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,14 +95,7 @@ defineExpose({
|
|||||||
|
|
||||||
async function install(instance) {
|
async function install(instance) {
|
||||||
instance.installing = true
|
instance.installing = true
|
||||||
const version = versions.value.find((v) => {
|
const version = findPreferredVersion(versions.value, project.value, instance)
|
||||||
return (
|
|
||||||
v.game_versions.includes(instance.game_version) &&
|
|
||||||
(project.value.project_type === 'mod'
|
|
||||||
? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
|
|
||||||
: true)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!version) {
|
if (!version) {
|
||||||
instance.installing = false
|
instance.installing = false
|
||||||
@@ -109,7 +104,7 @@ async function install(instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await installMod(instance.path, version.id).catch(handleError)
|
await installMod(instance.path, version.id).catch(handleError)
|
||||||
await installVersionDependencies(instance, version)
|
await installVersionDependencies(instance, version).catch(handleError)
|
||||||
|
|
||||||
instance.installedMod = true
|
instance.installedMod = true
|
||||||
instance.installing = false
|
instance.installing = false
|
||||||
@@ -164,27 +159,28 @@ const reset_icon = () => {
|
|||||||
const createInstance = async () => {
|
const createInstance = async () => {
|
||||||
creatingInstance.value = true
|
creatingInstance.value = true
|
||||||
|
|
||||||
const loader =
|
const gameVersions = versions.value[0].game_versions
|
||||||
versions.value[0].loaders[0] !== 'forge' &&
|
const gameVersion = gameVersions[0]
|
||||||
versions.value[0].loaders[0] !== 'fabric' &&
|
|
||||||
versions.value[0].loaders[0] !== 'quilt'
|
|
||||||
? 'vanilla'
|
|
||||||
: versions.value[0].loaders[0]
|
|
||||||
|
|
||||||
const id = await create(
|
const loaders = versions.value[0].loaders
|
||||||
name.value,
|
const loader = loaders.contains('fabric')
|
||||||
versions.value[0].game_versions[0],
|
? 'fabric'
|
||||||
loader,
|
: loaders.contains('neoforge')
|
||||||
'latest',
|
? 'neoforge'
|
||||||
icon.value,
|
: loaders.contains('forge')
|
||||||
).catch(handleError)
|
? 'forge'
|
||||||
|
: loaders.contains('quilt')
|
||||||
|
? 'quilt'
|
||||||
|
: 'vanilla'
|
||||||
|
|
||||||
|
const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError)
|
||||||
|
|
||||||
await installMod(id, versions.value[0].id).catch(handleError)
|
await installMod(id, versions.value[0].id).catch(handleError)
|
||||||
|
|
||||||
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
||||||
|
|
||||||
const instance = await get(id, true)
|
const instance = await get(id, true)
|
||||||
await installVersionDependencies(instance, versions.value[0])
|
await installVersionDependencies(instance, versions.value[0]).catch(handleError)
|
||||||
|
|
||||||
trackEvent('InstanceCreate', {
|
trackEvent('InstanceCreate', {
|
||||||
profile_name: name.value,
|
profile_name: name.value,
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { CopyIcon, EditIcon, PlusIcon, SpinnerIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
Checkbox,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
OverflowMenu,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { SpinnerIcon, TrashIcon, UploadIcon, PlusIcon, EditIcon, CopyIcon } from '@modrinth/assets'
|
|
||||||
import { Avatar, ButtonStyled, OverflowMenu, Checkbox } from '@modrinth/ui'
|
|
||||||
import { computed, ref, type Ref, watch } from 'vue'
|
|
||||||
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
import { computed, type Ref, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
|
||||||
import type { InstanceSettingsTabProps, GameInstance } from '../../../helpers/types'
|
|
||||||
|
|
||||||
|
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
|
||||||
|
|
||||||
|
import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox } from '@modrinth/ui'
|
import { Checkbox, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { get } from '@/helpers/settings.ts'
|
|
||||||
import { edit } from '@/helpers/profile'
|
|
||||||
import type { InstanceSettingsTabProps, AppSettings, Hooks } from '../../../helpers/types'
|
|
||||||
|
|
||||||
|
import { edit } from '@/helpers/profile'
|
||||||
|
import { get } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
|
import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const props = defineProps<InstanceSettingsTabProps>()
|
const props = defineProps<InstanceSettingsTabProps>()
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
TransferIcon,
|
|
||||||
IssuesIcon,
|
|
||||||
HammerIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
WrenchIcon,
|
HammerIcon,
|
||||||
UndoIcon,
|
IssuesIcon,
|
||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
UnplugIcon,
|
TransferIcon,
|
||||||
|
UndoIcon,
|
||||||
UnlinkIcon,
|
UnlinkIcon,
|
||||||
|
UnplugIcon,
|
||||||
|
WrenchIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Checkbox, Chips, ButtonStyled, TeleportDropdownMenu } from '@modrinth/ui'
|
import {
|
||||||
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
Avatar,
|
||||||
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
|
ButtonStyled,
|
||||||
import { handleError } from '@/store/notifications'
|
Checkbox,
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
Chips,
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
Combobox,
|
||||||
import { get_loader_versions } from '@/helpers/metadata'
|
defineMessages,
|
||||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
formatCategory,
|
formatCategory,
|
||||||
type GameVersionTag,
|
type GameVersionTag,
|
||||||
@@ -25,22 +27,25 @@ import {
|
|||||||
type Project,
|
type Project,
|
||||||
type Version,
|
type Version,
|
||||||
} from '@modrinth/utils'
|
} from '@modrinth/utils'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import { get_project, get_version_many } from '@/helpers/cache'
|
|
||||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
|
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||||
|
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { get_project, get_version_many } from '@/helpers/cache'
|
||||||
|
import { get_loader_versions } from '@/helpers/metadata'
|
||||||
|
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
|
||||||
|
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
InstanceSettingsTabProps,
|
InstanceSettingsTabProps,
|
||||||
ManifestLoaderVersion,
|
|
||||||
Manifest,
|
Manifest,
|
||||||
|
ManifestLoaderVersion,
|
||||||
} from '../../../helpers/types'
|
} from '../../../helpers/types'
|
||||||
|
|
||||||
import { initAuthlibPatching } from '@/helpers/utils.js'
|
|
||||||
const authLibPatchingModal = ref(null)
|
|
||||||
const isAuthLibPatchedSuccess = ref(false)
|
|
||||||
const isAuthLibPatching = ref(false)
|
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const repairConfirmModal = ref()
|
const repairConfirmModal = ref()
|
||||||
@@ -155,6 +160,21 @@ const selectableGameVersionNumbers = computed(() => {
|
|||||||
.map((x) => x.version)
|
.map((x) => x.version)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const gameVersionOptions = computed(() =>
|
||||||
|
(selectableGameVersionNumbers.value ?? []).map((v) => ({ value: v, label: v })),
|
||||||
|
)
|
||||||
|
|
||||||
|
const loaderVersionOptions = computed(() =>
|
||||||
|
(selectableLoaderVersions.value ?? []).map((opt, index) => ({ value: index, label: opt.id })),
|
||||||
|
)
|
||||||
|
|
||||||
|
const loaderVersionLabel = computed(() => {
|
||||||
|
const idx = loaderVersionIndex.value
|
||||||
|
return idx >= 0 && selectableLoaderVersions.value
|
||||||
|
? selectableLoaderVersions.value[idx]?.id
|
||||||
|
: 'Select version'
|
||||||
|
})
|
||||||
|
|
||||||
const selectableLoaderVersions: ComputedRef<ManifestLoaderVersion[] | undefined> = computed(() => {
|
const selectableLoaderVersions: ComputedRef<ManifestLoaderVersion[] | undefined> = computed(() => {
|
||||||
if (gameVersion.value) {
|
if (gameVersion.value) {
|
||||||
if (loader.value === 'fabric') {
|
if (loader.value === 'fabric') {
|
||||||
@@ -453,43 +473,9 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'reinstall',
|
defaultMessage: 'reinstall',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleInitAuthLibPatching(ismojang: boolean) {
|
|
||||||
isAuthLibPatching.value = true
|
|
||||||
let state = false
|
|
||||||
let instance_path = props.instance.loader_version != null ? props.instance.game_version + "-" + props.instance.loader_version : props.instance.game_version
|
|
||||||
try {
|
|
||||||
state = await initAuthlibPatching(instance_path, ismojang)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
isAuthLibPatching.value = false
|
|
||||||
isAuthLibPatchedSuccess.value = state
|
|
||||||
authLibPatchingModal.value.show()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper
|
|
||||||
ref="authLibPatchingModal"
|
|
||||||
:header="'AuthLib installation report'"
|
|
||||||
:closable="true"
|
|
||||||
@close="authLibPatchingModal.hide()"
|
|
||||||
>
|
|
||||||
<div class="modal-body">
|
|
||||||
<h2 class="text-lg font-bold text-contrast space-y-2">
|
|
||||||
<p class="flex items-center gap-2 neon-text">
|
|
||||||
<span v-if="isAuthLibPatchedSuccess" class="neon-text">
|
|
||||||
AuthLib installation completed successfully! Now you can log in and play!
|
|
||||||
</span>
|
|
||||||
<span v-else class="neon-text">
|
|
||||||
Failed to install AuthLib. It's possible that no compatible AuthLib version was found for the selected game and/or mod loader version.
|
|
||||||
There may also be a problem with accessing resources behind CloudFlare.
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</ModalWrapper>
|
|
||||||
<ConfirmModalWrapper
|
<ConfirmModalWrapper
|
||||||
ref="repairConfirmModal"
|
ref="repairConfirmModal"
|
||||||
:title="formatMessage(messages.repairConfirmTitle)"
|
:title="formatMessage(messages.repairConfirmTitle)"
|
||||||
@@ -545,7 +531,8 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2">
|
<div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2">
|
||||||
<p class="text-brand-red font-medium mt-0">
|
<p class="text-brand-red font-medium mt-0">
|
||||||
<IssuesIcon class="top-[3px] relative" /> {{ formatMessage(messages.noModpackFound) }}
|
<IssuesIcon class="top-[3px] relative" />
|
||||||
|
{{ formatMessage(messages.noModpackFound) }}
|
||||||
</p>
|
</p>
|
||||||
<p>{{ formatMessage(messages.debugInformation) }}</p>
|
<p>{{ formatMessage(messages.debugInformation) }}</p>
|
||||||
<div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary">
|
<div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary">
|
||||||
@@ -572,7 +559,9 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
|||||||
{{
|
{{
|
||||||
modpackProject
|
modpackProject
|
||||||
? modpackProject.title
|
? modpackProject.title
|
||||||
: formatMessage(messages.minecraftVersion, { version: instance.game_version })
|
: formatMessage(messages.minecraftVersion, {
|
||||||
|
version: instance.game_version,
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-secondary leading-none">
|
<span class="text-sm text-secondary leading-none">
|
||||||
@@ -675,11 +664,11 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
|||||||
{{ formatMessage(messages.gameVersion) }}
|
{{ formatMessage(messages.gameVersion) }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex flex-wrap mt-2 gap-2">
|
<div class="flex flex-wrap mt-2 gap-2">
|
||||||
<TeleportDropdownMenu
|
<Combobox
|
||||||
v-if="selectableGameVersionNumbers !== undefined"
|
v-if="selectableGameVersionNumbers !== undefined"
|
||||||
v-model="gameVersion"
|
v-model="gameVersion"
|
||||||
:options="selectableGameVersionNumbers"
|
:options="gameVersionOptions"
|
||||||
name="Game Version Dropdown"
|
:display-value="gameVersion || formatMessage(messages.unknownVersion)"
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="hasSnapshots"
|
v-if="hasSnapshots"
|
||||||
@@ -691,18 +680,22 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
|||||||
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
|
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
|
||||||
{{ formatMessage(messages.loaderVersion, { loader: formatCategory(loader) }) }}
|
{{ formatMessage(messages.loaderVersion, { loader: formatCategory(loader) }) }}
|
||||||
</h2>
|
</h2>
|
||||||
<TeleportDropdownMenu
|
<Combobox
|
||||||
v-if="selectableLoaderVersions"
|
v-if="selectableLoaderVersions"
|
||||||
:model-value="selectableLoaderVersions[loaderVersionIndex]"
|
v-model="loaderVersionIndex"
|
||||||
:options="selectableLoaderVersions"
|
:options="loaderVersionOptions"
|
||||||
:display-name="(option: ManifestLoaderVersion) => option?.id"
|
:display-value="loaderVersionLabel"
|
||||||
name="Version selector"
|
name="Version selector"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@change="(value) => (loaderVersionIndex = value.index)"
|
|
||||||
/>
|
/>
|
||||||
<div v-else class="mt-2 text-brand-red flex gap-2 items-center">
|
<div v-else class="mt-2 text-brand-red flex gap-2 items-center">
|
||||||
<IssuesIcon />
|
<IssuesIcon />
|
||||||
{{ formatMessage(messages.noLoaderVersions, { loader: loader, version: gameVersion }) }}
|
{{
|
||||||
|
formatMessage(messages.noLoaderVersions, {
|
||||||
|
loader: loader,
|
||||||
|
version: gameVersion,
|
||||||
|
})
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="mt-4 flex flex-wrap gap-2">
|
<div class="mt-4 flex flex-wrap gap-2">
|
||||||
@@ -760,24 +753,6 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
|
|||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
|
|
||||||
<div v-if="isAuthLibPatching" class="w-6 h-6 cursor-pointer hover:brightness-75 neon-icon pulse">
|
|
||||||
<SpinnerIcon class="size-4 animate-spin" />
|
|
||||||
</div>
|
|
||||||
Auth system (Skins) <span class="text-sm font-bold px-2 bg-brand-highlight text-brand rounded-full">Beta</span>
|
|
||||||
</h2>
|
|
||||||
<div class="mt-4 flex gap-2">
|
|
||||||
<ButtonStyled class="neon-button neon">
|
|
||||||
<button :disabled="isAuthLibPatching" @click="handleInitAuthLibPatching(true)">
|
|
||||||
Install Microsoft
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled class="neon-button neon">
|
|
||||||
<button :disabled="isAuthLibPatching" @click="handleInitAuthLibPatching(false) ">
|
|
||||||
Install Ely.By
|
|
||||||
</button>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="instance.linked_data && instance.linked_data.locked">
|
<template v-if="instance.linked_data && instance.linked_data.locked">
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox, Slider } from '@modrinth/ui'
|
|
||||||
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
|
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
|
||||||
|
import { Checkbox, defineMessages, injectNotificationManager, Slider, useVIntl } from '@modrinth/ui'
|
||||||
import { computed, readonly, ref, watch } from 'vue'
|
import { computed, readonly, ref, watch } from 'vue'
|
||||||
import { edit, get_optimal_jre_key } from '@/helpers/profile'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
|
||||||
import { get } from '@/helpers/settings.ts'
|
|
||||||
import type { InstanceSettingsTabProps, AppSettings, MemorySettings } from '../../../helpers/types'
|
|
||||||
import useMemorySlider from '@/composables/useMemorySlider'
|
|
||||||
|
|
||||||
|
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||||
|
import useMemorySlider from '@/composables/useMemorySlider'
|
||||||
|
import { edit, get_optimal_jre_key } from '@/helpers/profile'
|
||||||
|
import { get } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
|
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const props = defineProps<InstanceSettingsTabProps>()
|
const props = defineProps<InstanceSettingsTabProps>()
|
||||||
|
|
||||||
const globalSettings = (await get().catch(handleError)) as AppSettings
|
const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
|
||||||
|
|
||||||
const overrideJavaInstall = ref(!!props.instance.java_path)
|
const overrideJavaInstall = ref(!!props.instance.java_path)
|
||||||
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
||||||
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
||||||
|
|
||||||
const overrideJavaArgs = ref(props.instance.extra_launch_args?.length !== undefined)
|
const overrideJavaArgs = ref((props.instance.extra_launch_args?.length ?? 0) > 0)
|
||||||
const javaArgs = ref(
|
const javaArgs = ref(
|
||||||
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
||||||
)
|
)
|
||||||
|
|
||||||
const overrideEnvVars = ref(props.instance.custom_env_vars?.length !== undefined)
|
const overrideEnvVars = ref((props.instance.custom_env_vars?.length ?? 0) > 0)
|
||||||
const envVars = ref(
|
const envVars = ref(
|
||||||
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
||||||
.map((x) => x.join('='))
|
.map((x) => x.join('='))
|
||||||
@@ -34,39 +35,29 @@ const envVars = ref(
|
|||||||
|
|
||||||
const overrideMemorySettings = ref(!!props.instance.memory)
|
const overrideMemorySettings = ref(!!props.instance.memory)
|
||||||
const memory = ref(props.instance.memory ?? globalSettings.memory)
|
const memory = ref(props.instance.memory ?? globalSettings.memory)
|
||||||
const { maxMemory, snapPoints } = await useMemorySlider()
|
const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
|
||||||
|
maxMemory: number
|
||||||
|
snapPoints: number[]
|
||||||
|
}
|
||||||
|
|
||||||
const editProfileObject = computed(() => {
|
const editProfileObject = computed(() => {
|
||||||
const editProfile: {
|
return {
|
||||||
java_path?: string
|
java_path:
|
||||||
extra_launch_args?: string[]
|
overrideJavaInstall.value && javaInstall.value.path !== ''
|
||||||
custom_env_vars?: string[][]
|
? javaInstall.value.path.replace('java.exe', 'javaw.exe')
|
||||||
memory?: MemorySettings
|
: null,
|
||||||
} = {}
|
extra_launch_args: overrideJavaArgs.value
|
||||||
|
? javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||||
if (overrideJavaInstall.value) {
|
: null,
|
||||||
if (javaInstall.value.path !== '') {
|
custom_env_vars: overrideEnvVars.value
|
||||||
editProfile.java_path = javaInstall.value.path.replace('java.exe', 'javaw.exe')
|
? envVars.value
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overrideJavaArgs.value) {
|
|
||||||
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overrideEnvVars.value) {
|
|
||||||
editProfile.custom_env_vars = envVars.value
|
|
||||||
.trim()
|
.trim()
|
||||||
.split(/\s+/)
|
.split(/\s+/)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((x) => x.split('=').filter(Boolean))
|
.map((x) => x.split('=').filter(Boolean))
|
||||||
|
: null,
|
||||||
|
memory: overrideMemorySettings.value ? memory.value : null,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overrideMemorySettings.value) {
|
|
||||||
editProfile.memory = memory.value
|
|
||||||
}
|
|
||||||
|
|
||||||
return editProfile
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox, Toggle } from '@modrinth/ui'
|
import { Checkbox, defineMessages, injectNotificationManager, Toggle, useVIntl } from '@modrinth/ui'
|
||||||
import { computed, ref, type Ref, watch } from 'vue'
|
import { computed, type Ref, ref, watch } from 'vue'
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { get } from '@/helpers/settings.ts'
|
|
||||||
import { edit } from '@/helpers/profile'
|
import { edit } from '@/helpers/profile'
|
||||||
|
import { get } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
|
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const props = defineProps<InstanceSettingsTabProps>()
|
const props = defineProps<InstanceSettingsTabProps>()
|
||||||
@@ -24,20 +25,16 @@ const fullscreenSetting: Ref<boolean> = ref(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const editProfileObject = computed(() => {
|
const editProfileObject = computed(() => {
|
||||||
const editProfile: {
|
if (!overrideWindowSettings.value) {
|
||||||
force_fullscreen?: boolean
|
return {
|
||||||
game_resolution?: [number, number]
|
force_fullscreen: null,
|
||||||
} = {}
|
game_resolution: null,
|
||||||
|
|
||||||
if (overrideWindowSettings.value) {
|
|
||||||
editProfile.force_fullscreen = fullscreenSetting.value
|
|
||||||
|
|
||||||
if (!fullscreenSetting.value) {
|
|
||||||
editProfile.game_resolution = resolution.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
return editProfile
|
force_fullscreen: fullscreenSetting.value,
|
||||||
|
game_resolution: fullscreenSetting.value ? null : resolution.value,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -93,14 +90,6 @@ const messages = defineMessages({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
v-model="overrideWindowSettings"
|
v-model="overrideWindowSettings"
|
||||||
:label="formatMessage(messages.customWindowSettings)"
|
:label="formatMessage(messages.customWindowSettings)"
|
||||||
@update:model-value="
|
|
||||||
(value) => {
|
|
||||||
if (!value) {
|
|
||||||
resolution = globalSettings.game_resolution
|
|
||||||
fullscreenSetting = globalSettings.force_fullscreen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
<div class="mt-2 flex items-center gap-4 justify-between">
|
<div class="mt-2 flex items-center gap-4 justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,31 +1,40 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ReportIcon,
|
|
||||||
AstralRinthLogo,
|
|
||||||
ShieldIcon,
|
|
||||||
SettingsIcon,
|
|
||||||
GaugeIcon,
|
|
||||||
PaintbrushIcon,
|
|
||||||
GameIcon,
|
|
||||||
CoffeeIcon,
|
CoffeeIcon,
|
||||||
|
GameIcon,
|
||||||
|
GaugeIcon,
|
||||||
|
AstralRinthLogo,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
|
LanguagesIcon,
|
||||||
|
PaintbrushIcon,
|
||||||
|
ReportIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
ShieldIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { TabbedModal } from '@modrinth/ui'
|
import {
|
||||||
import { computed, ref, watch } from 'vue'
|
commonMessages,
|
||||||
import { useVIntl, defineMessage } from '@vintl/vintl'
|
defineMessage,
|
||||||
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
|
defineMessages,
|
||||||
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
|
ProgressBar,
|
||||||
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
|
TabbedModal,
|
||||||
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
|
useVIntl,
|
||||||
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
|
} from '@modrinth/ui'
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { version as getOsVersion, platform as getOsPlatform } from '@tauri-apps/plugin-os'
|
import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os'
|
||||||
import { useTheming } from '@/store/state'
|
import { computed, ref, watch } from 'vue'
|
||||||
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
|
||||||
|
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
|
||||||
|
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
|
||||||
|
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
|
||||||
|
import LanguageSettings from '@/components/ui/settings/LanguageSettings.vue'
|
||||||
|
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
|
||||||
|
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
|
||||||
import { get, set } from '@/helpers/settings.ts'
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
// [AR] Imports
|
|
||||||
|
// This code is modified by AstralRinth
|
||||||
import { installState, getRemote, updateState } from '@/helpers/update.js'
|
import { installState, getRemote, updateState } from '@/helpers/update.js'
|
||||||
|
|
||||||
const updateModalView = ref(null)
|
const updateModalView = ref(null)
|
||||||
@@ -42,6 +51,8 @@ const initDownload = async () => {
|
|||||||
updateRequestFailView.value.show()
|
updateRequestFailView.value.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||||
|
import { useTheming } from '@/store/state'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
@@ -63,6 +74,15 @@ const tabs = [
|
|||||||
icon: PaintbrushIcon,
|
icon: PaintbrushIcon,
|
||||||
content: AppearanceSettings,
|
content: AppearanceSettings,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: defineMessage({
|
||||||
|
id: 'app.settings.tabs.language',
|
||||||
|
defaultMessage: 'Language',
|
||||||
|
}),
|
||||||
|
icon: LanguagesIcon,
|
||||||
|
content: LanguageSettings,
|
||||||
|
badge: commonMessages.beta,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: defineMessage({
|
name: defineMessage({
|
||||||
id: 'app.settings.tabs.privacy',
|
id: 'app.settings.tabs.privacy',
|
||||||
@@ -116,6 +136,8 @@ const isOpen = computed(() => modal.value?.isOpen)
|
|||||||
|
|
||||||
defineExpose({ show, isOpen })
|
defineExpose({ show, isOpen })
|
||||||
|
|
||||||
|
const { progress, version: downloadingVersion } = injectAppUpdateDownloadProgress()
|
||||||
|
|
||||||
const version = await getVersion()
|
const version = await getVersion()
|
||||||
const osPlatform = getOsPlatform()
|
const osPlatform = getOsPlatform()
|
||||||
const osVersion = getOsVersion()
|
const osVersion = getOsVersion()
|
||||||
@@ -141,6 +163,13 @@ function devModeCount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
downloading: {
|
||||||
|
id: 'app.settings.downloading',
|
||||||
|
defaultMessage: 'Downloading v{version}',
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="modal">
|
<ModalWrapper ref="modal">
|
||||||
@@ -153,20 +182,32 @@ function devModeCount() {
|
|||||||
<TabbedModal :tabs="tabs.filter((t) => !t.developerOnly || themeStore.devMode)">
|
<TabbedModal :tabs="tabs.filter((t) => !t.developerOnly || themeStore.devMode)">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="mt-auto text-secondary text-sm">
|
<div class="mt-auto text-secondary text-sm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<template v-if="progress > 0 && progress < 1">
|
||||||
|
<p class="m-0 mb-2">
|
||||||
|
{{ formatMessage(messages.downloading, { version: downloadingVersion }) }}
|
||||||
|
</p>
|
||||||
|
<ProgressBar :progress="progress" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
|
<p v-if="themeStore.devMode" class="text-brand font-semibold m-0 mb-2">
|
||||||
{{ formatMessage(developerModeEnabled) }}
|
{{ formatMessage(developerModeEnabled) }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
||||||
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
|
:class="{
|
||||||
@click="devModeCount">
|
'text-brand': themeStore.devMode,
|
||||||
|
'text-secondary': !themeStore.devMode,
|
||||||
|
}"
|
||||||
|
@click="devModeCount"
|
||||||
|
>
|
||||||
<AstralRinthLogo class="w-6 h-6" />
|
<AstralRinthLogo class="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<p class="m-0">AstralRinth App {{ version }}</p>
|
<p class="m-0">AstralRinth App {{ version }}</p>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
<span v-if="osPlatform === 'macos'">MacOS</span>
|
<span v-if="osPlatform === 'macos'">macOS</span>
|
||||||
<span v-else class="capitalize">{{ osPlatform }}</span>
|
<span v-else class="capitalize">{{ osPlatform }}</span>
|
||||||
{{ osVersion }}
|
{{ osVersion }}
|
||||||
</p>
|
</p>
|
||||||
@@ -187,28 +228,34 @@ function devModeCount() {
|
|||||||
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
|
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<p>The new version of the AstralRinth launcher is available.</p>
|
<strong>The new version of the AstralRinth launcher is available!</strong>
|
||||||
<p>Your version is outdated. We recommend that you update to the latest version.</p>
|
<p>Your version is outdated. We recommend that you update to the latest version.</p>
|
||||||
<p><strong>⚠️ Warning ⚠️</strong></p>
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<p><strong>⚠️ Please, read this notice before initialize update process</strong></p>
|
||||||
<p>
|
<p>
|
||||||
Before updating, make sure that you have saved all running instances and made a backup copy of the instances
|
Before updating, make sure that you have saved and closed all running instances and made a backup copy of the launcher data such as
|
||||||
that are valuable to you. Remember that the authors of the product are not responsible for the breakdown of
|
<code>%appdata%\Roaming\AstralRinthApp</code> on Windows or <code>~/Library/Application Support/AstralRinthApp</code> on macOS.
|
||||||
your files, so you should always make copies of them and keep them in a safe place.
|
Remember that the authors of the product are not responsible for the breakdown of
|
||||||
|
your files, so you should always make back up copies of them and keep them in a safe place.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-secondary space-y-1">
|
<div class="text-sm text-secondary space-y-1">
|
||||||
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank"
|
|
||||||
rel="noopener noreferrer"><strong>Source:</strong> Git Astralium</a>
|
|
||||||
<p>
|
<p>
|
||||||
<strong>Version on remote server:</strong>
|
<strong>☁️ Latest release tag:</strong>
|
||||||
<span id="releaseData" class="neon-text"></span>
|
<span id="releaseTag" class="neon-text"></span>
|
||||||
</p>
|
<br/>
|
||||||
<p>
|
<strong>☁️ Latest release title:</strong>
|
||||||
<strong>Version on local device:</strong>
|
<span id="releaseTitle" class="neon-text"></span>
|
||||||
|
<br/>
|
||||||
|
<strong>💾 Installed & Running version:</strong>
|
||||||
<span class="neon-text">v{{ version }}</span>
|
<span class="neon-text">v{{ version }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank"
|
||||||
|
rel="noopener noreferrer">
|
||||||
|
Checkout our git repository
|
||||||
|
</a>
|
||||||
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
|
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
|
||||||
<Button class="bordered" @click="updateModalView.hide()">Cancel</Button>
|
<Button class="bordered" @click="updateModalView.hide()">Cancel</Button>
|
||||||
<Button class="bordered" @click="initDownload()">Download file</Button>
|
<Button class="bordered" @click="initDownload()">Download file</Button>
|
||||||
@@ -247,4 +294,11 @@ function devModeCount() {
|
|||||||
@import '../../../../../../packages/assets/styles/neon-icon.scss';
|
@import '../../../../../../packages/assets/styles/neon-icon.scss';
|
||||||
@import '../../../../../../packages/assets/styles/neon-button.scss';
|
@import '../../../../../../packages/assets/styles/neon-button.scss';
|
||||||
@import '../../../../../../packages/assets/styles/neon-text.scss';
|
@import '../../../../../../packages/assets/styles/neon-text.scss';
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: linear-gradient(90deg, #005eff, #00cfff);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LogInIcon, SpinnerIcon } from '@modrinth/assets'
|
import { LogInIcon, SpinnerIcon } from '@modrinth/assets'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ConfirmModal } from '@modrinth/ui'
|
import { ConfirmModal } from '@modrinth/ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useTheming } from '@/store/theme.js'
|
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||||
|
import { useTheming } from '@/store/theme.ts'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
@@ -52,10 +53,11 @@ const modal = ref(null)
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: () => {
|
show: () => {
|
||||||
|
// hide_ads_window()
|
||||||
modal.value.show()
|
modal.value.show()
|
||||||
},
|
},
|
||||||
hide: () => {
|
hide: () => {
|
||||||
// onModalHide()
|
onModalHide()
|
||||||
modal.value.hide()
|
modal.value.hide()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ChevronRightIcon } from '@modrinth/assets'
|
import { ChevronRightIcon } from '@modrinth/assets'
|
||||||
import { Avatar } from '@modrinth/ui'
|
import { Avatar } from '@modrinth/ui'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
import type { GameInstance } from '@/helpers/types'
|
import type { GameInstance } from '@/helpers/types'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
CodeIcon,
|
||||||
CoffeeIcon,
|
CoffeeIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
WrenchIcon,
|
|
||||||
MonitorIcon,
|
MonitorIcon,
|
||||||
CodeIcon,
|
WrenchIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
|
import { Avatar, defineMessage, TabbedModal, type TabbedModalTab, useVIntl } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
||||||
|
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
|
||||||
import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue'
|
import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue'
|
||||||
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
|
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
|
||||||
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
|
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
|
||||||
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
|
||||||
import type { InstanceSettingsTabProps } from '../../../helpers/types'
|
import type { InstanceSettingsTabProps } from '../../../helpers/types'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTemplateRef } from 'vue'
|
|
||||||
import { NewModal as Modal } from '@modrinth/ui'
|
import { NewModal as Modal } from '@modrinth/ui'
|
||||||
// import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
import { useTemplateRef } from 'vue'
|
||||||
import { useTheming } from '@/store/theme.js'
|
|
||||||
|
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||||
|
import { useTheming } from '@/store/theme.ts'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
@@ -11,6 +12,10 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
hideHeader: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
closable: {
|
closable: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
@@ -18,7 +23,7 @@ const props = defineProps({
|
|||||||
onHide: {
|
onHide: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default() {
|
default() {
|
||||||
return () => { }
|
return () => {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// showAdOnClose: {
|
// showAdOnClose: {
|
||||||
@@ -40,15 +45,22 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function onModalHide() {
|
function onModalHide() {
|
||||||
// if (props.showAdOnClose) {
|
// if (props.showAdOnClose) {
|
||||||
// show_ads_window()
|
// show_ads_window()
|
||||||
// }
|
// }
|
||||||
props.onHide?.()
|
props.onHide?.()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
|
<Modal
|
||||||
|
ref="modal"
|
||||||
|
:header="header"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
:closable="closable"
|
||||||
|
:hide-header="hideHeader"
|
||||||
|
@hide="onModalHide"
|
||||||
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<slot name="title" />
|
<slot name="title" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ShareModal } from '@modrinth/ui'
|
import { ShareModal } from '@modrinth/ui'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { useTheming } from '@/store/theme.js'
|
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||||
|
import { useTheming } from '@/store/theme.ts'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const modal = ref(null)
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: (passedContent) => {
|
show: (passedContent) => {
|
||||||
|
// hide_ads_window()
|
||||||
modal.value.show(passedContent)
|
modal.value.show(passedContent)
|
||||||
},
|
},
|
||||||
hide: () => {
|
hide: () => {
|
||||||
@@ -40,9 +42,21 @@ defineExpose({
|
|||||||
modal.value.hide()
|
modal.value.hide()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// function onModalHide() {
|
||||||
|
// show_ads_window()
|
||||||
|
// }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ShareModal ref="modal" :header="header" :share-title="shareTitle" :share-text="shareText" :link="link"
|
<ShareModal
|
||||||
:open-in-new-tab="openInNewTab" :on-hide="onModalHide" :noblur="!themeStore.advancedRendering" />
|
ref="modal"
|
||||||
|
:header="header"
|
||||||
|
:share-title="shareTitle"
|
||||||
|
:share-text="shareText"
|
||||||
|
:link="link"
|
||||||
|
:open-in-new-tab="openInNewTab"
|
||||||
|
:on-hide="onModalHide"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TeleportDropdownMenu, ThemeSelector, Toggle } from '@modrinth/ui'
|
import { Combobox, ThemeSelector, Toggle } from '@modrinth/ui'
|
||||||
import { useTheming } from '@/store/state'
|
|
||||||
import { get, set } from '@/helpers/settings.ts'
|
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
import { getOS } from '@/helpers/utils'
|
import { getOS } from '@/helpers/utils'
|
||||||
|
import { useTheming } from '@/store/state'
|
||||||
import type { ColorTheme } from '@/store/theme.ts'
|
import type { ColorTheme } from '@/store/theme.ts'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
@@ -49,7 +50,7 @@ watch(
|
|||||||
:model-value="themeStore.advancedRendering"
|
:model-value="themeStore.advancedRendering"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(e) => {
|
(e) => {
|
||||||
themeStore.advancedRendering = e
|
themeStore.advancedRendering = !!e
|
||||||
settings.advanced_rendering = themeStore.advancedRendering
|
settings.advanced_rendering = themeStore.advancedRendering
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -85,12 +86,13 @@ watch(
|
|||||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Default landing page</h2>
|
<h2 class="m-0 text-lg font-extrabold text-contrast">Default landing page</h2>
|
||||||
<p class="m-0 mt-1">Change the page to which the launcher opens on.</p>
|
<p class="m-0 mt-1">Change the page to which the launcher opens on.</p>
|
||||||
</div>
|
</div>
|
||||||
<TeleportDropdownMenu
|
<Combobox
|
||||||
id="opening-page"
|
id="opening-page"
|
||||||
v-model="settings.default_page"
|
v-model="settings.default_page"
|
||||||
name="Opening page dropdown"
|
name="Opening page dropdown"
|
||||||
class="w-40"
|
class="w-40"
|
||||||
:options="['Home', 'Library']"
|
:options="['Home', 'Library'].map((v) => ({ value: v, label: v }))"
|
||||||
|
:display-value="settings.default_page ?? 'Select an option'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ watch(
|
|||||||
:model-value="settings.toggle_sidebar"
|
:model-value="settings.toggle_sidebar"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(e) => {
|
(e) => {
|
||||||
settings.toggle_sidebar = e
|
settings.toggle_sidebar = !!e
|
||||||
themeStore.toggleSidebar = settings.toggle_sidebar
|
themeStore.toggleSidebar = settings.toggle_sidebar
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { get, set } from '@/helpers/settings.ts'
|
import { injectNotificationManager, Slider, Toggle } from '@modrinth/ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { Slider, Toggle } from '@modrinth/ui'
|
|
||||||
import useMemorySlider from '@/composables/useMemorySlider'
|
import useMemorySlider from '@/composables/useMemorySlider'
|
||||||
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const fetchSettings = await get()
|
const fetchSettings = await get()
|
||||||
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
|
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
|
||||||
@@ -10,7 +13,10 @@ fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).jo
|
|||||||
|
|
||||||
const settings = ref(fetchSettings)
|
const settings = ref(fetchSettings)
|
||||||
|
|
||||||
const { maxMemory, snapPoints } = await useMemorySlider()
|
const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
|
||||||
|
maxMemory: number
|
||||||
|
snapPoints: number[]
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
settings,
|
settings,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toggle } from '@modrinth/ui'
|
import { Toggle } from '@modrinth/ui'
|
||||||
import { useTheming } from '@/store/state'
|
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
|
||||||
|
import { useTheming } from '@/store/state'
|
||||||
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
|
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { injectNotificationManager } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { get_java_versions, set_java_version } from '@/helpers/jre'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||||
|
import { get_java_versions, set_java_version } from '@/helpers/jre'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const javaVersions = ref(await get_java_versions().catch(handleError))
|
const javaVersions = ref(await get_java_versions().catch(handleError))
|
||||||
async function updateJavaVersion(version) {
|
async function updateJavaVersion(version) {
|
||||||
@@ -18,7 +21,7 @@ async function updateJavaVersion(version) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div v-for="(javaVersion, index) in [21, 17, 8]" :key="`java-${javaVersion}`">
|
<div v-for="(javaVersion, index) in [25, 21, 17, 8]" :key="`java-${javaVersion}`">
|
||||||
<h2 class="m-0 text-lg font-extrabold text-contrast" :class="{ 'mt-4': index !== 0 }">
|
<h2 class="m-0 text-lg font-extrabold text-contrast" :class="{ 'mt-4': index !== 0 }">
|
||||||
Java {{ javaVersion }} location
|
Java {{ javaVersion }} location
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Admonition,
|
||||||
|
AutoLink,
|
||||||
|
IntlFormatted,
|
||||||
|
LanguageSelector,
|
||||||
|
languageSelectorMessages,
|
||||||
|
LOCALES,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
|
import i18n from '@/i18n.config'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
const platform = formatMessage(languageSelectorMessages.platformApp)
|
||||||
|
|
||||||
|
const settings = ref(await get())
|
||||||
|
|
||||||
|
watch(
|
||||||
|
settings,
|
||||||
|
async () => {
|
||||||
|
await set(settings.value)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
const $isChanging = ref(false)
|
||||||
|
|
||||||
|
async function onLocaleChange(newLocale: string) {
|
||||||
|
if (settings.value.locale === newLocale) return
|
||||||
|
|
||||||
|
$isChanging.value = true
|
||||||
|
try {
|
||||||
|
i18n.global.locale.value = newLocale
|
||||||
|
settings.value.locale = newLocale
|
||||||
|
} finally {
|
||||||
|
$isChanging.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h2 class="m-0 text-lg font-extrabold text-contrast">Language</h2>
|
||||||
|
|
||||||
|
<Admonition type="warning" class="mt-2 mb-4">
|
||||||
|
{{ formatMessage(languageSelectorMessages.languageWarning, { platform }) }}
|
||||||
|
</Admonition>
|
||||||
|
|
||||||
|
<p class="m-0 mb-4">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="languageSelectorMessages.languagesDescription"
|
||||||
|
:values="{ platform }"
|
||||||
|
>
|
||||||
|
<template #~crowdin-link="{ children }">
|
||||||
|
<AutoLink to="https://translate.modrinth.com">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</AutoLink>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<LanguageSelector
|
||||||
|
:current-locale="settings.locale"
|
||||||
|
:locales="LOCALES"
|
||||||
|
:on-locale-change="onLocaleChange"
|
||||||
|
:is-changing="$isChanging"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { get, set } from '@/helpers/settings.ts'
|
|
||||||
import { Toggle } from '@modrinth/ui'
|
import { Toggle } from '@modrinth/ui'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
|
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
|
||||||
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
const settings = ref(await get())
|
const settings = ref(await get())
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ watch(
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
|
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
|
||||||
<p class="m-0 text-sm">
|
<p class="m-0 text-sm">
|
||||||
(Hard disabled by AR) • Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
||||||
option, you opt out and ads will no longer be shown based on your interests.
|
option, you opt out and ads will no longer be shown based on your interests.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +39,7 @@ watch(
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
|
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
|
||||||
<p class="m-0 text-sm">
|
<p class="m-0 text-sm">
|
||||||
(Hard disabled by AR) • Modrinth collects anonymized analytics and usage data to improve our user experience and
|
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||||
customize your experience. By disabling this option, you opt out and your data will no
|
customize your experience. By disabling this option, you opt out and your data will no
|
||||||
longer be collected.
|
longer be collected.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Button, Slider } from '@modrinth/ui'
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import { get, set } from '@/helpers/settings.ts'
|
|
||||||
import { purge_cache_types } from '@/helpers/cache.js'
|
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
import { Button, injectNotificationManager, Slider } from '@modrinth/ui'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||||
|
import { purge_cache_types } from '@/helpers/cache.js'
|
||||||
|
import { get, set } from '@/helpers/settings.ts'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const settings = ref(await get())
|
const settings = ref(await get())
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -100,37 +100,41 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, useTemplateRef } from 'vue'
|
|
||||||
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
|
|
||||||
import {
|
import {
|
||||||
SkinPreviewRenderer,
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
SaveIcon,
|
||||||
|
SpinnerIcon,
|
||||||
|
UploadIcon,
|
||||||
|
XIcon,
|
||||||
|
} from '@modrinth/assets'
|
||||||
|
import {
|
||||||
Button,
|
Button,
|
||||||
RadioButtons,
|
ButtonStyled,
|
||||||
CapeButton,
|
CapeButton,
|
||||||
CapeLikeTextButton,
|
CapeLikeTextButton,
|
||||||
ButtonStyled,
|
injectNotificationManager,
|
||||||
|
RadioButtons,
|
||||||
|
SkinPreviewRenderer,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
import { computed, ref, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
|
||||||
|
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
|
||||||
import {
|
import {
|
||||||
add_and_equip_custom_skin,
|
add_and_equip_custom_skin,
|
||||||
remove_custom_skin,
|
|
||||||
unequip_skin,
|
|
||||||
type Skin,
|
|
||||||
type Cape,
|
type Cape,
|
||||||
type SkinModel,
|
|
||||||
get_normalized_skin_texture,
|
|
||||||
determineModelType,
|
determineModelType,
|
||||||
|
get_normalized_skin_texture,
|
||||||
|
remove_custom_skin,
|
||||||
|
type Skin,
|
||||||
|
type SkinModel,
|
||||||
|
type SkinTextureUrl,
|
||||||
|
unequip_skin,
|
||||||
} from '@/helpers/skins.ts'
|
} from '@/helpers/skins.ts'
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import {
|
const { handleError } = injectNotificationManager()
|
||||||
UploadIcon,
|
|
||||||
CheckIcon,
|
|
||||||
SaveIcon,
|
|
||||||
XIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
SpinnerIcon,
|
|
||||||
} from '@modrinth/assets'
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
|
|
||||||
|
|
||||||
const modal = useTemplateRef('modal')
|
const modal = useTemplateRef('modal')
|
||||||
const selectCapeModal = useTemplateRef('selectCapeModal')
|
const selectCapeModal = useTemplateRef('selectCapeModal')
|
||||||
@@ -139,7 +143,7 @@ const currentSkin = ref<Skin | null>(null)
|
|||||||
const shouldRestoreModal = ref(false)
|
const shouldRestoreModal = ref(false)
|
||||||
const isSaving = ref(false)
|
const isSaving = ref(false)
|
||||||
|
|
||||||
const uploadedTextureUrl = ref<string | null>(null)
|
const uploadedTextureUrl = ref<SkinTextureUrl | null>(null)
|
||||||
const previewSkin = ref<string>('')
|
const previewSkin = ref<string>('')
|
||||||
|
|
||||||
const variant = ref<SkinModel>('CLASSIC')
|
const variant = ref<SkinModel>('CLASSIC')
|
||||||
@@ -185,7 +189,7 @@ function getSortedCapeExcluding(excludeId: string): Cape | undefined {
|
|||||||
|
|
||||||
async function loadPreviewSkin() {
|
async function loadPreviewSkin() {
|
||||||
if (uploadedTextureUrl.value) {
|
if (uploadedTextureUrl.value) {
|
||||||
previewSkin.value = uploadedTextureUrl.value
|
previewSkin.value = uploadedTextureUrl.value.normalized
|
||||||
} else if (currentSkin.value) {
|
} else if (currentSkin.value) {
|
||||||
try {
|
try {
|
||||||
previewSkin.value = await get_normalized_skin_texture(currentSkin.value)
|
previewSkin.value = await get_normalized_skin_texture(currentSkin.value)
|
||||||
@@ -250,11 +254,11 @@ async function show(e: MouseEvent, skin?: Skin) {
|
|||||||
modal.value?.show(e)
|
modal.value?.show(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showNew(e: MouseEvent, skinTextureUrl: string) {
|
async function showNew(e: MouseEvent, skinTextureUrl: SkinTextureUrl) {
|
||||||
mode.value = 'new'
|
mode.value = 'new'
|
||||||
currentSkin.value = null
|
currentSkin.value = null
|
||||||
uploadedTextureUrl.value = skinTextureUrl
|
uploadedTextureUrl.value = skinTextureUrl
|
||||||
variant.value = await determineModelType(skinTextureUrl)
|
variant.value = await determineModelType(skinTextureUrl.original)
|
||||||
selectedCape.value = undefined
|
selectedCape.value = undefined
|
||||||
visibleCapeList.value = []
|
visibleCapeList.value = []
|
||||||
initVisibleCapeList()
|
initVisibleCapeList()
|
||||||
@@ -264,7 +268,7 @@ async function showNew(e: MouseEvent, skinTextureUrl: string) {
|
|||||||
modal.value?.show(e)
|
modal.value?.show(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restoreWithNewTexture(skinTextureUrl: string) {
|
async function restoreWithNewTexture(skinTextureUrl: SkinTextureUrl) {
|
||||||
uploadedTextureUrl.value = skinTextureUrl
|
uploadedTextureUrl.value = skinTextureUrl
|
||||||
await loadPreviewSkin()
|
await loadPreviewSkin()
|
||||||
|
|
||||||
@@ -358,7 +362,7 @@ async function save() {
|
|||||||
let textureUrl: string
|
let textureUrl: string
|
||||||
|
|
||||||
if (uploadedTextureUrl.value) {
|
if (uploadedTextureUrl.value) {
|
||||||
textureUrl = uploadedTextureUrl.value
|
textureUrl = uploadedTextureUrl.value.original
|
||||||
} else {
|
} else {
|
||||||
textureUrl = currentSkin.value!.texture
|
textureUrl = currentSkin.value!.texture
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTemplateRef, ref, computed } from 'vue'
|
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||||
import type { Cape, SkinModel } from '@/helpers/skins.ts'
|
|
||||||
import {
|
import {
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
ScrollablePanel,
|
|
||||||
CapeButton,
|
CapeButton,
|
||||||
CapeLikeTextButton,
|
CapeLikeTextButton,
|
||||||
|
ScrollablePanel,
|
||||||
SkinPreviewRenderer,
|
SkinPreviewRenderer,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
import { computed, ref, useTemplateRef } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import type { Cape, SkinModel } from '@/helpers/skins.ts'
|
||||||
|
|
||||||
const modal = useTemplateRef('modal')
|
const modal = useTemplateRef('modal')
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onBeforeUnmount, watch } from 'vue'
|
|
||||||
import { UploadIcon } from '@modrinth/assets'
|
import { UploadIcon } from '@modrinth/assets'
|
||||||
import { useNotifications } from '@/store/state'
|
import { injectNotificationManager } from '@modrinth/ui'
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||||
|
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
import { get_dragged_skin_data } from '@/helpers/skins'
|
import { get_dragged_skin_data } from '@/helpers/skins'
|
||||||
|
|
||||||
const notifications = useNotifications()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
|
||||||
const modal = ref()
|
const modal = ref()
|
||||||
const fileInput = ref<HTMLInputElement>()
|
const fileInput = ref<HTMLInputElement>()
|
||||||
@@ -99,7 +100,7 @@ async function setupDragDropListener() {
|
|||||||
const data = await get_dragged_skin_data(filePath)
|
const data = await get_dragged_skin_data(filePath)
|
||||||
await processData(data.buffer)
|
await processData(data.buffer)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.addNotification({
|
addNotification({
|
||||||
title: 'Error processing file',
|
title: 'Error processing file',
|
||||||
text: error instanceof Error ? error.message : 'Failed to read the dropped file.',
|
text: error instanceof Error ? error.message : 'Failed to read the dropped file.',
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Dayjs } from 'dayjs'
|
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import {
|
import {
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
@@ -13,25 +11,29 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
SmartClickable,
|
SmartClickable,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, nextTick, ref, onMounted, onUnmounted } from 'vue'
|
|
||||||
import { showProfileInFolder } from '@/helpers/utils'
|
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import type { GameInstance } from '@/helpers/types'
|
|
||||||
import { get_project } from '@/helpers/cache'
|
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import { kill, run } from '@/helpers/profile'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { handleSevereError } from '@/store/error'
|
import type { Dayjs } from 'dayjs'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import dayjs from 'dayjs'
|
||||||
import { get_by_profile_path } from '@/helpers/process'
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { handleError } from '@/store/notifications'
|
import { useRouter } from 'vue-router'
|
||||||
import { process_listener } from '@/helpers/events'
|
|
||||||
|
|
||||||
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { get_project } from '@/helpers/cache'
|
||||||
|
import { process_listener } from '@/helpers/events'
|
||||||
|
import { get_by_profile_path } from '@/helpers/process'
|
||||||
|
import { kill, run } from '@/helpers/profile'
|
||||||
|
import type { GameInstance } from '@/helpers/types'
|
||||||
|
import { showProfileInFolder } from '@/helpers/utils'
|
||||||
|
import { handleSevereError } from '@/store/error'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
@@ -128,7 +130,7 @@ onUnmounted(() => {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised rounded-xl smart-clickable:highlight-on-hover"
|
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised card-shadow rounded-xl smart-clickable:highlight-on-hover"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
:src="instanceIcon ? convertFileSrc(instanceIcon) : undefined"
|
:src="instanceIcon ? convertFileSrc(instanceIcon) : undefined"
|
||||||
|
|||||||
@@ -1,29 +1,38 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { LoaderCircleIcon } from '@modrinth/assets'
|
||||||
type ServerWorld,
|
import type { GameVersion } from '@modrinth/ui'
|
||||||
type ServerData,
|
import { GAME_MODES, HeadingLink, injectNotificationManager } from '@modrinth/ui'
|
||||||
type WorldWithProfile,
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
get_recent_worlds,
|
|
||||||
getWorldIdentifier,
|
|
||||||
get_profile_protocol_version,
|
|
||||||
refreshServerData,
|
|
||||||
start_join_server,
|
|
||||||
start_join_singleplayer_world,
|
|
||||||
} from '@/helpers/worlds.ts'
|
|
||||||
import { HeadingLink, GAME_MODES } from '@modrinth/ui'
|
|
||||||
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
|
||||||
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
|
||||||
import { watch, onMounted, onUnmounted, ref, computed } from 'vue'
|
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useTheming } from '@/store/theme.ts'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import { kill, run } from '@/helpers/profile'
|
|
||||||
import { handleError } from '@/store/notifications'
|
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
|
||||||
|
import WorldItem from '@/components/ui/world/WorldItem.vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { process_listener, profile_listener } from '@/helpers/events'
|
import { process_listener, profile_listener } from '@/helpers/events'
|
||||||
import { get_all } from '@/helpers/process'
|
import { get_all } from '@/helpers/process'
|
||||||
|
import { kill, run } from '@/helpers/profile'
|
||||||
|
import { get_game_versions } from '@/helpers/tags'
|
||||||
import type { GameInstance } from '@/helpers/types'
|
import type { GameInstance } from '@/helpers/types'
|
||||||
|
import {
|
||||||
|
get_profile_protocol_version,
|
||||||
|
get_recent_worlds,
|
||||||
|
getWorldIdentifier,
|
||||||
|
hasServerQuickPlaySupport,
|
||||||
|
hasWorldQuickPlaySupport,
|
||||||
|
type ProtocolVersion,
|
||||||
|
refreshServerData,
|
||||||
|
type ServerData,
|
||||||
|
type ServerWorld,
|
||||||
|
start_join_server,
|
||||||
|
start_join_singleplayer_world,
|
||||||
|
type WorldWithProfile,
|
||||||
|
} from '@/helpers/worlds.ts'
|
||||||
import { handleSevereError } from '@/store/error'
|
import { handleSevereError } from '@/store/error'
|
||||||
|
import { useTheming } from '@/store/theme.ts'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
recentInstances: GameInstance[]
|
recentInstances: GameInstance[]
|
||||||
@@ -32,12 +41,19 @@ const props = defineProps<{
|
|||||||
const theme = useTheming()
|
const theme = useTheming()
|
||||||
|
|
||||||
const jumpBackInItems = ref<JumpBackInItem[]>([])
|
const jumpBackInItems = ref<JumpBackInItem[]>([])
|
||||||
|
const loading = ref(true)
|
||||||
const serverData = ref<Record<string, ServerData>>({})
|
const serverData = ref<Record<string, ServerData>>({})
|
||||||
const protocolVersions = ref<Record<string, number | null>>({})
|
const protocolVersions = ref<Record<string, ProtocolVersion | null>>({})
|
||||||
|
const gameVersions = ref<GameVersion[]>(await get_game_versions().catch(() => []))
|
||||||
|
|
||||||
const MIN_JUMP_BACK_IN = 3
|
const MIN_JUMP_BACK_IN = 3
|
||||||
const MAX_JUMP_BACK_IN = 6
|
const MAX_JUMP_BACK_IN = 6
|
||||||
const TWO_WEEKS_AGO = dayjs().subtract(14, 'day')
|
const TWO_WEEKS_AGO = dayjs().subtract(14, 'day')
|
||||||
|
const MAX_LINUX_POPULATES = 3
|
||||||
|
|
||||||
|
// Track populate calls on Linux to prevent server ping spam
|
||||||
|
const isLinux = platform() === 'linux'
|
||||||
|
const linuxPopulateCount = ref(0)
|
||||||
|
|
||||||
type BaseJumpBackInItem = {
|
type BaseJumpBackInItem = {
|
||||||
last_played: Dayjs
|
last_played: Dayjs
|
||||||
@@ -63,11 +79,19 @@ watch([() => props.recentInstances, () => showWorlds.value], async () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
await populateJumpBackIn().catch(() => {
|
populateJumpBackIn()
|
||||||
|
.catch(() => {
|
||||||
console.error('Failed to populate jump back in')
|
console.error('Failed to populate jump back in')
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
async function populateJumpBackIn() {
|
async function populateJumpBackIn() {
|
||||||
|
// On Linux, limit automatic populates to prevent server ping spam
|
||||||
|
if (isLinux && linuxPopulateCount.value >= MAX_LINUX_POPULATES) return
|
||||||
|
if (isLinux) linuxPopulateCount.value++
|
||||||
|
|
||||||
console.info('Repopulating jump back in...')
|
console.info('Repopulating jump back in...')
|
||||||
|
|
||||||
const worldItems: WorldJumpBackInItem[] = []
|
const worldItems: WorldJumpBackInItem[] = []
|
||||||
@@ -121,11 +145,8 @@ async function populateJumpBackIn() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// fetch each server's data
|
servers.forEach(({ instancePath, address }) =>
|
||||||
Promise.all(
|
|
||||||
servers.map(({ instancePath, address }) =>
|
|
||||||
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address),
|
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +171,8 @@ async function populateJumpBackIn() {
|
|||||||
.slice(0, MAX_JUMP_BACK_IN)
|
.slice(0, MAX_JUMP_BACK_IN)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshServer(address: string, instancePath: string) {
|
function refreshServer(address: string, instancePath: string) {
|
||||||
await refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
|
refreshServerData(serverData.value[address], protocolVersions.value[instancePath], address)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinWorld(world: WorldWithProfile) {
|
async function joinWorld(world: WorldWithProfile) {
|
||||||
@@ -219,6 +240,7 @@ const checkProcesses = async () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkProcesses()
|
checkProcesses()
|
||||||
|
linuxPopulateCount.value = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -228,7 +250,15 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="jumpBackInItems.length > 0" class="flex flex-col gap-2">
|
<div v-if="loading" class="flex flex-col gap-2">
|
||||||
|
<span class="flex mt-1 mb-3 leading-none items-center gap-1 text-primary text-lg font-bold">
|
||||||
|
Jump back in
|
||||||
|
</span>
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<LoaderCircleIcon class="mx-auto size-8 animate-spin text-contrast" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="jumpBackInItems.length > 0" class="flex flex-col gap-2">
|
||||||
<HeadingLink v-if="theme.getFeatureFlag('worlds_tab')" to="/worlds" class="mt-1">
|
<HeadingLink v-if="theme.getFeatureFlag('worlds_tab')" to="/worlds" class="mt-1">
|
||||||
Jump back in
|
Jump back in
|
||||||
</HeadingLink>
|
</HeadingLink>
|
||||||
@@ -255,7 +285,14 @@ onUnmounted(() => {
|
|||||||
? serverData[item.world.address].refreshing && !serverData[item.world.address].status
|
? serverData[item.world.address].refreshing && !serverData[item.world.address].status
|
||||||
: undefined
|
: undefined
|
||||||
"
|
"
|
||||||
supports-quick-play
|
:supports-server-quick-play="
|
||||||
|
item.world.type === 'server' &&
|
||||||
|
hasServerQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||||
|
"
|
||||||
|
:supports-world-quick-play="
|
||||||
|
item.world.type === 'singleplayer' &&
|
||||||
|
hasWorldQuickPlaySupport(gameVersions, item.instance.game_version || '')
|
||||||
|
"
|
||||||
:server-status="
|
:server-status="
|
||||||
item.world.type === 'server' ? serverData[item.world.address].status : undefined
|
item.world.type === 'server' ? serverData[item.world.address].status : undefined
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import type { ServerStatus, ServerWorld, SingleplayerWorld, World } from '@/helpers/worlds.ts'
|
|
||||||
import { set_world_display_status, getWorldIdentifier } from '@/helpers/worlds.ts'
|
|
||||||
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
|
||||||
import {
|
import {
|
||||||
useRelativeTime,
|
|
||||||
Avatar,
|
|
||||||
ButtonStyled,
|
|
||||||
commonMessages,
|
|
||||||
OverflowMenu,
|
|
||||||
SmartClickable,
|
|
||||||
} from '@modrinth/ui'
|
|
||||||
import {
|
|
||||||
IssuesIcon,
|
|
||||||
EyeIcon,
|
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
|
EyeIcon,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
|
IssuesIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
NoSignalIcon,
|
NoSignalIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
@@ -29,14 +17,34 @@ import {
|
|||||||
UserIcon,
|
UserIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import type { MessageDescriptor } from '@vintl/vintl'
|
import type { MessageDescriptor } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
OverflowMenu,
|
||||||
|
SmartClickable,
|
||||||
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Tooltip } from 'floating-vue'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { copyToClipboard } from '@/helpers/utils'
|
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Tooltip } from 'floating-vue'
|
|
||||||
|
import { copyToClipboard } from '@/helpers/utils'
|
||||||
|
import type {
|
||||||
|
ProtocolVersion,
|
||||||
|
ServerStatus,
|
||||||
|
ServerWorld,
|
||||||
|
SingleplayerWorld,
|
||||||
|
World,
|
||||||
|
} from '@/helpers/worlds.ts'
|
||||||
|
import { getWorldIdentifier, set_world_display_status } from '@/helpers/worlds.ts'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
@@ -54,8 +62,9 @@ const props = withDefaults(
|
|||||||
playingInstance?: boolean
|
playingInstance?: boolean
|
||||||
playingWorld?: boolean
|
playingWorld?: boolean
|
||||||
startingInstance?: boolean
|
startingInstance?: boolean
|
||||||
supportsQuickPlay?: boolean
|
supportsServerQuickPlay?: boolean
|
||||||
currentProtocol?: number | null
|
supportsWorldQuickPlay?: boolean
|
||||||
|
currentProtocol?: ProtocolVersion | null
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
|
|
||||||
// Server only
|
// Server only
|
||||||
@@ -78,7 +87,8 @@ const props = withDefaults(
|
|||||||
playingInstance: false,
|
playingInstance: false,
|
||||||
playingWorld: false,
|
playingWorld: false,
|
||||||
startingInstance: false,
|
startingInstance: false,
|
||||||
supportsQuickPlay: false,
|
supportsServerQuickPlay: true,
|
||||||
|
supportsWorldQuickPlay: false,
|
||||||
currentProtocol: null,
|
currentProtocol: null,
|
||||||
|
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
@@ -102,7 +112,8 @@ const serverIncompatible = computed(
|
|||||||
!!props.serverStatus &&
|
!!props.serverStatus &&
|
||||||
!!props.serverStatus.version?.protocol &&
|
!!props.serverStatus.version?.protocol &&
|
||||||
!!props.currentProtocol &&
|
!!props.currentProtocol &&
|
||||||
props.serverStatus.version.protocol !== props.currentProtocol,
|
(props.serverStatus.version.protocol !== props.currentProtocol.version ||
|
||||||
|
props.serverStatus.version.legacy !== props.currentProtocol.legacy),
|
||||||
)
|
)
|
||||||
|
|
||||||
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)
|
const locked = computed(() => props.world.type === 'singleplayer' && props.world.locked)
|
||||||
@@ -120,9 +131,13 @@ const messages = defineMessages({
|
|||||||
id: 'instance.worlds.a_minecraft_server',
|
id: 'instance.worlds.a_minecraft_server',
|
||||||
defaultMessage: 'A Minecraft Server',
|
defaultMessage: 'A Minecraft Server',
|
||||||
},
|
},
|
||||||
noQuickPlay: {
|
noServerQuickPlay: {
|
||||||
id: 'instance.worlds.no_quick_play',
|
id: 'instance.worlds.no_server_quick_play',
|
||||||
defaultMessage: 'You can only jump straight into worlds on Minecraft 1.20+',
|
defaultMessage: 'You can only jump straight into servers on Minecraft Alpha 1.0.5+',
|
||||||
|
},
|
||||||
|
noSingleplayerQuickPlay: {
|
||||||
|
id: 'instance.worlds.no_singleplayer_quick_play',
|
||||||
|
defaultMessage: 'You can only jump straight into singleplayer worlds on Minecraft 1.20+',
|
||||||
},
|
},
|
||||||
gameAlreadyOpen: {
|
gameAlreadyOpen: {
|
||||||
id: 'instance.worlds.game_already_open',
|
id: 'instance.worlds.game_already_open',
|
||||||
@@ -144,10 +159,6 @@ const messages = defineMessages({
|
|||||||
id: 'instance.worlds.view_instance',
|
id: 'instance.worlds.view_instance',
|
||||||
defaultMessage: 'View instance',
|
defaultMessage: 'View instance',
|
||||||
},
|
},
|
||||||
playAnyway: {
|
|
||||||
id: 'instance.worlds.play_anyway',
|
|
||||||
defaultMessage: 'Play anyway',
|
|
||||||
},
|
|
||||||
playInstance: {
|
playInstance: {
|
||||||
id: 'instance.worlds.play_instance',
|
id: 'instance.worlds.play_instance',
|
||||||
defaultMessage: 'Play instance',
|
defaultMessage: 'Play instance',
|
||||||
@@ -171,14 +182,16 @@ const messages = defineMessages({
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised smart-clickable:highlight-on-hover rounded-xl"
|
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised card-shadow smart-clickable:highlight-on-hover rounded-xl"
|
||||||
:class="{
|
:class="{
|
||||||
'world-item-highlighted': highlighted,
|
'world-item-highlighted': highlighted,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
:src="
|
:src="
|
||||||
world.type === 'server' && serverStatus ? serverStatus.favicon ?? world.icon : world.icon
|
world.type === 'server' && serverStatus
|
||||||
|
? (serverStatus.favicon ?? world.icon)
|
||||||
|
: world.icon
|
||||||
"
|
"
|
||||||
size="48px"
|
size="48px"
|
||||||
/>
|
/>
|
||||||
@@ -226,7 +239,8 @@ const messages = defineMessages({
|
|||||||
/>
|
/>
|
||||||
<Tooltip :disabled="!hasPlayersTooltip">
|
<Tooltip :disabled="!hasPlayersTooltip">
|
||||||
<span :class="{ 'cursor-help': hasPlayersTooltip }">
|
<span :class="{ 'cursor-help': hasPlayersTooltip }">
|
||||||
{{ formatNumber(serverStatus.players?.online, false) }} online
|
{{ formatNumber(serverStatus.players?.online, false) }}
|
||||||
|
online
|
||||||
</span>
|
</span>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
@@ -239,7 +253,8 @@ const messages = defineMessages({
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" /> Offline
|
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
|
||||||
|
Offline
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,7 +264,9 @@ const messages = defineMessages({
|
|||||||
world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null
|
world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null
|
||||||
"
|
"
|
||||||
class="w-fit shrink-0"
|
class="w-fit shrink-0"
|
||||||
:class="{ 'cursor-help smart-clickable:allow-pointer-events': world.last_played }"
|
:class="{
|
||||||
|
'cursor-help smart-clickable:allow-pointer-events': world.last_played,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<template v-if="world.last_played">
|
<template v-if="world.last_played">
|
||||||
{{
|
{{
|
||||||
@@ -322,17 +339,28 @@ const messages = defineMessages({
|
|||||||
<ButtonStyled v-else>
|
<ButtonStyled v-else>
|
||||||
<button
|
<button
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
!serverStatus
|
world.type === 'server'
|
||||||
|
? !supportsServerQuickPlay
|
||||||
|
? formatMessage(messages.noServerQuickPlay)
|
||||||
|
: playingOtherWorld
|
||||||
|
? formatMessage(messages.gameAlreadyOpen)
|
||||||
|
: !serverStatus
|
||||||
? formatMessage(messages.noContact)
|
? formatMessage(messages.noContact)
|
||||||
: serverIncompatible
|
: serverIncompatible
|
||||||
? formatMessage(messages.incompatibleServer)
|
? formatMessage(messages.incompatibleServer)
|
||||||
: !supportsQuickPlay
|
: null
|
||||||
? formatMessage(messages.noQuickPlay)
|
: !supportsWorldQuickPlay
|
||||||
|
? formatMessage(messages.noSingleplayerQuickPlay)
|
||||||
: playingOtherWorld || locked
|
: playingOtherWorld || locked
|
||||||
? formatMessage(messages.gameAlreadyOpen)
|
? formatMessage(messages.gameAlreadyOpen)
|
||||||
: null
|
: null
|
||||||
"
|
"
|
||||||
:disabled="!supportsQuickPlay || playingOtherWorld || startingInstance"
|
:disabled="
|
||||||
|
playingOtherWorld ||
|
||||||
|
startingInstance ||
|
||||||
|
(world.type == 'server' && !supportsServerQuickPlay) ||
|
||||||
|
(world.type == 'singleplayer' && !supportsWorldQuickPlay)
|
||||||
|
"
|
||||||
@click="emit('play')"
|
@click="emit('play')"
|
||||||
>
|
>
|
||||||
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
|
<SpinnerIcon v-if="startingInstance && playingWorld" class="animate-spin" />
|
||||||
@@ -349,11 +377,6 @@ const messages = defineMessages({
|
|||||||
disabled: playingInstance,
|
disabled: playingInstance,
|
||||||
action: () => emit('play-instance'),
|
action: () => emit('play-instance'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'play-anyway',
|
|
||||||
shown: serverIncompatible && !playingInstance && supportsQuickPlay,
|
|
||||||
action: () => emit('play'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'open-instance',
|
id: 'open-instance',
|
||||||
shown: !!instancePath,
|
shown: !!instancePath,
|
||||||
@@ -419,26 +442,25 @@ const messages = defineMessages({
|
|||||||
<PlayIcon aria-hidden="true" />
|
<PlayIcon aria-hidden="true" />
|
||||||
{{ formatMessage(messages.playInstance) }}
|
{{ formatMessage(messages.playInstance) }}
|
||||||
</template>
|
</template>
|
||||||
<template #play-anyway>
|
|
||||||
<PlayIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(messages.playAnyway) }}
|
|
||||||
</template>
|
|
||||||
<template #open-instance>
|
<template #open-instance>
|
||||||
<EyeIcon aria-hidden="true" />
|
<EyeIcon aria-hidden="true" />
|
||||||
{{ formatMessage(messages.viewInstance) }}
|
{{ formatMessage(messages.viewInstance) }}
|
||||||
</template>
|
</template>
|
||||||
<template #edit>
|
<template #edit>
|
||||||
<EditIcon aria-hidden="true" /> {{ formatMessage(commonMessages.editButton) }}
|
<EditIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonMessages.editButton) }}
|
||||||
</template>
|
</template>
|
||||||
<template #open-folder>
|
<template #open-folder>
|
||||||
<FolderOpenIcon aria-hidden="true" />
|
<FolderOpenIcon aria-hidden="true" />
|
||||||
{{ formatMessage(commonMessages.openFolderButton) }}
|
{{ formatMessage(commonMessages.openFolderButton) }}
|
||||||
</template>
|
</template>
|
||||||
<template #copy-address>
|
<template #copy-address>
|
||||||
<ClipboardCopyIcon aria-hidden="true" /> {{ formatMessage(messages.copyAddress) }}
|
<ClipboardCopyIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(messages.copyAddress) }}
|
||||||
</template>
|
</template>
|
||||||
<template #refresh>
|
<template #refresh>
|
||||||
<UpdatedIcon aria-hidden="true" /> {{ formatMessage(commonMessages.refreshButton) }}
|
<UpdatedIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonMessages.refreshButton) }}
|
||||||
</template>
|
</template>
|
||||||
<template #dont-show-on-home>
|
<template #dont-show-on-home>
|
||||||
<XIcon aria-hidden="true" />
|
<XIcon aria-hidden="true" />
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import type { GameInstance } from '@/helpers/types'
|
|
||||||
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
|
||||||
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
|
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
|
||||||
|
|
||||||
|
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
||||||
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||||
|
import type { GameInstance } from '@/helpers/types'
|
||||||
|
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SaveIcon, XIcon } from '@modrinth/assets'
|
import { SaveIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessage,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||||
|
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
||||||
import type { GameInstance } from '@/helpers/types'
|
import type { GameInstance } from '@/helpers/types'
|
||||||
import {
|
import {
|
||||||
type ServerPackStatus,
|
type DisplayStatus,
|
||||||
edit_server_in_profile,
|
edit_server_in_profile,
|
||||||
|
type ServerPackStatus,
|
||||||
type ServerWorld,
|
type ServerWorld,
|
||||||
set_world_display_status,
|
set_world_display_status,
|
||||||
type DisplayStatus,
|
|
||||||
} from '@/helpers/worlds.ts'
|
} from '@/helpers/worlds.ts'
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
|
|
||||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChevronRightIcon, SaveIcon, XIcon, UndoIcon } from '@modrinth/assets'
|
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled, commonMessages } from '@modrinth/ui'
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
||||||
import type { GameInstance } from '@/helpers/types'
|
import type { GameInstance } from '@/helpers/types'
|
||||||
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
|
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
|
||||||
import { set_world_display_status, rename_world, reset_world_icon } from '@/helpers/worlds.ts'
|
import { rename_world, reset_world_icon, set_world_display_status } from '@/helpers/worlds.ts'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { handleError } from '@/store/notifications'
|
|
||||||
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
|
|
||||||
|
|
||||||
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
import { Checkbox, defineMessage, useVIntl } from '@modrinth/ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { Checkbox } from '@modrinth/ui'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const value = defineModel<boolean>({ required: true })
|
const value = defineModel<boolean>({ required: true })
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TeleportDropdownMenu } from '@modrinth/ui'
|
import { Combobox, defineMessages, type MessageDescriptor, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
import type { ServerPackStatus } from '@/helpers/worlds.ts'
|
import type { ServerPackStatus } from '@/helpers/worlds.ts'
|
||||||
import { type MessageDescriptor, defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
@@ -73,12 +73,19 @@ defineExpose({ resourcePackOptions })
|
|||||||
{{ formatMessage(messages.resourcePack) }}
|
{{ formatMessage(messages.resourcePack) }}
|
||||||
</h2>
|
</h2>
|
||||||
<div>
|
<div>
|
||||||
<TeleportDropdownMenu
|
<Combobox
|
||||||
v-model="resourcePack"
|
v-model="resourcePack"
|
||||||
:options="resourcePackOptions"
|
:options="
|
||||||
|
resourcePackOptions.map((o) => ({
|
||||||
|
value: o,
|
||||||
|
label: formatMessage(resourcePackOptionMessages[o]),
|
||||||
|
}))
|
||||||
|
"
|
||||||
name="Server resource pack"
|
name="Server resource pack"
|
||||||
:display-name="
|
:display-value="
|
||||||
(option: ServerPackStatus) => formatMessage(resourcePackOptionMessages[option])
|
resourcePack
|
||||||
|
? formatMessage(resourcePackOptionMessages[resourcePack])
|
||||||
|
: 'Select an option'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
import cssContent from '@/assets/stylesheets/macFix.css?inline'
|
import cssContent from '@/assets/stylesheets/macFix.css?inline'
|
||||||
|
|
||||||
export async function useCheckDisableMouseover() {
|
export async function useCheckDisableMouseover() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { get_max_memory } from '@/helpers/jre.js'
|
import { get_max_memory } from '@/helpers/jre.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
|
||||||
|
|
||||||
export default async function () {
|
export default async function () {
|
||||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
const maxMemory = ref(Math.floor((await get_max_memory()) / 1024))
|
||||||
|
|
||||||
const snapPoints = computed(() => {
|
const snapPoints = computed(() => {
|
||||||
let points = []
|
let points = []
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { posthog } from 'posthog-js'
|
// import { posthog } from 'posthog-js'
|
||||||
|
|
||||||
export const initAnalytics = () => {
|
export const initAnalytics = () => {
|
||||||
posthog.init('phc_9Iqi6lFs9sr5BSqh9RRNRSJ0mATS9PSgirDiX3iOYJ', {
|
// posthog.init('phc_9Iqi6lFs9sr5BSqh9RRNRSJ0mATS9PSgirDiX3iOYJ', {
|
||||||
persistence: 'localStorage',
|
// persistence: 'localStorage',
|
||||||
api_host: 'https://posthog.modrinth.com',
|
// api_host: 'https://posthog.modrinth.com',
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const debugAnalytics = () => {
|
export const debugAnalytics = () => {
|
||||||
posthog.debug()
|
// posthog.debug()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const optOutAnalytics = () => {
|
export const optOutAnalytics = () => {
|
||||||
posthog.opt_out_capturing()
|
// posthog.opt_out_capturing()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const optInAnalytics = () => {
|
export const optInAnalytics = () => {
|
||||||
posthog.opt_in_capturing()
|
// posthog.opt_in_capturing()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const trackEvent = (eventName, properties) => {
|
export const trackEvent = (eventName, properties) => {
|
||||||
posthog.capture(eventName, properties)
|
// posthog.capture(eventName, properties)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user