Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a0254ec19 | ||
|
|
c3661d3c94 | ||
|
|
bcbc770ca7 | ||
|
|
55b47a3c02 | ||
|
|
97743f55ef | ||
|
|
11c5617abc | ||
|
|
cf89e59af8 | ||
|
|
587557177e | ||
|
|
6285bd3fd4 | ||
|
|
7ba95f0b90 | ||
|
|
13aa48982f | ||
|
|
0a5c97d985 | ||
|
|
c1b6c3e414 | ||
|
|
5e6aaace8c | ||
|
|
3be81520ce | ||
|
|
30139cae8d | ||
|
|
96570a97ff | ||
|
|
7eab79e48d | ||
|
|
05e613d667 | ||
|
|
d34aab37b2 | ||
|
|
4aba8d8642 | ||
|
|
0d70471bd6 | ||
|
|
7ea5add407 | ||
|
|
41edddcaec | ||
|
|
1d36998967 | ||
|
|
3178c4c957 | ||
|
|
48eb8a9c07 | ||
|
|
fcee9fb278 | ||
|
|
853abc8867 | ||
|
|
c099a8954e | ||
|
|
96a894aa70 | ||
|
|
0b3d52d31d | ||
|
|
27685087be | ||
|
|
fec3480963 | ||
|
|
536734746b | ||
|
|
b352576358 | ||
|
|
f2ea015ef3 | ||
|
|
253c7a73d6 | ||
|
|
e62115e6ac | ||
|
|
08bf69c723 | ||
|
|
3e2c198204 | ||
|
|
c3f75e6428 | ||
|
|
4a6003ded3 | ||
|
|
b114adf940 | ||
|
|
7c31dfd4ab | ||
|
|
faabd45b7a | ||
|
|
d7254913cd | ||
|
|
237ba1464a | ||
|
|
d5d50d687c | ||
|
|
2c28758ae0 | ||
|
|
f025465f40 | ||
|
|
4b5eeadc2b | ||
|
|
898939485c | ||
|
|
593783866a | ||
|
|
9b3871706f | ||
|
|
5be6d8c80b | ||
|
|
f750cc6596 | ||
|
|
f67bbb822d | ||
|
|
819c058517 | ||
|
|
b2feb9a130 | ||
|
|
6a10570fa6 | ||
|
|
6243ee7205 | ||
|
|
72c1f48bdb | ||
|
|
06eec89dfe | ||
|
|
367a26c109 | ||
|
|
2552568ac1 | ||
|
|
512a791aa4 | ||
|
|
9bdc84418a | ||
|
|
243a9b2ddc | ||
|
|
f313f090f8 | ||
|
|
2e55247de0 | ||
|
|
4895456bb0 | ||
|
|
aec0fdac8b | ||
|
|
eb600f1715 | ||
|
|
bd6a641336 | ||
|
|
aeebf069c2 | ||
|
|
a77111b2ad | ||
|
|
031f9e4663 | ||
|
|
1181707b43 | ||
|
|
927fa56862 | ||
|
|
d17073297c | ||
|
|
34a3de1209 | ||
|
|
3a42435dc5 | ||
|
|
7865f7cfa7 | ||
|
|
e711921725 | ||
|
|
d0110d7f47 | ||
|
|
26748719a0 | ||
|
|
b20fe6fa62 | ||
|
|
9aec50d901 | ||
|
|
c31c1a0524 | ||
|
|
c989c273c4 | ||
|
|
c4c550b239 | ||
|
|
22334de8a7 | ||
|
|
50cb970828 | ||
|
|
80c088626e | ||
|
|
2d5fb11dc0 | ||
|
|
708532d18b | ||
|
|
dad66401cc | ||
|
|
1bc89c46f1 | ||
|
|
1d3ddfc06c | ||
|
|
34cfa279dc | ||
|
|
2e21926189 | ||
|
|
7a946708c3 | ||
|
|
fcf7c46cad | ||
|
|
28063912ce | ||
|
|
beacca12c3 | ||
|
|
fe0a2e738d | ||
|
|
bc880bca8d | ||
|
|
f546e40318 | ||
|
|
08c861b32e | ||
|
|
00d7d7345f | ||
|
|
f6afb824a4 | ||
|
|
b487a756b2 | ||
|
|
fcdfeb25ea | ||
|
|
e1c9bc7285 | ||
|
|
b89f123d1b | ||
|
|
707ce3715b | ||
|
|
1e132c6784 | ||
|
|
ebb2e2e4a4 | ||
|
|
e755bc9380 | ||
|
|
c2ac01d940 | ||
|
|
1b5601db45 | ||
|
|
ff1ed2d9ec | ||
|
|
8a78bb2c6f | ||
|
|
ca5d33a1cc | ||
|
|
6ddb72e28e | ||
|
|
25dbd5c874 | ||
|
|
50a9a2bdd0 | ||
|
|
6c4c9337e7 | ||
|
|
6776353556 | ||
|
|
3b19257ab1 | ||
|
|
a0cd7456ee | ||
|
|
aeafa6a396 | ||
|
|
448426a96d | ||
|
|
dfa6176689 | ||
|
|
d996fba291 | ||
|
|
0cb2a95ff3 | ||
|
|
be5220fd51 | ||
|
|
f23c0d6fe1 | ||
|
|
5aca650602 | ||
|
|
7c72348016 | ||
|
|
5eee6505f9 | ||
|
|
43b7ea65a5 | ||
|
|
85149dcd03 | ||
|
|
a767c8949c | ||
|
|
bbeb476a62 | ||
|
|
c444350cef | ||
|
|
13a318d519 | ||
|
|
e744d96c96 | ||
|
|
491044d37a | ||
|
|
9d30cf1e7d | ||
|
|
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 | ||
|
|
41d4de6150 | ||
|
|
bcf3333dd2 | ||
|
|
47fda64c90 | ||
|
|
4fce05b7d1 | ||
|
|
363bcc6060 | ||
|
|
bc734a5d25 | ||
|
|
3f95678098 | ||
|
|
e52c971aed | ||
|
|
1f82067752 | ||
|
|
0ac1a4c088 | ||
|
|
0fda25b482 | ||
|
|
dd70265cb6 | ||
|
|
62e7d48f3c | ||
|
|
4905106953 | ||
|
|
48edab452d | ||
|
|
7a7076b174 | ||
|
|
c8e1c5fbb7 | ||
|
|
50ceaf6097 | ||
|
|
722d415e75 | ||
|
|
0d4221203b | ||
|
|
4a64895e81 | ||
|
|
e271cb4555 | ||
|
|
0bf54e666b | ||
|
|
0913ef2060 | ||
|
|
7cc9fb11b6 | ||
|
|
9c51b1e0ee | ||
|
|
ba1f458907 | ||
|
|
e7848cb965 | ||
|
|
564ada10f5 | ||
|
|
48721ef7a9 | ||
|
|
bae06e2187 | ||
|
|
8b8e6cb422 | ||
|
|
2ecc990aae | ||
|
|
b47a841207 |
@@ -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
|
||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
*
|
||||||
|
!gradlew
|
||||||
|
!gradle/
|
||||||
|
!gradle/**
|
||||||
|
!build.gradle.kts
|
||||||
|
!settings.gradle.kts
|
||||||
|
!src/
|
||||||
|
!src/**
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Gradle Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '25'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Build with Gradle
|
||||||
|
run: |
|
||||||
|
mkdir data
|
||||||
|
bash ./src/main/resources/meta/update.sh
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew build
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Build script credit to https://github.com/OpenIntelWireless/itlwm/blob/master/.github/workflows/main.yml
|
||||||
|
name: Nightly Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths: ['src/**']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
permissions: write-all
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: '10'
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '25'
|
||||||
|
distribution: 'temurin'
|
||||||
|
server-id: github
|
||||||
|
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2
|
||||||
|
|
||||||
|
- name: Build Artifact
|
||||||
|
run: bash ./tools/build.sh
|
||||||
|
|
||||||
|
- name: Delete previous nightly release
|
||||||
|
run: |
|
||||||
|
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: "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 nightly --draft=false
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
db/
|
||||||
web/
|
web/
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
@@ -76,3 +77,13 @@ gradle-app.setting
|
|||||||
### Gradle Patch ###
|
### Gradle Patch ###
|
||||||
# Java heap dump
|
# Java heap dump
|
||||||
*.hprof
|
*.hprof
|
||||||
|
.jpb
|
||||||
|
src/main/resources/meta/*/*.json
|
||||||
|
*.log.*.gz
|
||||||
|
*.salive
|
||||||
|
test-diff
|
||||||
|
htmlReport
|
||||||
|
docs/logs
|
||||||
|
|
||||||
|
# Sensitive keys
|
||||||
|
game_encryption.json
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
image: gradle:alpine
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- GRADLE_USER_HOME="$(pwd)/.gradle"
|
|
||||||
- export GRADLE_USER_HOME
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
script: gradle --build-cache assemble
|
|
||||||
cache:
|
|
||||||
key: "$CI_COMMIT_REF_NAME"
|
|
||||||
policy: push
|
|
||||||
paths:
|
|
||||||
- build
|
|
||||||
- .gradle
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
script: gradle check
|
|
||||||
cache:
|
|
||||||
key: "$CI_COMMIT_REF_NAME"
|
|
||||||
policy: pull
|
|
||||||
paths:
|
|
||||||
- build
|
|
||||||
- .gradle
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# This should reference your AquaDX instance's :80 port.
|
||||||
|
# Sometimes issues with CORS may appear, the easiest fix is to use a reverse proxy like Caddy to put AquaDX on the /aqua path,
|
||||||
|
# and set this to match the /aqua path (see example below)
|
||||||
|
VITE_AQUA_HOST=https://aquadx.net/aqua
|
||||||
|
|
||||||
|
# Link to converted game data (ex: all_music.json, all_items.json, etc.)
|
||||||
|
# Profile pictures are also served through this path, you may want to disable profile pictures if using the primary instance for data fetching
|
||||||
|
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
|
||||||
|
VITE_GITHUB_REPOSITORY=https://github.com/MewoLab/AquaDX
|
||||||
|
|
||||||
|
# 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
|
||||||
|
VITE_USERBOX_DEFAULT_URL=
|
||||||
|
|
||||||
|
# the following is used at some case where custom access code QR code URL has been done.
|
||||||
|
# example:
|
||||||
|
# https://aquadx.net/cards/#accesskey=....
|
||||||
|
VITE_ACCESSCODE_QKEY=accesskey
|
||||||
|
|
||||||
|
VITE_DEFAULT_GAME=mai2
|
||||||
@@ -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',
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
.env
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
You will need to have Node.js (or equivalent) and `pnpm` installed, Start by installing dependencies by running:
|
||||||
|
```
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you would need to start your testing AquaDX server and configure the `VITE_AQUA_HOST` in `.env` (copy `.env.example` file to `.env`) to use your URL.
|
||||||
|
Please leave `VITE_DATA_HOST` unchanged if you're not sure what it is.
|
||||||
|
|
||||||
|
Finally, run:
|
||||||
|
```
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
### Running locally (with bun)
|
||||||
|
|
||||||
|
Alternatively, If you prefer [Bun](https://bun.sh), You can do the same as above.
|
||||||
|
Start by configuring the `VITE_AQUA_HOST` in `.env` (copy `.env.example` file to `.env`) to use your URL.
|
||||||
|
|
||||||
|
and finally:
|
||||||
|
```
|
||||||
|
bun install
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { existsSync } from "fs"
|
||||||
|
|
||||||
|
if (!existsSync(".env")) {
|
||||||
|
console.error(" I did not see .env being here. You sure it was there?")
|
||||||
|
console.error(" Copy .env.example to .env, edit .env, and try again.\n")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>AquaNet</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="AquaNet (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="AquaNet (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="AquaNet (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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "aqua-net",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node buildcheck.js && vite",
|
||||||
|
"build": "node buildcheck.js && 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"
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 6.9 MiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 7.0 MiB |
|
After Width: | Height: | Size: 778 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -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>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.6 MiB |
@@ -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"
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 894 KiB |
|
After Width: | Height: | Size: 528 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,160 @@
|
|||||||
|
<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/announcement";
|
||||||
|
import { DEFAULT_GAME } from "./libs/config"
|
||||||
|
import { t } from "./libs/i18n";
|
||||||
|
import Transfer from "./pages/Transfer/Transfer.svelte";
|
||||||
|
import { link } from "d3";
|
||||||
|
import Communities from "./pages/Home/Communities.svelte";
|
||||||
|
import LinkCard from "./pages/Home/LinkCard.svelte";
|
||||||
|
import SetupInstructions from "./pages/Home/SetupInstructions.svelte";
|
||||||
|
import PageNotFound from "./pages/PageNotFound.svelte";
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
let recentGame: string = DEFAULT_GAME;
|
||||||
|
|
||||||
|
if (USER.isLoggedIn())
|
||||||
|
{
|
||||||
|
USER.me().then(m => {
|
||||||
|
me = m
|
||||||
|
CARD.userGames(me.username).then(game => {
|
||||||
|
playedMai = !!game.mai2
|
||||||
|
recentGame = Object.keys(game)
|
||||||
|
.filter(k => !!game[k])
|
||||||
|
.sort((a, b) => {
|
||||||
|
return (new Date(game[b].lastLogin)) - (new Date(game[a].lastLogin))
|
||||||
|
})[0] ?? "mai2"
|
||||||
|
})
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
}
|
||||||
|
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> -->
|
||||||
|
<!-- kill me -->
|
||||||
|
<a href={`/ranking/${recentGame}`}>{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="/support" component={Communities} />
|
||||||
|
<Route path="/cards" component={LinkCard} />
|
||||||
|
<Route path="/setup" component={SetupInstructions} />
|
||||||
|
<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="/settings/:page" component={Settings} />
|
||||||
|
<Route path="/pictures" component={MaiPhoto} />
|
||||||
|
<Route path="/transfer" component={Transfer} />
|
||||||
|
<Route component={PageNotFound} />
|
||||||
|
</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>
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
@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
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: "⚠"
|
||||||
|
margin-right: 1em
|
||||||
|
|
||||||
|
&.success
|
||||||
|
$c1: rgba(156, 255, 149, 0.05)
|
||||||
|
$c2: rgba(152, 255, 174, 0.02)
|
||||||
|
background: repeating-linear-gradient(45deg, $c1, $c1 10px, $c2 10px, $c2 20px)
|
||||||
|
border-left: solid #97fa8c 3px
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: "✓"
|
||||||
|
margin-right: 1em
|
||||||
|
|
||||||
|
&.info
|
||||||
|
$c1: rgba(149, 183, 255, 0.05)
|
||||||
|
$c2: rgba(152, 186, 255, 0.02)
|
||||||
|
background: repeating-linear-gradient(45deg, $c1, $c1 10px, $c2 10px, $c2 20px)
|
||||||
|
border-left: solid #6e8bff 3px
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: "♢"
|
||||||
|
margin-right: 1em
|
||||||
|
|
||||||
|
#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)
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<!-- Svelte 4.2.11 -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from "@iconify/svelte";
|
||||||
|
|
||||||
|
export let color: string = '179, 198, 255'
|
||||||
|
export let icon: string
|
||||||
|
export let href: string | undefined = undefined
|
||||||
|
export let isSmall: boolean = false
|
||||||
|
|
||||||
|
// 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>
|
||||||
|
|
||||||
|
<a class="action-card" class:small={isSmall} style="--card-color: {color}" on:click {href} 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>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@use '../vars'
|
||||||
|
|
||||||
|
.action-card
|
||||||
|
overflow: hidden
|
||||||
|
display: block
|
||||||
|
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))
|
||||||
|
color: rgba(255, 255, 255, 0.78)
|
||||||
|
|
||||||
|
&: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), &.small
|
||||||
|
font-size: 1.2rem
|
||||||
|
display: block
|
||||||
|
margin-bottom: 0.5rem
|
||||||
|
color: color-mix(in oklab, rgb(var(--card-color)) 25%, rgba(255, 255, 255, 0.75))
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<!-- Svelte 4.2.11 -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Icon from "@iconify/svelte";
|
||||||
|
|
||||||
|
export let color: string = '179, 198, 255'
|
||||||
|
export let icon: string
|
||||||
|
export let href: string
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a class="action-card" style="--card-color: {color}" {href} role="button" tabindex="0" on:keydown>
|
||||||
|
<slot/>
|
||||||
|
|
||||||
|
<div class="icon">
|
||||||
|
<Icon icon={icon} />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@use '../vars'
|
||||||
|
|
||||||
|
.action-card
|
||||||
|
overflow: hidden
|
||||||
|
display: block
|
||||||
|
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))
|
||||||
|
color: rgba(255, 255, 255, 0.78)
|
||||||
|
|
||||||
|
&: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
|
||||||
|
color: color-mix(in oklab, rgb(var(--card-color)) 25%, rgba(255, 255, 255, 0.75))
|
||||||
|
|
||||||
|
.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>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from "../libs/i18n";
|
||||||
|
|
||||||
|
const tabs: Record<string, string> = {
|
||||||
|
[t('home.nav.portal')]: `/home`,
|
||||||
|
[t('home.nav.link-card')]: `/cards`,
|
||||||
|
[t('home.nav.game-setup')]: `/setup`,
|
||||||
|
[t('home.nav.support')]: `/support`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="tabs">
|
||||||
|
{#each Object.entries(tabs) as t, i}
|
||||||
|
<a class="clickable"
|
||||||
|
href={t[1]}
|
||||||
|
class:active={
|
||||||
|
new URL(location.href).pathname === t[1]
|
||||||
|
}>
|
||||||
|
{t[0]}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@use "../vars"
|
||||||
|
.tabs
|
||||||
|
display: flex
|
||||||
|
gap: 1rem
|
||||||
|
|
||||||
|
a
|
||||||
|
&.active
|
||||||
|
color: vars.$c-main
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
export let page: number
|
||||||
|
export let totalPages: number
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let editing = false
|
||||||
|
let inputPage: number
|
||||||
|
|
||||||
|
function focus(node: HTMLInputElement) {
|
||||||
|
node.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePage(newPage: number) {
|
||||||
|
const p = Math.floor(Number(newPage))
|
||||||
|
if (!isNaN(p) && p > 0 && p <= totalPages) dispatch('updatePage', p)
|
||||||
|
}
|
||||||
|
|
||||||
|
function startEditing() {
|
||||||
|
inputPage = page
|
||||||
|
editing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishEditing() {
|
||||||
|
editing = false
|
||||||
|
if (inputPage !== page) updatePage(inputPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Enter') finishEditing()
|
||||||
|
else if (event.key === 'Escape') editing = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<button on:click={() => updatePage(page - 1)} disabled={page <= 1}>Previous</button>
|
||||||
|
|
||||||
|
{#if editing}
|
||||||
|
<input type="number" bind:value={inputPage} on:blur={finishEditing} on:keydown={handleKeydown} min="1" max={totalPages} use:focus/>
|
||||||
|
{:else}
|
||||||
|
<span on:click={startEditing} role="button" tabindex="0" on:keydown={(e) => e.key === 'Enter' && startEditing()}>
|
||||||
|
Page {page} of {totalPages}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button on:click={() => updatePage(page + 1)} disabled={page >= totalPages}>Next</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.pagination
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
margin: 1rem 0
|
||||||
|
gap: 1rem
|
||||||
|
|
||||||
|
input
|
||||||
|
width: 100px
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
span[role="button"]
|
||||||
|
cursor: pointer
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<!-- 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)
|
||||||
|
|
||||||
|
let mustResize = split && split.length > 12
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if split && split.length > 0 && comp}
|
||||||
|
<div class="rating-composition-container" class:must-resize={mustResize}>
|
||||||
|
{#if mustResize}
|
||||||
|
<input type="checkbox">
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<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
|
||||||
|
|
||||||
|
overflow: hidden
|
||||||
|
transition: max-height 250ms
|
||||||
|
|
||||||
|
.rating-composition-container
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
input
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
position: absolute
|
||||||
|
z-index: 10
|
||||||
|
|
||||||
|
border: none
|
||||||
|
border-radius: 0
|
||||||
|
opacity: 0
|
||||||
|
|
||||||
|
transition: opacity 250ms
|
||||||
|
|
||||||
|
.rating-composition-container.must-resize
|
||||||
|
input
|
||||||
|
& ~ .rating-composition
|
||||||
|
max-height: 250px
|
||||||
|
|
||||||
|
& ~ h2::after
|
||||||
|
content: "▾"
|
||||||
|
margin-left: 0.5em
|
||||||
|
&:checked ~ h2::after
|
||||||
|
content: "▴"
|
||||||
|
|
||||||
|
&:checked
|
||||||
|
opacity: 0
|
||||||
|
&:checked ~ .rating-composition
|
||||||
|
max-height: 3000px
|
||||||
|
|
||||||
|
opacity: 1
|
||||||
|
background: linear-gradient(#0000 250px, rgb(28.35, 28.35, 28.35) 100%) /* for some reason the color.adjust doesn't work here so whatever */
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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: Event) {
|
||||||
|
console.log('over')
|
||||||
|
isHovered = true
|
||||||
|
updatePosition(event as MouseEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mouseMove(event: Event) {
|
||||||
|
console.log('move')
|
||||||
|
updatePosition(event as MouseEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
<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, GAME, SETTING, USER } from "../../libs/sdk.js";
|
||||||
|
import StatusOverlays from "../StatusOverlays.svelte";
|
||||||
|
import GameSettingFields from "./GameSettingFields.svelte";
|
||||||
|
import { EN_REF } from "../../libs/i18n/en_ref.js";
|
||||||
|
|
||||||
|
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 settings: Record<string, GameOption> = {};
|
||||||
|
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
settings = Object.fromEntries(s.map(v => [v.key, v]))
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLvDifficulty() {
|
||||||
|
if (submitting) return false
|
||||||
|
submitting = `chusanLvDifficulty`
|
||||||
|
await SETTING.set("chusanLvDifficulty", settings["chusanLvDifficulty"].value).catch(e => error = e.message).finally(() => submitting = null);
|
||||||
|
changed = changed.filter(v => v != `chusanLvDifficulty`)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
let linkedVerseAvailable = false;
|
||||||
|
USER.me().then(async me => {
|
||||||
|
let summary = await GAME.userSummary(me.username, "chu3")
|
||||||
|
let version = summary.lastVersion.split(".");
|
||||||
|
if (version[0] == "2" && parseInt(version[1]) >= 40)
|
||||||
|
linkedVerseAvailable = true;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<StatusOverlays {error} {loading}/>
|
||||||
|
|
||||||
|
<div class="matching">
|
||||||
|
<h2>{t("userbox.header.matching")}{linkedVerseAvailable ? ` & ${t("userbox.header.linkedVerse")}` : ""}</h2>
|
||||||
|
<blockquote class="info">
|
||||||
|
{t("settings.cabNotice")}
|
||||||
|
{#if linkedVerseAvailable}
|
||||||
|
{t("userbox.lv.diffnotice")}
|
||||||
|
{/if}
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
{#if linkedVerseAvailable}
|
||||||
|
<GameSettingFields game="chu3-linked-verse" />
|
||||||
|
{#if settings["chusanLvDifficulty"]}
|
||||||
|
<a target="_blank" href="https://silentblue.remywiki.com/CHUNITHM:Linked_VERSE">{t(`userbox.lv.help`)}</a>
|
||||||
|
<div class="field">
|
||||||
|
<label for={`chusanLvDifficulty`}>{ts(`userbox.lv.difficulty`)}</label>
|
||||||
|
<div>
|
||||||
|
<select bind:value={settings["chusanLvDifficulty"].value} id={`chusanLvDifficulty`} on:change={() => {changed = [...changed, `chusanLvDifficulty`];}}>
|
||||||
|
{#each {length: 6}, i}
|
||||||
|
<option value={i}>{t(`userbox.lv.difficulty.${i}` as keyof typeof EN_REF)}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{#if changed.includes(`chusanLvDifficulty`)}
|
||||||
|
<button transition:slide={{axis: "x"}} disabled={!!submitting} on:click={updateLvDifficulty}>
|
||||||
|
{t("settings.profile.save")}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,684 @@
|
|||||||
|
<!-- Svelte 4.2.11 -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
type AquaNetUser,
|
||||||
|
type GameUserOption,
|
||||||
|
type UserBox,
|
||||||
|
type UserItem,
|
||||||
|
} from "../../libs/generalTypes";
|
||||||
|
import { DATA, USER, USERBOX, GAME, SETTING } 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";
|
||||||
|
import UserOptionSlider from "./UserOptionSlider.svelte";
|
||||||
|
|
||||||
|
let user: AquaNetUser
|
||||||
|
let [loading, error, submitting, preview] = [true, "", "", ""]
|
||||||
|
let changed: string[] = [];
|
||||||
|
|
||||||
|
let userOptions: GameUserOption = {};
|
||||||
|
let userIsUsingPreset = false;
|
||||||
|
|
||||||
|
// 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, stage: 13 }
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
userOptions = await SETTING.optionGet('chu3').catch(_ => {
|
||||||
|
loading = false
|
||||||
|
error = t("userbox.error.nodata")
|
||||||
|
}) as GameUserOption
|
||||||
|
if (userOptions["optionSet"] != 3)
|
||||||
|
userIsUsingPreset = true;
|
||||||
|
|
||||||
|
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: any) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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", "stage"]
|
||||||
|
|
||||||
|
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>
|
||||||
|
<div class="general-options">
|
||||||
|
<GameSettingFields game="chu3"/>
|
||||||
|
|
||||||
|
<InputField bind:field={userNameField}
|
||||||
|
callback={() => USERBOX.setUserBox({ field: "userName", value: userNameField.value })}/>
|
||||||
|
|
||||||
|
<!-- User Options -->
|
||||||
|
{#if userIsUsingPreset}
|
||||||
|
<blockquote class="info">
|
||||||
|
{t('settings.options.all.preset-warning')}
|
||||||
|
</blockquote>
|
||||||
|
{/if}
|
||||||
|
<div class="fields-ranges">
|
||||||
|
<UserOptionSlider
|
||||||
|
game="chu3" type="headphone"
|
||||||
|
minValue={0} maxValue={60}
|
||||||
|
defaultValue={userOptions["headphone"]}
|
||||||
|
getTextFunction={(value: number) => `${((value / 60) * 100).toFixed(0)}%`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="chu3" type="trackSkip"
|
||||||
|
minValue={0} maxValue={7}
|
||||||
|
defaultValue={userOptions["trackSkip"]}
|
||||||
|
getTextFunction={(value: number) => [t('settings.options.all.none'), `S`, `S+`, `SS`, `SS+`, `SSS`, `SSS+`, t('settings.options.all.personal-best')][value]}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="chu3" type="speed"
|
||||||
|
minValue={0} maxValue={61}
|
||||||
|
defaultValue={userOptions["speed"]}
|
||||||
|
getTextFunction={(value: number) => value >= 61 ? "SONIC" : value < 55 ? `${(value / 4) + 1}` : `${value - 40}`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="chu3" type="speed_120"
|
||||||
|
minValue={0} maxValue={61}
|
||||||
|
defaultValue={userOptions["speed_120"]}
|
||||||
|
getTextFunction={(value: number) => value >= 61 ? "SONIC" : value < 55 ? `${(value / 4) + 1}` : `${value - 40}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2>{t("userbox.header.userbox")}</h2>
|
||||||
|
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
|
||||||
|
<div class="fields">
|
||||||
|
{#each userItems as { iKey, ubKey, items }, i}
|
||||||
|
{#if items.length > 0}
|
||||||
|
<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>
|
||||||
|
{/if}
|
||||||
|
{/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}
|
||||||
|
{#if items.length > 0}
|
||||||
|
<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>
|
||||||
|
{/if}
|
||||||
|
{/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) }}} 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%
|
||||||
|
|
||||||
|
.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
|
||||||
|
|
||||||
|
|
||||||
|
.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
|
||||||
|
|
||||||
|
|
||||||
|
.fields-ranges
|
||||||
|
display: flex
|
||||||
|
flex-wrap: wrap
|
||||||
|
margin: 0.5rem 0
|
||||||
|
gap: 0 1rem
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
:global(.field)
|
||||||
|
flex: calc(50% - 1rem)
|
||||||
|
width: 50%
|
||||||
|
|
||||||
|
/* 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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||||
|
import { t, ts } from "../../libs/i18n";
|
||||||
|
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||||
|
import RegionSelector from "./RegionSelector.svelte";
|
||||||
|
import { USER } from "../../libs/sdk";
|
||||||
|
import type { AquaNetUser } from "../../libs/generalTypes";
|
||||||
|
import StatusOverlays from "../StatusOverlays.svelte";
|
||||||
|
|
||||||
|
const rounding = useLocalStorage("rounding", true);
|
||||||
|
|
||||||
|
let me: AquaNetUser;
|
||||||
|
let submitting = "";
|
||||||
|
let error = "";
|
||||||
|
let loading = true;
|
||||||
|
|
||||||
|
USER.me().then((m) => {
|
||||||
|
me = m;
|
||||||
|
loading = false;
|
||||||
|
}).catch(e => error = e.message)
|
||||||
|
|
||||||
|
function submit(field: string, value: string) {
|
||||||
|
if (submitting) return
|
||||||
|
submitting = field
|
||||||
|
|
||||||
|
USER.setting(field, value).then(() => {
|
||||||
|
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !loading}
|
||||||
|
<div class="fields">
|
||||||
|
<blockquote class="info">
|
||||||
|
{ts("settings.siteNotice")}
|
||||||
|
</blockquote>
|
||||||
|
<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="field m-t">
|
||||||
|
<div class="bool">
|
||||||
|
<input id="displayCandidates" type="checkbox" bind:checked={me.displayCandidates}
|
||||||
|
on:change={() => submit('displayCandidates', me.displayCandidates.toString())}/>
|
||||||
|
<label for="displayCandidates">
|
||||||
|
<span class="name">{ts(`settings.fields.displayCandidates.name`)}</span>
|
||||||
|
<span class="desc">{ts(`settings.fields.displayCandidates.desc`)}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<blockquote class="info">
|
||||||
|
{ts("settings.regionNotice")}
|
||||||
|
</blockquote>
|
||||||
|
<RegionSelector/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<StatusOverlays {error} {loading} />
|
||||||
|
|
||||||
|
<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>
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { slide, fade } from "svelte/transition";
|
||||||
|
import { FADE_IN, FADE_OUT, DATA_HOST } from "../../libs/config";
|
||||||
|
import { t } from "../../libs/i18n.js";
|
||||||
|
import Icon from "@iconify/svelte";
|
||||||
|
import StatusOverlays from "../StatusOverlays.svelte";
|
||||||
|
import { GAME, SETTING, USER } from "../../libs/sdk";
|
||||||
|
import GameSettingFields from "./GameSettingFields.svelte";
|
||||||
|
import { download } from "../../libs/ui";
|
||||||
|
import UserOptionSlider from "./UserOptionSlider.svelte";
|
||||||
|
import type { GameUserOption } from "../../libs/generalTypes";
|
||||||
|
import InputField from "../ui/InputField.svelte";
|
||||||
|
|
||||||
|
let userNameField: any
|
||||||
|
|
||||||
|
let error: string
|
||||||
|
let submitting = ""
|
||||||
|
let loading = true
|
||||||
|
let changed: string[] = []
|
||||||
|
|
||||||
|
let userOptions: GameUserOption = {}
|
||||||
|
let userIsUsingPreset = false
|
||||||
|
|
||||||
|
USER.me().then(me =>
|
||||||
|
GAME.userSummary(me.username, 'mai2').then(async ({name}) => {
|
||||||
|
userNameField = {key: "gameUsername", value: name, type: "String"}
|
||||||
|
|
||||||
|
userOptions = await SETTING.optionGet('mai2').catch(_ => {
|
||||||
|
error = t("userbox.error.nodata")
|
||||||
|
loading = true
|
||||||
|
}) as GameUserOption
|
||||||
|
if (userOptions["optionKind"] != 3)
|
||||||
|
userIsUsingPreset = true
|
||||||
|
loading = false
|
||||||
|
}).catch(e => error = e.message));
|
||||||
|
|
||||||
|
async function exportBatchManual() {
|
||||||
|
submitting = "batchExport"
|
||||||
|
const DIFFICULTY_MAP: Record<number, string> = {
|
||||||
|
0: "Basic",
|
||||||
|
1: "Advanced",
|
||||||
|
2: "Expert",
|
||||||
|
3: "Master",
|
||||||
|
4: "Re:Master"
|
||||||
|
}
|
||||||
|
|
||||||
|
const DAN_MAP: Record<number, string> = {
|
||||||
|
1: "DAN_1",
|
||||||
|
2: "DAN_2",
|
||||||
|
3: "DAN_3",
|
||||||
|
4: "DAN_4",
|
||||||
|
5: "DAN_5",
|
||||||
|
6: "DAN_6",
|
||||||
|
7: "DAN_7",
|
||||||
|
8: "DAN_8",
|
||||||
|
9: "DAN_9",
|
||||||
|
10: "DAN_10",
|
||||||
|
11: "SHINDAN_1",
|
||||||
|
12: "SHINDAN_2",
|
||||||
|
13: "SHINDAN_3",
|
||||||
|
14: "SHINDAN_4",
|
||||||
|
15: "SHINDAN_5",
|
||||||
|
16: "SHINDAN_6",
|
||||||
|
17: "SHINDAN_7",
|
||||||
|
18: "SHINDAN_8",
|
||||||
|
19: "SHINDAN_9",
|
||||||
|
20: "SHINDAN_10",
|
||||||
|
21: "SHINKAIDEN",
|
||||||
|
22: "URAKAIDEN"
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLASS_MAP: Record<number, string> = {
|
||||||
|
0: "B5",
|
||||||
|
1: "B4",
|
||||||
|
2: "B3",
|
||||||
|
3: "B2",
|
||||||
|
4: "B1",
|
||||||
|
5: "A5",
|
||||||
|
6: "A4",
|
||||||
|
7: "A3",
|
||||||
|
8: "A2",
|
||||||
|
9: "A1",
|
||||||
|
10: "S5",
|
||||||
|
11: "S4",
|
||||||
|
12: "S3",
|
||||||
|
13: "S2",
|
||||||
|
14: "S1",
|
||||||
|
15: "SS5",
|
||||||
|
16: "SS4",
|
||||||
|
17: "SS3",
|
||||||
|
18: "SS2",
|
||||||
|
19: "SS1",
|
||||||
|
20: "SSS5",
|
||||||
|
21: "SSS4",
|
||||||
|
22: "SSS3",
|
||||||
|
23: "SSS2",
|
||||||
|
24: "SSS1",
|
||||||
|
25: "LEGEND"
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: any
|
||||||
|
let musicData: any
|
||||||
|
let output: any = {
|
||||||
|
"meta": {
|
||||||
|
"game": "maimaidx",
|
||||||
|
"playtype": "Single",
|
||||||
|
"service": "AquaDX-Manual"
|
||||||
|
},
|
||||||
|
"scores": [],
|
||||||
|
"classes": {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
musicData = await fetch(`${DATA_HOST}/d/mai2/00/all-music.json`).then(res => res.json())
|
||||||
|
} catch (e: any) {
|
||||||
|
error = e.message;
|
||||||
|
submitting = ""
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
data = await GAME.export('mai2');
|
||||||
|
} catch (e: any) {
|
||||||
|
error = e.message;
|
||||||
|
submitting = ""
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data && "userPlaylogList" in data) {
|
||||||
|
for (let score of data.userPlaylogList) {
|
||||||
|
if(score.musicId > 100000){
|
||||||
|
continue; // UTAGE charts are not supported
|
||||||
|
}
|
||||||
|
const musicItem = musicData[score.musicId as string];
|
||||||
|
if (!musicItem) continue;
|
||||||
|
let difficulty = null;
|
||||||
|
|
||||||
|
if (!(score.level in DIFFICULTY_MAP))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const isDX = score.musicId >= 10000;
|
||||||
|
difficulty = isDX ? `DX ${DIFFICULTY_MAP[score.level]}` : DIFFICULTY_MAP[score.level];
|
||||||
|
|
||||||
|
const percent = score.achievement/10000;
|
||||||
|
|
||||||
|
const pcrit = score.tapCriticalPerfect + score.holdCriticalPerfect + score.slideCriticalPerfect + score.touchCriticalPerfect + score.breakCriticalPerfect;
|
||||||
|
const perfect = score.tapPerfect + score.holdPerfect + score.slidePerfect + score.touchPerfect + score.breakPerfect;
|
||||||
|
const great = score.tapGreat + score.holdGreat + score.slideGreat + score.touchGreat + score.breakGreat;
|
||||||
|
const good = score.tapGood + score.holdGood + score.slideGood + score.touchGood + score.breakGood;
|
||||||
|
const miss = score.tapMiss + score.holdMiss + score.slideMiss + score.touchMiss + score.breakMiss;
|
||||||
|
const judgements = {
|
||||||
|
"pcrit": pcrit,
|
||||||
|
"perfect": perfect,
|
||||||
|
"great": great,
|
||||||
|
"good": good,
|
||||||
|
"miss": miss
|
||||||
|
}
|
||||||
|
let lamp = null;
|
||||||
|
if (score.isAllPerfect) {
|
||||||
|
lamp = "ALL PERFECT";
|
||||||
|
if (percent >= 101.0) {
|
||||||
|
lamp = "ALL PERFECT+";
|
||||||
|
}
|
||||||
|
} else if (score.isFullCombo) {
|
||||||
|
lamp = "FULL COMBO";
|
||||||
|
if (!good) {
|
||||||
|
lamp = "FULL COMBO+";
|
||||||
|
}
|
||||||
|
} else if (score.isClear) {
|
||||||
|
lamp = "CLEAR";
|
||||||
|
} else {
|
||||||
|
lamp = "FAILED";
|
||||||
|
}
|
||||||
|
|
||||||
|
const optional = {
|
||||||
|
"fast": score.fastCount,
|
||||||
|
"slow": score.lateCount,
|
||||||
|
"maxCombo": score.maxCombo
|
||||||
|
}
|
||||||
|
|
||||||
|
output.scores.push({
|
||||||
|
"percent": percent,
|
||||||
|
"lamp": lamp,
|
||||||
|
"matchType": "inGameID",
|
||||||
|
"identifier": score.musicId.toString(),
|
||||||
|
"difficulty": difficulty,
|
||||||
|
"timeAchieved": new Date(score.userPlayDate).getTime(),
|
||||||
|
"judgements": judgements,
|
||||||
|
"optional": optional
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.userData.courseRank in DAN_MAP){
|
||||||
|
output.classes["dan"] = DAN_MAP[data.userData.courseRank]
|
||||||
|
}
|
||||||
|
if(data.userData.classRank in CLASS_MAP){
|
||||||
|
output.classes["matchingClass"] = CLASS_MAP[data.userData.classRank]
|
||||||
|
}
|
||||||
|
download(JSON.stringify(output), `AquaDX_maimai2_BatchManualExport_${userNameField.value}.json`)
|
||||||
|
submitting = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportData() {
|
||||||
|
submitting = "export"
|
||||||
|
GAME.export('mai2')
|
||||||
|
.then(data => download(JSON.stringify(data), `AquaDX_maimai2_export_${userNameField.value}.json`))
|
||||||
|
.catch(e => error = e.message)
|
||||||
|
.finally(() => submitting = "")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !loading}
|
||||||
|
<div class="fields">
|
||||||
|
<InputField bind:field={userNameField}
|
||||||
|
callback={() => GAME.changeName('mai2', userNameField.value)}/>
|
||||||
|
{#if userIsUsingPreset}
|
||||||
|
<blockquote class="info">
|
||||||
|
{t('settings.options.all.preset-warning')}
|
||||||
|
</blockquote>
|
||||||
|
{/if}
|
||||||
|
<div class="fields-ranges">
|
||||||
|
<!-- TODO: determine min & max -->
|
||||||
|
<UserOptionSlider
|
||||||
|
game="mai2" type="noteSpeed"
|
||||||
|
minValue={0} maxValue={37}
|
||||||
|
defaultValue={userOptions["noteSpeed"]}
|
||||||
|
getTextFunction={(value: number) => value >= 37 ? "SONIC" : `${(value / 4) + 1}`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="mai2" type="touchSpeed"
|
||||||
|
minValue={0} maxValue={37}
|
||||||
|
defaultValue={userOptions["touchSpeed"]}
|
||||||
|
getTextFunction={(value: number) => value >= 37 ? "SONIC" : `${(value / 4) + 1}`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="mai2" type="slideSpeed"
|
||||||
|
minValue={0} maxValue={20}
|
||||||
|
defaultValue={userOptions["slideSpeed"]}
|
||||||
|
getTextFunction={(value: number) => value == 10 ? "Normal" : `${value > 10 ? "Late" : "Fast"} ${(Math.abs(value - 10) / 10).toFixed(1)}`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="mai2" type="headPhoneVolume"
|
||||||
|
minValue={0} maxValue={19}
|
||||||
|
defaultValue={userOptions["headPhoneVolume"]}
|
||||||
|
getTextFunction={(value: number) => `${((value / 19) * 100).toFixed(0)}%`}
|
||||||
|
/>
|
||||||
|
<UserOptionSlider
|
||||||
|
game="mai2" type="trackSkip"
|
||||||
|
minValue={0} maxValue={11}
|
||||||
|
defaultValue={userOptions["trackSkip"]}
|
||||||
|
getTextFunction={(value: number) => [
|
||||||
|
t('settings.options.all.none'),
|
||||||
|
t('settings.options.all.push'),
|
||||||
|
`S`, `SS`, `SSS`,
|
||||||
|
t('settings.options.all.personal-best'),
|
||||||
|
t('settings.options.all.rival-score'),
|
||||||
|
`${t('settings.options.all.life')} 300`,
|
||||||
|
`${t('settings.options.all.life')} 100`,
|
||||||
|
`${t('settings.options.all.life')} 50`,
|
||||||
|
`${t('settings.options.all.life')} 10`,
|
||||||
|
`${t('settings.options.all.life')} 1`
|
||||||
|
][value]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<GameSettingFields game="mai2"/>
|
||||||
|
<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}
|
||||||
|
|
||||||
|
<StatusOverlays {error} loading={!userNameField || !!submitting || loading}/>
|
||||||
|
|
||||||
|
<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
|
||||||
|
|
||||||
|
.fields-ranges
|
||||||
|
display: flex
|
||||||
|
flex-wrap: wrap
|
||||||
|
margin: 0.5rem 0
|
||||||
|
gap: 0 1rem
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
:global(.field)
|
||||||
|
flex: calc(50% - 1rem)
|
||||||
|
width: 50%
|
||||||
|
|
||||||
|
.exportButton
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
gap: 5px
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||||
|
import GameSettingFields from "./GameSettingFields.svelte";
|
||||||
|
import { GAME, SETTING, USER } from "../../libs/sdk";
|
||||||
|
import { download } from "../../libs/ui";
|
||||||
|
import InputField from "../ui/InputField.svelte";
|
||||||
|
import Icon from "@iconify/svelte";
|
||||||
|
import { t } from "../../libs/i18n";
|
||||||
|
import StatusOverlays from "../StatusOverlays.svelte";
|
||||||
|
|
||||||
|
let userNameField: any
|
||||||
|
|
||||||
|
let submitting = "";
|
||||||
|
let error: string
|
||||||
|
let loading = true;
|
||||||
|
|
||||||
|
USER.me().then(me =>
|
||||||
|
GAME.userSummary(me.username, 'ongeki').then(async ({name}) => {
|
||||||
|
userNameField = {key: "gameUsername", value: name, type: "String"}
|
||||||
|
loading = false;
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
function exportData() {
|
||||||
|
submitting = "export"
|
||||||
|
GAME.export('ongeki')
|
||||||
|
.then(data => download(JSON.stringify(data), `AquaDX_ongeki_export_${userNameField.value}.json`))
|
||||||
|
.catch(e => error = e.message)
|
||||||
|
.finally(() => submitting = "")
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if !loading}
|
||||||
|
<div>
|
||||||
|
<div class="fields">
|
||||||
|
<InputField bind:field={userNameField}
|
||||||
|
callback={() => SETTING.detailSet('ongeki', 'userName', userNameField.value)}/>
|
||||||
|
<GameSettingFields game="ongeki"/>
|
||||||
|
<button class="exportButton" on:click={exportData}>
|
||||||
|
<Icon icon="bxs:file-export"/>
|
||||||
|
{t('settings.export')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<StatusOverlays {error} loading={!userNameField || !!submitting || loading}/>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.fields
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 12px
|
||||||
|
</style>
|
||||||
@@ -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 = $state("")
|
||||||
|
let error: string = $state("");
|
||||||
|
|
||||||
|
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} onchange={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>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from "../../libs/i18n";
|
||||||
|
import { EN_REF } from "../../libs/i18n/en_ref";
|
||||||
|
import type { GameName } from "../../libs/scoring";
|
||||||
|
import { SETTING } from "../../libs/sdk";
|
||||||
|
|
||||||
|
export let getTextFunction: (value: number) => string
|
||||||
|
export let defaultValue: number = 0
|
||||||
|
|
||||||
|
export let minValue: number = 0
|
||||||
|
export let maxValue: number = 0
|
||||||
|
export let type: string = "speed";
|
||||||
|
export let game: GameName;
|
||||||
|
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
|
||||||
|
let value = defaultValue;
|
||||||
|
let rationalizedValue = defaultValue / maxValue;
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
await SETTING.optionSet(game, type, value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div class="field range">
|
||||||
|
<div class="range-label">
|
||||||
|
{t(`settings.options.${game}.${type}` as keyof typeof EN_REF)} <!-- TODO: i18n -->
|
||||||
|
</div>
|
||||||
|
<div class="range-value">
|
||||||
|
<input
|
||||||
|
type="range" min={minValue} {disabled}
|
||||||
|
on:change={submit} max={maxValue} bind:value={value}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{getTextFunction(value)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style lang="sass">
|
||||||
|
@use "../../vars"
|
||||||
|
|
||||||
|
.field
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.range-value
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
div
|
||||||
|
min-width: 4rem
|
||||||
|
white-space: nowrap
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
|
||||||
|
input
|
||||||
|
flex: 100%
|
||||||
|
|
||||||
|
input
|
||||||
|
-webkit-appearance: none
|
||||||
|
width: 100%
|
||||||
|
height: 5px !important
|
||||||
|
outline: none
|
||||||
|
transition: opacity 200ms
|
||||||
|
padding: 4px
|
||||||
|
border-radius: 4px
|
||||||
|
opacity: 80%
|
||||||
|
background: linear-gradient(to right, transparent 0%, #caf1 10%, #caf1 90%, transparent 100%)
|
||||||
|
|
||||||
|
margin: 16px 0
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
opacity: 100%
|
||||||
|
|
||||||
|
*:focus
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
input::-webkit-slider-thumb
|
||||||
|
-webkit-appearance: none
|
||||||
|
appearance: none
|
||||||
|
width: 8px
|
||||||
|
height: 30px
|
||||||
|
background: vars.$c-main
|
||||||
|
border-radius: 8px
|
||||||
|
cursor: pointer
|
||||||
|
box-shadow: 0 0 2px 0 vars.$c-main
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { slide, fade } from "svelte/transition";
|
||||||
|
import type { AquaNetUser, ConfirmProps } from "../../libs/generalTypes";
|
||||||
|
import { CARD, GAME, USER, type ExportGameName } from "../../libs/sdk";
|
||||||
|
import StatusOverlays from "../../components/StatusOverlays.svelte";
|
||||||
|
import Icon from "@iconify/svelte";
|
||||||
|
import { download, pfp } from "../../libs/ui";
|
||||||
|
import { t, ts } from "../../libs/i18n";
|
||||||
|
import Cropper from "svelte-easy-crop";
|
||||||
|
|
||||||
|
let me: AquaNetUser;
|
||||||
|
let error: string;
|
||||||
|
let submitting = ""
|
||||||
|
let loading = false;
|
||||||
|
let confirm: ConfirmProps | null = null;
|
||||||
|
|
||||||
|
const profileFields = [
|
||||||
|
[ 'displayName', t('settings.profile.name') ],
|
||||||
|
[ 'username', t('settings.profile.username') ],
|
||||||
|
[ 'password', t('settings.profile.password') ],
|
||||||
|
[ 'profileBio', t('settings.profile.bio') ],
|
||||||
|
] as const
|
||||||
|
|
||||||
|
// Fetch user data
|
||||||
|
const getMe = () => {
|
||||||
|
loading = true;
|
||||||
|
USER.me().then((m) => {
|
||||||
|
if (pfpCropURL != null) {
|
||||||
|
URL.revokeObjectURL(pfpCropURL);
|
||||||
|
pfpField.value = "";
|
||||||
|
pfpCropURL = null;
|
||||||
|
}; me = m;
|
||||||
|
loading = false;
|
||||||
|
}).catch(e => error = e.message)
|
||||||
|
}
|
||||||
|
getMe();
|
||||||
|
|
||||||
|
let changed: string[] = []
|
||||||
|
let pfpField: HTMLInputElement
|
||||||
|
let pfpCropURL: string | null = null;
|
||||||
|
let pfpCrop = { width: 0, height: 0, x: 0, y: 0 };
|
||||||
|
|
||||||
|
function submit(field: string, value: string) {
|
||||||
|
if (submitting) return
|
||||||
|
submitting = field
|
||||||
|
|
||||||
|
USER.setting(field, value).then(() => {
|
||||||
|
changed = changed.filter(c => c !== field)
|
||||||
|
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadPfp() {
|
||||||
|
if (submitting) return
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
let ctx = canvas.getContext("2d");
|
||||||
|
const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024));
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
let img = document.createElement("img");
|
||||||
|
img.onload = () => {
|
||||||
|
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size);
|
||||||
|
canvas.toBlob(blob => {
|
||||||
|
if (!blob) return;
|
||||||
|
submitting = 'profilePicture'
|
||||||
|
USER.uploadPfp(blob as File).then(() => {
|
||||||
|
me.profilePicture = me.username
|
||||||
|
setTimeout(getMe, 200);
|
||||||
|
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
img.src = pfpCropURL ?? "";
|
||||||
|
}
|
||||||
|
function handlePfpUpload(e: Event & { currentTarget: HTMLInputElement }) {
|
||||||
|
if (!e.target) return;
|
||||||
|
let files = e?.currentTarget?.files;
|
||||||
|
if (!files || files.length <= 0) return;
|
||||||
|
let file = files[0];
|
||||||
|
console.log(me.username, me);
|
||||||
|
switch (file.type) {
|
||||||
|
case "image/gif":
|
||||||
|
USER.uploadPfp(file).then(() => {
|
||||||
|
me.profilePicture = me.username
|
||||||
|
// reload
|
||||||
|
setTimeout(getMe, 200);
|
||||||
|
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||||
|
break;
|
||||||
|
case "image/png":
|
||||||
|
case "image/jpeg":
|
||||||
|
case "image/webp":
|
||||||
|
pfpCropURL = URL.createObjectURL(file);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = t("settings.profile.bad-format");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function logOut() {
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
location.href = "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestAccountDeletion() {
|
||||||
|
if (submitting) return;
|
||||||
|
confirm = {
|
||||||
|
title: t('settings.profile.delete-confirm-title'),
|
||||||
|
message: t('settings.profile.delete-confirm-message'),
|
||||||
|
dangerous: true,
|
||||||
|
cancel: () => confirm = null,
|
||||||
|
confirm: deleteAccount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccount() {
|
||||||
|
if (submitting) return;
|
||||||
|
submitting = "deleteAccount";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const gameSummary = await CARD.userGames(me.username);
|
||||||
|
const games = (['mai2', 'chu3', 'ongeki', 'wacca', 'diva'] as ExportGameName[])
|
||||||
|
.filter(game => gameSummary[game] !== null);
|
||||||
|
const exports = await Promise.all(games.map(async game => ({
|
||||||
|
game,
|
||||||
|
data: await GAME.export(game),
|
||||||
|
})));
|
||||||
|
|
||||||
|
exports.forEach(({game, data}) => download(
|
||||||
|
JSON.stringify(data, null, 2),
|
||||||
|
`AquaDX_${game}_export_${me.username}.json`,
|
||||||
|
));
|
||||||
|
await USER.deleteAccount();
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
location.href = "/";
|
||||||
|
} catch (e) {
|
||||||
|
error = (e as Error).message;
|
||||||
|
submitting = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
||||||
|
if (whether) node.type = 'password'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<StatusOverlays bind:confirm={confirm} {error} loading={!!submitting || loading} />
|
||||||
|
{#if !submitting && !error && me}
|
||||||
|
<div>
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<label for="profile-upload">{t('settings.profile.picture')}</label>
|
||||||
|
<div>
|
||||||
|
{#if me && me.profilePicture}
|
||||||
|
<div on:click={() => pfpField.click()} on:keydown={e => e.key === 'Enter' && pfpField.click()}
|
||||||
|
role="button" tabindex="0" class="clickable">
|
||||||
|
<img use:pfp={me} alt="Profile" />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button on:click={() => pfpField.click()}>
|
||||||
|
{t('settings.profile.upload-new')}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<input id="profile-upload" type="file" accept="image/gif,image/png,image/jpeg,image/webp" hidden bind:this={pfpField}
|
||||||
|
on:change={handlePfpUpload} />
|
||||||
|
</div>
|
||||||
|
{#each profileFields as [field, name], i (field)}
|
||||||
|
<div class="field">
|
||||||
|
<label for={field}>{name}</label>
|
||||||
|
<div>
|
||||||
|
{#if field == "profileBio"}
|
||||||
|
<textarea id={field} bind:value={me[field]} on:input={() => changed = [...changed, field]} maxlength=255 placeholder={t('settings.profile.unset')}></textarea>
|
||||||
|
{:else}
|
||||||
|
<input id={field} type="text" use:passwordAction={field === 'password'}
|
||||||
|
bind:value={me[field]} on:input={() => changed = [...changed, field]}
|
||||||
|
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if changed.includes(field) && me[field]}
|
||||||
|
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, me[field])}>
|
||||||
|
{#if submitting === field}
|
||||||
|
<Icon icon="line-md:loading-twotone-loop" />
|
||||||
|
{:else}
|
||||||
|
{t('settings.profile.save')}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="field m-t">
|
||||||
|
<div class="bool">
|
||||||
|
<input id="optOutOfLeaderboard" type="checkbox" bind:checked={me.optOutOfLeaderboard}
|
||||||
|
on:change={() => submit('optOutOfLeaderboard', me.optOutOfLeaderboard.toString())}/>
|
||||||
|
<label for="optOutOfLeaderboard">
|
||||||
|
<span class="name">{ts(`settings.fields.optOutOfLeaderboard.name`)}</span>
|
||||||
|
<span class="desc">{ts(`settings.fields.optOutOfLeaderboard.desc`)}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field m-t">
|
||||||
|
<div class="bool">
|
||||||
|
<input id="hideCountry" type="checkbox" bind:checked={me.hideCountry}
|
||||||
|
on:change={() => submit('hideCountry', me.hideCountry.toString())}/>
|
||||||
|
<label for="hideCountry">
|
||||||
|
<span class="name">{ts(`settings.fields.hideCountry.name`)}</span>
|
||||||
|
<span class="desc">{ts(`settings.fields.hideCountry.desc`)}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field m-t">
|
||||||
|
<div>
|
||||||
|
<button on:click={logOut}>{ts(`settings.profile.logout`)}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field danger-zone m-t">
|
||||||
|
<span class="name">{t('settings.profile.delete-title')}</span>
|
||||||
|
<span class="desc">{t('settings.profile.delete-description')}</span>
|
||||||
|
<div>
|
||||||
|
<button class="error" on:click={requestAccountDeletion}>
|
||||||
|
<Icon icon="tabler:trash-x-filled" />
|
||||||
|
{t('settings.profile.delete')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if pfpCropURL != null}
|
||||||
|
<div class="overlay" transition:fade>
|
||||||
|
<div>
|
||||||
|
<div class="cropper-container">
|
||||||
|
<Cropper maxZoom={1e9} oncropcomplete={(e) => pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"></Cropper>
|
||||||
|
</div>
|
||||||
|
<button on:click={uploadPfp}>
|
||||||
|
{t("settings.profile.save")}
|
||||||
|
</button>
|
||||||
|
<button on:click={getMe}>
|
||||||
|
{t("back")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<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
|
||||||
|
|
||||||
|
.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, > textarea
|
||||||
|
flex: 1
|
||||||
|
|
||||||
|
img
|
||||||
|
max-width: 100px
|
||||||
|
max-height: 100px
|
||||||
|
border-radius: vars.$border-radius
|
||||||
|
object-fit: cover
|
||||||
|
aspect-ratio: 1
|
||||||
|
|
||||||
|
.danger-zone
|
||||||
|
margin-top: 1.5rem
|
||||||
|
padding-top: 1.5rem
|
||||||
|
border-top: 1px solid vars.$ov-lighter
|
||||||
|
|
||||||
|
.desc
|
||||||
|
opacity: 0.6
|
||||||
|
|
||||||
|
button
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: 0.5rem
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.cropper-container
|
||||||
|
position: relative
|
||||||
|
width: 400px
|
||||||
|
aspect-ratio: 1
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
|
<GameSettingFields game="wacca"/>
|
||||||
|
</div>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { fade } from "svelte/transition";
|
||||||
|
import { t } from "../../libs/i18n";
|
||||||
|
import { DISCORD_INVITE } from "../../libs/config";
|
||||||
|
|
||||||
|
export let error: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="overlay" transition:fade>
|
||||||
|
<div>
|
||||||
|
<h2 class="error">{t('status.error')}</h2>
|
||||||
|
<span class="detail">{error}</span>
|
||||||
|
<a class="hint" href="/support">{t("status.error.hint")}</a>
|
||||||
|
<div class="actions">
|
||||||
|
<button on:click={() => location.reload()} class="error">
|
||||||
|
{t('action.refresh')}
|
||||||
|
</button>
|
||||||
|
<button on:click={() => location.href = "/home"} class="error">
|
||||||
|
{t('action.home')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.actions
|
||||||
|
display: flex
|
||||||
|
gap: 0.5em
|
||||||
|
|
||||||
|
button
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
.detail
|
||||||
|
white-space: pre-line
|
||||||
|
line-height: 1.2
|
||||||
|
|
||||||
|
.hint
|
||||||
|
font-size: 0.875em
|
||||||
|
|
||||||
|
.overlay > div
|
||||||
|
min-width: 15rem
|
||||||
|
max-width: 25rem
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// card handler. for dealing user QR scan.
|
||||||
|
// this will assumes that the link of the generated QR code in [access code]
|
||||||
|
// page has been altered by modder to link to custom
|
||||||
|
|
||||||
|
import { ACCESSCODE_QKEY } from "./config";
|
||||||
|
|
||||||
|
const query = new URLSearchParams(location.hash.slice(1));
|
||||||
|
const lsKey = "acurl"
|
||||||
|
|
||||||
|
export const url_has = (): boolean => query.has(ACCESSCODE_QKEY);
|
||||||
|
export const get = (): string => localStorage.getItem(lsKey) || "";
|
||||||
|
export const has = (): boolean => !!localStorage.getItem(lsKey);
|
||||||
|
export const clear = (): void => localStorage.removeItem(lsKey);
|
||||||
|
|
||||||
|
export function check() {
|
||||||
|
if (!url_has()) return
|
||||||
|
|
||||||
|
console.log("Found access code in URL. Saving.")
|
||||||
|
localStorage.setItem(lsKey, query.get(ACCESSCODE_QKEY) || "")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function preview() {
|
||||||
|
const card = get()
|
||||||
|
const masked =
|
||||||
|
card.slice(0, 4) +
|
||||||
|
"x".repeat(card.length - 4);
|
||||||
|
|
||||||
|
return masked.match(/.{1,4}/g)?.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function previewR() {
|
||||||
|
const card = get()
|
||||||
|
|
||||||
|
return card.match(/.{1,4}/g)?.join(" ");
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
|
export const ANNOUNCEMENT = writable('')
|
||||||
|
|
||||||
|
// This task must not bother the existing one. Keep it async in the background instead
|
||||||
|
void (async () => {
|
||||||
|
let r = await fetch("/announcement.txt")
|
||||||
|
if (!r.ok) return;
|
||||||
|
|
||||||
|
ANNOUNCEMENT.set(await r.text())
|
||||||
|
})()
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
export const GITHUB_REPOSITORY = import.meta.env.VITE_GITHUB_REPOSITORY
|
||||||
|
|
||||||
|
// UI
|
||||||
|
export const FADE_OUT = { duration: 200 }
|
||||||
|
export const FADE_IN = { delay: 400 }
|
||||||
|
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
||||||
|
|
||||||
|
// Key name for access key scanned from QR code
|
||||||
|
export const ACCESSCODE_QKEY = import.meta.env.VITE_ACCESSCODE_QKEY || "accesskey"
|
||||||
|
|
||||||
|
export const USERBOX_DEFAULT_URL = import.meta.env.VITE_USERBOX_DEFAULT_URL || ""
|
||||||
|
|
||||||
|
export const HAS_USERBOX_ASSETS = true
|
||||||
|
|
||||||
|
export const DEFAULT_GAME = import.meta.env.VITE_DEFAULT_GAME || "mai2"
|
||||||
|
|
||||||
|
// Meow meow meow
|
||||||
|
|
||||||
|
// Matching servers
|
||||||
|
export const CHU3_MATCHINGS: ChusanMatchingOption[] = [
|
||||||
|
{
|
||||||
|
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"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
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,
|
||||||
|
canModifyKeychips: boolean,
|
||||||
|
hideCountry: boolean,
|
||||||
|
displayCandidates: 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: string
|
||||||
|
accuracy: number
|
||||||
|
rating: number
|
||||||
|
ratingHighest: number,
|
||||||
|
ratingNotGeneric: boolean,
|
||||||
|
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 type GameUserOption = Record<string, number>
|
||||||
|
|
||||||
|
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[]
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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 }): string {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
export const EN_REF_USER = {
|
||||||
|
'UserHome.ServerRank': 'Server Rank',
|
||||||
|
'UserHome.DXRating': 'DX Rating',
|
||||||
|
'UserHome.Rating': 'Rating',
|
||||||
|
'UserHome.HighestDXRating': 'Peak DX Rating',
|
||||||
|
'UserHome.HighestRating': 'Peak 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.RatingComposition.Best': 'Best ${n}',
|
||||||
|
'UserHome.RatingComposition.New': 'New ${n}',
|
||||||
|
'UserHome.RatingComposition.BestCandidates': 'Best ${n} Candidates',
|
||||||
|
'UserHome.RatingComposition.NewCandidates': 'New ${n} Candidates',
|
||||||
|
'UserHome.RatingComposition.Platinum': 'Platinum ${n}',
|
||||||
|
'UserHome.RatingComposition.PlatinumCandidates': 'Platinum ${n} Candidates',
|
||||||
|
'UserHome.RatingComposition.Recent': 'Recent ${n}',
|
||||||
|
'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.',
|
||||||
|
'welcome.login_link': 'Login / register to link your card.'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EN_REF_LEADERBOARD = {
|
||||||
|
'Leaderboard.Title': 'Server Leaderboard',
|
||||||
|
'Leaderboard.Rank': 'Rank',
|
||||||
|
'Leaderboard.Rating': 'Rating',
|
||||||
|
'Leaderboard.LegacyRating': 'Legacy Rating',
|
||||||
|
'Leaderboard.ModernRating': 'Modern Rating',
|
||||||
|
'Leaderboard.Accuracy': 'Accuracy',
|
||||||
|
'Leaderboard.FC': 'FC',
|
||||||
|
'Leaderboard.AP': 'AP',
|
||||||
|
'Leaderboard.Loading': "Loading..."
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EN_REF_GENERAL = {
|
||||||
|
'game.mai2': 'Mai',
|
||||||
|
'game.chu3': 'Chuni',
|
||||||
|
'game.ongeki': 'Ongeki',
|
||||||
|
'game.wacca': 'Wacca',
|
||||||
|
'status.error': 'Error',
|
||||||
|
'status.error.hint': 'Support',
|
||||||
|
'status.detail': 'Detail: ${detail}',
|
||||||
|
'action.refresh': 'Retry',
|
||||||
|
'action.cancel': 'Cancel',
|
||||||
|
'action.confirm': 'Confirm',
|
||||||
|
'action.home': 'Home',
|
||||||
|
'navigation.profile': 'Profile',
|
||||||
|
'navigation.maps': 'Maps',
|
||||||
|
'navigation.home': 'Home',
|
||||||
|
'navigation.rankings': 'Rankings',
|
||||||
|
'navigation.notice': 'Notice',
|
||||||
|
'loading': `Please wait...`,
|
||||||
|
'404': 'Page not found (${pathname}). If this is in error, please report it via an appropriate support channel.'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EN_REF_HOME = {
|
||||||
|
'home.nav.portal': 'Portal',
|
||||||
|
'home.nav.link-card': 'Link Card',
|
||||||
|
'home.nav.game-setup': 'Game Setup',
|
||||||
|
'home.nav.support': 'Support',
|
||||||
|
'home.user-profile': 'Profile',
|
||||||
|
'home.rankings': 'Rankings',
|
||||||
|
'home.settings': 'Settings',
|
||||||
|
'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': 'Community & Support',
|
||||||
|
'home.join-community-description': 'Join our community for support and chatting with other players.',
|
||||||
|
'home.setup': 'Setup Network Connection',
|
||||||
|
'home.setup-description': 'Configure a game to connect to our servers.',
|
||||||
|
'home.import': 'Import Player Data',
|
||||||
|
'home.import-description': 'Add player data from a separate instance.',
|
||||||
|
'home.linkcard.cards': 'Your Cards',
|
||||||
|
'home.linkcard.description': 'Access cards that are currently linked',
|
||||||
|
'home.linkcard.account-card': 'Account Card',
|
||||||
|
'home.linkcard.registered': 'Registered',
|
||||||
|
'home.linkcard.lastused': 'Last used',
|
||||||
|
'home.linkcard.enter-info': 'Please enter either an Access Code or a Serial Number in the following boxes. Dragging and dropping aime.txt or felica.txt is also supported.',
|
||||||
|
'home.linkcard.access-code': 'If you have an Access Code, enter it below. Access Codes can be obtained from the back of your card or from the title screen of your game (press the Access Code button and card in). If you do not have an Access Code, you may generate one.',
|
||||||
|
'home.linkcard.enter-sn': 'If you have a Serial Number, enter it below. Serial Numbers can be obtained via most smartphones (NFC Tools: <a href="https://play.google.com/store/apps/details?id=com.wakdev.wdnfc">Android</a> / <a href="https://apps.apple.com/us/app/nfc-tools/id1252962749">Apple</a>) or a card reader.',
|
||||||
|
'home.linkcard.kdx-notice': 'If you are playing KanadeDX, please enter the virtual card number here (can be found in Settings > Card Number).',
|
||||||
|
'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.linkcard.card-security-warning': 'Access Cards give full access to your games\' player data. Do not share your card information with anyone.',
|
||||||
|
'home.community.discord': 'Discord',
|
||||||
|
'home.community.telegram': 'Telegram (Chinese)',
|
||||||
|
'home.community.qq': 'QQ (Chinese)',
|
||||||
|
'home.community.github': 'GitHub Repository',
|
||||||
|
'home.import.unknown-game': 'Unknown game type. Currently only Mai and Chuni 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_SETUP = {
|
||||||
|
'setup.welcome': `Welcome! If you have a game set up, please follow the instructions below to set up the connection with AquaDX.`,
|
||||||
|
'setup.keychip-warning': `Your keychip(s) are linked to your account and should be kept secure.`,
|
||||||
|
'setup.steps.one': `Pick a method of setting up network communications. Some browsers may not be able to do automatic setup.`,
|
||||||
|
'setup.steps.two': `Link your Aime card to your AquaDX account using the <a href="/cards">Cards</a> page via it's access code or serial number.`,
|
||||||
|
'setup.steps.three': `Start the game. Upon reaching the title screen, the network icon in the corner should now show green instead of grey.`,
|
||||||
|
'setup.support-info': `If you need additional assistance, feel free to make an inquiry in a <a href="/support">support channel</a>.`,
|
||||||
|
'setup.reveal-keychip': `Reveal keychip`,
|
||||||
|
'setup.type.automatic': `Automatic Setup`,
|
||||||
|
'setup.type.manual': `Manual Setup`,
|
||||||
|
'setup.manual': `Please modify your segatools.ini with the following information.`,
|
||||||
|
'setup.automatic': `Select your segatools.ini below to autofill the network information.`,
|
||||||
|
'setup.automatic.success': `Success, data has been updated`,
|
||||||
|
'setup.automatic.failure': `Failed to update information, please ensure access is not blocked and try again.`,
|
||||||
|
'setup.automatic.select': `Pick file`,
|
||||||
|
'setup.troubleshooting.header': 'Setup Troubleshooting (FAQ)',
|
||||||
|
'setup.troubleshooting.items.one': `» It displays DNS BAD on Network Test<br>
|
||||||
|
You likely misconfigured your segatools.ini. Ensure it's pointing to the DNS shown above (under Manual Setup).`,
|
||||||
|
'setup.troubleshooting.items.two': `» It displays タイトル サーバー BAD (Title Server BAD) on Network Test<br>
|
||||||
|
You should check your segatools.ini to ensure your keychip is set correctly.<br>
|
||||||
|
Additionally, Card Maker users and players using a game version higher than what's listed may need to disable encryption
|
||||||
|
<a target="_blank" href="https://gitea.tendokyu.moe/Dniel97/SEGAguide/wiki/SDED#patching-assemblycsharpdll">(instructions for Card Maker)</a>`,
|
||||||
|
'setup.troubleshooting.items.three': `» I can't scan my card<br>
|
||||||
|
The built-in emulated card reader, by default, is configured to use the Enter / Return key to scan in (hold the key).`,
|
||||||
|
'setup.keychip': 'Keychip Management',
|
||||||
|
'setup.keychip.warning': 'Keychips can be manually set here for cabinet owners with physical keychips trying to connect to AquaDX. Selecting a keychip will autofill it for the setup below.',
|
||||||
|
'setup.keychip.add': `Add keychip`,
|
||||||
|
'setup.keychip.delete': `Delete`,
|
||||||
|
'setup.keychip.placeholder': `New Keychip ID`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EN_REF_SETTINGS = {
|
||||||
|
'settings.title': 'Settings',
|
||||||
|
'settings.page-title': '${page} Settings',
|
||||||
|
'settings.tabs.profile': 'Profile',
|
||||||
|
'settings.tabs.global': 'Global',
|
||||||
|
'settings.tabs.chu3': 'Chuni',
|
||||||
|
'settings.tabs.mai2': 'Mai',
|
||||||
|
'settings.tabs.ongeki': 'Ongeki',
|
||||||
|
'settings.tabs.wacca': 'Wacca',
|
||||||
|
'settings.options.all.preset-warning': 'You are using a preset option set. Options may be reflected incorrectly.',
|
||||||
|
'settings.options.all.personal-best': 'Record',
|
||||||
|
'settings.options.all.rival-score': 'Rival',
|
||||||
|
'settings.options.all.life': 'Life',
|
||||||
|
'settings.options.all.push': 'Push',
|
||||||
|
'settings.options.all.none': 'None',
|
||||||
|
// Chuni User Options
|
||||||
|
'settings.options.chu3.speed': 'Lane Speed (60fps)',
|
||||||
|
'settings.options.chu3.speed_120': 'Lane Speed (120fps)',
|
||||||
|
'settings.options.chu3.trackSkip': 'Track Skip Type',
|
||||||
|
'settings.options.chu3.headphone': 'Headphone Volume',
|
||||||
|
// Maimai User Options
|
||||||
|
'settings.options.mai2.noteSpeed': 'Note Speed',
|
||||||
|
'settings.options.mai2.touchSpeed': 'Touch Speed',
|
||||||
|
'settings.options.mai2.slideSpeed': 'Slide Speed',
|
||||||
|
'settings.options.mai2.headPhoneVolume': 'Headphone Volume',
|
||||||
|
'settings.options.mai2.trackSkip': 'Track Skip Type',
|
||||||
|
|
||||||
|
'settings.fields.mai2UnlockMusic.name': 'Unlock All Music',
|
||||||
|
'settings.fields.mai2UnlockMusic.desc': 'Unlock all music and master difficulty.',
|
||||||
|
'settings.fields.mai2UnlockChara.name': 'Unlock All Characters',
|
||||||
|
'settings.fields.mai2UnlockChara.desc': 'Unlock all characters (new characters start at level 1).',
|
||||||
|
'settings.fields.mai2UnlockCharaMaxLevel.name': 'Max Character Level',
|
||||||
|
'settings.fields.mai2UnlockCharaMaxLevel.desc': 'Set all characters to max level.',
|
||||||
|
'settings.fields.mai2UnlockPartners.name': 'Unlock All Partners',
|
||||||
|
'settings.fields.mai2UnlockPartners.desc': 'Unlock all partners.',
|
||||||
|
'settings.fields.mai2UnlockCollectables.name': 'Unlock All Collectables',
|
||||||
|
'settings.fields.mai2UnlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame).',
|
||||||
|
'settings.fields.mai2UnlockTickets.name': 'Unlock All Tickets',
|
||||||
|
'settings.fields.mai2UnlockTickets.desc': 'Infinite tickets (Note: client still limits which tickets can be used).',
|
||||||
|
'settings.fields.waccaUnlockMusic.name': 'Unlock All Music',
|
||||||
|
'settings.fields.waccaUnlockMusic.desc': 'Unlock all music.',
|
||||||
|
'settings.fields.waccaUnlockPlates.name': 'Unlock All Plates',
|
||||||
|
'settings.fields.waccaUnlockPlates.desc': 'Unlock all plates.',
|
||||||
|
'settings.fields.waccaUnlockCollectables.name': 'Unlock All Collectables',
|
||||||
|
'settings.fields.waccaUnlockCollectables.desc': 'Unlock all collectables (icon, trophy).',
|
||||||
|
'settings.fields.waccaUnlockTickets.name': 'Infinite Tickets',
|
||||||
|
'settings.fields.waccaUnlockTickets.desc': 'Infinite tickets.',
|
||||||
|
'settings.fields.waccaInfiniteWp.name': 'Infinite WP',
|
||||||
|
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999.',
|
||||||
|
'settings.fields.waccaAlwaysVip.name': '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.chusanUnlock29672999Ultima.name': 'Unlock 2967, 2999 Ultima',
|
||||||
|
'settings.fields.chusanUnlock29672999Ultima.desc': 'Unlocks Ultima difficulty ONLY for musicIds 2967, 2999. Do not use if you don\'t know why you need this.',
|
||||||
|
'settings.fields.chusanLvUnlockAll.name': 'Unlock Linked Gates',
|
||||||
|
'settings.fields.chusanLvUnlockAll.desc': 'Incurs a long animated sequence, cannot be undone',
|
||||||
|
'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': 'Player Name',
|
||||||
|
'settings.fields.gameUsername.desc': 'Your name shown in game',
|
||||||
|
'settings.fields.optOutOfLeaderboard.name': 'Opt Out of Leaderboard',
|
||||||
|
'settings.fields.optOutOfLeaderboard.desc': 'It may take some time for this to take effect',
|
||||||
|
'settings.fields.hideCountry.name': 'Hide Country',
|
||||||
|
'settings.fields.hideCountry.desc': 'Your country of registration will not be visible on your profile',
|
||||||
|
'settings.fields.displayCandidates.name': 'Display Candidates',
|
||||||
|
'settings.fields.displayCandidates.desc': 'Shows upcoming songs that could improve your rating on your profile (when available)',
|
||||||
|
'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.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.profile.delete-title': 'Delete account',
|
||||||
|
'settings.profile.delete-description': 'Download a backup, then permanently delete your account and all associated game data.',
|
||||||
|
'settings.profile.delete': 'Delete account',
|
||||||
|
'settings.profile.delete-confirm-title': 'Permanently delete your account?',
|
||||||
|
'settings.profile.delete-confirm-message': 'A backup of all game data will be downloaded first. After that, your account and all associated game data will be permanently deleted. This cannot be undone.',
|
||||||
|
'settings.export': 'Export Player Data',
|
||||||
|
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||||
|
'settings.cabNotice': "These settings only apply for your keychip. If you're playing on someone else's setup, please ask them to change these settings.",
|
||||||
|
'settings.siteNotice': "These settings only apply to the website.",
|
||||||
|
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.",
|
||||||
|
'settings.regionSelector.title': "Prefecture Selector",
|
||||||
|
'settings.regionSelector.desc': "Select the region where you want the game to identify you",
|
||||||
|
'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.linkedVerse': 'Linked Verse',
|
||||||
|
'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.stageId': 'Stage',
|
||||||
|
'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.lv.help': 'Information about Linked Verse Difficulty',
|
||||||
|
'userbox.lv.diffnotice': 'In Linked Verse, you will be matched separately based on your difficulty.',
|
||||||
|
'userbox.lv.difficulty': 'Linked Verse Difficulty',
|
||||||
|
'userbox.lv.difficulty.0': 'LEVEL ∞ (Only for supported gates)',
|
||||||
|
'userbox.lv.difficulty.1': 'LEVEL V',
|
||||||
|
'userbox.lv.difficulty.2': 'LEVEL IV',
|
||||||
|
'userbox.lv.difficulty.3': 'LEVEL III',
|
||||||
|
'userbox.lv.difficulty.4': 'LEVEL II',
|
||||||
|
'userbox.lv.difficulty.5': 'LEVEL I',
|
||||||
|
|
||||||
|
'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': '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': "This server may have rules against tools such as AquaTrans, please use caution.",
|
||||||
|
}
|
||||||
|
|
||||||
|
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, ...EN_REF_SETUP
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LocalizedMessages = typeof EN_REF
|
||||||