Compare commits
1794 Commits
0.0.47-nig
...
v1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2608472d8 | ||
|
|
34aae0c87a | ||
|
|
69bd35a579 | ||
|
|
3e6c0b4159 | ||
|
|
a33ec8b11c | ||
|
|
dd03ca38a1 | ||
|
|
1cac5e451a | ||
|
|
010d4592e4 | ||
|
|
b0d0f8ef7d | ||
|
|
967d311ee4 | ||
|
|
d5b777d720 | ||
|
|
2ab2666ad0 | ||
|
|
4971f2be78 | ||
|
|
b0a49d6626 | ||
|
|
d830854eaa | ||
|
|
68820d5a86 | ||
|
|
8b079bc40b | ||
|
|
b0dd9b845f | ||
|
|
b3d0670e1d | ||
|
|
e4734924f3 | ||
|
|
6ca419dd5b | ||
|
|
fc3f2171ee | ||
|
|
3d95a84739 | ||
|
|
15412911a9 | ||
|
|
9dc7a790cc | ||
|
|
d0b67c37f6 | ||
|
|
f6aa7d1fe3 | ||
|
|
2dc53cfbd7 | ||
|
|
db43e18b16 | ||
|
|
1a54527428 | ||
|
|
73026911da | ||
|
|
86558cd07e | ||
|
|
218d2788e8 | ||
|
|
0a37c2a854 | ||
|
|
7eda890473 | ||
|
|
2431bd09af | ||
|
|
7b21a38e17 | ||
|
|
bf51f48961 | ||
|
|
92868201a3 | ||
|
|
c01c40fe45 | ||
|
|
39ed8af840 | ||
|
|
82adf5c138 | ||
|
|
e0d12acf61 | ||
|
|
955743aecd | ||
|
|
4fb815a184 | ||
|
|
13ffe45dc6 | ||
|
|
5b699a2c3c | ||
|
|
bd32677e9e | ||
|
|
a98db63bec | ||
|
|
2430b8c448 | ||
|
|
e3486042a5 | ||
|
|
d79a4e5499 | ||
|
|
068b6179e5 | ||
|
|
3b90ac3c77 | ||
|
|
42b8eabb3a | ||
|
|
11dbe849cf | ||
|
|
ac6cbb9dd3 | ||
|
|
5c1f659437 | ||
|
|
155202dab9 | ||
|
|
71512bdad4 | ||
|
|
88d4a3d298 | ||
|
|
2563a31d15 | ||
|
|
9c91d730b4 | ||
|
|
3aaeebae96 | ||
|
|
63a5f4441f | ||
|
|
47a171b1a4 | ||
|
|
a3b3b3dd93 | ||
|
|
98f128ae07 | ||
|
|
7fd20c3d9a | ||
|
|
d7b45e4ce7 | ||
|
|
2e1eb2c879 | ||
|
|
21142a53df | ||
|
|
1f847439a7 | ||
|
|
236266cd7a | ||
|
|
611f6dbffc | ||
|
|
1d4bb9b534 | ||
|
|
110a2144fa | ||
|
|
c87889ba41 | ||
|
|
88ea5c83b5 | ||
|
|
e962ac7ca7 | ||
|
|
7f4ee0784e | ||
|
|
f222632dfb | ||
|
|
8d65952e40 | ||
|
|
6b99ab9e43 | ||
|
|
646136e6ea | ||
|
|
3f2a337497 | ||
|
|
2798e81f49 | ||
|
|
d2983253bb | ||
|
|
fa0ebd20c3 | ||
|
|
dc8482b884 | ||
|
|
64fe4b682a | ||
|
|
636bea080a | ||
|
|
24a6efa2c7 | ||
|
|
07e5d0e983 | ||
|
|
f67879a847 | ||
|
|
ce7f35bada | ||
|
|
76145bc354 | ||
|
|
677b84b13c | ||
|
|
f336408951 | ||
|
|
1abd176616 | ||
|
|
18d09f4184 | ||
|
|
756f274155 | ||
|
|
25340075d5 | ||
|
|
a9c0fe5ff8 | ||
|
|
348b2e17f0 | ||
|
|
a6837f4555 | ||
|
|
7e4b4991fd | ||
|
|
f774d33966 | ||
|
|
f61ca2d647 | ||
|
|
3c1d3013ce | ||
|
|
685129fede | ||
|
|
c32d334aab | ||
|
|
577b758c99 | ||
|
|
8b9797595a | ||
|
|
0e6c55c56e | ||
|
|
e65269ad29 | ||
|
|
e00bbeadde | ||
|
|
1193192e81 | ||
|
|
95286bae1c | ||
|
|
654cda736d | ||
|
|
a2b27090db | ||
|
|
278b0205fc | ||
|
|
90f8cd8c65 | ||
|
|
05b8eda84a | ||
|
|
2ea3a2a8e4 | ||
|
|
e58e84da35 | ||
|
|
8a35cf002f | ||
|
|
c98e73883b | ||
|
|
f3e83193d6 | ||
|
|
c0604bc989 | ||
|
|
9af383af88 | ||
|
|
9c295f6012 | ||
|
|
57d83439f3 | ||
|
|
7320a982f6 | ||
|
|
42ffea41ab | ||
|
|
1c1350d84b | ||
|
|
8be5dc20a9 | ||
|
|
0429cb060c | ||
|
|
13aabda72a | ||
|
|
73281d1316 | ||
|
|
fdfdf66fa3 | ||
|
|
d43a0dd862 | ||
|
|
cbf1e2709a | ||
|
|
fb75cd1add | ||
|
|
d34b34b5bd | ||
|
|
1df5b4e8ba | ||
|
|
ff9ee24894 | ||
|
|
b4b70f7efe | ||
|
|
20ca84e5ab | ||
|
|
90b259b609 | ||
|
|
f463aea3ef | ||
|
|
064f674b14 | ||
|
|
d94a011413 | ||
|
|
8549a5caae | ||
|
|
3ea63a5ccf | ||
|
|
23ddb2c6e1 | ||
|
|
c524950e35 | ||
|
|
4434b6ca2a | ||
|
|
e29a0eff17 | ||
|
|
bac33c66d9 | ||
|
|
3c790134ee | ||
|
|
c1196042bf | ||
|
|
8649a74612 | ||
|
|
7ab58c6495 | ||
|
|
7fb46441f4 | ||
|
|
f72ee54ff4 | ||
|
|
8578f6e048 | ||
|
|
bfdcdc30d6 | ||
|
|
2def7a8861 | ||
|
|
166fd9e6b7 | ||
|
|
8efb3d7554 | ||
|
|
02b4a70dd2 | ||
|
|
cb039df33e | ||
|
|
da648190db | ||
|
|
2b26304d92 | ||
|
|
33f97fe21f | ||
|
|
5fe20906d9 | ||
|
|
2f319e661b | ||
|
|
35e7d796ab | ||
|
|
3d93cc300a | ||
|
|
e7085f7602 | ||
|
|
4f3a6cba45 | ||
|
|
05dea088df | ||
|
|
edf5dd133b | ||
|
|
917f8476b9 | ||
|
|
b2f10e31f7 | ||
|
|
22fffcc422 | ||
|
|
c93f47744b | ||
|
|
c6af5b7d87 | ||
|
|
182c3ba393 | ||
|
|
4e249601fe | ||
|
|
2f966d4fa9 | ||
|
|
2ef950ae26 | ||
|
|
219705d2f3 | ||
|
|
aa6730b9da | ||
|
|
b519537b69 | ||
|
|
1a0e70636a | ||
|
|
da8337a681 | ||
|
|
ac8e2981a9 | ||
|
|
444595406b | ||
|
|
b31e450c4b | ||
|
|
77e94595d8 | ||
|
|
7fd02976cb | ||
|
|
8d3613201e | ||
|
|
7320f0ca7f | ||
|
|
f57f2dd585 | ||
|
|
3ac6d79644 | ||
|
|
a076d50cb3 | ||
|
|
072e3519bb | ||
|
|
6fe9fbb6cc | ||
|
|
f7e5cd1a05 | ||
|
|
519f4fc74c | ||
|
|
66e5395a60 | ||
|
|
e54619da46 | ||
|
|
9b8a76349f | ||
|
|
5bf017395c | ||
|
|
eb45075414 | ||
|
|
c4e0717317 | ||
|
|
67d2e52fbc | ||
|
|
d5b4e1ca14 | ||
|
|
72181e7ef7 | ||
|
|
d4d3a2b36c | ||
|
|
1dcaddb4c4 | ||
|
|
0c891218b2 | ||
|
|
8fd378852f | ||
|
|
aecb5572cd | ||
|
|
6ffee3466f | ||
|
|
9b7f50aebb | ||
|
|
5375c3c1fa | ||
|
|
c2cd281efe | ||
|
|
6252cbbefe | ||
|
|
3b2199127b | ||
|
|
d903a2bc69 | ||
|
|
c53b3967cd | ||
|
|
b811432e7e | ||
|
|
23c83fed8b | ||
|
|
fe6d95786b | ||
|
|
c3963e7fe2 | ||
|
|
9d8a3c5132 | ||
|
|
af734b7814 | ||
|
|
4be340c723 | ||
|
|
5816f5dffb | ||
|
|
44f62e8f54 | ||
|
|
6bdfab6cba | ||
|
|
e1d33691b4 | ||
|
|
47a508e8a9 | ||
|
|
d983d7a5f5 | ||
|
|
226ba475aa | ||
|
|
8bbde9e7e3 | ||
|
|
9ac4b56ef7 | ||
|
|
cc5ffdf644 | ||
|
|
6d02c53eb3 | ||
|
|
6e6adb8caa | ||
|
|
b0392cd3e6 | ||
|
|
43a54be20e | ||
|
|
dc7f8e990b | ||
|
|
ff3d6da461 | ||
|
|
30600a5b9c | ||
|
|
54eada1a66 | ||
|
|
c74c0456de | ||
|
|
e3db8a1fdf | ||
|
|
b40dcf85bd | ||
|
|
9c76286660 | ||
|
|
a099d8bdf3 | ||
|
|
b2f680da4e | ||
|
|
18ecbe0f44 | ||
|
|
bef38ce45f | ||
|
|
70a90d9a92 | ||
|
|
7ce4b0058e | ||
|
|
a105871a98 | ||
|
|
d5be354a84 | ||
|
|
3da92de951 | ||
|
|
5caeaccec8 | ||
|
|
eef40e39d1 | ||
|
|
6c21afaa57 | ||
|
|
ca09e0e3f7 | ||
|
|
fb9ef65346 | ||
|
|
344f62c275 | ||
|
|
d28b9bf5a8 | ||
|
|
b23385ba28 | ||
|
|
586d108d32 | ||
|
|
df395a613f | ||
|
|
45e3f23dc9 | ||
|
|
113769643a | ||
|
|
ecf12175f4 | ||
|
|
0a0f350a1d | ||
|
|
ff7abf4c41 | ||
|
|
7fc328b60a | ||
|
|
5adbcc0aff | ||
|
|
6bdfc69668 | ||
|
|
547ad4d0f8 | ||
|
|
122a6776a2 | ||
|
|
b840f6709b | ||
|
|
ab6f6cd990 | ||
|
|
97a56bbbfd | ||
|
|
a2b127cf4f | ||
|
|
a4524a7182 | ||
|
|
0133f85800 | ||
|
|
9745e65eed | ||
|
|
43e5f93a37 | ||
|
|
07607a489c | ||
|
|
46e82eae3c | ||
|
|
3f0d1e345b | ||
|
|
d2a2bad111 | ||
|
|
7427609bee | ||
|
|
7431e58f70 | ||
|
|
519f67071d | ||
|
|
56fe553870 | ||
|
|
c69570147a | ||
|
|
d031602789 | ||
|
|
b26a5a566b | ||
|
|
93d28db11a | ||
|
|
5a06ae52a9 | ||
|
|
81743b22e9 | ||
|
|
2ef4b7241d | ||
|
|
9d19c99abe | ||
|
|
db7be134c7 | ||
|
|
99e1d130f0 | ||
|
|
f33e1a0ae0 | ||
|
|
5fb5e52e54 | ||
|
|
16112d4f1d | ||
|
|
7d214ba214 | ||
|
|
2a6f3745c3 | ||
|
|
151535139f | ||
|
|
dfee2cd71f | ||
|
|
b178d7fd8d | ||
|
|
43582f0528 | ||
|
|
202df27f88 | ||
|
|
93518aa1f4 | ||
|
|
1421d55a56 | ||
|
|
762f0ef445 | ||
|
|
deb923fcfd | ||
|
|
9210582a4b | ||
|
|
24c83f9596 | ||
|
|
7a18b4499d | ||
|
|
c6bd6e862d | ||
|
|
df2f05a914 | ||
|
|
d79af91a8d | ||
|
|
f43eaa4577 | ||
|
|
f6cd0edbc2 | ||
|
|
709419bc2d | ||
|
|
95460ca98f | ||
|
|
277f103535 | ||
|
|
34ed1af242 | ||
|
|
4328ca3280 | ||
|
|
1075256f21 | ||
|
|
69ec608212 | ||
|
|
3a4651adcd | ||
|
|
1801c25fdc | ||
|
|
dc7e7b2c20 | ||
|
|
f654e12546 | ||
|
|
3c1dbeab15 | ||
|
|
f6f17dd328 | ||
|
|
2b6c283cd1 | ||
|
|
a374f7a44b | ||
|
|
10933046d6 | ||
|
|
d7abb343a7 | ||
|
|
aeab453e8b | ||
|
|
c5dbe778ea | ||
|
|
b17d784d80 | ||
|
|
146e4bac0f | ||
|
|
8f7f422b28 | ||
|
|
2d35d41779 | ||
|
|
3114a9b8c6 | ||
|
|
830b10878e | ||
|
|
7363bb307d | ||
|
|
2e0c567158 | ||
|
|
1a82fa27a9 | ||
|
|
146f171cbc | ||
|
|
8a04bb014a | ||
|
|
5290597b2b | ||
|
|
661af76ed6 | ||
|
|
18d95a1ccd | ||
|
|
0f87ed82e3 | ||
|
|
bb9bce67d8 | ||
|
|
208fb8cf73 | ||
|
|
ef8cb7e0ee | ||
|
|
52ef582be6 | ||
|
|
5dd06ba501 | ||
|
|
f6a5a03346 | ||
|
|
767d396171 | ||
|
|
da159b715c | ||
|
|
79fa5448a0 | ||
|
|
b5f41cdab9 | ||
|
|
99507c7c6d | ||
|
|
d7db45d700 | ||
|
|
111304481f | ||
|
|
64827ec0fc | ||
|
|
c5b40f64e4 | ||
|
|
401d182fc6 | ||
|
|
3878103eaa | ||
|
|
5f6cd43b35 | ||
|
|
03b452e426 | ||
|
|
528960940c | ||
|
|
9063fbc13d | ||
|
|
d14998565b | ||
|
|
4a71e15fd5 | ||
|
|
94a0086fdd | ||
|
|
65cc3095e2 | ||
|
|
cd90f2745a | ||
|
|
69a0e60fee | ||
|
|
aad43c9f9c | ||
|
|
f033496d20 | ||
|
|
b6757434b7 | ||
|
|
3e9abda042 | ||
|
|
cf1e745c14 | ||
|
|
eb1745d179 | ||
|
|
2b88713315 | ||
|
|
ef435130ee | ||
|
|
17df365b2c | ||
|
|
852617975b | ||
|
|
90bed4413e | ||
|
|
ba59649946 | ||
|
|
e90e79ebf4 | ||
|
|
18c84ae310 | ||
|
|
f06031f753 | ||
|
|
1d605ebb94 | ||
|
|
e3145bbdd6 | ||
|
|
130129c9bd | ||
|
|
6d5a61fe04 | ||
|
|
ee4f923a2d | ||
|
|
500e469ee0 | ||
|
|
cf7af0ff34 | ||
|
|
e92c962c14 | ||
|
|
e37ca4a18e | ||
|
|
8efc1a96ea | ||
|
|
16de6ec208 | ||
|
|
7824ab907b | ||
|
|
574e885da3 | ||
|
|
ccd88a10ab | ||
|
|
3a69717a9d | ||
|
|
8b1fe940d2 | ||
|
|
7083e1a117 | ||
|
|
51af357c5a | ||
|
|
7ad4bc2ba5 | ||
|
|
6cfd0a91fc | ||
|
|
a70e9130f5 | ||
|
|
01cb0c4b90 | ||
|
|
4c3ed1d0da | ||
|
|
250d92d225 | ||
|
|
e16bb5a34f | ||
|
|
9ff07f9f2b | ||
|
|
7e2cc100e6 | ||
|
|
4820c38bce | ||
|
|
89e682df3d | ||
|
|
d25678d7b4 | ||
|
|
56e424c29b | ||
|
|
96fb815bd8 | ||
|
|
41636b09db | ||
|
|
8b2518a25d | ||
|
|
9db091a2d2 | ||
|
|
d220f369e9 | ||
|
|
73792e4294 | ||
|
|
8b9236ae43 | ||
|
|
877c23b9d7 | ||
|
|
aed5c20700 | ||
|
|
f6efd392b9 | ||
|
|
9197b3ca93 | ||
|
|
01a064f1ab | ||
|
|
af9cd81220 | ||
|
|
8203a70b60 | ||
|
|
f290e6e576 | ||
|
|
dbe3f3393c | ||
|
|
a87dec0d64 | ||
|
|
14bca470bb | ||
|
|
df3deee316 | ||
|
|
62a55a40c2 | ||
|
|
42a4a11c49 | ||
|
|
3ef7f40e37 | ||
|
|
82a0473287 | ||
|
|
41e746a70e | ||
|
|
e2d6e29d7b | ||
|
|
5445fbed6c | ||
|
|
e9e9e0a621 | ||
|
|
891dffce8d | ||
|
|
4ad66fa4dc | ||
|
|
5570aa79f7 | ||
|
|
288d336fb6 | ||
|
|
d7b7d617bd | ||
|
|
c0f7d11828 | ||
|
|
1bd4f4f423 | ||
|
|
ce130c1e15 | ||
|
|
e69a201e97 | ||
|
|
143b36ab66 | ||
|
|
90900446ee | ||
|
|
29d34fb52c | ||
|
|
241dbc8238 | ||
|
|
aea202bcf2 | ||
|
|
0bc4f14845 | ||
|
|
7539d75ddb | ||
|
|
9ea0dcb5a7 | ||
|
|
40707b7d23 | ||
|
|
d146ed9a7d | ||
|
|
a09329eb82 | ||
|
|
0bda8406c3 | ||
|
|
9489151bc1 | ||
|
|
80fc8417dc | ||
|
|
cb0f46c5db | ||
|
|
85b5910ea9 | ||
|
|
d5a1a26091 | ||
|
|
256b48a0ad | ||
|
|
bfb269b378 | ||
|
|
54bed879a5 | ||
|
|
4ded3d9752 | ||
|
|
f68bd54ccd | ||
|
|
2edad4efdb | ||
|
|
08af00da29 | ||
|
|
21e023e609 | ||
|
|
f6489d5ac0 | ||
|
|
256aac8faf | ||
|
|
a1be699ec5 | ||
|
|
d71af941b0 | ||
|
|
a1b56f6e0b | ||
|
|
d8022cc1a4 | ||
|
|
9ba7f5022e | ||
|
|
437ed2ee60 | ||
|
|
4d4335004f | ||
|
|
ce95f2165d | ||
|
|
931e611cf7 | ||
|
|
81ef029bf6 | ||
|
|
223de57b65 | ||
|
|
f1d1b81456 | ||
|
|
8aa829ab02 | ||
|
|
8fb443d41d | ||
|
|
edc62b3cfc | ||
|
|
644cdef95f | ||
|
|
dc54473669 | ||
|
|
332eacd2cc | ||
|
|
c26a670b05 | ||
|
|
1ccb8694d8 | ||
|
|
91db21067c | ||
|
|
834546e3ba | ||
|
|
6518fe6946 | ||
|
|
9b21193be2 | ||
|
|
99983b1eb1 | ||
|
|
712e2c9d02 | ||
|
|
f963e6aa03 | ||
|
|
d3c25e6b12 | ||
|
|
667abf2131 | ||
|
|
8a1e17ecd3 | ||
|
|
809004e16b | ||
|
|
e35df8a029 | ||
|
|
d9081563c2 | ||
|
|
29e757ba75 | ||
|
|
48724bae8b | ||
|
|
b1de430f0b | ||
|
|
6cd18ba7f7 | ||
|
|
07d167d961 | ||
|
|
dadedbe129 | ||
|
|
6d8948cdf1 | ||
|
|
27ca67b6f9 | ||
|
|
7e27bf0785 | ||
|
|
a4e8cbe9e1 | ||
|
|
3d58a15b10 | ||
|
|
5c80aec50b | ||
|
|
b52916e62c | ||
|
|
91ea0c9c8e | ||
|
|
2a80a10eec | ||
|
|
7b8fb02398 | ||
|
|
74aa319f41 | ||
|
|
0de4856247 | ||
|
|
da61b1a3e7 | ||
|
|
0632213c8b | ||
|
|
daa8de203b | ||
|
|
537558e3d5 | ||
|
|
8d23b262c7 | ||
|
|
04a178eda6 | ||
|
|
977f353f9c | ||
|
|
ae6ff97b62 | ||
|
|
fa45891af4 | ||
|
|
159b36607a | ||
|
|
a731687607 | ||
|
|
a77a74ba61 | ||
|
|
fc35381e1b | ||
|
|
6f837830ab | ||
|
|
8197361fb0 | ||
|
|
c7d12fbdf8 | ||
|
|
0cb3fd3134 | ||
|
|
77a791e5da | ||
|
|
42d94b43b1 | ||
|
|
cb6bf00236 | ||
|
|
4f6bd11a70 | ||
|
|
0f14326449 | ||
|
|
b421b4476b | ||
|
|
18554ec439 | ||
|
|
806e24b9f1 | ||
|
|
fa1d69f1f9 | ||
|
|
33aebc42b3 | ||
|
|
ffcb94674e | ||
|
|
c1323a6ba1 | ||
|
|
ea70da8fbf | ||
|
|
1bdb17f073 | ||
|
|
506031b5cb | ||
|
|
4a981900aa | ||
|
|
6fa052bfcf | ||
|
|
19ac32d328 | ||
|
|
44eab78935 | ||
|
|
a2413f3635 | ||
|
|
13b1d8fc34 | ||
|
|
8b90449970 | ||
|
|
038e76ed94 | ||
|
|
390c80c46c | ||
|
|
dffae008cd | ||
|
|
bcf9af71e2 | ||
|
|
5787d32c1a | ||
|
|
39b5032303 | ||
|
|
5f871b1945 | ||
|
|
346f1c991a | ||
|
|
b14a56bb6c | ||
|
|
f75d0acb1c | ||
|
|
b2d1fd916d | ||
|
|
b7f1e30708 | ||
|
|
88863d8d01 | ||
|
|
c275c54fca | ||
|
|
b97ace2c6e | ||
|
|
c78c4689f1 | ||
|
|
f39ccf7629 | ||
|
|
885dfb5bea | ||
|
|
8037273672 | ||
|
|
2e9c0656de | ||
|
|
e85d294d12 | ||
|
|
24bf6cffc3 | ||
|
|
16762d1a46 | ||
|
|
6a54005472 | ||
|
|
812d910212 | ||
|
|
b7c8fba464 | ||
|
|
89424f6466 | ||
|
|
0411505341 | ||
|
|
6938083463 | ||
|
|
054b286388 | ||
|
|
add1e02d2f | ||
|
|
fd44744029 | ||
|
|
6844e1b435 | ||
|
|
8140380673 | ||
|
|
66ad9e8856 | ||
|
|
da467ec8ee | ||
|
|
f0923c51e6 | ||
|
|
d1953e792a | ||
|
|
b3294eed68 | ||
|
|
37946c5aba | ||
|
|
ce80f65e9f | ||
|
|
4dce42b85f | ||
|
|
88702085bb | ||
|
|
2719522e07 | ||
|
|
dd573945ed | ||
|
|
9cffb19332 | ||
|
|
6631bb593c | ||
|
|
f5959925aa | ||
|
|
5b20cb316b | ||
|
|
8cb7ff8ed4 | ||
|
|
4bcf1f2d9e | ||
|
|
452b077822 | ||
|
|
f37a32ceab | ||
|
|
7182514a64 | ||
|
|
da60131051 | ||
|
|
aa9804d2df | ||
|
|
10c1b9bc29 | ||
|
|
698422a41e | ||
|
|
ac16f40303 | ||
|
|
e41bdecd5b | ||
|
|
c9a0a8d2b5 | ||
|
|
c308940c4b | ||
|
|
99770ccd2f | ||
|
|
2d4bb90acc | ||
|
|
e78d80b99d | ||
|
|
9f5cd6dc88 | ||
|
|
85c0b670da | ||
|
|
813ec7d294 | ||
|
|
d66eb239fa | ||
|
|
9fcc46b5d5 | ||
|
|
3ebf8a2061 | ||
|
|
de98085e84 | ||
|
|
2557b55817 | ||
|
|
128706e8a1 | ||
|
|
d854d8ae0b | ||
|
|
637191836a | ||
|
|
69ab9d96f7 | ||
|
|
073febe24a | ||
|
|
f01a4fcfac | ||
|
|
4f81a4e9b4 | ||
|
|
1a06033964 | ||
|
|
c5dad11e5e | ||
|
|
5ed89754b3 | ||
|
|
ee88be613c | ||
|
|
ebafb4c05e | ||
|
|
70466d0c94 | ||
|
|
340003c568 | ||
|
|
db5343fba3 | ||
|
|
8434842c65 | ||
|
|
2482881117 | ||
|
|
4afe2160e1 | ||
|
|
6225390b7f | ||
|
|
d5a9c98ff9 | ||
|
|
bed1b85319 | ||
|
|
8a728ad28a | ||
|
|
c42f17c96e | ||
|
|
054352356b | ||
|
|
2646f642b5 | ||
|
|
436bdde60a | ||
|
|
07210a23b7 | ||
|
|
da36ef4002 | ||
|
|
e3b06b110f | ||
|
|
792dce6843 | ||
|
|
0ec048ceba | ||
|
|
07631e9b02 | ||
|
|
4834363fb5 | ||
|
|
734dbfb761 | ||
|
|
37044dae01 | ||
|
|
e9ee31b22a | ||
|
|
cf0e3ce989 | ||
|
|
27664164fa | ||
|
|
b7360c426b | ||
|
|
0e176d5608 | ||
|
|
a947a81772 | ||
|
|
bbb4185fac | ||
|
|
e34f0587fe | ||
|
|
0e02dd660c | ||
|
|
2376e511ac | ||
|
|
56bf447cdb | ||
|
|
5ebb1718d6 | ||
|
|
9143b92932 | ||
|
|
fbff4a8cb1 | ||
|
|
b02371e4c3 | ||
|
|
e32a2bbe81 | ||
|
|
10ebd61519 | ||
|
|
7ac90891ca | ||
|
|
711c18a7f1 | ||
|
|
b3cb08316a | ||
|
|
42b8b9ce4a | ||
|
|
fc4834ebd6 | ||
|
|
24ab79a09a | ||
|
|
786a8832d3 | ||
|
|
4d25b6a43c | ||
|
|
29bb54d2cc | ||
|
|
cd075a3559 | ||
|
|
0455a83ef1 | ||
|
|
6c5791b1fe | ||
|
|
705b6cc03d | ||
|
|
b190e54285 | ||
|
|
478db15211 | ||
|
|
1542f3811d | ||
|
|
85dd8029af | ||
|
|
11beb6676e | ||
|
|
99d7fe5ca2 | ||
|
|
248c1ce189 | ||
|
|
bf972681d5 | ||
|
|
996632ac73 | ||
|
|
b28a1986c9 | ||
|
|
fb96e93184 | ||
|
|
ac4db91df4 | ||
|
|
408845878b | ||
|
|
7933d49bb2 | ||
|
|
6945032077 | ||
|
|
0af137ba8c | ||
|
|
de3d376063 | ||
|
|
36da872932 | ||
|
|
ff2ed50dea | ||
|
|
6bb2685e03 | ||
|
|
5eb0424ee7 | ||
|
|
80536ef4fb | ||
|
|
f3bebc6fa2 | ||
|
|
d0bb3cc75c | ||
|
|
98213cff67 | ||
|
|
c074de5876 | ||
|
|
906bdfa15e | ||
|
|
e844164cf6 | ||
|
|
1b47bfa2f1 | ||
|
|
33997c9a82 | ||
|
|
4713a44573 | ||
|
|
be7b0945e9 | ||
|
|
0f1bfc5a17 | ||
|
|
3bc9f1382c | ||
|
|
a08e93d975 | ||
|
|
91a120599f | ||
|
|
5d399b2497 | ||
|
|
0cab18b9b5 | ||
|
|
903da8732d | ||
|
|
6857ae5182 | ||
|
|
5bcbffcdf0 | ||
|
|
953083a0bf | ||
|
|
1810bbe2d5 | ||
|
|
f716ab0c1b | ||
|
|
e04e5596a3 | ||
|
|
f239d498ad | ||
|
|
7b768b5b5b | ||
|
|
60813274dc | ||
|
|
ad5bc4fc0d | ||
|
|
c1c6949175 | ||
|
|
26840700ee | ||
|
|
ec610de266 | ||
|
|
f4129ff5c2 | ||
|
|
b8cc6d9809 | ||
|
|
9384d1d96f | ||
|
|
854b6b76a0 | ||
|
|
bc836e973c | ||
|
|
bf9855abd1 | ||
|
|
4006438d93 | ||
|
|
cdfb86e021 | ||
|
|
81e0232712 | ||
|
|
e67b68aa20 | ||
|
|
a075de4711 | ||
|
|
6d782352f7 | ||
|
|
587993c957 | ||
|
|
aaca3e65ce | ||
|
|
ce53acdacf | ||
|
|
a449bac130 | ||
|
|
060bd32417 | ||
|
|
08a1595d3e | ||
|
|
b88c56b67a | ||
|
|
7deb395fd9 | ||
|
|
2ef104224b | ||
|
|
d84b2f3870 | ||
|
|
7e5467935b | ||
|
|
f7c1714cb8 | ||
|
|
12724cea56 | ||
|
|
2cea66cba5 | ||
|
|
ac01469eac | ||
|
|
cb4cc4e7d9 | ||
|
|
c648493a9e | ||
|
|
a36da6ebde | ||
|
|
4fd2fc7e00 | ||
|
|
39646732b6 | ||
|
|
8791507aca | ||
|
|
c173b2a230 | ||
|
|
05d2df623e | ||
|
|
e75a1fcd12 | ||
|
|
529165f2b5 | ||
|
|
9a5743a27e | ||
|
|
271ef9bf00 | ||
|
|
3a97f7645e | ||
|
|
a84bf9efef | ||
|
|
daa5129f65 | ||
|
|
ac375abf5e | ||
|
|
bae5a7c838 | ||
|
|
1bcb7210c6 | ||
|
|
c15dcf6b98 | ||
|
|
9ead7a413e | ||
|
|
07817b04fb | ||
|
|
e65d67f12e | ||
|
|
e39f013808 | ||
|
|
c34affc215 | ||
|
|
07b8cc04be | ||
|
|
78a396ce4b | ||
|
|
43997f2215 | ||
|
|
e9bac0a737 | ||
|
|
e7c69d2a6b | ||
|
|
8c3400ee41 | ||
|
|
3d79c939e9 | ||
|
|
27b8e6bd21 | ||
|
|
24ecaab570 | ||
|
|
74e39c437d | ||
|
|
c1c7788cd3 | ||
|
|
a2db465825 | ||
|
|
9605264b9a | ||
|
|
24e6808984 | ||
|
|
0d9c7a4cc2 | ||
|
|
3c6ecf1563 | ||
|
|
5c634d6ff9 | ||
|
|
6b51155bac | ||
|
|
1873ad8355 | ||
|
|
8087396188 | ||
|
|
5128db9f6c | ||
|
|
ef832461c0 | ||
|
|
85493cdfd8 | ||
|
|
e557f1361d | ||
|
|
9598ac5a50 | ||
|
|
5ee7add355 | ||
|
|
81c1e6e887 | ||
|
|
b7004b3866 | ||
|
|
5341326811 | ||
|
|
776c08e605 | ||
|
|
b7c5d18df1 | ||
|
|
39dc6c576a | ||
|
|
d9fc262003 | ||
|
|
9b7f2b3a79 | ||
|
|
1ad4ac2d63 | ||
|
|
9ca7949bf0 | ||
|
|
d2174364b2 | ||
|
|
91238c3a9c | ||
|
|
d32c8c999b | ||
|
|
6580b78485 | ||
|
|
0eec8dea05 | ||
|
|
8fa356242e | ||
|
|
a13611f601 | ||
|
|
e8307cdcd9 | ||
|
|
ca425cf949 | ||
|
|
9f57d393bf | ||
|
|
84c59e2c8b | ||
|
|
212f60db60 | ||
|
|
fbbdb056d7 | ||
|
|
e3c0fe5e78 | ||
|
|
a3afb1a2b8 | ||
|
|
ac94b6d917 | ||
|
|
8db9580ff5 | ||
|
|
489c00ebb0 | ||
|
|
d58fe84439 | ||
|
|
ffe3843747 | ||
|
|
b370af3c19 | ||
|
|
cdd3c81bdc | ||
|
|
eb72839e2b | ||
|
|
6457cedd9b | ||
|
|
be72ea0c98 | ||
|
|
bf5691bdb6 | ||
|
|
a6a8734599 | ||
|
|
d0aecc76ed | ||
|
|
3b80b8d7f1 | ||
|
|
c11bb3be59 | ||
|
|
7ee4c14fae | ||
|
|
17a0209c8c | ||
|
|
fc10c05731 | ||
|
|
9ef0d0edfb | ||
|
|
473f4a4295 | ||
|
|
94ba1f0b09 | ||
|
|
8903fa268a | ||
|
|
6ad980d471 | ||
|
|
9a6e9c4660 | ||
|
|
f7c842774b | ||
|
|
fde952fcd9 | ||
|
|
a71c2bd8ec | ||
|
|
7c4f887ef4 | ||
|
|
b32b0e970c | ||
|
|
836f789fc9 | ||
|
|
6b71e2f22a | ||
|
|
1fa83d3f8f | ||
|
|
247f8f132b | ||
|
|
3fcdf38d4a | ||
|
|
0626d1c466 | ||
|
|
f0da7c6300 | ||
|
|
2554478a38 | ||
|
|
d7f24759d8 | ||
|
|
df3bd6fbec | ||
|
|
11ab81a484 | ||
|
|
91e7a092c4 | ||
|
|
bca5130020 | ||
|
|
153029abdd | ||
|
|
03ed3f13f4 | ||
|
|
8c7fd78bd4 | ||
|
|
a813535e3f | ||
|
|
7cae5f8f10 | ||
|
|
070c19d784 | ||
|
|
0833cd8a9b | ||
|
|
64f3a2db58 | ||
|
|
ad8a425d30 | ||
|
|
40d5c8d79f | ||
|
|
c6e471323f | ||
|
|
60a0c8726e | ||
|
|
5772ff78e6 | ||
|
|
222ed29b6c | ||
|
|
23870523fb | ||
|
|
b1a1d36b66 | ||
|
|
2946c51774 | ||
|
|
8b72214780 | ||
|
|
0f701ad2d3 | ||
|
|
d686c48a0b | ||
|
|
80555f9c96 | ||
|
|
ec1155b1ba | ||
|
|
7377386ee2 | ||
|
|
f4bb1101bf | ||
|
|
e44188b830 | ||
|
|
c5d81afdf6 | ||
|
|
f6d55fec35 | ||
|
|
2ef8219f15 | ||
|
|
bf9197b3e4 | ||
|
|
27b1a31436 | ||
|
|
9b51c8cab4 | ||
|
|
114a452609 | ||
|
|
ef85156bae | ||
|
|
e55d17fd08 | ||
|
|
8b83205b0a | ||
|
|
2251350a4e | ||
|
|
f106a31990 | ||
|
|
c9f222583a | ||
|
|
13fc51a8a5 | ||
|
|
21309cddf0 | ||
|
|
86164ba518 | ||
|
|
8173003144 | ||
|
|
f9c8b00587 | ||
|
|
b77da0f143 | ||
|
|
ed955150df | ||
|
|
f282197611 | ||
|
|
93ce932d28 | ||
|
|
29505fa4a3 | ||
|
|
991442d5c0 | ||
|
|
e7b5991dbf | ||
|
|
fb72317c6f | ||
|
|
7992568c0f | ||
|
|
38666b7c99 | ||
|
|
1f3f143ffb | ||
|
|
156ece4bb5 | ||
|
|
770d1ae689 | ||
|
|
d7287c48cf | ||
|
|
b4c329f2f9 | ||
|
|
cb2219e2cd | ||
|
|
65f0bfa8a4 | ||
|
|
7090e0a47b | ||
|
|
fbb4d61194 | ||
|
|
634b0b50ff | ||
|
|
7ff66e9277 | ||
|
|
b93cc3ab20 | ||
|
|
55e7052189 | ||
|
|
5a9808de59 | ||
|
|
a30b34df70 | ||
|
|
482b19dd5a | ||
|
|
7895ed89f1 | ||
|
|
8449853076 | ||
|
|
affec8d3c1 | ||
|
|
362b69d921 | ||
|
|
9d463c7b4a | ||
|
|
cd7da64794 | ||
|
|
5c95f2971f | ||
|
|
ab3f3f0633 | ||
|
|
ba61ac46d1 | ||
|
|
48205d8a6c | ||
|
|
b4cbb1fd14 | ||
|
|
c8db3ec762 | ||
|
|
f6cf157930 | ||
|
|
4a84a9ed8e | ||
|
|
dbb41ba249 | ||
|
|
d3adec5a23 | ||
|
|
1fd030f909 | ||
|
|
b6dfeb475d | ||
|
|
39050c6de6 | ||
|
|
c36926c915 | ||
|
|
3bf3241bd7 | ||
|
|
df863e879f | ||
|
|
b13af00061 | ||
|
|
1c2215a8a2 | ||
|
|
555ae35bb9 | ||
|
|
8337a1698e | ||
|
|
f0bfa96937 | ||
|
|
7b143dd38f | ||
|
|
5201c5933c | ||
|
|
4c1d501856 | ||
|
|
e001533f33 | ||
|
|
c854dd9a45 | ||
|
|
4215b39539 | ||
|
|
8e882aafa1 | ||
|
|
25edbf06c7 | ||
|
|
4f05365da3 | ||
|
|
bf7de99524 | ||
|
|
08c27b6c58 | ||
|
|
60661757c6 | ||
|
|
5ba64483fb | ||
|
|
a30c9391eb | ||
|
|
7023e726bd | ||
|
|
c616ea81c6 | ||
|
|
65f8b587af | ||
|
|
14f6b9c759 | ||
|
|
c83e0f8cff | ||
|
|
2bf86423c9 | ||
|
|
8dc3035b66 | ||
|
|
0aff0330e7 | ||
|
|
41852f2467 | ||
|
|
442ec76828 | ||
|
|
d8fc14e71b | ||
|
|
2630d32764 | ||
|
|
74d7eff577 | ||
|
|
355c9e2a3d | ||
|
|
501bf06ada | ||
|
|
4574bc0b2f | ||
|
|
c6c91b84fe | ||
|
|
066b33e3e8 | ||
|
|
15002c45d6 | ||
|
|
b41f3b9370 | ||
|
|
02e2700e96 | ||
|
|
6441dfd219 | ||
|
|
1e229c12cc | ||
|
|
4219f2db5b | ||
|
|
36ce636093 | ||
|
|
47f09f81ff | ||
|
|
bfa6df904d | ||
|
|
99d4f55c50 | ||
|
|
7728b4b1ab | ||
|
|
6a475434ad | ||
|
|
876a0bd108 | ||
|
|
ba13bfd9ad | ||
|
|
44bab8c0c7 | ||
|
|
2d229b82c3 | ||
|
|
c6cce7aa9a | ||
|
|
5cbf09f24e | ||
|
|
3ca7d3d615 | ||
|
|
25840be694 | ||
|
|
34ab608425 | ||
|
|
b498160b3a | ||
|
|
23aae3b5b9 | ||
|
|
97fdd096a8 | ||
|
|
0d21a02da9 | ||
|
|
ab94250b05 | ||
|
|
42ca6f79dc | ||
|
|
646795b753 | ||
|
|
de649915e2 | ||
|
|
0093f5a0de | ||
|
|
686b50eeda | ||
|
|
0c93b85024 | ||
|
|
49d4e88022 | ||
|
|
3a8616e225 | ||
|
|
d4178c85a9 | ||
|
|
de46790bdf | ||
|
|
c27070ae28 | ||
|
|
bb4c9477da | ||
|
|
95e78e4f93 | ||
|
|
d3d7b5a5c7 | ||
|
|
45a3d74284 | ||
|
|
cd972b5c61 | ||
|
|
341be8bdc1 | ||
|
|
101c24edc5 | ||
|
|
be34915cdf | ||
|
|
70aed1d5db | ||
|
|
d8c1144881 | ||
|
|
68ec7f504a | ||
|
|
3ab2b16042 | ||
|
|
d7fc6f9f49 | ||
|
|
26a72244c0 | ||
|
|
abc21badb1 | ||
|
|
aa1caacfd6 | ||
|
|
3663eb63e7 | ||
|
|
e885700680 | ||
|
|
d6170d602a | ||
|
|
4dbb287e11 | ||
|
|
e537e0f115 | ||
|
|
3613d7a37b | ||
|
|
b5e98f505f | ||
|
|
3dc9ca6822 | ||
|
|
e13ddeaaad | ||
|
|
56ce7f9696 | ||
|
|
4ebddf78ed | ||
|
|
2682165da8 | ||
|
|
373e7dc8ad | ||
|
|
0551f8bff1 | ||
|
|
b4454cc812 | ||
|
|
40fb1c8868 | ||
|
|
f97cb4a1bb | ||
|
|
56d0786702 | ||
|
|
d880ecd709 | ||
|
|
1bee9e19e6 | ||
|
|
c5879ae5a7 | ||
|
|
64f458e15a | ||
|
|
2fa5d09fc9 | ||
|
|
d6fc60e02b | ||
|
|
bb9bfd6396 | ||
|
|
0fbe139e8d | ||
|
|
571591f021 | ||
|
|
8a1d2383b8 | ||
|
|
00c5edcea7 | ||
|
|
39d62099df | ||
|
|
c5d6f6f5b9 | ||
|
|
13f3cf1e90 | ||
|
|
93f6bf8ba3 | ||
|
|
1cdbed51cd | ||
|
|
50ae04bb4e | ||
|
|
a55d503faa | ||
|
|
7fc4f83eb5 | ||
|
|
bc831b4d30 | ||
|
|
c6190146aa | ||
|
|
3f01152a4a | ||
|
|
ad5c652a8f | ||
|
|
9609db941b | ||
|
|
bbb8447f5c | ||
|
|
22ca06af3e | ||
|
|
af11758190 | ||
|
|
32fcc25ea4 | ||
|
|
b3fcf8dd5e | ||
|
|
b7d2a97f05 | ||
|
|
ad13875137 | ||
|
|
e14a131480 | ||
|
|
64ba0db228 | ||
|
|
c99d8e7e75 | ||
|
|
305d1cea94 | ||
|
|
f314b3982e | ||
|
|
5ea2615b93 | ||
|
|
17123fec35 | ||
|
|
73d05e7cbf | ||
|
|
3380ea3609 | ||
|
|
101527d3e1 | ||
|
|
df9ab3250c | ||
|
|
d533df52de | ||
|
|
d2cf16d046 | ||
|
|
40a65b5e13 | ||
|
|
fa33cb680e | ||
|
|
2757eb91ce | ||
|
|
2842429ced | ||
|
|
fb2a26c5b7 | ||
|
|
cab1dc8838 | ||
|
|
0ec76dcde3 | ||
|
|
c41046953e | ||
|
|
30f740a430 | ||
|
|
1e8c0ce99b | ||
|
|
aa3a3d9181 | ||
|
|
6d0f528201 | ||
|
|
131cd5915c | ||
|
|
f5512fa162 | ||
|
|
484bb758ae | ||
|
|
89461893a4 | ||
|
|
54e865feb2 | ||
|
|
015fa3dc9f | ||
|
|
cf015be49f | ||
|
|
b6c8993f7e | ||
|
|
1ef37d91e8 | ||
|
|
7fc81cf363 | ||
|
|
123bf9de34 | ||
|
|
d3f6b75d34 | ||
|
|
a5fe5f53e2 | ||
|
|
e91029f66e | ||
|
|
2a7ce54c28 | ||
|
|
f3b2d4dc57 | ||
|
|
95b9871f7f | ||
|
|
533af83749 | ||
|
|
e4330fee92 | ||
|
|
5fec57e8e3 | ||
|
|
95a06d572b | ||
|
|
cc8c125934 | ||
|
|
91c605ee4b | ||
|
|
f44fe4def1 | ||
|
|
7c0a1ea089 | ||
|
|
ce5c4d1111 | ||
|
|
98952972a0 | ||
|
|
f728b6ab1b | ||
|
|
e799b48877 | ||
|
|
fc8ecb7470 | ||
|
|
ac18234e29 | ||
|
|
59b17aa47e | ||
|
|
9155bfb886 | ||
|
|
cbe683d25e | ||
|
|
64f057a415 | ||
|
|
3da308346e | ||
|
|
313dd681de | ||
|
|
aaf7e1e3e5 | ||
|
|
9f831fd8b5 | ||
|
|
450397481e | ||
|
|
6fb8978f48 | ||
|
|
4a7bf4b31e | ||
|
|
38e94210e4 | ||
|
|
d338809750 | ||
|
|
7fd7e17d1d | ||
|
|
a5a5bd80c4 | ||
|
|
d264ca1ed4 | ||
|
|
85dd19509c | ||
|
|
faf1945933 | ||
|
|
3c6d6ff702 | ||
|
|
c6ecc89ad3 | ||
|
|
abed79441d | ||
|
|
906199a517 | ||
|
|
6f34c21d94 | ||
|
|
9ba1a68b51 | ||
|
|
073c72fd63 | ||
|
|
3ac4af1558 | ||
|
|
5057f6848f | ||
|
|
b3955731c2 | ||
|
|
af83cf552e | ||
|
|
a0426044e8 | ||
|
|
432635d567 | ||
|
|
02b78320ec | ||
|
|
f1461f905d | ||
|
|
e1cdb3ab65 | ||
|
|
6218424be3 | ||
|
|
5a9b7e296f | ||
|
|
f4cc9c7734 | ||
|
|
e0c7998448 | ||
|
|
752d65557f | ||
|
|
a952674df7 | ||
|
|
25f5f6e1f7 | ||
|
|
0f1d6c0984 | ||
|
|
8dd4bb9d61 | ||
|
|
98275ade59 | ||
|
|
95cc9f0e21 | ||
|
|
742ea50c2c | ||
|
|
54b1174e1b | ||
|
|
e07de72fa4 | ||
|
|
13b4af3734 | ||
|
|
29566a6c93 | ||
|
|
7669f7d9a0 | ||
|
|
5913d5b585 | ||
|
|
d9a332de44 | ||
|
|
e85533686e | ||
|
|
0100140dc0 | ||
|
|
8def9e8931 | ||
|
|
6fc2f26983 | ||
|
|
ed1ed6cbe9 | ||
|
|
10d19a5392 | ||
|
|
7bbd90ab91 | ||
|
|
9565d48b04 | ||
|
|
284d366b44 | ||
|
|
a9893379f4 | ||
|
|
50677ad81d | ||
|
|
71d7fcbe65 | ||
|
|
8342acbd49 | ||
|
|
d5296763ad | ||
|
|
73efa4fe91 | ||
|
|
82f573e1a1 | ||
|
|
4ef0ac3fee | ||
|
|
bc246f39d2 | ||
|
|
f9af23dbca | ||
|
|
68f8ef0b24 | ||
|
|
16f6acf8fc | ||
|
|
3faa5b2f52 | ||
|
|
04a7c068f4 | ||
|
|
92dee27634 | ||
|
|
7dda25f96b | ||
|
|
40f700910a | ||
|
|
aa90b34511 | ||
|
|
45cf082bb9 | ||
|
|
0ab78983d4 | ||
|
|
e137210cbc | ||
|
|
3093755c9e | ||
|
|
94c4950d23 | ||
|
|
fa0a624b7c | ||
|
|
f3fabe1708 | ||
|
|
52ec890e2c | ||
|
|
2a10471e0b | ||
|
|
94c1974d2f | ||
|
|
f0a8014efb | ||
|
|
96cac6ca68 | ||
|
|
0da50bc693 | ||
|
|
1169ac44b4 | ||
|
|
38367279ff | ||
|
|
ef00cfbddd | ||
|
|
e514e4b64e | ||
|
|
f1af07e921 | ||
|
|
44cf022e70 | ||
|
|
7e68de5a17 | ||
|
|
51f73d77bf | ||
|
|
fa4ccf07b8 | ||
|
|
49da7aafd0 | ||
|
|
58ca71baaa | ||
|
|
2c550a0874 | ||
|
|
dcb671acd8 | ||
|
|
56600d3f27 | ||
|
|
6913f7bdf5 | ||
|
|
bcc2d286ed | ||
|
|
aed6c2123f | ||
|
|
68626fecd7 | ||
|
|
441d7376cb | ||
|
|
c9ac38de01 | ||
|
|
b9c063c41e | ||
|
|
55804be70e | ||
|
|
2b749af917 | ||
|
|
9378dfdd04 | ||
|
|
46768c77b7 | ||
|
|
ff9358b986 | ||
|
|
f3090870be | ||
|
|
666fbe8ce7 | ||
|
|
67b29851ea | ||
|
|
1a2cd201a7 | ||
|
|
5041bf67a5 | ||
|
|
1a2f3bf80e | ||
|
|
d7a231eb18 | ||
|
|
21c9c190aa | ||
|
|
a781c2d665 | ||
|
|
09c3ce3164 | ||
|
|
a7888a63fa | ||
|
|
79dd56d017 | ||
|
|
882d04f50c | ||
|
|
a7fd414ce6 | ||
|
|
eb30451cfa | ||
|
|
a48f2b1f17 | ||
|
|
d3665d64a6 | ||
|
|
a4bbc9c3c6 | ||
|
|
b0ffda42bc | ||
|
|
b333045d41 | ||
|
|
ef5d0a81eb | ||
|
|
48819c10a9 | ||
|
|
9ae23e4395 | ||
|
|
4d36efebb7 | ||
|
|
e842a37654 | ||
|
|
c821626dc1 | ||
|
|
16aba9ff96 | ||
|
|
a11bfdb13b | ||
|
|
c0437e55eb | ||
|
|
279bcbfeab | ||
|
|
6555263496 | ||
|
|
8db4e17a8a | ||
|
|
4a5bd3135f | ||
|
|
32eb98361a | ||
|
|
2ba5073d55 | ||
|
|
9ea5e2cd90 | ||
|
|
284a1f0b57 | ||
|
|
7b97f3d535 | ||
|
|
fb431fcc7b | ||
|
|
b16100e627 | ||
|
|
fde6b5df9b | ||
|
|
dc1ac106c0 | ||
|
|
0d4a26c05e | ||
|
|
ffe7a9294b | ||
|
|
15004b6ba2 | ||
|
|
dfd8d1b0c9 | ||
|
|
c2fef3fa25 | ||
|
|
d33c892303 | ||
|
|
e4ce97cf5d | ||
|
|
f331916bd5 | ||
|
|
5d3194dd41 | ||
|
|
f2574b516e | ||
|
|
9ee3e973c1 | ||
|
|
eb9e797017 | ||
|
|
d0c305b3eb | ||
|
|
db2a7208da | ||
|
|
e8958f5e53 | ||
|
|
8acee1251f | ||
|
|
acf117e43b | ||
|
|
ed1b7f477b | ||
|
|
63cf1f5fa1 | ||
|
|
fa9b738cba | ||
|
|
3efbefe4c5 | ||
|
|
dcb797db38 | ||
|
|
8f9f9e9e82 | ||
|
|
642754a46b | ||
|
|
a5578335d3 | ||
|
|
c4309aa14c | ||
|
|
af3eb10034 | ||
|
|
88b7804123 | ||
|
|
4a383521d7 | ||
|
|
279b65cfa0 | ||
|
|
6ce644ea18 | ||
|
|
9ef5e8d037 | ||
|
|
a9e14a93dd | ||
|
|
8e2c0d8653 | ||
|
|
39a19fd9e6 | ||
|
|
68e1a0489f | ||
|
|
ece64c3f4a | ||
|
|
37f67469a6 | ||
|
|
a1b546152b | ||
|
|
3ae1f6c556 | ||
|
|
14757e2a35 | ||
|
|
d20a762dd8 | ||
|
|
5ff79f5ee1 | ||
|
|
afe28733db | ||
|
|
5e5fe6013d | ||
|
|
3f0196c8f8 | ||
|
|
4e38cf9d40 | ||
|
|
6026f6aebd | ||
|
|
856bcf1647 | ||
|
|
8e7196181c | ||
|
|
beb6697507 | ||
|
|
fd482d32a7 | ||
|
|
10169b03ce | ||
|
|
5f4a7cd7c9 | ||
|
|
59b52b8a47 | ||
|
|
02bffab38f | ||
|
|
256f08396f | ||
|
|
988a280111 | ||
|
|
7a44a457d5 | ||
|
|
2fa153e569 | ||
|
|
b589c78cfc | ||
|
|
293acbcc03 | ||
|
|
be0a841926 | ||
|
|
763cbfa656 | ||
|
|
d149b02c06 | ||
|
|
b83773dfa6 | ||
|
|
c992701387 | ||
|
|
bf43944c27 | ||
|
|
6dbed875e1 | ||
|
|
5166387f34 | ||
|
|
b44121597f | ||
|
|
f086b8abe9 | ||
|
|
795da9557b | ||
|
|
76249cb8f7 | ||
|
|
f4c4162e4b | ||
|
|
902cc9009e | ||
|
|
d93c2ee267 | ||
|
|
e5b864f07e | ||
|
|
8df4cd3dd6 | ||
|
|
2fdb6f15cb | ||
|
|
c7e493d7f5 | ||
|
|
043537a7b4 | ||
|
|
e7643f3894 | ||
|
|
bb2c8ae8e5 | ||
|
|
1c8860c596 | ||
|
|
66e65fcd14 | ||
|
|
7cef8f24db | ||
|
|
a82f3a7b07 | ||
|
|
4f41068c99 | ||
|
|
50dfb95c48 | ||
|
|
0b29ac00a7 | ||
|
|
759519d374 | ||
|
|
3d713b13da | ||
|
|
fcbe52539a | ||
|
|
bcd64286cd | ||
|
|
b89147120c | ||
|
|
c9ffd3cd11 | ||
|
|
cd62f31c17 | ||
|
|
98d63b880b | ||
|
|
04e11b0fea | ||
|
|
a873b28d9b | ||
|
|
b1b2ff6b8c | ||
|
|
cb96b5fa8f | ||
|
|
eb960209bf | ||
|
|
51a0e46f8c | ||
|
|
1251205fdd | ||
|
|
9a05629144 | ||
|
|
e0c71006d5 | ||
|
|
3d716a516a | ||
|
|
096648b2d7 | ||
|
|
02e57707de | ||
|
|
20468e612d | ||
|
|
af3aa497d1 | ||
|
|
bb53d1448b | ||
|
|
eccdd73908 | ||
|
|
1d4e1a8be2 | ||
|
|
8dc0f299a9 | ||
|
|
db1ffd5091 | ||
|
|
64a27e5708 | ||
|
|
214a356135 | ||
|
|
84f7953f21 | ||
|
|
a9a947203d | ||
|
|
8f250e755e | ||
|
|
30a7fa7ead | ||
|
|
4324d655d2 | ||
|
|
da1be9226a | ||
|
|
5597bf5d1e | ||
|
|
50029fbb24 | ||
|
|
f5c2dc747d | ||
|
|
dd55e336e4 | ||
|
|
a001a45cc4 | ||
|
|
49320ff623 | ||
|
|
8e898c50b4 | ||
|
|
5fa93e2a2a | ||
|
|
178cca1611 | ||
|
|
9e543e2c5a | ||
|
|
6a16e5534d | ||
|
|
2d1cad870b | ||
|
|
3fdf255ca5 | ||
|
|
ec9225dbf2 | ||
|
|
b469fe92dd | ||
|
|
dbc54b016c | ||
|
|
2cbad36f80 | ||
|
|
8b21f33eb6 | ||
|
|
1e8ff7dbc0 | ||
|
|
0937915839 | ||
|
|
a128546954 | ||
|
|
806953d107 | ||
|
|
afa39b29ed | ||
|
|
4c899555dd | ||
|
|
3e8395b0c6 | ||
|
|
a620f02d57 | ||
|
|
79a078fb70 | ||
|
|
cac2f49b06 | ||
|
|
06993b9d66 | ||
|
|
fce5ca592a | ||
|
|
e7058cf3c8 | ||
|
|
a2eeac786e | ||
|
|
ff7873313b | ||
|
|
f3b06ac0a6 | ||
|
|
00e57fc17d | ||
|
|
c8cc59aaca | ||
|
|
54057922f6 | ||
|
|
c175173821 | ||
|
|
52e9285551 | ||
|
|
823eea1f0a | ||
|
|
ae03a700de | ||
|
|
a089eade6e | ||
|
|
f8fb3d8a70 | ||
|
|
00a75f154e | ||
|
|
8d2313d799 | ||
|
|
0b8384fc3b | ||
|
|
73ab9efdb4 | ||
|
|
705f69510b | ||
|
|
f7e0a33935 | ||
|
|
729015d719 | ||
|
|
d83127a265 | ||
|
|
6e8f7ae698 | ||
|
|
17ee24286c | ||
|
|
133140bf71 | ||
|
|
5fafbf9ee8 | ||
|
|
6085da15a4 | ||
|
|
b93caf1839 | ||
|
|
c9787a521b | ||
|
|
26cabef74c | ||
|
|
8d2474768b | ||
|
|
a87146a401 | ||
|
|
bbf5ee5395 | ||
|
|
103ae607be | ||
|
|
6f63998000 | ||
|
|
a94952babc | ||
|
|
4b8385419e | ||
|
|
878a543818 | ||
|
|
e7337777cd | ||
|
|
fa1ed52c32 | ||
|
|
eda3fccb51 | ||
|
|
ec55fae1ec | ||
|
|
c88a98e355 | ||
|
|
0567e0f251 | ||
|
|
befa7d0e8e | ||
|
|
91913da205 | ||
|
|
cd8677a26d | ||
|
|
adf091e300 | ||
|
|
aa3b831a68 | ||
|
|
ab075c0554 | ||
|
|
4202012bbd | ||
|
|
739854935d | ||
|
|
3d88e734df | ||
|
|
a716a69b8b | ||
|
|
7ac7aacb6c | ||
|
|
7368001e3b | ||
|
|
574e0b4074 | ||
|
|
5d258eb8e1 | ||
|
|
5715fa97f7 | ||
|
|
7fe869b98b | ||
|
|
37aaa30387 | ||
|
|
c3b2d7653f | ||
|
|
cb22161156 | ||
|
|
6ad06c2d75 | ||
|
|
b291dd0ad7 | ||
|
|
3039a32f29 | ||
|
|
a8f5380070 | ||
|
|
e37867b9db | ||
|
|
110d6c81ee | ||
|
|
3da44ce604 | ||
|
|
9770c15188 | ||
|
|
cc568d9569 | ||
|
|
500a4b0b7e | ||
|
|
55cfb7b358 | ||
|
|
77b2f90259 | ||
|
|
e962baaf48 | ||
|
|
2cb5b18975 | ||
|
|
32084eb1e7 | ||
|
|
2815d76b1d | ||
|
|
9c4f146778 | ||
|
|
7b89016359 | ||
|
|
c7a4902af0 | ||
|
|
6f9b686317 | ||
|
|
16550e7a83 | ||
|
|
a9aa47e390 | ||
|
|
0846fb94db | ||
|
|
4c3aafd266 | ||
|
|
1e606f8b85 | ||
|
|
58596377b1 | ||
|
|
94a3234874 | ||
|
|
7e9db5b52d | ||
|
|
dc098d1ec7 | ||
|
|
195a8b4315 | ||
|
|
7df80bc56a | ||
|
|
1c541a4adf | ||
|
|
f29f563e50 | ||
|
|
f4280c0768 | ||
|
|
295ae14658 | ||
|
|
ccc2bcffce | ||
|
|
3a94ef57e3 | ||
|
|
db8d8db280 | ||
|
|
fdcef95d07 | ||
|
|
7b1d9a777d | ||
|
|
3cd8764dbf | ||
|
|
32826440cb | ||
|
|
a65fa8cf10 | ||
|
|
0ae29b1920 | ||
|
|
5748a11788 | ||
|
|
7d3579af4f | ||
|
|
e0dc3bd1f4 | ||
|
|
6200c56144 | ||
|
|
0b4a0eeb55 | ||
|
|
467f5bd2eb | ||
|
|
322d90adfa | ||
|
|
11eb7c058f | ||
|
|
361b251952 | ||
|
|
3d503971ae | ||
|
|
9faabba361 | ||
|
|
f33629aba1 | ||
|
|
a47ed71799 | ||
|
|
4a9b9d57e4 | ||
|
|
98c3f0ce5b | ||
|
|
78a3082bcb | ||
|
|
b64af43a7e | ||
|
|
1bcacbfebe | ||
|
|
f32db6c83b | ||
|
|
437b638973 | ||
|
|
670918efd3 | ||
|
|
006a49cfdb | ||
|
|
9794ee259a | ||
|
|
643e0e0c1f | ||
|
|
6afcb364d1 | ||
|
|
6d4a38404c | ||
|
|
b925c2ef20 | ||
|
|
e3f931d4f5 | ||
|
|
01b5d63972 | ||
|
|
97794ce7c5 | ||
|
|
ac4c66a1f7 | ||
|
|
9750e26d4b | ||
|
|
4b14412190 | ||
|
|
207c2e10e3 | ||
|
|
7c73515427 | ||
|
|
aea7108940 | ||
|
|
2bdd97d889 | ||
|
|
93b6dd3374 | ||
|
|
bf3c123658 | ||
|
|
06c0a361fd | ||
|
|
92510845d6 | ||
|
|
8ab57859f6 | ||
|
|
0608b50193 | ||
|
|
e14ff26915 | ||
|
|
8932a16468 | ||
|
|
3804db142f | ||
|
|
7e198bd7a1 | ||
|
|
85301c92ec | ||
|
|
2ca6be77ed | ||
|
|
48558bec0f | ||
|
|
31af8669b5 | ||
|
|
d716ee5d26 | ||
|
|
8e1c07d530 | ||
|
|
0d7b52aadc | ||
|
|
25eb99f014 | ||
|
|
ab8276df2f | ||
|
|
68569ad875 | ||
|
|
c0e77d9eec | ||
|
|
27297c5d24 | ||
|
|
b6a7a02b23 | ||
|
|
534e20a072 | ||
|
|
9478da81a9 | ||
|
|
ee958f20d2 | ||
|
|
00edf44828 | ||
|
|
f3a49533fd | ||
|
|
3b6517090c | ||
|
|
5b2687ae83 | ||
|
|
c3402e8d44 | ||
|
|
a9625dfecd | ||
|
|
d6c8464e97 | ||
|
|
1b557d5f8c | ||
|
|
e1cf944db7 | ||
|
|
cb873efd38 | ||
|
|
ee5acfa35f | ||
|
|
2904c55f84 | ||
|
|
4667e9652f | ||
|
|
905b4fe92e | ||
|
|
85ccc78f8f | ||
|
|
ad3bb89dc9 | ||
|
|
106bded9b6 | ||
|
|
f46c8a03d9 | ||
|
|
5d9693c419 | ||
|
|
126546a938 | ||
|
|
bb3902730b | ||
|
|
1b9e25e81c | ||
|
|
b11439ca87 | ||
|
|
66034ea407 | ||
|
|
6690c665dd | ||
|
|
4b71cd9940 | ||
|
|
8b5ef24681 | ||
|
|
e161890eaa | ||
|
|
348f27237b | ||
|
|
c19164269a | ||
|
|
071491b459 | ||
|
|
e1180a9a14 | ||
|
|
f76a027b32 | ||
|
|
cc1a91e5cd | ||
|
|
fcee4d13da | ||
|
|
fa567ce0e2 | ||
|
|
c10085b65a | ||
|
|
a8465408cf | ||
|
|
d123a50054 | ||
|
|
bd09e4017a | ||
|
|
8b5e29d29e | ||
|
|
6e427b060a | ||
|
|
7d0f70f1c0 | ||
|
|
5f1ca64d65 | ||
|
|
fb58f08e44 | ||
|
|
017d00371d | ||
|
|
abe1d3ad29 | ||
|
|
fc48ba1994 | ||
|
|
279fe5dcb8 | ||
|
|
ccb3f7ef34 | ||
|
|
de12ec6548 | ||
|
|
14bd2480ce | ||
|
|
8152b9ab0d | ||
|
|
e6e4782d51 | ||
|
|
aa5f4fb986 | ||
|
|
aa4fe50eeb | ||
|
|
df072f1c40 | ||
|
|
9b3e202eb8 | ||
|
|
b403189afb | ||
|
|
82d076b87d | ||
|
|
dcdf951ebc | ||
|
|
4c1f8e4005 | ||
|
|
672ecc7f0a | ||
|
|
81f05528d8 | ||
|
|
57214aadfc | ||
|
|
6209a0120b | ||
|
|
98dcb3fbcb | ||
|
|
2211e1c816 | ||
|
|
36bf37da8d | ||
|
|
21be74fb05 | ||
|
|
870ca6cd7f | ||
|
|
4e832a5eb2 | ||
|
|
7b9c23c203 | ||
|
|
fc91807e07 | ||
|
|
8aab359b0b | ||
|
|
363c9ff028 | ||
|
|
2a800a825b | ||
|
|
36c1b1e0dd | ||
|
|
01d7cdc7de | ||
|
|
6f25cb9017 | ||
|
|
c4ff479af4 | ||
|
|
cc8406cd79 | ||
|
|
b94c1915a8 | ||
|
|
99a6685845 | ||
|
|
10bf60126e | ||
|
|
14d8f0730f | ||
|
|
618503ccf2 | ||
|
|
f64a837172 |
20
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
FROM gradle:8.8.0-jdk21
|
||||
|
||||
ENV NODE_VERSION=22
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y curl && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && \
|
||||
apt-get install -y nodejs && \
|
||||
npm install -g npm@latest
|
||||
|
||||
RUN npm install -g bun
|
||||
|
||||
RUN apt-get install -y maven
|
||||
|
||||
RUN gradle --version && \
|
||||
node --version && \
|
||||
npm --version && \
|
||||
bun --version
|
||||
|
||||
WORKDIR /workspace
|
||||
17
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "AquaDX Dev Container",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"vscjava.vscode-gradle",
|
||||
"vscjava.vscode-java-pack",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"fwcd.kotlin"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
63
.github/workflows/docker-image.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
# Trigger the workflow on push to the main branch or on manual dispatch
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Runs at midnight UTC every Sunday
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# 1. Checkout the repository
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 2. Set up QEMU and Docker Buildx (for multi-platform builds, optional)
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# 3. Log in to Docker Hub
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: hykilpikonna
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# 3. Log in to GitHub Container Registry (ghcr.io)
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
# Use GITHUB_TOKEN for authentication
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# 4. Cache Docker layers to speed up builds (optional but recommended)
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
# 5. Build and push the Docker image
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
hykilpikonna/aquadx:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
15
.github/workflows/gradle.yml
vendored
@ -3,22 +3,23 @@ name: Gradle Build
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
server-id: github
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
run: |
|
||||
mkdir data
|
||||
bash ./src/main/resources/meta/update.sh
|
||||
chmod +x gradlew
|
||||
./gradlew build
|
||||
|
||||
42
.github/workflows/nightly.yml
vendored
@ -4,6 +4,8 @@ name: Nightly Build
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths: ['src/**']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -24,52 +26,26 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Manage Version
|
||||
run: |
|
||||
git fetch --prune --unshallow --tags
|
||||
GIT_SHA="$(git rev-parse --short HEAD)"
|
||||
CUR_TAG="$(git tag -l | grep 'nightly' | tail -1)"
|
||||
VER="$(sed -n 's/version = "\(.*\)"/\1/p' build.gradle.kts)"
|
||||
echo "SHORT_SHA=$GIT_SHA" >> $GITHUB_ENV
|
||||
echo "VER=$VER" >> $GITHUB_ENV
|
||||
if [[ -z $CUR_TAG ]]; then
|
||||
echo "OLD_PRE_TAG=NULL" >> $GITHUB_ENV
|
||||
else
|
||||
echo "OLD_PRE_TAG=$CUR_TAG" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build Artifact
|
||||
run: |
|
||||
./gradlew build
|
||||
rm -rf build/libs/*-plain.jar
|
||||
cp build/libs/*.jar "build/libs/aqua-nightly.jar"
|
||||
run: bash ./tools/build.sh
|
||||
|
||||
- name: Generate Prerelease Release Notes
|
||||
- name: Delete previous nightly release
|
||||
run: |
|
||||
echo '### Nightly Release' >> ReleaseNotes.md
|
||||
echo 'This nightly release is automatically built by github actions.' >> ReleaseNotes.md
|
||||
echo '### The latest five updates are:' >> ReleaseNotes.md
|
||||
git log -"5" --format="- %H %s" | sed '/^$/d' >> ReleaseNotes.md
|
||||
|
||||
- name: Delete Old Prerelease
|
||||
if: env.OLD_PRE_TAG != 'NULL'
|
||||
uses: dev-drprasad/delete-tag-and-release@v1.0
|
||||
with:
|
||||
tag_name: ${{ env.OLD_PRE_TAG }}
|
||||
gh release delete --cleanup-tag --yes --repo $GITHUB_REPOSITORY nightly || true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish GitHub Release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
bodyFile: ReleaseNotes.md
|
||||
artifacts: "build/libs/aqua-nightly.jar"
|
||||
tag: "${{ env.VER }}-nightly"
|
||||
bodyFile: "build/release/ReleaseNotes.md"
|
||||
artifacts: "build/libs/aqua-nightly.jar,build/aqua-nightly.zip"
|
||||
tag: "nightly"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: false
|
||||
|
||||
- name: Mark release undraft
|
||||
run: |
|
||||
gh release edit "${{ env.VER }}-nightly" --draft=false
|
||||
gh release edit nightly --draft=false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
10
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
db/
|
||||
web/
|
||||
bin/
|
||||
|
||||
@ -75,4 +76,11 @@ gradle-app.setting
|
||||
|
||||
### Gradle Patch ###
|
||||
# Java heap dump
|
||||
*.hprof
|
||||
*.hprof
|
||||
.jpb
|
||||
src/main/resources/meta/*/*.json
|
||||
*.log.*.gz
|
||||
*.salive
|
||||
test-diff
|
||||
htmlReport
|
||||
docs/logs
|
||||
|
||||
20
AquaNet/.editorconfig
Normal file
@ -0,0 +1,20 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Matches multiple files: .ts, .json, .svelte .sass
|
||||
[*.{json,svelte,ts,sass}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
|
||||
# Markdown files (e.g., README.md) often use a line length of 80 characters
|
||||
[*.md]
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = false
|
||||
9
AquaNet/.env
Normal file
@ -0,0 +1,9 @@
|
||||
VITE_AQUA_HOST=https://aquadx.net/aqua
|
||||
VITE_DATA_HOST=https://aquadx.net
|
||||
|
||||
VITE_AQUA_CONNECTION=aquadx.hydev.org
|
||||
|
||||
VITE_TURNSTILE_SITE_KEY=0x4AAAAAAASGA2KQEIelo9P9
|
||||
VITE_DISCORD_INVITE=https://discord.gg/FNgveqFF7s
|
||||
VITE_TELEGRAM_INVITE=https://t.me/+zBL4RZdyfvUzZGU1
|
||||
VITE_QQ_INVITE=https://qm.qq.com/q/dpYmGoVHnG
|
||||
41
AquaNet/.eslintrc.cjs
Normal file
@ -0,0 +1,41 @@
|
||||
// ..eslintrc.cjs example
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2023: true
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '..eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
// Custom styling rules
|
||||
'comma-dangle': ['warn', 'only-multiline'],
|
||||
'indent': ['warn', 2],
|
||||
'semi': ['warn', 'never'],
|
||||
'quotes': ['warn', 'single'],
|
||||
'arrow-parens': ['warn', 'as-needed'],
|
||||
'linebreak-style': ['warn', 'unix'],
|
||||
'object-curly-spacing': ['warn', 'always'],
|
||||
'array-bracket-spacing': ["error", "always", {
|
||||
"singleValue": false,
|
||||
"objectsInArrays": false,
|
||||
"arraysInArrays": false
|
||||
}],
|
||||
|
||||
// Disabled rules
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
}
|
||||
38
AquaNet/.gitignore
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Yarn 3 files
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
public/chu3
|
||||
|
||||
# local env file
|
||||
*.local
|
||||
893
AquaNet/.yarn/releases/yarn-4.1.1.cjs
vendored
Normal file
20
AquaNet/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# AquaNet
|
||||
|
||||
This is the codebase for the new frontend of AquaDX.
|
||||
This project is also heavily WIP, so more details will be added later on.
|
||||
|
||||
## Development
|
||||
|
||||
This project uses Svelte (NOT SvelteKit) + TypeScript + Sass, built using Vite. The preferred editor is VSCode.
|
||||
|
||||
### Running locally
|
||||
|
||||
First, you would need to install Node.js and bun.
|
||||
Then, you would need to start your testing AquaDX server and configure the `aqua_host` in `src/libs/config.ts` to use your URL.
|
||||
Please leave `data_host` unchanged if you're not sure what it is.
|
||||
Finally, run:
|
||||
|
||||
```shell
|
||||
bun install
|
||||
bun run dev
|
||||
```
|
||||
BIN
AquaNet/bun.lockb
Executable file
45
AquaNet/index.html
Normal file
@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AquaDX.net</title>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png">
|
||||
<link rel="manifest" href="/assets/icons/site.webmanifest">
|
||||
<link rel="mask-icon" href="/assets/icons/safari-pinned-tab.svg" color="#b3c6ff">
|
||||
<link rel="shortcut icon" href="/assets/icons/favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-config" content="/assets/icons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<!-- Font CSS -->
|
||||
<link rel="stylesheet" href="/assets/fonts/Quicksand.400.css" />
|
||||
<link rel="stylesheet" href="/assets/fonts/Quicksand.500.css" />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<meta name="title" content="AquaDX.net" />
|
||||
<meta name="description" content="A certain magical arcade server 🪄" />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://aquadx.net/" />
|
||||
<meta property="og:title" content="AquaDX.net" />
|
||||
<meta property="og:description" content="A certain magical arcade server 🪄" />
|
||||
<meta property="og:image" content="https://aquadx.net/assets/meta/meta.png" />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content="https://aquadx.net/" />
|
||||
<meta property="twitter:title" content="AquaDX.net" />
|
||||
<meta property="twitter:description" content="A certain magical arcade server 🪄" />
|
||||
<meta property="twitter:image" content="https://aquadx.net/assets/meta/meta.png" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
38
AquaNet/nginx.conf.example
Normal file
@ -0,0 +1,38 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name aqua.example.com;
|
||||
ssl_certificate /etc/nginx/certs/aqua.example.com.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/aqua.example.com.key;
|
||||
|
||||
# Absolute path to store the compiled AquaNet,
|
||||
# there should be index.html in the directory.
|
||||
location / {
|
||||
root /var/www/html/AquaNet/;
|
||||
try_files $uri $uri/ @router;
|
||||
index index.html;
|
||||
}
|
||||
|
||||
# Route for redirection to index.html
|
||||
location @router{
|
||||
rewrite ^.*$ /index.html last;
|
||||
}
|
||||
|
||||
# If you have modified DATA_HOST,
|
||||
# you will need to prepare your own resources.
|
||||
# location /d/ {
|
||||
# root /var/www/html/GameData/;
|
||||
# }
|
||||
|
||||
# Reverse Proxy to your AquaDX.
|
||||
location /aqua/ {
|
||||
proxy_redirect off;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_pass http://127.0.0.1/;
|
||||
proxy_buffering off;
|
||||
}
|
||||
}
|
||||
46
AquaNet/package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "aqua-net",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"lint": "eslint . --ext ts,tsx,svelte --max-warnings 0 --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/svelte": "^4.1.0",
|
||||
"@iconify/tools": "^4.1.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tsconfig/svelte": "^5.0.4",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/wicg-file-system-access": "^2023.10.5",
|
||||
"@unocss/svelte-scoped": "^0.65.2",
|
||||
"chartjs-adapter-moment": "^1.0.1",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-svelte": "^2.46.1",
|
||||
"sass": "^1.83.0",
|
||||
"shiki": "^1.24.4",
|
||||
"svelte": "^5",
|
||||
"svelte-check": "^4",
|
||||
"svelte-turnstile": "^0.9.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"unocss": "^0.65.2",
|
||||
"vite": "^6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"cal-heatmap": "^4.2.4",
|
||||
"chart.js": "^4.4.7",
|
||||
"d3": "^7.9.0",
|
||||
"lxgw-wenkai-lite-webfont": "^1.7.0",
|
||||
"modern-normalize": "^3.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"svelte-easy-crop": "^4.0.0",
|
||||
"svelte5-router": "^3.0.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
|
||||
}
|
||||
4780
AquaNet/pnpm-lock.yaml
generated
Normal file
BIN
AquaNet/psd/icons/AquaDX Cat.psd
Normal file
BIN
AquaNet/psd/icons/Icon.psd
Normal file
BIN
AquaNet/psd/imgs/All Perfect.psd
Normal file
BIN
AquaNet/psd/imgs/Full Combo.psd
Normal file
BIN
AquaNet/psd/imgs/no_cover.psd
Normal file
BIN
AquaNet/psd/imgs/no_profile.psd
Normal file
BIN
AquaNet/public/assets/email/border.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
AquaNet/public/assets/fonts/GothicA1.woff2
Normal file
27
AquaNet/public/assets/fonts/Quicksand.400.css
Normal file
@ -0,0 +1,27 @@
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.400.vietnamese.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.400.latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.400.latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
AquaNet/public/assets/fonts/Quicksand.400.latin-ext.woff2
Normal file
BIN
AquaNet/public/assets/fonts/Quicksand.400.latin.woff2
Normal file
BIN
AquaNet/public/assets/fonts/Quicksand.400.vietnamese.woff2
Normal file
27
AquaNet/public/assets/fonts/Quicksand.500.css
Normal file
@ -0,0 +1,27 @@
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.500.vietnamese.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.500.latin-ext.woff2) format('woff2');
|
||||
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(Quicksand.400.latin.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
BIN
AquaNet/public/assets/fonts/Quicksand.500.latin-ext.woff2
Normal file
BIN
AquaNet/public/assets/fonts/Quicksand.500.latin.woff2
Normal file
BIN
AquaNet/public/assets/fonts/Quicksand.500.vietnamese.woff2
Normal file
BIN
AquaNet/public/assets/fonts/ZenMaru.woff2
Normal file
BIN
AquaNet/public/assets/icons/AquaDX Cat Badge.png
Normal file
|
After Width: | Height: | Size: 6.9 MiB |
BIN
AquaNet/public/assets/icons/AquaDX Cat.128px.webp
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
AquaNet/public/assets/icons/AquaDX Cat.png
Normal file
|
After Width: | Height: | Size: 7.0 MiB |
BIN
AquaNet/public/assets/icons/AquaDX Cat.webp
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
AquaNet/public/assets/icons/Icon.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
AquaNet/public/assets/icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
AquaNet/public/assets/icons/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
AquaNet/public/assets/icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
9
AquaNet/public/assets/icons/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/assets/icons/mstile-150x150.png"/>
|
||||
<TileColor>#ffffff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
AquaNet/public/assets/icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
AquaNet/public/assets/icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
AquaNet/public/assets/icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AquaNet/public/assets/icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
71869
AquaNet/public/assets/icons/safari-pinned-tab.svg
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
19
AquaNet/public/assets/icons/site.webmanifest
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
BIN
AquaNet/public/assets/imgs/All Perfect.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
AquaNet/public/assets/imgs/Full Combo.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
AquaNet/public/assets/imgs/no_cover.jpg
Normal file
|
After Width: | Height: | Size: 894 KiB |
BIN
AquaNet/public/assets/imgs/no_profile.png
Normal file
|
After Width: | Height: | Size: 528 KiB |
BIN
AquaNet/public/assets/imgs/no_texture.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
AquaNet/public/assets/meta/meta.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
163
AquaNet/public/assets/theme/cn.css
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
|
||||
Happy April Fools!
|
||||
This theme will stay here.
|
||||
Note that I made it with Stylish in mind, it's quite jank.
|
||||
|
||||
*/
|
||||
* {
|
||||
font-family: "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, "MS Pゴシック",
|
||||
"MS P Gothic", sans-serif;
|
||||
}
|
||||
nav > a,
|
||||
nav > *.active,
|
||||
.setting-icon path {
|
||||
color: unset !important;
|
||||
}
|
||||
.aqua-tooltip {
|
||||
background: black;
|
||||
}
|
||||
.fw-block {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
#app {
|
||||
background: url(/assets/theme/cn/logo.bin),
|
||||
#f9f9db;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 4px;
|
||||
max-width: 528px !important;
|
||||
margin: 0 auto;
|
||||
padding: 100px 0 0 0 !important;
|
||||
height: unset !important;
|
||||
box-shadow: -8px 0 0 0 #fdd500, -12px 0 0 0 #f9f9db, 8px 0 0 0 #fdd500,
|
||||
12px 0 0 0 #f9f9db;
|
||||
}
|
||||
nav:has(.logo) {
|
||||
position: absolute !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% - 96px);
|
||||
}
|
||||
nav {
|
||||
color: black;
|
||||
}
|
||||
.user-pfp {
|
||||
margin-top: -56px !important;
|
||||
}
|
||||
.outer-title-options,
|
||||
.outer-title-options *,
|
||||
nav.tabs {
|
||||
color: white !important;
|
||||
}
|
||||
.outer-title-options {
|
||||
margin-top: 0 !important;
|
||||
display: unset !important;
|
||||
}
|
||||
.outer-title-options h2 {
|
||||
width: 460px;
|
||||
position: relative;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0 0 10px 0 !important;
|
||||
background: url(/assets/theme/cn/header.bin);
|
||||
}
|
||||
.chuni-userbox-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chuni-userbox button {
|
||||
width: calc(100% / 4) !important;
|
||||
font-size: 0px;
|
||||
}
|
||||
.chuni-userbox-row button {
|
||||
width: unset !important;
|
||||
flex: 0 1 calc(100% / 3) !important;
|
||||
}
|
||||
.chuni-userbox-row button img {
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
}
|
||||
.chuni-nameplate {
|
||||
background: none !important;
|
||||
position: relative !important;
|
||||
left: 20px;
|
||||
}
|
||||
.chuni-userbox {
|
||||
background: none !important;
|
||||
}
|
||||
main {
|
||||
max-width: calc(460px - 40px) !important;
|
||||
margin: 16px auto 0 auto !important;
|
||||
background: #2c4056 !important;
|
||||
border-radius: unset !important;
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
main:has(.user-pfp) {
|
||||
margin: 64px auto 0 auto !important;
|
||||
}
|
||||
.rating-composition {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 !important;
|
||||
}
|
||||
.rating-composition > div {
|
||||
width: 47.5%;
|
||||
margin: 1.25%;
|
||||
}
|
||||
.map-detail-container {
|
||||
background: none !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.lv {
|
||||
border-radius: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 !important;
|
||||
width: 50px !important;
|
||||
}
|
||||
.rank-text {
|
||||
min-width: 20px !important;
|
||||
}
|
||||
.chuni-userbox-container {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.profile-bio-text {
|
||||
white-space: unset !important;
|
||||
}
|
||||
.chuni-penguin-container {
|
||||
padding: 64px 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(249, 249, 219, 1) 0%,
|
||||
rgba(249, 249, 219, 1) 69%,
|
||||
rgba(231, 231, 202, 1) 70%,
|
||||
rgba(231, 231, 202, 1) 100%
|
||||
);
|
||||
}
|
||||
body {
|
||||
background: #fdd500 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
#app {
|
||||
background-position: 50% 60px !important;
|
||||
padding-top: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1028px) {
|
||||
#app {
|
||||
background-size: 90%;
|
||||
}
|
||||
|
||||
.user-pfp {
|
||||
margin-top: -36px !important;
|
||||
}
|
||||
.user-pfp nav {
|
||||
top: -10px !important;
|
||||
}
|
||||
}
|
||||
BIN
AquaNet/public/assets/theme/cn/header.bin
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
AquaNet/public/assets/theme/cn/logo.bin
Normal file
|
After Width: | Height: | Size: 54 KiB |
151
AquaNet/src/App.svelte
Normal file
@ -0,0 +1,151 @@
|
||||
<script lang="ts">
|
||||
import { Route, Router } from "svelte5-router";
|
||||
import Welcome from "./pages/Welcome.svelte";
|
||||
import UserHome from "./pages/UserHome.svelte";
|
||||
import Home from "./pages/Home.svelte";
|
||||
import Ranking from "./pages/Ranking.svelte";
|
||||
import { CARD, USER } from "./libs/sdk";
|
||||
import type { AquaNetUser } from "./libs/generalTypes";
|
||||
import Settings from "./pages/User/Settings.svelte";
|
||||
import MaiPhoto from "./pages/MaiPhoto.svelte";
|
||||
import { pfp, tooltip } from "./libs/ui"
|
||||
import { ANNOUNCEMENT } from "./libs/config";
|
||||
import { t } from "./libs/i18n";
|
||||
import Transfer from "./pages/Transfer/Transfer.svelte";
|
||||
import { link } from "d3";
|
||||
|
||||
console.log(`%c
|
||||
┏━┓ ┳━┓━┓┏━
|
||||
┣━┫┏━┓┓ ┏┏━┓┃ ┃ ┣┫
|
||||
┛ ┗┗━┫┗━┻┗━┻┻━┛━┛┗━
|
||||
┗ v${APP_VERSION}`, `
|
||||
background: linear-gradient(-45deg, rgba(18,194,233,1) 0%, rgba(196,113,237,1) 50%, rgba(246,79,89,1) 100%);
|
||||
font-size: 2em;
|
||||
font-family: Monospace;
|
||||
unicode-bidi: isolate;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;`)
|
||||
|
||||
export let url = "";
|
||||
let me: AquaNetUser
|
||||
let playedMai = false
|
||||
|
||||
if (USER.isLoggedIn())
|
||||
{
|
||||
USER.me().then(m => {
|
||||
me = m
|
||||
CARD.userGames(me.username).then(game => {
|
||||
playedMai = !!game.mai2
|
||||
})
|
||||
}).catch(e => console.error(e))
|
||||
|
||||
const themeStyle = document.createElement("link");
|
||||
themeStyle.rel = "stylesheet";
|
||||
switch (localStorage.getItem("theme")) {
|
||||
case "cn":
|
||||
themeStyle.href = "/assets/theme/cn.css";
|
||||
};
|
||||
if (themeStyle.href)
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
let path = window.location.pathname;
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
{#if path !== "/"}
|
||||
<a class="logo" href={USER.isLoggedIn() ? "/home" : "/"}>
|
||||
<img src="/assets/icons/android-chrome-192x192.png" alt="AquaDX"/>
|
||||
<span>AquaNet</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if ANNOUNCEMENT}
|
||||
<div class="announcement">
|
||||
<strong>{t('navigation.notice')}</strong>: {ANNOUNCEMENT}
|
||||
</div>
|
||||
{/if}
|
||||
<a href="/home">{t('navigation.home').toLowerCase()}</a>
|
||||
<!-- <div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
|
||||
role="button" tabindex="0">{t('navigation.maps').toLowerCase()}</div> -->
|
||||
<a href="/ranking">{t('navigation.rankings').toLowerCase()}</a>
|
||||
{#if playedMai}
|
||||
<a href="/pictures">photo</a>
|
||||
{/if}
|
||||
{#if me}
|
||||
<a href="/u/{me.username}" use:tooltip={t('navigation.profile')}>
|
||||
<img alt="profile" class="pfp" use:pfp={me}/>
|
||||
</a>
|
||||
{/if}
|
||||
</nav>
|
||||
|
||||
<Router {url}>
|
||||
<Route path="/" component={Welcome} />
|
||||
<Route path="/verify" component={Welcome} /> <!-- For email verification only, backwards compatibility with AquaNet2 in the future -->
|
||||
<Route path="/reset-password" component={Welcome} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route path="/ranking" component={Ranking} />
|
||||
<Route path="/ranking/:game" component={Ranking} />
|
||||
<Route path="/u/:username" component={UserHome} />
|
||||
<Route path="/u/:username/:game" component={UserHome} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/pictures" component={MaiPhoto} />
|
||||
<Route path="/transfer" component={Transfer} />
|
||||
</Router>
|
||||
|
||||
<style lang="sass">
|
||||
@use "vars"
|
||||
|
||||
nav
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
align-items: center
|
||||
gap: 32px
|
||||
height: vars.$nav-height
|
||||
|
||||
padding: 0 48px
|
||||
|
||||
z-index: 10
|
||||
position: relative
|
||||
|
||||
img
|
||||
width: 1.5rem
|
||||
height: 1.5rem
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
|
||||
.announcement
|
||||
position: absolute
|
||||
left: 50%
|
||||
transform: translate(-50%, 0)
|
||||
top: 0
|
||||
width: 50%
|
||||
height: 100%
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-content: center
|
||||
z-index: -1
|
||||
background: linear-gradient(90deg, #6f0f0f00 0%, vars.$c-shadow 50%, #6f0f0f00 100%)
|
||||
font-size: 1.125em
|
||||
text-decoration: none !important
|
||||
color: inherit !important
|
||||
|
||||
.pfp
|
||||
width: 2rem
|
||||
height: 2rem
|
||||
|
||||
.logo
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 8px
|
||||
font-weight: bold
|
||||
color: vars.$c-main
|
||||
letter-spacing: 0.2em
|
||||
flex: 1
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
> span
|
||||
display: none
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
justify-content: center
|
||||
|
||||
</style>
|
||||
363
AquaNet/src/app.sass
Normal file
@ -0,0 +1,363 @@
|
||||
@use "sass:color"
|
||||
@use "vars"
|
||||
@import 'components/font/twemoji-flags.css'
|
||||
@import 'lxgw-wenkai-lite-webfont/style.css'
|
||||
|
||||
html
|
||||
height: 100%
|
||||
|
||||
body
|
||||
font-family: vars.$font
|
||||
line-height: 1.5
|
||||
font-weight: 400
|
||||
width: 100%
|
||||
height: 100%
|
||||
margin: 0
|
||||
overflow-x: hidden
|
||||
|
||||
color-scheme: dark
|
||||
color: rgba(255, 255, 255, 0.87)
|
||||
background-color: vars.$c-bg
|
||||
|
||||
font-synthesis: none
|
||||
text-rendering: optimizeLegibility
|
||||
-webkit-font-smoothing: antialiased
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
|
||||
a
|
||||
font-weight: 500
|
||||
color: vars.$c-main
|
||||
text-decoration: inherit
|
||||
|
||||
a:hover
|
||||
color: vars.$c-main
|
||||
|
||||
h1
|
||||
font-size: 3.2em
|
||||
line-height: 1.1
|
||||
|
||||
|
||||
.card
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
border-radius: vars.$border-radius
|
||||
padding: 12px 16px
|
||||
background: vars.$ov-light
|
||||
|
||||
|
||||
blockquote
|
||||
$c1: rgba(255, 149, 149, 0.05)
|
||||
$c2: rgba(255, 152, 152, 0.12)
|
||||
background: repeating-linear-gradient(45deg, $c1, $c1 10px, $c2 10px, $c2 20px)
|
||||
padding: 10px 20px 10px 20px
|
||||
margin: 16px 0
|
||||
border-left: solid #ff7c7c 3px
|
||||
border-radius: vars.$border-radius
|
||||
|
||||
|
||||
#app
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
|
||||
main:not(.no-margin)
|
||||
max-width: 1280px
|
||||
|
||||
margin: 0 auto
|
||||
padding-bottom: 100px
|
||||
|
||||
|
||||
button
|
||||
border-radius: vars.$border-radius
|
||||
border: 1px solid transparent
|
||||
padding: 0.6em 1.2em
|
||||
font-size: 1em
|
||||
font-weight: 500
|
||||
font-family: inherit
|
||||
background-color: vars.$ov-lighter
|
||||
opacity: 0.9
|
||||
cursor: pointer
|
||||
transition: vars.$transition
|
||||
white-space: nowrap
|
||||
|
||||
button:hover
|
||||
border-color: vars.$c-main
|
||||
|
||||
button:focus, button:focus-visible
|
||||
color: vars.$c-main
|
||||
outline: none
|
||||
|
||||
button.error
|
||||
color: unset
|
||||
&:hover
|
||||
border-color: vars.$c-error
|
||||
color: vars.$c-error
|
||||
//background: $c-error
|
||||
//border-color: transparent
|
||||
|
||||
button.icon
|
||||
padding: 0.6em
|
||||
font-size: 1.2em
|
||||
border-radius: 50px
|
||||
@extend .flex-center
|
||||
|
||||
.level-0
|
||||
//--lv-color: #6ED43E
|
||||
--lv-color: 110, 212, 62
|
||||
|
||||
.level-1
|
||||
//--lv-color: #F7B807
|
||||
--lv-color: 247, 184, 7
|
||||
|
||||
.level-2
|
||||
//--lv-color: #FF828D
|
||||
--lv-color: 255, 130, 141
|
||||
|
||||
.level-3
|
||||
//--lv-color: #A051DC
|
||||
--lv-color: 160, 81, 220
|
||||
|
||||
.level-4
|
||||
//--lv-color: #c299e7
|
||||
--lv-color: 194, 153, 231
|
||||
|
||||
.level-5
|
||||
// World's End for chunithm, or Utage for maimai
|
||||
// --lv-color: #eff2e1
|
||||
// --lv-color: 239, 242, 225
|
||||
--lv-text-clip: linear-gradient(110deg, #5ac42c, #5ccc22, #959f26, #cc7c23, #c93143, #8f4876, #4c3eb1, #3c3397)
|
||||
|
||||
.warning
|
||||
color: vars.$c-warning
|
||||
|
||||
.error
|
||||
color: vars.$c-error
|
||||
|
||||
input, textarea
|
||||
border-radius: vars.$border-radius
|
||||
border: 1px solid transparent
|
||||
padding: 0.6em 1.2em
|
||||
font-size: 1em
|
||||
font-weight: 500
|
||||
font-family: inherit
|
||||
background-color: vars.$ov-lighter
|
||||
transition: vars.$transition
|
||||
box-sizing: border-box
|
||||
resize: none
|
||||
|
||||
textarea
|
||||
height: 5em
|
||||
|
||||
// Dropdown
|
||||
select
|
||||
border-radius: vars.$border-radius
|
||||
border: 1px solid transparent
|
||||
padding: 0.6em 1.2em
|
||||
font-size: 1em
|
||||
font-weight: 500
|
||||
font-family: inherit
|
||||
background-color: vars.$ov-lighter
|
||||
transition: vars.$transition
|
||||
box-sizing: border-box
|
||||
|
||||
option
|
||||
background-color: #333
|
||||
color: #fff
|
||||
|
||||
option:hover
|
||||
background-color: #555
|
||||
|
||||
input[type="checkbox"]
|
||||
width: 1.2em
|
||||
height: 1.2em
|
||||
margin: 0
|
||||
padding: 0
|
||||
border: 1px solid vars.$c-main
|
||||
background-color: vars.$ov-lighter
|
||||
appearance: none
|
||||
cursor: pointer
|
||||
flex-shrink: 0
|
||||
|
||||
&:checked
|
||||
background-color: vars.$c-main
|
||||
border-color: vars.$c-main
|
||||
|
||||
label
|
||||
cursor: pointer
|
||||
|
||||
input:focus, input:focus-visible
|
||||
border: 1px solid vars.$c-main
|
||||
outline: none
|
||||
|
||||
input.warning
|
||||
border: 1px solid vars.$c-warning
|
||||
|
||||
input.error
|
||||
border: 1px solid vars.$c-error
|
||||
|
||||
.flex-center
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
|
||||
.inline-flex-center
|
||||
display: inline-flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
.clickable
|
||||
cursor: pointer
|
||||
user-select: none
|
||||
|
||||
|
||||
// Content containers
|
||||
.content-main
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 20px
|
||||
margin: 100px auto 0
|
||||
padding: 32px 32px 128px
|
||||
min-height: 100%
|
||||
max-width: vars.$w-max
|
||||
|
||||
background-color: color.adjust(vars.$c-bg, $lightness: -3%)
|
||||
border-radius: 16px 16px 0 0
|
||||
|
||||
@media (max-width: #{vars.$w-max + (64px) * 2})
|
||||
margin: 100px 32px 0
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
margin: 100px 0 0
|
||||
|
||||
.fw-block
|
||||
margin-left: -32px
|
||||
margin-right: -32px
|
||||
padding: 12px 32px
|
||||
background-color: vars.$ov-darker
|
||||
// Inner shadow
|
||||
box-shadow: inset 0 10px 10px -2px vars.$c-shadow, inset 0 -10px 10px -2px vars.$c-shadow
|
||||
|
||||
> h2.outer-title, > .outer-title-options
|
||||
margin-top: -5rem
|
||||
margin-bottom: 1rem
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
text-align: center
|
||||
|
||||
> .outer-title-options
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
|
||||
nav
|
||||
display: flex
|
||||
flex-direction: row
|
||||
gap: 10px
|
||||
top: 4px
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
flex-direction: column
|
||||
|
||||
> h2, > .outer-title-options > h2
|
||||
margin: 0
|
||||
|
||||
main.content
|
||||
@extend .content-main
|
||||
|
||||
// Not used. still need a lot of work
|
||||
.content-popup
|
||||
position: absolute
|
||||
inset: 0
|
||||
|
||||
> div
|
||||
@extend .content-main
|
||||
position: absolute
|
||||
inset: 0
|
||||
top: 100px
|
||||
background: rgba(color.adjust(vars.$c-bg, $lightness: -3%), 0.9)
|
||||
backdrop-filter: blur(5px)
|
||||
box-shadow: 0 0 10px 6px rgba(black, 0.4)
|
||||
max-width: calc(vars.$w-max + 20px)
|
||||
|
||||
@media (max-width: #{vars.$w-max + (64px) * 2})
|
||||
margin: 100px 22px 0
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
margin: 100px 0 0
|
||||
|
||||
|
||||
|
||||
// Overlay
|
||||
.overlay
|
||||
position: fixed
|
||||
inset: 0
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.5)
|
||||
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
z-index: 1000
|
||||
backdrop-filter: blur(5px)
|
||||
|
||||
h2, p
|
||||
user-select: none
|
||||
margin: 0
|
||||
|
||||
> div
|
||||
background-color: vars.$c-bg
|
||||
padding: 2rem
|
||||
border-radius: vars.$border-radius
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 1rem
|
||||
|
||||
max-width: 400px
|
||||
|
||||
.aqua-tooltip
|
||||
z-index: 900
|
||||
|
||||
.no-margin
|
||||
margin: 0
|
||||
|
||||
nav
|
||||
> div, > a
|
||||
cursor: pointer
|
||||
transition: vars.$transition
|
||||
text-decoration: underline 1px solid transparent
|
||||
text-underline-offset: 0.1em
|
||||
|
||||
display: flex
|
||||
align-items: center
|
||||
color: unset
|
||||
font-weight: unset
|
||||
|
||||
&:hover
|
||||
color: vars.$c-main
|
||||
text-decoration-color: vars.$c-main
|
||||
text-underline-offset: 0.5em
|
||||
|
||||
&.active
|
||||
color: vars.$c-main
|
||||
|
||||
|
||||
.hide-scrollbar
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
-ms-overflow-style: none
|
||||
scrollbar-width: none
|
||||
|
||||
|
||||
.aqua-tooltip
|
||||
box-shadow: 0 0 5px 0 vars.$c-shadow
|
||||
border-radius: vars.$border-radius
|
||||
position: absolute
|
||||
padding: 4px 8px
|
||||
background: vars.$ov-lighter
|
||||
backdrop-filter: blur(5px)
|
||||
|
||||
|
||||
76
AquaNet/src/components/ActionCard.svelte
Normal file
@ -0,0 +1,76 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
|
||||
export let color: string = '179, 198, 255'
|
||||
export let icon: string
|
||||
|
||||
// Manually positioned icons
|
||||
const iconPos = [
|
||||
[1, 0.5, 2],
|
||||
[6, 2, 1.5],
|
||||
[-0.5, 4.5, 1.3],
|
||||
[5, -0.5],
|
||||
[3.5, 4.5],
|
||||
[9.5, 0.3, 1.2],
|
||||
[12.5, 2.5, 0.8],
|
||||
[10, 4.4, 0.8],
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown>
|
||||
<slot/>
|
||||
|
||||
<div class="icons">
|
||||
{#each iconPos as [x, y, size], i}
|
||||
<Icon icon={icon} style={`top: ${y}rem; right: ${x}rem; font-size: ${size || 1}em`} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
@use '../vars'
|
||||
|
||||
.action-card
|
||||
overflow: hidden
|
||||
padding: 1rem
|
||||
border-radius: vars.$border-radius
|
||||
box-shadow: 0 5px 5px 1px vars.$c-shadow
|
||||
transition: all 0.2s ease
|
||||
cursor: pointer
|
||||
position: relative
|
||||
background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%)
|
||||
outline: 1px solid transparent
|
||||
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0))
|
||||
|
||||
&:hover
|
||||
box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow
|
||||
transform: translateY(-3px)
|
||||
|
||||
// Drop shadow glow
|
||||
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
|
||||
outline-color: rgba(var(--card-color), 0.5)
|
||||
|
||||
:global(span)
|
||||
font-size: 1.2rem
|
||||
display: block
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
.icons
|
||||
position: absolute
|
||||
inset: 0
|
||||
color: rgba(var(--card-color), 0.5)
|
||||
font-size: 2rem
|
||||
transition: all 0.2s ease
|
||||
z-index: -1
|
||||
mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%)
|
||||
opacity: 0.8
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
opacity: 0.6
|
||||
|
||||
:global(> svg)
|
||||
position: absolute
|
||||
rotate: 20deg
|
||||
</style>
|
||||
62
AquaNet/src/components/CommunityCard.svelte
Normal file
@ -0,0 +1,62 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
|
||||
export let color: string = '179, 198, 255'
|
||||
export let icon: string
|
||||
|
||||
</script>
|
||||
|
||||
<div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown>
|
||||
<slot/>
|
||||
|
||||
<div class="icon">
|
||||
<Icon icon={icon} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
@use '../vars'
|
||||
|
||||
.action-card
|
||||
overflow: hidden
|
||||
padding: 1rem
|
||||
border-radius: vars.$border-radius
|
||||
box-shadow: 0 5px 5px 1px vars.$c-shadow
|
||||
transition: all 0.2s ease
|
||||
cursor: pointer
|
||||
position: relative
|
||||
background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%)
|
||||
outline: 1px solid transparent
|
||||
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0))
|
||||
|
||||
&:hover
|
||||
box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow
|
||||
transform: translateY(-3px)
|
||||
|
||||
// Drop shadow glow
|
||||
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
|
||||
outline-color: rgba(var(--card-color), 0.5)
|
||||
|
||||
:global(span)
|
||||
font-size: 1.2rem
|
||||
display: block
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
.icon
|
||||
position: absolute
|
||||
display: flex
|
||||
color: rgba(var(--card-color), 0.5)
|
||||
font-size: 4rem
|
||||
right: 0
|
||||
bottom: 0
|
||||
padding: .5rem
|
||||
transition: all 0.2s ease
|
||||
z-index: -1
|
||||
mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%)
|
||||
opacity: 0.8
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
opacity: 0.6
|
||||
</style>
|
||||
32
AquaNet/src/components/MunetRegisterBanner.svelte
Normal file
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
export let username: string;
|
||||
export let email: string;
|
||||
|
||||
let shouldShow = navigator.language.startsWith('zh');
|
||||
|
||||
// 会导致瞬间出现,但是不知道为什么 svelte 的 transition 动画不工作
|
||||
// if (!shouldShow) {
|
||||
// fetch('https://47.122.72.135/ip/isChina')
|
||||
// .then(it => it.json())
|
||||
// .then(it => shouldShow = it)
|
||||
// .catch(() => shouldShow = false);
|
||||
// }
|
||||
|
||||
const jump = () => {
|
||||
const params = new URLSearchParams();
|
||||
if (username) params.set('username', username);
|
||||
if (email) params.set('email', email);
|
||||
location.href = `https://portal.mumur.net/register?${params.toString()}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldShow}
|
||||
<div class="cursor-pointer" on:click={jump}>
|
||||
<h2>MuNET 了解一下!</h2>
|
||||
<div>
|
||||
<p>MuNET 是 AquaDX 的继任者,提供更适合中国用户的服务器和更好的游戏体验。</p>
|
||||
<p>如果你还没有游戏数据,建议在 MuNET 上创建账号并开始游戏。点击立即前往</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
64
AquaNet/src/components/RankDetails.svelte
Normal file
@ -0,0 +1,64 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { t } from "../libs/i18n";
|
||||
import type { GenericGameSummary } from "../libs/generalTypes";
|
||||
|
||||
export let g: GenericGameSummary
|
||||
|
||||
const detail = Object.entries(g.detailedRanks).toSorted((a, b) => +b[0] - +a[0])
|
||||
</script>
|
||||
|
||||
<div class="rank-detail-container fw-block" transition:slide>
|
||||
<div>
|
||||
<h2>{t("UserHome.RankDetail.Title")}</h2>
|
||||
<table><tbody>
|
||||
<!-- rankDetails: { Level : { Rank : Count } } -->
|
||||
<!-- Rows are levels, columns are ranks -->
|
||||
|
||||
<!-- Headers -->
|
||||
<tr>
|
||||
<th>{t("UserHome.RankDetail.Level")}</th>
|
||||
{#each Object.values(g.ranks) as rankMap}<th>{rankMap.name}</th>{/each}
|
||||
</tr>
|
||||
|
||||
<!-- Data -->
|
||||
{#each detail as [level, rankMap]}
|
||||
<tr>
|
||||
<td>{level}</td>
|
||||
{#each Object.values(rankMap) as count}<td>{count}</td>{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
|
||||
.rank-detail-container
|
||||
> div
|
||||
margin: 1em auto
|
||||
max-width: 500px
|
||||
|
||||
table
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
table-layout: fixed
|
||||
|
||||
th:not(:first-child)
|
||||
background: vars.$grad-special
|
||||
-webkit-background-clip: text
|
||||
-webkit-text-fill-color: transparent
|
||||
background-clip: text
|
||||
color: vars.$c-main
|
||||
padding: 0.5em
|
||||
|
||||
th, td
|
||||
padding: 0.5em
|
||||
text-align: center
|
||||
|
||||
&:first-child
|
||||
color: vars.$c-main
|
||||
</style>
|
||||
143
AquaNet/src/components/RatingCompSong.svelte
Normal file
@ -0,0 +1,143 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { t } from "../libs/i18n";
|
||||
import { type GameName, type ParsedComposition, roundFloor } from "../libs/scoring";
|
||||
import { coverNotFound } from "../libs/ui";
|
||||
import { tooltip } from "../libs/ui";
|
||||
import useLocalStorage from "../libs/hooks/useLocalStorage.svelte";
|
||||
|
||||
export let game: GameName
|
||||
export let p: ParsedComposition
|
||||
const rounding = useLocalStorage("rounding", true)
|
||||
</script>
|
||||
|
||||
<div class="map-detail-container" transition:slide>
|
||||
<div class="scores">
|
||||
<div>
|
||||
<img src={p.img} alt="" on:error={coverNotFound} />
|
||||
<div class="info">
|
||||
<div class="first-line">
|
||||
<div class="song-title">{p.name ?? t("UserHome.UnknownSong")}</div>
|
||||
<span class={`lv level-${p.diffId === 10 ? 3 : p.diffId}`}>
|
||||
{ p.difficulty ?? '-' }
|
||||
</span>
|
||||
</div>
|
||||
<div class="second-line">
|
||||
<span class={`rank-${p.rank[0]}`}>
|
||||
<span class="rank-text">{p.rank.replace("p", "+")}</span>
|
||||
<span class="rank-num" use:tooltip={(p.score / 10000).toFixed(4)}>
|
||||
{rounding.value ? roundFloor(p.score, game, 1) : (p.score / 10000).toFixed(4)}%
|
||||
</span>
|
||||
</span>
|
||||
{#if p.ratingChange !== undefined}
|
||||
<span class="dx-change">{ p.ratingChange }</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
|
||||
@use "../vars"
|
||||
vars.$gap: 20px
|
||||
|
||||
.map-detail-container
|
||||
background-color: rgb(35,35,35)
|
||||
border-radius: vars.$border-radius
|
||||
overflow: hidden
|
||||
|
||||
.scores
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-wrap: wrap
|
||||
gap: vars.$gap
|
||||
|
||||
// Image and song info
|
||||
> div
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 12px
|
||||
max-width: 100%
|
||||
box-sizing: border-box
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
|
||||
// Song info and score
|
||||
> div.info
|
||||
flex: 1
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
overflow: hidden
|
||||
flex-direction: column
|
||||
|
||||
.first-line
|
||||
display: flex
|
||||
flex-direction: row
|
||||
|
||||
// Limit song name to one line
|
||||
.song-title
|
||||
flex: 1
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
// Make song score and rank not wrap
|
||||
> div:last-child
|
||||
white-space: nowrap
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
.rank-text
|
||||
text-align: left
|
||||
|
||||
.rank-S
|
||||
// Gold green gradient on text
|
||||
background: vars.$grad-special
|
||||
-webkit-background-clip: text
|
||||
color: transparent
|
||||
|
||||
.rank-A
|
||||
color: #ff8a8a
|
||||
|
||||
.rank-B
|
||||
color: #6ba6ff
|
||||
|
||||
.lv
|
||||
width: 30px
|
||||
text-align: center
|
||||
background: rgba(var(--lv-color), 0.6)
|
||||
padding: 0 6px
|
||||
border-radius: 0 vars.$border-radius 0 vars.$border-radius
|
||||
|
||||
// Inset shadow, like it's a paper below this card with a cut
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.5)
|
||||
|
||||
span
|
||||
display: inline-block
|
||||
text-align: left
|
||||
|
||||
.second-line
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
|
||||
// Vertical table-like alignment
|
||||
span.rank-text
|
||||
min-width: 40px
|
||||
span.rank-num
|
||||
min-width: 60px
|
||||
span.dx-change
|
||||
margin-right: 0.5rem
|
||||
color: vars.$c-good
|
||||
</style>
|
||||
42
AquaNet/src/components/RatingComposition.svelte
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import RatingCompSong from "./RatingCompSong.svelte";
|
||||
import { parseComposition, type GameName } from "../libs/scoring";
|
||||
import { type MusicMeta } from "../libs/generalTypes";
|
||||
|
||||
export let title: string;
|
||||
export let comp: string | undefined;
|
||||
export let allMusics: Record<string, MusicMeta>;
|
||||
export let game: GameName;
|
||||
export let top: number | undefined = undefined;
|
||||
|
||||
let split = comp?.split(",")?.filter(it => it.split(":")[0] !== '0')
|
||||
?.map(it => parseComposition(it, allMusics, game))
|
||||
|
||||
if (top) split = split?.toSorted((a, b) => b.score - a.score).slice(0, top)
|
||||
if (split) console.log("Split", split)
|
||||
</script>
|
||||
|
||||
{#if split && comp}
|
||||
<div>
|
||||
<h2>{title}</h2>
|
||||
<div class="rating-composition">
|
||||
{#each split as p}
|
||||
<div>
|
||||
<RatingCompSong {p} {game}/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
|
||||
.rating-composition
|
||||
display: grid
|
||||
// 3 columns
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
|
||||
gap: vars.$gap
|
||||
</style>
|
||||
52
AquaNet/src/components/StatusOverlays.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { ConfirmProps } from "../libs/generalTypes";
|
||||
import { t } from "../libs/i18n"
|
||||
import Loading from './ui/Loading.svelte';
|
||||
import Error from './ui/Error.svelte';
|
||||
|
||||
// Props
|
||||
export let confirm: ConfirmProps | null = null
|
||||
export let error: string | null = null
|
||||
export let loading: boolean = false
|
||||
|
||||
function doConfirm(fn?: () => void) {
|
||||
confirm = null
|
||||
fn && fn()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if confirm}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>{confirm.title}</h2>
|
||||
<span>{confirm.message}</span>
|
||||
|
||||
<div class="actions">
|
||||
{#if confirm.cancel}
|
||||
<button on:click={() => doConfirm(confirm?.cancel)}>{t('action.cancel')}</button>
|
||||
{/if}
|
||||
<button on:click={() => doConfirm(confirm?.confirm)} class:error={confirm.dangerous}>{t('action.confirm')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<Error {error}/>
|
||||
{/if}
|
||||
|
||||
{#if loading && !error}
|
||||
<Loading/>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
|
||||
button
|
||||
width: 100%
|
||||
</style>
|
||||
76
AquaNet/src/components/Tooltip.svelte
Normal file
@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
|
||||
export let triggeredBy: string
|
||||
export let loading: boolean = false
|
||||
let isHovered = false
|
||||
let x: number, y: number
|
||||
let targets: Element[] = []
|
||||
|
||||
onMount(() => {
|
||||
targets = [...document.querySelectorAll(triggeredBy)]
|
||||
targets.forEach((el) => {
|
||||
el.addEventListener('mouseover', mouseOver)
|
||||
el.addEventListener('mousemove', mouseMove)
|
||||
el.addEventListener('mouseleave', mouseLeave)
|
||||
})
|
||||
if (targets.length === 0) {
|
||||
console.warn(`No elements found with selector "${triggeredBy}"`)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
targets.forEach((el) => {
|
||||
el.removeEventListener('mouseover', mouseOver)
|
||||
el.removeEventListener('mousemove', mouseMove)
|
||||
el.removeEventListener('mouseleave', mouseLeave)
|
||||
})
|
||||
})
|
||||
|
||||
function mouseOver(event: MouseEvent) {
|
||||
console.log('over')
|
||||
isHovered = true
|
||||
updatePosition(event)
|
||||
}
|
||||
|
||||
function mouseMove(event: MouseEvent) {
|
||||
console.log('move')
|
||||
updatePosition(event)
|
||||
}
|
||||
|
||||
function updatePosition(event: MouseEvent) {
|
||||
x = event.pageX + 5
|
||||
y = event.pageY + 20
|
||||
}
|
||||
|
||||
function mouseLeave() {
|
||||
console.log('leave')
|
||||
isHovered = false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isHovered}
|
||||
<div style="top: {y}px; left: {x}px" class="tooltip" class:loading>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
|
||||
.tooltip
|
||||
position: absolute
|
||||
z-index: 900
|
||||
background: white
|
||||
padding: 10px 16px
|
||||
border-radius: vars.$border-radius
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1)
|
||||
pointer-events: none
|
||||
white-space: nowrap
|
||||
color: #242424
|
||||
transform: translate(-50%, 0)
|
||||
transition: opacity 0.2s
|
||||
|
||||
&.loading
|
||||
opacity: 0
|
||||
</style>
|
||||
56
AquaNet/src/components/UserCard.svelte
Normal file
@ -0,0 +1,56 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import type { GenericGameSummary } from "../libs/generalTypes";
|
||||
import { GAME } from "../libs/sdk";
|
||||
import type { GameName } from "../libs/scoring";
|
||||
import { pfp, pfpNotFound } from "../libs/ui";
|
||||
|
||||
export let username: string
|
||||
export let game: GameName
|
||||
export let setLoading: (loading: boolean) => void = () => {}
|
||||
|
||||
let data: GenericGameSummary
|
||||
let error = ""
|
||||
|
||||
setLoading(true)
|
||||
GAME.userSummary(username, game).then(d => data = d).catch(e => error = e).finally(_ => setLoading(false))
|
||||
</script>
|
||||
|
||||
{#if !data}
|
||||
<div>Loading...</div>
|
||||
{:else if error}
|
||||
<div>Error: {error}</div>
|
||||
{:else}
|
||||
<div class="user-card">
|
||||
<img use:pfp={data.aquaUser} alt="Profile" />
|
||||
<div class="details">
|
||||
<span class="in-game-name">{data.name}</span>
|
||||
<span class="username">@{username}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
|
||||
.user-card
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: vars.$gap
|
||||
|
||||
.details
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
|
||||
.username
|
||||
font-size: 0.8em
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: 50%
|
||||
object-fit: cover
|
||||
object-position: center
|
||||
</style>
|
||||
29
AquaNet/src/components/chart/Line.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { Chart, Tooltip, type ChartData, type ChartOptions } from 'chart.js';
|
||||
import type { HTMLCanvasAttributes } from 'svelte/elements';
|
||||
import 'chart.js/auto';
|
||||
|
||||
interface Props extends HTMLCanvasAttributes {
|
||||
data: ChartData<'line', any, string>
|
||||
options: ChartOptions<'line'>
|
||||
}
|
||||
const { data, options, ...rest }: Props = $props()
|
||||
|
||||
Chart.register(Tooltip)
|
||||
|
||||
let canvasElem: HTMLCanvasElement
|
||||
let chart: Chart
|
||||
|
||||
$effect(() => {
|
||||
chart = new Chart(canvasElem, { type: 'line', data, options })
|
||||
return () => chart.destroy()
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (!chart) return
|
||||
chart.data = data
|
||||
chart.update()
|
||||
})
|
||||
</script>
|
||||
|
||||
<canvas bind:this={canvasElem} {...rest}></canvas>
|
||||
BIN
AquaNet/src/components/font/TwemojiCountryFlags.woff2
Normal file
11
AquaNet/src/components/font/twemoji-flags.css
Normal file
@ -0,0 +1,11 @@
|
||||
@font-face {
|
||||
font-family: 'TwemojiCountryFlags';
|
||||
src: url('./TwemojiCountryFlags.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.country {
|
||||
font-family: TwemojiCountryFlags,"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif;
|
||||
font-size: 2em;
|
||||
}
|
||||
211
AquaNet/src/components/settings/ChuniMatchingSettings.svelte
Normal file
@ -0,0 +1,211 @@
|
||||
<script lang="ts">
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { CHU3_MATCHINGS } from "../../libs/config.js";
|
||||
import type { ChusanMatchingOption, GameOption } from "../../libs/generalTypes.js";
|
||||
import { t, ts } from "../../libs/i18n.js";
|
||||
import { DATA, SETTING } from "../../libs/sdk.js";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
|
||||
let custom = false
|
||||
let overlay = false
|
||||
let loading = false
|
||||
let error = ""
|
||||
|
||||
let changed: string[] = [];
|
||||
let symbols: Record<number, number> = {};
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
let submitting: string | undefined | null;
|
||||
|
||||
let existingUrl = "";
|
||||
SETTING.get().then(s => {
|
||||
existingUrl = s.filter(it => it.key === 'chusanMatchingServer')[0]?.value
|
||||
|
||||
if (existingUrl && !CHU3_MATCHINGS.some(it => it.matching === existingUrl)) {
|
||||
custom = true
|
||||
}
|
||||
|
||||
const symbolKey = "chusanSymbolChat"
|
||||
s.forEach(opt => {
|
||||
if (opt.key.substring(0, symbolKey.length) == symbolKey && opt.value)
|
||||
symbols[parseInt(opt.key.substring(symbolKey.length))] = opt.value;
|
||||
})
|
||||
})
|
||||
|
||||
async function fetchSymbolData() {
|
||||
allItems = await DATA.allItems('chu3').catch(_ => {
|
||||
loading = false
|
||||
error = t("userbox.error.nodata")
|
||||
}) as typeof allItems
|
||||
}
|
||||
|
||||
async function submitSymbol(id: number) {
|
||||
if (submitting) return false
|
||||
const field = `chusanSymbolChat${id + 1}`;
|
||||
submitting = field
|
||||
|
||||
await SETTING.set(field, symbols[id + 1]).catch(e => error = e.message).finally(() => submitting = null);
|
||||
changed = changed.filter(v => v != `chusanSymbolChat${id}`)
|
||||
return true
|
||||
}
|
||||
|
||||
// Click on "Custom" option"
|
||||
function clickCustom() {
|
||||
custom = true
|
||||
overlay = false
|
||||
}
|
||||
|
||||
// Click on a matching option, set the reflector and matching server
|
||||
function clickOption(opt: ChusanMatchingOption) {
|
||||
Promise.all([
|
||||
SETTING.set('chusanMatchingReflector', opt.reflector),
|
||||
SETTING.set('chusanMatchingServer', opt.matching),
|
||||
]).then(() => {
|
||||
overlay = false
|
||||
custom = false
|
||||
existingUrl = opt.matching
|
||||
}).catch(e => error = e.message)
|
||||
}
|
||||
</script>
|
||||
|
||||
<StatusOverlays {error} {loading}/>
|
||||
|
||||
<div class="matching">
|
||||
<h2>{t("userbox.header.matching")}</h2>
|
||||
<p class="notice">{t("settings.cabNotice")}</p>
|
||||
|
||||
<div class="matching-selector">
|
||||
<button on:click={_ => overlay = true}>{t('userbox.matching.select')}</button>
|
||||
</div>
|
||||
|
||||
{#if custom}
|
||||
<GameSettingFields game="chu3-matching"/>
|
||||
{/if}
|
||||
|
||||
<h2>{t("userbox.header.matching.symbolChat")}</h2>
|
||||
{#await fetchSymbolData() then}
|
||||
{#each {length: 4}, i}
|
||||
<div class="field">
|
||||
<label for={`chusanSymbolChat${i}`}>{ts(`userbox.matching.symbolChat`) + ` #${i + 1}`}</label>
|
||||
<div>
|
||||
<select bind:value={symbols[i + 1]} id={`chusanSymbolChat${i}`} on:change={() => {changed = [...changed, `chusanSymbolChat${i}`];}}>
|
||||
<option value={null}>{ts(`userbox.matching.symbolChat.default`)}</option>
|
||||
{#each Object.entries(allItems.symbolChat).filter((f) => parseInt(f[0]) !== 0) as [id, option]}
|
||||
<option value={parseInt(id)}>{option?.name || `(unknown ${id})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(`chusanSymbolChat${i}`)}
|
||||
<button transition:slide={{axis: "x"}} disabled={!!submitting} on:click={() => submitSymbol(i)}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
{#if overlay}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<div>
|
||||
<h2>{t('userbox.header.matching')}</h2>
|
||||
<p>{t('userbox.matching.select.sub')}</p>
|
||||
</div>
|
||||
<div class="options">
|
||||
<!-- Selectable options -->
|
||||
{#each CHU3_MATCHINGS as option}
|
||||
<div class="clickable option" on:click={() => clickOption(option)}
|
||||
role="button" tabindex="0" on:keypress={e => e.key === 'Enter' && clickOption(option)}
|
||||
class:selected={!custom && existingUrl === option.matching}>
|
||||
|
||||
<span class="name">{option.name}</span>
|
||||
<div class="links">
|
||||
<a href={option.ui} target="_blank" rel="noopener">{t('userbox.matching.option.ui')}</a> /
|
||||
<a href={option.guide} target="_blank" rel="noopener">{t('userbox.matching.option.guide')}</a>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="coop">
|
||||
<span>{t('userbox.matching.option.collab')}</span>
|
||||
<div>
|
||||
{#each option.coop as coop}
|
||||
<span>{coop}</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<!-- Placeholder option for "Custom" -->
|
||||
<div class="clickable option" on:click={clickCustom}
|
||||
role="button" tabindex="0" on:keypress={e => e.key === 'Enter' && clickCustom()}
|
||||
class:selected={custom}>
|
||||
|
||||
<span class="name">{t('userbox.matching.custom.name')}</span>
|
||||
<p class="notice custom">{t('userbox.matching.custom.sub')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.matching
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
h2
|
||||
margin-bottom: 0
|
||||
|
||||
p.notice
|
||||
opacity: 0.6
|
||||
margin: 0
|
||||
|
||||
&.custom
|
||||
font-size: 0.9rem
|
||||
|
||||
.options
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
gap: 1rem
|
||||
|
||||
.option
|
||||
flex: 1
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
|
||||
border-radius: vars.$border-radius
|
||||
background: vars.$ov-light
|
||||
padding: 1rem
|
||||
min-width: 150px
|
||||
|
||||
&.selected
|
||||
border: 1px solid vars.$c-main
|
||||
|
||||
.divider
|
||||
width: 100%
|
||||
height: 0.5px
|
||||
background: white
|
||||
opacity: 0.2
|
||||
margin: 0.8rem 0
|
||||
|
||||
.name
|
||||
font-size: 1.1rem
|
||||
font-weight: bold
|
||||
|
||||
.coop
|
||||
text-align: center
|
||||
|
||||
div
|
||||
display: flex
|
||||
flex-direction: column
|
||||
font-size: 0.9rem
|
||||
opacity: 0.6
|
||||
|
||||
</style>
|
||||
667
AquaNet/src/components/settings/ChuniSettings.svelte
Normal file
@ -0,0 +1,667 @@
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
type AquaNetUser,
|
||||
type UserBox,
|
||||
type UserItem,
|
||||
} from "../../libs/generalTypes";
|
||||
import { DATA, USER, USERBOX, GAME } from "../../libs/sdk";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import { FADE_IN, FADE_OUT, USERBOX_DEFAULT_URL } from "../../libs/config";
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
|
||||
import { userboxFileProcess, ddsDB, initializeDb } from "../../libs/userbox/userbox"
|
||||
|
||||
import ChuniPenguinComponent from "./userbox/ChuniPenguin.svelte"
|
||||
import ChuniUserplateComponent from "./userbox/ChuniUserplate.svelte";
|
||||
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import { DDS } from "../../libs/userbox/dds";
|
||||
import ChuniMatchingSettings from "./ChuniMatchingSettings.svelte";
|
||||
import InputField from "../ui/InputField.svelte";
|
||||
|
||||
let user: AquaNetUser
|
||||
let [loading, error, submitting, preview] = [true, "", "", ""]
|
||||
let changed: string[] = [];
|
||||
|
||||
// Available (unlocked) options for each kind of item
|
||||
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
let iKinds = { namePlate: 1, frame: 2, trophy: 3, trophySub1: 4, trophySub2: 5, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
|
||||
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
|
||||
let userbox: UserBox
|
||||
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
|
||||
// iKey should match allItems keys, and ubKey should match userbox keys
|
||||
let userItems: { iKey: string, ubKey: keyof UserBox, items: UserItem[] }[] = []
|
||||
let userNameField: any
|
||||
|
||||
// Submit changes
|
||||
function submit(field: keyof UserBox) {
|
||||
let obj = { field, value: userbox[field] }
|
||||
if (submitting) return
|
||||
submitting = obj.field
|
||||
|
||||
USERBOX.setUserBox(obj)
|
||||
.then(() => changed = changed.filter((c) => c !== obj.field))
|
||||
.catch(e => error = e.message)
|
||||
.finally(() => submitting = "")
|
||||
}
|
||||
|
||||
// Fetch data from the server
|
||||
async function fetchData() {
|
||||
const profile = await USERBOX.getProfile().catch(_ => {
|
||||
loading = false
|
||||
error = t("userbox.error.nodata")
|
||||
})
|
||||
if (!profile) return
|
||||
userbox = profile.user
|
||||
userNameField = {key: "gameUsername", value: userbox.userName, type: "String"}
|
||||
userItems = Object.entries(iKinds).flatMap(([iKey, iKind]) => {
|
||||
if (iKey != 'avatarAccessory') {
|
||||
let ubKey = `${iKey}Id`
|
||||
if (iKey.slice('trophy'.length, 'trophy'.length + 3) == "Sub") {
|
||||
ubKey = `trophyIdSub${iKey.slice('trophySub'.length, 'trophySub'.length + 1)}`;
|
||||
iKey = `trophy`;
|
||||
}
|
||||
if (ubKey == 'namePlateId') ubKey = 'nameplateId'
|
||||
if (ubKey == 'systemVoiceId') ubKey = 'voiceId'
|
||||
return [{ iKey, ubKey: ubKey as keyof UserBox,
|
||||
items: profile.items.filter(x => x.itemKind === iKind || (iKey == "trophy" && x.itemKind == 3))
|
||||
}]
|
||||
}
|
||||
|
||||
return avatarKinds.map((aKind, i) => {
|
||||
let items = profile.items.filter(x => x.itemKind === iKind && Math.floor(x.itemId / 100000) % 10 === i + 1)
|
||||
return { iKey, ubKey: `avatar${aKind}` as keyof UserBox, items }
|
||||
})
|
||||
})
|
||||
|
||||
allItems = await DATA.allItems('chu3').catch(_ => {
|
||||
loading = false
|
||||
error = t("userbox.error.nodata")
|
||||
}) as typeof allItems
|
||||
|
||||
console.log("User Items", userItems)
|
||||
console.log("All items", allItems)
|
||||
console.log("Userbox", userbox)
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
USER.me().then(u => {
|
||||
if (!u) throw new Error(t("userbox.error.nodata"))
|
||||
user = u
|
||||
return fetchData()
|
||||
}).catch((e) => { loading = false; error = e.message });
|
||||
|
||||
function exportData() {
|
||||
submitting = "export"
|
||||
GAME.export('chu3')
|
||||
.then(data => download(JSON.stringify(data), `AquaDX_chu3_export_${userbox.userName}.json`))
|
||||
.catch(e => error = e.message)
|
||||
.finally(() => submitting = "")
|
||||
}
|
||||
|
||||
async function exportBatchManual() {
|
||||
submitting = "batchExport"
|
||||
|
||||
const DIFFICULTY_MAP: Record<number, string> = {
|
||||
0: "BASIC",
|
||||
1: "ADVANCED",
|
||||
2: "EXPERT",
|
||||
3: "MASTER",
|
||||
4: "ULTIMA"
|
||||
} as const // WORLD'S END scores not supported by Tachi
|
||||
const DAN_MAP: Record<number, string> = {
|
||||
1: "DAN_I",
|
||||
2: "DAN_II",
|
||||
3: "DAN_III",
|
||||
4: "DAN_IV",
|
||||
5: "DAN_V",
|
||||
6: "DAN_INFINITE"
|
||||
} as const
|
||||
const SKILL_IDS: Record<number, string> = {
|
||||
100009: 'CATASTROPHY',
|
||||
102009: 'CATASTROPHY',
|
||||
103007: 'CATASTROPHY',
|
||||
|
||||
100008: 'ABSOLUTE',
|
||||
101008: 'ABSOLUTE',
|
||||
102008: 'ABSOLUTE',
|
||||
103006: 'ABSOLUTE',
|
||||
|
||||
100007: 'BRAVE',
|
||||
101007: 'BRAVE',
|
||||
102007: 'BRAVE',
|
||||
103005: 'BRAVE',
|
||||
|
||||
100005: 'HARD',
|
||||
100006: 'HARD',
|
||||
101004: 'HARD',
|
||||
101005: 'HARD',
|
||||
101006: 'HARD',
|
||||
102004: 'HARD',
|
||||
102005: 'HARD',
|
||||
102006: 'HARD',
|
||||
103002: 'HARD',
|
||||
103003: 'HARD',
|
||||
103004: 'HARD'
|
||||
} as const
|
||||
// Shamelessly stolen from https://github.com/beer-psi/saekawa/commit/b3bee13e126df2f4e2a449bdf971debb8c95ba40, needs to be updated every major version :(
|
||||
|
||||
let data: any
|
||||
let output: any = {
|
||||
"meta": {
|
||||
"game": "chunithm",
|
||||
"playtype": "Single",
|
||||
"service": "AquaDX-Manual"
|
||||
},
|
||||
"scores": [],
|
||||
"classes": {}
|
||||
}
|
||||
|
||||
try {
|
||||
data = await GAME.export('chu3')
|
||||
}
|
||||
catch (e) {
|
||||
error = e.message
|
||||
submitting = ""
|
||||
return
|
||||
}
|
||||
|
||||
if (data && "userPlaylogList" in data) {
|
||||
for (let score of data.userPlaylogList) {
|
||||
let clearLamp = null
|
||||
let noteLamp = null
|
||||
|
||||
if (score.level in DIFFICULTY_MAP) {
|
||||
if (score.isClear) {
|
||||
clearLamp = score.skillId in SKILL_IDS ? SKILL_IDS[score.skillId] : "CLEAR"
|
||||
}
|
||||
else {
|
||||
clearLamp = "FAILED"
|
||||
}
|
||||
|
||||
if (score.score === 1010000) {
|
||||
noteLamp = "ALL JUSTICE CRITICAL"
|
||||
}
|
||||
else if (score.isAllJustice) {
|
||||
noteLamp = "ALL JUSTICE"
|
||||
}
|
||||
else if (score.isFullCombo) {
|
||||
noteLamp = "FULL COMBO"
|
||||
}
|
||||
else {
|
||||
noteLamp = "NONE"
|
||||
}
|
||||
|
||||
output.scores.push({
|
||||
"score": score.score,
|
||||
"clearLamp": clearLamp,
|
||||
"noteLamp": noteLamp,
|
||||
"judgements": {
|
||||
"jcrit": score.judgeHeaven + score.judgeCritical,
|
||||
"justice": score.judgeJustice,
|
||||
"attack": score.judgeAttack,
|
||||
"miss": score.judgeGuilty
|
||||
},
|
||||
"matchType": "inGameID",
|
||||
"identifier": score.musicId.toString(),
|
||||
"difficulty": DIFFICULTY_MAP[score.level],
|
||||
"timeAchieved": score.sortNumber * 1000,
|
||||
"optional": {
|
||||
"maxCombo": score.maxCombo
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.userData.classEmblemMedal in DAN_MAP) {
|
||||
output.classes["dan"] = DAN_MAP[data.userData.classEmblemMedal]
|
||||
}
|
||||
|
||||
if (data.userData.classEmblemBase in DAN_MAP) {
|
||||
output.classes["emblem"] = DAN_MAP[data.userData.classEmblemBase]
|
||||
}
|
||||
|
||||
download(JSON.stringify(output), `AquaDX_chu3_BatchManualExport_${userbox.userName}.json`)
|
||||
submitting = ""
|
||||
}
|
||||
|
||||
function download(data: string, filename: string) {
|
||||
const blob = new Blob([data]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
}
|
||||
|
||||
function g(v: string) {
|
||||
if (v != ("\x63\x68\x75\x6E\x69\x74\x68\x6D ").repeat(3).trim()) return;
|
||||
const t = v.substring(5, 6) + v.substring(1, 2) + "eme";
|
||||
if (!localStorage.getItem(t)) {
|
||||
localStorage.setItem(t, v.substring(0, 1) + "\x6E");
|
||||
} else
|
||||
localStorage.removeItem(t);
|
||||
setTimeout(location.reload, 1000); // ?
|
||||
}
|
||||
|
||||
let DDSreader: DDS | undefined;
|
||||
|
||||
let USERBOX_PROGRESS = 0;
|
||||
let USERBOX_SETUP_RUN = false;
|
||||
let USERBOX_SETUP_MODE = false;
|
||||
let USERBOX_SETUP_TEXT = t("userbox.new.setup");
|
||||
|
||||
let USERBOX_ENABLED = useLocalStorage("userboxNew", false);
|
||||
let USERBOX_PROFILE_ENABLED = useLocalStorage("userboxNewProfile", false);
|
||||
let USERBOX_INSTALLED = false;
|
||||
let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype;
|
||||
|
||||
type OnlyNumberPropsOf<T extends Record<string, any>> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]}
|
||||
let userboxSelected: keyof OnlyNumberPropsOf<UserBox> = "avatarWear";
|
||||
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon"]
|
||||
|
||||
async function userboxSafeDrop(event: Event & { currentTarget: EventTarget & HTMLInputElement; }) {
|
||||
if (!event.target) return null;
|
||||
let input = event.target as HTMLInputElement;
|
||||
let folder = input.webkitEntries[0];
|
||||
error = await userboxFileProcess(folder, (progress: number, progressString: string) => {
|
||||
USERBOX_SETUP_TEXT = progressString;
|
||||
USERBOX_PROGRESS = progress;
|
||||
}) ?? "";
|
||||
}
|
||||
|
||||
let USERBOX_URL_STATE = useLocalStorage("userboxURL", USERBOX_DEFAULT_URL);
|
||||
function userboxHandleInput(baseURL: string, isSetByServer: boolean = false) {
|
||||
if (baseURL != "")
|
||||
try {
|
||||
// validate url
|
||||
new URL(baseURL, location.href);
|
||||
} catch(err) {
|
||||
if (isSetByServer)
|
||||
return;
|
||||
return error = t("userbox.new.error.invalidUrl")
|
||||
}
|
||||
USERBOX_URL_STATE.value = baseURL;
|
||||
USERBOX_ENABLED.value = true;
|
||||
USERBOX_PROFILE_ENABLED.value = true;
|
||||
location.reload();
|
||||
}
|
||||
|
||||
if (USERBOX_DEFAULT_URL && !USERBOX_URL_STATE.value)
|
||||
userboxHandleInput(USERBOX_DEFAULT_URL, true);
|
||||
|
||||
indexedDB.databases().then(async (dbi) => {
|
||||
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
|
||||
if (USERBOX_URL_STATE.value && databaseExists) {
|
||||
indexedDB.deleteDatabase("userboxChusanDDS")
|
||||
}
|
||||
if (databaseExists) {
|
||||
await initializeDb();
|
||||
}
|
||||
if (databaseExists || USERBOX_URL_STATE.value) {
|
||||
DDSreader = new DDS(ddsDB);
|
||||
USERBOX_INSTALLED = databaseExists || USERBOX_URL_STATE.value != "";
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<StatusOverlays {error} loading={loading || !!submitting} />
|
||||
{#if !loading && !error}
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<h2>{t("userbox.header.general")}</h2>
|
||||
<div class="general-options">
|
||||
<GameSettingFields game="chu3"/>
|
||||
|
||||
<InputField bind:field={userNameField}
|
||||
callback={() => USERBOX.setUserBox({ field: "userName", value: userNameField.value })}/>
|
||||
</div>
|
||||
<h2>{t("userbox.header.userbox")}</h2>
|
||||
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
|
||||
<div class="fields">
|
||||
{#each userItems as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="chuni-userbox-container">
|
||||
<ChuniUserplateComponent chuniIsUserbox={true} on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level.toString()} chuniRating={userbox.playerRating / 100}
|
||||
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
|
||||
<ChuniPenguinComponent chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
|
||||
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
|
||||
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
|
||||
</div>
|
||||
<div class="chuni-userbox-row">
|
||||
{#each avatarKinds as avatarKind}
|
||||
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${userbox[`avatar${avatarKind}`].toString().padStart(8, "0")}`) then imageURL}
|
||||
<button on:click={() => userboxSelected = `avatar${avatarKind}`}>
|
||||
<img src={imageURL} class={userboxSelected == `avatar${avatarKind}` ? "focused" : ""} alt={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name} title={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="chuni-userbox">
|
||||
{#if userboxSelected == "nameplateId"}
|
||||
{#each userItems.find(f => f.ubKey == "nameplateId")?.items ?? [] as item}
|
||||
{#await DDSreader?.getFile(`nameplate:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
|
||||
<button class="nameplate" on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
|
||||
<img src={imageURL} alt={allItems.namePlate[item.itemId].name} title={allItems.namePlate[item.itemId].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
{:else}
|
||||
{#each userItems.find(f => f.ubKey == userboxSelected)?.items ?? [] as item}
|
||||
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
|
||||
<button on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
|
||||
<img src={imageURL} alt={allItems.avatarAccessory[item.itemId].name} title={allItems.avatarAccessory[item.itemId].name}>
|
||||
</button>
|
||||
{/await}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="fields">
|
||||
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i}
|
||||
<div class="field">
|
||||
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
|
||||
<div>
|
||||
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
|
||||
{#each items as option}
|
||||
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if changed.includes(ubKey)}
|
||||
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if USERBOX_INSTALLED}
|
||||
<!-- god this is a mess but idgaf atp -->
|
||||
<div class="field boolean" style:margin-top="1em">
|
||||
<input type="checkbox" bind:checked={USERBOX_ENABLED.value} id="newUserbox">
|
||||
<label for="newUserbox">
|
||||
<span class="name">{t("userbox.new.activate")}</span>
|
||||
<span class="desc">{t(`userbox.new.activate_desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field boolean" style:margin-top="1em">
|
||||
<input type="checkbox" bind:checked={USERBOX_PROFILE_ENABLED.value} id="newUserboxProfile">
|
||||
<label for="newUserboxProfile">
|
||||
<span class="name">{t("userbox.new.activate_profile")}</span>
|
||||
<span class="desc">{t(`userbox.new.activate_profile_desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if USERBOX_SUPPORT && !USERBOX_DEFAULT_URL}
|
||||
<p>
|
||||
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
|
||||
</p>
|
||||
{/if}
|
||||
<ChuniMatchingSettings/><br>
|
||||
<button class="exportButton" on:click={exportData}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.export')}
|
||||
</button>
|
||||
<button class="exportBatchManualButton" on:click={exportBatchManual}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.batchManualExport')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if USERBOX_SETUP_RUN && !error}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>{t('userbox.new.name')}</h2>
|
||||
<span>{USERBOX_SETUP_MODE ? t('userbox.new.url_warning') : USERBOX_SETUP_TEXT}</span>
|
||||
<div class="actions">
|
||||
{#if USERBOX_SETUP_MODE}
|
||||
<input type="text" on:keyup={e => {if (e.key == "Enter") { userboxHandleInput((e.target as HTMLInputElement).value) } else g(e.currentTarget.value)}} class="add-margin" placeholder="Base URL">
|
||||
{:else}
|
||||
{#if USERBOX_PROGRESS != 0}
|
||||
<div class="progress">
|
||||
<div class="progress-bar" style="width: {USERBOX_PROGRESS}%"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="notice add-margin">
|
||||
{t('userbox.new.setup.notice')}
|
||||
</p>
|
||||
<button class="drop-btn">
|
||||
<input type="file" on:input={userboxSafeDrop} on:click={e => e.preventDefault()}>
|
||||
{t('userbox.new.drop')}
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if USERBOX_PROGRESS == 0}
|
||||
<button on:click={() => USERBOX_SETUP_RUN = false}>
|
||||
{t('back')}
|
||||
</button>
|
||||
<button on:click={() => USERBOX_SETUP_MODE = !USERBOX_SETUP_MODE}>
|
||||
{t(USERBOX_SETUP_MODE ? 'userbox.new.switch.to_drop' : 'userbox.new.switch.to_url')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
input
|
||||
width: 100%
|
||||
|
||||
|
||||
h2
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
.general-options
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-wrap: wrap
|
||||
gap: 12px
|
||||
|
||||
p.notice
|
||||
opacity: 0.6
|
||||
margin-top: 0
|
||||
|
||||
.progress
|
||||
width: 100%
|
||||
height: 10px
|
||||
box-shadow: 0 0 1px 1px vars.$ov-lighter
|
||||
border-radius: 25px
|
||||
margin-bottom: 15px
|
||||
overflow: hidden
|
||||
|
||||
.progress-bar
|
||||
background: #b3c6ff
|
||||
height: 100%
|
||||
border-radius: 25px
|
||||
|
||||
|
||||
.add-margin, .drop-btn
|
||||
margin-bottom: 1em
|
||||
|
||||
.drop-btn
|
||||
position: relative
|
||||
width: 100%
|
||||
aspect-ratio: 3
|
||||
background: transparent
|
||||
box-shadow: 0 0 1px 1px vars.$ov-lighter
|
||||
|
||||
> input
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
opacity: 0
|
||||
|
||||
.preview
|
||||
margin-top: 32px
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: space-between
|
||||
gap: 32px
|
||||
|
||||
> div
|
||||
position: relative
|
||||
width: 100px
|
||||
height: 100px
|
||||
overflow: hidden
|
||||
background: vars.$ov-lighter
|
||||
border-radius: vars.$border-radius
|
||||
|
||||
span
|
||||
position: absolute
|
||||
bottom: 0
|
||||
width: 100%
|
||||
text-align: center
|
||||
z-index: 10
|
||||
background: rgba(0, 0, 0, 0.2)
|
||||
backdrop-filter: blur(2px)
|
||||
|
||||
img
|
||||
position: absolute
|
||||
inset: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
object-fit: contain
|
||||
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
width: 100%
|
||||
flex-grow: 0
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
select
|
||||
width: 100%
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
width: 100%
|
||||
|
||||
label
|
||||
max-width: max-content
|
||||
|
||||
> div:not(.bool)
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
margin-top: 0.5rem
|
||||
|
||||
> select
|
||||
flex: 1
|
||||
|
||||
|
||||
.field.boolean
|
||||
display: flex
|
||||
flex-direction: row
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
width: auto
|
||||
|
||||
input
|
||||
width: auto
|
||||
aspect-ratio: 1 / 1
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
max-width: max-content
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
/* AquaBox */
|
||||
|
||||
.chuni-userbox-row
|
||||
width: 100%
|
||||
display: flex
|
||||
|
||||
button
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 100%
|
||||
flex: 0 1 100%
|
||||
background: none
|
||||
aspect-ratio: 1
|
||||
|
||||
img
|
||||
width: 100%
|
||||
filter: brightness(50%)
|
||||
|
||||
&.focused
|
||||
filter: brightness(75%)
|
||||
|
||||
.chuni-userbox
|
||||
width: calc(100% - 20px)
|
||||
height: 350px
|
||||
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
padding: 10px
|
||||
background: vars.$c-bg
|
||||
border-radius: 16px
|
||||
overflow-y: auto
|
||||
margin-bottom: 15px
|
||||
justify-content: center
|
||||
|
||||
button
|
||||
padding: 0
|
||||
margin: 0
|
||||
width: 20%
|
||||
align-self: flex-start
|
||||
background: none
|
||||
aspect-ratio: 1
|
||||
|
||||
img
|
||||
width: 100%
|
||||
|
||||
&.nameplate
|
||||
width: 50%
|
||||
aspect-ratio: unset
|
||||
border: none
|
||||
|
||||
.chuni-userbox-container
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
@media (max-width: 1000px)
|
||||
.chuni-userbox-container
|
||||
flex-wrap: wrap
|
||||
</style>
|
||||
41
AquaNet/src/components/settings/GameSettingFields.svelte
Normal file
@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { SETTING } from "../../libs/sdk";
|
||||
import type { GameOption } from "../../libs/generalTypes";
|
||||
import { ts } from "../../libs/i18n";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import InputWithButton from "../ui/InputWithButton.svelte";
|
||||
import InputField from "../ui/InputField.svelte";
|
||||
|
||||
export let game: string;
|
||||
let gameFields: GameOption[] = []
|
||||
let submitting = ""
|
||||
let error: string;
|
||||
|
||||
SETTING.get().then(s => {
|
||||
gameFields = s.filter(it => it.game === game)
|
||||
})
|
||||
|
||||
async function submitGameOption(field: string, value: any) {
|
||||
if (submitting) return false
|
||||
submitting = field
|
||||
|
||||
await SETTING.set(field, value).catch(e => error = e.message).finally(() => submitting = "")
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fields">
|
||||
{#each gameFields as field}
|
||||
<InputField field={field} callback={() => submitGameOption(field.key, field.value)}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!gameFields.length || !!submitting}/>
|
||||
|
||||
<style lang="sass">
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
</style>
|
||||
59
AquaNet/src/components/settings/GeneralGameSettings.svelte
Normal file
@ -0,0 +1,59 @@
|
||||
<script>
|
||||
import { fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import RegionSelector from "./RegionSelector.svelte";
|
||||
|
||||
const rounding = useLocalStorage("rounding", true);
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
<blockquote>
|
||||
{ts("settings.gameNotice")}
|
||||
</blockquote>
|
||||
<GameSettingFields game="general"/>
|
||||
<div class="field">
|
||||
<div class="bool">
|
||||
<input id="rounding" type="checkbox" bind:checked={rounding.value}/>
|
||||
<label for="rounding">
|
||||
<span class="name">{ts(`settings.fields.rounding.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.rounding.desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<blockquote>
|
||||
{ts("settings.regionNotice")}
|
||||
</blockquote>
|
||||
<RegionSelector/>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.bool
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
.divider
|
||||
width: 100%
|
||||
height: 0.5px
|
||||
background: white
|
||||
opacity: 0.2
|
||||
margin: 0.4rem 0
|
||||
</style>
|
||||
104
AquaNet/src/components/settings/Mai2Settings.svelte
Normal file
@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import { t } from "../../libs/i18n.js";
|
||||
import Icon from "@iconify/svelte";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import { GAME } from "../../libs/sdk";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { download } from "../../libs/ui";
|
||||
|
||||
const profileFields = [
|
||||
['name', t('settings.mai2.name')],
|
||||
]
|
||||
|
||||
export let username: string;
|
||||
let error: string
|
||||
let submitting = ""
|
||||
let values = Array(profileFields.length).fill('')
|
||||
let changed: string[] = []
|
||||
|
||||
GAME.userSummary(username, 'mai2').then(({name}) => {
|
||||
values = [name]
|
||||
}).catch(e => error = e.message)
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
switch (field) {
|
||||
case 'name':
|
||||
GAME.changeName('mai2', value).then(({newName}) => {
|
||||
changed = changed.filter(c => c !== field)
|
||||
values = [newName]
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function exportData() {
|
||||
submitting = "export"
|
||||
GAME.export('mai2')
|
||||
.then(data => download(JSON.stringify(data), `AquaDX_maimai2_export_${values[0]}.json`))
|
||||
.catch(e => error = e.message)
|
||||
.finally(() => submitting = "")
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
<label for={field}>{name}</label>
|
||||
<div>
|
||||
<input id={field} type="text"
|
||||
bind:value={values[i]} on:input={() => changed = [...changed, field]}
|
||||
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||
{#if changed.includes(field) && values[i]}
|
||||
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, values[i])}>
|
||||
{#if submitting === field}
|
||||
<Icon icon="line-md:loading-twotone-loop"/>
|
||||
{:else}
|
||||
{t('settings.profile.save')}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<GameSettingFields game="mai2"/>
|
||||
<button class="exportButton" on:click={exportData}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.export')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!values[0] || !!submitting}/>
|
||||
|
||||
<style lang="sass">
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
label
|
||||
max-width: max-content
|
||||
|
||||
> div:not(.bool)
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
margin-top: 0.5rem
|
||||
|
||||
> input
|
||||
flex: 1
|
||||
|
||||
.exportButton
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
gap: 5px
|
||||
</style>
|
||||
9
AquaNet/src/components/settings/OngekiSettings.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<GameSettingFields game="ongeki"/>
|
||||
</div>
|
||||
59
AquaNet/src/components/settings/RegionSelector.svelte
Normal file
@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { USER} from "../../libs/sdk";
|
||||
import { ts } from "../../libs/i18n";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
let regionId = $state(0);
|
||||
let submitting = ""
|
||||
let error: string;
|
||||
|
||||
const prefectures = ["None","Aichi","Aomori","Akita","Ishikawa","Ibaraki","Iwate","Ehime","Oita","Osaka","Okayama","Okinawa","Kagawa","Kagoshima","Kanagawa","Gifu","Kyoto","Kumamoto","Gunma","Kochi","Saitama","Saga","Shiga","Shizuoka","Shimane","Chiba","Tokyo","Tokushima","Tochigi","Tottori","Toyama","Nagasaki","Nagano","Nara","Niigata","Hyogo","Hiroshima","Fukui","Fukuoka","Fukushima","Hokkaido","Mie","Miyagi","Miyazaki","Yamagata","Yamaguchi","Yamanashi","Wakayama"]
|
||||
|
||||
USER.me().then(user => {
|
||||
const parsedRegion = parseInt(user.region);
|
||||
if (!isNaN(parsedRegion) && parsedRegion > 0) {
|
||||
regionId = parsedRegion - 1;
|
||||
} else {
|
||||
regionId = 0;
|
||||
}
|
||||
})
|
||||
|
||||
async function saveNewRegion() {
|
||||
if (submitting) return false
|
||||
submitting = "region"
|
||||
|
||||
await USER.changeRegion(regionId+1).catch(e => error = e.message).finally(() => submitting = "")
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fields">
|
||||
<label for="rounding">
|
||||
<span class="name">{ts(`settings.regionSelector.title`)}</span>
|
||||
<span class="desc">{ts(`settings.regionSelector.desc`)}</span>
|
||||
</label>
|
||||
<select bind:value={regionId} on:change={saveNewRegion}>
|
||||
<option value={0} disabled selected>{ts("settings.regionSelector.select")}</option>
|
||||
{#each prefectures.slice(1) as prefecture, index}
|
||||
<option value={index}>{prefecture}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!!submitting}/>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
</style>
|
||||
9
AquaNet/src/components/settings/WaccaSettings.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<GameSettingFields game="wacca"/>
|
||||
</div>
|
||||
227
AquaNet/src/components/settings/userbox/ChuniPenguin.svelte
Normal file
@ -0,0 +1,227 @@
|
||||
<script lang="ts">
|
||||
import { removeImg } from "../../../libs/ui";
|
||||
import { DDS } from "../../../libs/userbox/dds"
|
||||
import { ddsDB } from "../../../libs/userbox/userbox"
|
||||
|
||||
const DDSreader = new DDS(ddsDB);
|
||||
|
||||
export var chuniWear = 1100001;
|
||||
export var chuniHead = 1200001;
|
||||
export var chuniFace = 1300001;
|
||||
export var chuniSkin = 1400001;
|
||||
export var chuniItem = 1500001;
|
||||
export var chuniFront = 1600001;
|
||||
export var chuniBack = 1700001;
|
||||
export var classPassthrough: string = ``;
|
||||
</script>
|
||||
<div class="chuni-penguin {classPassthrough}">
|
||||
<div class="chuni-penguin-body">
|
||||
<!-- Body -->
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 0, 256, 400, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-skin" src={imageURL} alt="Body" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
<!-- Face -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_face_00.dds", 0, 0, 225, 150, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-eyes chuni-penguin-accessory" src={imageURL} alt="Eyes" on:error={removeImg}>
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 86, 103, 96, 43, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-beak chuni-penguin-accessory" src={imageURL} alt="Beak" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
{#if chuniItem != 1500001}
|
||||
<!-- Arms (straight) -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-left chuni-penguin-arm" src={imageURL} alt="Left Arm" on:error={removeImg}/>
|
||||
<div class="chuni-penguin-arm-left chuni-penguin-arm-type-1 chuni-penguin-arm">
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniItem.toString().padStart(8, "0")}`, 0, 0, 200, 544, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-item chuni-penguin-accessory chuni-penguin-item-left" src={imageURL} alt="Item" on:error={removeImg}>
|
||||
{/await}
|
||||
</div>
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 0, 0, 85, 160, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-right chuni-penguin-arm" src={imageURL} alt="Right Arm" on:error={removeImg}>
|
||||
<div class="chuni-penguin-arm-right chuni-penguin-arm-type-1 chuni-penguin-arm">
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniItem.toString().padStart(8, "0")}`, 200, 0, 200, 544, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-item chuni-penguin-accessory chuni-penguin-item-right" src={imageURL} alt="Item" on:error={removeImg}>
|
||||
{/await}
|
||||
</div>
|
||||
{/await}
|
||||
{:else}
|
||||
<!-- Arms (bent) -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 80, 0, 110, 100, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-left chuni-penguin-arm chuni-penguin-arm-type-2" src={imageURL} alt="Left Arm" on:error={removeImg}>
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 80, 0, 110, 100, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-arm-right chuni-penguin-arm chuni-penguin-arm-type-2" src={imageURL} alt="Right Arm" on:error={removeImg}>
|
||||
{/await}
|
||||
{/if}
|
||||
|
||||
<!-- Wear -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniWear.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01100001`) then imageURL}
|
||||
<img class="chuni-penguin-wear chuni-penguin-accessory" src={imageURL} alt="Wear" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
<!-- Head -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniHead.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01200001`) then imageURL}
|
||||
<img class="chuni-penguin-head chuni-penguin-accessory" src={imageURL} alt="Head" on:error={removeImg}>
|
||||
{/await}
|
||||
{#if chuniHead == 1200001}
|
||||
<!-- If wearing original hat, add the feather -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 5, 160, 100, 150, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-head-3 chuni-penguin-accessory" src={imageURL} alt="Head3" on:error={removeImg}>
|
||||
{/await}
|
||||
{/if}
|
||||
<!-- Oops, I realized just now that the thing on it's forehead applies to all hats. My mistake! -->
|
||||
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 105, 153, 56, 58, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-head-2 chuni-penguin-accessory" src={imageURL} alt="Head2" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
<!-- Face (Accessory) -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniFace.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01300001`) then imageURL}
|
||||
<img class="chuni-penguin-face-accessory chuni-penguin-accessory" src={imageURL} alt="Face (Accessory)" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
<!-- Front -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniFront.toString().padStart(8, "0")}`, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-front chuni-penguin-accessory" src={imageURL} alt="Front" on:error={removeImg}>
|
||||
{/await}
|
||||
|
||||
<!-- Back -->
|
||||
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniBack.toString().padStart(8, "0")}`, 0.75) then imageURL}
|
||||
<img class="chuni-penguin-back chuni-penguin-accessory" src={imageURL} alt="Back" on:error={removeImg}>
|
||||
{/await}
|
||||
</div>
|
||||
<div class="chuni-penguin-feet">
|
||||
<!-- Feet -->
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 410, 85, 80, 0.75) then imageURL}
|
||||
<img src={imageURL} alt="Foot" on:error={removeImg}>
|
||||
{/await}
|
||||
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 85, 410, 85, 80, 0.75) then imageURL}
|
||||
<img src={imageURL} alt="Foot" on:error={removeImg}>
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Truly sorry for the horrors below -->
|
||||
<style lang="sass">
|
||||
@keyframes chuniPenguinBodyBob
|
||||
0%
|
||||
transform: translate(-50%, 5px) translate(0%, -50%)
|
||||
50%
|
||||
transform: translate(-50%, 0%) translate(0%, -50%)
|
||||
100%
|
||||
transform: translate(-50%, 5px) translate(0%, -50%)
|
||||
@keyframes chuniPenguinArmLeft
|
||||
0%
|
||||
transform: translate(-50%, 0) rotate(-2deg)
|
||||
50%
|
||||
transform: translate(-50%, 0) rotate(2deg)
|
||||
100%
|
||||
transform: translate(-50%, 0) rotate(-2deg)
|
||||
@keyframes chuniPenguinArmRight
|
||||
0%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(-2deg)
|
||||
50%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(2deg)
|
||||
100%
|
||||
transform: translate(-50%, 0) scaleX(-1) rotate(-2deg)
|
||||
|
||||
img
|
||||
-webkit-user-drag: none
|
||||
user-select: none
|
||||
|
||||
.chuni-penguin
|
||||
height: 512px
|
||||
aspect-ratio: 1/2
|
||||
position: relative
|
||||
pointer-events: none
|
||||
|
||||
z-index: 1
|
||||
|
||||
&.chuni-penguin-float
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
transform: translate(-50%, -50%)
|
||||
|
||||
.chuni-penguin-body, .chuni-penguin-feet
|
||||
transform: translate(-50%, -50%)
|
||||
position: absolute
|
||||
left: 50%
|
||||
|
||||
.chuni-penguin-body
|
||||
top: 50%
|
||||
z-index: 1
|
||||
animation: chuniPenguinBodyBob 1s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
.chuni-penguin-feet
|
||||
top: 80%
|
||||
z-index: 0
|
||||
width: 175px
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
img
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
|
||||
.chuni-penguin-arm
|
||||
transform-origin: 90% 10%
|
||||
position: absolute
|
||||
top: 40%
|
||||
z-index: 0
|
||||
&.chuni-penguin-arm-type-1
|
||||
width: calc(85px * 0.75)
|
||||
height: calc(160px * 0.75)
|
||||
z-index: 2
|
||||
&.chuni-penguin-arm-type-2
|
||||
transform-origin: 40% 10%
|
||||
z-index: 2
|
||||
|
||||
&.chuni-penguin-arm-left
|
||||
left: 0%
|
||||
transform: translate(-50%, 0)
|
||||
animation: chuniPenguinArmLeft 1s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
&.chuni-penguin-arm-type-2
|
||||
left: 15%
|
||||
&.chuni-penguin-arm-right
|
||||
left: 72.5%
|
||||
transform: translate(-50%, 0) scaleX(-1)
|
||||
animation: chuniPenguinArmRight 1s infinite cubic-bezier(0.45, 0, 0.55, 1)
|
||||
&.chuni-penguin-arm-type-2
|
||||
left: 95%
|
||||
|
||||
.chuni-penguin-accessory
|
||||
transform: translate(-50%, -50%)
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: 50%
|
||||
|
||||
.chuni-penguin-item
|
||||
z-index: 1
|
||||
top: 25%
|
||||
left: 0
|
||||
|
||||
&.chuni-penguin-item-left
|
||||
transform: translate(-50%, -50%) rotate(-15deg)
|
||||
&.chuni-penguin-item-right
|
||||
transform: translate(-50%, -50%) scaleX(-1) rotate(15deg)
|
||||
|
||||
.chuni-penguin-eyes
|
||||
top: 22.5%
|
||||
.chuni-penguin-beak
|
||||
top: 29.5%
|
||||
.chuni-penguin-wear
|
||||
top: 60%
|
||||
.chuni-penguin-head
|
||||
top: 7.5%
|
||||
z-index: 10
|
||||
.chuni-penguin-head-2
|
||||
top: 13.5%
|
||||
.chuni-penguin-head-3
|
||||
top: -12.5%
|
||||
.chuni-penguin-face-accessory
|
||||
top: 27.5%
|
||||
.chuni-penguin-back
|
||||
z-index: -1
|
||||
|
||||
</style>
|
||||
@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { initializeDb } from "../../../libs/userbox/userbox"
|
||||
import ChuniPenguinComponent from "./ChuniPenguin.svelte"
|
||||
import ChuniUserplateComponent from "./ChuniUserplate.svelte"
|
||||
import { type UserBox } from "../../../libs/generalTypes"
|
||||
import { DATA, USERBOX } from "../../../libs/sdk"
|
||||
import useLocalStorage from "../../../libs/hooks/useLocalStorage.svelte"
|
||||
import { t } from "../../../libs/i18n"
|
||||
|
||||
/**
|
||||
* This is a UserBox viewer on the Profile page (UserHome), added by raymond
|
||||
* to view other user's penguins on their profile.
|
||||
*/
|
||||
|
||||
export let game: string
|
||||
export let username: string
|
||||
export let error: string = ""
|
||||
|
||||
let USERBOX_ACTIVE = useLocalStorage("userboxNewProfile", false)
|
||||
let USERBOX_INSTALLED = false
|
||||
|
||||
let userbox: UserBox
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
|
||||
if (game == "chu3" && USERBOX_ACTIVE.value) {
|
||||
indexedDB.databases().then(async (dbi) => {
|
||||
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS")
|
||||
if (databaseExists) {
|
||||
await initializeDb()
|
||||
const profile = await USERBOX.getUserProfile(username).catch(_ => null)
|
||||
if (!profile) return
|
||||
userbox = profile
|
||||
console.log(userbox)
|
||||
|
||||
allItems = await DATA.allItems('chu3').catch(_ => {
|
||||
error = t("userbox.error.nodata")
|
||||
}) as typeof allItems
|
||||
USERBOX_INSTALLED = databaseExists
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if USERBOX_ACTIVE.value && USERBOX_INSTALLED && game == "chu3"}
|
||||
<div class="chuni-userbox-container">
|
||||
<ChuniUserplateComponent chuniCharacter={userbox.characterId} chuniRating={userbox.playerRating / 100} chuniLevel={userbox.level.toString()}
|
||||
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
|
||||
<div class="chuni-penguin-container">
|
||||
<ChuniPenguinComponent classPassthrough="chuni-penguin-float" chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
|
||||
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
|
||||
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
.chuni-userbox-container
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
.chuni-penguin-container
|
||||
height: 256px
|
||||
aspect-ratio: 1
|
||||
position: relative
|
||||
|
||||
@media (max-width: 1000px)
|
||||
.chuni-userbox-container
|
||||
flex-wrap: wrap
|
||||
</style>
|
||||
192
AquaNet/src/components/settings/userbox/ChuniUserplate.svelte
Normal file
@ -0,0 +1,192 @@
|
||||
<script lang="ts">
|
||||
import { DDS, type RGB } from "../../../libs/userbox/dds"
|
||||
import { ddsDB } from "../../../libs/userbox/userbox"
|
||||
|
||||
const DDSreader = new DDS(ddsDB);
|
||||
|
||||
export var chuniLevel: string = "╳"
|
||||
export var chuniName: string = "AquaDX"
|
||||
export var chuniRating: number = 1.23
|
||||
export var chuniNameplate: number = 1
|
||||
export var chuniCharacter: number = 0
|
||||
export var chuniTrophyName: string = "NEWCOMER"
|
||||
export var chuniIsUserbox: boolean = false;
|
||||
|
||||
let ratingToString = (rating: number) => {
|
||||
return rating.toFixed(2)
|
||||
}
|
||||
|
||||
interface RatingRange {
|
||||
min: number,
|
||||
offset: number,
|
||||
color?: RGB
|
||||
};
|
||||
// https://en.wikipedia.org/wiki/Chunithm#Rating
|
||||
const ratingColors: RatingRange[] = ([
|
||||
{min: 0.00, offset: 4, color: {r: 0, g: 191, b: 64}},
|
||||
{min: 4.00, offset: 4, color: {r: 255, g: 111, b: 0}},
|
||||
{min: 7.00, offset: 4, color: {r: 255, g: 64, b: 64}},
|
||||
{min: 10.00, offset: 4, color: {r: 147, g: 38, b: 255}},
|
||||
{min: 12.00, offset: 3},
|
||||
{min: 13.25, offset: 2},
|
||||
{min: 14.50, offset: 1},
|
||||
{min: 15.25, offset: 0},
|
||||
{min: 16.00, offset: 5}
|
||||
]).filter(f => f.min <= chuniRating);
|
||||
const ratingDigitOrder = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]
|
||||
const ratingColorData = (ratingColors[ratingColors.length - 1] ?? ratingColors[0]);
|
||||
</script>
|
||||
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`, `nameplate:00000001`) then nameplateURL}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div on:click class="chuni-nameplate" class:chuni-nameplate-clickable={chuniIsUserbox} style:background={`url(${nameplateURL})`}>
|
||||
{#await DDSreader?.getFile(`characterThumbnail:${chuniCharacter.toString().padStart(6, "0")}`, `characterThumbnail:000000`) then characterThumbnailURL}
|
||||
<img class="chuni-character" src={characterThumbnailURL} alt="Character">
|
||||
{/await}
|
||||
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_title_rank_00_v10.dds", 5, 5 + (75 * 2), 595, 64) then trophyURL}
|
||||
<div class="chuni-trophy" title={chuniTrophyName}>
|
||||
{chuniTrophyName}
|
||||
</div>
|
||||
<img src={trophyURL} class="chuni-trophy-bg" alt="Trophy" title={chuniTrophyName}>
|
||||
{/await}
|
||||
<div class="chuni-user-info">
|
||||
<div class="chuni-user-name">
|
||||
<span>
|
||||
Lv.
|
||||
<span class="chuni-user-level">
|
||||
{chuniLevel}
|
||||
</span>
|
||||
</span>
|
||||
<span class="chuni-user-name-text">
|
||||
{chuniName}
|
||||
</span>
|
||||
</div>
|
||||
<div class={`chuni-user-rating color-${ratingColorData.color}`}>
|
||||
|
||||
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_Common_01_v11.dds", 485, 5 + (28 * ratingColorData.offset), 62, 15, undefined, ratingColorData.color) then url}
|
||||
{#if url}
|
||||
<img src={url} alt="Rating">
|
||||
<span class="chuni-user-rating-number">
|
||||
{#each ratingToString(chuniRating).split("") as digit}
|
||||
{#await DDSreader?.getFileFromSheet("surfboard:CHU_UI_Common_01_v11.dds", 552 + (24 * (ratingDigitOrder.indexOf(digit) ?? 0)), 1 + (28 * ratingColorData.offset), 16, 20, undefined, ratingColorData.color) then url}
|
||||
<img src={url} alt="Rating Digit">
|
||||
{/await}
|
||||
{/each}
|
||||
</span>
|
||||
{:else}
|
||||
RATING
|
||||
<span class="chuni-user-rating-number">
|
||||
{ratingToString(chuniRating)}
|
||||
</span>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
<style lang="sass">
|
||||
@use "../../../vars"
|
||||
|
||||
@font-face
|
||||
font-family: "Gothic A1"
|
||||
src: url("/assets/fonts/GothicA1.woff2")
|
||||
|
||||
.chuni-nameplate
|
||||
width: 576px
|
||||
height: 228px
|
||||
position: relative
|
||||
font-size: 16px
|
||||
/* Overlap penguin avatar when put side to side */
|
||||
z-index: 1
|
||||
|
||||
&.chuni-nameplate-clickable
|
||||
cursor: pointer
|
||||
|
||||
.chuni-trophy
|
||||
width: 390px
|
||||
height: 45px
|
||||
background-position: center
|
||||
background-size: cover
|
||||
color: black
|
||||
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
position: absolute
|
||||
right: 25px
|
||||
top: 40px
|
||||
|
||||
font-size: 1.15em
|
||||
font-family: "Gothic A1", sans-serif
|
||||
font-weight: bold
|
||||
|
||||
overflow-x: hidden
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
|
||||
z-index: 1
|
||||
text-shadow: 0 1px white
|
||||
margin: 0 10px
|
||||
|
||||
img.chuni-trophy-bg
|
||||
width: 410px
|
||||
height: 45px
|
||||
position: absolute
|
||||
top: 40px
|
||||
right: 25px
|
||||
z-index: -1
|
||||
|
||||
.chuni-character
|
||||
position: absolute
|
||||
top: 87px
|
||||
right: 25px
|
||||
width: 82px
|
||||
aspect-ratio: 1
|
||||
box-shadow: 0 0 1px 1px white
|
||||
background: #efefef
|
||||
|
||||
.chuni-user-info
|
||||
height: 82px
|
||||
width: 320px
|
||||
position: absolute
|
||||
top: 87px
|
||||
right: 110px
|
||||
background: #fff9
|
||||
border-radius: 1px
|
||||
box-shadow: 0 0 1px 1px #ccc
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.chuni-user-name, .chuni-user-rating
|
||||
margin: 0 4px
|
||||
display: flex
|
||||
align-items: center
|
||||
color: black
|
||||
font-family: "Gothic A1", sans-serif
|
||||
font-weight: bold
|
||||
|
||||
.chuni-user-name
|
||||
flex: 1 0 65%
|
||||
box-shadow: 0 1px 0 #ccc
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
|
||||
.chuni-user-level
|
||||
font-size: 1.5em
|
||||
margin-left: 10px
|
||||
|
||||
.chuni-user-name-text
|
||||
margin-left: auto
|
||||
font-size: 2em
|
||||
|
||||
.chuni-user-rating
|
||||
flex: 1 0 35%
|
||||
font-size: 0.875em
|
||||
text-shadow: #333 1px 1px, #333 1px -1px, #333 -1px 1px, #333 -1px -1px
|
||||
color: #fff
|
||||
|
||||
.chuni-user-rating-number
|
||||
font-size: 1.5em
|
||||
margin-left: 10px
|
||||
|
||||
</style>
|
||||
39
AquaNet/src/components/ui/Error.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { fade } from "svelte/transition";
|
||||
import { t } from "../../libs/i18n";
|
||||
import { DISCORD_INVITE } from "../../libs/config";
|
||||
|
||||
export let error: string;
|
||||
export let expected: boolean = false;
|
||||
</script>
|
||||
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2 class="error">{t('status.error')}</h2>
|
||||
{#if !expected}
|
||||
<span>{t('status.error.hint')}<a href={DISCORD_INVITE}>{t('status.error.hint.link')}</a></span>
|
||||
{/if}
|
||||
<span class="detail">{error}</span>
|
||||
|
||||
<div class="actions">
|
||||
<button on:click={() => location.reload()} class="error">
|
||||
{t('action.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
|
||||
button
|
||||
width: 100%
|
||||
|
||||
.detail
|
||||
white-space: pre-line
|
||||
font-size: 0.9em
|
||||
line-height: 1.2
|
||||
opacity: 0.8
|
||||
</style>
|
||||
47
AquaNet/src/components/ui/InputField.svelte
Normal file
@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { ts } from "../../libs/i18n";
|
||||
import InputWithButton from "./InputWithButton.svelte";
|
||||
|
||||
export let field: {key: string, value: any, type: string, changed?: boolean};
|
||||
export let callback: () => Promise<boolean>;
|
||||
</script>
|
||||
|
||||
<div class="field {field.type.toLowerCase()}">
|
||||
{#if field.type.toLowerCase() === "boolean"}
|
||||
<input id={field.key} type="checkbox" bind:checked={field.value} on:change={callback}/>
|
||||
<label for={field.key}>
|
||||
<span class="name">{ts(`settings.fields.${field.key}.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span>
|
||||
</label>
|
||||
{/if}
|
||||
{#if field.type.toLowerCase() === "string"}
|
||||
<label for={field.key}>
|
||||
<span class="name">{ts(`settings.fields.${field.key}.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span>
|
||||
</label>
|
||||
<InputWithButton bind:field={field} callback={callback}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.field.string
|
||||
flex-direction: column
|
||||
align-items: flex-start
|
||||
gap: 0.5rem
|
||||
|
||||
.field.boolean
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
.field
|
||||
display: flex
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
max-width: max-content
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
</style>
|
||||
29
AquaNet/src/components/ui/InputWithButton.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { ts } from "../../libs/i18n";
|
||||
|
||||
export let field: {key: string, value: string, changed?: boolean};
|
||||
export let callback: () => Promise<boolean>;
|
||||
</script>
|
||||
|
||||
<div class="field">
|
||||
<input id={field.key} type="text" bind:value={field.value}
|
||||
on:input={() => field.changed = true}/>
|
||||
{#if field.changed}
|
||||
<button on:click={async () => { if (await callback()) field.changed = false } }
|
||||
transition:slide={{axis: 'x'}}>
|
||||
{ts('settings.profile.save')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.field
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
width: 100%
|
||||
|
||||
input
|
||||
flex: 1
|
||||
</style>
|
||||
30
AquaNet/src/components/ui/Loading.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { fade } from 'svelte/transition'
|
||||
</script>
|
||||
|
||||
<div class="overlay loading" transition:fade>
|
||||
<Icon class="icon" icon="svg-spinners:pulse-2"/>
|
||||
<span><span>LOADING</span></span>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.loading.overlay
|
||||
font-size: 28rem
|
||||
|
||||
:global(.icon)
|
||||
opacity: 0.5
|
||||
|
||||
> span
|
||||
position: absolute
|
||||
inset: 0
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
background: transparent
|
||||
|
||||
letter-spacing: 20px
|
||||
margin-left: 20px
|
||||
|
||||
font-size: 1.5rem
|
||||
</style>
|
||||
47
AquaNet/src/libs/config.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import type { ChusanMatchingOption } from "./generalTypes"
|
||||
|
||||
export const AQUA_HOST = import.meta.env.VITE_AQUA_HOST
|
||||
export const DATA_HOST = import.meta.env.VITE_DATA_HOST
|
||||
|
||||
// This will be displayed for users to connect from the client
|
||||
export const AQUA_CONNECTION = import.meta.env.VITE_AQUA_CONNECTION
|
||||
|
||||
export const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY
|
||||
export const DISCORD_INVITE = import.meta.env.VITE_DISCORD_INVITE
|
||||
export const TELEGRAM_INVITE = import.meta.env.VITE_TELEGRAM_INVITE
|
||||
export const QQ_INVITE = import.meta.env.VITE_QQ_INVITE
|
||||
|
||||
// UI
|
||||
export const FADE_OUT = { duration: 200 }
|
||||
export const FADE_IN = { delay: 400 }
|
||||
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
||||
|
||||
export const ANNOUNCEMENT = '' // If set, will add an announcement to the top bar. Keep it short.
|
||||
|
||||
// Documentation for Userbox mode can be found in `docs/aquabox-url-mode.md`
|
||||
// Please note that if this is set, it must be manually unset by users in Chuni Settings -> Update Userbox -> Switch to URL mode -> (empty value) -> Enter key
|
||||
export const USERBOX_DEFAULT_URL = ""
|
||||
|
||||
export const HAS_USERBOX_ASSETS = true
|
||||
|
||||
// Meow meow meow
|
||||
|
||||
// Matching servers
|
||||
export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
|
||||
{
|
||||
name: "林国对战",
|
||||
ui: "https://chu3-match.sega.ink/rooms",
|
||||
guide: "https://performai.evilleaker.com/manual/games/chunithm/national_battle/",
|
||||
matching: "https://chu3-match.sega.ink/",
|
||||
reflector: "http://reflector.naominet.live:18080/",
|
||||
coop: ["RinNET", "MysteriaNET"],
|
||||
},
|
||||
{
|
||||
name: "Yukiotoko",
|
||||
ui: "https://yukiotoko.metatable.sh/",
|
||||
guide: "https://github.com/MewoLab/AquaDX/blob/v1-dev/docs/chu3-national-matching.md",
|
||||
matching: "http://yukiotoko.chara.lol:9004/",
|
||||
reflector: "http://yukiotoko.chara.lol:50201/",
|
||||
coop: ["Missless", "CozyNet", "GMG"]
|
||||
}
|
||||
]
|
||||
170
AquaNet/src/libs/generalTypes.ts
Normal file
@ -0,0 +1,170 @@
|
||||
export type Dict = Record<string, any>
|
||||
|
||||
export interface TrendEntry {
|
||||
date: string
|
||||
rating: number
|
||||
plays: number
|
||||
}
|
||||
|
||||
export interface Card {
|
||||
luid: string
|
||||
registerTime: string
|
||||
accessTime: string
|
||||
linked: boolean
|
||||
isGhost: boolean
|
||||
}
|
||||
|
||||
export interface AquaNetUser {
|
||||
username: string
|
||||
email: string
|
||||
displayName: string
|
||||
country: string
|
||||
region:string
|
||||
lastLogin: number
|
||||
regTime: number
|
||||
profileLocation: string
|
||||
profileBio: string
|
||||
profilePicture: string
|
||||
emailConfirmed: boolean
|
||||
ghostCard: Card
|
||||
cards: Card[]
|
||||
computedName: string,
|
||||
password: string,
|
||||
optOutOfLeaderboard: boolean,
|
||||
}
|
||||
|
||||
export interface CardSummaryGame {
|
||||
name: string
|
||||
rating: number
|
||||
lastLogin: string
|
||||
}
|
||||
|
||||
export interface CardSummary {
|
||||
mai2: CardSummaryGame | null
|
||||
chu3: CardSummaryGame | null
|
||||
ongeki: CardSummaryGame | null
|
||||
diva: CardSummaryGame | null
|
||||
wacca: CardSummaryGame | null
|
||||
}
|
||||
|
||||
|
||||
export interface ConfirmProps {
|
||||
title: string
|
||||
message: string
|
||||
confirm?: () => void
|
||||
cancel?: () => void
|
||||
dangerous?: boolean
|
||||
}
|
||||
|
||||
export interface GenericGamePlaylog {
|
||||
musicId: number
|
||||
level: number
|
||||
playDate: string
|
||||
achievement: number
|
||||
maxCombo: number
|
||||
totalCombo: number
|
||||
afterRating: number
|
||||
beforeRating: number
|
||||
isFullCombo?: boolean
|
||||
isAllPerfect?: boolean
|
||||
isAllJustice?: boolean
|
||||
}
|
||||
|
||||
export interface GenericRanking {
|
||||
name: string
|
||||
username: string
|
||||
rank: number
|
||||
accuracy: number
|
||||
rating: number
|
||||
fullCombo: number
|
||||
allPerfect: number
|
||||
lastSeen: string
|
||||
}
|
||||
|
||||
export interface RankCount {
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface GenericGameSummary {
|
||||
name: string
|
||||
iconId: number
|
||||
aquaUser?: AquaNetUser
|
||||
serverRank: number
|
||||
accuracy: number
|
||||
rating: number
|
||||
ratingHighest: number
|
||||
ranks: RankCount[]
|
||||
detailedRanks: { [key: number]: { [key: string]: number } }
|
||||
maxCombo: number
|
||||
fullCombo: number
|
||||
allPerfect: number
|
||||
totalScore: number
|
||||
plays: number
|
||||
totalPlayTime: number
|
||||
joined: string
|
||||
lastSeen: string
|
||||
lastVersion: string
|
||||
ratingComposition: { [key: string]: any }
|
||||
recent: GenericGamePlaylog[]
|
||||
rival?: boolean,
|
||||
favorites?: number[]
|
||||
}
|
||||
|
||||
export interface MusicMeta {
|
||||
name: string,
|
||||
composer: string,
|
||||
bpm: number,
|
||||
ver: number,
|
||||
notes: {
|
||||
lv: number
|
||||
designer: string
|
||||
lv_id: number
|
||||
notes: number
|
||||
}[],
|
||||
worldsEndTag?: string
|
||||
worldsEndStars?: number
|
||||
}
|
||||
|
||||
export type AllMusic = { [key: string]: MusicMeta }
|
||||
|
||||
export interface GameOption {
|
||||
key: string
|
||||
value: any
|
||||
type: 'Boolean' | 'String'
|
||||
game: string
|
||||
|
||||
changed?: boolean
|
||||
}
|
||||
|
||||
export interface UserItem { itemKind: number, itemId: number, stock: number }
|
||||
export interface UserBox {
|
||||
userName: string,
|
||||
nameplateId: number,
|
||||
frameId: number,
|
||||
characterId: number,
|
||||
trophyId: number,
|
||||
trophyIdSub1: number,
|
||||
trophyIdSub2: number,
|
||||
mapIconId: number,
|
||||
voiceId: number,
|
||||
avatarWear: number,
|
||||
avatarHead: number,
|
||||
avatarFace: number,
|
||||
avatarSkin: number,
|
||||
avatarItem: number,
|
||||
avatarFront: number,
|
||||
avatarBack: number,
|
||||
|
||||
level: number
|
||||
playerRating: number
|
||||
}
|
||||
|
||||
export interface ChusanMatchingOption {
|
||||
name: string
|
||||
ui: string
|
||||
guide: string
|
||||
matching: string
|
||||
reflector: string
|
||||
coop: string[]
|
||||
}
|
||||
24
AquaNet/src/libs/hooks/useLocalStorage.svelte.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const useLocalStorage = <T>(key: string, initialValue: T) => {
|
||||
let value = initialValue;
|
||||
|
||||
const currentValue = localStorage.getItem(key);
|
||||
if (currentValue) value = JSON.parse(currentValue);
|
||||
|
||||
const save = () => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
return {
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(v: T) {
|
||||
value = v;
|
||||
save();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
||||
93
AquaNet/src/libs/i18n.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { EN_REF, type LocalizedMessages } from "./i18n/en_ref";
|
||||
import { ZH } from "./i18n/zh";
|
||||
import type { GameName } from "./scoring";
|
||||
|
||||
import zhCountires from "./i18n/zh_countries.json"
|
||||
import enCountires from "./i18n/en_countries.json"
|
||||
|
||||
type Lang = 'en' | 'zh'
|
||||
|
||||
const msgs: Record<Lang, LocalizedMessages> = {
|
||||
en: EN_REF,
|
||||
zh: ZH
|
||||
}
|
||||
|
||||
const countries: Record<Lang, typeof enCountires> = {
|
||||
en: enCountires,
|
||||
zh: zhCountires
|
||||
}
|
||||
|
||||
let lang: Lang = 'en'
|
||||
|
||||
// Infer language from browser
|
||||
if (navigator.language.startsWith('zh')) {
|
||||
lang = 'zh'
|
||||
}
|
||||
|
||||
export function ts(key: string, variables?: { [index: string]: any }) {
|
||||
return t(key as keyof LocalizedMessages, variables)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the translation for the given key
|
||||
*
|
||||
* TODO: Check for translation completion on build
|
||||
*
|
||||
* @param key
|
||||
* @param variables
|
||||
*/
|
||||
export function t(key: keyof LocalizedMessages, variables?: { [index: string]: any }) {
|
||||
// Check if the key exists
|
||||
let msg = msgs[lang][key]
|
||||
if (!msg) {
|
||||
// Check if the key exists in English
|
||||
if (!(msg = msgs.en[key])) {
|
||||
msg = key
|
||||
console.error(`ERROR!! Missing translation reference entry (English) for ${key}`)
|
||||
}
|
||||
else console.warn(`Missing translation for ${key} in ${lang}`)
|
||||
}
|
||||
// Replace variables
|
||||
if (variables) {
|
||||
return msg.replace(/\${(.*?)}/g, (_: string, v: string | number) => variables[v] + "")
|
||||
}
|
||||
return msg
|
||||
}
|
||||
Object.assign(window, { t })
|
||||
|
||||
export function getCountryName(code: keyof typeof enCountires) {
|
||||
return countries[lang][code]
|
||||
}
|
||||
|
||||
export const GAME_TITLE: { [key in GameName]: string } =
|
||||
{chu3: t("game.chu3"), mai2: t("game.mai2"), ongeki: t("game.ongeki"), wacca: t("game.wacca")}
|
||||
|
||||
/**
|
||||
* Converts a two-letter country code to its corresponding flag emoji.
|
||||
*
|
||||
* The Unicode flag emoji is represented by two Regional Indicator Symbols.
|
||||
* Each letter in the country code is transformed into a Regional Indicator Symbol
|
||||
* by adding its alphabetical position (A = 0, B = 1, etc.) to the base code point U+1F1E6.
|
||||
*
|
||||
* @param countryCode - A two-letter ISO country code (e.g., "US", "GB").
|
||||
* @returns The corresponding flag emoji if the country code is valid; otherwise, an empty string.
|
||||
*/
|
||||
export function countryCodeToEmoji(countryCode: string): string {
|
||||
if (!countryCode) return ""
|
||||
if (countryCode.length !== 2) return ""
|
||||
|
||||
// Convert the country code to uppercase to standardize it
|
||||
const code = countryCode.toUpperCase();
|
||||
|
||||
// The base code point for Regional Indicator Symbol Letter A is 0x1F1E6.
|
||||
const OFFSET = 0x1F1E6;
|
||||
const firstCharCode = code.charCodeAt(0);
|
||||
const secondCharCode = code.charCodeAt(1);
|
||||
|
||||
// 'A' has a char code of 65.
|
||||
const firstIndicator = OFFSET + (firstCharCode - 65);
|
||||
const secondIndicator = OFFSET + (secondCharCode - 65);
|
||||
|
||||
// Create and return the flag emoji string
|
||||
return String.fromCodePoint(firstIndicator, secondIndicator);
|
||||
}
|
||||
248
AquaNet/src/libs/i18n/en_countries.json
Normal file
@ -0,0 +1,248 @@
|
||||
{
|
||||
"AF": "Afghanistan",
|
||||
"AX": "Aland Islands",
|
||||
"AL": "Albania",
|
||||
"DZ": "Algeria",
|
||||
"AS": "American Samoa",
|
||||
"AD": "Andorra",
|
||||
"AO": "Angola",
|
||||
"AI": "Anguilla",
|
||||
"AQ": "Antarctica",
|
||||
"AG": "Antigua And Barbuda",
|
||||
"AR": "Argentina",
|
||||
"AM": "Armenia",
|
||||
"AW": "Aruba",
|
||||
"AU": "Australia",
|
||||
"AT": "Austria",
|
||||
"AZ": "Azerbaijan",
|
||||
"BS": "Bahamas",
|
||||
"BH": "Bahrain",
|
||||
"BD": "Bangladesh",
|
||||
"BB": "Barbados",
|
||||
"BY": "Belarus",
|
||||
"BE": "Belgium",
|
||||
"BZ": "Belize",
|
||||
"BJ": "Benin",
|
||||
"BM": "Bermuda",
|
||||
"BT": "Bhutan",
|
||||
"BO": "Bolivia",
|
||||
"BA": "Bosnia And Herzegovina",
|
||||
"BW": "Botswana",
|
||||
"BV": "Bouvet Island",
|
||||
"BR": "Brazil",
|
||||
"IO": "British Indian Ocean Territory",
|
||||
"BN": "Brunei Darussalam",
|
||||
"BG": "Bulgaria",
|
||||
"BF": "Burkina Faso",
|
||||
"BI": "Burundi",
|
||||
"KH": "Cambodia",
|
||||
"CM": "Cameroon",
|
||||
"CA": "Canada",
|
||||
"CV": "Cape Verde",
|
||||
"KY": "Cayman Islands",
|
||||
"CF": "Central African Republic",
|
||||
"TD": "Chad",
|
||||
"CL": "Chile",
|
||||
"CN": "China",
|
||||
"CX": "Christmas Island",
|
||||
"CC": "Cocos (Keeling) Islands",
|
||||
"CO": "Colombia",
|
||||
"KM": "Comoros",
|
||||
"CG": "Congo",
|
||||
"CD": "Congo, Democratic Republic",
|
||||
"CK": "Cook Islands",
|
||||
"CR": "Costa Rica",
|
||||
"CI": "Cote D\"Ivoire",
|
||||
"HR": "Croatia",
|
||||
"CU": "Cuba",
|
||||
"CY": "Cyprus",
|
||||
"CZ": "Czech Republic",
|
||||
"DK": "Denmark",
|
||||
"DJ": "Djibouti",
|
||||
"DM": "Dominica",
|
||||
"DO": "Dominican Republic",
|
||||
"EC": "Ecuador",
|
||||
"EG": "Egypt",
|
||||
"SV": "El Salvador",
|
||||
"GQ": "Equatorial Guinea",
|
||||
"ER": "Eritrea",
|
||||
"EE": "Estonia",
|
||||
"ET": "Ethiopia",
|
||||
"FK": "Falkland Islands (Malvinas)",
|
||||
"FO": "Faroe Islands",
|
||||
"FJ": "Fiji",
|
||||
"FI": "Finland",
|
||||
"FR": "France",
|
||||
"GF": "French Guiana",
|
||||
"PF": "French Polynesia",
|
||||
"TF": "French Southern Territories",
|
||||
"GA": "Gabon",
|
||||
"GM": "Gambia",
|
||||
"GE": "Georgia",
|
||||
"DE": "Germany",
|
||||
"GH": "Ghana",
|
||||
"GI": "Gibraltar",
|
||||
"GR": "Greece",
|
||||
"GL": "Greenland",
|
||||
"GD": "Grenada",
|
||||
"GP": "Guadeloupe",
|
||||
"GU": "Guam",
|
||||
"GT": "Guatemala",
|
||||
"GG": "Guernsey",
|
||||
"GN": "Guinea",
|
||||
"GW": "Guinea-Bissau",
|
||||
"GY": "Guyana",
|
||||
"HT": "Haiti",
|
||||
"HM": "Heard Island & Mcdonald Islands",
|
||||
"VA": "Holy See (Vatican City State)",
|
||||
"HN": "Honduras",
|
||||
"HK": "Hong Kong",
|
||||
"HU": "Hungary",
|
||||
"IS": "Iceland",
|
||||
"IN": "India",
|
||||
"ID": "Indonesia",
|
||||
"IR": "Iran, Islamic Republic Of",
|
||||
"IQ": "Iraq",
|
||||
"IE": "Ireland",
|
||||
"IM": "Isle Of Man",
|
||||
"IL": "Israel",
|
||||
"IT": "Italy",
|
||||
"JM": "Jamaica",
|
||||
"JP": "Japan",
|
||||
"JE": "Jersey",
|
||||
"JO": "Jordan",
|
||||
"KZ": "Kazakhstan",
|
||||
"KE": "Kenya",
|
||||
"KI": "Kiribati",
|
||||
"KR": "Korea",
|
||||
"KP": "North Korea",
|
||||
"KW": "Kuwait",
|
||||
"KG": "Kyrgyzstan",
|
||||
"LA": "Lao People\"s Democratic Republic",
|
||||
"LV": "Latvia",
|
||||
"LB": "Lebanon",
|
||||
"LS": "Lesotho",
|
||||
"LR": "Liberia",
|
||||
"LY": "Libyan Arab Jamahiriya",
|
||||
"LI": "Liechtenstein",
|
||||
"LT": "Lithuania",
|
||||
"LU": "Luxembourg",
|
||||
"MO": "Macao",
|
||||
"MK": "Macedonia",
|
||||
"MG": "Madagascar",
|
||||
"MW": "Malawi",
|
||||
"MY": "Malaysia",
|
||||
"MV": "Maldives",
|
||||
"ML": "Mali",
|
||||
"MT": "Malta",
|
||||
"MH": "Marshall Islands",
|
||||
"MQ": "Martinique",
|
||||
"MR": "Mauritania",
|
||||
"MU": "Mauritius",
|
||||
"YT": "Mayotte",
|
||||
"MX": "Mexico",
|
||||
"FM": "Micronesia, Federated States Of",
|
||||
"MD": "Moldova",
|
||||
"MC": "Monaco",
|
||||
"MN": "Mongolia",
|
||||
"ME": "Montenegro",
|
||||
"MS": "Montserrat",
|
||||
"MA": "Morocco",
|
||||
"MZ": "Mozambique",
|
||||
"MM": "Myanmar",
|
||||
"NA": "Namibia",
|
||||
"NR": "Nauru",
|
||||
"NP": "Nepal",
|
||||
"NL": "Netherlands",
|
||||
"AN": "Netherlands Antilles",
|
||||
"NC": "New Caledonia",
|
||||
"NZ": "New Zealand",
|
||||
"NI": "Nicaragua",
|
||||
"NE": "Niger",
|
||||
"NG": "Nigeria",
|
||||
"NU": "Niue",
|
||||
"NF": "Norfolk Island",
|
||||
"MP": "Northern Mariana Islands",
|
||||
"NO": "Norway",
|
||||
"OM": "Oman",
|
||||
"PK": "Pakistan",
|
||||
"PW": "Palau",
|
||||
"PS": "Palestinian Territory, Occupied",
|
||||
"PA": "Panama",
|
||||
"PG": "Papua New Guinea",
|
||||
"PY": "Paraguay",
|
||||
"PE": "Peru",
|
||||
"PH": "Philippines",
|
||||
"PN": "Pitcairn",
|
||||
"PL": "Poland",
|
||||
"PT": "Portugal",
|
||||
"PR": "Puerto Rico",
|
||||
"QA": "Qatar",
|
||||
"RE": "Reunion",
|
||||
"RO": "Romania",
|
||||
"RU": "Russian Federation",
|
||||
"RW": "Rwanda",
|
||||
"BL": "Saint Barthelemy",
|
||||
"SH": "Saint Helena",
|
||||
"KN": "Saint Kitts And Nevis",
|
||||
"LC": "Saint Lucia",
|
||||
"MF": "Saint Martin",
|
||||
"PM": "Saint Pierre And Miquelon",
|
||||
"VC": "Saint Vincent And Grenadines",
|
||||
"WS": "Samoa",
|
||||
"SM": "San Marino",
|
||||
"ST": "Sao Tome And Principe",
|
||||
"SA": "Saudi Arabia",
|
||||
"SN": "Senegal",
|
||||
"RS": "Serbia",
|
||||
"SC": "Seychelles",
|
||||
"SL": "Sierra Leone",
|
||||
"SG": "Singapore",
|
||||
"SK": "Slovakia",
|
||||
"SI": "Slovenia",
|
||||
"SB": "Solomon Islands",
|
||||
"SO": "Somalia",
|
||||
"ZA": "South Africa",
|
||||
"GS": "South Georgia And Sandwich Isl.",
|
||||
"ES": "Spain",
|
||||
"LK": "Sri Lanka",
|
||||
"SD": "Sudan",
|
||||
"SR": "Suriname",
|
||||
"SJ": "Svalbard And Jan Mayen",
|
||||
"SZ": "Swaziland",
|
||||
"SE": "Sweden",
|
||||
"CH": "Switzerland",
|
||||
"SY": "Syrian Arab Republic",
|
||||
"TW": "Taiwan",
|
||||
"TJ": "Tajikistan",
|
||||
"TZ": "Tanzania",
|
||||
"TH": "Thailand",
|
||||
"TL": "Timor-Leste",
|
||||
"TG": "Togo",
|
||||
"TK": "Tokelau",
|
||||
"TO": "Tonga",
|
||||
"TT": "Trinidad And Tobago",
|
||||
"TN": "Tunisia",
|
||||
"TR": "Turkey",
|
||||
"TM": "Turkmenistan",
|
||||
"TC": "Turks And Caicos Islands",
|
||||
"TV": "Tuvalu",
|
||||
"UG": "Uganda",
|
||||
"UA": "Ukraine",
|
||||
"AE": "United Arab Emirates",
|
||||
"GB": "United Kingdom",
|
||||
"US": "United States",
|
||||
"UM": "United States Outlying Islands",
|
||||
"UY": "Uruguay",
|
||||
"UZ": "Uzbekistan",
|
||||
"VU": "Vanuatu",
|
||||
"VE": "Venezuela",
|
||||
"VN": "Vietnam",
|
||||
"VG": "Virgin Islands, British",
|
||||
"VI": "Virgin Islands, U.S.",
|
||||
"WF": "Wallis And Futuna",
|
||||
"EH": "Western Sahara",
|
||||
"YE": "Yemen",
|
||||
"ZM": "Zambia",
|
||||
"ZW": "Zimbabwe"
|
||||
}
|
||||
300
AquaNet/src/libs/i18n/en_ref.ts
Normal file
@ -0,0 +1,300 @@
|
||||
export const EN_REF_USER = {
|
||||
'UserHome.ServerRank': 'Server Rank',
|
||||
'UserHome.DXRating': 'DX Rating',
|
||||
'UserHome.Rating': 'Rating',
|
||||
'UserHome.Statistics': 'Statistics',
|
||||
'UserHome.Accuracy': 'Accuracy',
|
||||
'UserHome.MaxCombo': 'Max Combo',
|
||||
'UserHome.FullCombo': 'Full Combo',
|
||||
'UserHome.AllPerfect': 'All Perfect',
|
||||
'UserHome.DXScore': 'DX Score',
|
||||
'UserHome.Score': 'Score',
|
||||
'UserHome.PlayActivity': ' Play Activity',
|
||||
'UserHome.Plays': 'Plays',
|
||||
'UserHome.PlayTime': 'Play Time',
|
||||
'UserHome.FirstSeen': 'First Seen',
|
||||
'UserHome.LastSeen': 'Last Seen',
|
||||
'UserHome.Version': 'Last Version',
|
||||
'UserHome.RecentScores': 'Recent Scores',
|
||||
'UserHome.NoData': 'No data in the past ${days} days',
|
||||
'UserHome.UnknownSong': '(unknown song)',
|
||||
'UserHome.Settings': 'Settings',
|
||||
'UserHome.NoValidGame': 'The user hasn\'t played any game yet.',
|
||||
'UserHome.ShowRanksDetails': 'Click to show details',
|
||||
'UserHome.RankDetail.Title': 'Achievement Details',
|
||||
'UserHome.RankDetail.Level': 'Level',
|
||||
'UserHome.B50': 'B50',
|
||||
'UserHome.AddRival': "Add to Rival",
|
||||
'UserHome.RemoveRival': "Remove from Rival",
|
||||
'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.",
|
||||
'UserHome.ShowMoreRecent': 'Show more',
|
||||
'UserHome.FavoriteSongs': 'Favorite Songs'
|
||||
}
|
||||
|
||||
export const EN_REF_Welcome = {
|
||||
'back': 'Back',
|
||||
'email': 'Email',
|
||||
'password': 'Password',
|
||||
'new-password': 'New password',
|
||||
'username': 'Username',
|
||||
'welcome.btn-login': 'Log in',
|
||||
'welcome.btn-signup': 'Sign up',
|
||||
'welcome.btn-reset-password': 'Forgot password?',
|
||||
'welcome.btn-submit-reset-password': 'Send reset link',
|
||||
'welcome.btn-submit-new-password': 'Change password',
|
||||
'welcome.email-missing': 'Email is required',
|
||||
'welcome.password-missing': 'Password is required',
|
||||
'welcome.username-missing': 'Username/email is required',
|
||||
'welcome.email-password-missing': 'Email and password are required',
|
||||
'welcome.waiting-turnstile': 'Waiting for Turnstile to verify your network environment...',
|
||||
'welcome.turnstile-error': 'Error verifying your network environment. Please turn off your VPN and try again.',
|
||||
'welcome.turnstile-timeout': 'Network verification timed out. Please try again.',
|
||||
'welcome.verification-sent': 'A verification email has been sent to ${email}. Please check your inbox!',
|
||||
'welcome.reset-password-sent': 'A password reset email has been sent to ${email}. Please check your inbox!',
|
||||
'welcome.verify-state-0': 'You haven\'t verified your email. A verification email has been sent to your inbox just now. Please check your inbox!',
|
||||
'welcome.verify-state-1': 'You haven\'t verified your email. You have requested too many emails, please try again later.',
|
||||
'welcome.verify-state-2': 'You haven\'t verified your email. We just sent you another verification email. Please check your inbox!',
|
||||
'welcome.reset-state-0': 'A reset email has been sent to your inbox just now. Please check your inbox!',
|
||||
'welcome.reset-state-1': 'Too many emails have been sent. Another will not be sent.',
|
||||
'welcome.verifying': 'Verifying your email... please wait.',
|
||||
'welcome.verified': 'Your email has been verified! You can now log in now.',
|
||||
'welcome.verification-failed': 'Verification failed: ${message}. Please try again.',
|
||||
'welcome.password-reset-done': 'Your password has been updated! Please log back in.',
|
||||
}
|
||||
|
||||
export const EN_REF_LEADERBOARD = {
|
||||
'Leaderboard.Title': 'Server Leaderboard',
|
||||
'Leaderboard.Rank': 'Rank',
|
||||
'Leaderboard.Rating': 'Rating',
|
||||
'Leaderboard.Accuracy': 'Accuracy',
|
||||
'Leaderboard.FC': 'FC',
|
||||
'Leaderboard.AP': 'AP',
|
||||
}
|
||||
|
||||
export const EN_REF_GENERAL = {
|
||||
'game.mai2': 'Mai',
|
||||
'game.chu3': 'Chuni',
|
||||
'game.ongeki': 'Ongeki',
|
||||
'game.wacca': 'Wacca',
|
||||
'status.error': 'Error',
|
||||
'status.error.hint': 'Something went wrong, please try again later or ',
|
||||
'status.error.hint.link': 'join our discord for support.',
|
||||
'status.detail': 'Detail: ${detail}',
|
||||
'action.refresh': 'Refresh',
|
||||
'action.cancel': 'Cancel',
|
||||
'action.confirm': 'Confirm',
|
||||
'navigation.profile': 'Profile',
|
||||
'navigation.maps': 'Maps',
|
||||
'navigation.home': 'Home',
|
||||
'navigation.rankings': 'Rankings',
|
||||
'navigation.notice': 'Notice'
|
||||
}
|
||||
|
||||
export const EN_REF_HOME = {
|
||||
'home.nav.portal': 'Portal',
|
||||
'home.nav.link-card': 'Link Card',
|
||||
'home.nav.game-setup': 'Game Setup',
|
||||
'home.manage-cards': 'Manage Cards',
|
||||
'home.manage-cards-description': 'Link, unlink, and manage your cards.',
|
||||
'home.link-card': 'Link Card',
|
||||
'home.link-cards-description': 'Link your Amusement IC / Aime card to play games.',
|
||||
'home.join-community': 'Join Community',
|
||||
'home.join-community-description': 'Join our community to chat with other players and get help.',
|
||||
'home.setup': 'Setup Connection',
|
||||
'home.setup-description': 'If you own a cab or arcade setup, begin setting up the connection.',
|
||||
'home.import': 'Import Player Data',
|
||||
'home.import-description': 'If you are from another server, you can import your data here.',
|
||||
'home.linkcard.cards': 'Your Cards',
|
||||
'home.linkcard.description': 'Here are the cards you have linked to your account',
|
||||
'home.linkcard.account-card': 'Account Card',
|
||||
'home.linkcard.registered': 'Registered',
|
||||
'home.linkcard.lastused': 'Last used',
|
||||
'home.linkcard.enter-info': 'Please enter the following information, or drag and drop your aime.txt / felica.txt file here',
|
||||
'home.linkcard.access-code': 'The 20-digit access code on the back of your card. (If it doesn\'t work, please try scanning your card in game and enter the access code shown on screen)',
|
||||
'home.linkcard.enter-sn1': 'Download the NFC Tools app on your phone',
|
||||
'home.linkcard.enter-sn2': 'and scan your card. Then, enter the Serial Number.',
|
||||
'home.linkcard.kdx-notice': "If you're using KanadeDX, please enter the simulated card number (you can find it in settings > card).",
|
||||
'home.linkcard.link': 'Link',
|
||||
'home.linkcard.data-conflict': 'Data Conflict',
|
||||
'home.linkcard.name': 'Name',
|
||||
'home.linkcard.rating': 'Rating',
|
||||
'home.linkcard.last-login': 'Last Login',
|
||||
'home.linkcard.linked-own': 'This card is already linked to your account',
|
||||
'home.linkcard.linked-another': 'This card is already linked to another account',
|
||||
'home.linkcard.notfound': 'Card not found',
|
||||
'home.linkcard.unlink': 'Unlink Card',
|
||||
'home.linkcard.unlink-notice': 'Are you sure you want to unlink this card?',
|
||||
'home.linkcard.felica-ac-warning': 'This Access Code is of a FeliCa AIC card.\nIf you are logging in with a physical card (not aime.txt emulation), unlike the official server, you need to bind the FeliCa SN of the card (or the 00-prefixed card number shown in the game) instead of this code.\nIf you are logging in with aime.txt emulation, please ignore this warning and proceed.',
|
||||
'home.setup.welcome': 'Welcome! If you own an arcade cabinet or game setup, please follow the instructions below to set up the connection with AquaDX.',
|
||||
'home.setup.blockquote': 'We assume that you already have the required files and can run the game (e.g. ROM and segatools) that come with the cabinet or game setup. If not, please contact the seller of your device for the required files, as we will not provide them for copyright reasons.',
|
||||
'home.setup.get': 'Get started',
|
||||
'home.setup.edit': 'Please edit your segatools.ini file and modify the following lines',
|
||||
'home.setup.test': 'Then, after you restart the game, you should be able to connect to AquaDX. Please verify that the network tests are all GOOD in the test menu.',
|
||||
'home.setup.ask': 'If you have any questions, please ask in our',
|
||||
'home.setup.support': 'server',
|
||||
'home.setup.keychip-tips': 'This is your unique keychip, do not share it with anyone',
|
||||
'home.community.discord': 'Discord',
|
||||
'home.community.telegram': 'Telegram (Chinese)',
|
||||
'home.community.qq': 'QQ (Chinese)',
|
||||
'home.import.unknown-game': 'Unknown game type. Currently only maimai and chunithm are supported for importing.',
|
||||
'home.import.new-data': 'Data to import',
|
||||
'home.import.data-conflict': 'Proceed will override your current data',
|
||||
}
|
||||
|
||||
export const EN_REF_SETTINGS = {
|
||||
'settings.title': 'Settings',
|
||||
'settings.tabs.profile': 'Profile',
|
||||
'settings.tabs.game': 'Game',
|
||||
'settings.tabs.chu3': 'Chuni',
|
||||
'settings.tabs.mai2': 'Mai',
|
||||
'settings.tabs.ongeki': 'Ongeki',
|
||||
'settings.tabs.wacca': 'Wacca',
|
||||
'settings.fields.unlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
|
||||
'settings.fields.unlockChara.name': 'Unlock All Characters',
|
||||
'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
|
||||
'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
|
||||
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
|
||||
'settings.fields.unlockTickets.name': 'Unlock All Tickets',
|
||||
'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (Note: maimai still limits which tickets can be used).',
|
||||
'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
|
||||
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
|
||||
'settings.fields.chusanTeamName.name': 'Team Name',
|
||||
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
|
||||
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
|
||||
'settings.fields.chusanInfinitePenguins.desc': 'Set penguin statues for character level prompting to 999.',
|
||||
'settings.fields.chusanMatchingReflector.name': 'Matching Server Reflector',
|
||||
'settings.fields.chusanMatchingReflector.desc': 'URL of the national matching server\'s UDP reflector.',
|
||||
'settings.fields.chusanMatchingServer.name': 'Matching Server',
|
||||
'settings.fields.chusanMatchingServer.desc': 'URL of the national matching server.',
|
||||
'settings.fields.ongekiInfiniteKaika.name': 'Infinite Kaika',
|
||||
'settings.fields.ongekiInfiniteKaika.desc': 'Set Kaika to 999',
|
||||
'settings.fields.rounding.name': 'Score Rounding',
|
||||
'settings.fields.rounding.desc': 'Round the score to one decimal place',
|
||||
'settings.fields.gameUsername.name': 'In-Game Username',
|
||||
'settings.fields.gameUsername.desc': 'Your name shown in game',
|
||||
'settings.fields.optOutOfLeaderboard.name': 'Opt Out of Leaderboard',
|
||||
'settings.fields.optOutOfLeaderboard.desc': 'You will still be able to see yourself on the leaderboard after logging in',
|
||||
'settings.fields.enableMusicRank.name': 'Enable Recommended Music Rank on Your Machine',
|
||||
'settings.fields.enableMusicRank.desc': 'If you have your own ranking, you can turn this off. It only affects your own machine',
|
||||
'settings.mai2.name': 'Player Name',
|
||||
'settings.profile.picture': 'Profile Picture',
|
||||
'settings.profile.upload-new': 'Upload New',
|
||||
'settings.profile.bad-format': 'Invalid image format. Supported types are PNG, JPG, JPEG, WEBP & GIF.',
|
||||
'settings.profile.save': 'Save',
|
||||
'settings.profile.name': 'Display Name',
|
||||
'settings.profile.username': 'Username',
|
||||
'settings.profile.password': 'Password',
|
||||
'settings.profile.country': 'Country',
|
||||
'settings.profile.location': 'Location',
|
||||
'settings.profile.bio': 'Bio',
|
||||
'settings.profile.unset': 'Unset',
|
||||
'settings.profile.logout': 'Log out',
|
||||
'settings.profile.unchanged': 'Unchanged',
|
||||
'settings.export': 'Export Player Data',
|
||||
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
|
||||
'settings.gameNotice': "These only apply to Mai and Wacca.",
|
||||
'settings.regionNotice': "These only apply to Mai, Ongeki and Chuni.",
|
||||
'settings.regionSelector.title': "Prefecture Selector",
|
||||
'settings.regionSelector.desc': "Select the region where you want the game to think you are playing",
|
||||
'settings.regionSelector.select': "Select Prefecture",
|
||||
}
|
||||
|
||||
export const EN_REF_USERBOX = {
|
||||
'userbox.header.general': 'General Settings',
|
||||
'userbox.header.matching': 'National Matching',
|
||||
'userbox.header.matching.symbolChat': 'Chat Symbols (Matching)',
|
||||
'userbox.header.userbox': 'UserBox Settings',
|
||||
'userbox.header.preview': 'UserBox Preview',
|
||||
'userbox.nameplateId': 'Nameplate',
|
||||
'userbox.frameId': 'Frame',
|
||||
'userbox.trophyId': 'Trophy (Title)',
|
||||
'userbox.trophyIdSub1': 'Trophy Sub #1 (Title)',
|
||||
'userbox.trophyIdSub2': 'Trophy Sub #2 (Title)',
|
||||
'userbox.mapIconId': 'Map Icon',
|
||||
'userbox.voiceId': 'System Voice',
|
||||
'userbox.avatarWear': 'Avatar Wear',
|
||||
'userbox.avatarHead': 'Avatar Head',
|
||||
'userbox.avatarFace': 'Avatar Face',
|
||||
'userbox.avatarSkin': 'Avatar Skin',
|
||||
'userbox.avatarItem': 'Avatar Item',
|
||||
'userbox.avatarFront': 'Avatar Front',
|
||||
'userbox.avatarBack': 'Avatar Back',
|
||||
'userbox.preview.url': 'Image URL',
|
||||
'userbox.error.nodata': 'Chuni data not found',
|
||||
|
||||
'userbox.matching.select': 'Select Matching Server',
|
||||
'userbox.matching.select.sub': 'Choose the matching server you want to use.',
|
||||
'userbox.matching.option.ui': 'Rooms',
|
||||
'userbox.matching.option.guide': 'Guide',
|
||||
'userbox.matching.option.collab': 'Collaborators',
|
||||
'userbox.matching.custom.name': 'Custom',
|
||||
'userbox.matching.custom.sub': 'Enter your own URL',
|
||||
'userbox.matching.symbolChat': 'Message Choice',
|
||||
'userbox.matching.symbolChat.default': 'Default',
|
||||
|
||||
'userbox.new.name': 'AquaBox',
|
||||
'userbox.new.setup': 'Drag and drop your Chuni game folder (Lumi or newer) into the box below to display UserBoxes with their nameplate & avatar. All files are handled in-browser.',
|
||||
'userbox.new.setup.notice': 'Select the highest folder containing your game data.',
|
||||
'userbox.new.setup.processing_file': 'Processing',
|
||||
'userbox.new.setup.finalizing': 'Saving to internal storage',
|
||||
'userbox.new.drop': 'Drop game folder here',
|
||||
'userbox.new.switch.to_url': 'Switch to URL mode',
|
||||
'userbox.new.switch.to_drop': 'Switch to drop mode',
|
||||
'userbox.new.url_warning': 'Enter in the path to access Userbox assets. You are responsible for any results in this state. Please read the documentation. Don\'t expect support for this mode.',
|
||||
'userbox.new.activate_first': 'Enable AquaBox (game files required)',
|
||||
'userbox.new.activate_update': 'Update AquaBox (game files required)',
|
||||
'userbox.new.activate': 'Use AquaBox',
|
||||
'userbox.new.activate_desc': 'Enable displaying UserBoxes with their nameplate & avatar',
|
||||
'userbox.new.activate_profile': 'Use AquaBox on profiles',
|
||||
'userbox.new.activate_profile_desc': 'Enable displaying UserBoxes with their nameplate & avatar on profile pages',
|
||||
'userbox.new.error.invalidFolder': 'The folder you selected is invalid. Ensure that your game\'s version is Lumi or newer and that the "A000" option pack is present.',
|
||||
'userbox.new.error.invalidUrl': 'The URL you inputted is invalid.'
|
||||
}
|
||||
|
||||
export const EN_REF_MAI_PHOTO = {
|
||||
'maiphoto.title': 'Mai Memorial Photo Gallery',
|
||||
'maiphoto.url_warning': 'Note: If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
|
||||
'maiphoto.none': 'No photo found. You can upload photo by clicking upload at the end of each game session.',
|
||||
}
|
||||
|
||||
export const EN_REF_AQUATRANS = {
|
||||
'trans.title': '🏳️⚧️ AquaTrans™ Data Transfer',
|
||||
'trans.confirm.unbackuped.title': 'Confirm transfer',
|
||||
'trans.confirm.unbackuped.msg': "It seems like you haven't backed up your destination data. Are you sure you want to proceed? (This will overwrite your destination server's data)",
|
||||
'trans.confirm.untested.title': 'Error',
|
||||
'trans.confirm.untested.msg': "It seems like you haven't tested both connections yet. Please test the connections first.",
|
||||
'trans.confirm.done.title': 'Done!',
|
||||
'trans.confirm.done.msg': 'Transfer completed successfully! Your data on ${dst} is overwritten with your data from ${src}.',
|
||||
'trans.alert.in-progress': "Transfer already in progress!",
|
||||
'trans.prompt-html': `
|
||||
<p>👋 Welcome to the AquaTrans™ server data transfer tool!</p>
|
||||
<p>You can use this to export data from any server, and input data into any server using the connection credentials (card number, server address, and keychip id).</p>
|
||||
<p>This tool will simulate a game client and pull your data from the source server, and push your data to the destination server.</p>
|
||||
<p>Please fill out the info below to get started!</p>
|
||||
`,
|
||||
'trans.error.empty': 'Please fill out all fields.',
|
||||
'trans.error.untested': 'Please test the connections first.',
|
||||
'trans.success.import': 'Data imported successfully!',
|
||||
'trans.source.title': 'Source Server',
|
||||
'trans.target.title': 'Destination Server',
|
||||
'trans.field.addr': 'Server Address',
|
||||
'trans.field.keychip': 'Keychip ID',
|
||||
'trans.field.game': 'Game',
|
||||
'trans.field.version': 'Version',
|
||||
'trans.field.card': 'Card Number',
|
||||
'trans.btn.test': 'Test Connection',
|
||||
'trans.btn.export': 'Export Data',
|
||||
'trans.btn.import': 'Import Data',
|
||||
'trans.blacklist': "Your server's rules doesn't allow using this tool. You might get banned if you try (idk, ask them if you want to know why)",
|
||||
}
|
||||
|
||||
export const EN_REF = { ...EN_REF_USER, ...EN_REF_Welcome, ...EN_REF_GENERAL,
|
||||
...EN_REF_LEADERBOARD, ...EN_REF_HOME, ...EN_REF_SETTINGS, ...EN_REF_USERBOX,
|
||||
...EN_REF_MAI_PHOTO, ...EN_REF_AQUATRANS
|
||||
}
|
||||
|
||||
export type LocalizedMessages = typeof EN_REF
|
||||
315
AquaNet/src/libs/i18n/zh.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import {
|
||||
EN_REF_AQUATRANS,
|
||||
EN_REF_GENERAL,
|
||||
EN_REF_HOME,
|
||||
EN_REF_LEADERBOARD,
|
||||
EN_REF_MAI_PHOTO,
|
||||
EN_REF_SETTINGS,
|
||||
EN_REF_USER,
|
||||
EN_REF_USERBOX,
|
||||
type EN_REF_Welcome
|
||||
} from "./en_ref";
|
||||
|
||||
const zhUser: typeof EN_REF_USER = {
|
||||
'UserHome.ServerRank': '服务器排名',
|
||||
'UserHome.DXRating': 'DX B50',
|
||||
'UserHome.Rating': '评分',
|
||||
'UserHome.Statistics': '统计数据',
|
||||
'UserHome.Accuracy': '准确率',
|
||||
'UserHome.MaxCombo': '最大连击',
|
||||
'UserHome.FullCombo': '全连曲目',
|
||||
'UserHome.AllPerfect': '完美曲目',
|
||||
'UserHome.DXScore': 'DX 总分',
|
||||
'UserHome.Score': '总分',
|
||||
'UserHome.PlayActivity': '游戏活动',
|
||||
'UserHome.Plays': '出勤次数',
|
||||
'UserHome.PlayTime': '游玩时间',
|
||||
'UserHome.FirstSeen': '发现新大陆',
|
||||
'UserHome.LastSeen': '上次出勤',
|
||||
'UserHome.Version': '游戏版本',
|
||||
'UserHome.RecentScores': '成绩',
|
||||
'UserHome.NoData': '过去 ${days} 天内没有玩过',
|
||||
'UserHome.UnknownSong': "(未知曲目)",
|
||||
'UserHome.Settings': '设置',
|
||||
'UserHome.NoValidGame': "用户还没有玩过游戏",
|
||||
'UserHome.ShowRanksDetails': "点击显示评分详细",
|
||||
'UserHome.RankDetail.Title': '评分详细',
|
||||
'UserHome.RankDetail.Level': "等级",
|
||||
'UserHome.B50': "B50",
|
||||
'UserHome.AddRival': "添加劲敌",
|
||||
'UserHome.RemoveRival': "移除劲敌",
|
||||
'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、华卡和音击。",
|
||||
'UserHome.ShowMoreRecent': "显示更多",
|
||||
'UserHome.FavoriteSongs': "收藏歌曲"
|
||||
}
|
||||
|
||||
const zhWelcome: typeof EN_REF_Welcome = {
|
||||
'back': '返回',
|
||||
'email': '邮箱',
|
||||
'password': '密码',
|
||||
'new-password': '新密码',
|
||||
'username': '用户名',
|
||||
'welcome.btn-login': '登录',
|
||||
'welcome.btn-signup': '注册',
|
||||
'welcome.btn-reset-password': '忘记密码?',
|
||||
'welcome.btn-submit-reset-password': '发送重置链接',
|
||||
'welcome.btn-submit-new-password': '修改密码',
|
||||
'welcome.email-missing': '邮箱必须填哦',
|
||||
'welcome.password-missing': '密码必须填哦',
|
||||
'welcome.username-missing': '用户名/邮箱必须填哦',
|
||||
'welcome.email-password-missing': '邮箱和密码必须填哦',
|
||||
'welcome.waiting-turnstile': '正在验证网络环境…',
|
||||
'welcome.turnstile-error': '验证网络环境出错了,请关闭 VPN 后重试',
|
||||
'welcome.turnstile-timeout': '验证网络环境超时了,请重试',
|
||||
'welcome.verification-sent': '验证邮件已发送至 ${email},请翻翻收件箱',
|
||||
'welcome.reset-password-sent': '重置邮件已发送至 ${email},请翻翻收件箱',
|
||||
'welcome.verify-state-0': '您还没有验证邮箱哦!验证邮件一分钟内刚刚发到您的邮箱,请翻翻收件箱',
|
||||
'welcome.verify-state-1': '您还没有验证邮箱哦!我们在过去的 24 小时内已经发送了 3 封验证邮件,所以我们不会再发送了,请翻翻收件箱',
|
||||
'welcome.verify-state-2': '您还没有验证邮箱哦!我们刚刚又发送了一封验证邮件,请翻翻收件箱',
|
||||
'welcome.reset-state-0': '重置邮件刚刚发送到你的邮箱啦,请翻翻收件箱!',
|
||||
'welcome.reset-state-1': '邮件发送次数过多,暂时不会再发送新的重置邮件了',
|
||||
'welcome.verifying': '正在验证邮箱…请稍等',
|
||||
'welcome.verified': '您的邮箱已经验证成功!您现在可以登录了',
|
||||
'welcome.verification-failed': '验证失败:${message}。请重试',
|
||||
'welcome.password-reset-done': '您的密码已更新!请重新登录',
|
||||
}
|
||||
|
||||
const zhLeaderboard: typeof EN_REF_LEADERBOARD = {
|
||||
'Leaderboard.Title': '排行榜',
|
||||
'Leaderboard.Rank': '排名',
|
||||
'Leaderboard.Rating': '评分',
|
||||
'Leaderboard.Accuracy': '准确率',
|
||||
'Leaderboard.FC': 'FC',
|
||||
'Leaderboard.AP': 'AP',
|
||||
}
|
||||
|
||||
const zhGeneral: typeof EN_REF_GENERAL = {
|
||||
'game.mai2': "舞萌",
|
||||
'game.chu3': "中二",
|
||||
'game.ongeki': "音击",
|
||||
'game.wacca': "华卡",
|
||||
"status.error": "发生错误",
|
||||
"status.error.hint": "出了一些问题,请稍后刷新重试或者",
|
||||
"status.error.hint.link": "加我们的 Discord 群问一问",
|
||||
"status.detail": "详细信息:${detail}",
|
||||
"action.refresh": "刷新",
|
||||
"action.cancel": "取消",
|
||||
"action.confirm": "确认",
|
||||
'navigation.profile': '个人资料',
|
||||
'navigation.maps': '地图',
|
||||
'navigation.home': '首页',
|
||||
'navigation.rankings': '排行榜',
|
||||
'navigation.notice': '公告'
|
||||
}
|
||||
|
||||
const zhHome: typeof EN_REF_HOME = {
|
||||
'home.nav.portal': "主页",
|
||||
'home.nav.link-card': "绑卡",
|
||||
'home.nav.game-setup': "连接设置",
|
||||
'home.manage-cards': '管理游戏卡',
|
||||
'home.manage-cards-description': '绑定、解绑、管理游戏数据卡',
|
||||
'home.link-card': '绑定游戏卡',
|
||||
'home.link-cards-description':'绑定游戏数据卡(Amusement IC 或 Aime 卡)后才可以访问游戏存档哦',
|
||||
'home.join-community': '加入群组',
|
||||
'home.join-community-description': '加入我们的聊天群组,与其他玩家聊天、获取帮助',
|
||||
'home.setup': '连接到 AquaDX',
|
||||
'home.setup-description': '如果您有街机框体或者手台,点击这里设置服务器的连接',
|
||||
'home.import': '导入玩家数据',
|
||||
'home.import-description': '如果你来自其他在线服,可以点击这里导入从其他服务器导出的数据',
|
||||
'home.linkcard.cards': "已绑卡片",
|
||||
'home.linkcard.description': "这些是您绑定到帐户的卡",
|
||||
'home.linkcard.account-card': "账户卡",
|
||||
'home.linkcard.registered': "注册于",
|
||||
'home.linkcard.lastused': "上次使用",
|
||||
'home.linkcard.enter-info': "请输入以下信息,或将 aime.txt / felica.txt 文件拖放到此区域",
|
||||
'home.linkcard.access-code': "卡背面的 20 位卡号(如果提示找不到卡,请尝试使用游戏内置的显示卡号功能,输入游戏读取到的卡号)",
|
||||
'home.linkcard.enter-sn1': "在您的手机",
|
||||
'home.linkcard.enter-sn2': "上下载 NFC Tools 并扫描您的卡。然后输入显示的 SN 号。",
|
||||
'home.linkcard.kdx-notice': "如果你在玩 KanadeDX,请在这里输入虚拟卡号(可以在设置 > 卡号中找到卡号)",
|
||||
'home.linkcard.link': "绑定",
|
||||
'home.linkcard.data-conflict': "卡号冲突",
|
||||
'home.linkcard.name': "名称",
|
||||
'home.linkcard.rating': "Rating",
|
||||
'home.linkcard.last-login': "上次登录",
|
||||
'home.linkcard.linked-own': "此卡已链接到您的帐户",
|
||||
'home.linkcard.linked-another': "此卡已链接到其他用户",
|
||||
'home.linkcard.notfound': "找不到卡",
|
||||
'home.linkcard.unlink': "取消链接",
|
||||
'home.linkcard.unlink-notice': "你确定要取消此卡的链接吗?",
|
||||
'home.linkcard.felica-ac-warning': "该 Access Code 是一张 FeliCa AIC 卡。\n如果你使用实体卡(而非 aime.txt 模拟)刷卡登录游戏,与官方服务器不同,你需要绑定该卡的 FeliCa SN(或与之对应的,游戏界面中查看得到的 00 开头的卡号)而非此号码。\n如果你使用 aime.txt 模拟登录,请忽略本警告继续绑定。",
|
||||
'home.setup.welcome': "欢迎! 如果您有街机框体或者手台,请按照以下说明设置以连接到 AquaDX。",
|
||||
'home.setup.blockquote': "我们假设您已经拥有所需的文件,并且可以启动机台或手台附带的游戏(例如 ROM 和 segatools)。如果没有,请联系您设备的卖家以获取所需的文件,因为出于版权原因,我们不会提供这些文件。",
|
||||
'home.setup.get': "开始",
|
||||
'home.setup.edit': "请打开您的 segatools.ini 文件并修改以下行",
|
||||
'home.setup.test': "在您重新启动游戏后,应该能够连接到 AquaDX。可以验证测试菜单中的网络测试测试连接是否全部良好。",
|
||||
'home.setup.ask': "如果您有任何问题,请加入我们的",
|
||||
'home.setup.support': "以获取支持",
|
||||
'home.setup.keychip-tips': "这是你的狗号,不要与任何人分享",
|
||||
'home.community.discord': 'Discord',
|
||||
'home.community.telegram': 'Telegram (中文)',
|
||||
'home.community.qq': 'QQ (中文)',
|
||||
'home.import.unknown-game': '未知游戏类型 (目前导入只支持舞萌和中二)',
|
||||
'home.import.new-data': '要导入的数据',
|
||||
'home.import.data-conflict': '继续导入将覆盖现有数据',
|
||||
}
|
||||
|
||||
const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.title': '用户设置',
|
||||
'settings.tabs.profile': '个人资料',
|
||||
'settings.tabs.game': '游戏设置',
|
||||
'settings.tabs.chu3': '中二',
|
||||
'settings.tabs.mai2': '舞萌',
|
||||
'settings.tabs.ongeki': '音击',
|
||||
'settings.tabs.wacca': '华卡',
|
||||
'settings.fields.unlockMusic.name': '解锁谱面',
|
||||
'settings.fields.unlockMusic.desc': '在游戏中解锁所有曲目和大师难度谱面。',
|
||||
'settings.fields.unlockChara.name': '解锁角色',
|
||||
'settings.fields.unlockChara.desc': '在游戏中解锁所有角色、语音和伙伴。',
|
||||
'settings.fields.unlockCollectables.name': '解锁收藏品',
|
||||
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图)。',
|
||||
'settings.fields.unlockTickets.name': '解锁游戏券',
|
||||
'settings.fields.unlockTickets.desc': '无限跑图券/解锁券(注:maimai 客户端仍限制一些券不能使用)。',
|
||||
'settings.fields.waccaInfiniteWp.name': '华卡:无限 WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': '华卡:永久会员',
|
||||
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
|
||||
'settings.fields.chusanTeamName.name': '队伍名称',
|
||||
'settings.fields.chusanTeamName.desc': '自定义显示在个人资料顶部的文本。',
|
||||
'settings.fields.chusanInfinitePenguins.name': '我是桐谷遥',
|
||||
'settings.fields.chusanInfinitePenguins.desc': '将角色界限突破的企鹅雕像数量设置为 999。',
|
||||
'settings.fields.chusanMatchingReflector.name': '全国对战 Reflector',
|
||||
'settings.fields.chusanMatchingReflector.desc': '全国对战服务器的 UDP 反射服务器的 URL',
|
||||
'settings.fields.chusanMatchingServer.name': '全国对战服务器',
|
||||
'settings.fields.chusanMatchingServer.desc': '全国对战服务器的 URL',
|
||||
'settings.fields.ongekiInfiniteKaika.name': '无限解花',
|
||||
'settings.fields.ongekiInfiniteKaika.desc': '将解花数量设置为 999。',
|
||||
'settings.fields.rounding.name': '分数舍入',
|
||||
'settings.fields.rounding.desc': '把分数四舍五入到一位小数',
|
||||
'settings.fields.gameUsername.name': '游戏用户名',
|
||||
'settings.fields.gameUsername.desc': '在游戏中显示的用户名',
|
||||
'settings.fields.optOutOfLeaderboard.name': '不参与排行榜',
|
||||
'settings.fields.optOutOfLeaderboard.desc': '登录之后还是可以在排行榜上看到自己',
|
||||
'settings.fields.enableMusicRank.name': '在你的机台上启用「推荐乐曲排行榜」',
|
||||
'settings.fields.enableMusicRank.desc': '如果你自己设计了排行榜的话,可以关闭这个(会影响你自己的机器)。',
|
||||
'settings.mai2.name': '玩家名字',
|
||||
'settings.profile.picture': '头像',
|
||||
'settings.profile.upload-new': '上传',
|
||||
'settings.profile.bad-format': '无效的图片格式,支持的格式有 PNG、JPG、JPEG、WEBP 和 GIF。',
|
||||
'settings.profile.save': '保存',
|
||||
'settings.profile.name': '昵称',
|
||||
'settings.profile.username': '用户名',
|
||||
'settings.profile.password': '密码',
|
||||
'settings.profile.country': '国家',
|
||||
'settings.profile.location': '位置',
|
||||
'settings.profile.bio': '简介',
|
||||
'settings.profile.unset': '未设置',
|
||||
'settings.profile.logout': '登出',
|
||||
'settings.profile.unchanged': '未更改',
|
||||
'settings.export': '导出玩家数据',
|
||||
'settings.batchManualExport': "导出 Batch Manual 格式(用于 Tachi)",
|
||||
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置',
|
||||
'settings.gameNotice': "这些设置仅对舞萌和华卡生效。",
|
||||
// AI
|
||||
'settings.regionNotice': "这些设置仅适用于舞萌、音击和中二。",
|
||||
// AI
|
||||
'settings.regionSelector.title': "地区选择器",
|
||||
// AI
|
||||
'settings.regionSelector.desc': "选择游戏中显示的地区",
|
||||
// AI
|
||||
'settings.regionSelector.select': "选择地区",
|
||||
}
|
||||
|
||||
export const zhUserbox: typeof EN_REF_USERBOX = {
|
||||
'userbox.header.general': '游戏设置',
|
||||
'userbox.header.matching': '全国对战',
|
||||
'userbox.header.matching.symbolChat': '全国对战聊天表情',
|
||||
'userbox.header.userbox': 'UserBox 设置',
|
||||
'userbox.header.preview': 'UserBox 预览',
|
||||
'userbox.nameplateId': '名牌',
|
||||
'userbox.frameId': '边框',
|
||||
'userbox.trophyId': '称号',
|
||||
'userbox.trophyIdSub1': '称号2',
|
||||
'userbox.trophyIdSub2': '称号3',
|
||||
'userbox.mapIconId': '地图图标',
|
||||
'userbox.voiceId': '系统语音',
|
||||
'userbox.avatarWear': '企鹅服饰',
|
||||
'userbox.avatarHead': '企鹅头饰',
|
||||
'userbox.avatarFace': '企鹅面部',
|
||||
'userbox.avatarSkin': '企鹅皮肤',
|
||||
'userbox.avatarItem': '企鹅物品',
|
||||
'userbox.avatarFront': '企鹅前景',
|
||||
'userbox.avatarBack': '企鹅背景',
|
||||
'userbox.preview.url': '图床 URL',
|
||||
'userbox.error.nodata': '未找到中二数据',
|
||||
|
||||
'userbox.matching.select': '选择对战服务器',
|
||||
'userbox.matching.select.sub': '选择你想加入的跨服全国对战服务器',
|
||||
'userbox.matching.option.ui': '房间列表',
|
||||
'userbox.matching.option.guide': '教程',
|
||||
'userbox.matching.option.collab': '合作伙伴',
|
||||
'userbox.matching.custom.name': '自定义',
|
||||
'userbox.matching.custom.sub': '输入其他的匹配 URL',
|
||||
'userbox.matching.symbolChat': '表情选择',
|
||||
'userbox.matching.symbolChat.default': '默认',
|
||||
|
||||
'userbox.new.name': 'AquaBox',
|
||||
'userbox.new.setup': '将中二(Lumi 或更高版本)的游戏文件夹拖放到下方区域,以显示带有名牌和头像的 UserBox。所有文件都在浏览器中处理。',
|
||||
'userbox.new.setup.notice': '选择包含游戏数据的最外层文件夹。',
|
||||
'userbox.new.setup.processing_file': '正在处理文件',
|
||||
'userbox.new.setup.finalizing': '正在保存到内部存储',
|
||||
'userbox.new.drop': '将游戏文件夹拖到此处',
|
||||
'userbox.new.switch.to_url': '切换到 URL 模式',
|
||||
'userbox.new.switch.to_drop': '切换到游戏目录模式',
|
||||
'userbox.new.url_warning': '请输入访问 UserBox 资源的 URL(请参阅文档)',
|
||||
'userbox.new.activate_first': '启用 AquaBox(需要游戏文件)',
|
||||
'userbox.new.activate_update': '更新 AquaBox(需要游戏文件)',
|
||||
'userbox.new.activate': '使用 AquaBox',
|
||||
'userbox.new.activate_desc': '启用后可显示带有名牌和头像的 UserBox',
|
||||
'userbox.new.activate_profile': '在用户页面启用 AquaBox',
|
||||
'userbox.new.activate_profile_desc': '启用后可在个人页面显示带有名牌和头像的 UserBox',
|
||||
'userbox.new.error.invalidFolder': '所选文件夹无效。请确认游戏版本为 Lumi 或更新,并且包含 “A000” 选项包。',
|
||||
'userbox.new.error.invalidUrl': '输入的 URL 无效。'
|
||||
};
|
||||
|
||||
export const zhMaiPhoto: typeof EN_REF_MAI_PHOTO = {
|
||||
'maiphoto.title': 'Mai 纪念照片库',
|
||||
'maiphoto.url_warning': '注意:如果想与朋友分享图片的话,请先保存照片再发出去。不要复制图片 URL,因为 URL 中包含 AquaDX 账号信息。',
|
||||
'maiphoto.none': '还没有图片哦~ 可以在每次游戏结束的时候点击上传来上传照片。',
|
||||
}
|
||||
|
||||
export const zhAquaTrans: typeof EN_REF_AQUATRANS = {
|
||||
'trans.title': '🏳️⚧️ AquaTrans™ 数据迁移工具',
|
||||
'trans.confirm.unbackuped.title': '确认迁移',
|
||||
'trans.confirm.unbackuped.msg': '似乎还没有备份目标服务器的数据,真的要继续吗?(推荐先备份一下,因为迁移的时候会覆盖数据)',
|
||||
'trans.confirm.untested.title': '不太聪明喵',
|
||||
'trans.confirm.untested.msg': '在两个服务器上都测试完连接之后才能进行数据迁移哦!',
|
||||
'trans.confirm.done.title': '完成!',
|
||||
'trans.confirm.done.msg': '数据迁移成功!在 ${dst} 上的数据已被来自 ${src} 的数据覆盖。',
|
||||
'trans.alert.in-progress': '在迁移了在迁移了',
|
||||
'trans.prompt-html': `
|
||||
<p>👋 欢迎使用 AquaTrans™ 服务器游玩数据迁移工具!</p>
|
||||
<p>这个工具可以导出任意服务器的数据,并使用连接凭证(卡号、服务器地址和 Keychip ID)将数据导入任何其他服务器。</p>
|
||||
<p>我将模拟游戏客户端,从源服务器拉取游戏数据并推送到目标服务器。</p>
|
||||
<p>填写下面的表格开始迁移吧!</p>
|
||||
`,
|
||||
'trans.error.empty': '请填写所有字段。',
|
||||
'trans.error.untested': '请先进行连接测试。',
|
||||
'trans.success.import': '数据导入成功!',
|
||||
'trans.source.title': '源服务器',
|
||||
'trans.target.title': '目标服务器',
|
||||
'trans.field.addr': '服务器地址',
|
||||
'trans.field.keychip': '狗号',
|
||||
'trans.field.game': '游戏',
|
||||
'trans.field.version': '版本',
|
||||
'trans.field.card': '卡号',
|
||||
'trans.btn.test': '测试连接',
|
||||
'trans.btn.export': '导出数据',
|
||||
'trans.btn.import': '导入数据',
|
||||
'trans.blacklist': "这个服务器的服主把这个导出工具 ban 了,所以不能从这里导出",
|
||||
}
|
||||
|
||||
|
||||
export const ZH = { ...zhUser, ...zhWelcome, ...zhGeneral,
|
||||
...zhLeaderboard, ...zhHome, ...zhSettings, ...zhUserbox, ...zhMaiPhoto,
|
||||
...zhAquaTrans
|
||||
}
|
||||
248
AquaNet/src/libs/i18n/zh_countries.json
Normal file
@ -0,0 +1,248 @@
|
||||
{
|
||||
"AF": "阿富汗",
|
||||
"AX": "奥兰群岛",
|
||||
"AL": "阿尔巴尼亚",
|
||||
"DZ": "阿尔及利亚",
|
||||
"AS": "美属萨摩亚",
|
||||
"AD": "安道尔",
|
||||
"AO": "安哥拉",
|
||||
"AI": "安圭拉",
|
||||
"AQ": "南极洲",
|
||||
"AG": "安提瓜和巴布达",
|
||||
"AR": "阿根廷",
|
||||
"AM": "亚美尼亚",
|
||||
"AW": "阿鲁巴",
|
||||
"AU": "澳大利亚",
|
||||
"AT": "奥地利",
|
||||
"AZ": "阿塞拜疆",
|
||||
"BS": "巴哈马",
|
||||
"BH": "巴林",
|
||||
"BD": "孟加拉国",
|
||||
"BB": "巴巴多斯",
|
||||
"BY": "白俄罗斯",
|
||||
"BE": "比利时",
|
||||
"BZ": "伯利兹",
|
||||
"BJ": "贝宁",
|
||||
"BM": "百慕大",
|
||||
"BT": "不丹",
|
||||
"BO": "玻利维亚",
|
||||
"BA": "波斯尼亚和黑塞哥维那",
|
||||
"BW": "博茨瓦纳",
|
||||
"BV": "布维岛",
|
||||
"BR": "巴西",
|
||||
"IO": "英属印度洋领地",
|
||||
"BN": "文莱达鲁萨兰国",
|
||||
"BG": "保加利亚",
|
||||
"BF": "布基纳法索",
|
||||
"BI": "布隆迪",
|
||||
"KH": "柬埔寨",
|
||||
"CM": "喀麦隆",
|
||||
"CA": "加拿大",
|
||||
"CV": "佛得角",
|
||||
"KY": "开曼群岛",
|
||||
"CF": "中非共和国",
|
||||
"TD": "乍得",
|
||||
"CL": "智利",
|
||||
"CN": "中国",
|
||||
"CX": "圣诞岛",
|
||||
"CC": "科科斯(基林)群岛",
|
||||
"CO": "哥伦比亚",
|
||||
"KM": "科摩罗",
|
||||
"CG": "刚果",
|
||||
"CD": "刚果民主共和国",
|
||||
"CK": "库克群岛",
|
||||
"CR": "哥斯达黎加",
|
||||
"CI": "科特迪瓦",
|
||||
"HR": "克罗地亚",
|
||||
"CU": "古巴",
|
||||
"CY": "塞浦路斯",
|
||||
"CZ": "捷克共和国",
|
||||
"DK": "丹麦",
|
||||
"DJ": "吉布提",
|
||||
"DM": "多米尼加",
|
||||
"DO": "多米尼加共和国",
|
||||
"EC": "厄瓜多尔",
|
||||
"EG": "埃及",
|
||||
"SV": "萨尔瓦多",
|
||||
"GQ": "赤道几内亚",
|
||||
"ER": "厄立特里亚",
|
||||
"EE": "爱沙尼亚",
|
||||
"ET": "埃塞俄比亚",
|
||||
"FK": "福克兰群岛(马尔维纳斯群岛)",
|
||||
"FO": "法罗群岛",
|
||||
"FJ": "斐济",
|
||||
"FI": "芬兰",
|
||||
"FR": "法国",
|
||||
"GF": "法属圭亚那",
|
||||
"PF": "法属波利尼西亚",
|
||||
"TF": "法属南部领土",
|
||||
"GA": "加蓬",
|
||||
"GM": "冈比亚",
|
||||
"GE": "格鲁吉亚",
|
||||
"DE": "德国",
|
||||
"GH": "加纳",
|
||||
"GI": "直布罗陀",
|
||||
"GR": "希腊",
|
||||
"GL": "格陵兰",
|
||||
"GD": "格林纳达",
|
||||
"GP": "瓜德罗普",
|
||||
"GU": "关岛",
|
||||
"GT": "危地马拉",
|
||||
"GG": "根西岛",
|
||||
"GN": "几内亚",
|
||||
"GW": "几内亚比绍",
|
||||
"GY": "圭亚那",
|
||||
"HT": "海地",
|
||||
"HM": "赫德岛和麦克唐纳群岛",
|
||||
"VA": "梵蒂冈(教廷)",
|
||||
"HN": "洪都拉斯",
|
||||
"HK": "香港",
|
||||
"HU": "匈牙利",
|
||||
"IS": "冰岛",
|
||||
"IN": "印度",
|
||||
"ID": "印度尼西亚",
|
||||
"IR": "伊朗(伊斯兰共和国)",
|
||||
"IQ": "伊拉克",
|
||||
"IE": "爱尔兰",
|
||||
"IM": "马恩岛",
|
||||
"IL": "以色列",
|
||||
"IT": "意大利",
|
||||
"JM": "牙买加",
|
||||
"JP": "日本",
|
||||
"JE": "泽西岛",
|
||||
"JO": "约旦",
|
||||
"KZ": "哈萨克斯坦",
|
||||
"KE": "肯尼亚",
|
||||
"KI": "基里巴斯",
|
||||
"KR": "韩国",
|
||||
"KP": "朝鲜",
|
||||
"KW": "科威特",
|
||||
"KG": "吉尔吉斯斯坦",
|
||||
"LA": "老挝人民民主共和国",
|
||||
"LV": "拉脱维亚",
|
||||
"LB": "黎巴嫩",
|
||||
"LS": "莱索托",
|
||||
"LR": "利比里亚",
|
||||
"LY": "利比亚阿拉伯民众国",
|
||||
"LI": "列支敦士登",
|
||||
"LT": "立陶宛",
|
||||
"LU": "卢森堡",
|
||||
"MO": "澳门",
|
||||
"MK": "马其顿",
|
||||
"MG": "马达加斯加",
|
||||
"MW": "马拉维",
|
||||
"MY": "马来西亚",
|
||||
"MV": "马尔代夫",
|
||||
"ML": "马里",
|
||||
"MT": "马耳他",
|
||||
"MH": "马绍尔群岛",
|
||||
"MQ": "马提尼克",
|
||||
"MR": "毛里塔尼亚",
|
||||
"MU": "毛里求斯",
|
||||
"YT": "马约特",
|
||||
"MX": "墨西哥",
|
||||
"FM": "密克罗尼西亚联邦",
|
||||
"MD": "摩尔多瓦",
|
||||
"MC": "摩纳哥",
|
||||
"MN": "蒙古",
|
||||
"ME": "黑山",
|
||||
"MS": "蒙特塞拉特",
|
||||
"MA": "摩洛哥",
|
||||
"MZ": "莫桑比克",
|
||||
"MM": "缅甸",
|
||||
"NA": "纳米比亚",
|
||||
"NR": "瑙鲁",
|
||||
"NP": "尼泊尔",
|
||||
"NL": "荷兰",
|
||||
"AN": "荷属安的列斯",
|
||||
"NC": "新喀里多尼亚",
|
||||
"NZ": "新西兰",
|
||||
"NI": "尼加拉瓜",
|
||||
"NE": "尼日尔",
|
||||
"NG": "尼日利亚",
|
||||
"NU": "纽埃",
|
||||
"NF": "诺福克岛",
|
||||
"MP": "北马里亚纳群岛",
|
||||
"NO": "挪威",
|
||||
"OM": "阿曼",
|
||||
"PK": "巴基斯坦",
|
||||
"PW": "帕劳",
|
||||
"PS": "被占领的巴勒斯坦领土",
|
||||
"PA": "巴拿马",
|
||||
"PG": "巴布亚新几内亚",
|
||||
"PY": "巴拉圭",
|
||||
"PE": "秘鲁",
|
||||
"PH": "菲律宾",
|
||||
"PN": "皮特凯恩",
|
||||
"PL": "波兰",
|
||||
"PT": "葡萄牙",
|
||||
"PR": "波多黎各",
|
||||
"QA": "卡塔尔",
|
||||
"RE": "留尼汪",
|
||||
"RO": "罗马尼亚",
|
||||
"RU": "俄罗斯联邦",
|
||||
"RW": "卢旺达",
|
||||
"BL": "圣巴泰勒米",
|
||||
"SH": "圣赫勒拿",
|
||||
"KN": "圣基茨和尼维斯",
|
||||
"LC": "圣卢西亚",
|
||||
"MF": "圣马丁",
|
||||
"PM": "圣皮埃尔和密克隆",
|
||||
"VC": "圣文森特和格林纳丁斯",
|
||||
"WS": "萨摩亚",
|
||||
"SM": "圣马力诺",
|
||||
"ST": "圣多美和普林西比",
|
||||
"SA": "沙特阿拉伯",
|
||||
"SN": "塞内加尔",
|
||||
"RS": "塞尔维亚",
|
||||
"SC": "塞舌尔",
|
||||
"SL": "塞拉利昂",
|
||||
"SG": "新加坡",
|
||||
"SK": "斯洛伐克",
|
||||
"SI": "斯洛文尼亚",
|
||||
"SB": "所罗门群岛",
|
||||
"SO": "索马里",
|
||||
"ZA": "南非",
|
||||
"GS": "南乔治亚和南桑威奇群岛",
|
||||
"ES": "西班牙",
|
||||
"LK": "斯里兰卡",
|
||||
"SD": "苏丹",
|
||||
"SR": "苏里南",
|
||||
"SJ": "斯瓦尔巴和扬马延",
|
||||
"SZ": "斯威士兰",
|
||||
"SE": "瑞典",
|
||||
"CH": "瑞士",
|
||||
"SY": "叙利亚",
|
||||
"TW": "台湾",
|
||||
"TJ": "塔吉克斯坦",
|
||||
"TZ": "坦桑尼亚",
|
||||
"TH": "泰国",
|
||||
"TL": "东帝汶",
|
||||
"TG": "多哥",
|
||||
"TK": "托克劳",
|
||||
"TO": "汤加",
|
||||
"TT": "特立尼达和多巴哥",
|
||||
"TN": "突尼斯",
|
||||
"TR": "土耳其",
|
||||
"TM": "土库曼斯坦",
|
||||
"TC": "特克斯和凯科斯群岛",
|
||||
"TV": "图瓦卢",
|
||||
"UG": "乌干达",
|
||||
"UA": "乌克兰",
|
||||
"AE": "阿拉伯联合酋长国",
|
||||
"GB": "英国",
|
||||
"US": "美国",
|
||||
"UM": "美国本土外岛",
|
||||
"UY": "乌拉圭",
|
||||
"UZ": "乌兹别克斯坦",
|
||||
"VU": "瓦努阿图",
|
||||
"VE": "委内瑞拉",
|
||||
"VN": "越南",
|
||||
"VG": "英属维京群岛",
|
||||
"VI": "美属维京群岛",
|
||||
"WF": "瓦利斯和富图纳",
|
||||
"EH": "西撒哈拉",
|
||||
"YE": "也门",
|
||||
"ZM": "赞比亚",
|
||||
"ZW": "津巴布韦"
|
||||
}
|
||||
13
AquaNet/src/libs/maimai.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { AQUA_HOST, DATA_HOST } from './config'
|
||||
|
||||
|
||||
export async function getMaimai(endpoint: string, params: any) {
|
||||
return await fetch(`${AQUA_HOST}/Maimai2Servlet/${endpoint}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(params)
|
||||
}).then(res => res.json())
|
||||
}
|
||||
|
||||
export async function getMaimaiAllMusic(): Promise<{ [key: string]: any }> {
|
||||
return fetch(`${DATA_HOST}/maimai/meta/00/all-music.json`).then(it => it.json())
|
||||
}
|
||||
107
AquaNet/src/libs/maimaiTypes.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import type { MusicMeta } from "./generalTypes";
|
||||
|
||||
export interface Rating {
|
||||
musicId: number
|
||||
level: number
|
||||
achievement: number
|
||||
}
|
||||
|
||||
export interface ParsedRating extends Rating {
|
||||
music: MusicMeta,
|
||||
calc: number,
|
||||
rank: string
|
||||
}
|
||||
|
||||
export interface MaimaiUserSummaryEntry {
|
||||
name: string
|
||||
iconId: number
|
||||
serverRank: number
|
||||
accuracy: number
|
||||
rating: number
|
||||
ratingHighest: number
|
||||
ranks: { name: string, count: number }[]
|
||||
maxCombo: number
|
||||
fullCombo: number
|
||||
allPerfect: number
|
||||
totalDxScore: number
|
||||
plays: number
|
||||
totalPlayTime: number
|
||||
joined: string
|
||||
lastSeen: string
|
||||
lastVersion: string
|
||||
best35: string
|
||||
best15: string
|
||||
recent: MaimaiUserPlaylog[]
|
||||
}
|
||||
|
||||
export interface MaimaiUserPlaylog {
|
||||
id: number;
|
||||
musicId: number;
|
||||
level: number;
|
||||
userPlayDate: string;
|
||||
trackNo: number;
|
||||
vsRank: number;
|
||||
achievement: number;
|
||||
deluxscore: number;
|
||||
scoreRank: number;
|
||||
maxCombo: number;
|
||||
totalCombo: number;
|
||||
maxSync: number;
|
||||
totalSync: number;
|
||||
tapCriticalPerfect: number;
|
||||
tapPerfect: number;
|
||||
tapGreat: number;
|
||||
tapGood: number;
|
||||
tapMiss: number;
|
||||
holdCriticalPerfect: number;
|
||||
holdPerfect: number;
|
||||
holdGreat: number;
|
||||
holdGood: number;
|
||||
holdMiss: number;
|
||||
slideCriticalPerfect: number;
|
||||
slidePerfect: number;
|
||||
slideGreat: number;
|
||||
slideGood: number;
|
||||
slideMiss: number;
|
||||
touchCriticalPerfect: number;
|
||||
touchPerfect: number;
|
||||
touchGreat: number;
|
||||
touchGood: number;
|
||||
touchMiss: number;
|
||||
breakCriticalPerfect: number;
|
||||
breakPerfect: number;
|
||||
breakGreat: number;
|
||||
breakGood: number;
|
||||
breakMiss: number;
|
||||
isTap: boolean;
|
||||
isHold: boolean;
|
||||
isSlide: boolean;
|
||||
isTouch: boolean;
|
||||
isBreak: boolean;
|
||||
isCriticalDisp: boolean;
|
||||
isFastLateDisp: boolean;
|
||||
fastCount: number;
|
||||
lateCount: number;
|
||||
isAchieveNewRecord: boolean;
|
||||
isDeluxscoreNewRecord: boolean;
|
||||
comboStatus: number;
|
||||
syncStatus: number;
|
||||
isClear: boolean;
|
||||
beforeRating: number;
|
||||
afterRating: number;
|
||||
beforeGrade: number;
|
||||
afterGrade: number;
|
||||
afterGradeRank: number;
|
||||
beforeDeluxRating: number;
|
||||
afterDeluxRating: number;
|
||||
isPlayTutorial: boolean;
|
||||
isEventMode: boolean;
|
||||
isFreedomMode: boolean;
|
||||
playMode: number;
|
||||
isNewFree: boolean;
|
||||
trialPlayAchievement: number;
|
||||
extNum1: number;
|
||||
extNum2: number;
|
||||
extNum4: number;
|
||||
extBool1: boolean;
|
||||
}
|
||||
24
AquaNet/src/libs/ongekiTypes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { GenericGamePlaylog } from "./generalTypes";
|
||||
|
||||
export interface OngekiUserSummaryEntry {
|
||||
name: string
|
||||
iconId: number
|
||||
serverRank: number
|
||||
accuracy: number
|
||||
rating: number
|
||||
ratingHighest: number
|
||||
ranks: { name: string, count: number }[]
|
||||
maxCombo: number
|
||||
fullCombo: number
|
||||
allPerfect: number
|
||||
totalDxScore: number
|
||||
plays: number
|
||||
totalPlayTime: number
|
||||
joined: string
|
||||
lastSeen: string
|
||||
lastVersion: string
|
||||
best30: string
|
||||
best15: string
|
||||
recent10: string
|
||||
recent: GenericGamePlaylog[]
|
||||
}
|
||||
153
AquaNet/src/libs/scoring.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { DATA_HOST } from "./config"
|
||||
import type { MusicMeta } from "./generalTypes"
|
||||
|
||||
export type GameName = 'mai2' | 'chu3' | 'ongeki' | 'wacca'
|
||||
|
||||
const multTable = {
|
||||
'mai2': [
|
||||
[ 100.5, 22.4, 'SSSp' ],
|
||||
[ 100.0, 21.6, 'SSS' ],
|
||||
[ 99.5, 21.1, 'SSp' ],
|
||||
[ 99, 20.8, 'SS' ],
|
||||
[ 98, 20.3, 'Sp' ],
|
||||
[ 97, 20, 'S' ],
|
||||
[ 94, 16.8, 'AAA' ],
|
||||
[ 90, 15.2, 'AA' ],
|
||||
[ 80, 13.6, 'A' ],
|
||||
[ 75, 12, 'BBB' ],
|
||||
[ 70, 11.2, 'BB' ],
|
||||
[ 60, 9.6, 'B' ],
|
||||
[ 50, 8, 'C' ],
|
||||
[ 40, 6.4, 'D' ],
|
||||
[ 30, 4.8, 'D' ],
|
||||
[ 20, 3.2, 'D' ],
|
||||
[ 10, 1.6, 'D' ],
|
||||
[ 0, 0, 'D' ]
|
||||
],
|
||||
|
||||
// TODO: Fill in multipliers for Chunithm and Ongeki
|
||||
'chu3': [
|
||||
[ 100.9, 215, 'SSS+' ],
|
||||
[ 100.75, 200, 'SSS' ],
|
||||
[ 100.0, 0, 'SS' ],
|
||||
[ 97.5, 0, 'S' ],
|
||||
[ 95.0, 0, 'AAA' ],
|
||||
[ 92.5, 0, 'AA' ],
|
||||
[ 90.0, 0, 'A' ],
|
||||
[ 80.0, 0, 'BBB' ],
|
||||
[ 70.0, 0, 'BB' ],
|
||||
[ 60.0, 0, 'B' ],
|
||||
[ 50.0, 0, 'C' ],
|
||||
[ 0.0, 0, 'D' ]
|
||||
],
|
||||
|
||||
'ongeki': [
|
||||
[ 100.75, 0, 'SSS+' ],
|
||||
[ 100.0, 0, 'SSS' ],
|
||||
[ 99.0, 0, 'SS' ],
|
||||
[ 97.0, 0, 'S' ],
|
||||
[ 94.0, 0, 'AAA' ],
|
||||
[ 90.0, 0, 'AA' ],
|
||||
[ 85.0, 0, 'A' ],
|
||||
[ 80.0, 0, 'BBB' ],
|
||||
[ 75.0, 0, 'BB' ],
|
||||
[ 70.0, 0, 'B' ],
|
||||
[ 50.0, 0, 'C' ],
|
||||
[ 0.0, 0, 'D' ]
|
||||
],
|
||||
|
||||
'wacca': [
|
||||
[ 100.0, 0, 'AP' ],
|
||||
[ 98.0, 0, 'SSS' ],
|
||||
[ 95.0, 0, 'SS' ],
|
||||
[ 90.0, 0, 'S' ],
|
||||
[ 85.0, 0, 'AAA' ],
|
||||
[ 80.0, 0, 'AA' ],
|
||||
[ 70.0, 0, 'A' ],
|
||||
[ 60.0, 0, 'B' ],
|
||||
[ 1.0, 0, 'C' ],
|
||||
[ 0.0, 0, 'D' ]
|
||||
],
|
||||
}
|
||||
|
||||
export function getMult(achievement: number, game: GameName) {
|
||||
achievement /= 10000
|
||||
const mt = multTable[game]
|
||||
for (let i = 0; i < mt.length; i++) {
|
||||
if (achievement >= (mt[i][0] as number)) return mt[i]
|
||||
}
|
||||
return [ 0, 0, 0 ]
|
||||
}
|
||||
|
||||
export function roundFloor(achievement: number, game: GameName, digits = 2) {
|
||||
// Round, but if the rounded number reaches the next rank, use floor instead
|
||||
const mult = getMult(achievement, game);
|
||||
achievement /= 10000
|
||||
const rounded = achievement.toFixed(digits);
|
||||
if (getMult(+rounded * 10000, game)[2] === mult[2] && rounded !== '101.0') return rounded;
|
||||
return (+rounded - Math.pow(10, -digits)).toFixed(digits);
|
||||
}
|
||||
|
||||
export function chusanRating(lv: number, score: number) {
|
||||
lv = lv * 100
|
||||
if (score >= 1009000) return lv + 215; // SSS+
|
||||
if (score >= 1007500) return lv + 200 + (score - 1007500) / 100; // SSS
|
||||
if (score >= 1005000) return lv + 150 + (score - 1005000) / 50; // SS+
|
||||
if (score >= 1000000) return lv + 100 + (score - 1000000) / 100; // SS
|
||||
if (score >= 975000) return lv + (score - 975000) / 250; // S+, S
|
||||
if (score >= 925000) return lv - 300 + (score - 925000) * 3 / 500; // AA
|
||||
if (score >= 900000) return lv - 500 + (score - 900000) * 4 / 500; // A
|
||||
if (score >= 800000) return ((lv - 500) / 2 + (score - 800000) * ((lv - 500) / 2) / (100000)); // BBB
|
||||
return 0; // C
|
||||
}
|
||||
|
||||
export interface ParsedComposition {
|
||||
name?: string
|
||||
musicId: number
|
||||
diffId: number // ID of the difficulty
|
||||
score: number
|
||||
cutoff: number
|
||||
mult: number
|
||||
rank: string // e.g. 'SSS+'
|
||||
difficulty?: number // Actual difficulty of the map
|
||||
img: string
|
||||
ratingChange?: string // Rating change after playing this map
|
||||
}
|
||||
|
||||
|
||||
export function parseComposition(item: string, allMusics: Record<string, MusicMeta>, game: GameName): ParsedComposition {
|
||||
// Chuni & ongeki: musicId, difficultId, score
|
||||
// Mai: musicId, level (difficultyId), romVersion, achievement (score)
|
||||
const mapData = item.split(':').map(Number)
|
||||
if (game === 'mai2') mapData.splice(2, 1)
|
||||
const [ musicId, diffId, score ] = mapData
|
||||
const meta = allMusics[musicId]
|
||||
|
||||
// Get score multiplier
|
||||
const tup = getMult(score, game)
|
||||
const [ cutoff, mult ] = [ +tup[0], +tup[1] ]
|
||||
const rank = "" + tup[2]
|
||||
|
||||
let diff = meta?.notes?.[diffId === 10 ? 0 : diffId]?.lv
|
||||
|
||||
function calcDxChange() {
|
||||
if (!diff) return
|
||||
if (game === 'mai2')
|
||||
return Math.floor(diff * mult * (Math.min(100.5, score / 10000) / 100)).toFixed(0)
|
||||
if (game === 'chu3' || game === 'ongeki')
|
||||
return (Math.floor(chusanRating(diff, score)) / 100).toFixed(2)
|
||||
}
|
||||
|
||||
return {
|
||||
name: meta?.name,
|
||||
musicId,
|
||||
diffId,
|
||||
score,
|
||||
cutoff,
|
||||
mult,
|
||||
rank,
|
||||
difficulty: diff,
|
||||
img: `${DATA_HOST}/d/${game}/music/00${mapData[0].toString().padStart(6, '0').substring(2)}.png`,
|
||||
ratingChange: calcDxChange()
|
||||
}
|
||||
}
|
||||