1289 Commits

Author SHA1 Message Date
Azalea
385bbd115d [F] Fix rank 2025-01-03 10:16:21 -05:00
Azalea
6a45df683b [+] Maimai2 unique constraints 2025-01-03 03:44:45 -05:00
Azalea
21e023e609 Revert "[U] Upgrade dependencies"
This reverts commit 332eacd2cc.
2025-01-03 03:09:53 -05:00
Azalea
f6489d5ac0 [+] Add token to logging 2025-01-03 02:19:11 -05:00
Azalea
256aac8faf [+] Add error logging 2025-01-03 01:45:42 -05:00
Azalea
a1be699ec5 [+] characterId 2025-01-02 10:51:45 -05:00
Azalea
d71af941b0 [+] Add some get-only fields 2025-01-02 10:44:06 -05:00
Azalea
a1b56f6e0b [O] Do not log GetUserPortraitApi 2025-01-02 07:13:06 -05:00
Azalea
d8022cc1a4 [F] Fix userbox file logic #97 2025-01-02 07:12:46 -05:00
Azalea
9ba7f5022e [+] Add userbox.new chinese i18n #97 2025-01-01 19:05:45 -05:00
Azalea
437ed2ee60 [+] Chuni Userbox with Assets (#97) 2025-01-01 18:59:55 -05:00
Raymond
4d4335004f refactor: move DDS cache
moved the DDS cache from dds.ts to ddsCache.ts and added caching for scaled images

Co-authored-by: split / May <split@split.pet>
2025-01-01 16:30:48 -05:00
Raymond
ce95f2165d style: make nameplates fit better 2025-01-01 15:06:52 -05:00
split / May
931e611cf7 revert: Reverse decision to remove the classic UserBox preview
Adds back the classic UserBox preview when AquaBox is disabled / unavailable
2025-01-01 06:21:12 -08:00
split / May
81ef029bf6 docs: 📝 add TSDoc comments to functions in the DDS class 2025-01-01 05:21:29 -08:00
split / May
223de57b65 style: linebreaks 2025-01-01 05:13:13 -08:00
split / May
f1d1b81456 refactor: ♻️ replace "CHUNITHM" string with "AquaDX"
recommendation from @raymonable
2025-01-01 04:23:04 -08:00
Raymond
8aa829ab02 [+] Chuni Userbox with Assets
Co-authored-by: split / May  <split@split.pet>
2025-01-01 06:27:38 -05:00
Azalea
8fb443d41d Update docker-image.yml 2024-12-31 05:25:01 +08:00
Azalea
edc62b3cfc Update docker-image.yml 2024-12-31 04:27:25 +08:00
Azalea
644cdef95f Update docker-image.yml 2024-12-31 04:19:50 +08:00
Azalea
dc54473669 [U] Upgrade gradle 2024-12-29 20:31:16 -05:00
Azalea
332eacd2cc [U] Upgrade dependencies 2024-12-29 20:23:21 -05:00
Azalea
c26a670b05 [U] Upgrade kt only 2024-12-29 09:36:30 -05:00
Azalea
1ccb8694d8 Revert "[U] Upgrade deps"
This reverts commit d3c25e6b12.
2024-12-29 09:24:15 -05:00
Azalea
91db21067c [+] Use options in GetGameSettings 2024-12-29 07:16:19 -05:00
Azalea
834546e3ba [+] TODO: Proxy matching 2024-12-29 07:16:00 -05:00
Azalea
6518fe6946 [+] Proxied matching option 2024-12-29 07:10:32 -05:00
Azalea
9b21193be2 [+] Props for matching 2024-12-29 07:07:47 -05:00
Azalea
99983b1eb1 [+] Repo 2024-12-29 07:02:00 -05:00
Azalea
712e2c9d02 [M] Combine other pojos 2024-12-29 07:01:47 -05:00
Azalea
f963e6aa03 [O] Turn MatchingMember into db table 2024-12-29 06:58:01 -05:00
Azalea
d3c25e6b12 [U] Upgrade deps 2024-12-29 06:57:42 -05:00
Azalea
667abf2131 [O] Split matching apis 2024-12-29 05:16:09 -05:00
Azalea
8a1e17ecd3 Merge branch 'v1-dev' into matching 2024-12-29 05:11:58 -05:00
Azalea
809004e16b [O] Rewrite BooleanIntDeserializer 2024-12-29 05:11:51 -05:00
Azalea
e35df8a029 [U] Update userbox apis 2024-12-29 04:50:03 -05:00
Azalea
d9081563c2 [U] Update userbox SDK 2024-12-29 04:49:43 -05:00
Azalea
29e757ba75 [U] Update i18n for userbox 2024-12-29 04:49:31 -05:00
Azalea
48724bae8b [-] Remove unused types 2024-12-29 04:49:15 -05:00
Azalea
b1de430f0b [O] Rewrite userbox 2024-12-29 04:48:36 -05:00
Azalea
6cd18ba7f7 [M] Move settings components 2024-12-29 04:48:24 -05:00
Azalea
07d167d961 [F] Put chusan settings in chusan tab 2024-12-29 02:31:48 -05:00
Azalea
dadedbe129 [+] i18n for game option 2024-12-29 02:31:33 -05:00
Azalea
6d8948cdf1 [+] Implement infinite penguins 2024-12-29 01:42:00 -05:00
Azalea
27ca67b6f9 [+] Infinite penguins option 2024-12-29 01:41:44 -05:00
Azalea
7e27bf0785 [+] Card.sensitiveInfo 2024-12-29 00:44:04 -05:00
Azalea
a4e8cbe9e1 [+] debug-user-profile 2024-12-29 00:41:08 -05:00
Azalea
3d58a15b10 [+] Implement matching? 2024-12-28 08:49:29 -05:00
Azalea
5c80aec50b [F] Fix timezones 2024-12-28 07:30:49 -05:00
Azalea
b52916e62c [F] Fix chusan user creation 2024-12-28 03:11:23 -05:00
Azalea
91ea0c9c8e [O] Better logging 2024-12-28 02:36:17 -05:00
Azalea
2a80a10eec [+] Add worlds end 2024-12-28 01:55:01 -05:00
Azalea
7b8fb02398 [+] Add chusan unique constraints 2024-12-28 01:06:25 -05:00
Azalea
74aa319f41 [F] Fix nextIndex -1 2024-12-28 01:06:00 -05:00
Azalea
0de4856247 [F] Fix item 2024-12-28 00:19:51 -05:00
Azalea
da61b1a3e7 [+] Paged post process 2024-12-28 00:18:15 -05:00
Azalea
0632213c8b [F] Fix cache key 2024-12-27 23:52:49 -05:00
Azalea
daa8de203b [-] Remove unnecessary assignment 2024-12-27 23:34:52 -05:00
Azalea
537558e3d5 [+] Rewrite using paging 2024-12-27 23:31:58 -05:00
Azalea
8d23b262c7 [O] Better pagination 2024-12-27 23:31:22 -05:00
Azalea
04a178eda6 [+] Free paging 2024-12-27 22:05:46 -05:00
Azalea
977f353f9c [O] Replace CM 2024-12-27 22:04:20 -05:00
Azalea
ae6ff97b62 [O] Make handlers abstract 2024-12-27 16:03:02 -05:00
Azalea
fa45891af4 [F] Fix paging 2024-12-27 06:10:12 -05:00
Azalea
159b36607a [F] Fix corner case when only one song is pressent 2024-12-27 03:26:09 -05:00
Azalea
a731687607 [F] Fix favorite saving 2024-12-27 03:24:20 -05:00
Azalea
a77a74ba61 [+] Save favorite music 2024-12-27 02:15:05 -05:00
Azalea
fc35381e1b [F] oops 2024-12-27 01:13:55 -05:00
Azalea
6f837830ab [+] SEO meta 2024-12-27 01:13:15 -05:00
Azalea
8197361fb0 [F] Fix 2024-12-27 00:36:16 -05:00
Azalea
c7d12fbdf8 [F] Fix user recent rating 2024-12-27 00:19:40 -05:00
Azalea
0cb3fd3134 [O] Better diffing 2024-12-27 00:19:16 -05:00
Azalea
77a791e5da [+] Top 10 out of recent 40 2024-12-26 23:15:14 -05:00
Azalea
42d94b43b1 [F] Fix score display 2024-12-26 23:08:12 -05:00
Azalea
cb6bf00236 [+] Allow saving chuni team name 2024-12-26 22:52:16 -05:00
Azalea
4f6bd11a70 [O] Rewrite login handler 2024-12-26 22:11:14 -05:00
Azalea
0f14326449 [F] Fix CM 2024-12-26 21:50:07 -05:00
Azalea
b421b4476b [+] Abstract mapper 2024-12-26 21:49:51 -05:00
Azalea
18554ec439 [-] Remove unused 2024-12-26 21:36:39 -05:00
Azalea
806e24b9f1 [O] Rewrite GetUserMusic 2024-12-26 21:30:34 -05:00
Azalea
fa1d69f1f9 [O] Rewrite GetUserRecentRating 2024-12-26 20:58:18 -05:00
Azalea
33aebc42b3 [O] Rewrite GetUserLoginBonus 2024-12-26 20:52:36 -05:00
Azalea
ffcb94674e [+] Rewrite More CM 2024-12-26 20:47:46 -05:00
Azalea
c1323a6ba1 [-] Remove duplicates 2024-12-26 20:38:49 -05:00
Azalea
ea70da8fbf [O] Split 2024-12-26 20:26:22 -05:00
Azalea
1bdb17f073 [F] Fix team name encoding 2024-12-26 20:24:50 -05:00
Azalea
506031b5cb [F] Fix team name null 2024-12-26 20:15:41 -05:00
Azalea
4a981900aa [O] Rewrite CM user data 2024-12-26 20:11:39 -05:00
Azalea
6fa052bfcf [+] Team name 2024-12-26 20:09:35 -05:00
Azalea
19ac32d328 [+] Add chusan team name option 2024-12-26 20:08:17 -05:00
Azalea
44eab78935 [+] Add todo 2024-12-26 19:51:18 -05:00
Azalea
a2413f3635 [F] Fix item kind 2024-12-26 19:46:20 -05:00
Azalea
13b1d8fc34 [F] Fix 2024-12-26 19:38:06 -05:00
Azalea
8b90449970 [+] Chusan rating calculation 2024-12-26 19:37:55 -05:00
Azalea
038e76ed94 [-] Remove comment 2024-12-26 19:24:26 -05:00
Azalea
390c80c46c [F] Fix StringMapper 2024-12-26 19:24:05 -05:00
Azalea
dffae008cd [F] Fix dates 2024-12-26 19:06:18 -05:00
Azalea
bcf9af71e2 [O] Rewrite roll gacha 2024-12-26 18:53:08 -05:00
Azalea
5787d32c1a [O] Rewrite user preview 2024-12-26 18:52:49 -05:00
Azalea
39b5032303 [O] Rewrite list getters 2024-12-26 18:52:16 -05:00
Azalea
5f871b1945 [O] Rewrite map area condition 2024-12-26 18:51:26 -05:00
Azalea
346f1c991a [O] Rewrite upsert chargelog 2024-12-26 18:51:07 -05:00
Azalea
b14a56bb6c [O] Rewrite Begin matching 2024-12-26 18:50:20 -05:00
Azalea
f75d0acb1c [-] Remove more unused 2024-12-26 17:41:54 -05:00
Azalea
b2d1fd916d [+] Add lazy uid to context 2024-12-26 17:41:33 -05:00
Azalea
b7f1e30708 [-] Remove unused 2024-12-26 17:41:26 -05:00
Azalea
88863d8d01 [O] Everything can be special 2024-12-26 17:41:05 -05:00
Azalea
c275c54fca [O] Rename composition 2024-12-26 12:25:23 -05:00
Azalea
b97ace2c6e [+] Chusan b40? 2024-12-26 12:22:43 -05:00
Azalea
c78c4689f1 [F] Fix sql syntax error 2024-12-26 12:19:19 -05:00
Azalea
f39ccf7629 [O] Better logging in json 2024-12-26 11:57:40 -05:00
Azalea
885dfb5bea [+] Repo query 2024-12-26 11:57:31 -05:00
Azalea
8037273672 [+] Update data version in upsert all 2024-12-26 11:57:19 -05:00
Azalea
2e9c0656de [+] Return correct data version in get game settings 2024-12-26 11:57:02 -05:00
Azalea
e85d294d12 [+] Version cache 2024-12-26 11:56:23 -05:00
Azalea
24bf6cffc3 [M] Rename 2024-12-26 11:55:31 -05:00
Azalea
16762d1a46 [F] Fix url passing 2024-12-26 08:30:21 -05:00
Azalea
6a54005472 [F] Fix matching url 2024-12-26 08:08:22 -05:00
Azalea
812d910212 [F] Fix safety :( 2024-12-26 07:55:54 -05:00
Azalea
b7c8fba464 [F] Fix safety 2024-12-26 07:53:36 -05:00
Azalea
89424f6466 [O] remove unnecessary toString 2024-12-26 07:24:52 -05:00
Azalea
0411505341 [O] riik GetGameSetting 2024-12-26 07:24:32 -05:00
Azalea
6938083463 [+] Special handler 2024-12-26 07:16:29 -05:00
Azalea
054b286388 [F] Fix build 2024-12-26 06:45:27 -05:00
Azalea
add1e02d2f [O] riik Chuni upsert all 2024-12-26 06:44:27 -05:00
Azalea
fd44744029 [O] riik Get cmission handler 2024-12-26 06:43:41 -05:00
Azalea
6844e1b435 [O] riik UpsertUserAll model 2024-12-26 06:42:27 -05:00
Azalea
8140380673 [O] riik 2024-12-26 06:41:58 -05:00
Azalea
66ad9e8856 [O] Remove coderesp 2024-12-26 06:41:28 -05:00
Azalea
da467ec8ee [M] riik CMission repos 2024-12-26 06:38:45 -05:00
Azalea
f0923c51e6 [F] Fix trailing } 2024-12-26 02:43:25 -05:00
Azalea
d1953e792a [+] Show fc, ap 2024-12-26 02:42:19 -05:00
Azalea
b3294eed68 [+] Recreated assets 2024-12-26 02:30:51 -05:00
Azalea
37946c5aba [+] Helpful message for unsupported games 2024-12-25 16:31:01 -05:00
Azalea
ce80f65e9f [F] Fix avatar saving 2024-12-23 20:16:59 -05:00
Azalea
4dce42b85f [M] Migrate to svelte 5 routing 2024-12-23 19:51:34 -05:00
Azalea
88702085bb [-] Remove unnecessary showOpenFilePicker library 2024-12-23 19:49:31 -05:00
Azalea
2719522e07 [-] Remove unused rating page 2024-12-23 19:23:47 -05:00
Azalea
dd573945ed [F] Fix build warnings 2024-12-23 18:56:44 -05:00
Azalea
9cffb19332 [M] Migrate usage 2024-12-23 18:32:20 -05:00
Azalea
6631bb593c [U] Migrate to svelte 5, switch to bun 2024-12-23 18:32:02 -05:00
Azalea
f5959925aa [O] Handle ranking with binary search 2024-12-23 18:00:06 -05:00
Azalea
5b20cb316b [O] Pre-compute ranking cache 2024-12-23 15:58:02 -05:00
Azalea
8cb7ff8ed4 [+] Diff tool 2024-12-23 15:57:06 -05:00
Azalea
4bcf1f2d9e [F] Fix inconsistencies 2024-12-20 11:18:03 -05:00
Azalea
452b077822 [F] Fix nullable 2024-12-20 10:04:48 -05:00
Azalea
f37a32ceab [-] Remove unused functions 2024-12-20 09:32:16 -05:00
Azalea
7182514a64 [M] Rename var 2024-12-20 09:29:48 -05:00
Azalea
da60131051 [O] Refactor 2024-12-20 09:27:50 -05:00
Azalea
aa9804d2df [+] User data handlers 2024-12-20 09:00:13 -05:00
Azalea
10c1b9bc29 [M] Static endpoints 2024-12-20 08:42:43 -05:00
Azalea
698422a41e [-] Collapse more 2024-12-20 07:29:39 -05:00
Azalea
ac16f40303 [O] Collapse boring handlers 2024-12-20 07:14:14 -05:00
Azalea
e41bdecd5b [O] Better chusan handling 2024-12-20 07:01:19 -05:00
Azalea
c9a0a8d2b5 [F] Fix matching bad for real for real 2024-12-20 07:00:32 -05:00
Azalea
c308940c4b [F] Fix matching bad for real 2024-12-20 06:57:48 -05:00
Azalea
99770ccd2f [F] Fix matching bad??? 2024-12-20 06:54:40 -05:00
Azalea
2d4bb90acc [F] Fix chusan duplicate key 2024-12-20 06:25:41 -05:00
Azalea
e78d80b99d [-] Remove json property order? 2024-12-20 05:21:08 -05:00
Azalea
9f5cd6dc88 [M] Migrate chusan user data (NEED TESTING) 2024-12-20 05:12:44 -05:00
Azalea
85c0b670da [F] Fix lastClientId null 2024-12-20 04:53:48 -05:00
Azalea
813ec7d294 [O] Migrate sass import 2024-12-20 04:37:51 -05:00
Azalea
d66eb239fa [F] Fix table 2024-12-20 04:25:39 -05:00
Azalea
9fcc46b5d5 [O] Common superclass for chusan user entities
Co-Authored-By: 凌莞~(=^▽^=) <opensource@c5y.moe>
2024-12-20 03:34:04 -05:00
Azalea
3ebf8a2061 [F] 263 should be 264
Co-Authored-By: 云 <i@muir.fun>

#96
2024-12-20 01:29:01 -05:00
Azalea
de98085e84 [U] Update readme 2024-12-20 14:07:45 +08:00
2557b55817 [+] Chusan Luminous Plus A072-A112 (#96) 2024-12-20 14:03:21 +08:00
Azalea
128706e8a1 [O] Switch to bun 2024-12-17 21:46:34 -05:00
Azalea
d854d8ae0b [F] Fix userbox 2024-12-17 20:36:01 -05:00
Azalea
637191836a [F} Fix dropdown white on white 2024-12-17 20:35:44 -05:00
Azalea
69ab9d96f7 [F] Fix userbox 2024-12-17 20:09:48 -05:00
Azalea
073febe24a [+] Remove classes, migration guide 2024-12-17 20:09:26 -05:00
Azalea
f01a4fcfac [+] UserBox endpoints 2024-12-17 15:28:28 -05:00
Azalea
4f81a4e9b4 Merge branch 'metrics' into v1-dev 2024-12-12 17:07:55 -05:00
Azalea
1a06033964 [+] Hide allnet port 2024-12-12 17:06:32 -05:00
Menci
c5dad11e5e [+] Metrics (#95)
* Add actuator and micrometer

* update

* [-] Remove unused import

* [O] Make code less verbose

* format

* refactor

---------

Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>
2024-12-13 05:47:31 +08:00
Menci
5ed89754b3 refactor 2024-12-12 02:33:14 +08:00
Menci
ee88be613c format 2024-12-11 11:11:17 +08:00
Azalea
ebafb4c05e [O] Make code less verbose 2024-12-10 21:56:15 -05:00
Azalea
70466d0c94 [-] Remove unused import 2024-12-10 19:13:40 -05:00
Menci
340003c568 update 2024-12-10 23:30:30 +08:00
Menci
db5343fba3 Add actuator and micrometer 2024-12-07 02:32:48 +08:00
akatki
8434842c65 [F] AimeDB Felica Lookup v2 rename package parameter (#94)
* [F] AimeDB Felica Lookup v2 rename package parameter

* [F] AimeDB Felica Lookup v2 rename package parameter
2024-12-01 11:39:51 +08:00
Clansty
2482881117 [RF] AquaMai moved to new repo 2024-11-30 15:18:08 +08:00
Clansty
4afe2160e1 [F] CI after moving assets 2024-11-30 14:45:47 +08:00
Menci
6225390b7f [F] Set ForceAsSserver to default ON (#92)
* Set ForceAsSserver to default ON

* work as origin

---------

Co-authored-by: Clansty <i@gao4.pw>
2024-11-30 14:43:25 +08:00
凌莞~(=^▽^=)
d5a9c98ff9 [O] ResetTouchAfterTrack -> ResetTouch, add press key to reset (#93)
* [O] ResetTouchAfterTrack -> ResetTouch, add press key to reset

* fix

* update

* fix: Remove not work

---------

Co-authored-by: Menci <mencici@msn.com>
2024-11-30 05:29:30 +08:00
Clansty
bed1b85319 [+] LogUnity 2024-11-29 11:58:50 +08:00
Clansty
8a728ad28a [F] JudgeAccuracyInfo crashes demo 2024-11-29 05:43:41 +08:00
Clansty
c42f17c96e [+] Bump version 2024-11-27 03:49:27 +08:00
Clansty
054352356b [O] Locale 2024-11-27 03:35:01 +08:00
Menci
2646f642b5 [+] AquaMai.Config.ApiVersion (#91)
* Add ApiVersion

* Fix SectionNameOrder
2024-11-27 00:19:37 +08:00
Clansty
436bdde60a [O] Do not reset touch panel when quick retry 2024-11-26 21:22:18 +08:00
Clansty
07210a23b7 [F] unused lockCredits 2024-11-26 05:53:20 +08:00
Clansty
da36ef4002 [O] enforce type for SetEntryValue and some comment and type chore 2024-11-26 05:14:58 +08:00
Menci
e3b06b110f [+] LogNetworkRequests (#90)
* It doesn't work...

* Implement

* rename
2024-11-26 00:03:50 +08:00
Clansty
792dce6843 [+] Add some interfaces for attributes 2024-11-26 00:03:35 +08:00
Clansty
0ec048ceba [F] LoadFromPacked Occupying file 2024-11-25 22:32:50 +08:00
Menci
07631e9b02 [F] AquaMai upload task shouldn't run on PR builds 2024-11-25 18:32:48 +08:00
Clansty
4834363fb5 [+] Add LatestVersion to interface 2024-11-25 04:04:04 +08:00
Menci
734dbfb761 [F] Fix AquaMai CI after config refactor (#89)
Message from previous commit:

[RF] AquaMai configuration refactor (#82)

更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用)
旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们

Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default)
Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
2024-11-25 02:15:04 +08:00
Menci
37044dae01 [RF] AquaMai configuration refactor (#82)
更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用)
旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们

Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default)
Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
2024-11-25 01:25:19 +08:00
Clansty
e9ee31b22a [F] 0.10000000149011612 2024-11-22 18:18:48 +08:00
Azalea
cf0e3ce989 [F] Should be card id, not game user id 2024-11-21 12:41:24 -05:00
Azalea
27664164fa [F] Fix ranking reindex 2024-11-21 12:34:59 -05:00
Azalea
b7360c426b [F] Fix encountMapNpcList runtime error 2024-11-21 12:28:09 -05:00
Azalea
0e176d5608 [F] Fix warnings 2024-11-21 12:26:36 -05:00
Azalea
a947a81772 [F] oops forgor 2024-11-21 12:16:17 -05:00
Azalea
bbb4185fac Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-11-21 12:12:09 -05:00
Azalea
e34f0587fe [O] Optimize ranking 2024-11-21 12:10:44 -05:00
Clansty
0e02dd660c [F] IconLoader 2024-11-22 00:59:25 +08:00
Azalea
2376e511ac [+] DB migration for ranking optimization 2024-11-21 11:39:36 -05:00
Azalea
56bf447cdb [M] Move opt out leaderboard to correct place 2024-11-21 11:39:23 -05:00
Azalea
5ebb1718d6 [-] Remove SQLite migrations 2024-11-21 10:49:21 -05:00
Clansty
9143b92932 [F] BasicFix 2024-11-21 18:47:56 +08:00
Azalea
fbff4a8cb1 [O] Optimize chu3 user data 2024-11-21 01:51:29 -05:00
Azalea
b02371e4c3 [O] Optimize auto-ban 2024-11-21 01:49:45 -05:00
Azalea
e32a2bbe81 [F] Fix hibernate enhance compilation 2024-11-20 22:29:45 -05:00
Azalea
10ebd61519 [+] Kotlin Hibernate enhance 2024-11-20 22:25:48 -05:00
Clansty
7ac90891ca [F] should hook getter 2024-11-21 01:53:12 +08:00
Clansty
711c18a7f1 [+] ForceIgnoreError 2024-11-20 04:06:15 +08:00
Clansty
b3cb08316a [+] ForceNonTarget 2024-11-20 02:46:41 +08:00
Clansty
42b8b9ce4a [O] RemoveEncryption should default true 2024-11-20 02:25:16 +08:00
村場 榞彦
fc4834ebd6 [+] Support More Custom Png Assets (#85)
素材放置于AssetBundleImages,支持姓名框背景板旅行伙伴,Icon仍在修,navichara和PartnerResult在写
2024-11-20 02:16:22 +08:00
Clansty
24ab79a09a [O] Comments 2024-11-20 02:08:47 +08:00
Clansty
786a8832d3 [+] Make it configurable 2024-11-19 01:38:29 +08:00
Minepig
4d25b6a43c Accuracy info and other features (#84)
* tweaks slide fade in

* judge accuracy info

* Update SlideArrowAnimation.cs
2024-11-19 01:32:18 +08:00
Clansty
29bb54d2cc [O] Show quick skip after 1sec 2024-11-16 18:42:10 +08:00
Clansty
cd075a3559 [F] RatingUpWhenSSSp Algo 2024-11-16 01:47:07 +08:00
Clansty
0455a83ef1 [+] Play Count in SelectionDetail 2024-11-16 00:53:22 +08:00
Clansty
6c5791b1fe [F] UpsertUserAll Crash with usernames longer than 8 chars 2024-11-14 22:58:20 +08:00
Clansty
705b6cc03d [F] Maybe fixed some PractiseMode problems 2024-11-14 22:52:40 +08:00
Clansty
b190e54285 [+] TouchResetAfterTrack 2024-11-14 22:52:21 +08:00
Clansty
478db15211 [F] Practice mode crash on AdvDemoProcess 2024-11-06 13:58:07 +08:00
Clansty
1542f3811d [F] Skip button background 2024-11-06 12:10:20 +08:00
Minepig
85dd8029af [+] Slide related visual feature (#81)
New Features:
1. Invert the Slide hierarchy
2. Slide Track shrinking animation

Changes:
1. Improve the visual effect of Break-Slide judge blink
2. `DisableTrackStartTabs` now also hide user's best achievement
2024-11-06 11:54:53 +08:00
Clansty
11beb6676e [+] Show alert in CI builds 2024-11-06 11:40:20 +08:00
Clansty
99d7fe5ca2 [F] SQL 2024-11-04 20:42:37 +08:00
Clansty
248c1ce189 [+] optOutOfLeaderboard 2024-11-04 20:32:50 +08:00
bf972681d5 [+] CardMaker 1.39 support (#79) 2024-11-04 19:18:56 +08:00
Clansty
996632ac73 [O] Allow change maimai name to chinese characters in settings 2024-11-04 19:09:48 +08:00
Clansty
b28a1986c9 [O] Disallow using card/summary to query others' card 2024-11-04 19:00:44 +08:00
Clansty
fb96e93184 [+] Configurable mod key map manager 2024-11-01 17:03:57 +08:00
Clansty
ac4db91df4 [F] userActivityList not unique error 2024-10-31 01:30:18 +08:00
Clansty
408845878b [+] Increase version code 2024-10-30 07:27:54 +08:00
Clansty
7933d49bb2 [RF] Move some patches to visual 2024-10-28 06:09:42 +08:00
Clansty
6945032077 [RF] Move sub-config classes to their own directory 2024-10-28 05:56:54 +08:00
Clansty
0af137ba8c [+] Add config entries for new patches (#77) 2024-10-28 05:43:55 +08:00
Clansty
de3d376063 [F] SinglePlayer compatibility with 1.09.00 2024-10-28 00:04:43 +08:00
Clansty
36da872932 [O] Locale 2024-10-27 23:56:26 +08:00
Clansty
ff2ed50dea [+] Mark supported game versions with attributes 2024-10-27 23:34:41 +08:00
Clansty
6bb2685e03 [+] Generate example config via attributes 2024-10-27 22:44:12 +08:00
Clansty
5eb0424ee7 [F] Bot renderer crash when b35 or b15 is empty 2024-10-27 04:32:02 +08:00
Menci
80536ef4fb [+] New APIs 2024-10-26 02:24:42 +08:00
Clansty
f3bebc6fa2 [F] Fody should merge System.Numerics 2024-10-25 22:16:08 +08:00
Minepig
d0bb3cc75c [+] Slide code support & split multiple patches (#77)
* 功能拆分

将不同的功能分拆到不同文件

* Slide code notation support

This is part of Maimai DX 2077 patch set.
New MA2 commands: NMSSS, BRSSS, EXSSS, BXSSS, CNSSS
2024-10-25 20:42:08 +08:00
WYH2004
98213cff67 [+] ShowErrorLog (#74)
* [+] ShowErrorLog

* [O] Fixed spelling errors in method names
2024-10-25 20:20:26 +08:00
Clansty
c074de5876 [+] SinglePlayer support legacy game versions 2024-10-25 00:45:12 +08:00
Clansty
906bdfa15e [+] GUI style 2024-10-24 02:12:36 +08:00
Clansty
e844164cf6 [+] Splash+ support 2024-10-24 01:15:55 +08:00
Clansty
1b47bfa2f1 [F] Unable to save in lower versions 2024-10-24 00:27:04 +08:00
Azalea
33997c9a82 Merge pull request #69 from Becods/v1-dev
[+] Maimai DX Buddies Plus I011
2024-10-22 06:21:55 -07:00
Azalea
4713a44573 [F] Fix #68 2024-10-22 09:20:33 -04:00
Clansty
be7b0945e9 [+] Increase version code 2024-10-22 00:19:43 +08:00
Clansty
0f1bfc5a17 [+] TouchPanelBaudRate 2024-10-22 00:19:12 +08:00
Clansty
3bc9f1382c [O] Hide skip button after click 2024-10-19 19:56:47 +08:00
Menci
a08e93d975 [F] Refactor CustomCameraId and remove hard-coded enums / IDs (#71)
* refactor

* Add PrintCameraList check

* Separate PrintCameraList to a class

* cleanup
2024-10-19 01:34:53 +08:00
Clansty
91a120599f [F] Repeat resets speed 2024-10-17 16:52:46 +08:00
Becods
5d399b2497 [+] Maimai DX Buddies Plus I011 2024-10-17 11:11:27 +08:00
WYH2004
0cab18b9b5 [+] CustomCameraId (#66)
* [+] CustomCameraId

* [F] Map CameraType to the correct Camera IDs using enum

* [+] GameInfo Utils

* [+] CustomCameraId Add ChimeCamera Support

* [+] Decide whether to print a CameraList based on the Config
2024-10-17 02:26:44 +08:00
Clansty
903da8732d [+] keep note speed when changed speed 2024-10-16 19:23:32 +08:00
Clansty
6857ae5182 [F] CurrentPlayMsec conflict with speed settings 2024-10-16 18:59:31 +08:00
Clansty
5bcbffcdf0 [F] Notes shift after set speed 2024-10-16 18:41:07 +08:00
Clansty
953083a0bf [F] Seek resets speed 2024-10-16 18:11:17 +08:00
Clansty
1810bbe2d5 [+] QuickEndPlay button when notes play end 2024-10-16 01:05:04 +08:00
Azalea
f716ab0c1b Merge pull request #67 from Becods/v1-dev
[+] Chusan Luminous Plus Events
2024-10-15 06:05:39 -04:00
Clansty
e04e5596a3 [+] IgnoreAimeServerError 2024-10-15 17:05:55 +08:00
Becods
f239d498ad [+] Chusan Luminous Plus A071 2024-10-15 10:35:05 +08:00
Becods
7b768b5b5b [+] Chusan Luminous Plus A000 to A001 2024-10-15 10:06:45 +08:00
Azalea
60813274dc [F] Forgor to login :( 2024-10-14 15:27:01 +08:00
Azalea
ad5bc4fc0d [+] Add ghcr.io repo as well 2024-10-14 15:26:02 +08:00
Azalea
c1c6949175 [+] ARM support? 2024-10-14 15:20:02 +08:00
Azalea
26840700ee [-] Revert docker-compose to using pre-built image 2024-10-14 15:11:52 +08:00
Azalea
ec610de266 [PR] #52 from istareatscreens/add-arm-support
Change docker-compose to support ARM
2024-10-14 03:10:49 -04:00
Azalea
f4129ff5c2 [+] Docker image action 2024-10-14 15:08:48 +08:00
Menci
b8cc6d9809 CI (#62) 2024-10-14 02:05:23 +08:00
Menci
9384d1d96f [+] Bypass Cake.dll hash check and SSL pinning (#63)
* Bypass Cake.dll hash check and SSL pinning

* Move to BasicFix
2024-10-14 02:04:26 +08:00
Menci
854b6b76a0 Fix headphone volume not set with SkipToMusicSelection (#65) 2024-10-14 02:02:58 +08:00
Menci
bc836e973c [F] Fix reflect invoking arguments in Shim (#64) 2024-10-14 01:06:15 +08:00
Clansty
bf9855abd1 [O] better _isPlaying detection 2024-10-13 23:54:53 +08:00
WYH2004
4006438d93 [+] TouchToButtonInput On Aquamai (#58)
Co-authored-by: Clansty <i@gao4.pw>
2024-10-13 23:36:28 +08:00
Menci
cdfb86e021 [+] Support official quick retry (3456) in UX/QuickSkip
Merge pull request #60

* Official quick retry
2024-10-13 21:45:14 +08:00
Menci
81e0232712 [O] Support SDGA and other game version in one binary
Merge pull request #61

* Merge targets

* Merge branch 'v1-dev' into fork/Menci/merge-targets

* [O] Move Shim to AquaMai.Helpers

---------

Co-authored-by: Clansty <i@gao4.pw>
2024-10-13 21:38:28 +08:00
Menci
e67b68aa20 [O] Refactor AquaMai.csproj to SDK style (#59)
* SDK style

* Update CI

* remove extra code

* [F] CI build and add CI for PR

* [F] Assembly version info

* [F] Do not generate satellite assembly for locale

---------

Co-authored-by: Clansty <i@gao4.pw>
2024-10-13 20:25:25 +08:00
Clansty
a075de4711 [+] Increase version code 2024-10-08 17:02:25 +08:00
Clansty
6d782352f7 [F] Compatibility for modified package 2024-10-07 23:29:59 +08:00
凌莞~(=^▽^=)
587993c957 Merge pull request #56 from shirokosunaookami/patch-1
Fix QuickRetry when use io4 firmware
2024-10-06 20:59:54 +08:00
shirokosunaookami
aaca3e65ce Fix QuickRetry when use io4 firmware 2024-10-06 20:57:49 +08:00
Azalea
ce53acdacf [F] Fix bound must be greater than origin 2024-10-05 05:51:55 -04:00
Azalea
a449bac130 [U] Update usage 2024-10-05 05:36:53 -04:00
Azalea
060bd32417 [F] Fix docstring 2024-10-05 05:36:28 -04:00
Azalea
08a1595d3e Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-10-05 05:35:53 -04:00
Azalea
b88c56b67a [F] Fix migrate card setting null for non-null field 2024-10-05 05:35:48 -04:00
Clansty
7deb395fd9 [+] Fix level display everywhere 2024-10-05 00:20:07 +08:00
Clansty
2ef104224b [O] make image same in two monitors 2024-10-04 23:07:44 +08:00
Clansty
d84b2f3870 [+] CustomLogo 2024-10-04 22:45:48 +08:00
Clansty
7e5467935b [+] HanabiFix + HideHanabi 2024-10-04 20:48:06 +08:00
Clansty
f7c1714cb8 [+] config file entry 2024-10-04 20:19:51 +08:00
Clansty
12724cea56 [+] Add options 2024-10-04 19:38:02 +08:00
凌莞~(=^▽^=)
2cea66cba5 Merge pull request #55
Customize note skin (just like MajdataView) &  several patches
2024-10-04 19:23:55 +08:00
凌莞~(=^▽^=)
ac01469eac Merge branch 'v1-dev' into v1-dev 2024-10-04 19:23:16 +08:00
Clansty
cb4cc4e7d9 [F] PractiseMode 2024-10-04 16:12:14 +08:00
Minepig
c648493a9e 自定义皮肤功能 2024-10-04 13:47:07 +08:00
Minepig
a36da6ebde 修复了闲置播放紫谱忘记改touch速度的问题 2024-10-04 13:45:50 +08:00
Minepig
4fd2fc7e00 个人录制谱面确认时用得到的patch 2024-10-04 13:44:51 +08:00
Minepig
39646732b6 增加了BreakSlide判定闪烁、圆弧Slide判定对齐、Wifi判定区分上下 2024-10-04 13:44:20 +08:00
Minepig
8791507aca 修正了Slide在AutoPlay时的轨迹箭头显示 2024-10-04 13:42:53 +08:00
Minepig
c173b2a230 修复连锁Slide的读取逻辑(刹那旅程爆机bug) 2024-10-04 13:41:48 +08:00
Clansty
05d2df623e [O] update config example 2024-10-04 00:26:54 +08:00
Clansty
e75a1fcd12 [+] PracticeMode config entry 2024-10-03 20:51:42 +08:00
Clansty
529165f2b5 [F] Fix text shadow a little bit 2024-10-03 20:02:10 +08:00
Clansty
9a5743a27e [+] Show play time on PractiseModeUI 2024-10-03 17:05:00 +08:00
Clansty
271ef9bf00 [F] SinglePlayer without remove mask 2024-10-03 16:40:51 +08:00
Clansty
3a97f7645e [O] Check object with maybeUserMusicList 2024-10-03 16:06:32 +08:00
Clansty
a84bf9efef [+] Import from a userMusicDetailList Json 2024-10-03 15:51:18 +08:00
Azalea
daa5129f65 [O] Make a non-callback version of cardByName 2024-10-03 02:47:20 -04:00
Clansty
ac375abf5e [+] Some font features 2024-10-02 23:28:33 +08:00
Clansty
bae5a7c838 [O] Make HideMask separate component 2024-10-02 19:03:30 +08:00
Clansty
1bcb7210c6 [+] Custom Keymap 2024-10-02 14:41:20 +08:00
Clansty
c15dcf6b98 [+] FrameRate lock + display 2024-10-02 00:52:37 +08:00
Clansty
9ead7a413e [+] able to switch off WindowState 2024-10-01 15:50:00 +08:00
Clansty
07817b04fb [F] SelectionDetail window size 2024-10-01 01:10:16 +08:00
Clansty
e65d67f12e [+] Network error reason 2024-10-01 01:00:32 +08:00
Clansty
e39f013808 [+] Show tip when saving is done 2024-10-01 00:15:55 +08:00
Clansty
c34affc215 [F] Maybe fix practise mode 2024-09-30 14:21:10 +08:00
Clansty
07b8cc04be [+] i18n 2024-09-30 01:26:54 +08:00
Clansty
78a396ce4b [F] SelectionDetail Font size
[RF] Move SelectionDetail to Utils
[RF] Remove UrGui
[RF] Refactor SelectionDetail
2024-09-29 23:12:22 +08:00
Clansty
43997f2215 [+] Increase version code 2024-09-29 13:05:51 +08:00
Clansty
e9bac0a737 [O] Disable MipMap 2024-09-29 13:03:54 +08:00
Clansty
e7c69d2a6b [+] PractiseModeUI 2024-09-28 23:06:06 +08:00
Clansty
8c3400ee41 [O] better get player 2024-09-28 19:42:42 +08:00
Clansty
3d79c939e9 [+] Speed change 2024-09-28 17:35:59 +08:00
Clansty
27b8e6bd21 [+] PractiseMode Debugging 2024-09-28 16:30:19 +08:00
Clansty
24ecaab570 [O] better polyfill load 2024-09-28 15:31:58 +08:00
Clansty
74e39c437d [+] Window Magic! 2024-09-27 21:50:27 +08:00
Clansty
c1c7788cd3 [O] Auto detect if DebugFeature is need to be patched 2024-09-27 20:18:53 +08:00
Clansty
a2db465825 [+] Skip track start screen 2024-09-27 19:33:37 +08:00
Clansty
9605264b9a [RF] Move some settings to TimeSavingConfig 2024-09-27 19:19:42 +08:00
Clansty
24e6808984 [+] seeking in DebugFeature 2024-09-27 18:59:27 +08:00
Clansty
0d9c7a4cc2 [F] Default string value 2024-09-24 20:18:10 +08:00
Azalea
3c6ecf1563 [+] Add cat 🐱 2024-09-23 15:57:55 -04:00
Clansty
5c634d6ff9 [+] Telegram and QQ join link 2024-09-22 19:41:47 +08:00
Clansty
6b51155bac [F] Remove debug log 2024-09-20 02:36:48 +08:00
Clansty
1873ad8355 [+] Increase version code 2024-09-17 11:21:11 +08:00
Clansty
8087396188 [+] TouchSensitivity 2024-09-17 06:25:52 +08:00
Clansty
5128db9f6c [+] Global judge adjust 2024-09-17 04:13:51 +08:00
Clansty
ef832461c0 [O] Change target name 2024-09-17 03:26:33 +08:00
Clansty
85493cdfd8 [+] Write example config when config not found 2024-09-17 03:24:35 +08:00
Clansty
e557f1361d [+] Error notice 2024-09-17 02:58:37 +08:00
Clansty
9598ac5a50 [F] Crash with the new modified SDEZ145 DLL with deleted methods 2024-09-17 02:48:07 +08:00
Clansty
5ee7add355 [+] Force Paid play 2024-09-17 02:34:38 +08:00
Clansty
81c1e6e887 [+] Increase version code 2024-09-12 21:56:23 +08:00
Clansty
b7004b3866 [+] Extend notes pool to support some odd charts 2024-09-12 21:54:37 +08:00
Clansty
5341326811 [O] Coming soon! -> Coming soon™ 2024-09-11 01:57:17 +08:00
Clansty
776c08e605 [F] RemoveEncryption error with SDEZ 2024-09-11 01:35:17 +08:00
Clansty
b7c5d18df1 [+] Log user ID on login 2024-09-11 00:56:15 +08:00
Clansty
39dc6c576a [F] Nested types patches without enable 2024-09-11 00:38:13 +08:00
Clansty
d9fc262003 [F] Nested types patches without enable 2024-09-11 00:26:45 +08:00
Clansty
9b7f2b3a79 fix: Level display shift when level number not match display level 2024-09-08 17:09:38 +08:00
Clansty
1ad4ac2d63 [+] SelfMadeChartsWhiteListUsers 2024-09-07 22:17:01 +08:00
Clansty
9ca7949bf0 [+] Globally disable self made charts 2024-09-07 22:11:01 +08:00
Clansty
d2174364b2 [F] reset HideSelfMadeCharts when enter login screen 2024-09-07 21:27:48 +08:00
Clansty
91238c3a9c [+] Increase version code 2024-09-07 00:22:43 +08:00
Clansty
d32c8c999b [O] more accurate genreName and versionName for SelectionDetail 2024-09-06 23:24:44 +08:00
Clansty
6580b78485 [+] Load png TabTitle 2024-09-06 23:15:53 +08:00
Clansty
0eec8dea05 [+] Support 2p SelectionDetail 2024-09-06 17:34:43 +08:00
Clansty
8fa356242e [+] Port SelectionDetail 2024-09-06 16:41:29 +08:00
istareatscreens
a13611f601 Change docker-compose to support ARM 2024-09-05 08:33:38 -04:00
Clansty
e8307cdcd9 [+] toggle the display of self-made charts 2024-09-05 02:31:07 +08:00
Clansty
ca425cf949 [+] Increase version code 2024-09-04 18:54:27 +08:00
Clansty
9f57d393bf [F] LoadJacketPng and RandomBgm crash when LocalAssets not exists 2024-09-04 15:32:11 +08:00
Clansty
84c59e2c8b [+] Support all Melonloader versions 2024-09-04 15:12:32 +08:00
Clansty
212f60db60 [+] Increase version code 2024-09-03 01:11:01 +08:00
Clansty
fbbdb056d7 [O] Retry attempts of action 2024-09-02 23:50:05 +08:00
Clansty
e3c0fe5e78 [+] Also enable shop name display in SDGA when CustomPlaceName is set 2024-09-02 23:15:25 +08:00
Clansty
a3afb1a2b8 [+] Custom shop name in photo 2024-09-02 23:02:47 +08:00
Clansty
ac94b6d917 [F] Uppercase extension 2024-09-02 21:45:47 +08:00
Clansty
8db9580ff5 [+] Restore AutoPlay(Home) and Pause(Enter) for SDGA 2024-09-02 17:35:31 +08:00
Clansty
489c00ebb0 [+] Load Jacket from A???\AssetBundleImages\jacket\*.png 2024-09-02 17:06:34 +08:00
Clansty
d58fe84439 [+] Make ForceAsServer and ForceFreePlay individual components 2024-08-22 20:17:26 +08:00
Clansty
ffe3843747 [i18n] Add zh version of config toml 2024-08-22 02:16:38 +08:00
Clansty
b370af3c19 [F] Remove debug logs 2024-08-22 01:49:41 +08:00
Clansty
cdd3c81bdc [+] Prevent accidental touch of the Test button 2024-08-20 00:36:41 +08:00
Clansty
eb72839e2b [+] trigger QuickSkip with service key 2024-08-20 00:00:00 +08:00
Clansty
6457cedd9b [+] CI build for AquaMai 2024-08-16 19:23:12 +08:00
凌莞~(=^▽^=)
be72ea0c98 Merge pull request #50 from Becods/v1-dev
The play count should be the PC count not the TRACK count.
2024-08-12 14:50:43 +08:00
bf5691bdb6 [F] The play count should be the PC count not the TRACK count. 2024-08-12 12:45:20 +08:00
Clansty
a6a8734599 [+] Setting of score rounding and fix bugs 2024-08-08 14:49:20 +08:00
Clansty
d0aecc76ed [O] Move general game settings to general tab 2024-08-08 13:50:14 +08:00
Azalea
3b80b8d7f1 [F] Fix wrong implementation in GetUserMapAreaApi
Thanks rinsama for the patch
2024-08-08 00:31:18 -04:00
Clansty
c11bb3be59 [F] Rival may lead to unable to login the game 2024-08-07 19:46:35 +08:00
Clansty
7ee4c14fae [+] API for bot to ban user from ranking board 2024-08-06 15:13:54 +08:00
Clansty
17a0209c8c [F] Chuni rating display 2024-08-05 23:34:02 +08:00
Clansty
fc10c05731 [F] export api and game api leaking lastClientId 2024-08-05 21:36:20 +08:00
Clansty
9ef0d0edfb [F] mai2: Error when rival list empty 2024-08-05 21:15:32 +08:00
Clansty
473f4a4295 [+] mai2: support adding rival 2024-08-05 20:37:18 +08:00
Clansty
94ba1f0b09 [+] AquaMai: Add CalcSpecialNum 2024-08-05 20:37:07 +08:00
Clansty
8903fa268a [+] CheckServerHash 2024-08-04 12:27:19 +08:00
Clansty
6ad980d471 [F] Encrypt and Decrypt hook run without enabled in config 2024-08-04 12:26:48 +08:00
Clansty
9a6e9c4660 [+] Some API endpoints for bot query 2024-08-02 08:51:24 +08:00
Clansty
f7c842774b [+] Return AquaNet profile photo as maimai userPortrait 2024-08-01 13:34:54 +08:00
Clansty
fde952fcd9 [+] Add option to disable the legacy aquaviewer api 2024-08-01 09:38:36 +08:00
Clansty
a71c2bd8ec [+] Import player data, tested with maimai 2024-08-01 08:41:11 +08:00
Clansty
7c4f887ef4 [+] Export maimai userdata 2024-08-01 06:56:31 +08:00
Clansty
b32b0e970c [+] Add change name for maimai and refactor settings page 2024-07-31 09:03:26 +08:00
Clansty
836f789fc9 [+] Only show UserBox if user played chuni 2024-07-31 07:06:16 +08:00
凌莞~(=^▽^=)
6b71e2f22a Merge pull request #42 from alexay7/usebox
Added UserBox page (Chunithm)
2024-07-31 05:20:43 +08:00
凌莞~(=^▽^=)
1fa83d3f8f [+] Add SDGA 1.45 support in README 2024-07-30 01:00:33 +08:00
Clansty
247f8f132b [F] Some compatability for 2p mode 2024-07-30 00:39:58 +08:00
Clansty
3fcdf38d4a [F] Game crash on ResultProcess when login as guest 2024-07-29 23:59:30 +08:00
Clansty
0626d1c466 [F] Crash when playing tutorial 2024-07-29 23:38:47 +08:00
Clansty
f0da7c6300 [F] Wrong SQL syntax 2024-07-29 23:08:10 +08:00
Azalea
2554478a38 Merge pull request #47 from Becods/v1-dev
[+] Chusan Luminous A151 to A181
2024-07-29 07:22:28 -07:00
Clansty
d7f24759d8 [+] Allow use any characters in maimai name 2024-07-29 10:25:15 +08:00
Clansty
df3bd6fbec [+] Allow register-card via http 2024-07-29 06:18:53 +08:00
Clansty
11ab81a484 [F] Upsert error when isNewFavoritemusicList is Null 2024-07-29 06:15:55 +08:00
Becods
91e7a092c4 [+] Chusan Luminous A151 to A181 2024-07-29 05:07:23 +08:00
alexay7
bca5130020 Modify way to get user luid 2024-07-28 03:45:22 +02:00
Clansty
153029abdd [+] Add Character all unlock 2024-07-28 06:02:19 +08:00
Clansty
03ed3f13f4 [+] Support setting favourite music 2024-07-28 00:56:18 +08:00
Clansty
8c7fd78bd4 [+] Support setting favourite items 2024-07-27 23:58:12 +08:00
Clansty
a813535e3f [+] Fix DebugInput (MouseTouchPanel) 2024-07-27 05:47:45 +08:00
Clansty
7cae5f8f10 [+] Add 6 new APIs 2024-07-27 00:37:47 +08:00
Clansty
070c19d784 [+] GetGameWeeklyDataApi 2024-07-26 23:59:01 +08:00
Clansty
0833cd8a9b [+] Basic fix 2024-07-26 23:24:42 +08:00
Clansty
64f3a2db58 [F] Build for SDGA1.45 2024-07-26 22:55:53 +08:00
Azalea
ad8a425d30 Merge pull request #46 from Becods/v1-dev
Player names should allow the use of hiragana and katakana.
2024-07-20 17:29:30 +08:00
40d5c8d79f [F] Player names should allow the use of hiragana and katakana 2024-07-18 20:16:03 +08:00
Clansty
c6e471323f [+] Fixes required to run SDGA 2024-07-09 18:11:06 +08:00
Clansty
60a0c8726e [O] Move SkipVersionCheck to Fix 2024-07-09 16:30:30 +08:00
Clansty
5772ff78e6 [+] Allow login with higher data version 2024-07-09 16:18:54 +08:00
Clansty
222ed29b6c [F] SDGA Support 2024-07-09 16:18:22 +08:00
Azalea
23870523fb Merge pull request #45 from yuuz233/patch-1
Update game_specific_notes.md
2024-06-27 16:02:08 +08:00
yuzusoft
b1a1d36b66 Update game_specific_notes.md
Latest doc for latest tested working support of game
2024-06-27 10:24:02 +08:00
Clansty
2946c51774 [+] Use the png jacket as bga 2024-06-26 03:11:56 +08:00
Clansty
8b72214780 [+] Load jacket with higher resolution 2024-06-19 21:12:29 +08:00
Clansty
0f701ad2d3 [+] Unlock Utage 2024-06-19 20:57:32 +08:00
Clansty
d686c48a0b [+] Save immediate after playing a song 2024-06-16 17:58:20 +08:00
Clansty
80555f9c96 [F] B50 algorithm 2024-06-15 01:47:18 +08:00
alexay7
ec1155b1ba Merge branch 'hykilpikonna:v1-dev' into usebox 2024-06-06 23:54:35 +02:00
alexay7
7377386ee2 Fix layout breaks when options are large 2024-06-05 19:43:40 +02:00
alexay7
f4bb1101bf Added UserBox page 2024-06-05 18:57:48 +02:00
Azalea
e44188b830 Merge pull request #40 from alexay7/v1-dev
Filter days with no plays from heatmap
2024-06-05 12:28:00 +08:00
alexay7
c5d81afdf6 Filter days with no plays from heatmap 2024-06-05 02:46:29 +02:00
Clansty
f6d55fec35 [+] Skip "Discovered new area" 2024-05-26 13:36:56 +08:00
Clansty
2ef8219f15 [F] Skip Event Info 2024-05-26 13:21:03 +08:00
Clansty
bf9197b3e4 [F] Game crash after one track with new AIME with SkipToMusicSelection 2024-05-26 12:59:16 +08:00
Clansty
27b1a31436 [+] Quick end game with "select" key 2024-05-26 11:58:04 +08:00
Clansty
9b51c8cab4 [+] Totally disable and hide timer + fix side effects when ExtendTimer is on 2024-05-26 11:28:06 +08:00
Clansty
114a452609 [+] Skip "Dont tap or slide vigorously" and "Bye" when QuickSkip is on 2024-05-26 10:33:29 +08:00
Clansty
ef85156bae [F] Need to press skip multiple time to exit photo edit while ExtendTimer is on 2024-05-26 10:11:29 +08:00
Clansty
e55d17fd08 [+] Skip the wait progress after one player login in 1p mode 2024-05-26 09:40:24 +08:00
Clansty
8b83205b0a [F] Mouse input with 1P mode 2024-05-22 12:04:36 +08:00
Clansty
2251350a4e [+] Change every timer to 200 seconds 2024-05-21 21:17:30 +08:00
Clansty
f106a31990 [+] Unlock maps that are not in this version 2024-05-21 03:49:19 +08:00
Clansty
c9f222583a [+] Play "Master" difficulty on Demo screen 2024-05-20 23:36:52 +08:00
Clansty
13fc51a8a5 [F] May fix sometime custom command not triggered 2024-05-20 22:40:02 +08:00
Clansty
21309cddf0 [+] Execute some command on game idle or on game start 2024-05-20 22:31:27 +08:00
Azalea
86164ba518 [F] Fix link card null 2024-05-17 21:22:13 +08:00
Azalea
8173003144 [F] Fix rating composition null elements, generalize
#35
2024-05-16 11:25:22 +08:00
Azalea
f9c8b00587 [PR] #35 from Becods: i18n and rating page fixes
I18n update and rating page fixes
2024-05-16 11:04:15 +08:00
Becods
b77da0f143 [+] More i18n 2024-05-16 10:48:48 +08:00
Becods
ed955150df [F] If id is 0, skip 2024-05-16 10:43:02 +08:00
Azalea
f282197611 Merge pull request #34 from Becods/v1-dev
[+] Add ongeki b55
2024-05-16 01:53:20 +08:00
Clansty
93ce932d28 [F] Unable to load music Acb 2024-05-15 23:57:55 +08:00
Clansty
29505fa4a3 [F] Disable 2P music 2024-05-15 23:29:42 +08:00
Clansty
991442d5c0 [+] Random old version maimai BGM 2024-05-15 21:01:19 +08:00
Becods
e7b5991dbf [+] Add ongeki b55 2024-05-13 10:13:17 +08:00
Azalea
fb72317c6f [+] AquaMai: Skip event and info screen for new users 2024-05-08 21:47:36 +08:00
Azalea
7992568c0f Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-05-08 21:43:40 +08:00
Azalea
38666b7c99 [O] Try to fix reboot (again) 2024-05-08 21:43:19 +08:00
Azalea
1f3f143ffb Merge pull request #32 from Becods/v1-dev
[+] Add ongeki 1.45 and supplement missing events
2024-05-06 11:31:52 +08:00
Becods
156ece4bb5 [+] Add ongeki 1.45 and supplement missing events 2024-05-06 11:29:25 +08:00
Clansty
770d1ae689 [F] Modify ImproveLoadSpeed to only skip delays to reduce bugs 2024-05-05 19:54:50 +08:00
Azalea
d7287c48cf Merge pull request #30 from Becods/v1-dev
[+] Add nginx configuration template
2024-05-01 07:35:10 -04:00
Azalea
b4c329f2f9 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-05-01 19:23:40 +08:00
Azalea
cb2219e2cd [O] Clarify something in the readme 2024-05-01 19:23:36 +08:00
65f0bfa8a4 [+] Add nginx configuration template 2024-05-01 01:26:08 +08:00
Clansty
7090e0a47b [F] Play Activity maybe missing last several days of current month 2024-04-30 23:43:04 +08:00
Azalea
fbb4d61194 [U] Update readme features 2024-04-28 09:38:17 -04:00
Azalea
634b0b50ff [F] Fix disable reboot patch 2024-04-28 09:13:22 -04:00
Azalea
7ff66e9277 [+] AquaMai: Disable reboot 2024-04-28 09:04:46 -04:00
Azalea
b93cc3ab20 [F] Fix migration 2024-04-28 08:17:59 -04:00
Azalea
55e7052189 [+] Migrate: Remove signed IDs 2024-04-28 08:15:13 -04:00
Azalea
5a9808de59 [F] Narrow id range to avoid the signed bit in uint32 2024-04-28 07:48:44 -04:00
Azalea
a30b34df70 [F] Fix mai reboot? 2024-04-26 11:19:45 -04:00
Azalea
482b19dd5a [F] Fix mai reboot? 2024-04-26 11:15:07 -04:00
Azalea
7895ed89f1 [+] Add SDGA and SDGB to AllNet 2024-04-26 00:13:24 -04:00
Azalea
8449853076 [F] Fix maimai reboot time setting 2024-04-26 00:11:50 -04:00
Azalea
affec8d3c1 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-04-25 17:38:43 -04:00
Azalea
362b69d921 [F] Fix maimai2 reboot window? 2024-04-25 17:38:36 -04:00
Azalea
9d463c7b4a [U] Updating instructions 2024-04-25 14:35:49 -07:00
Azalea
cd7da64794 [O] Pre-build docker image 2024-04-25 17:25:14 -04:00
Azalea
5c95f2971f [F] Fix CRLF 2024-04-25 17:16:20 -04:00
Azalea
ab3f3f0633 [F] Fix windows WSL2 volume bug 2024-04-25 16:57:12 -04:00
Azalea
ba61ac46d1 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-04-25 16:48:45 -04:00
Azalea
48205d8a6c [U] Update running instructions 2024-04-25 16:48:43 -04:00
Clansty
b4cbb1fd14 [+] Load AssetBundle without manifest 2024-04-23 18:51:49 +08:00
Clansty
c8db3ec762 [+] Skip to next step or restart current song 2024-04-23 17:34:50 +08:00
Azalea
f6cf157930 [+] Last played host 2024-04-22 11:17:16 -04:00
Azalea
4a84a9ed8e [F] Fix tooltip pos 2024-04-22 10:57:51 -04:00
Azalea
dbb41ba249 [O] Hide loading 2024-04-22 10:43:55 -04:00
Azalea
d3adec5a23 [+] Leaderboard hover 2024-04-22 10:30:34 -04:00
Azalea
1fd030f909 [+] User card 2024-04-22 10:29:55 -04:00
Azalea
b6dfeb475d [+] Slot tooltip 2024-04-22 10:29:45 -04:00
Azalea
39050c6de6 [-] Remove logging 2024-04-22 09:17:30 -04:00
Azalea
c36926c915 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-04-22 09:16:04 -04:00
Azalea
3bf3241bd7 [O] Advanced achievement rounding 2024-04-22 09:15:57 -04:00
Clansty
df863e879f [+] Feature for load self-made charts jacket from file 2024-04-22 20:59:08 +08:00
Azalea
b13af00061 [O] B50 tooltip & hide unknown song level 2024-04-22 08:58:47 -04:00
Azalea
1c2215a8a2 [O] Reduce displayed digits 2024-04-22 08:48:32 -04:00
Azalea
555ae35bb9 [F] Fix mai2 rating calculation 2024-04-22 08:46:22 -04:00
Azalea
8337a1698e [U] Upgrade yarn 1 -> 4, update dependencies 2024-04-21 22:40:36 -04:00
Azalea
f0bfa96937 [F] Fix AquaNet ghost card detection 2024-04-21 14:53:50 -04:00
Azalea
7b143dd38f [+] More logging 2024-04-21 14:45:56 -04:00
Clansty
5201c5933c [+] B15 in user detail 2024-04-22 01:55:15 +08:00
Azalea
4c1d501856 [F] Fix b50 NPE 2024-04-21 13:43:44 -04:00
Azalea
e001533f33 [F] Fix diva migration sql 2024-04-20 14:20:09 +09:00
Azalea
c854dd9a45 [F] Fix ranking username conflict 2024-04-20 11:25:37 +09:00
Azalea
4215b39539 [F] Try to fix wacca uint overflow 2024-04-20 09:37:34 +09:00
Azalea
8e882aafa1 [F] Fix register error not displaying 2024-04-19 16:05:57 +09:00
Azalea
25edbf06c7 [F] Fix account card linking 2024-04-18 21:47:13 +09:00
Azalea
4f05365da3 [F] Fix results not saved 2024-04-18 12:30:36 +09:00
Azalea
bf7de99524 [+] NFKC Normalization before processing 2024-04-18 11:37:40 +09:00
Azalea
08c27b6c58 [+] Automatically ban people with unacceptable names 2024-04-18 11:30:18 +09:00
Azalea
60661757c6 [+] Safety moderation 2024-04-18 11:29:02 +09:00
Azalea
5ba64483fb [+] OpenAI Settings in application properties 2024-04-18 09:12:34 +09:00
Azalea
a30c9391eb [F] Fix null pointer 2024-04-17 00:06:32 +09:00
Azalea
7023e726bd Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-04-17 00:03:47 +09:00
Azalea
c616ea81c6 [S] Make b35 look better 2024-04-17 00:03:35 +09:00
Azalea
65f8b587af [S] Fix tooltip startup glitch 2024-04-16 23:48:41 +09:00
Azalea
14f6b9c759 Merge branch 'b50-dev' of https://github.com/colasama/AquaDX into v1-dev 2024-04-16 23:44:30 +09:00
Clansty
c83e0f8cff [F] Fix Chime scanning and (maybe) DX Pass crash 2024-04-12 00:40:29 +08:00
Azalea
2bf86423c9 [PR] #28 from clansty: CustomVersionString
Feature: Custom version display string
2024-04-10 15:36:51 -04:00
凌莞~(=^▽^=)
8dc3035b66 Merge branch 'v1-dev' into feat/CustomVersionString 2024-04-11 03:20:12 +08:00
Azalea
0aff0330e7 [U] Update config description 2024-04-10 07:13:54 -07:00
Azalea
41852f2467 [PR] #29 from clansty: ImproveLoadSpeed
Feature: Option to disable some useless checks and delays to speedup the game boot process
2024-04-10 02:10:35 -04:00
Clansty
442ec76828 [+] Option to disable some useless checks and delays to speedup the game boot process 2024-04-08 17:02:14 +08:00
Clansty
d8fc14e71b [+] Custom version string feature 2024-04-08 15:03:43 +08:00
Azalea
2630d32764 [F] Fix card controller after pdid change 2024-04-07 04:06:17 -04:00
Azalea
74d7eff577 [F] Fix DIVA pd_id overflow 2024-04-06 23:36:00 -04:00
Azalea
355c9e2a3d [+] Counter measure 2024-04-03 08:08:02 -04:00
Azalea
501bf06ada [S] Better mobile alignment 2024-04-02 20:34:20 -04:00
Azalea
4574bc0b2f [F] Fix responsive 2024-04-02 20:25:23 -04:00
Azalea
c6c91b84fe [F] Fix wacca rating calculation 2024-04-02 06:26:18 -04:00
Azalea
066b33e3e8 [F] Fix wacca invalid music 2024-04-02 02:33:32 -04:00
Azalea
15002c45d6 [O] Rewrite chusan item handler 2024-04-02 02:13:20 -04:00
Azalea
b41f3b9370 [F] Fix jvm name clash 2024-04-02 02:07:39 -04:00
Azalea
02e2700e96 [O] Make always vip configurable 2024-04-02 02:04:42 -04:00
Azalea
6441dfd219 [+] Wacca item unlocks 2024-04-02 01:56:42 -04:00
Azalea
1e229c12cc [+] Wacca cheat options 2024-04-02 01:56:31 -04:00
Azalea
4219f2db5b [+] Wacca items 2024-04-02 01:44:12 -04:00
Azalea
36ce636093 [-] Don't display AAA 2024-04-02 01:27:07 -04:00
Azalea
47f09f81ff [U] Correct wacca scoring 2024-04-02 01:26:24 -04:00
Azalea
bfa6df904d [O] Proper ticket unlock for wacca 2024-04-02 00:56:59 -04:00
Azalea
99d4f55c50 [+] Chinese i18n for user settings 2024-04-02 00:28:35 -04:00
Azalea
7728b4b1ab [M] Move setting descriptions to i18n 2024-04-02 00:16:34 -04:00
Azalea
6a475434ad [F] Fix typo 2024-04-02 00:09:14 -04:00
Azalea
876a0bd108 [+] Implement game options 2024-04-02 00:07:50 -04:00
Azalea
ba13bfd9ad [+] SDK for settings/get settings/set 2024-04-01 23:51:13 -04:00
Azalea
44bab8c0c7 [+] Add "type" field to settings/get 2024-04-01 23:51:00 -04:00
Azalea
2d229b82c3 [+] Game settings tab 2024-04-01 23:38:15 -04:00
Azalea
c6cce7aa9a [S] Fix css label box overflow 2024-04-01 23:37:45 -04:00
Azalea
5cbf09f24e [F] Fix splash 2024-04-01 23:27:19 -04:00
Azalea
3ca7d3d615 [U] Update i18n interface 2024-04-01 23:18:50 -04:00
Azalea
25840be694 [U] Update readme jdk version 2024-04-01 19:58:15 -07:00
Azalea
34ab608425 [U] Readme: Add license details 2024-04-01 19:49:50 -07:00
Azalea
b498160b3a [+] CC BY-NC-SA License 2024-04-01 19:37:18 -07:00
Azalea
23aae3b5b9 [F] Fix test build 2024-04-01 22:30:49 -04:00
Azalea
97fdd096a8 [F] Fix music info parsing 2024-04-01 22:19:29 -04:00
Azalea
0d21a02da9 [F] Fix wacca music unlock 2024-04-01 22:17:11 -04:00
Azalea
ab94250b05 [+] Wacca: Unlock all music 2024-04-01 22:12:14 -04:00
Azalea
42ca6f79dc [F] Force color output 2024-04-01 21:59:47 -04:00
Azalea
646795b753 [+] Wacca username character constraint 2024-04-01 21:54:54 -04:00
Azalea
de649915e2 [U] Upgrade to JDK 21 2024-04-01 21:48:08 -04:00
Azalea
0093f5a0de Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-04-01 21:05:04 -04:00
Azalea
686b50eeda [F] Fix chusan export 2024-04-01 21:04:49 -04:00
Azalea
0c93b85024 [F] Fix docker build
Closes #24
2024-04-01 14:04:19 -04:00
Azalea
49d4e88022 [F] Fix chusan import reflection 2024-03-31 11:53:36 -04:00
Azalea
3a8616e225 [+] Generalize data import for chusan 2024-03-30 23:52:29 -04:00
Azalea
d4178c85a9 [F] Fix wacca song unlock (!) 2024-03-30 20:49:40 -04:00
Azalea
de46790bdf [F] Fix wacca song unlock (?) 2024-03-30 20:31:56 -04:00
Azalea
c27070ae28 [F] Fix ongeki userId overflow 2024-03-30 19:40:47 -04:00
Azalea
bb4c9477da [F] Fix wacca logging 2024-03-29 21:11:28 -04:00
Colanns
95e78e4f93 [+] Add a B50 / rating page to frontend 2024-03-29 23:49:08 +08:00
Azalea
d3d7b5a5c7 [O] Don't translate game name 2024-03-29 09:15:48 -04:00
Azalea
45a3d74284 [F] Fix wacca query 2024-03-29 09:02:30 -04:00
Azalea
cd972b5c61 [+] Add wacca to frontend 2024-03-29 08:20:37 -04:00
Azalea
341be8bdc1 [+] Add wacca to card controller 2024-03-29 08:20:21 -04:00
Azalea
101c24edc5 [+] Add wacca support to readme 2024-03-29 05:58:27 -04:00
Azalea
be34915cdf [+] Wacca: Calculate player rating server side 2024-03-29 05:53:00 -04:00
Azalea
70aed1d5db [+] Wacca endpoints 2024-03-29 05:45:41 -04:00
Azalea
d8c1144881 [-] Remove spring devtools 2024-03-29 05:13:43 -04:00
Azalea
68ec7f504a [+] Wacca NET controller (incomplete) 2024-03-29 05:13:22 -04:00
Azalea
3ab2b16042 [+] Chusan 216 events 2024-03-29 05:12:54 -04:00
Azalea
d7fc6f9f49 [F] Fix music difficulty unlock conflict 2024-03-29 02:58:25 -04:00
Azalea
26a72244c0 [M] Rename database fields 2024-03-29 00:47:54 -04:00
Azalea
abc21badb1 [+] Wacca: More database fields 2024-03-29 00:24:02 -04:00
Azalea
aa1caacfd6 [F] Fix AllNet Wacca path 2024-03-29 00:09:40 -04:00
Azalea
3663eb63e7 [F] Fix maimai2 UpsertUserChargelogApi (TODO) 2024-03-29 00:09:27 -04:00
Azalea
e885700680 [+] wacca user/goods/purchase 2024-03-28 23:34:38 -04:00
Azalea
d6170d602a [+] wacca user/trial/update 2024-03-28 23:34:26 -04:00
Azalea
4dbb287e11 [-] Remove version 2024-03-28 23:34:05 -04:00
Azalea
e537e0f115 [U] Update migration 2024-03-28 23:19:51 -04:00
Azalea
3613d7a37b [+] user/trial/get, user/vip/get, user/vip/start 2024-03-28 23:19:05 -04:00
Azalea
b5e98f505f [O] Code cleanup 2024-03-28 22:45:34 -04:00
Azalea
3dc9ca6822 [+] Wacca api progress 2024-03-28 22:36:50 -04:00
Azalea
e13ddeaaad [+] user/music/unlock 2024-03-28 22:07:03 -04:00
Azalea
56ce7f9696 [F] Wacca fix impl details 2024-03-28 20:17:33 -04:00
Azalea
4ebddf78ed [F] Wacca: Fix handle 2024-03-28 19:08:59 -04:00
Azalea
2682165da8 [+] Wacca: user/info/update 2024-03-28 19:05:54 -04:00
Azalea
373e7dc8ad [+] Wacca: user/status/update 2024-03-28 19:05:46 -04:00
Azalea
0551f8bff1 [+] Wacca: user/rating/update 2024-03-28 19:05:35 -04:00
Azalea
b4454cc812 [+] Wacca: Fix test inconsistency, error handling 2024-03-28 19:05:19 -04:00
Azalea
40fb1c8868 [O] Wacca: Simplify data storage, re-init database 2024-03-28 19:04:16 -04:00
Azalea
f97cb4a1bb [F] Fix integer list converter behavior on empty lists 2024-03-28 19:02:21 -04:00
Azalea
56d0786702 [+] Wacca more tests 2024-03-28 11:09:29 -04:00
Azalea
d880ecd709 [+] Wacca user/music/update 2024-03-28 11:09:12 -04:00
Azalea
1bee9e19e6 [+] Wacca user/mission/update 2024-03-28 11:09:01 -04:00
Azalea
c5879ae5a7 [+] Wacca user/sugoroku/update 2024-03-28 11:08:48 -04:00
Azalea
64f458e15a [F] Fix consecutive login 2024-03-28 11:08:33 -04:00
Azalea
2fa5d09fc9 [O] Redesign wacca score model 2024-03-28 11:08:09 -04:00
Azalea
d6fc60e02b [F] Wacca user/status/getDetail 2024-03-28 07:05:32 -04:00
Azalea
bb9bfd6396 [+] Wacca user/status/GetDetail 2024-03-28 05:57:07 -04:00
Azalea
0fbe139e8d [F] Fix wacca db constraints 2024-03-28 05:56:52 -04:00
Azalea
571591f021 [O] Unify item interface 2024-03-28 05:24:05 -04:00
Azalea
8a1d2383b8 [+] Wacca user/status/login tests 2024-03-28 03:37:26 -04:00
Azalea
00c5edcea7 [+] Wacca user/status/login 2024-03-28 03:37:14 -04:00
Azalea
39d62099df [F] Fix status return 2024-03-28 02:51:41 -04:00
Azalea
c5d6f6f5b9 [+] Wacca user/status/create 2024-03-28 02:50:44 -04:00
Azalea
13f3cf1e90 [+] Test constants 2024-03-28 02:10:39 -04:00
Azalea
93f6bf8ba3 [+] Wacca user/status/get 2024-03-28 02:10:25 -04:00
Azalea
1cdbed51cd [F] Fix long casting 2024-03-28 01:56:23 -04:00
Azalea
50ae04bb4e [+] Wacca test (incomplete) 2024-03-28 01:22:16 -04:00
Azalea
a55d503faa [F] Fix allnet compression 2024-03-28 01:14:29 -04:00
Azalea
7fc4f83eb5 [F] Fix test build 2024-03-28 01:05:13 -04:00
Azalea
bc831b4d30 [F] Fix filter 2024-03-28 01:00:59 -04:00
Azalea
c6190146aa [F] Fix zlib compression happening after response commit 2024-03-28 00:58:55 -04:00
Azalea
3f01152a4a [+] More extensions 2024-03-27 23:11:05 -04:00
Azalea
ad5c652a8f [+] Return wacca url for AllNet 2024-03-27 23:10:52 -04:00
Azalea
9609db941b [+] Wacca server handler (incomplete) 2024-03-27 23:10:41 -04:00
Azalea
bbb8447f5c [+] Wacca constants & repos 2024-03-27 23:09:13 -04:00
Azalea
22ca06af3e [+] Wacca request model 2024-03-27 23:08:58 -04:00
Azalea
af11758190 [+] Exclude wacca in compression filter 2024-03-27 23:08:22 -04:00
Azalea
32fcc25ea4 [F] mai2 error response 2024-03-27 23:07:27 -04:00
Azalea
b3fcf8dd5e [F] Fix mai2 error response 2024-03-27 23:05:46 -04:00
Azalea
b7d2a97f05 [O] Separate register function 2024-03-27 22:42:05 -04:00
Azalea
ad13875137 [O] Separate common functions for tests 2024-03-27 22:39:27 -04:00
Azalea
e14a131480 [F] Fix wacca db migration for MariaDB 11.3.2 2024-03-27 04:48:04 -04:00
Azalea
64ba0db228 [F] Fix memory leak 2024-03-27 00:52:21 -04:00
Azalea
c99d8e7e75 [O] More cleanup, return 400 for bad requests 2024-03-26 23:03:40 -04:00
Azalea
305d1cea94 [O] Disable integration tests on build 2024-03-26 22:31:49 -04:00
Azalea
f314b3982e [F] Fix mai event id 2024-03-26 22:27:12 -04:00
Azalea
5ea2615b93 [O] Collapse basic handlers 2024-03-26 22:26:29 -04:00
Azalea
17123fec35 [F] Remove redundant fields in GetUserMap, LoginBonus, UserExtend, UserData 2024-03-26 20:52:40 -04:00
Azalea
73d05e7cbf [F] Fix bearer discrepency 2024-03-26 20:51:10 -04:00
Azalea
3380ea3609 [F] Fix mai2 username encoding check 2024-03-26 20:50:57 -04:00
Azalea
101527d3e1 [U] Update testing properties 2024-03-26 20:50:30 -04:00
Azalea
df9ab3250c [+] Maimai2 play simulation testing 2024-03-26 20:49:40 -04:00
Azalea
d533df52de [F] Fix maimai2 user item 2024-03-26 20:22:38 -04:00
Azalea
d2cf16d046 [F] Fix username decoding 2024-03-26 18:51:17 -04:00
Azalea
40a65b5e13 [+] gzip & deflate 2024-03-26 18:09:27 -04:00
Azalea
fa33cb680e [PR] #23 from Teud/v1-dev
Add maimai 1.40 H061 events
2024-03-25 22:50:48 -04:00
Teud
2757eb91ce fix 2024-03-25 23:12:00 +01:00
Teud
2842429ced Add maimai 1.40 H061 events 2024-03-25 23:04:16 +01:00
Azalea
fb2a26c5b7 [F] Fix dependencies 2024-03-25 14:21:14 -04:00
Azalea
cab1dc8838 [O] Set all items to valid 2024-03-25 14:18:04 -04:00
Azalea
0ec76dcde3 [F] JsonIgnore ID 2024-03-25 14:15:49 -04:00
Azalea
c41046953e [F] Fix user item isValid field 2024-03-25 14:15:03 -04:00
Azalea
30f740a430 [-] Remove old code 2024-03-25 13:56:49 -04:00
Azalea
1e8c0ce99b [O] Optimize mai2 GetUserMusic 2024-03-25 13:56:30 -04:00
Azalea
aa3a3d9181 [F] Fix chusan playlog integer overflow 2024-03-25 13:40:41 -04:00
Azalea
6d0f528201 [F] Fix ongeki upsert all: UserData might be empty list 2024-03-25 13:39:00 -04:00
Azalea
131cd5915c [F] Fix chusan user cmission saving bug 2024-03-25 13:29:49 -04:00
Azalea
f5512fa162 [F] Fix db: Make user_id non-null 2024-03-25 03:07:28 -04:00
Azalea
484bb758ae [+] Wacca database models 2024-03-25 03:06:28 -04:00
Azalea
89461893a4 [+] Allow ftk as an auth token 2024-03-23 12:22:32 -04:00
Azalea
54e865feb2 [O] Optimize GetUserItemApi loading speed 2024-03-23 07:19:58 -04:00
Azalea
015fa3dc9f [F] Fix maimai2 events 2024-03-23 07:01:00 -04:00
Azalea
cf015be49f [F] Fix maimai get rating crash for some users 2024-03-23 05:53:56 -04:00
Azalea
b6c8993f7e [F] Fix total achievement overflowing int32 max 2024-03-23 02:24:10 -04:00
Azalea
1ef37d91e8 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-03-22 16:59:47 -04:00
Azalea
7fc81cf363 [+] Add chusan 220 events 2024-03-22 16:59:42 -04:00
Azalea
123bf9de34 [U] Update README.md 2024-03-22 13:11:06 -07:00
Azalea
d3f6b75d34 [F] Fix reflection 2024-03-22 15:17:27 -04:00
Azalea
a5fe5f53e2 [+] chusan: Luminous 2024-03-22 15:09:29 -04:00
Azalea
e91029f66e [F] Fix merge conflicts 2024-03-22 15:06:31 -04:00
Azalea
2a7ce54c28 Merge dev.s-ul.net:rinsama/aqua into v1-dev 2024-03-22 14:58:41 -04:00
HoshimiRIN
f3b2d4dc57 [chusan]fix build issue 2024-03-22 14:51:52 +08:00
HoshimiRIN
95b9871f7f [chusan]add support for luminous 2024-03-22 14:29:43 +08:00
Azalea
533af83749 [O] Don't jsonignore id 2024-03-21 22:28:42 -04:00
Azalea
e4330fee92 [F] Fix artemis conversion 2024-03-21 04:09:48 -04:00
Azalea
5fec57e8e3 [+] Add artemis import endpoint 2024-03-21 04:04:23 -04:00
Azalea
95a06d572b [F] Fix mai2 import 2024-03-21 03:59:35 -04:00
Azalea
cc8c125934 [F] Fix deleting data on import 2024-03-21 01:07:25 -04:00
Azalea
91c605ee4b [F] Fix unique constraints on user detail 2024-03-21 01:00:52 -04:00
Azalea
f44fe4def1 [+] Mai2 Import feature done! 2024-03-21 00:36:23 -04:00
Azalea
7c0a1ea089 [F] Fix mai2 entity nullability 2024-03-21 00:36:00 -04:00
Azalea
ce5c4d1111 [F] Fix userid nullability 2024-03-21 00:35:57 -04:00
Azalea
98952972a0 [+] Add cascade relationship to user-mapped fks 2024-03-21 00:05:58 -04:00
Azalea
f728b6ab1b [O] Check username 2024-03-20 22:43:03 -04:00
Azalea
e799b48877 [O] Huge refactor 2024-03-20 21:27:29 -04:00
Azalea
fc8ecb7470 [+] More work on import feature (TODO) 2024-03-20 18:52:11 -04:00
Azalea
ac18234e29 [+] Spring devtools dependency 2024-03-20 17:57:34 -04:00
Azalea
59b17aa47e [+] Check sqlite before application start 2024-03-20 17:54:16 -04:00
Azalea
9155bfb886 [+] SDGS support 2024-03-20 13:01:57 -04:00
Azalea
cbe683d25e [F] Fix matching server 2024-03-20 12:57:01 -04:00
Azalea
64f057a415 [F] Fix build 2024-03-20 12:46:08 -04:00
Azalea
3da308346e [O] Unify even more BaseHandlers 2024-03-20 12:45:46 -04:00
Azalea
313dd681de [+] Import (TODO) 2024-03-20 12:33:33 -04:00
Azalea
aaf7e1e3e5 [+] Chusan export version support 2024-03-20 12:32:34 -04:00
Azalea
9f831fd8b5 [O] Refactor chusan controller 2024-03-20 12:32:14 -04:00
Azalea
450397481e [M] Unify BaseHandler 2024-03-20 12:24:31 -04:00
Azalea
6fb8978f48 [+] Chusan playlog sunplus fields 2024-03-20 05:13:47 -04:00
Azalea
4a7bf4b31e [+] Chusan artemis import 2024-03-20 05:09:42 -04:00
Azalea
38e94210e4 [+] Jackson datetime parsing 2024-03-20 05:08:57 -04:00
Azalea
d338809750 [M] Generalize artemis import code 2024-03-20 04:17:35 -04:00
Azalea
7fd7e17d1d [+] mai2 artemis import script 2024-03-20 02:00:28 -04:00
Azalea
a5a5bd80c4 [+] Helper to read sql file 2024-03-20 01:59:21 -04:00
Azalea
d264ca1ed4 [+] Custom json deserializers 2024-03-20 01:59:08 -04:00
Azalea
85dd19509c [M] Move class 2024-03-19 21:04:07 -04:00
Azalea
faf1945933 [M] Move packages 2024-03-19 21:02:24 -04:00
Azalea
3c6d6ff702 [-] Ignore log files 2024-03-19 20:52:20 -04:00
Azalea
c6ecc89ad3 [+] user-detail endpoint 2024-03-19 20:52:04 -04:00
Azalea
abed79441d [U] Upgrade to kotlin 2.0.0-Beta4 2024-03-19 20:22:28 -04:00
Azalea
906199a517 [+] Change in-game settings 2024-03-19 19:58:00 -04:00
Azalea
6f34c21d94 [O] Optimize startup speed 2024-03-18 18:56:44 -04:00
Azalea
9ba1a68b51 [F] Fix setting api 2024-03-18 18:56:16 -04:00
Azalea
073c72fd63 [F] Fix error response 2024-03-18 09:34:37 -04:00
Azalea
3ac4af1558 [F] Fix typo 2024-03-18 09:33:33 -04:00
Azalea
5057f6848f [+] Settings api 2024-03-18 09:32:14 -04:00
Azalea
b3955731c2 [+] Maimai item unlock 2024-03-18 05:31:55 -04:00
Azalea
af83cf552e [S] Coming soon message 2024-03-18 04:03:26 -04:00
Azalea
a0426044e8 [+] i18n for home page 2024-03-18 03:57:58 -04:00
Azalea
432635d567 [F] Fix entrypoint 2024-03-18 03:06:27 -04:00
Azalea
02b78320ec [+] Better logging 2024-03-18 03:06:05 -04:00
Azalea
f1461f905d [+] i18n for status messages 2024-03-18 01:55:22 -04:00
Azalea
e1cdb3ab65 [F] More detailed error handling 2024-03-18 01:37:57 -04:00
Azalea
6218424be3 [O] Better error handling 2024-03-18 01:32:57 -04:00
Azalea
5a9b7e296f [O] Reduce loc 2024-03-18 01:32:38 -04:00
Azalea
f4cc9c7734 [F] Fix maimai music unlock 2024-03-18 01:32:00 -04:00
Azalea
e0c7998448 [+] DB cleanup 2024-03-18 01:31:39 -04:00
Azalea
752d65557f [+] Unlock option database model 2024-03-18 01:31:28 -04:00
Azalea
a952674df7 [+] Maimai music unlock 2024-03-17 02:03:58 -04:00
Azalea
25f5f6e1f7 [+] Maimai export 2024-03-17 00:12:02 -04:00
Azalea
0f1d6c0984 [O] Remove unnecessarily long constructors 2024-03-16 22:50:08 -04:00
Azalea
8dd4bb9d61 [M] Rename 2024-03-16 22:14:18 -04:00
Azalea
98275ade59 [O] Refactor chusan repos 2024-03-16 22:09:21 -04:00
Azalea
95cc9f0e21 [M] Rename 2024-03-16 21:41:17 -04:00
Azalea
742ea50c2c [O] More simplification 2024-03-16 21:35:17 -04:00
Azalea
54b1174e1b [O] Refactor maimai2 repositories 2024-03-16 19:50:05 -04:00
Azalea
e07de72fa4 [O] Generalize ranking and find by card 2024-03-16 19:22:08 -04:00
Azalea
13b4af3734 [M] Move files 2024-03-16 19:10:22 -04:00
Azalea
29566a6c93 [O] Make unclickable when no data is available 2024-03-15 01:33:07 -04:00
Azalea
7669f7d9a0 [O] Refactor 2024-03-15 01:26:52 -04:00
Azalea
5913d5b585 [F] Fix script 2024-03-15 01:13:18 -04:00
Azalea
d9a332de44 [+] Display rating details 2024-03-15 01:10:15 -04:00
Azalea
e85533686e [F] Fix detailed ranks 2024-03-15 00:37:30 -04:00
Azalea
0100140dc0 Revert "[+] Detailed ranks"
This reverts commit a9893379f4.
2024-03-14 23:21:30 -04:00
Azalea
8def9e8931 [F] Fix link 2024-03-14 22:59:26 -04:00
Azalea
6fc2f26983 [O] Optimize fonts 2024-03-14 22:55:11 -04:00
Azalea
ed1ed6cbe9 [+] Leaderboard i18n 2024-03-14 22:41:23 -04:00
Azalea
10d19a5392 [+] Switching games in leaderboard 2024-03-14 22:35:36 -04:00
Azalea
7bbd90ab91 [+] Emphasize registered users 2024-03-14 22:23:48 -04:00
Azalea
9565d48b04 [F] Fix "no-data" 2024-03-14 22:15:19 -04:00
Azalea
284d366b44 [+] Ranking for different games 2024-03-14 22:15:10 -04:00
Azalea
a9893379f4 [+] Detailed ranks 2024-03-14 22:11:04 -04:00
Azalea
50677ad81d [F] Fix no data when a user hasn't played maimai 2024-03-14 21:47:39 -04:00
Azalea
71d7fcbe65 [+] Get user games endpoint 2024-03-14 21:47:14 -04:00
Azalea
8342acbd49 [F] Fix heatmap displaying even when plays is 0 2024-03-06 17:04:51 -05:00
Azalea
d5296763ad [O] Do not overwrite println in gendocs 2024-03-06 10:49:02 -05:00
Azalea
73efa4fe91 [U] Update docs 2024-03-06 10:46:11 -05:00
Azalea
82f573e1a1 [O] More information to frontier endpoint 2024-03-06 10:42:31 -05:00
Azalea
4ef0ac3fee [F] Fix typo 2024-03-06 10:37:06 -05:00
Azalea
bc246f39d2 [+] Frontier endpoint 2024-03-06 10:34:12 -05:00
Azalea
f9af23dbca [+] More i18n 2024-03-06 10:33:31 -05:00
Azalea
68f8ef0b24 [O] More i18n 2024-03-06 10:17:04 -05:00
Azalea
16f6acf8fc [+] More i18n 2024-03-06 10:13:08 -05:00
Azalea
3faa5b2f52 [F] Fix typo 2024-03-06 09:57:05 -05:00
Azalea
04a7c068f4 [M] Fix typos 2024-03-06 09:52:37 -05:00
Azalea
92dee27634 [F] Fix i18n typing 2024-03-06 09:51:03 -05:00
Azalea
7dda25f96b Merge pull request #22 from chiba233/v1-dev
[U] update i18n.ts type and more intuitive
2024-03-06 09:32:49 -05:00
chiba
40f700910a Update UseHome.svelte I18n support 2024-03-06 21:18:27 +08:00
chiba
aa90b34511 [F] FIX i18n.ts type 2024-03-06 17:19:51 +08:00
chiba
45cf082bb9 [F] FIX i18n.ts type 2024-03-06 16:23:45 +08:00
chiba
0ab78983d4 [U] update i18n.ts type and more intuitive 2024-03-06 16:17:06 +08:00
Azalea
e137210cbc [S] Better title 2024-03-06 01:33:47 -05:00
Azalea
3093755c9e [S] Better heading style 2024-03-06 01:31:16 -05:00
Azalea
94c4950d23 [O] i18n placeholders 2024-03-06 01:25:08 -05:00
Azalea
fa0a624b7c [O] Infer language 2024-03-06 01:21:11 -05:00
Azalea
f3fabe1708 [+] i18n 2024-03-06 01:20:28 -05:00
Azalea
52ec890e2c [+] Show aqua net pfp instead of in-game pfp 2024-03-06 00:37:35 -05:00
Azalea
2a10471e0b [O] Don't expose all fields 2024-03-06 00:35:44 -05:00
Azalea
94c1974d2f [O] Just pass through aquanetuser instead. 2024-03-06 00:26:34 -05:00
Azalea
f0a8014efb [+] Add profile picture in game summary 2024-03-06 00:25:02 -05:00
Azalea
96cac6ca68 [O] Optimize imports 2024-03-06 00:21:27 -05:00
Azalea
0da50bc693 [F] Fix profile path 2024-03-06 00:20:04 -05:00
Azalea
1169ac44b4 [-] Remove path concat 2024-03-06 00:18:28 -05:00
Azalea
38367279ff [F] Fix upload paths 2024-03-06 00:16:53 -05:00
Azalea
ef00cfbddd [+] Serve uploads directory 2024-03-06 00:07:57 -05:00
Azalea
e514e4b64e [+] Wrapper for pfp path 2024-03-05 23:35:49 -05:00
Azalea
f1af07e921 [+] UI for uploading profile picture 2024-03-05 23:27:08 -05:00
Azalea
44cf022e70 [F] Fix profile file name extension 2024-03-05 23:26:11 -05:00
Azalea
7e68de5a17 [F] Fix profile picture upload api 2024-03-05 23:24:41 -05:00
Azalea
51f73d77bf [F] Fix felica lookup v2 2024-03-05 22:34:53 -05:00
Azalea
fa4ccf07b8 [+] Implement user settings 2024-03-05 22:18:38 -05:00
Azalea
49da7aafd0 [O] Show edit profile button only if it's me 2024-03-05 19:36:23 -05:00
Azalea
58ca71baaa [+] Tooltip 2024-03-05 19:32:36 -05:00
Azalea
2c550a0874 [S] Unify border radius 2024-03-05 19:19:05 -05:00
Azalea
dcb671acd8 [+] Profile setting button in home 2024-03-05 19:09:43 -05:00
Azalea
56600d3f27 [O] Change default hosts 2024-03-05 18:22:20 -05:00
Azalea
6913f7bdf5 [F] Fix types
#21
2024-03-05 18:21:32 -05:00
Azalea
bcc2d286ed [+] Display profile picture
#21
2024-03-05 18:21:05 -05:00
Azalea
aed6c2123f [F] Fix type nullability 2024-03-05 18:20:03 -05:00
Azalea
68626fecd7 [+] Add profile picture field to aqua net user 2024-03-05 17:59:44 -05:00
Azalea
441d7376cb [+] Upload pfp endpoint 2024-03-05 17:56:33 -05:00
Azalea
c9ac38de01 [+] Optimize upload photo 2024-03-05 17:56:16 -05:00
Azalea
b9c063c41e [O] Reject unauthenticated aimedb requests 2024-03-05 14:47:02 -05:00
Azalea
55804be70e [F] Fix code suggestions
Closes #20
2024-03-05 14:25:11 -05:00
Azalea
2b749af917 [+] Filter dates on frontend 2024-03-05 03:44:16 -05:00
Azalea
9378dfdd04 [F] Fix graph logic 2024-03-05 03:41:23 -05:00
Azalea
46768c77b7 [S] Hide heatmap scrollbar and automatically scroll to right 2024-03-05 03:32:55 -05:00
Azalea
ff9358b986 [F] Fix index out of bounds 2024-03-05 03:22:53 -05:00
Azalea
f3090870be [O] Don't filter on backend 2024-03-05 03:22:07 -05:00
Azalea
666fbe8ce7 [+] Display no data 2024-03-05 03:21:49 -05:00
Azalea
67b29851ea [F] Fix utage song info 2024-03-05 02:53:04 -05:00
Azalea
1a2cd201a7 Merge pull request #18 from chiba233/v1-dev
improve UserHome.svelte song level and music name
2024-03-05 02:51:40 -05:00
Azalea
5041bf67a5 Merge branch 'v1-dev' into v1-dev 2024-03-05 02:51:33 -05:00
Azalea
1a2f3bf80e [F] Fix user page css text overflow 2024-03-05 02:50:00 -05:00
chiba
d7a231eb18 improve UserHome.svelte song level and music name 2024-03-05 15:41:01 +08:00
Azalea
21c9c190aa Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-03-04 21:18:26 -05:00
Azalea
a781c2d665 [-] Delete unused files 2024-03-04 21:18:17 -05:00
Azalea
09c3ce3164 [PR] #17 from chiba233/v1-dev
improve LinkCard.svelte more intuitive
2024-03-04 21:08:16 -05:00
Azalea
a7888a63fa [F] Fix typing 2024-03-04 21:05:32 -05:00
chiba
79dd56d017 improve LinkCard.svelte when click button clean input 2024-03-04 23:37:56 +08:00
chiba
882d04f50c add .editorconfig to improve multiple developers working 2024-03-04 23:30:47 +08:00
chiba
a7fd414ce6 improve LinkCard.svelte more intuitive 2024-03-04 22:26:48 +08:00
Azalea
eb30451cfa [O] Aimedb: ignore invalid requests 2024-03-03 18:01:27 -05:00
Azalea
a48f2b1f17 [-] Remove jsonignore for playlog id 2024-03-03 17:48:13 -05:00
Azalea
d3665d64a6 [+] Default game endpoint 2024-03-03 17:46:40 -05:00
Azalea
a4bbc9c3c6 [+] Recent endpoint 2024-03-03 17:46:22 -05:00
Azalea
b0ffda42bc [+] User profile game switching 2024-03-03 17:10:13 -05:00
Azalea
b333045d41 [F] Fix out of bounds 2024-03-03 17:09:55 -05:00
Azalea
ef5d0a81eb [O] Display relative time in rating chart 2024-03-03 15:31:59 -05:00
Azalea
48819c10a9 [O] Better trend graph 2024-03-03 14:53:18 -05:00
Azalea
9ae23e4395 [F] Fix import 2024-03-03 14:46:55 -05:00
Azalea
4d36efebb7 [U] Update app version 2024-03-03 14:45:24 -05:00
Azalea
e842a37654 [+] Display splash and app version in frontend 2024-03-03 14:45:15 -05:00
Azalea
c821626dc1 [+] Remake self test 2024-03-03 14:32:21 -05:00
Azalea
16aba9ff96 [O] Render gradient splash text using HyLogger 2024-03-03 14:26:27 -05:00
Azalea
a11bfdb13b [F] Reset turnstile after login error 2024-03-03 12:30:52 -05:00
Azalea
c0437e55eb [F] Fix token invalidation 2024-03-03 12:24:20 -05:00
Azalea
279bcbfeab [+] More instructions 2024-03-03 02:00:32 -05:00
Azalea
6555263496 [F] Fix discord page link 2024-03-03 01:05:23 -05:00
Azalea
8db4e17a8a [F] Fix login redirect logic 2024-03-03 01:00:36 -05:00
Azalea
4a5bd3135f [O] More logging 2024-03-03 00:47:57 -05:00
Azalea
32eb98361a [+] Redirect option 2024-03-03 00:29:48 -05:00
Azalea
2ba5073d55 [M] Rename project & prepare for 1.0.0 2024-03-03 00:00:33 -05:00
Azalea
9ea5e2cd90 [-] Disable plain jar 2024-03-02 23:59:03 -05:00
Azalea
284a1f0b57 [S] Better hover transition 2024-03-02 23:32:32 -05:00
Azalea
7b97f3d535 [S] Blur for loading 2024-03-02 23:19:25 -05:00
Azalea
fb431fcc7b [+] Add discord link in setup instructions 2024-03-02 23:15:28 -05:00
Azalea
b16100e627 [S] Minor text inconsistency 2024-03-02 23:08:50 -05:00
Azalea
fde6b5df9b [S] Minor adjustments 2024-03-02 23:07:01 -05:00
Azalea
dc1ac106c0 [-] Remove unused code 2024-03-02 23:04:29 -05:00
Azalea
0d4a26c05e [+] Action card 2024-03-02 23:03:55 -05:00
Azalea
ffe7a9294b [+] Home action cards 2024-03-02 23:03:46 -05:00
Azalea
15004b6ba2 [S] Better home page tab styling 2024-03-02 20:22:11 -05:00
Azalea
dfd8d1b0c9 [-] Remove the unnecessary clz shorthand 2024-03-02 19:55:52 -05:00
Azalea
c2fef3fa25 [+] Home page tabs 2024-03-02 19:48:29 -05:00
Azalea
d33c892303 [+] Better fading 2024-03-02 19:47:45 -05:00
Azalea
e4ce97cf5d [+] Add UI constants 2024-03-02 19:45:08 -05:00
Azalea
f331916bd5 [O] Use better error and loading 2024-03-02 19:32:16 -05:00
Azalea
5d3194dd41 [O] Better loading and error display 2024-03-02 19:29:35 -05:00
Azalea
f2574b516e [F] Fix: Automatically redirect users if not logged in 2024-03-02 19:27:13 -05:00
Azalea
9ee3e973c1 [+] Add discord invite url 2024-03-02 19:26:55 -05:00
Azalea
eb9e797017 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-03-02 11:53:21 -05:00
Azalea
d0c305b3eb [+] Playlog api 2024-03-02 11:53:19 -05:00
Galexion
db2a7208da AquaNet: got typescript to stop yelling at me, it was driving me up a wall 2024-03-01 23:16:14 -05:00
Galexion
e8958f5e53 AquaNet: Added Loading Circle 2024-03-01 01:37:59 -05:00
Azalea
8acee1251f [+] Return card id in ranking 2024-03-01 00:45:57 -05:00
Azalea
acf117e43b [O] Use user{id} instead 2024-03-01 00:42:39 -05:00
Azalea
ed1b7f477b [F] Not extId 2024-03-01 00:40:57 -05:00
Azalea
63cf1f5fa1 [+] Allow querying card user ids 2024-03-01 00:38:33 -05:00
Azalea
fa9b738cba Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-03-01 00:30:38 -05:00
Azalea
3efbefe4c5 [+] Limit user creation 2024-03-01 00:30:37 -05:00
Galexion
dcb797db38 AquaNet: Make Registered users on Ranking Page link back to their user profile 2024-03-01 00:16:58 -05:00
Azalea
8f9f9e9e82 [F] Fix code overflow on mobile 2024-02-29 23:59:49 -05:00
Azalea
642754a46b Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-02-29 23:57:52 -05:00
Galexion
a5578335d3 AquaNet: Append to make Error Message h2 not use margin-top 2024-02-29 22:50:58 -05:00
Galexion
c4309aa14c AquaNet: Added an Error Message trigger to pages that didn't telegraph to the user that there was an error before. 2024-02-29 22:48:41 -05:00
Azalea
af3eb10034 [+] Cache ranking 2024-02-29 19:31:38 -05:00
Azalea
88b7804123 [O] Simplify userlogin 2024-02-29 18:31:05 -05:00
Azalea
4a383521d7 [+] Properly update last access time for card 2024-02-29 18:30:20 -05:00
Azalea
279b65cfa0 [+] Return username in ranking 2024-02-29 18:29:37 -05:00
Azalea
6ce644ea18 [F] Fix NPE when no play data is found 2024-02-29 16:57:23 -05:00
Azalea
9ef5e8d037 [F] Fix user id being null 2024-02-29 16:53:02 -05:00
Azalea
a9e14a93dd [F] Fix mai2 playlog by introducing a backlog 2024-02-29 16:24:56 -05:00
Azalea
8e2c0d8653 [F] Fix transition 2024-02-29 11:21:29 -05:00
Azalea
39a19fd9e6 [F] Fix accuracy calculation 2024-02-29 11:14:13 -05:00
Azalea
68e1a0489f [S] Style ranking 2024-02-29 11:07:44 -05:00
Azalea
ece64c3f4a [F] Fix ranking typing 2024-02-29 10:42:22 -05:00
Azalea
37f67469a6 [F] Fix keychip flow
q
2024-02-29 10:38:12 -05:00
Azalea
a1b546152b [-] Remove unnecessary tests 2024-02-29 10:30:48 -05:00
Azalea
3ae1f6c556 Merge branch 'v1-dev' of https://github.com/hykilpikonna/AquaDX into v1-dev 2024-02-29 10:30:00 -05:00
Azalea
14757e2a35 [F] Fix gradle build 2024-02-29 10:29:55 -05:00
Galexion
d20a762dd8 AquaNet Ranking Page: Dived and classed instead 2024-02-29 01:05:07 -05:00
Galexion
5ff79f5ee1 Leaderboard Rough Draft 2024-02-29 00:59:05 -05:00
Azalea
afe28733db [+] Setup instructions: Segatools configruation 2024-02-29 00:50:29 -05:00
Azalea
5e5fe6013d [+] Install shiki dependnecy 2024-02-29 00:49:53 -05:00
Azalea
3f0196c8f8 [+] Keychip sdk 2024-02-29 00:49:24 -05:00
Azalea
4e38cf9d40 [+] Keychip endpoint 2024-02-29 00:48:55 -05:00
Azalea
6026f6aebd [+] Export aqua connection host 2024-02-29 00:25:57 -05:00
Azalea
856bcf1647 [+] Aqua connection host in config 2024-02-29 00:25:06 -05:00
Azalea
8e7196181c [F] Fix accuracy 2024-02-29 00:08:48 -05:00
Azalea
beb6697507 [F] Fix all perfect count 2024-02-28 23:37:27 -05:00
Azalea
fd482d32a7 [+] Ranking endpoint 2024-02-28 22:09:53 -05:00
Azalea
10169b03ce [+] API Doc generator 2024-02-28 20:52:23 -05:00
Azalea
5f4a7cd7c9 [+] API documentation 2024-02-28 20:45:38 -05:00
Azalea
59b52b8a47 [F] Fix delete transaction 2024-02-28 20:08:45 -05:00
Azalea
02bffab38f [F] Fix #16 2024-02-28 20:07:31 -05:00
Azalea
256f08396f [+] Setup instrcutions page 2024-02-28 17:55:52 -05:00
Azalea
988a280111 [S] Blockquote styling 2024-02-28 17:55:44 -05:00
Azalea
7a44a457d5 [F] Fix another DIVA endpoint 2024-02-28 17:21:08 -05:00
Azalea
2fa153e569 [F] Fix diva request mapping 2024-02-27 23:47:05 -05:00
Azalea
b589c78cfc [+] More logging for Diva compression 2024-02-27 23:16:45 -05:00
Azalea
293acbcc03 [O] Hide unnecessarily long EOF error for Diva compression 2024-02-27 22:55:43 -05:00
Azalea
be0a841926 [F] Fix downloadOrder typo 2024-02-27 22:52:31 -05:00
Azalea
763cbfa656 [F] Fix full combo count 2024-02-27 22:46:49 -05:00
Azalea
d149b02c06 Merge pull request #14 from Sensokaku/patch-1
Fix playtime
2024-02-27 19:38:21 -05:00
Sensokaku
b83773dfa6 Fix playtime
Playtime wasn't being showing up correctly till I removed one of the 60's
2024-02-28 05:36:39 +08:00
Azalea
c992701387 [S] Fix level color 2024-02-27 13:34:06 -05:00
Azalea
bf43944c27 [S] Fix level color 2024-02-27 13:33:41 -05:00
Azalea
6dbed875e1 [F] Fix interface treated as repository 2024-02-27 13:21:16 -05:00
Azalea
5166387f34 [O] Generalize card migration 2024-02-27 13:17:27 -05:00
Azalea
b44121597f [F] Fix apis 2024-02-26 23:19:10 -05:00
Azalea
f086b8abe9 [+] Image on error 2024-02-26 23:18:03 -05:00
Azalea
795da9557b [O] Better url scheme 2024-02-26 20:17:39 -05:00
Azalea
76249cb8f7 [+] Chunithm adaptation 2024-02-26 20:17:30 -05:00
Azalea
f4c4162e4b [+] Add game api in sdk 2024-02-26 20:17:06 -05:00
Azalea
902cc9009e [M] Move scoring to separate file 2024-02-26 20:16:49 -05:00
Azalea
d93c2ee267 [+] More types 2024-02-26 20:16:31 -05:00
Azalea
e5b864f07e [+] No profile profile image 2024-02-26 20:16:17 -05:00
Azalea
8df4cd3dd6 [+] No cover cover image 2024-02-26 20:16:10 -05:00
Azalea
2fdb6f15cb Merge branch 'master' into v1-dev 2024-02-26 17:06:35 -05:00
Azalea
c7e493d7f5 [F] Fix null 2024-02-26 17:05:32 -05:00
Azalea
043537a7b4 [F] Fix null 2024-02-26 16:38:54 -05:00
Azalea
e7643f3894 [F] Fix beans 2024-02-26 15:14:11 -05:00
Azalea
bb2c8ae8e5 [+] Ongeki adaptor 2024-02-26 15:08:45 -05:00
Azalea
1c8860c596 [F] fix shown ranks 2024-02-26 15:08:22 -05:00
Azalea
66e65fcd14 [+] chu3 rating composition 2024-02-26 12:09:17 -05:00
Azalea
7cef8f24db [-] Remove mysql 2024-02-26 11:59:53 -05:00
Azalea
a82f3a7b07 [U] Add maimai 140 fields 2024-02-26 11:59:30 -05:00
Azalea
4f41068c99 Merge branch 'master' into v1-dev 2024-02-26 11:58:49 -05:00
Azalea
50dfb95c48 [+] Chusan user summary 2024-02-26 11:55:43 -05:00
Azalea
0b29ac00a7 [+] Generalize game trend & summary apis 2024-02-26 11:54:31 -05:00
Azalea
759519d374 [PR] #13 from Becods: Extra fields from bud
[+] Extra fields from bud
2024-02-26 10:40:14 -05:00
Becod
3d713b13da [+] Extra fields from bud 2024-02-26 21:42:00 +08:00
Azalea
fcbe52539a [F] Fix extra slash in url 2024-02-26 00:42:29 -05:00
Azalea
bcd64286cd [U] Update migration 2024-02-26 00:38:14 -05:00
Azalea
b89147120c [+] Find session when validating request 2024-02-26 00:32:38 -05:00
Azalea
c9ffd3cd11 [F] Fix overlapping filter 2024-02-26 00:29:15 -05:00
Azalea
cd62f31c17 [+] Add authentication token at poweron 2024-02-26 00:29:00 -05:00
Azalea
98d63b880b [U] Update default config 2024-02-26 00:28:30 -05:00
Azalea
04e11b0fea [+] Keychip session 2024-02-26 00:23:51 -05:00
Azalea
a873b28d9b [O] Reject unauthenticated requests 2024-02-25 23:21:34 -05:00
Azalea
b1b2ff6b8c [+] Securing allnet requests 2024-02-25 23:09:56 -05:00
Azalea
cb96b5fa8f [M] Move game URLs 2024-02-25 21:42:58 -05:00
Azalea
eb960209bf [+] Rewrite billing 2024-02-25 21:11:52 -05:00
Azalea
51a0e46f8c [+] Rewrite allnet 2024-02-25 20:23:06 -05:00
Azalea
1251205fdd [+] Map extensions 2024-02-25 20:22:09 -05:00
Azalea
9a05629144 [O] Reduce duplicate code 2024-02-25 17:58:04 -05:00
Azalea
e0c71006d5 [O] Remake maimai2 user summary api 2024-02-25 17:22:12 -05:00
Azalea
3d716a516a [M] Rename field 2024-02-25 17:21:50 -05:00
Azalea
096648b2d7 [O] Simplify class name for logging 2024-02-23 02:14:33 -05:00
Azalea
02e57707de Merge branch 'master' into v1-dev 2024-02-23 00:12:08 -05:00
Azalea
20468e612d Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-23 00:11:05 -05:00
Azalea
af3aa497d1 [O] Better understanding of the data type of different games 2024-02-22 23:51:22 -05:00
Azalea
bb53d1448b [O] Better logging 2024-02-22 23:50:58 -05:00
Azalea
eccdd73908 [+] Backend card linking 2024-02-22 22:45:24 -05:00
Azalea
1d4e1a8be2 [O] Better logging 2024-02-22 22:45:08 -05:00
Azalea
8dc0f299a9 [-] Remove global logger field 2024-02-22 22:38:21 -05:00
Azalea
db1ffd5091 [-] Remove unnecessary beans 2024-02-22 22:37:07 -05:00
Azalea
64a27e5708 [O] Speed up bootup by making email init async 2024-02-22 22:36:48 -05:00
Azalea
214a356135 [O] Better logging 2024-02-22 22:19:21 -05:00
Azalea
84f7953f21 [F] Fix encryption 2024-02-22 22:19:12 -05:00
Azalea
a9a947203d [O] Reduce code 2024-02-22 21:22:08 -05:00
Azalea
8f250e755e [O] Refactor AimeDB 2024-02-22 20:55:13 -05:00
Azalea
30a7fa7ead [O] Convert more stuff to kotlin 2024-02-22 19:47:35 -05:00
Azalea
4324d655d2 [M] Refactor relevant stuff to kotlin 2024-02-22 19:33:51 -05:00
Azalea
da1be9226a [S] Bottom padding 2024-02-22 19:11:25 -05:00
Azalea
5597bf5d1e [O] Show confirm dialog when unlinking a card 2024-02-22 19:06:18 -05:00
Azalea
50029fbb24 [F] Fix TS errors 2024-02-22 19:00:14 -05:00
Azalea
f5c2dc747d [+] Unlink frontend 2024-02-22 18:31:59 -05:00
Azalea
dd55e336e4 [+] API for unlinking 2024-02-22 18:20:54 -05:00
Azalea
a001a45cc4 [M] Rename: Bind -> Link 2024-02-22 18:19:55 -05:00
Azalea
49320ff623 [M] Move LinkCard component 2024-02-22 18:17:23 -05:00
Azalea
8e898c50b4 [O] Ghost cards should not be guessed 2024-02-22 18:14:58 -05:00
Azalea
5fa93e2a2a [F] Fix lateinit issue 2024-02-22 18:11:22 -05:00
Azalea
178cca1611 [O] Reduce bits for DIVA 2024-02-22 17:31:10 -05:00
Azalea
9e543e2c5a [+] Add isGhost to card 2024-02-22 17:29:28 -05:00
Azalea
6a16e5534d [+] Sanitize card id when creating card 2024-02-22 17:29:13 -05:00
Azalea
2d1cad870b [S] Always put ghost card at the top 2024-02-22 17:07:01 -05:00
Azalea
3fdf255ca5 [F] Use bigint 2024-02-22 17:06:50 -05:00
Azalea
ec9225dbf2 [+] Iconify tools 2024-02-22 17:06:40 -05:00
Azalea
b469fe92dd [+] Link card when not found 2024-02-22 16:39:00 -05:00
Azalea
dbc54b016c [O] Check if already linked 2024-02-22 12:30:28 -05:00
Azalea
2cbad36f80 [+] Check card is linked 2024-02-22 12:26:27 -05:00
Azalea
8b21f33eb6 [-] Remove sensitive info 2024-02-22 10:52:09 -05:00
Azalea
1e8ff7dbc0 [+] Create new card if not exists 2024-02-22 10:51:49 -05:00
Azalea
0937915839 [+] Link card limit on the backend 2024-02-22 10:51:22 -05:00
Azalea
a128546954 [+] Add field isBound to Card 2024-02-22 10:31:13 -05:00
Azalea
806953d107 [S] Unify transition style 2024-02-22 10:30:43 -05:00
Azalea
afa39b29ed [+] Implement link conflict resolution 2024-02-22 10:30:28 -05:00
Azalea
4c899555dd [S] Style overlay 2024-02-22 10:00:28 -05:00
Azalea
3e8395b0c6 [+] Show card conflicts 2024-02-22 10:00:18 -05:00
Azalea
a620f02d57 [-] Remove test dialect 2024-02-21 17:43:03 -05:00
Azalea
79a078fb70 [+] Automatically invalidate token on expiry 2024-02-21 17:42:42 -05:00
Azalea
cac2f49b06 [+] Card summary sdk 2024-02-21 17:42:15 -05:00
Azalea
06993b9d66 [S] Style existing cards, display card type 2024-02-21 15:03:49 -05:00
Azalea
fce5ca592a [S] Style existing cards 2024-02-21 14:26:12 -05:00
Azalea
e7058cf3c8 [+] Show existing cards 2024-02-21 14:14:38 -05:00
Azalea
a2eeac786e [+] /user/me sdk 2024-02-21 14:14:28 -05:00
Azalea
ff7873313b [+] Check while typing 2024-02-21 13:59:40 -05:00
Azalea
f3b06ac0a6 [+] Input validation for card SN 2024-02-21 13:47:58 -05:00
Azalea
00e57fc17d [+] Bind card element 2024-02-21 13:37:13 -05:00
Azalea
c8cc59aaca [S] Adjust input visibility 2024-02-21 13:37:01 -05:00
Azalea
54057922f6 [O] Better clazz 2024-02-21 13:36:21 -05:00
Azalea
c175173821 Merge pull request #12 from Zaphkito/master
Add maimai 140 h041 event data
2024-02-21 05:46:55 -05:00
zaphkito
52e9285551 Add maimai 140 h041 event data 2024-02-21 18:39:39 +08:00
Azalea
823eea1f0a [F] Fix card detection 2024-02-21 04:28:38 -05:00
Azalea
ae03a700de [S} Fix mobile view 2024-02-21 01:23:44 -05:00
Azalea
a089eade6e [S] Make letter spacing reponsive to font size 2024-02-21 01:15:52 -05:00
Azalea
f8fb3d8a70 [+] Router logo 2024-02-21 01:15:30 -05:00
Azalea
00a75f154e [F] Fix asking for email confirmation when email feature is disabled 2024-02-21 01:14:58 -05:00
Azalea
8d2313d799 [-] Remove comments in build.gradle 2024-02-21 00:44:44 -05:00
Azalea
0b8384fc3b [+] Home page 2024-02-21 00:42:04 -05:00
Azalea
73ab9efdb4 [S] Globalize content css 2024-02-21 00:15:31 -05:00
Azalea
705f69510b [F] Fix import, clear query param 2024-02-21 00:15:17 -05:00
Azalea
f7e0a33935 [+] Verify email workflow 2024-02-21 00:11:45 -05:00
Azalea
729015d719 [+] SDK confirmEmail 2024-02-21 00:11:31 -05:00
Azalea
d83127a265 [+] Check email confirmation on login 2024-02-21 00:02:58 -05:00
Azalea
6e8f7ae698 [F] Fix one-to-one relationship in email confirmation 2024-02-21 00:02:04 -05:00
Azalea
17ee24286c [+] Add a verify email state, state switching animation 2024-02-20 23:19:17 -05:00
Azalea
133140bf71 [O] Rename home to welcome 2024-02-20 18:46:15 -05:00
Azalea
5fafbf9ee8 [+] Login form 2024-02-20 18:44:15 -05:00
Azalea
6085da15a4 [S] Style input 2024-02-20 18:43:56 -05:00
Azalea
b93caf1839 [U] yarn upgrade 2024-02-20 18:43:42 -05:00
Azalea
c9787a521b [+] SDK 2024-02-20 18:41:59 -05:00
Azalea
26cabef74c [F] Export turnstile site key 2024-02-20 18:32:42 -05:00
Azalea
8d2474768b [S] More color variables 2024-02-20 18:32:25 -05:00
Azalea
a87146a401 [+] Svelte turnstile 2024-02-20 18:29:59 -05:00
Azalea
bbf5ee5395 [+] Add mariadb migration 2024-02-20 17:43:35 -05:00
Azalea
103ae607be [F] Forgot to save ;-; 2024-02-20 16:26:10 -05:00
Azalea
6f63998000 [O] Ignore serial id 2024-02-20 16:19:06 -05:00
Azalea
a94952babc [O] Reduce code 2024-02-20 16:18:43 -05:00
Azalea
4b8385419e [O] Limit exposure of fields 2024-02-20 16:16:11 -05:00
Azalea
878a543818 [F] Fix infinite recursion on serializing user card 2024-02-20 16:12:08 -05:00
Azalea
e7337777cd [F] Set last login time in login 2024-02-20 16:11:50 -05:00
Azalea
fa1ed52c32 [+] Bind card 2024-02-20 16:06:46 -05:00
Azalea
eda3fccb51 [F] Fix RNG 2024-02-20 16:01:07 -05:00
Azalea
ec55fae1ec [+] Settings API 2024-02-20 15:47:25 -05:00
Azalea
c88a98e355 [+] Separate user validator 2024-02-20 15:47:17 -05:00
Azalea
0567e0f251 [+] Add @ API macro 2024-02-20 15:46:48 -05:00
Azalea
befa7d0e8e [+] Extend jwt auth block 2024-02-20 15:45:25 -05:00
Azalea
91913da205 [+] Card summary 2024-02-20 02:01:15 -05:00
Azalea
cd8677a26d [+] Try lookup id function 2024-02-19 21:49:55 -05:00
Azalea
adf091e300 [O] Use randExtId 2024-02-19 21:49:09 -05:00
Azalea
aa3b831a68 [O] Separate randExtId 2024-02-19 21:48:42 -05:00
Azalea
ab075c0554 [F] Fix ghost card column 2024-02-19 21:46:43 -05:00
Azalea
4202012bbd [+] Confirm email 2024-02-19 21:05:24 -05:00
Azalea
739854935d [+] Create ghost card on registration 2024-02-19 21:05:17 -05:00
Azalea
3d88e734df [+] Add ghostCard for user 2024-02-19 21:04:57 -05:00
Azalea
a716a69b8b [F] Fix tests 2024-02-19 21:03:38 -05:00
Azalea
7ac7aacb6c [O] Refactor code 2024-02-19 20:58:16 -05:00
Azalea
7368001e3b [+] Send confirmation email on register 2024-02-19 06:59:32 -05:00
Azalea
574e0b4074 [+] Generate email 2024-02-19 06:59:13 -05:00
Azalea
5d258eb8e1 [+] Computed name for user 2024-02-19 06:59:00 -05:00
Azalea
5715fa97f7 [+] Email confirmation table 2024-02-19 06:58:51 -05:00
Azalea
7fe869b98b [+] Email template 2024-02-19 06:58:40 -05:00
Azalea
37aaa30387 [+] Add email web host option 2024-02-19 06:58:26 -05:00
Azalea
c3b2d7653f [+] Add email border 2024-02-19 06:34:59 -05:00
Azalea
cb22161156 [+] Add email confirmation field 2024-02-19 05:09:36 -05:00
Azalea
6ad06c2d75 [F] Fix json parsing 2024-02-19 05:09:23 -05:00
Azalea
b291dd0ad7 [-] Remove dialect 2024-02-19 04:54:44 -05:00
Azalea
3039a32f29 [F] Fix CORS 2024-02-19 04:51:01 -05:00
Azalea
a8f5380070 [-] Remove unnecessary mariadb dialect 2024-02-19 04:48:14 -05:00
Azalea
e37867b9db [O] Disable whitelabel error page 2024-02-19 04:43:09 -05:00
Azalea
110d6c81ee [+] Add /me endpoint 2024-02-19 04:42:50 -05:00
Azalea
3da44ce604 [-] Remove duplicate email self test 2024-02-19 04:42:11 -05:00
Azalea
9770c15188 [+] JWT.auth 2024-02-19 04:39:40 -05:00
Azalea
cc568d9569 [F] Fix token too short 2024-02-19 04:39:28 -05:00
Azalea
500a4b0b7e [M] Move security config 2024-02-19 04:35:08 -05:00
Azalea
55cfb7b358 [+] Login 2024-02-19 03:21:49 -05:00
Azalea
77b2f90259 [F] Fix response syntax limitation 2024-02-19 03:16:35 -05:00
Azalea
e962baaf48 [M] Move services to components package 2024-02-19 03:07:32 -05:00
Azalea
2cb5b18975 [+] JWT class 2024-02-19 03:06:42 -05:00
Azalea
32084eb1e7 [F] Fix: Email and username should ignore case 2024-02-19 03:06:30 -05:00
Azalea
2815d76b1d [+] JWT authentication settings 2024-02-19 02:40:00 -05:00
Azalea
9c4f146778 [O] More checks 2024-02-19 02:29:45 -05:00
Azalea
7b89016359 [F] Fix runtime dependency issue 2024-02-19 02:26:20 -05:00
Azalea
c7a4902af0 [F] Ignore geoip not found error 2024-02-19 02:26:01 -05:00
Azalea
6f9b686317 [F] Fix error reporting 2024-02-19 02:25:50 -05:00
Azalea
16550e7a83 [O] Better error messages 2024-02-19 02:25:41 -05:00
Azalea
a9aa47e390 [U] Update config 2024-02-19 01:57:56 -05:00
Azalea
0846fb94db [F] Fix null case 2024-02-19 01:57:42 -05:00
Azalea
4c3aafd266 [O] Unwrap spaghetti code 2024-02-19 01:49:29 -05:00
Azalea
1e606f8b85 [+] Add username check 2024-02-19 01:38:42 -05:00
Azalea
58596377b1 [+] Add username field 2024-02-19 01:38:30 -05:00
Azalea
94a3234874 [O] CORS allow all 2024-02-19 01:37:50 -05:00
Azalea
7e9db5b52d [+] GeoIP service 2024-02-19 01:36:53 -05:00
Azalea
dc098d1ec7 [O] Make data dir if not exist 2024-02-19 01:36:39 -05:00
Azalea
195a8b4315 [+] GeoLite settings 2024-02-19 01:36:18 -05:00
Azalea
7df80bc56a [+] GeoIP library 2024-02-19 01:35:34 -05:00
Azalea
1c541a4adf [F] Fix rating calculation 2024-02-19 00:39:43 -05:00
Azalea
f29f563e50 [F] Fix null pointer 2024-02-19 00:39:27 -05:00
Azalea
f4280c0768 Merge pull request #11 from Zaphkito/master
Maimai 140 h031 event data and charge data
2024-02-18 15:57:25 -05:00
zaphkito
295ae14658 Add maimai2 charge 2024-02-19 04:40:48 +08:00
zaphkito
ccc2bcffce Maimai 140 h031 event data 2024-02-19 04:12:53 +08:00
Azalea
3a94ef57e3 [+] Email service 2024-02-17 04:16:16 -05:00
Azalea
db8d8db280 [+] Email settings 2024-02-17 04:04:39 -05:00
Azalea
fdcef95d07 [+] Simple java mail 2024-02-17 04:04:14 -05:00
Azalea
7b1d9a777d [+] Email settings 2024-02-17 03:45:49 -05:00
Azalea
3cd8764dbf [+] Turnstile site key 2024-02-17 03:30:05 -05:00
Azalea
32826440cb [F] Fix spring autowire for AquaNetUser 2024-02-17 01:43:52 -05:00
Azalea
a65fa8cf10 [U] Finalize mysql migration guide 2024-02-17 01:33:34 -05:00
Azalea
0ae29b1920 [+] Write mysql migration guide 2024-02-17 01:07:23 -05:00
Azalea
5748a11788 [-] Completely drop mysql support 2024-02-17 01:07:09 -05:00
Azalea
7d3579af4f [-] Drop mysql support 2024-02-17 00:37:06 -05:00
Azalea
e0dc3bd1f4 [+] Validate captcha 2024-02-17 00:31:40 -05:00
Azalea
6200c56144 [+] User registration endpoint 2024-02-17 00:25:40 -05:00
Azalea
0b4a0eeb55 [+] AquaNetUser JPA entity 2024-02-17 00:25:19 -05:00
Azalea
467f5bd2eb [U] Update config 2024-02-17 00:25:04 -05:00
Azalea
322d90adfa [+] Automatic obtain request ip 2024-02-17 00:24:35 -05:00
Azalea
11eb7c058f [U] Update gitignore 2024-02-17 00:22:48 -05:00
Azalea
361b251952 [+] More extensions 2024-02-16 23:57:31 -05:00
Azalea
3d503971ae [+] Turnstile utility class 2024-02-16 23:56:12 -05:00
Azalea
9faabba361 [+] Turnstile settings 2024-02-16 23:55:44 -05:00
Azalea
f33629aba1 [+] Ktor dependency 2024-02-16 23:55:30 -05:00
Azalea
a47ed71799 [F] Fix typos in readme 2024-02-16 20:49:44 -08:00
Azalea
4a9b9d57e4 [O] Optimize imports 2024-02-16 17:51:36 -05:00
Azalea
98c3f0ce5b [F] Fix SNI 2024-02-16 17:45:41 -05:00
Azalea
78a3082bcb [U] Use kotlin entrypoint 2024-02-16 17:07:58 -05:00
Azalea
b64af43a7e [F] Fix security config 2024-02-16 17:07:45 -05:00
Azalea
1bcacbfebe [U] Update default config 2024-02-16 17:06:53 -05:00
Azalea
f32db6c83b [U] Update config 2024-02-16 17:06:39 -05:00
Azalea
437b638973 [M] javax -> jakarta 2024-02-16 17:05:43 -05:00
Azalea
670918efd3 [+] Migrate dependencies to Spring 3 2024-02-16 17:03:52 -05:00
Azalea
006a49cfdb [F] Fix dependency CVE 2024-02-16 15:54:09 -05:00
Azalea
9794ee259a [U] Upgrade gradle wrapper 2024-02-16 15:52:05 -05:00
Azalea
643e0e0c1f [O] Lint 2024-02-16 01:46:11 -05:00
Azalea
6afcb364d1 [+] Add eslint 2024-02-16 01:43:32 -05:00
Azalea
6d4a38404c [O] Sort recent by date, display level 2024-02-16 01:04:29 -05:00
Azalea
b925c2ef20 [U] Update readme 2024-02-12 11:26:37 -05:00
Azalea
e3f931d4f5 [PR] #10 from afonsopbarros: Update readme
Update AquaiMai README
2024-02-12 09:45:24 -05:00
Afonso
01b5d63972 Update AquaiMai README 2024-02-12 12:50:01 +01:00
Azalea
97794ce7c5 [S] Responsive chart 2024-02-12 04:49:02 -05:00
Azalea
ac4c66a1f7 [S] Fix home page 2024-02-12 04:48:51 -05:00
Azalea
9750e26d4b [F] Fix height 2024-02-12 04:35:02 -05:00
Azalea
4b14412190 Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-12 04:33:39 -05:00
Azalea
207c2e10e3 [F] Fix height 2024-02-12 04:33:35 -05:00
Azalea
7c73515427 [U] Add related projects 2024-02-12 00:49:07 -08:00
Azalea
aea7108940 [U] Yarn upgrade 2024-02-12 03:38:34 -05:00
Azalea
2bdd97d889 [O] Center page 2024-02-12 03:38:25 -05:00
Azalea
93b6dd3374 [S] Fix song name overflow 2024-02-12 03:20:09 -05:00
Azalea
bf3c123658 [S] Mobile 2024-02-12 03:03:07 -05:00
Azalea
06c0a361fd [S] Good color 2024-02-12 03:00:24 -05:00
Azalea
92510845d6 [S] Style recent 2024-02-12 03:00:16 -05:00
Azalea
8ab57859f6 [S] mobile 2024-02-12 03:00:07 -05:00
Azalea
0608b50193 [+] Display recent scores 2024-02-12 02:59:46 -05:00
Azalea
e14ff26915 [+] UI helper for class 2024-02-12 02:59:33 -05:00
Azalea
8932a16468 [O] Encapsulate all music 2024-02-12 02:59:25 -05:00
Azalea
3804db142f [S] Style UserHome 2024-02-12 02:04:25 -05:00
Azalea
7e198bd7a1 [+] Display more info on user page 2024-02-12 01:48:28 -05:00
Azalea
85301c92ec [O] Import font 2024-02-12 01:48:08 -05:00
Azalea
2ca6be77ed [+] Add summary to SDK 2024-02-12 01:23:32 -05:00
Azalea
48558bec0f [+] Even more info 2024-02-12 01:11:20 -05:00
Azalea
31af8669b5 [O] More info 2024-02-12 01:05:15 -05:00
Azalea
d716ee5d26 [O] Optimize query 2024-02-12 01:02:02 -05:00
Azalea
8e1c07d530 [+] maimai user summary 2024-02-12 00:53:20 -05:00
Azalea
0d7b52aadc [+] Sinmai dev notes 2024-02-11 23:30:15 -05:00
Azalea
25eb99f014 [O] Hide chartjs lables 2024-02-11 22:53:31 -05:00
Azalea
ab8276df2f [S] Style chartjs 2024-02-11 22:40:12 -05:00
Azalea
68569ad875 [O] Externalize cal heatmap 2024-02-11 22:40:04 -05:00
Azalea
c0e77d9eec [O] Switch to cal-heatmap 2024-02-11 22:32:10 -05:00
Azalea
27297c5d24 [O] Write readme 2024-02-10 06:43:01 -05:00
Azalea
b6a7a02b23 [+] Home view 2024-02-10 06:31:11 -05:00
Azalea
534e20a072 [+] Helper to register chartjs 2024-02-10 06:30:50 -05:00
Azalea
9478da81a9 [+] Maimai api endpoints 2024-02-10 06:30:22 -05:00
Azalea
ee958f20d2 [+] Declare user preview data type 2024-02-10 06:30:15 -05:00
Azalea
00edf44828 [+] Type declaration 2024-02-10 06:27:58 -05:00
Azalea
f3a49533fd [+] Add ChartJS, heatmap dependencies 2024-02-10 06:27:49 -05:00
Azalea
3b6517090c [+] Add play count to trend 2024-02-10 05:50:28 -05:00
Azalea
5b2687ae83 [+] New maimai api 2024-02-10 05:30:06 -05:00
Azalea
c3402e8d44 [O] Ignore errors on EOF 2024-02-10 05:29:53 -05:00
Azalea
a9625dfecd [F] Fix kotlin build 2024-02-10 05:29:31 -05:00
Azalea
d6c8464e97 [+] Favicon 2024-02-10 03:52:27 -05:00
Azalea
1b557d5f8c [S] Fix page height overflow 2024-02-10 03:10:21 -05:00
Azalea
e1cf944db7 [S] Fix layout 2024-02-10 03:06:18 -05:00
Azalea
cb873efd38 [S] Minor style issue 2024-02-10 02:59:54 -05:00
Azalea
ee5acfa35f [+] Navbar 2024-02-10 02:56:29 -05:00
Azalea
2904c55f84 [S] Optimize styling 2024-02-10 01:30:19 -05:00
Azalea
4667e9652f Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-10 01:24:05 -05:00
Azalea
905b4fe92e [S] Mobile friendly :3 2024-02-10 01:24:03 -05:00
Azalea
85ccc78f8f [S] Style home page 2024-02-10 01:20:10 -05:00
Azalea
ad3bb89dc9 [+] Home site 2024-02-10 00:40:56 -05:00
Azalea
106bded9b6 [F] Fix test failure 2024-02-09 19:39:09 -08:00
Azalea Gui
f46c8a03d9 [+] Character convert 2024-02-09 22:36:26 -05:00
Azalea Gui
5d9693c419 [U] Combine music jsons 2024-02-09 22:35:13 -05:00
Azalea Gui
126546a938 [O] Fix crash due to duplicate keys in maimai2_user_item 2024-02-09 22:34:16 -05:00
Azalea
bb3902730b [U] Update url 2024-02-09 21:55:08 -05:00
Azalea
1b9e25e81c [U] Update url 2024-02-09 21:43:53 -05:00
Azalea
b11439ca87 [U] Update url 2024-02-09 21:23:03 -05:00
Azalea
66034ea407 [O] Get userid dynamically 2024-02-09 21:05:53 -05:00
Azalea
6690c665dd [+] Actually query for data 2024-02-09 20:32:13 -05:00
Azalea
4b71cd9940 [O] Responsive 2024-02-08 03:32:33 -05:00
Azalea
8b5ef24681 [+] Show rating 2024-02-08 03:18:55 -05:00
Azalea
e161890eaa Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-08 02:53:58 -05:00
Azalea
348f27237b [+] Rating screen 2024-02-08 02:53:45 -05:00
Azalea
c19164269a [+] Add nginx configuration 2024-02-08 02:52:47 -05:00
Azalea
071491b459 [M] Rename 2024-02-08 02:51:31 -05:00
Azalea
e1180a9a14 Merge pull request #9 from hykilpikonna/tc21/fix-crash-slot-data-not-found
Fix crash during call to CommonMonitor.SetCharacterSlot
2024-02-07 23:19:28 -05:00
Tianyi Cao
f76a027b32 add a few more comments 2024-02-07 20:08:33 -08:00
Tianyi Cao
cc1a91e5cd Update and restore unintentionally-changed formatting 2024-02-07 20:04:15 -08:00
Tianyi Cao
fcee4d13da Fix crash during call to CommonMonitor.SetCharacterSlot 2024-02-07 20:02:39 -08:00
Azalea
fa567ce0e2 Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-07 22:59:58 -05:00
Azalea Gui
c10085b65a [+] Maimai data format convert 2024-02-07 22:22:58 -05:00
Azalea
a8465408cf [+] AquaTop: Create project 2024-02-07 20:13:53 -05:00
Azalea
d123a50054 [U] Detailed instructions
Closes #8
2024-02-07 16:39:46 -05:00
Azalea
bd09e4017a [F] Fix build 2024-02-07 16:36:18 -05:00
Azalea
8b5e29d29e Update nightly.yml 2024-02-07 13:35:12 -08:00
Azalea
6e427b060a [O] Better build script 2024-02-07 16:26:59 -05:00
Azalea
7d0f70f1c0 [U] Update documentation 2024-02-07 05:42:40 -05:00
Azalea
5f1ca64d65 [O] Apply patches using reflection 2024-02-07 05:40:13 -05:00
Azalea
fb58f08e44 [PR] #7 from tc21: skip-to-music
Add ability to skip a bunch of stuff directly to music selection
2024-02-07 05:26:17 -05:00
Tianyi Cao
017d00371d formatting 2024-02-07 02:22:45 -08:00
Tianyi Cao
abe1d3ad29 Merge branch 'master' into tc21/skip-to-music 2024-02-07 02:20:39 -08:00
Azalea
fc48ba1994 [U] Update readme 2024-02-07 05:18:47 -05:00
Tianyi Cao
279fe5dcb8 Add ability to skip a bunch of stuff directly to music selection 2024-02-07 02:17:00 -08:00
Azalea
ccb3f7ef34 [+] Add cheat config 2024-02-07 05:16:07 -05:00
Azalea
de12ec6548 [+] Ticket unlock patch 2024-02-07 05:15:48 -05:00
Azalea
14bd2480ce [+] Fix character selection crash 2024-02-07 05:15:18 -05:00
Azalea
8152b9ab0d [M] Migrate to tomlet 2024-02-07 04:06:58 -05:00
Azalea
e6e4782d51 [O] Wording consistency 2024-02-07 03:56:07 -05:00
Azalea
aa5f4fb986 [+] Add option 2024-02-07 03:53:13 -05:00
Azalea
aa4fe50eeb Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-07 03:50:19 -05:00
Azalea
df072f1c40 [F] Use yaml instead 2024-02-07 03:49:14 -05:00
Azalea
9b3e202eb8 [PR] Merge #6: Add single player mode
Add single player mode
2024-02-07 02:40:18 -05:00
Azalea
b403189afb [+] Config 2024-02-07 02:38:36 -05:00
Tianyi Cao
82d076b87d Add single player mode, enabled by default 2024-02-06 23:38:36 -08:00
Azalea
dcdf951ebc [+] Add Tomlyn dependency 2024-02-07 02:37:28 -05:00
Azalea
4c1f8e4005 [F] Fix actions 2024-02-07 02:00:14 -05:00
Azalea
672ecc7f0a [+] More libs 2024-02-07 01:59:12 -05:00
Azalea
81f05528d8 [F] Fix actions 2024-02-07 01:46:45 -05:00
Azalea
57214aadfc [F] Fix actions 2024-02-07 01:45:15 -05:00
Azalea
6209a0120b [F] Fix actions 2024-02-07 01:44:06 -05:00
Azalea
98dcb3fbcb [+] Encrypted DLL 2024-02-07 01:36:06 -05:00
Azalea
2211e1c816 Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-07 01:29:36 -05:00
Azalea
36bf37da8d [F] Fix actions 2024-02-06 22:17:54 -08:00
Azalea
21be74fb05 [M] Rename dotnet-desktop.yml to aquamai.yml 2024-02-06 22:17:21 -08:00
Azalea
870ca6cd7f Create dotnet-desktop.yml 2024-02-06 22:17:02 -08:00
Azalea
4e832a5eb2 Merge branch 'master' of https://github.com/hykilpikonna/AquaDX 2024-02-07 00:48:55 -05:00
Azalea
7b9c23c203 [O] Build nightly only when src change 2024-02-06 21:48:32 -08:00
Azalea
fc91807e07 [M] Move data to docs 2024-02-07 00:47:17 -05:00
Azalea
8aab359b0b [+] AquaMai: Init project 2024-02-07 00:45:47 -05:00
Azalea Gui
363c9ff028 [+] maimai DX Tickets 2024-02-06 06:13:08 -05:00
Azalea Gui
2a800a825b [U] Update readme 2024-02-06 05:52:51 -05:00
Azalea Gui
36c1b1e0dd [U] openai migrate 2024-02-06 05:26:45 -05:00
Azalea Gui
01d7cdc7de [F] Fix conflict 2024-02-06 05:26:32 -05:00
Azalea Gui
6f25cb9017 Merge Gamer2097/AquaDX : Updated game notes and added ongeki A084 to 108
99a66858 - Update game_specific_notes.md
10bf6012 - add ongeki A084 to 108

Co-authored-by: Gamer2097 <antoniomarco200564@gmail.com>
Co-authored-by: Gamer2097 <63710393+Gamer2097@users.noreply.github.com>
2024-02-06 05:25:38 -05:00
Azalea Gui
c4ff479af4 [+] Maimai 140 event data 2024-02-06 05:17:15 -05:00
Azalea Gui
cc8406cd79 [+] Maimai extract script 2024-02-06 05:16:56 -05:00
Azalea Gui
b94c1915a8 [+] Add kotlin 2024-02-06 05:16:41 -05:00
Gamer2097
99a6685845 Update game_specific_notes.md 2024-01-10 14:31:25 +01:00
Gamer2097
10bf60126e add ongeki A084 to 108 2024-01-08 15:24:34 +01:00
Azalea
14d8f0730f [U] Update readme 2023-12-28 14:20:01 -08:00
Azalea
618503ccf2 [O] Ignore non-existent release 2023-12-22 19:10:28 -05:00
Azalea
f64a837172 [O] Optimize gh actions 2023-12-22 19:06:01 -05:00
1199 changed files with 101955 additions and 126978 deletions

61
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
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:
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

View File

@@ -3,6 +3,7 @@ name: Gradle Build
on:
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
@@ -21,4 +22,6 @@ jobs:
uses: gradle/gradle-build-action@v2
- name: Build with Gradle
run: ./gradlew build
run: |
mkdir data
./gradlew build

View File

@@ -4,6 +4,8 @@ name: Nightly Build
on:
push:
branches: [master]
paths: ['src/**']
workflow_dispatch:
jobs:
build:
@@ -24,52 +26,26 @@ jobs:
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Manage Version
run: |
git fetch --prune --unshallow --tags
GIT_SHA="$(git rev-parse --short HEAD)"
CUR_TAG="$(git tag -l | grep 'nightly' | tail -1)"
VER="$(sed -n 's/version = "\(.*\)"/\1/p' build.gradle.kts)"
echo "SHORT_SHA=$GIT_SHA" >> $GITHUB_ENV
echo "VER=$VER" >> $GITHUB_ENV
if [[ -z $CUR_TAG ]]; then
echo "OLD_PRE_TAG=NULL" >> $GITHUB_ENV
else
echo "OLD_PRE_TAG=$CUR_TAG" >> $GITHUB_ENV
fi
- name: Build Artifact
run: |
./gradlew build
rm -rf build/libs/*-plain.jar
cp build/libs/*.jar "build/libs/aqua-nightly.jar"
run: bash ./tools/build.sh
- name: Generate Prerelease Release Notes
- name: Delete previous nightly release
run: |
echo '### Nightly Release' >> ReleaseNotes.md
echo 'This nightly release is automatically built by github actions.' >> ReleaseNotes.md
echo '### The latest five updates are:' >> ReleaseNotes.md
git log -"5" --format="- %H %s" | sed '/^$/d' >> ReleaseNotes.md
- name: Delete Old Prerelease
if: env.OLD_PRE_TAG != 'NULL'
uses: dev-drprasad/delete-tag-and-release@v1.0
with:
tag_name: ${{ env.OLD_PRE_TAG }}
gh release delete --cleanup-tag --yes --repo $GITHUB_REPOSITORY nightly || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish GitHub Release
uses: ncipollo/release-action@v1
with:
bodyFile: ReleaseNotes.md
artifacts: "build/libs/aqua-nightly.jar"
tag: "${{ env.VER }}-nightly"
bodyFile: "build/release/ReleaseNotes.md"
artifacts: "build/libs/aqua-nightly.jar,build/aqua-nightly.zip"
tag: "nightly"
token: ${{ secrets.GITHUB_TOKEN }}
draft: false
- name: Mark release undraft
run: |
gh release edit "${{ env.VER }}-nightly" --draft=false
gh release edit nightly --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

9
.gitignore vendored
View File

@@ -1,3 +1,4 @@
db/
web/
bin/
@@ -75,4 +76,10 @@ gradle-app.setting
### Gradle Patch ###
# Java heap dump
*.hprof
*.hprof
.jpb
src/main/resources/meta/*/*.json
*.log.*.gz
*.salive
test-diff
htmlReport

20
AquaNet/.editorconfig Normal file
View File

@@ -0,0 +1,20 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Matches multiple files: .ts, .json, .svelte .sass
[*.{json,svelte,ts,sass}]
indent_style = space
indent_size = 2
# Markdown files (e.g., README.md) often use a line length of 80 characters
[*.md]
max_line_length = 80
trim_trailing_whitespace = false

41
AquaNet/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,41 @@
// ..eslintrc.cjs example
module.exports = {
root: true,
env: {
browser: true,
es2023: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
],
ignorePatterns: ['dist', '..eslintrc.cjs'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
// Custom styling rules
'comma-dangle': ['warn', 'only-multiline'],
'indent': ['warn', 2],
'semi': ['warn', 'never'],
'quotes': ['warn', 'single'],
'arrow-parens': ['warn', 'as-needed'],
'linebreak-style': ['warn', 'unix'],
'object-curly-spacing': ['warn', 'always'],
'array-bracket-spacing': ["error", "always", {
"singleValue": false,
"objectsInArrays": false,
"arraysInArrays": false
}],
// Disabled rules
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-constant-condition': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
}

33
AquaNet/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# 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

893
AquaNet/.yarn/releases/yarn-4.1.1.cjs vendored Normal file

File diff suppressed because one or more lines are too long

20
AquaNet/README.md Normal file
View File

@@ -0,0 +1,20 @@
# AquaNet
This is the codebase for the new frontend of AquaDX.
This project is also heavily WIP, so more details will be added later on.
## Development
This project uses Svelte (NOT SvelteKit) + TypeScript + Sass, built using Vite. The preferred editor is VSCode.
### Running locally
First, you would need to install Node.js and bun.
Then, you would need to start your testing AquaDX server and configure the `aqua_host` in `src/libs/config.ts` to use your URL.
Please leave `data_host` unchanged if you're not sure what it is.
Finally, run:
```shell
bun install
bun run dev
```

BIN
AquaNet/bun.lockb Normal file

Binary file not shown.

45
AquaNet/index.html Normal file
View File

@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AquaDX.net</title>
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png">
<link rel="manifest" href="/assets/icons/site.webmanifest">
<link rel="mask-icon" href="/assets/icons/safari-pinned-tab.svg" color="#b3c6ff">
<link rel="shortcut icon" href="/assets/icons/favicon.ico">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-config" content="/assets/icons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- Font CSS -->
<link rel="stylesheet" href="/assets/fonts/Quicksand.400.css" />
<link rel="stylesheet" href="/assets/fonts/Quicksand.500.css" />
<!-- Primary Meta Tags -->
<meta name="title" content="AquaDX.net" />
<meta name="description" content="A certain magical arcade server 🪄" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://aquadx.net/" />
<meta property="og:title" content="AquaDX.net" />
<meta property="og:description" content="A certain magical arcade server 🪄" />
<meta property="og:image" content="https://aquadx.net/assets/meta/meta.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://aquadx.net/" />
<meta property="twitter:title" content="AquaDX.net" />
<meta property="twitter:description" content="A certain magical arcade server 🪄" />
<meta property="twitter:image" content="https://aquadx.net/assets/meta/meta.png" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,38 @@
server {
listen 443 ssl;
server_name aqua.example.com;
ssl_certificate /etc/nginx/certs/aqua.example.com.crt;
ssl_certificate_key /etc/nginx/certs/aqua.example.com.key;
# Absolute path to store the compiled AquaNet,
# there should be index.html in the directory.
location / {
root /var/www/html/AquaNet/;
try_files $uri $uri/ @router;
index index.html;
}
# Route for redirection to index.html
location @router{
rewrite ^.*$ /index.html last;
}
# If you have modified DATA_HOST,
# you will need to prepare your own resources.
# location /d/ {
# root /var/www/html/GameData/;
# }
# Reverse Proxy to your AquaDX.
location /aqua/ {
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1/;
proxy_buffering off;
}
}

45
AquaNet/package.json Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "aqua-net",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"lint": "eslint . --ext ts,tsx,svelte --max-warnings 0 --fix"
},
"devDependencies": {
"@iconify/svelte": "^4.1.0",
"@iconify/tools": "^4.1.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tsconfig/svelte": "^5.0.4",
"@types/d3": "^7.4.3",
"@types/wicg-file-system-access": "^2023.10.5",
"@unocss/svelte-scoped": "^0.65.2",
"chartjs-adapter-moment": "^1.0.1",
"eslint": "^9.17.0",
"eslint-plugin-svelte": "^2.46.1",
"sass": "^1.83.0",
"shiki": "^1.24.4",
"svelte": "^5",
"svelte-check": "^4",
"svelte-turnstile": "^0.9.0",
"tslib": "^2.8.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.2",
"unocss": "^0.65.2",
"vite": "^6.0.5"
},
"dependencies": {
"cal-heatmap": "^4.2.4",
"chart.js": "^4.4.7",
"d3": "^7.9.0",
"lxgw-wenkai-lite-webfont": "^1.7.0",
"modern-normalize": "^3.0.1",
"moment": "^2.30.1",
"svelte5-router": "^3.0.1"
},
"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
}

4499
AquaNet/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,27 @@
/* vietnamese */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(Quicksand.400.vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(Quicksand.400.latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(Quicksand.400.latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

View File

@@ -0,0 +1,27 @@
/* vietnamese */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(Quicksand.500.vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(Quicksand.500.latin-ext.woff2) format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(Quicksand.400.latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/assets/icons/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 4.6 MiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/assets/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

101
AquaNet/src/App.svelte Normal file
View File

@@ -0,0 +1,101 @@
<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 { USER } from "./libs/sdk";
import type { AquaNetUser } from "./libs/generalTypes";
import Settings from "./pages/User/Settings.svelte";
import { pfp } from "./libs/ui"
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
if (USER.isLoggedIn()) USER.me().then(m => me = m).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}
<a href="/home">home</a>
<div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
role="button" tabindex="0">maps</div>
<a href="/ranking">rankings</a>
{#if me}
<a href="/u/{me.username}">
<img alt="profile" class="pfp" use:pfp={me}/>
</a>
{/if}
</nav>
<Router {url}>
<Route path="/" component={Welcome} />
<Route path="/home" component={Home} />
<Route path="/ranking" component={Ranking} />
<Route path="/ranking/:game" component={Ranking} />
<Route path="/u/:username" component={UserHome} />
<Route path="/u/:username/:game" component={UserHome} />
<Route path="/settings" component={Settings} />
</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: 50%
object-fit: cover
.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>

349
AquaNet/src/app.sass Normal file
View File

@@ -0,0 +1,349 @@
@use "sass:color"
@use "vars"
@import 'lxgw-wenkai-lite-webfont/style.css'
html
height: 100%
body
font-family: vars.$font
line-height: 1.5
font-weight: 400
width: 100%
height: 100%
margin: 0
overflow-x: hidden
color-scheme: dark
color: rgba(255, 255, 255, 0.87)
background-color: vars.$c-bg
font-synthesis: none
text-rendering: optimizeLegibility
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
a
font-weight: 500
color: vars.$c-main
text-decoration: inherit
a:hover
color: vars.$c-main
h1
font-size: 3.2em
line-height: 1.1
.card
display: flex
flex-direction: column
border-radius: vars.$border-radius
padding: 12px 16px
background: vars.$ov-light
blockquote
$c1: rgba(255, 149, 149, 0.05)
$c2: rgba(255, 152, 152, 0.12)
background: repeating-linear-gradient(45deg, $c1, $c1 10px, $c2 10px, $c2 20px)
padding: 10px 20px 10px 20px
margin: 16px 0
border-left: solid #ff7c7c 3px
border-radius: vars.$border-radius
#app
width: 100%
height: 100%
main:not(.no-margin)
max-width: 1280px
margin: 0 auto
padding-bottom: 100px
button
border-radius: vars.$border-radius
border: 1px solid transparent
padding: 0.6em 1.2em
font-size: 1em
font-weight: 500
font-family: inherit
background-color: vars.$ov-lighter
opacity: 0.9
cursor: pointer
transition: vars.$transition
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)
.error
color: vars.$c-error
input
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
// 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.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
.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)

View File

@@ -0,0 +1,76 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import Icon from "@iconify/svelte";
export let color: string = '179, 198, 255'
export let icon: string
// Manually positioned icons
const iconPos = [
[1, 0.5, 2],
[6, 2, 1.5],
[-0.5, 4.5, 1.3],
[5, -0.5],
[3.5, 4.5],
[9.5, 0.3, 1.2],
[12.5, 2.5, 0.8],
[10, 4.4, 0.8],
]
</script>
<div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown>
<slot/>
<div class="icons">
{#each iconPos as [x, y, size], i}
<Icon icon={icon} style={`top: ${y}rem; right: ${x}rem; font-size: ${size || 1}em`} />
{/each}
</div>
</div>
<style lang="sass">
@use '../vars'
.action-card
overflow: hidden
padding: 1rem
border-radius: vars.$border-radius
box-shadow: 0 5px 5px 1px vars.$c-shadow
transition: all 0.2s ease
cursor: pointer
position: relative
background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%)
outline: 1px solid transparent
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0))
&:hover
box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow
transform: translateY(-3px)
// Drop shadow glow
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
outline-color: rgba(var(--card-color), 0.5)
:global(span)
font-size: 1.2rem
display: block
margin-bottom: 0.5rem
.icons
position: absolute
inset: 0
color: rgba(var(--card-color), 0.5)
font-size: 2rem
transition: all 0.2s ease
z-index: -1
mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%)
opacity: 0.8
@media (max-width: vars.$w-mobile)
opacity: 0.6
:global(> svg)
position: absolute
rotate: 20deg
</style>

View File

@@ -0,0 +1,62 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import Icon from "@iconify/svelte";
export let color: string = '179, 198, 255'
export let icon: string
</script>
<div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown>
<slot/>
<div class="icon">
<Icon icon={icon} />
</div>
</div>
<style lang="sass">
@use '../vars'
.action-card
overflow: hidden
padding: 1rem
border-radius: vars.$border-radius
box-shadow: 0 5px 5px 1px vars.$c-shadow
transition: all 0.2s ease
cursor: pointer
position: relative
background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%)
outline: 1px solid transparent
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0))
&:hover
box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow
transform: translateY(-3px)
// Drop shadow glow
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
outline-color: rgba(var(--card-color), 0.5)
:global(span)
font-size: 1.2rem
display: block
margin-bottom: 0.5rem
.icon
position: absolute
display: flex
color: rgba(var(--card-color), 0.5)
font-size: 4rem
right: 0
bottom: 0
padding: .5rem
transition: all 0.2s ease
z-index: -1
mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%)
opacity: 0.8
@media (max-width: vars.$w-mobile)
opacity: 0.6
</style>

View File

@@ -0,0 +1,64 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { slide } from "svelte/transition";
import { t } from "../libs/i18n";
import type { GenericGameSummary } from "../libs/generalTypes";
export let g: GenericGameSummary
const detail = Object.entries(g.detailedRanks).toSorted((a, b) => +b[0] - +a[0])
</script>
<div class="rank-detail-container fw-block" transition:slide>
<div>
<h2>{t("UserHome.RankDetail.Title")}</h2>
<table><tbody>
<!-- rankDetails: { Level : { Rank : Count } } -->
<!-- Rows are levels, columns are ranks -->
<!-- Headers -->
<tr>
<th>{t("UserHome.RankDetail.Level")}</th>
{#each Object.values(g.ranks) as rankMap}<th>{rankMap.name}</th>{/each}
</tr>
<!-- Data -->
{#each detail as [level, rankMap]}
<tr>
<td>{level}</td>
{#each Object.values(rankMap) as count}<td>{count}</td>{/each}
</tr>
{/each}
</tbody></table>
</div>
</div>
<style lang="sass">
@use "../vars"
.rank-detail-container
> div
margin: 1em auto
max-width: 500px
table
width: 100%
border-collapse: collapse
table-layout: fixed
th:not(:first-child)
background: vars.$grad-special
-webkit-background-clip: text
-webkit-text-fill-color: transparent
background-clip: text
color: vars.$c-main
padding: 0.5em
th, td
padding: 0.5em
text-align: center
&:first-child
color: vars.$c-main
</style>

View File

@@ -0,0 +1,143 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { slide } from "svelte/transition";
import { t } from "../libs/i18n";
import { type GameName, type ParsedComposition, roundFloor } from "../libs/scoring";
import { coverNotFound } from "../libs/ui";
import { tooltip } from "../libs/ui";
import useLocalStorage from "../libs/hooks/useLocalStorage.svelte";
export let game: GameName
export let p: ParsedComposition
const rounding = useLocalStorage("rounding", true)
</script>
<div class="map-detail-container" transition:slide>
<div class="scores">
<div>
<img src={p.img} alt="" on:error={coverNotFound} />
<div class="info">
<div class="first-line">
<div class="song-title">{p.name ?? t("UserHome.UnknownSong")}</div>
<span class={`lv level-${p.diffId === 10 ? 3 : p.diffId}`}>
{ p.difficulty ?? '-' }
</span>
</div>
<div class="second-line">
<span class={`rank-${p.rank[0]}`}>
<span class="rank-text">{p.rank.replace("p", "+")}</span>
<span class="rank-num" use:tooltip={(p.score / 10000).toFixed(4)}>
{rounding.value ? roundFloor(p.score, game, 1) : (p.score / 10000).toFixed(4)}%
</span>
</span>
{#if p.ratingChange !== undefined}
<span class="dx-change">{ p.ratingChange }</span>
{/if}
</div>
</div>
</div>
</div>
</div>
<style lang="sass">
@use "../vars"
vars.$gap: 20px
.map-detail-container
background-color: rgb(35,35,35)
border-radius: vars.$border-radius
overflow: hidden
.scores
display: flex
flex-direction: column
flex-wrap: wrap
gap: vars.$gap
// Image and song info
> div
display: flex
align-items: center
gap: 12px
max-width: 100%
box-sizing: border-box
img
width: 50px
height: 50px
border-radius: vars.$border-radius
object-fit: cover
// Song info and score
> div.info
flex: 1
display: flex
justify-content: space-between
overflow: hidden
flex-direction: column
.first-line
display: flex
flex-direction: row
// Limit song name to one line
.song-title
flex: 1
min-width: 0
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
// Make song score and rank not wrap
> div:last-child
white-space: nowrap
@media (max-width: vars.$w-mobile)
flex-direction: column
gap: 0
.rank-text
text-align: left
.rank-S
// Gold green gradient on text
background: vars.$grad-special
-webkit-background-clip: text
color: transparent
.rank-A
color: #ff8a8a
.rank-B
color: #6ba6ff
.lv
width: 30px
text-align: center
background: rgba(var(--lv-color), 0.6)
padding: 0 6px
border-radius: 0 vars.$border-radius 0 vars.$border-radius
// Inset shadow, like it's a paper below this card with a cut
box-shadow: inset 0 0 10px rgba(0,0,0,0.5)
span
display: inline-block
text-align: left
.second-line
display: flex
justify-content: space-between
align-items: center
// Vertical table-like alignment
span.rank-text
min-width: 40px
span.rank-num
min-width: 60px
span.dx-change
margin-right: 0.5rem
color: vars.$c-good
</style>

View File

@@ -0,0 +1,42 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import RatingCompSong from "./RatingCompSong.svelte";
import { parseComposition, type GameName } from "../libs/scoring";
import { type MusicMeta } from "../libs/generalTypes";
export let title: string;
export let comp: string | undefined;
export let allMusics: Record<string, MusicMeta>;
export let game: GameName;
export let top: number | undefined = undefined;
let split = comp?.split(",")?.filter(it => it.split(":")[0] !== '0')
?.map(it => parseComposition(it, allMusics, game))
if (top) split = split?.toSorted((a, b) => b.score - a.score).slice(0, top)
if (split) console.log("Split", split)
</script>
{#if split && comp}
<div>
<h2>{title}</h2>
<div class="rating-composition">
{#each split as p}
<div>
<RatingCompSong {p} {game}/>
</div>
{/each}
</div>
</div>
{/if}
<style lang="sass">
@use "../vars"
.rating-composition
display: grid
// 3 columns
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
gap: vars.$gap
</style>

View File

@@ -0,0 +1,87 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { fade } from 'svelte/transition'
import type { ConfirmProps } from "../libs/generalTypes";
import { DISCORD_INVITE } from "../libs/config";
import Icon from "@iconify/svelte";
import { t } from "../libs/i18n"
// Props
export let confirm: ConfirmProps | null = null
export let error: string | null
export let loading: boolean = false
</script>
{#if confirm}
<div class="overlay" transition:fade>
<div>
<h2>{confirm.title}</h2>
<span>{confirm.message}</span>
<div class="actions">
{#if confirm.cancel}
<!-- Svelte LSP is very annoying here -->
<button on:click={() => {
confirm && confirm.cancel && confirm.cancel()
// Set to null
confirm = null
}}>{t('action.cancel')}</button>
{/if}
<button on:click={() => confirm && confirm.confirm()} class:error={confirm.dangerous}>{t('action.confirm')}</button>
</div>
</div>
</div>
{/if}
{#if error}
<div class="overlay" transition:fade>
<div>
<h2 class="error">{t('status.error')}</h2>
<span>{t('status.error.hint')}<a href={DISCORD_INVITE}>{t('status.error.hint.link')}</a></span>
<span>{t('status.detail', { detail: error })}</span>
<div class="actions">
<button on:click={() => location.reload()} class="error">
{t('action.refresh')}
</button>
</div>
</div>
</div>
{/if}
{#if loading && !error}
<div class="overlay loading" transition:fade>
<Icon class="icon" icon="svg-spinners:pulse-2"/>
<span><span>LOADING</span></span>
</div>
{/if}
<style lang="sass">
.actions
display: flex
gap: 16px
button
width: 100%
.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>

View File

@@ -0,0 +1,76 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte'
export let triggeredBy: string
export let loading: boolean = false
let isHovered = false
let x: number, y: number
let targets: Element[] = []
onMount(() => {
targets = [...document.querySelectorAll(triggeredBy)]
targets.forEach((el) => {
el.addEventListener('mouseover', mouseOver)
el.addEventListener('mousemove', mouseMove)
el.addEventListener('mouseleave', mouseLeave)
})
if (targets.length === 0) {
console.warn(`No elements found with selector "${triggeredBy}"`)
}
})
onDestroy(() => {
targets.forEach((el) => {
el.removeEventListener('mouseover', mouseOver)
el.removeEventListener('mousemove', mouseMove)
el.removeEventListener('mouseleave', mouseLeave)
})
})
function mouseOver(event: MouseEvent) {
console.log('over')
isHovered = true
updatePosition(event)
}
function mouseMove(event: MouseEvent) {
console.log('move')
updatePosition(event)
}
function updatePosition(event: MouseEvent) {
x = event.pageX + 5
y = event.pageY + 20
}
function mouseLeave() {
console.log('leave')
isHovered = false
}
</script>
{#if isHovered}
<div style="top: {y}px; left: {x}px" class="tooltip" class:loading>
<slot />
</div>
{/if}
<style lang="sass">
@use "../vars"
.tooltip
position: absolute
z-index: 1000
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>

View File

@@ -0,0 +1,56 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import type { GenericGameSummary } from "../libs/generalTypes";
import { GAME } from "../libs/sdk";
import type { GameName } from "../libs/scoring";
import { pfp, pfpNotFound } from "../libs/ui";
export let username: string
export let game: GameName
export let setLoading: (loading: boolean) => void = () => {}
let data: GenericGameSummary
let error = ""
setLoading(true)
GAME.userSummary(username, game).then(d => data = d).catch(e => error = e).finally(_ => setLoading(false))
</script>
{#if !data}
<div>Loading...</div>
{:else if error}
<div>Error: {error}</div>
{:else}
<div class="user-card">
<img use:pfp={data.aquaUser} alt="Profile" />
<div class="details">
<span class="in-game-name">{data.name}</span>
<span class="username">@{username}</span>
</div>
</div>
{/if}
<style lang="sass">
@use "../vars"
.user-card
display: flex
align-items: center
gap: vars.$gap
.details
display: flex
flex-direction: column
justify-content: center
.username
font-size: 0.8em
img
width: 50px
height: 50px
border-radius: 50%
object-fit: cover
object-position: center
</style>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import { Chart, Tooltip, type ChartData, type ChartOptions } from 'chart.js';
import type { HTMLCanvasAttributes } from 'svelte/elements';
import 'chart.js/auto';
interface Props extends HTMLCanvasAttributes {
data: ChartData<'line', any, string>
options: ChartOptions<'line'>
}
const { data, options, ...rest }: Props = $props()
Chart.register(Tooltip)
let canvasElem: HTMLCanvasElement
let chart: Chart
$effect(() => {
chart = new Chart(canvasElem, { type: 'line', data, options })
return () => chart.destroy()
})
$effect(() => {
if (!chart) return
chart.data = data
chart.update()
})
</script>
<canvas bind:this={canvasElem} {...rest}></canvas>

View File

@@ -0,0 +1,456 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import {
type AquaNetUser,
type UserBox,
type UserItem,
} from "../../libs/generalTypes";
import { DATA, USER, USERBOX } from "../../libs/sdk";
import { t, ts } from "../../libs/i18n";
import { DATA_HOST, FADE_IN, FADE_OUT, HAS_USERBOX_ASSETS } 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 { filter } from "d3";
import { coverNotFound } from "../../libs/ui";
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";
let user: AquaNetUser
let [loading, error, submitting, preview] = [true, "", "", ""]
let changed: string[] = [];
// Available (unlocked) options for each kind of item
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
let allItems: Record<string, Record<string, { name: string }>> = {}
let iKinds = { namePlate: 1, frame: 2, trophy: 3, mapIcon: 8, systemVoice: 9, avatarAccessory: 11 }
// In userbox: 'nameplateId', 'frameId', 'trophyId', 'mapIconId', 'voiceId', 'avatar{Wear/Head/Face/Skin/Item/Front/Back}'
let userbox: UserBox
let avatarKinds = ['Wear', 'Head', 'Face', 'Skin', 'Item', 'Front', 'Back'] as const
// iKey should match allItems keys, and ubKey should match userbox keys
let userItems: { iKey: string, ubKey: keyof UserBox, items: UserItem[] }[] = []
// 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
userItems = Object.entries(iKinds).flatMap(([iKey, iKind]) => {
if (iKey != 'avatarAccessory') {
let ubKey = `${iKey}Id`
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)
}]
}
return avatarKinds.map((aKind, i) => {
let items = profile.items.filter(x => x.itemKind === iKind && Math.floor(x.itemId / 100000) % 10 === i + 1)
return { iKey, ubKey: `avatar${aKind}` as keyof UserBox, items }
})
})
allItems = await DATA.allItems('chu3').catch(_ => {
loading = false
error = t("userbox.error.nodata")
}) as typeof allItems
console.log("User Items", userItems)
console.log("All items", allItems)
console.log("Userbox", userbox)
loading = false
}
USER.me().then(u => {
if (!u) throw new Error(t("userbox.error.nodata"))
user = u
return fetchData()
}).catch((e) => { loading = false; error = e.message });
let DDSreader: DDS | undefined;
let USERBOX_PROGRESS = 0;
let USERBOX_SETUP_RUN = false;
let USERBOX_SETUP_TEXT = t("userbox.new.setup");
let USERBOX_ENABLED = useLocalStorage("userboxNew", false);
let USERBOX_INSTALLED = false;
let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype;
type OnlyNumberPropsOf<T extends Record<string, any>> = {[Prop in keyof T as (T[Prop] extends number ? Prop : never)]: T[Prop]}
let userboxSelected: keyof OnlyNumberPropsOf<UserBox> = "avatarWear";
const userboxNewOptions = ["systemVoice", "frame", "trophy", "mapIcon"]
async function userboxSafeDrop(event: Event & { currentTarget: EventTarget & HTMLInputElement; }) {
if (!event.target) return null;
let input = event.target as HTMLInputElement;
let folder = input.webkitEntries[0];
error = await userboxFileProcess(folder, (progress: number, progressString: string) => {
USERBOX_SETUP_TEXT = progressString;
USERBOX_PROGRESS = progress;
}) ?? "";
}
indexedDB.databases().then(async (dbi) => {
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
if (databaseExists) {
await initializeDb();
DDSreader = new DDS(ddsDB);
USERBOX_INSTALLED = databaseExists;
}
})
</script>
<StatusOverlays {error} loading={loading || !!submitting} />
{#if !loading && !error}
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
<h2>{t("userbox.header.general")}</h2>
<GameSettingFields game="chu3"/>
<h2>{t("userbox.header.userbox")}</h2>
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
<div class="fields">
{#each userItems as { iKey, ubKey, items }, i}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
</div>
</div>
{/each}
</div>
{:else}
<div class="chuni-userbox-container">
<ChuniUserplateComponent on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level} chuniRating={userbox.playerRating / 100}
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
<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 class="chuni-userbox-row">
{#each avatarKinds as avatarKind}
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${userbox[`avatar${avatarKind}`].toString().padStart(8, "0")}`) then imageURL}
<button on:click={() => userboxSelected = `avatar${avatarKind}`}>
<img src={imageURL} class={userboxSelected == `avatar${avatarKind}` ? "focused" : ""} alt={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name} title={allItems.avatarAccessory[userbox[`avatar${avatarKind}`]].name}>
</button>
{/await}
{/each}
</div>
<div class="chuni-userbox">
{#if userboxSelected == "nameplateId"}
{#each userItems.find(f => f.ubKey == "nameplateId")?.items ?? [] as item}
{#await DDSreader?.getFile(`nameplate:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
<button class="nameplate" on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
<img src={imageURL} alt={allItems.namePlate[item.itemId].name} title={allItems.namePlate[item.itemId].name}>
</button>
{/await}
{/each}
{:else}
{#each userItems.find(f => f.ubKey == userboxSelected)?.items ?? [] as item}
{#await DDSreader?.getFile(`avatarAccessoryThumbnail:${item.itemId.toString().padStart(8, "0")}`) then imageURL}
<button on:click={() => {userbox[userboxSelected] = item.itemId; submit(userboxSelected)}}>
<img src={imageURL} alt={allItems.avatarAccessory[item.itemId].name} title={allItems.avatarAccessory[item.itemId].name}>
</button>
{/await}
{/each}
{/if}
</div>
<div class="fields">
{#each userItems.filter(i => userboxNewOptions.includes(i.iKey)) as { iKey, ubKey, items }, i}
<div class="field">
<label for={ubKey}>{ts(`userbox.${ubKey}`)}</label>
<div>
<select bind:value={userbox[ubKey]} id={ubKey} on:change={() => changed = [...changed, ubKey]}>
{#each items as option}
<option value={option.itemId}>{allItems[iKey][option.itemId]?.name || `(unknown ${option.itemId})`}</option>
{/each}
</select>
{#if changed.includes(ubKey)}
<button transition:slide={{axis: "x"}} on:click={() => submit(ubKey)} disabled={!!submitting}>
{t("settings.profile.save")}
</button>
{/if}
</div>
</div>
{/each}
</div>
{/if}
{#if HAS_USERBOX_ASSETS}
{#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>
{/if}
{#if USERBOX_SUPPORT}
<p>
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
</p>
{/if}
{#if !USERBOX_SUPPORT || !USERBOX_INSTALLED || !USERBOX_ENABLED.value}
<h2>{t("userbox.header.preview")}</h2>
<p class="notice">{t("userbox.preview.notice")}</p>
<input bind:value={preview} placeholder={t("userbox.preview.url")}/>
{#if preview}
<div class="preview">
{#each userItems.filter(v => v.iKey != 'trophy' && v.iKey != 'systemVoice') as { iKey, ubKey, items }, i}
<div>
<span>{ts(`userbox.${ubKey}`)}</span>
<img src={`${preview}/${iKey}/${userbox[ubKey].toString().padStart(8, '0')}.png`} alt="" on:error={coverNotFound} />
</div>
{/each}
</div>
{/if}
{/if}
{/if}
</div>
{/if}
{#if USERBOX_SETUP_RUN && !error}
<div class="overlay" transition:fade>
<div>
<h2>{t('userbox.new.name')}</h2>
<span>{USERBOX_SETUP_TEXT}</span>
<div class="actions">
{#if USERBOX_PROGRESS != 0}
<div class="progress">
<div class="progress-bar" style="width: {USERBOX_PROGRESS}%"></div>
</div>
{:else}
<button class="drop-btn">
<input type="file" on:input={userboxSafeDrop} on:click={e => e.preventDefault()}>
{t('userbox.new.drop')}
</button>
<button on:click={() => USERBOX_SETUP_RUN = false}>
{t('back')}
</button>
{/if}
</div>
</div>
</div>
{/if}
<style lang="sass">
@use "../../vars"
input
width: 100%
h2
margin-bottom: 0.5rem
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
.drop-btn
position: relative
width: 100%
aspect-ratio: 3
background: transparent
box-shadow: 0 0 1px 1px vars.$ov-lighter
margin-bottom: 1em
> input
position: absolute
top: 0
left: 0
width: 100%
height: 100%
opacity: 0
.preview
margin-top: 32px
display: flex
flex-wrap: wrap
justify-content: space-between
gap: 32px
> div
position: relative
width: 100px
height: 100px
overflow: hidden
background: vars.$ov-lighter
border-radius: vars.$border-radius
span
position: absolute
bottom: 0
width: 100%
text-align: center
z-index: 10
background: rgba(0, 0, 0, 0.2)
backdrop-filter: blur(2px)
img
position: absolute
inset: 0
width: 100%
height: 100%
object-fit: contain
.fields
display: flex
flex-direction: column
gap: 12px
width: 100%
flex-grow: 0
label
display: flex
flex-direction: column
select
width: 100%
.field
display: flex
flex-direction: column
width: 100%
label
max-width: max-content
> div:not(.bool)
display: flex
align-items: center
gap: 1rem
margin-top: 0.5rem
> select
flex: 1
.field.boolean
display: flex
flex-direction: row
align-items: center
gap: 1rem
width: auto
input
width: auto
aspect-ratio: 1 / 1
label
display: flex
flex-direction: column
max-width: max-content
.desc
opacity: 0.6
/* AquaBox */
.chuni-userbox-row
width: 100%
display: flex
button
padding: 0
margin: 0
width: 100%
flex: 0 1 100%
background: none
aspect-ratio: 1
img
width: 100%
filter: brightness(50%)
&.focused
filter: brightness(75%)
.chuni-userbox
width: calc(100% - 20px)
height: 350px
display: flex
flex-direction: row
flex-wrap: wrap
padding: 10px
background: vars.$c-bg
border-radius: 16px
overflow-y: auto
margin-bottom: 15px
justify-content: center
button
padding: 0
margin: 0
width: 20%
align-self: flex-start
background: none
aspect-ratio: 1
img
width: 100%
&.nameplate
width: 50%
aspect-ratio: unset
border: none
.chuni-userbox-container
display: flex
align-items: center
justify-content: center
@media (max-width: 1000px)
.chuni-userbox-container
flex-wrap: wrap
</style>

View File

@@ -0,0 +1,76 @@
<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";
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}
<div class="field {field.type.toLowerCase()}">
{#if field.type === "Boolean"}
<input id={field.key} type="checkbox" bind:checked={field.value}
on:change={() => submitGameOption(field.key, field.value)}/>
<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 === "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={() => submitGameOption(field.key, field.value)}/>
{/if}
</div>
{/each}
</div>
<StatusOverlays {error} loading={!gameFields.length || !!submitting}/>
<style lang="sass">
.fields
display: flex
flex-direction: column
gap: 12px
.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>

View File

@@ -0,0 +1,57 @@
<script>
import { fade } from "svelte/transition";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import GameSettingFields from "./GameSettingFields.svelte";
import { ts } from "../../libs/i18n";
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
const rounding = useLocalStorage("rounding", true);
</script>
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
<GameSettingFields game="general"/>
<div class="field">
<div class="bool">
<input id="rounding" type="checkbox" bind:checked={rounding.value}/>
<label for="rounding">
<span class="name">{ts(`settings.fields.rounding.name`)}</span>
<span class="desc">{ts(`settings.fields.rounding.desc`)}</span>
</label>
</div>
</div>
</div>
<style lang="sass">
.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
flex: 1
</style>

View File

@@ -0,0 +1,110 @@
<script lang="ts">
import { slide, fade } from "svelte/transition";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import { t } from "../../libs/i18n.js";
import Icon from "@iconify/svelte";
import StatusOverlays from "../StatusOverlays.svelte";
import { GAME } from "../../libs/sdk";
const profileFields = [
['name', t('settings.mai2.name')],
]
export let username: string;
let error: string
let submitting = ""
let values = Array(profileFields.length).fill('')
let changed: string[] = []
GAME.userSummary(username, 'mai2').then(({name}) => {
values = [name]
}).catch(e => error = e.message)
function submit(field: string, value: string) {
if (submitting) return
submitting = field
switch (field) {
case 'name':
GAME.changeName('mai2', value).then(({newName}) => {
changed = changed.filter(c => c !== field)
values = [newName]
}).catch(e => error = e.message).finally(() => submitting = "")
break
}
}
function exportData() {
submitting = "export"
GAME.export('mai2')
.then(data => download(JSON.stringify(data), `AquaDX_maimai2_export_${values[0]}.json`))
.catch(e => error = e.message)
.finally(() => submitting = "")
}
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();
}
</script>
<div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}>
{#each profileFields as [field, name], i (field)}
<div class="field">
<label for={field}>{name}</label>
<div>
<input id={field} type="text"
bind:value={values[i]} on:input={() => changed = [...changed, field]}
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
{#if changed.includes(field) && values[i]}
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, values[i])}>
{#if submitting === field}
<Icon icon="line-md:loading-twotone-loop"/>
{:else}
{t('settings.profile.save')}
{/if}
</button>
{/if}
</div>
</div>
{/each}
<button class="exportButton" on:click={exportData}>
<Icon icon="bxs:file-export"/>
{t('settings.export')}
</button>
</div>
<StatusOverlays {error} loading={!values[0] || !!submitting}/>
<style lang="sass">
.fields
display: flex
flex-direction: column
gap: 12px
.field
display: flex
flex-direction: column
label
max-width: max-content
> div:not(.bool)
display: flex
align-items: center
gap: 1rem
margin-top: 0.5rem
> input
flex: 1
.exportButton
display: flex
justify-content: center
align-items: center
gap: 5px
</style>

View File

@@ -0,0 +1,9 @@
<script>
import { fade } from "svelte/transition";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import GameSettingFields from "./GameSettingFields.svelte";
</script>
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
<GameSettingFields game="wacca"/>
</div>

View File

@@ -0,0 +1,165 @@
<script lang="ts">
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">
{/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">
{/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">
{/await}
<!-- Arms (surfboard) -->
{#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">
{/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">
{/await}
<!-- 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">
{/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">
{/await}
{#if chuniHead == 1200001}
<!-- If wearing original hat, add the feather and attachment -->
{#await DDSreader.getFileFromSheet("surfboard:CHU_UI_Common_Avatar_body_00.dds", 104, 153, 57, 58, 0.75) then imageURL}
<img class="chuni-penguin-head-2 chuni-penguin-accessory" src={imageURL} alt="Head2">
{/await}
{#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">
{/await}
{/if}
<!-- 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)">
{/await}
<!-- Item -->
{#await DDSreader.getFileScaled(`avatarAccessory:${chuniItem.toString().padStart(8, "0")}`, 0.75, `avatarAccessory:01500001`) then imageURL}
<img class="chuni-penguin-item chuni-penguin-accessory" src={imageURL} alt="Item">
{/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">
{/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">
{/await}
</div>
<div class="chuni-penguin-feet">
<!-- Feet -->
{#await DDSreader.getFileFromSheet(`avatarAccessory:${chuniSkin.toString().padStart(8, "0")}`, 0, 410, 167, 80, 0.75) then imageURL}
<img src={imageURL} alt="Feet">
{/await}
</div>
</div>
<!-- Truly sorry for the horrors below -->
<style lang="sass">
@keyframes chuniPenguinBodyBob
0%
transform: translate(-50%, 0%) translate(0%, -50%)
50%
transform: translate(-50%, 10px) translate(0%, -50%)
100%
transform: translate(-50%, 0%) 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
.chuni-penguin
height: 512px
aspect-ratio: 1/2
position: relative
.chuni-penguin-body, .chuni-penguin-feet
transform: translate(-50%, -50%)
position: absolute
left: 50%
.chuni-penguin-body
top: 50%
z-index: 1
animation: chuniPenguinBodyBob 2s infinite cubic-bezier(0.45, 0, 0.55, 1)
.chuni-penguin-feet
top: 82.5%
z-index: 0
.chuni-penguin-arm
transform-origin: 95% 10%
position: absolute
top: 40%
.chuni-penguin-arm-left
left: 0%
animation: chuniPenguinArmLeft 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1)
.chuni-penguin-arm-right
left: 70%
animation: chuniPenguinArmRight 1.5s infinite cubic-bezier(0.45, 0, 0.55, 1)
.chuni-penguin-accessory
transform: translate(-50%, -50%)
position: absolute
top: 50%
left: 50%
.chuni-penguin-eyes
top: 22.5%
.chuni-penguin-beak
top: 29.5%
.chuni-penguin-wear
top: 57.5%
.chuni-penguin-head
top: 7.5%
z-index: 10
.chuni-penguin-head-2
top: 12.5%
.chuni-penguin-head-3
top: -12.5%
.chuni-penguin-face-accessory
top: 27.5%
.chuni-penguin-back
z-index: -1
</style>

View File

@@ -0,0 +1,137 @@
<script lang="ts">
import { DDS } from "../../../libs/userbox/dds"
import { ddsDB } from "../../../libs/userbox/userbox"
const DDSreader = new DDS(ddsDB);
export var chuniLevel: number = 1
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"
</script>
{#await DDSreader?.getFile(`nameplate:${chuniNameplate.toString().padStart(8, "0")}`) then nameplateURL}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div on:click class="chuni-nameplate" style:background={`url(${nameplateURL})`}>
{#await DDSreader?.getFile(`characterThumbnail:${chuniCharacter.toString().padStart(6, "0")}`) 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">
{chuniTrophyName}
<img src={trophyURL} class="chuni-trophy-bg" alt="Trophy">
</div>
{/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">
RATING
<span class="chuni-user-rating-number">
{chuniRating}
</span>
</div>
</div>
</div>
{/await}
<style lang="sass">
@use "../../../vars"
.chuni-nameplate
width: 576px
height: 228px
position: relative
font-size: 16px
/* Overlap penguin avatar when put side to side */
z-index: 2
cursor: pointer
.chuni-trophy
width: 410px
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: sans-serif
font-weight: bold
z-index: 1
text-shadow: 0 1px white
img
width: 100%
height: 100%
position: absolute
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: sans-serif
font-weight: bold
.chuni-user-name
flex: 1 0 65%
box-shadow: 0 1px 0 #ccc
.chuni-user-level
font-size: 2em
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: #ddf
.chuni-user-rating-number
font-size: 1.5em
margin-left: 10px
</style>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
import { slide } from "svelte/transition";
import { ts } from "../../libs/i18n";
export let field: {key: string, value: string, changed?: boolean};
export let callback: () => Promise<boolean>;
</script>
<div class="field">
<input id={field.key} type="text" bind:value={field.value}
on:input={() => field.changed = true}/>
{#if field.changed}
<button on:click={async () => { if (await callback()) field.changed = false } }
transition:slide={{axis: 'x'}}>
{ts('settings.profile.save')}
</button>
{/if}
</div>
<style lang="sass">
.field
display: flex
align-items: center
gap: 1rem
width: 100%
input
flex: 1
</style>

View File

@@ -0,0 +1,19 @@
export const AQUA_HOST = 'https://aquadx.net/aqua'
export const DATA_HOST = 'https://aquadx.net'
// This will be displayed for users to connect from the client
export const AQUA_CONNECTION = 'aquadx.hydev.org'
export const TURNSTILE_SITE_KEY = '0x4AAAAAAASGA2KQEIelo9P9'
export const DISCORD_INVITE = 'https://discord.gg/FNgveqFF7s'
export const TELEGRAM_INVITE = 'https://t.me/+zBL4RZdyfvUzZGU1'
export const QQ_INVITE = 'https://qm.qq.com/q/wvNXbXbHbO'
// UI
export const FADE_OUT = { duration: 200 }
export const FADE_IN = { delay: 400 }
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
// USERBOX_ASSETS
export const HAS_USERBOX_ASSETS = true

View File

@@ -0,0 +1,155 @@
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
lastLogin: number
regTime: number
profileLocation: string
profileBio: string
profilePicture: string
emailConfirmed: boolean
ghostCard: Card
cards: Card[]
computedName: string,
password: string,
optOutOfLeaderboard: boolean,
}
export interface CardSummaryGame {
name: string
rating: number
lastLogin: string
}
export interface CardSummary {
mai2: CardSummaryGame | null
chu3: CardSummaryGame | null
ongeki: CardSummaryGame | null
diva: CardSummaryGame | null
wacca: CardSummaryGame | null
}
export interface ConfirmProps {
title: string
message: string
confirm: () => void
cancel?: () => void
dangerous?: boolean
}
export interface GenericGamePlaylog {
musicId: number
level: number
playDate: string
achievement: number
maxCombo: number
totalCombo: number
afterRating: number
beforeRating: number
isFullCombo?: boolean
isAllPerfect?: boolean
isAllJustice?: boolean
}
export interface GenericRanking {
name: string
username: string
rank: number
accuracy: number
rating: number
fullCombo: number
allPerfect: number
lastSeen: string
}
export interface RankCount {
name: string
count: number
}
export interface GenericGameSummary {
name: string
iconId: number
aquaUser?: AquaNetUser
serverRank: number
accuracy: number
rating: number
ratingHighest: number
ranks: RankCount[]
detailedRanks: { [key: number]: { [key: string]: number } }
maxCombo: number
fullCombo: number
allPerfect: number
totalScore: number
plays: number
totalPlayTime: number
joined: string
lastSeen: string
lastVersion: string
ratingComposition: { [key: string]: any }
recent: GenericGamePlaylog[]
rival?: boolean
}
export interface MusicMeta {
name: string,
composer: string,
bpm: number,
ver: number,
notes: {
lv: number
designer: string
lv_id: number
notes: number
}[],
worldsEndTag?: string
worldsEndStars?: number
}
export type AllMusic = { [key: string]: MusicMeta }
export interface GameOption {
key: string
value: any
type: 'Boolean' | 'String'
game: string
changed?: boolean
}
export interface UserItem { itemKind: number, itemId: number, stock: number }
export interface UserBox {
userName: string,
nameplateId: number,
frameId: number,
characterId: number,
trophyId: number,
mapIconId: number,
voiceId: number,
avatarWear: number,
avatarHead: number,
avatarFace: number,
avatarSkin: number,
avatarItem: number,
avatarFront: number,
avatarBack: number,
level: number
playerRating: number
}

View File

@@ -0,0 +1,24 @@
import { onMount } from 'svelte';
const useLocalStorage = <T>(key: string, initialValue: T) => {
let value = initialValue;
const currentValue = localStorage.getItem(key);
if (currentValue) value = JSON.parse(currentValue);
const save = () => {
localStorage.setItem(key, JSON.stringify(value));
};
return {
get value() {
return value;
},
set value(v: T) {
value = v;
save();
},
};
};
export default useLocalStorage;

52
AquaNet/src/libs/i18n.ts Normal file
View File

@@ -0,0 +1,52 @@
import { EN_REF, type LocalizedMessages } from "./i18n/en_ref";
import { ZH } from "./i18n/zh";
import type { GameName } from "./scoring";
type Lang = 'en' | 'zh'
const msgs: Record<Lang, LocalizedMessages> = {
en: EN_REF,
zh: ZH
}
let lang: Lang = 'en'
// Infer language from browser
if (navigator.language.startsWith('zh')) {
lang = 'zh'
}
export function ts(key: string, variables?: { [index: string]: any }) {
return t(key as keyof LocalizedMessages, variables)
}
/**
* Load the translation for the given key
*
* TODO: Check for translation completion on build
*
* @param key
* @param variables
*/
export function t(key: keyof LocalizedMessages, variables?: { [index: string]: any }) {
// Check if the key exists
let msg = msgs[lang][key]
if (!msg) {
// Check if the key exists in English
if (!(msg = msgs.en[key])) {
msg = key
console.error(`ERROR!! Missing translation reference entry (English) for ${key}`)
}
else console.warn(`Missing translation for ${key} in ${lang}`)
}
// Replace variables
if (variables) {
return msg.replace(/\${(.*?)}/g, (_: string, v: string | number) => variables[v] + "")
}
return msg
}
Object.assign(window, { t })
export const GAME_TITLE: { [key in GameName]: string } =
{chu3: t("game.chu3"), mai2: t("game.mai2"), ongeki: t("game.ongeki"), wacca: t("game.wacca")}

View File

@@ -0,0 +1,198 @@
export const EN_REF_USER = {
'UserHome.ServerRank': 'Server Rank',
'UserHome.DXRating': 'DX Rating',
'UserHome.Rating': 'Rating',
'UserHome.Statistics': 'Statistics',
'UserHome.Accuracy': 'Accuracy',
'UserHome.MaxCombo': 'Max Combo',
'UserHome.FullCombo': 'Full Combo',
'UserHome.AllPerfect': 'All Perfect',
'UserHome.DXScore': 'DX Score',
'UserHome.Score': 'Score',
'UserHome.PlayActivity': ' Play Activity',
'UserHome.Plays': 'Plays',
'UserHome.PlayTime': 'Play Time',
'UserHome.FirstSeen': 'First Seen',
'UserHome.LastSeen': 'Last Seen',
'UserHome.Version': 'Last Version',
'UserHome.RecentScores': 'Recent Scores',
'UserHome.NoData': 'No data in the past ${days} days',
'UserHome.UnknownSong': '(unknown song)',
'UserHome.Settings': 'Settings',
'UserHome.NoValidGame': 'The user hasn\'t played any game yet.',
'UserHome.ShowRanksDetails': 'Click to show details',
'UserHome.RankDetail.Title': 'Achievement Details',
'UserHome.RankDetail.Level': 'Level',
'UserHome.B50': 'B50',
'UserHome.AddRival': "Add to Rival",
'UserHome.RemoveRival': "Remove from Rival",
'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.",
}
export const EN_REF_Welcome = {
'back': 'Back',
'email': 'Email',
'password': 'Password',
'username': 'Username',
'welcome.btn-login': 'Log in',
'welcome.btn-signup': 'Sign up',
'welcome.email-password-missing': 'Email and password are required',
'welcome.username-missing': 'Username/email is 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.verify-state-0': 'You haven\'t verified your email. A verification email had been sent to your inbox less than a minute ago. Please check your inbox!',
'welcome.verify-state-1': 'You haven\'t verified your email. We\'ve already sent 3 emails over the last 24 hours so we\'ll not send another one. Please check your inbox!',
'welcome.verify-state-2': 'You haven\'t verified your email. We just sent you another verification email. Please check your inbox!',
'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.',
}
export const EN_REF_LEADERBOARD = {
'Leaderboard.Title': 'Server Leaderboard',
'Leaderboard.Rank': 'Rank',
'Leaderboard.Rating': 'Rating',
'Leaderboard.Accuracy': 'Accuracy',
'Leaderboard.FC': 'FC',
'Leaderboard.AP': 'AP',
}
export const EN_REF_GENERAL = {
'game.mai2': 'Mai',
'game.chu3': 'Chuni',
'game.ongeki': 'Ongeki',
'game.wacca': 'Wacca',
'status.error': 'Error',
'status.error.hint': 'Something went wrong, please try again later or ',
'status.error.hint.link': 'join our discord for support.',
'status.detail': 'Detail: ${detail}',
'action.refresh': 'Refresh',
'action.cancel': 'Cancel',
'action.confirm': 'Confirm',
}
export const EN_REF_HOME = {
'home.nav.portal': 'Portal',
'home.nav.link-card': 'Link Card',
'home.nav.game-setup': 'Game Setup',
'home.manage-cards': 'Manage Cards',
'home.manage-cards-description': 'Link, unlink, and manage your cards.',
'home.link-card': 'Link Card',
'home.link-cards-description': 'Link your Amusement IC / Aime card to play games.',
'home.join-community': 'Join Community',
'home.join-community-description': 'Join our community to chat with other players and get help.',
'home.setup': 'Setup Connection',
'home.setup-description': 'If you own a cab or arcade setup, begin setting up the connection.',
'home.import': 'Import Player Data',
'home.import-description': 'If you are from another server, you can import your data here.',
'home.linkcard.cards': 'Your Cards',
'home.linkcard.description': 'Here are the cards you have linked to your account',
'home.linkcard.account-card': 'Account Card',
'home.linkcard.registered': 'Registered',
'home.linkcard.lastused': 'Last used',
'home.linkcard.enter-info': 'Please enter the following information',
'home.linkcard.access-code': 'The 20-digit access code on the back of your card. (If it doesn\'t work, please try scanning your card in game and enter the access code shown on screen)',
'home.linkcard.enter-sn1': 'Download the NFC Tools app on your phone',
'home.linkcard.enter-sn2': 'and scan your card. Then, enter the Serial Number.',
'home.linkcard.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.setup.welcome': 'Welcome! If you own an arcade cabinet or game setup, please follow the instructions below to set up the connection with AquaDX.',
'home.setup.blockquote': 'We assume that you already have the required files and can run the game (e.g. ROM and segatools) that come with the cabinet or game setup. If not, please contact the seller of your device for the required files, as we will not provide them for copyright reasons.',
'home.setup.get': 'Get started',
'home.setup.edit': 'Please edit your segatools.ini file and modify the following lines',
'home.setup.test': 'Then, after you restart the game, you should be able to connect to AquaDX. Please verify that the network tests are all GOOD in the test menu.',
'home.setup.ask': 'If you have any questions, please ask in our',
'home.setup.support': 'server',
'home.setup.keychip-tips': 'This is your unique keychip, do not share it with anyone',
'home.import.unknown-game': 'Unknown game type. Currently only maimai and chunithm are supported for importing.',
'home.import.new-data': 'Data to import',
'home.import.data-conflict': 'Proceed will override your current data',
}
export const EN_REF_SETTINGS = {
'settings.title': 'Settings',
'settings.tabs.profile': 'Profile',
'settings.tabs.game': 'Game',
'settings.tabs.chu3': 'Chuni',
'settings.tabs.mai2': 'Mai',
'settings.tabs.wacca': 'Wacca',
'settings.fields.unlockMusic.name': 'Unlock All Music',
'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
'settings.fields.unlockChara.name': 'Unlock All Characters',
'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
'settings.fields.unlockTickets.name': 'Unlock All Tickets',
'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (note: maimai still limits which tickets can be used).',
'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
'settings.fields.chusanTeamName.name': 'Chuni: Team Name',
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
'settings.fields.chusanInfinitePenguins.name': 'Chuni: Infinite Penguins',
'settings.fields.chusanInfinitePenguins.desc': 'Set penguin statues for character level prompting to 999.',
'settings.fields.rounding.name': 'Score Rounding',
'settings.fields.rounding.desc': 'Round the score to one decimal place',
'settings.fields.optOutOfLeaderboard.name': 'Opt Out of Leaderboard',
'settings.fields.optOutOfLeaderboard.desc': 'You will still be able to see yourself on the leaderboard after logging in',
'settings.mai2.name': 'Player Name',
'settings.profile.picture': 'Profile Picture',
'settings.profile.upload-new': 'Upload New',
'settings.profile.save': 'Save',
'settings.profile.name': 'Display Name',
'settings.profile.username': 'Username',
'settings.profile.password': 'Password',
'settings.profile.location': 'Location',
'settings.profile.bio': 'Bio',
'settings.profile.unset': 'Unset',
'settings.profile.unchanged': 'Unchanged',
'settings.export': 'Export Player Data',
}
export const EN_REF_USERBOX = {
'userbox.header.general': 'General Settings',
'userbox.header.userbox': 'UserBox Settings',
'userbox.header.preview': 'UserBox Preview',
'userbox.nameplateId': 'Nameplate',
'userbox.frameId': 'Frame',
'userbox.trophyId': 'Trophy (Title)',
'userbox.mapIconId': 'Map Icon',
'userbox.voiceId': 'System Voice',
'userbox.avatarWear': 'Avatar Wear',
'userbox.avatarHead': 'Avatar Head',
'userbox.avatarFace': 'Avatar Face',
'userbox.avatarSkin': 'Avatar Skin',
'userbox.avatarItem': 'Avatar Item',
'userbox.avatarFront': 'Avatar Front',
'userbox.avatarBack': 'Avatar Back',
'userbox.preview.notice': 'To honor the copyright, we cannot host the images of the userbox items. However, if someone else is willing to provide the images, you can enter their URL here and it will be displayed.',
'userbox.preview.url': 'Image URL',
'userbox.error.nodata': 'Chuni data not found',
'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.processing_file': 'Processing',
'userbox.new.setup.finalizing': 'Saving to internal storage',
'userbox.new.drop': 'Drop game folder here',
'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.error.invalidFolder': 'The folder you selected is invalid. Ensure that your game\'s version is Lumi or newer and that the "A001" option pack is present.'
}
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 }
export type LocalizedMessages = typeof EN_REF

206
AquaNet/src/libs/i18n/zh.ts Normal file
View File

@@ -0,0 +1,206 @@
import {
EN_REF_GENERAL,
EN_REF_HOME,
EN_REF_LEADERBOARD,
EN_REF_SETTINGS,
EN_REF_USER,
EN_REF_USERBOX,
type EN_REF_Welcome
} from "./en_ref";
const zhUser: typeof EN_REF_USER = {
'UserHome.ServerRank': '服务器排名',
'UserHome.DXRating': 'DX B50',
'UserHome.Rating': '评分',
'UserHome.Statistics': '统计数据',
'UserHome.Accuracy': '准确率',
'UserHome.MaxCombo': '最大连击',
'UserHome.FullCombo': '全连曲目',
'UserHome.AllPerfect': '完美曲目',
'UserHome.DXScore': 'DX 总分',
'UserHome.Score': '总分',
'UserHome.PlayActivity': '游戏活动',
'UserHome.Plays': '出勤次数',
'UserHome.PlayTime': '游玩时间',
'UserHome.FirstSeen': '发现新大陆',
'UserHome.LastSeen': '上次出勤',
'UserHome.Version': '游戏版本',
'UserHome.RecentScores': '成绩',
'UserHome.NoData': '过去 ${days} 天内没有玩过',
'UserHome.UnknownSong': "(未知曲目)",
'UserHome.Settings': '设置',
'UserHome.NoValidGame': "用户还没有玩过游戏",
'UserHome.ShowRanksDetails': "点击显示评分详细",
'UserHome.RankDetail.Title': '评分详细',
'UserHome.RankDetail.Level': "等级",
'UserHome.B50': "B50",
'UserHome.AddRival': "添加劲敌",
'UserHome.RemoveRival': "移除劲敌",
'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、Wacca 和音击。",
}
const zhWelcome: typeof EN_REF_Welcome = {
'back': '返回',
'email': '邮箱',
'password': '密码',
'username': '用户名',
'welcome.btn-login': '登录',
'welcome.btn-signup': '注册',
'welcome.email-password-missing': '邮箱和密码必须填哦',
'welcome.username-missing': '用户名/邮箱必须填哦',
'welcome.waiting-turnstile': '正在验证网络环境...',
'welcome.turnstile-error': '验证网络环境出错了请关闭VPN后重试',
'welcome.turnstile-timeout': '验证网络环境超时了,请重试',
'welcome.verification-sent': '验证邮件已发送至 ${email},请翻翻收件箱',
'welcome.verify-state-0': '您还没有验证邮箱哦!验证邮件一分钟内刚刚发到您的邮箱,请翻翻收件箱',
'welcome.verify-state-1': '您还没有验证邮箱哦我们在过去的24小时内已经发送了3封验证邮件所以我们不会再发送了请翻翻收件箱',
'welcome.verify-state-2': '您还没有验证邮箱哦!我们刚刚又发送了一封验证邮件,请翻翻收件箱',
'welcome.verifying': '正在验证邮箱...请稍等',
'welcome.verified': '您的邮箱已经验证成功!您现在可以登录了',
'welcome.verification-failed': '验证失败:${message}。请重试',
}
const zhLeaderboard: typeof EN_REF_LEADERBOARD = {
'Leaderboard.Title': '排行榜',
'Leaderboard.Rank': '排名',
'Leaderboard.Rating': '评分',
'Leaderboard.Accuracy': '准确率',
'Leaderboard.FC': 'FC',
'Leaderboard.AP': 'AP',
}
const zhGeneral: typeof EN_REF_GENERAL = {
'game.mai2': "舞萌",
'game.chu3': "中二",
'game.ongeki': "音击",
'game.wacca': "Wacca",
"status.error": "发生错误",
"status.error.hint": "出了一些问题,请稍后刷新重试或者",
"status.error.hint.link": "加我们的 Discord 群问一问",
"status.detail": "详细信息:${detail}",
"action.refresh": "刷新",
"action.cancel": "取消",
"action.confirm": "确认",
}
const zhHome: typeof EN_REF_HOME = {
'home.nav.portal': "主页",
'home.nav.link-card': "绑卡",
'home.nav.game-setup': "连接设置",
'home.manage-cards': '管理游戏卡',
'home.manage-cards-description': '绑定、解绑、管理游戏数据卡',
'home.link-card': '绑定游戏卡',
'home.link-cards-description':'绑定游戏数据卡 (Amusement IC 或 Aime 卡) 后才可以访问游戏存档哦',
'home.join-community': '加入群组',
'home.join-community-description': '加入我们的聊天群组,与其他玩家聊天、获取帮助',
'home.setup': '连接到 AquaDX',
'home.setup-description': '如果您有街机框体或者手台,点击这里设置服务器的连接',
'home.import': '导入玩家数据',
'home.import-description': '如果你来自其他在线服,可以点击这里导入从其他服务器导出的数据',
'home.linkcard.cards': "已绑卡片",
'home.linkcard.description': "这些是您绑定到帐户的卡",
'home.linkcard.account-card': "账户卡",
'home.linkcard.registered': "注册于",
'home.linkcard.lastused': "上次使用",
'home.linkcard.enter-info': "请输入以下信息",
'home.linkcard.access-code': "卡背面的20位卡号 (如果没有, 请尝试在游戏中扫描您的卡, 并输入屏幕上显示的卡号)",
'home.linkcard.enter-sn1': "在您的手机",
'home.linkcard.enter-sn2': "上下载 NFC Tools 并扫描您的卡。然后输入显示的 SN 号。",
'home.linkcard.link': "绑定",
'home.linkcard.data-conflict': "卡号冲突",
'home.linkcard.name': "名称",
'home.linkcard.rating': "Rating",
'home.linkcard.last-login': "上次登录",
'home.linkcard.linked-own': "此卡已链接到您的帐户",
'home.linkcard.linked-another': "此卡已链接到其他用户",
'home.linkcard.notfound': "找不到卡",
'home.linkcard.unlink': "取消链接",
'home.linkcard.unlink-notice': "你确定要取消此卡的链接吗?",
'home.setup.welcome': "欢迎! 如果您有街机框体或者手台, 请按照以下说明设置以连接到 AquaDX.",
'home.setup.blockquote': "我们假设您已经拥有所需的文件, 并且可以启动机台或手台附带的游戏 (例如 ROM 和 segatools )。如果没有, 请联系您设备的卖家以获取所需的文件, 因为出于版权原因, 我们不会提供这些文件。",
'home.setup.get': "开始",
'home.setup.edit': "请打开您的 segatools.ini 文件并修改以下行",
'home.setup.test': "在您重新启动游戏后, 应该能够连接到 AquaDX。可以验证测试菜单中的网络测试测试连接是否全部良好。",
'home.setup.ask': "如果您有任何问题, 请加入我们的",
'home.setup.support': "以获取支持",
'home.setup.keychip-tips': "这是你的狗号, 不要与任何人分享",
'home.import.unknown-game': '未知游戏类型 (目前导入只支持舞萌和中二)',
'home.import.new-data': '要导入的数据',
'home.import.data-conflict': '继续导入将覆盖现有数据',
}
const zhSettings: typeof EN_REF_SETTINGS = {
'settings.title': '用户设置',
'settings.tabs.profile': '个人资料',
'settings.tabs.game': '游戏设置',
'settings.tabs.chu3': '中二',
'settings.tabs.mai2': '舞萌',
'settings.tabs.wacca': 'Wacca',
'settings.fields.unlockMusic.name': '解锁谱面',
'settings.fields.unlockMusic.desc': '在游戏中解锁所有曲目和大师难度谱面。',
'settings.fields.unlockChara.name': '解锁角色',
'settings.fields.unlockChara.desc': '在游戏中解锁所有角色、语音和伙伴。',
'settings.fields.unlockCollectables.name': '解锁收藏品',
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图)。',
'settings.fields.unlockTickets.name': '解锁游戏券',
'settings.fields.unlockTickets.desc': '无限跑图券/解锁券maimai 客户端仍限制一些券不能使用)。',
'settings.fields.waccaInfiniteWp.name': 'Wacca: 无限 WP',
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
'settings.fields.waccaAlwaysVip.name': 'Wacca: 永久会员',
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
'settings.fields.chusanTeamName.name': '中二: 队伍名称',
'settings.fields.chusanTeamName.desc': '自定义显示在个人资料顶部的文本。',
'settings.fields.chusanInfinitePenguins.name': '中二: 无限企鹅',
'settings.fields.chusanInfinitePenguins.desc': '将角色界限突破的企鹅雕像数量设置为 999。',
'settings.fields.rounding.name': '分数舍入',
'settings.fields.rounding.desc': '把分数四舍五入到一位小数',
'settings.fields.optOutOfLeaderboard.name': '不参与排行榜',
'settings.fields.optOutOfLeaderboard.desc': '登录之后还是可以在排行榜上看到自己',
'settings.mai2.name': '玩家名字',
'settings.profile.picture': '头像',
'settings.profile.upload-new': '上传',
'settings.profile.save': '保存',
'settings.profile.name': '昵称',
'settings.profile.username': '用户名',
'settings.profile.password': '密码',
'settings.profile.location': '位置',
'settings.profile.bio': '简介',
'settings.profile.unset': '未设置',
'settings.profile.unchanged': '未更改',
'settings.export': '导出玩家数据',
}
export const zhUserbox: typeof EN_REF_USERBOX = {
'userbox.header.general': '游戏设置',
'userbox.header.userbox': 'UserBox 设置',
'userbox.header.preview': 'UserBox 预览',
'userbox.nameplateId': '名牌',
'userbox.frameId': '边框',
'userbox.trophyId': '称号',
'userbox.mapIconId': '地图图标',
'userbox.voiceId': '系统语音',
'userbox.avatarWear': '企鹅服饰',
'userbox.avatarHead': '企鹅头饰',
'userbox.avatarFace': '企鹅面部',
'userbox.avatarSkin': '企鹅皮肤',
'userbox.avatarItem': '企鹅物品',
'userbox.avatarFront': '企鹅前景',
'userbox.avatarBack': '企鹅背景',
'userbox.preview.notice': '「生存战略」:为了尊重版权,我们不会提供游戏内物品的图片。但是如果你认识其他愿意提供图床的人,在这里输入 URL 就可以显示出预览。',
'userbox.preview.url': '图床 URL',
'userbox.error.nodata': '未找到中二数据',
'userbox.new.name': 'AquaBox',
'userbox.new.setup': '将 ChuniLumi 或更高版本)的游戏文件夹拖放到下方区域,以显示带有名牌和头像的 UserBox。所有文件都在浏览器中处理。',
'userbox.new.setup.processing_file': '正在处理文件',
'userbox.new.setup.finalizing': '正在保存到内部存储',
'userbox.new.drop': '将游戏文件夹拖到此处',
'userbox.new.activate_first': '启用 AquaBox需要游戏文件',
'userbox.new.activate_update': '更新 AquaBox需要游戏文件',
'userbox.new.activate': '使用 AquaBox',
'userbox.new.activate_desc': '启用后可显示带有名牌和头像的 UserBox',
'userbox.new.error.invalidFolder': '所选文件夹无效。请确认游戏版本为 Lumi 或更新,并且包含 “A001” 选项包。'
};
export const ZH = { ...zhUser, ...zhWelcome, ...zhGeneral,
...zhLeaderboard, ...zhHome, ...zhSettings, ...zhUserbox }

View File

@@ -0,0 +1,13 @@
import { AQUA_HOST, DATA_HOST } from './config'
export async function getMaimai(endpoint: string, params: any) {
return await fetch(`${AQUA_HOST}/Maimai2Servlet/${endpoint}`, {
method: 'POST',
body: JSON.stringify(params)
}).then(res => res.json())
}
export async function getMaimaiAllMusic(): Promise<{ [key: string]: any }> {
return fetch(`${DATA_HOST}/maimai/meta/00/all-music.json`).then(it => it.json())
}

View File

@@ -0,0 +1,107 @@
import type { MusicMeta } from "./generalTypes";
export interface Rating {
musicId: number
level: number
achievement: number
}
export interface ParsedRating extends Rating {
music: MusicMeta,
calc: number,
rank: string
}
export interface MaimaiUserSummaryEntry {
name: string
iconId: number
serverRank: number
accuracy: number
rating: number
ratingHighest: number
ranks: { name: string, count: number }[]
maxCombo: number
fullCombo: number
allPerfect: number
totalDxScore: number
plays: number
totalPlayTime: number
joined: string
lastSeen: string
lastVersion: string
best35: string
best15: string
recent: MaimaiUserPlaylog[]
}
export interface MaimaiUserPlaylog {
id: number;
musicId: number;
level: number;
userPlayDate: string;
trackNo: number;
vsRank: number;
achievement: number;
deluxscore: number;
scoreRank: number;
maxCombo: number;
totalCombo: number;
maxSync: number;
totalSync: number;
tapCriticalPerfect: number;
tapPerfect: number;
tapGreat: number;
tapGood: number;
tapMiss: number;
holdCriticalPerfect: number;
holdPerfect: number;
holdGreat: number;
holdGood: number;
holdMiss: number;
slideCriticalPerfect: number;
slidePerfect: number;
slideGreat: number;
slideGood: number;
slideMiss: number;
touchCriticalPerfect: number;
touchPerfect: number;
touchGreat: number;
touchGood: number;
touchMiss: number;
breakCriticalPerfect: number;
breakPerfect: number;
breakGreat: number;
breakGood: number;
breakMiss: number;
isTap: boolean;
isHold: boolean;
isSlide: boolean;
isTouch: boolean;
isBreak: boolean;
isCriticalDisp: boolean;
isFastLateDisp: boolean;
fastCount: number;
lateCount: number;
isAchieveNewRecord: boolean;
isDeluxscoreNewRecord: boolean;
comboStatus: number;
syncStatus: number;
isClear: boolean;
beforeRating: number;
afterRating: number;
beforeGrade: number;
afterGrade: number;
afterGradeRank: number;
beforeDeluxRating: number;
afterDeluxRating: number;
isPlayTutorial: boolean;
isEventMode: boolean;
isFreedomMode: boolean;
playMode: number;
isNewFree: boolean;
trialPlayAchievement: number;
extNum1: number;
extNum2: number;
extNum4: number;
extBool1: boolean;
}

View File

@@ -0,0 +1,24 @@
import type { GenericGamePlaylog } from "./generalTypes";
export interface OngekiUserSummaryEntry {
name: string
iconId: number
serverRank: number
accuracy: number
rating: number
ratingHighest: number
ranks: { name: string, count: number }[]
maxCombo: number
fullCombo: number
allPerfect: number
totalDxScore: number
plays: number
totalPlayTime: number
joined: string
lastSeen: string
lastVersion: string
best30: string
best15: string
recent10: string
recent: GenericGamePlaylog[]
}

153
AquaNet/src/libs/scoring.ts Normal file
View File

@@ -0,0 +1,153 @@
import { DATA_HOST } from "./config"
import type { MusicMeta } from "./generalTypes"
export type GameName = 'mai2' | 'chu3' | 'ongeki' | 'wacca'
const multTable = {
'mai2': [
[ 100.5, 22.4, 'SSSp' ],
[ 100.0, 21.6, 'SSS' ],
[ 99.5, 21.1, 'SSp' ],
[ 99, 20.8, 'SS' ],
[ 98, 20.3, 'Sp' ],
[ 97, 20, 'S' ],
[ 94, 16.8, 'AAA' ],
[ 90, 15.2, 'AA' ],
[ 80, 13.6, 'A' ],
[ 75, 12, 'BBB' ],
[ 70, 11.2, 'BB' ],
[ 60, 9.6, 'B' ],
[ 50, 8, 'C' ],
[ 40, 6.4, 'D' ],
[ 30, 4.8, 'D' ],
[ 20, 3.2, 'D' ],
[ 10, 1.6, 'D' ],
[ 0, 0, 'D' ]
],
// TODO: Fill in multipliers for Chunithm and Ongeki
'chu3': [
[ 100.9, 215, 'SSS+' ],
[ 100.75, 200, 'SSS' ],
[ 100.0, 0, 'SS' ],
[ 97.5, 0, 'S' ],
[ 95.0, 0, 'AAA' ],
[ 92.5, 0, 'AA' ],
[ 90.0, 0, 'A' ],
[ 80.0, 0, 'BBB' ],
[ 70.0, 0, 'BB' ],
[ 60.0, 0, 'B' ],
[ 50.0, 0, 'C' ],
[ 0.0, 0, 'D' ]
],
'ongeki': [
[ 100.75, 0, 'SSS+' ],
[ 100.0, 0, 'SSS' ],
[ 99.0, 0, 'SS' ],
[ 97.0, 0, 'S' ],
[ 94.0, 0, 'AAA' ],
[ 90.0, 0, 'AA' ],
[ 85.0, 0, 'A' ],
[ 80.0, 0, 'BBB' ],
[ 75.0, 0, 'BB' ],
[ 70.0, 0, 'B' ],
[ 50.0, 0, 'C' ],
[ 0.0, 0, 'D' ]
],
'wacca': [
[ 100.0, 0, 'AP' ],
[ 98.0, 0, 'SSS' ],
[ 95.0, 0, 'SS' ],
[ 90.0, 0, 'S' ],
[ 85.0, 0, 'AAA' ],
[ 80.0, 0, 'AA' ],
[ 70.0, 0, 'A' ],
[ 60.0, 0, 'B' ],
[ 1.0, 0, 'C' ],
[ 0.0, 0, 'D' ]
]
}
export function getMult(achievement: number, game: GameName) {
achievement /= 10000
const mt = multTable[game]
for (let i = 0; i < mt.length; i++) {
if (achievement >= (mt[i][0] as number)) return mt[i]
}
return [ 0, 0, 0 ]
}
export function roundFloor(achievement: number, game: GameName, digits = 2) {
// Round, but if the rounded number reaches the next rank, use floor instead
const mult = getMult(achievement, game);
achievement /= 10000
const rounded = achievement.toFixed(digits);
if (getMult(+rounded * 10000, game)[2] === mult[2] && rounded !== '101.0') return rounded;
return (+rounded - Math.pow(10, -digits)).toFixed(digits);
}
export function chusanRating(lv: number, score: number) {
lv = lv * 100
if (score >= 1009000) return lv + 215; // SSS+
if (score >= 1007500) return lv + 200 + (score - 1007500) / 100; // SSS
if (score >= 1005000) return lv + 150 + (score - 1005000) / 50; // SS+
if (score >= 1000000) return lv + 100 + (score - 1000000) / 100; // SS
if (score >= 975000) return lv + (score - 975000) / 250; // S+, S
if (score >= 925000) return lv - 300 + (score - 925000) * 3 / 500; // AA
if (score >= 900000) return lv - 500 + (score - 900000) * 4 / 500; // A
if (score >= 800000) return ((lv - 500) / 2 + (score - 800000) * ((lv - 500) / 2) / (100000)); // BBB
return 0; // C
}
export interface ParsedComposition {
name?: string
musicId: number
diffId: number // ID of the difficulty
score: number
cutoff: number
mult: number
rank: string // e.g. 'SSS+'
difficulty?: number // Actual difficulty of the map
img: string
ratingChange?: string // Rating change after playing this map
}
export function parseComposition(item: string, allMusics: Record<string, MusicMeta>, game: GameName): ParsedComposition {
// Chuni & ongeki: musicId, difficultId, score
// Mai: musicId, level (difficultyId), romVersion, achievement (score)
const mapData = item.split(':').map(Number)
if (game === 'mai2') mapData.splice(2, 1)
const [ musicId, diffId, score ] = mapData
const meta = allMusics[musicId]
// Get score multiplier
const tup = getMult(score, game)
const [ cutoff, mult ] = [ +tup[0], +tup[1] ]
const rank = "" + tup[2]
let diff = meta?.notes?.[diffId === 10 ? 0 : diffId]?.lv
function calcDxChange() {
if (!diff) return
if (game === 'mai2')
return Math.floor(diff * mult * (Math.min(100.5, score / 10000) / 100)).toFixed(0)
if (game === 'chu3')
return (chusanRating(diff, score) / 100).toFixed(1)
}
return {
name: meta?.name,
musicId,
diffId,
score,
cutoff,
mult,
rank,
difficulty: diff,
img: `${DATA_HOST}/d/${game}/music/00${mapData[0].toString().padStart(6, '0').substring(2)}.png`,
ratingChange: calcDxChange()
}
}

313
AquaNet/src/libs/sdk.ts Normal file
View File

@@ -0,0 +1,313 @@
import { AQUA_HOST, DATA_HOST } from './config'
import type {
AllMusic,
Card,
CardSummary,
GenericGameSummary,
GenericRanking,
TrendEntry,
AquaNetUser, GameOption,
UserBox,
UserItem
} from './generalTypes'
import type { GameName } from './scoring'
interface RequestInitWithParams extends RequestInit {
params?: { [index: string]: string }
localCache?: boolean
}
/**
* Modify a fetch url
*
* @param input Fetch url input
* @param callback Callback for modification
*/
export function reconstructUrl(input: URL | RequestInfo, callback: (url: URL) => URL | void): RequestInfo | URL {
let u = new URL((input instanceof Request) ? input.url : input)
const result = callback(u)
if (result) u = result
if (input instanceof Request) {
// @ts-ignore
return { url: u, ...input }
}
return u
}
/**
* Fetch with url parameters
*/
export function fetchWithParams(input: URL | RequestInfo, init?: RequestInitWithParams): Promise<Response> {
return fetch(reconstructUrl(input, u => {
u.search = new URLSearchParams(init?.params ?? {}).toString()
}), init)
}
const cache: { [index: string]: any } = {}
export async function post(endpoint: string, params: Record<string, any> = {}, init?: RequestInitWithParams): Promise<any> {
// Add token if exists
const token = localStorage.getItem('token')
if (token && !('token' in params)) params = { ...(params ?? {}), token }
if (init?.localCache) {
const cached = cache[endpoint + JSON.stringify(params) + JSON.stringify(init)]
if (cached) return cached
}
const res = await fetchWithParams(AQUA_HOST + endpoint, {
method: 'POST',
params,
...init
}).catch(e => {
console.error(e)
throw new Error('Network error')
})
if (!res.ok) {
const text = await res.text()
console.error(`${res.status}: ${text}`)
// If 400 invalid token is caught, should invalidate the token and redirect to signin
if (text === 'Invalid token') {
localStorage.removeItem('token')
window.location.href = '/'
}
// Try to parse as json
let json
try {
json = JSON.parse(text)
} catch (e) {
throw new Error(text)
}
if (json.error) throw new Error(json.error)
}
const ret = res.json()
cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] = ret
return ret
}
export async function get(endpoint: string, params:any,init?: RequestInitWithParams): Promise<any> {
// Add token if exists
const token = localStorage.getItem('token')
if (token && !('token' in params)) params = { ...(params ?? {}), token }
if (init?.localCache) {
const cached = cache[endpoint + JSON.stringify(init)]
if (cached) return cached
}
const res = await fetchWithParams(AQUA_HOST + endpoint, {
method: 'GET',
params,
...init
}).catch(e => {
console.error(e)
throw new Error('Network error')
})
if (!res.ok) {
const text = await res.text()
console.error(`${res.status}: ${text}`)
// If 400 invalid token is caught, should invalidate the token and redirect to signin
if (text === 'Invalid token') {
localStorage.removeItem('token')
window.location.href = '/'
}
// Try to parse as json
let json
try {
json = JSON.parse(text)
} catch (e) {
throw new Error(text)
}
if (json.error) throw new Error(json.error)
}
const ret = res.json()
cache[endpoint + JSON.stringify(init)] = ret
return ret
}
export async function put(endpoint: string, params: any, init?: RequestInitWithParams): Promise<any> {
// Add token if exists
const token = localStorage.getItem('token')
if (token && !('token' in params)) params = { ...(params ?? {}), token }
if (init?.localCache) {
const cached = cache[endpoint + JSON.stringify(params) + JSON.stringify(init)]
if (cached) return cached
}
const res = await fetchWithParams(AQUA_HOST + endpoint, {
method: 'PUT',
body: JSON.stringify(params),
headers:{
'Content-Type':'application/json',
...init?.headers
},
...init
}).catch(e => {
console.error(e)
throw new Error('Network error')
})
if (!res.ok) {
const text = await res.text()
console.error(`${res.status}: ${text}`)
// If 400 invalid token is caught, should invalidate the token and redirect to signin
if (text === 'Invalid token') {
localStorage.removeItem('token')
window.location.href = '/'
}
// Try to parse as json
let json
try {
json = JSON.parse(text)
} catch (e) {
throw new Error(text)
}
if (json.error) throw new Error(json.error)
}
const ret = res.json()
cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] = ret
return ret
}
export async function realPost(endpoint: string, params: any, init?: RequestInitWithParams): Promise<any> {
const res = await fetchWithParams(AQUA_HOST + endpoint, {
method: 'POST',
body: JSON.stringify(params),
headers:{
'Content-Type':'application/json',
...init?.headers
},
...init
}).catch(e => {
console.error(e)
throw new Error('Network error')
})
if (!res.ok) {
const text = await res.text()
console.error(`${res.status}: ${text}`)
// If 400 invalid token is caught, should invalidate the token and redirect to signin
if (text === 'Invalid token') {
localStorage.removeItem('token')
window.location.href = '/'
}
// Try to parse as json
let json
try {
json = JSON.parse(text)
} catch (e) {
throw new Error(text)
}
if (json.error) throw new Error(json.error)
}
return res.json()
}
/**
* aqua.net.UserRegistrar
*
* @param user
*/
async function register(user: { username: string, email: string, password: string, turnstile: string }) {
return await post('/api/v2/user/register', user)
}
async function login(user: { email: string, password: string, turnstile: string }) {
const data = await post('/api/v2/user/login', user)
// Put token into local storage
localStorage.setItem('token', data.token)
}
const isLoggedIn = () => !!localStorage.getItem('token')
const ensureLoggedIn = () => !isLoggedIn() && (window.location.href = '/')
export const USER = {
register,
login,
confirmEmail: (token: string) =>
post('/api/v2/user/confirm-email', { token }),
me: (): Promise<AquaNetUser> => {
ensureLoggedIn()
return post('/api/v2/user/me', {})
},
keychip: (): Promise<string> =>
post('/api/v2/user/keychip', {}).then(it => it.keychip),
setting: (key: string, value: string) =>
post('/api/v2/user/setting', { key: key === 'password' ? 'pwHash' : key, value }),
uploadPfp: (file: File) => {
const formData = new FormData()
formData.append('file', file)
return post('/api/v2/user/upload-pfp', { }, { method: 'POST', body: formData })
},
isLoggedIn,
ensureLoggedIn,
}
export const USERBOX = {
getProfile: (): Promise<{ user: UserBox, items: UserItem[] }> =>
get('/api/v2/game/chu3/user-box', {}),
setUserBox: (d: { field: string, value: number | string }) =>
post(`/api/v2/game/chu3/user-detail-set`, d),
}
export const CARD = {
summary: (cardId: string): Promise<{card: Card, summary: CardSummary}> =>
post('/api/v2/card/summary', { cardId }),
link: (props: { cardId: string, migrate: string }) =>
post('/api/v2/card/link', props),
unlink: (cardId: string) =>
post('/api/v2/card/unlink', { cardId }),
userGames: (username: string): Promise<CardSummary> =>
post('/api/v2/card/user-games', { username }),
}
export const GAME = {
trend: (username: string, game: GameName): Promise<TrendEntry[]> =>
post(`/api/v2/game/${game}/trend`, { username }),
userSummary: (username: string, game: GameName): Promise<GenericGameSummary> =>
post(`/api/v2/game/${game}/user-summary`, { username }),
ranking: (game: GameName): Promise<GenericRanking[]> =>
post(`/api/v2/game/${game}/ranking`, { }),
changeName: (game: GameName, newName: string): Promise<{ newName: string }> =>
post(`/api/v2/game/${game}/change-name`, { newName }),
export: (game: GameName): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/export`),
import: (game: GameName, data: any): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/import`, {}, { body: JSON.stringify(data) }),
importMusicDetail: (game: GameName, data: any): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/import-music-detail`, {}, {body: JSON.stringify(data), headers: {'Content-Type': 'application/json'}}),
setRival: (game: GameName, rivalUserName: string, isAdd: boolean) =>
post(`/api/v2/game/${game}/set-rival`, { rivalUserName, isAdd }),
}
export const DATA = {
allMusic: (game: GameName): Promise<AllMusic> =>
fetch(`${DATA_HOST}/d/${game}/00/all-music.json`).then(it => it.json()),
allItems: (game: GameName): Promise<Record<string, Record<string, any>>> =>
fetch(`${DATA_HOST}/d/${game}/00/all-items.json`).then(it => it.json()),
}
export const SETTING = {
get: (): Promise<GameOption[]> =>
post('/api/v2/settings/get', {}),
set: (key: string, value: any) =>
post('/api/v2/settings/set', { key, value: `${value}` }),
}

213
AquaNet/src/libs/ui.ts Normal file
View File

@@ -0,0 +1,213 @@
import {
CategoryScale,
Chart as ChartJS,
type ChartOptions,
Legend,
LinearScale,
LineElement,
PointElement,
TimeScale,
Title,
Tooltip,
} from 'chart.js'
import moment from 'moment/moment'
// @ts-expect-error Cal-heatmap does not have proper types
import CalHeatmap from 'cal-heatmap'
// @ts-expect-error Cal-heatmap does not have proper types
import CalTooltip from 'cal-heatmap/plugins/Tooltip'
import { AQUA_HOST, DEFAULT_PFP } from "./config"
import type { AquaNetUser } from "./generalTypes"
export function title(t: string) {
document.title = `AquaNet - ${t}`
}
export function registerChart() {
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
TimeScale
)
}
const dayTemplate = (DateHelper) => {
const ROWS_COUNT = 7
const ALLOWED_DOMAIN_TYPE = ['month']
return {
name: 'ghDayFix',
allowedDomainType: ALLOWED_DOMAIN_TYPE,
rowsCount: () => ROWS_COUNT,
columnsCount: (ts) => {
let count = DateHelper.getWeeksCountInMonth(ts)
const endOfMonth = moment().endOf('month').toDate()
const clampEnd = DateHelper.getFirstWeekOfMonth(endOfMonth).toDate()
if(moment(ts).isSame(new Date(), 'month') && endOfMonth > clampEnd) {
count++
}
return count
},
mapping: (startTimestamp, endTimestamp) => {
const clampStart = DateHelper.getFirstWeekOfMonth(startTimestamp)
let clampEnd = DateHelper.getFirstWeekOfMonth(endTimestamp)
if(moment(startTimestamp).isSame(new Date(), 'month')){
clampEnd = DateHelper.date().add(1, 'day')
}
let x = -1
const pivotDay = clampStart.weekday()
return DateHelper.intervals('day', clampStart, clampEnd).map((ts) => {
const weekday = DateHelper.date(ts).weekday()
if (weekday === pivotDay) {
x += 1
}
return {
t: ts,
x,
y: weekday,
}
})
},
extractUnit: (ts) => DateHelper.date(ts).startOf('day').valueOf(),
}
}
export function renderCal(el: HTMLElement, d: { date: any, value: any }[]): Promise<any> {
const cal = new CalHeatmap()
cal.addTemplates(dayTemplate)
return cal.paint({
itemSelector: el,
domain: {
type: 'month',
label: { text: 'MMM', textAlign: 'start', position: 'top' },
},
subDomain: {
type: 'ghDayFix',
radius: 2, width: 11, height: 11, gutter: 4
},
range: 12,
data: { source: d.filter(x => x.value > 0), x: 'date', y: 'value' },
scale: {
color: {
type: 'linear',
range: [ '#14432a', '#4dd05a' ],
domain: [ 0, d.reduce((a, b) => Math.max(a, b.value), 0) ]
},
},
date: { start: moment().subtract(1, 'year').add(1, 'month').toDate() },
theme: 'dark',
}, [
[ CalTooltip, {
text: (_: Date, v: number, d: any) =>
`${v ?? 'No'} songs played on ${d.format('MMMM D, YYYY')}`
} ]
])
}
const now = moment()
export const CHARTJS_OPT: ChartOptions<'line'> = {
responsive: true,
maintainAspectRatio: false,
// TODO: Show point on hover
elements: {
point: {
radius: 0
}
},
scales: {
xAxis: {
type: 'time',
display: false
},
y: {
display: false,
}
},
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
title: (tooltipItems) => {
const date = tooltipItems[0].parsed.x
const diff = now.diff(date, 'days')
return diff ? `${diff} days ago` : 'Today'
}
}
}
},
}
export const pfpNotFound = (e: Event) => (e.target as HTMLImageElement).src = DEFAULT_PFP
export const coverNotFound = (e: Event) => (e.target as HTMLImageElement).src = "/assets/imgs/no_cover.jpg"
/**
* use:tooltip
*/
export function tooltip(element: HTMLElement, params: { text: string, dom: HTMLElement } | string | HTMLElement) {
// Create div if not exists
if (!document.querySelector('.aqua-tooltip')) {
const div = document.createElement('div')
div.classList.add('aqua-tooltip')
// Initially hidden
div.style.display = 'none'
document.body.appendChild(div)
}
let isFocus = false
let div: HTMLDivElement = document.querySelector('.aqua-tooltip')!
const p: string = typeof params === 'string' ? params
: 'dom' in params ? params.dom.outerHTML
: params.outerHTML
function updatePosition(event: MouseEvent) {
div.style.top = `${event.pageY + 10}px`
div.style.left = `${event.pageX - div.clientWidth / 2 + 5}px`
}
function mouseOver(event: MouseEvent) {
if (isFocus) return
div.innerHTML = p
div.style.display = ''
updatePosition(event)
isFocus = true
}
function mouseLeave() {
isFocus = false
div.style.display = 'none'
}
element.addEventListener('mouseover', mouseOver)
element.addEventListener('mouseleave', mouseLeave)
element.addEventListener('mousemove', updatePosition)
return {
destroy() {
element.removeEventListener('mouseover', mouseOver)
element.removeEventListener('mouseleave', mouseLeave)
element.removeEventListener('mousemove', updatePosition)
}
}
}
export function pfp(node: HTMLImageElement, me?: AquaNetUser) {
node.src = me?.profilePicture ? `${AQUA_HOST}/uploads/net/portrait/${me.profilePicture}` : DEFAULT_PFP
node.onerror = e => pfpNotFound(e as Event)
}

View File

@@ -0,0 +1,336 @@
/*
A simplified DDS parser with Chusan userbox in mind.
There are some issues on Safari. I don't really care, to be honest.
Authored by Raymond and May.
DDS header parsing based off of https://gist.github.com/brett19/13c83c2e5e38933757c2
*/
import DDSCache from "./ddsCache";
function makeFourCC(string: string) {
return string.charCodeAt(0) +
(string.charCodeAt(1) << 8) +
(string.charCodeAt(2) << 16) +
(string.charCodeAt(3) << 24);
};
/**
* @description Magic bytes for the DDS file format (see https://en.wikipedia.org/wiki/Magic_number_(programming))
*/
const DDS_MAGIC_BYTES = 0x20534444;
/*
to get around the fact that TS's builtin Object.fromEntries() typing
doesn't persist strict types and instead only uses broad types
without creating a new function to get around it...
sorry, this is a really ugly solution, but it's not my problem
*/
/**
* @description List of compression type markers used in DDS
*/
const DDS_COMPRESSION_TYPE_MARKERS = ["DXT1", "DXT3", "DXT5"] as const;
/**
* @description Object mapping string versions of DDS compression type markers to their value in uint32s
*/
const DDS_COMPRESSION_TYPE_MARKERS_MAP = Object.fromEntries(
DDS_COMPRESSION_TYPE_MARKERS
.map(e => [e, makeFourCC(e)] as [typeof e, number])
) as Record<typeof DDS_COMPRESSION_TYPE_MARKERS[number], number>
const DDS_DECOMPRESS_VERTEX_SHADER = `
attribute vec2 aPosition;
varying highp vec2 vTextureCoord;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vTextureCoord = ((aPosition * vec2(1.0, -1.0)) / 2.0 + 0.5);
}`;
const DDS_DECOMPRESS_FRAGMENT_SHADER = `
varying highp vec2 vTextureCoord;
uniform sampler2D uTexture;
void main() {
gl_FragColor = texture2D(uTexture, vTextureCoord);
}`
export class DDS {
constructor(db: IDBDatabase | undefined) {
this.cache = new DDSCache(db);
let gl = this.canvasGL.getContext("webgl");
if (!gl) throw new Error("Failed to get WebGL rendering context") // TODO: make it switch to Classic userbox
this.gl = gl;
let ctx = this.canvas2D.getContext("2d");
if (!ctx) throw new Error("Failed to reach minimum system requirements") // TODO: make it switch to Classic userbox
this.ctx = ctx;
let ext =
gl.getExtension("WEBGL_compressed_texture_s3tc") ||
gl.getExtension("MOZ_WEBGL_compressed_texture_s3tc") ||
gl.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");
if (!ext) throw new Error("Browser is not supported."); // TODO: make it switch to Classic userbox
this.ext = ext;
/* Initialize shaders */
this.compileShaders();
this.gl.useProgram(this.shader);
/* Setup position buffer */
let attributeLocation = this.gl.getAttribLocation(this.shader ?? 0, "aPosition");
let positionBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0]), this.gl.STATIC_DRAW);
this.gl.vertexAttribPointer(
attributeLocation,
2, this.gl.FLOAT,
false, 0, 0
);
this.gl.enableVertexAttribArray(attributeLocation)
}
/**
* @description Loads a DDS file into the internal canvas object.
* @param buffer Uint8Array to load DDS from.
* @returns String if failed to load, void if success
*/
load(buffer: Uint8Array) {
let header = this.loadHeader(buffer);
if (!header) return;
let compressionMode: GLenum = this.ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
if (header.pixelFormat.flags & 0x4) {
switch (header.pixelFormat.type) {
case DDS_COMPRESSION_TYPE_MARKERS_MAP.DXT1:
compressionMode = this.ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case DDS_COMPRESSION_TYPE_MARKERS_MAP.DXT3:
compressionMode = this.ext.COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case DDS_COMPRESSION_TYPE_MARKERS_MAP.DXT5:
compressionMode = this.ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
};
} else return;
/* Initialize and configure the texture */
let texture = this.gl.createTexture();
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
this.gl.compressedTexImage2D(
this.gl.TEXTURE_2D,
0,
compressionMode,
header.width,
header.height,
0,
buffer.slice(128)
);
this.gl.uniform1i(this.gl.getUniformLocation(this.shader || 0, "uTexture"), 0);
/* Prepare the canvas for drawing */
this.canvasGL.width = header.width;
this.canvasGL.height = header.height
this.gl.viewport(0, 0, this.canvasGL.width, this.canvasGL.height);
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.gl.deleteTexture(texture);
};
/**
* @description Export a Blob from the parsed DDS texture
* @returns DDS texture in specified format
* @param inFormat Mime type to export in
*/
getBlob(inFormat?: string): Promise<Blob | null> {
return new Promise(res => this.canvasGL.toBlob(res, inFormat))
}
get2DBlob(inFormat?: string): Promise<Blob | null> {
return new Promise(res => this.canvas2D.toBlob(res, inFormat))
}
/**
* @description Helper function to load in a Blob
* @input Blob to use
*/
async fromBlob(input: Blob) {
this.load(new Uint8Array(await input.arrayBuffer()));
}
/**
* @description Read a DDS file header
* @param buffer Uint8Array of the DDS file's contents
*/
loadHeader(buffer: Uint8Array) {
if (this.getUint32(buffer, 0) !== DDS_MAGIC_BYTES) return;
return {
size: this.getUint32(buffer, 4),
flags: this.getUint32(buffer, 8),
height: this.getUint32(buffer, 12),
width: this.getUint32(buffer, 16),
mipmaps: this.getUint32(buffer, 24),
/* TODO: figure out if we can cut any of this out (we totally can btw) */
pixelFormat: {
size: this.getUint32(buffer, 76),
flags: this.getUint32(buffer, 80),
type: this.getUint32(buffer, 84),
}
}
};
/**
* @description Retrieve a file from the IndexedDB database and load it into the DDS loader
* @param path File path
* @returns Whether or not the attempt to retrieve the file was successful
*/
loadFile(path: string) : Promise<boolean> {
return new Promise(async r => {
let file = await this.cache?.getFromDatabase(path)
if (file != null)
await this.fromBlob(file)
r(file != null)
})
};
/**
* @description Retrieve a file from a path
* @param path File path
* @param fallback Path to a file to fallback to if loading this file fails
* @returns An object URL which correlates to a Blob
*/
async getFile(path: string, fallback?: string) : Promise<string> {
if (this.cache?.cached(path))
return this.cache.find(path) ?? ""
if (!await this.loadFile(path))
if (fallback) {
if (!await this.loadFile(fallback))
return "";
} else
return ""
let blob = await this.getBlob("image/png");
if (!blob) return ""
return this.cache?.save(
path, URL.createObjectURL(blob)
) ?? "";
};
/**
* @description Transform a spritesheet located at a path to match the dimensions specified in the parameters
* @param path Spritesheet path
* @param x Crop: X
* @param y Crop: Y
* @param w Crop: Width
* @param h Crop: Height
* @param s Scale factor
* @returns An object URL which correlates to a Blob
*/
async getFileFromSheet(path: string, x: number, y: number, w: number, h: number, s?: number): Promise<string> {
if (!await this.loadFile(path))
return "";
this.canvas2D.width = w * (s ?? 1);
this.canvas2D.height = h * (s ?? 1);
this.ctx.drawImage(this.canvasGL, x, y, w, h, 0, 0, w * (s ?? 1), h * (s ?? 1));
/* We don't want to cache this, it's a spritesheet piece. */
return URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([]));
};
/**
* @description Retrieve a file and scale it by a specified scale factor
* @param path File path
* @param s Scale factor
* @param fallback Path to a file to fallback to if loading this file fails
* @returns An object URL which correlates to a Blob
*/
async getFileScaled(path: string, s: number, fallback?: string): Promise<string> {
if (this.cache?.cached(path, s))
return this.cache.find(path, s) ?? ""
if (!await this.loadFile(path))
if (fallback) {
if (!await this.loadFile(fallback))
return "";
} else
return "";
this.canvas2D.width = this.canvasGL.width * (s ?? 1);
this.canvas2D.height = this.canvasGL.height * (s ?? 1);
this.ctx.drawImage(this.canvasGL, 0, 0, this.canvasGL.width, this.canvasGL.height, 0, 0, this.canvasGL.width * (s ?? 1), this.canvasGL.height * (s ?? 1));
return this.cache?.save(path, URL.createObjectURL(await this.get2DBlob("image/png") ?? new Blob([])), s) ?? "";
};
/**
* @description Retrieve a Uint32 from a Uint8Array at the specified offset
* @param buffer Uint8Array to retrieve the Uint32 from
* @param offset Offset at which to retrieve bytes
*/
getUint32(buffer: Uint8Array, offset: number) {
return (buffer[offset + 0] << 0) +
(buffer[offset + 1] << 8) +
(buffer[offset + 2] << 16) +
(buffer[offset + 3] << 24);
};
private compileShaders() {
let vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
let fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
if (!vertexShader || !fragmentShader) return;
this.gl.shaderSource(vertexShader, DDS_DECOMPRESS_VERTEX_SHADER);
this.gl.compileShader(vertexShader);
if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS))
throw new Error(
`An error occurred compiling vertex shader: ${this.gl.getShaderInfoLog(vertexShader)}`,
);
this.gl.shaderSource(fragmentShader, DDS_DECOMPRESS_FRAGMENT_SHADER);
this.gl.compileShader(fragmentShader);
if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS))
throw new Error(
`An error occurred compiling fragment shader: ${this.gl.getShaderInfoLog(fragmentShader)}`,
);
let program = this.gl.createProgram();
if (!program) return;
this.shader = program;
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS))
throw new Error(
`An error occurred linking the program: ${this.gl.getProgramInfoLog(program)}`,
);
};
canvas2D: HTMLCanvasElement = document.createElement("canvas");
canvasGL: HTMLCanvasElement = document.createElement("canvas");
cache: DDSCache | null;
ctx: CanvasRenderingContext2D;
gl: WebGLRenderingContext;
ext: ReturnType<typeof this.gl.getExtension>;
shader: WebGLShader | null = null;
};

View File

@@ -0,0 +1,64 @@
export default class DDSCache {
constructor(db: IDBDatabase | undefined) {
this.db = db;
}
/**
* @description Finds an object URL for the image with the specified path and scale
* @param path Image path
* @param scale Scale factor
*/
find(path: string, scale: number = 1): string | undefined {
return (this.urlCache.find(
p => p.path == path && p.scale == scale)?.url)
}
/**
* @description Checks whether an object URL is cached for the image with the specified path and scale
* @param path Image path
* @param scale Scale factor
*/
cached(path: string, scale: number = 1): boolean {
return this.urlCache.some(
p => p.path == path && p.scale == scale)
}
/**
* @description Save an object URL for the specified path and scale to the cache
* @param path Image path
* @param url Object URL
* @param scale Scale factor
*/
save(path: string, url: string, scale: number = 1) {
if (this.cached(path, scale)) {
URL.revokeObjectURL(url);
return this.find(path, scale)
}
this.urlCache.push({path, url, scale})
return url
}
/**
* @description Retrieve a Blob from a database based on the specified path
* @param path Image path
*/
getFromDatabase(path: string): Promise<Blob | null> {
return new Promise((resolve, reject) => {
if (!this.db)
return resolve(null);
let transaction = this.db.transaction(["dds"], "readonly");
let objectStore = transaction.objectStore("dds");
let request = objectStore.get(path);
request.onsuccess = async (e) => {
if (request.result)
if (request.result.blob)
return resolve(request.result.blob);
return resolve(null);
}
request.onerror = () => resolve(null);
})
};
private urlCache: {scale: number, path: string, url: string}[] = [];
private db: IDBDatabase | undefined;
}

View File

@@ -0,0 +1,180 @@
import { t, ts } from "../../libs/i18n";
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
const isDirectory = (e: FileSystemEntry): e is FileSystemDirectoryEntry => e.isDirectory
const isFile = (e: FileSystemEntry): e is FileSystemFileEntry => e.isFile
const getDirectory = (directory: FileSystemDirectoryEntry, path: string): Promise<FileSystemEntry> => new Promise((res, rej) => directory.getDirectory(path, {}, d => res(d), e => rej()));
const getFile = (directory: FileSystemDirectoryEntry, path: string): Promise<FileSystemEntry> => new Promise((res, rej) => directory.getFile(path, {}, d => res(d), e => rej()));
const getFiles = async (directory: FileSystemDirectoryEntry): Promise<Array<FileSystemEntry>> => {
let reader = directory.createReader();
let files: Array<FileSystemEntry> = [];
let currentFiles: number = 1e9;
while (currentFiles != 0) {
let entries = await new Promise<Array<FileSystemEntry>>(r => reader.readEntries(r));
files = files.concat(entries);
currentFiles = entries.length;
}
return files;
};
const validateDirectories = async (base: FileSystemDirectoryEntry, path: string): Promise<boolean> => {
const pathTrail = path.split("/");
let directory: FileSystemDirectoryEntry = base;
for (let part of pathTrail) {
let newDirectory = await getDirectory(directory, part).catch(_ => null);
if (newDirectory && isDirectory(newDirectory)) {
directory = newDirectory;
} else
return false;
};
return true
}
const getDirectoryFromPath = async (base: FileSystemDirectoryEntry, path: string): Promise<FileSystemDirectoryEntry | null> => {
const pathTrail = path.split("/");
let directory: FileSystemDirectoryEntry = base;
for (let part of pathTrail) {
let newDirectory = await getDirectory(directory, part).catch(_ => null);
if (newDirectory && isDirectory(newDirectory)) {
directory = newDirectory;
} else
return null;
};
return directory;
}
export let ddsDB: IDBDatabase | undefined ;
/* Technically, processName should be in the translation file but I figured it was such a small thing that it didn't REALLY matter... */
const DIRECTORY_PATHS = ([
{
folder: "ddsImage",
processName: "Characters",
path: "characterThumbnail",
filter: (name: string) => name.substring(name.length - 6, name.length) == "02.dds",
id: (name: string) => `0${name.substring(17, 21)}${name.substring(23, 24)}`
},
{
folder: "namePlate",
processName: "Nameplates",
path: "nameplate",
filter: () => true,
id: (name: string) => name.substring(17, 25)
},
{
folder: "avatarAccessory",
processName: "Avatar Accessory Thumbnails",
path: "avatarAccessoryThumbnail",
filter: (name: string) => name.substring(14, 18) == "Icon",
id: (name: string) => name.substring(19, 27)
},
{
folder: "avatarAccessory",
processName: "Avatar Accessories",
path: "avatarAccessory",
filter: (name: string) => name.substring(14, 17) == "Tex",
id: (name: string) => name.substring(18, 26)
},
{
folder: "texture",
processName: "Surfboard Textures",
useFileName: true,
path: "surfboard",
filter: (name: string) =>
([
"CHU_UI_Common_Avatar_body_00.dds",
"CHU_UI_Common_Avatar_face_00.dds",
"CHU_UI_title_rank_00_v10.dds"
]).includes(name),
id: (name: string) => name
}
] satisfies {folder: string, processName: string, path: string, useFileName?: boolean, filter: (name: string) => boolean, id: (name: string) => string}[] )
export const scanOptionFolder = async (optionFolder: FileSystemDirectoryEntry, progressUpdate: (progress: number, text: string) => void) => {
let filesToProcess: Record<string, FileSystemFileEntry[]> = {};
let directories = (await getFiles(optionFolder))
.filter(directory => isDirectory(directory) && ((directory.name.substring(0, 1) == "A" && directory.name.length == 4) || directory.name == "surfboard"))
for (let directory of directories)
if (isDirectory(directory)) {
for (const directoryData of DIRECTORY_PATHS) {
let folder = await getDirectoryFromPath(directory, directoryData.folder).catch(_ => null) ?? [];
if (folder) {
if (!filesToProcess[directoryData.path])
filesToProcess[directoryData.path] = [];
for (let dataFolderEntry of await getFiles(folder as FileSystemDirectoryEntry).catch(_ => null) ?? [])
if (isDirectory(dataFolderEntry)) {
for (let dataEntry of await getFiles(dataFolderEntry as FileSystemDirectoryEntry).catch(_ => null) ?? [])
if (isFile(dataEntry) && directoryData.filter(dataEntry.name))
filesToProcess[directoryData.path].push(dataEntry);
} else if (isFile(dataFolderEntry) && directoryData.filter(dataFolderEntry.name))
filesToProcess[directoryData.path].push(dataFolderEntry);
}
}
}
let data = [];
for (const [folder, files] of Object.entries(filesToProcess)) {
let reference = DIRECTORY_PATHS.find(r => r.path == folder);
for (const [idx, file] of files.entries()) {
progressUpdate((idx / files.length) * 100, `${t("userbox.new.setup.processing_file")} ${reference?.processName ?? "?"}...`)
data.push({
path: `${folder}:${reference?.id(file.name)}`, name: file.name, blob: await new Promise<File>(res => file.file(res))
});
}
}
progressUpdate(100, `${t("userbox.new.setup.finalizing")}...`)
let transaction = ddsDB?.transaction(['dds'], 'readwrite', { durability: "strict" })
if (!transaction) return; // TODO: bubble error up to user
transaction.onerror = e => e.preventDefault()
let objectStore = transaction.objectStore('dds');
for (let object of data)
objectStore.put(object)
// await transaction completion
await new Promise(r => transaction.addEventListener("complete", r, {once: true}))
};
export function initializeDb() : Promise<void> {
return new Promise(r => {
const dbRequest = indexedDB.open("userboxChusanDDS", 1)
dbRequest.addEventListener("upgradeneeded", (event) => {
if (!(event.target instanceof IDBOpenDBRequest)) return
ddsDB = event.target.result;
if (!ddsDB) return;
const store = ddsDB.createObjectStore('dds', { keyPath: 'path' });
store.createIndex('path', 'path', { unique: true })
store.createIndex('name', 'name', { unique: false })
store.createIndex('blob', 'blob', { unique: false })
r();
});
dbRequest.addEventListener("success", () => {
ddsDB = dbRequest.result;
r();
})
})
}
export async function userboxFileProcess(folder: FileSystemEntry, progressUpdate: (progress: number, progressString: string) => void): Promise<string | null> {
if (!isDirectory(folder))
return t("userbox.new.error.invalidFolder")
if (!(await validateDirectories(folder, "bin/option")) && !(await validateDirectories(folder, "data/A000")))
return t("userbox.new.error.invalidFolder");
initializeDb();
const optionFolder = await getDirectoryFromPath(folder, "bin/option");
if (optionFolder)
await scanOptionFolder(optionFolder, progressUpdate);
const dataFolder = await getDirectoryFromPath(folder, "data");
if (dataFolder)
await scanOptionFolder(dataFolder, progressUpdate);
useLocalStorage("userboxNew", false).value = true;
location.reload();
return null
}

7
AquaNet/src/main.ts Normal file
View File

@@ -0,0 +1,7 @@
import { mount } from 'svelte';
import './app.sass'
import App from './App.svelte'
const app = mount(App, { target: document.getElementById("app")! });
export default app

View File

@@ -0,0 +1,98 @@
<script lang="ts">
import { fade } from "svelte/transition";
import LinkCard from "./Home/LinkCard.svelte";
import SetupInstructions from "./Home/SetupInstructions.svelte";
import { DISCORD_INVITE, FADE_IN, FADE_OUT } from "../libs/config";
import { USER } from "../libs/sdk.js";
import type { AquaNetUser } from "../libs/generalTypes";
import StatusOverlays from "../components/StatusOverlays.svelte";
import ActionCard from "../components/ActionCard.svelte";
import { t } from "../libs/i18n";
import ImportDataAction from "./Home/ImportDataAction.svelte";
import Communities from "./Home/Communities.svelte";
USER.ensureLoggedIn();
let me: AquaNetUser
let error = ""
let tab = 0;
let tabs = [t('home.nav.portal'), t('home.nav.link-card'), t('home.nav.game-setup')]
USER.me().then((m) => me = m).catch(e => error = e.message)
</script>
<main class="content">
<!-- <h2 class="outer-title">&nbsp;</h2>-->
<nav class="tabs">
{#each tabs as t, i}
<div class="clickable"
class:active={tab === i}
on:click={() => tab = i}
on:keydown={(e) => e.key === "Enter" && (tab = i)}
role="button" tabindex={i}>{t}
</div>
{/each}
</nav>
{#if tab === 0}
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="action-cards">
<ActionCard color="255, 192, 203" icon="solar:card-bold-duotone" on:click={() => tab = 1}>
{#if me && me.cards.length > 1}
<h3>{t('home.manage-cards')}</h3>
<span>{t('home.manage-cards-description')}</span>
{:else if me}
<h3>{t('home.link-card')}</h3>
<span>{t('home.link-cards-description')}</span>
{/if}
</ActionCard>
<ActionCard color="82, 93, 233" icon="fluent:chat-12-filled" on:click={() => tab = 3}>
<h3>{t('home.join-community')}</h3>
<span>{t('home.join-community-description')}</span>
</ActionCard>
<ActionCard on:click={() => tab = 2} icon="uil:link-alt">
<h3>{t('home.setup')}</h3>
<span>{t('home.setup-description')}</span>
</ActionCard>
<ImportDataAction/>
</div>
{:else if tab === 1}
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
<LinkCard/>
</div>
{:else if tab === 2}
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
<SetupInstructions/>
</div>
{:else if tab === 3}
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
<Communities/>
</div>
{/if}
</main>
<StatusOverlays {error} loading={!me}/>
<style lang="sass">
@use "../vars"
.tabs
display: flex
gap: 1rem
div
&.active
color: vars.$c-main
h3
font-size: 1.3rem
margin: 0
.action-cards
display: flex
flex-direction: column
gap: 1rem
</style>

View File

@@ -0,0 +1,26 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { t } from "../../libs/i18n";
import CommunityCard from "../../components/CommunityCard.svelte";
import { DISCORD_INVITE, QQ_INVITE, TELEGRAM_INVITE } from "../../libs/config";
</script>
<div class="setup-instructions">
<h2>{t('home.join-community')}</h2>
<div class="grid cols-3 gap-4">
<CommunityCard color="82, 93, 233" icon="ic:baseline-discord" on:click={() => window.location.href = DISCORD_INVITE}>
<h3>Discord</h3>
</CommunityCard>
<CommunityCard color="46, 163, 224" icon="mingcute:telegram-fill" on:click={() => window.location.href = TELEGRAM_INVITE}>
<h3>Telegram</h3>
</CommunityCard>
<CommunityCard color="226, 60, 68" icon="ri:qq-fill" on:click={() => window.location.href = QQ_INVITE}>
<h3>QQ</h3>
</CommunityCard>
</div>
</div>
<style lang="sass">
</style>

View File

@@ -0,0 +1,162 @@
<script lang="ts">
import { fade } from "svelte/transition"
import { t } from "../../libs/i18n";
import ActionCard from "../../components/ActionCard.svelte";
import StatusOverlays from "../../components/StatusOverlays.svelte";
import { CARD, GAME, USER } from "../../libs/sdk";
import Icon from "@iconify/svelte";
let load = false;
let error = "";
let conflict: {
oldName: string,
oldRating: number,
newName: string,
newRating: number
} | null;
let confirmAction: (override: boolean) => void;
let fileInput: HTMLInputElement;
const startImport = async (e: Event & { currentTarget: EventTarget & HTMLInputElement; }) => {
const file = e.currentTarget.files?.[0]
if (!file) return;
load = true;
try {
const data = JSON.parse(await file.text()) as any;
const me = await USER.me();
const maybeUserMusicList = data?.userMusicList || data;
if (Array.isArray(maybeUserMusicList) && maybeUserMusicList.every(it => Array.isArray(it?.userMusicDetailList))) {
// Is music list array
await GAME.importMusicDetail("mai2", maybeUserMusicList.flatMap(it => it.userMusicDetailList));
location.href = `/u/${me.username}/mai2`;
return;
}
const game = getGameByCode(data.gameId);
const userGames = await CARD.userGames(me.username);
const existed = userGames[game];
if (existed) {
conflict = {
oldName: existed.name,
oldRating: existed.rating,
newName: data.userData.userName,
newRating: data.userData.playerRating
};
if (!await new Promise(resolve => confirmAction = resolve)) {
return;
}
conflict = null;
}
await GAME.import(game, data);
location.href = `/u/${me.username}/${game}`;
} catch (e: any) {
error = e.message;
console.error(e);
} finally {
conflict = null;
load = false;
}
}
const getGameByCode = (code: string) => {
switch (code?.toUpperCase()) {
case 'SDEZ':
return 'mai2';
case 'SDHD':
return 'chu3';
default:
throw new Error(t('home.import.unknown-game'));
}
}
</script>
<ActionCard color="209, 124, 102" icon="bxs:file-import" on:click={() => fileInput.click()}>
<h3>{t('home.import')}</h3>
<span>{t('home.import-description')}</span>
<input type="file" accept=".json" bind:this={fileInput} style="display: none"
on:change={startImport}/>
</ActionCard>
<StatusOverlays {error} loading={load}/>
{#if conflict}
<div class="overlay" transition:fade>
<div>
<h2>{t('home.import.data-conflict')}</h2>
<p></p>
<div class="conflict-cards">
<div class="old card">
<span class="type">{t('home.linkcard.account-card')}</span>
<span>{t('home.linkcard.name')}: {conflict.oldName}</span>
<span>{t('home.linkcard.rating')}: {conflict.oldRating}</span>
<div class="trash">
<Icon icon="ph:trash-duotone"/>
</div>
</div>
<div class="icon">
<Icon icon="icon-park-outline:down"/>
</div>
<div class="new card">
<span class="type">{t('home.import.new-data')}</span>
<span>{t('home.linkcard.name')}: {conflict.newName}</span>
<span>{t('home.linkcard.rating')}: {conflict.newRating}</span>
</div>
</div>
<p></p>
<div class="buttons">
<button on:click={() => confirmAction(false)}>{t('action.cancel')}</button>
<button class="error" on:click={() => confirmAction(true)}>{t('action.confirm')}</button>
</div>
</div>
</div>
{/if}
<style lang="sass">
@use "../../vars"
h3
font-size: 1.3rem
margin: 0
.conflict-cards
display: grid
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))
gap: 0.5rem
align-items: center
span:not(.type)
font-size: 0.8rem
.old
background: #ff6b6b20
border: 1px solid vars.$c-error
color: #ffffff99
position: relative
.trash
display: flex
position: absolute
bottom: 0.5rem
right: 0.5rem
color: vars.$c-error
opacity: 0.6
font-size: 2rem
.new
background: #646cff20
border: 1px solid vars.$c-darker
.buttons
display: grid
grid-template-columns: 1fr 1fr
gap: 1rem
.icon
display: flex
justify-content: center
font-size: 2rem
</style>

View File

@@ -0,0 +1,375 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { fade, slide } from "svelte/transition"
import type { Card, CardSummary, CardSummaryGame, ConfirmProps, AquaNetUser } from "../../libs/generalTypes";
import { CARD, USER } from "../../libs/sdk";
import moment from "moment"
import Icon from "@iconify/svelte";
import StatusOverlays from "../../components/StatusOverlays.svelte";
import { t } from "../../libs/i18n";
// State
let state: 'ready' | 'linking-AC' | 'linking-SN' | 'loading' = "loading"
let showConfirm: ConfirmProps | null = null
let error: string = ""
let me: AquaNetUser | null = null
let accountCardSummary: CardSummary | null = null
// Fetch data for current user
const updateMe = () => USER.me().then(m => {
me = m
m.cards.sort((a, b) => a.registerTime < b.registerTime ? 1 : -1)
CARD.summary(m.ghostCard.luid).then(s => accountCardSummary = s.summary)
// Always put the ghost card at the top
m.cards.sort((a, b) => a.isGhost ? -1 : 1)
state = "ready"
}).catch(e => error = e.message)
updateMe()
// Data conflict overlay
let conflictCardID: string = ""
let conflictSummary: CardSummary | null = null
let conflictGame: string = ""
let conflictNew: CardSummaryGame | null = null
let conflictOld: CardSummaryGame | null = null
let conflictToMigrate: string[] = []
function setError(msg: string, type: 'AC' | 'SN') {
type === 'AC' ? errorAC = msg : errorSN = msg
}
async function doLink(id: string, migrate: string) {
await CARD.link({cardId: id, migrate})
await updateMe()
state = "ready"
}
async function link(type: 'AC' | 'SN') {
if (state !== 'ready' || accountCardSummary === null) return
state = "linking-" + type
const id = type === 'AC' ? inputAC : inputSN
console.log("linking card", id)
// Check if this card is already linked in the account
if (me?.cards?.some(c => formatLUID(c.luid, c.isGhost).toLowerCase() === id.toLowerCase())) {
setError(t('home.linkcard.linked-own'), type)
state = "ready"
return
}
// First, lookup the card summary
const card = (await CARD.summary(id).catch(e => {
// If card is not found, create a card and link it
if (e.message === t('home.linkcard.notfound')) {
doLink(id, "")
return
}
setError(e.message, type)
state = "ready"
return
}))!
const summary = card.summary
// Check if it's already linked
if (card.card.linked) {
setError(t('home.linkcard.linked-another'), type)
state = "ready"
return
}
// If all games in summary are null or doesn't conflict with the ghost card,
// we can link the card directly
if (Object.keys(summary).every(k => summary[k as keyof CardSummary] === null
|| accountCardSummary!![k as keyof CardSummary] === null)) {
console.log("linking card directly")
await doLink(id, Object.keys(summary).filter(k => summary[k as keyof CardSummary] !== null).join(","))
}
// For each conflicting game, ask the user if they want to migrate the data
else {
conflictSummary = summary
conflictCardID = id
await linkConflictContinue(null)
}
}
async function linkConflictContinue(choose: "old" | "new" | null) {
if (accountCardSummary === null || conflictSummary === null) return
console.log("linking card with migration")
if (choose) {
// If old is chosen, nothing needs to be migrated
// If new is chosen, we need to migrate the data
if (choose === "new") {
conflictToMigrate.push(conflictGame)
}
// Continue to the next card
conflictSummary[conflictGame as keyof CardSummary] = null
}
let isConflict = false
for (const k in conflictSummary) {
conflictNew = conflictSummary[k as keyof CardSummary]
conflictOld = accountCardSummary[k as keyof CardSummary]
conflictGame = k
if (!conflictNew || !conflictOld) continue
isConflict = true
break
}
// If there are no longer conflicts, we can link the card
if (!isConflict) {
await doLink(conflictCardID, conflictToMigrate.join(","))
// Reset the conflict state
linkConflictCancel()
}
}
function linkConflictCancel() {
state = "ready"
conflictSummary = null
conflictCardID = ""
conflictGame = ""
conflictNew = null
conflictOld = null
conflictToMigrate = []
}
async function unlink(card: Card) {
showConfirm = {
title: t('home.linkcard.unlink'),
message: t('home.linkcard.unlink-notice'),
confirm: async () => {
await CARD.unlink(card.luid)
await updateMe()
showConfirm = null
},
cancel: () => showConfirm = null,
dangerous: true
}
}
// Access code input
const inputACRegex = /^(\d{4} ){0,4}\d{0,4}$/
let inputAC = ""
let errorAC = ""
function inputACChange(e: any) {
e = e as InputEvent
// Add spaces to the input
const old = inputAC
if (e.inputType === "insertText" && inputAC.length % 5 === 4 && inputAC.length < 24)
inputAC += " "
inputAC = inputAC.slice(0, 24)
if (inputAC !== old) errorAC = ""
}
// Serial number input
const inputSNRegex = /^([0-9A-Fa-f]{0,2}:){0,7}[0-9A-Fa-f]{0,2}$/
let inputSN = ""
let errorSN = ""
function inputSNChange(e: any) {
e = e as InputEvent
// Add colons to the input
const old = inputSN
if (e.inputType === "insertText" && inputSN.length % 3 === 2 && inputSN.length < 23)
inputSN += ":"
inputSN = inputSN.toUpperCase().slice(0, 23)
if (inputSN !== old) errorSN = ""
}
function formatLUID(luid: string, ghost: boolean = false) {
if (ghost) return luid.slice(0, 6) + " " + (luid.slice(6).match(/.{4}/g)?.join(" ") ?? "")
switch (cardType(luid)) {
case "Felica SN":
return BigInt(luid).toString(16).toUpperCase().padStart(16, "0").match(/.{1,2}/g)!.join(":")
case "Access Code":
return luid.match(/.{4}/g)!.join(" ")
default:
return luid
}
}
function cardType(luid: string) {
if (luid.startsWith("00")) return "Felica SN"
if (luid.length === 20) return "Access Code"
if (luid.includes(":")) return "Felica SN"
if (luid.includes(" ")) return "Access Code"
return "Unknown"
}
function isInput(e: KeyboardEvent) {
return e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey
}
</script>
<div class="link-card">
<h2>{t('home.linkcard.cards')}</h2>
<p>{t('home.linkcard.description')}:</p>
{#if me}
<div class="existing-cards" transition:slide>
{#each me.cards as card (card.luid)}
<div class:ghost={card.isGhost} class='existing card' transition:fade|global>
<span class="type">{card.isGhost ? t('home.linkcard.account-card') : cardType(card.luid)}</span>
<span class="register">{t('home.linkcard.registered')}: {moment(card.registerTime).format("YYYY MMM DD")}</span>
<span class="last">{t('home.linkcard.lastused')}: {moment(card.accessTime).format("YYYY MMM DD")}</span>
<div></div>
<span class="id">{formatLUID(card.luid, card.isGhost)}</span>
{#if !card.isGhost}
<button class="icon error" on:click={() => unlink(card)}><Icon icon="tabler:trash-x-filled"/></button>
{/if}
</div>
{/each}
</div>
{/if}
<h2>{t('home.link-card')}</h2>
<p>{t('home.linkcard.enter-info')}:</p>
{#if !inputSN}
<div out:slide={{ duration: 250 }}>
<p>{t('home.linkcard.access-code')}</p>
<label>
<!-- DO NOT change the order of bind:value and on:input. Their order determines the order of reactivity -->
<input placeholder="e.g. 5200 1234 5678 9012 3456"
on:keydown={(e) => {
e.key === "Enter" && link('AC')
// Ensure key is numeric
if (isInput(e) && !/[\d ]/.test(e.key)) e.preventDefault()
}}
bind:value={inputAC}
on:input={inputACChange}
class:error={inputAC && (!inputACRegex.test(inputAC) || errorAC)}>
{#if inputAC.length > 0}
<button transition:slide={{axis: 'x'}} on:click={() => {link('AC');inputAC=''}}>{t('home.linkcard.link')}</button>
{/if}
</label>
{#if errorAC}
<p class="error" transition:slide>{errorAC}</p>
{/if}
</div>
{/if}
{#if !inputAC}
<div out:slide={{ duration: 250 }}>
<p>{t('home.linkcard.enter-sn1')}
(<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>)
{t('home.linkcard.enter-sn2')}
</p>
<label>
<input placeholder="e.g. 01:2E:1A:2B:3C:4D:5E:6F"
on:keydown={(e) => {
e.key === "Enter" && link('SN')
// Ensure key is hex or colon
if (isInput(e) && !/[0-9A-Fa-f:]/.test(e.key)) e.preventDefault()
}}
bind:value={inputSN}
on:input={inputSNChange}
class:error={inputSN && (!inputSNRegex.test(inputSN) || errorSN)}>
{#if inputSN.length > 0}
<button transition:slide={{axis: 'x'}} on:click={() => {link('SN'); inputSN = ''}}>{t('home.linkcard.link')}</button>
{/if}
</label>
{#if errorSN}
<p class="error" transition:slide>{errorSN}</p>
{/if}
</div>
{/if}
{#if conflictOld && conflictNew && me}
<div class="overlay" transition:fade>
<div>
<h2>{t('home.linkcard.data-conflict')}</h2>
<p></p>
<div class="conflict-cards">
<div class="old card clickable" on:click={() => linkConflictContinue('old')}
role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('old')}>
<span class="type">{t('home.linkcard.account-card')}</span>
<span>{t('home.linkcard.name')}: {conflictOld.name}</span>
<span>{t('home.linkcard.rating')}: {conflictOld.rating}</span>
<span>{t('home.linkcard.last-login')}: {moment(conflictOld.lastLogin).format("YYYY MMM DD")}</span>
<span class="id">{formatLUID(me.ghostCard.luid, true)}</span>
</div>
<div class="new card clickable" on:click={() => linkConflictContinue('new')}
role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('new')}>
<span class="type">{cardType(conflictCardID)}</span>
<span>{t('home.linkcard.name')}: {conflictNew.name}</span>
<span>{t('home.linkcard.rating')}: {conflictNew.rating}</span>
<span>{t('home.linkcard.last-login')}: {moment(conflictNew.lastLogin).format("YYYY MMM DD")}</span>
<span class="id">{conflictCardID}</span>
</div>
</div>
<button class="error" on:click={linkConflictCancel}>{t('action.cancel')}</button>
</div>
</div>
{/if}
<StatusOverlays bind:confirm={showConfirm} bind:error={error} loading={!me} />
</div>
<style lang="sass">
@use "../../vars"
.link-card
input
width: 100%
label
display: flex
button
margin-left: 1rem
.existing-cards, .conflict-cards
display: grid
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))
gap: 1rem
.existing-cards .existing.card
min-height: 90px
position: relative
overflow: hidden
*
white-space: nowrap
&.ghost
background: rgba(vars.$c-darker, 0.8)
.register, .last
opacity: 0.7
span:not(.type)
font-size: 0.8rem
> div
flex: 1
button
position: absolute
right: 10px
bottom: 10px
.conflict-cards
.card
transition: vars.$transition
.card:hover
background: vars.$c-darker
span:not(.type)
font-size: 0.8rem
.id
opacity: 0.7
</style>

View File

@@ -0,0 +1,103 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { fade, slide } from "svelte/transition";
import { USER } from "../../libs/sdk";
import type { AquaNetUser } from "../../libs/generalTypes";
import { codeToHtml } from 'shiki'
import { AQUA_CONNECTION, DISCORD_INVITE, FADE_IN, FADE_OUT } from "../../libs/config";
import { t } from "../../libs/i18n";
let user: AquaNetUser
let keychip: string;
let keychipCode: string;
let getStartedRequesting = false;
USER.me().then((u) => {
user = u;
});
function getStarted() {
if (getStartedRequesting) return;
getStartedRequesting = true;
USER.keychip().then(k => {
getStartedRequesting = false;
keychip = k;
codeToHtml(`
[dns]
default=${AQUA_CONNECTION}
[keychip]
enable=1
; ${t('home.setup.keychip-tips')}
id=${keychip.slice(0, 4)}-${keychip.slice(4)}1337`.trim(), {
lang: 'ini',
theme: 'rose-pine',
}).then((html) => {
keychipCode = html;
});
});
}
</script>
<div class="setup-instructions">
<h2>{t('home.setup')}</h2>
<p>
{t('home.setup.welcome')}
</p>
<blockquote>
{t('home.setup.blockquote')}
</blockquote>
{#if user}
<div transition:slide>
{#if !keychip && !keychipCode}
<div class="no-margin" out:fade={FADE_OUT}>
<button class="emp" on:click={getStarted}>{t('home.setup.get')}</button>
</div>
{:else}
<div class="no-margin" in:fade={FADE_IN}>
<p>
{t('home.setup.edit')}:
</p>
<div class="code">
{@html keychipCode}
</div>
<p>
{t('home.setup.test')}
</p>
<p>
{t('home.setup.ask')} <a href={DISCORD_INVITE}>Discord</a> {t('home.setup.support')}.
</p>
</div>
{/if}
</div>
{:else}
<p>Loading...</p>
{/if}
</div>
<style lang="sass">
.code
overflow-x: auto
:global(pre.shiki)
background-color: transparent !important
:global(code)
counter-reset: step
counter-increment: step 0
:global(code .line::before)
content: counter(step)
counter-increment: step
width: 1rem
margin-right: 1.5rem
display: inline-block
text-align: right
color: rgba(115,138,148,.4)
</style>

View File

@@ -0,0 +1,136 @@
<script lang="ts">
import { title } from "../libs/ui";
import { GAME } from "../libs/sdk";
import type { GenericRanking } from "../libs/generalTypes";
import StatusOverlays from "../components/StatusOverlays.svelte";
import type { GameName } from "../libs/scoring";
import { GAME_TITLE } from "../libs/i18n";
import { t } from "../libs/i18n";
import UserCard from "../components/UserCard.svelte";
import Tooltip from "../components/Tooltip.svelte";
export let game: GameName = 'mai2';
title(`Ranking`);
let d: { users: GenericRanking[] };
let error: string | null;
Promise.all([GAME.ranking(game)])
.then(([users]) => {
console.log(users)
d = { users };
})
.catch((e) => error = e.message);
let hoveringUser = "";
let hoverLoading = false;
</script>
<main class="content leaderboard">
<div class="outer-title-options">
<h2>{t("Leaderboard.Title")}</h2>
<nav>
{#each Object.entries(GAME_TITLE) as [k, v]}
<a href="/ranking/{k}" class:active={k === game}>{v}</a>
{/each}
</nav>
</div>
{#if d}
<div class="leaderboard-container">
<div class="lb-user" on:mouseenter={() => hoveringUser = d.users[0].username} role="heading" aria-level="2">
<span class="rank">{t("Leaderboard.Rank")}</span>
<span class="name"></span>
<span class="rating">{t("Leaderboard.Rating")}</span>
<span class="accuracy">{t("Leaderboard.Accuracy")}</span>
<span class="fc">{t("Leaderboard.FC")}</span>
<span class="ap">{t("Leaderboard.AP")}</span>
</div>
{#each d.users as user, i (user.rank)}
<div class="lb-user" class:alternate={i % 2 === 1} role="listitem"
on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}>
<span class="rank">#{user.rank}</span>
<span class="name">
{#if user.username !== ""}
<a href="/u/{user.username}/{game}" class:registered={!(/user\d+/.test(user.username))}>{user.name}</a>
{:else}
<span>{user.name}</span>
{/if}
</span>
<span class="rating">{
game === 'chu3' ?
(user.rating / 100).toFixed(2) :
user.rating.toLocaleString()
}</span>
<span class="accuracy">{(+user.accuracy).toFixed(2)}%</span>
<span class="fc">{user.fullCombo}</span>
<span class="ap">{user.allPerfect}</span>
</div>
{/each}
</div>
<Tooltip triggeredBy=".name" loading={hoverLoading}>
<UserCard username={hoveringUser} {game} setLoading={l => hoverLoading = l} />
</Tooltip>
{/if}
<StatusOverlays error={error} loading={!d} />
</main>
<style lang="sass">
@use "../vars"
.leaderboard-container
display: flex
flex-direction: column
.lb-user
display: flex
align-items: center
justify-content: space-between
width: 100%
gap: 12px
border-radius: vars.$border-radius
padding: 6px 12px
box-sizing: border-box
> *:not(.name)
text-align: center
.name
min-width: 100px
flex: 1
> a
color: unset
.registered
background: vars.$grad-special
color: transparent
-webkit-background-clip: text
background-clip: text
.accuracy, .rating
width: 15%
min-width: 45px
.rating
font-weight: bold
color: white
.fc, .ap
width: 5%
min-width: 20px
@media (max-width: vars.$w-mobile)
font-size: 0.9rem
.accuracy
display: none
&.alternate
background-color: vars.$ov-light
</style>

View File

@@ -0,0 +1,200 @@
<!-- Svelte 4.2.11 -->
<script lang="ts">
import { slide, fade } from "svelte/transition";
import type { AquaNetUser } from "../../libs/generalTypes";
import { CARD, USER } from "../../libs/sdk";
import StatusOverlays from "../../components/StatusOverlays.svelte";
import Icon from "@iconify/svelte";
import { pfp } from "../../libs/ui";
import { t, ts } from "../../libs/i18n";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import UserBox from "../../components/settings/ChuniSettings.svelte";
import Mai2Settings from "../../components/settings/Mai2Settings.svelte";
import WaccaSettings from "../../components/settings/WaccaSettings.svelte";
import GeneralGameSettings from "../../components/settings/GeneralGameSettings.svelte";
USER.ensureLoggedIn()
let me: AquaNetUser;
let error: string;
let submitting = ""
let tab = 0
let tabs = [ 'profile', 'game' ]
const profileFields = [
[ 'displayName', t('settings.profile.name') ],
[ 'username', t('settings.profile.username') ],
[ 'password', t('settings.profile.password') ],
[ 'profileLocation', t('settings.profile.location') ],
[ 'profileBio', t('settings.profile.bio') ],
] as const
// Fetch user data
const getMe = () => USER.me().then((m) => {
me = m
CARD.userGames(m.username).then(games => {
if (games.chu3 && !tabs.includes('chu3')) {
tabs = [...tabs, 'chu3']
}
if (games.mai2 && !tabs.includes('mai2')) {
tabs = [...tabs, 'mai2']
}
if (games.wacca && !tabs.includes('wacca')) {
tabs = [...tabs, 'wacca']
}
})
}).catch(e => error = e.message)
getMe()
let changed: string[] = []
let pfpField: HTMLInputElement
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(file: File) {
if (submitting) return
submitting = 'profilePicture'
USER.uploadPfp(file).then(() => {
me.profilePicture = file.name
// reload
getMe()
}).catch(e => error = e.message).finally(() => submitting = "")
}
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
if (whether) node.type = 'password'
}
</script>
<main class="content">
<div class="outer-title-options">
<h2>{t('settings.title')}</h2>
<nav>
{#each tabs as tabName, i}
<div transition:slide={{axis: 'x'}} class:active={tab === i}
on:click={() => tab = i} on:keydown={e => e.key === 'Enter' && (tab = i)}
role="button" tabindex="0">
{ts(`settings.tabs.${tabName}`)}
</div>
{/each}
</nav>
</div>
{#if tab === 0 && me}
<!-- Tab 0: Profile settings -->
<div out:fade={FADE_OUT} in:fade={FADE_IN} 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/*" style="display: none" bind:this={pfpField}
on:change={() => pfpField.files && uploadPfp(pfpField.files[0])} />
</div>
{#each profileFields as [field, name], i (field)}
<div class="field">
<label for={field}>{name}</label>
<div>
<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 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>
{:else if tabs[tab] === 'chu3'}
<!-- Userbox settings -->
<UserBox />
{:else if tabs[tab] === 'mai2'}
<Mai2Settings username={me.username} />
{:else if tabs[tab] === 'wacca'}
<WaccaSettings />
{:else if tabs[tab] === 'game'}
<GeneralGameSettings />
{/if}
<StatusOverlays {error} loading={!me || !!submitting} />
</main>
<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
flex: 1
img
max-width: 100px
max-height: 100px
border-radius: vars.$border-radius
object-fit: cover
</style>

View File

@@ -0,0 +1,579 @@
<script lang="ts">
import { CHARTJS_OPT, coverNotFound, pfpNotFound, registerChart, renderCal, title, tooltip, pfp } from "../libs/ui";
import type {
GenericGamePlaylog,
GenericGameSummary,
MusicMeta,
TrendEntry,
AquaNetUser,
AllMusic
} from "../libs/generalTypes";
import { DATA_HOST } from "../libs/config";
import 'cal-heatmap/cal-heatmap.css';
import moment from "moment";
import 'chartjs-adapter-moment';
import { CARD, DATA, GAME, USER } from "../libs/sdk";
import { type GameName, getMult, roundFloor } from "../libs/scoring";
import StatusOverlays from "../components/StatusOverlays.svelte";
import Icon from "@iconify/svelte";
import { GAME_TITLE, t } from "../libs/i18n";
import RankDetails from "../components/RankDetails.svelte";
import RatingComposition from "../components/RatingComposition.svelte";
import useLocalStorage from "../libs/hooks/useLocalStorage.svelte";
import Line from "../components/chart/Line.svelte";
const TREND_DAYS = 60
registerChart()
export let username: string;
export let game: GameName = "mai2"
let calElement: HTMLElement
let error: string;
let me: AquaNetUser
title(`User ${username}`)
const rounding = useLocalStorage("rounding", true);
const titleText = GAME_TITLE[game]
interface MusicAndPlay extends MusicMeta, GenericGamePlaylog {}
let d: {
user: GenericGameSummary,
trend: TrendEntry[]
recent: MusicAndPlay[],
validGames: [ string, string ][],
} | null
let allMusics: AllMusic
let showDetailRank = false
let isLoading = false
function init() {
USER.isLoggedIn() && USER.me().then(u => me = u)
CARD.userGames(username).then(games => {
if (!games[game]) {
// Find a valid game
const valid = Object.entries(games).filter(([g, valid]) => valid)
if (!valid || !valid[0]) return error = t("UserHome.NoValidGame")
window.location.href = `/u/${username}/${valid[0][0]}`
}
Promise.all([
GAME.userSummary(username, game),
GAME.trend(username, game),
DATA.allMusic(game),
]).then(([user, trend, music]) => {
console.log(user)
console.log(trend)
console.log(games)
// If game is wacca, divide all ratings by 10
if (game === 'wacca') {
user.rating /= 10
trend.forEach(it => it.rating /= 10)
user.recent.forEach(it => {
it.beforeRating /= 10
it.afterRating /= 10
})
}
const minDate = moment().subtract(TREND_DAYS, 'days').format("YYYY-MM-DD")
d = {user,
trend: trend.filter(it => it.date >= minDate && it.plays != 0),
recent: user.recent.map(it => {return {...music[it.musicId], ...it}}),
validGames: Object.entries(GAME_TITLE).filter(g => games[g[0] as GameName])
}
allMusics = music
renderCal(calElement, trend.map(it => {return {date: it.date, value: it.plays}})).then(() => {
// Scroll to the rightmost
calElement.scrollLeft = calElement.scrollWidth - calElement.clientWidth
})
}).catch((e) => error = e.message);
}).catch((e) => { error = e.message; console.error(e) } );
}
if (Object.keys(GAME_TITLE).includes(game)) init()
else error = t("UserHome.InvalidGame", {game})
const setRival = (isAdd: boolean) => {
isLoading = true
GAME.setRival(game, username, isAdd).then(() => {
d!.user.rival = isAdd
}).catch(e => error = e.message).finally(() => isLoading = false)
}
</script>
<main id="user-home" class="content">
{#if d}
<div class="user-pfp">
<img use:pfp={d.user.aquaUser} alt="" class="pfp" on:error={pfpNotFound}>
<div class="name-box">
<h2>{d.user.name}</h2>
{#if typeof d.user.rival === 'boolean' && game === 'mai2'}
<span class="clickable" on:click={() => setRival(!d?.user.rival)} role="button" tabindex="0"
on:keydown={e => e.key === "Enter" && setRival(!d?.user.rival)}>
{d.user.rival ? t("UserHome.RemoveRival") : t("UserHome.AddRival")}
</span>
{/if}
{#if me && me.username === username}
<a class="setting-icon clickable" use:tooltip={t("UserHome.Settings")} href="/settings">
<Icon icon="eos-icons:rotating-gear"/>
</a>
{/if}
</div>
<nav>
{#each d.validGames as [g, name]}
<a href={`/u/${username}/${g}`} class:active={game === g}>{name}</a>
{/each}
</nav>
</div>
<div>
<h2>{titleText} {t('UserHome.Statistics')}</h2>
<div class="scoring-info">
<div class="chart">
<div class="info-top">
<div class="rating">
<span>{game === 'mai2' ? t("UserHome.DXRating"): t("UserHome.Rating")}</span>
<span>{
game === 'chu3' ?
(d.user.rating / 100).toFixed(2) :
d.user.rating.toLocaleString()
}</span>
</div>
<div class="rank">
<span>{t('UserHome.ServerRank')}</span>
<span>#{(d.user.serverRank + 1).toLocaleString()}</span>
</div>
</div>
<div class="trend">
<!-- ChartJS cannot be fully responsive unless there is a parent div that's independent from its size and helps it determine its size -->
<div class="chartjs-box-reference">
{#if d.trend.length <= 1}
<div class="no-data">{t("UserHome.NoData", { days: TREND_DAYS })}</div>
{:else}
<Line data={{
datasets: [
{
label: 'Rating',
data: d.trend.map(it => {return {x: Date.parse(it.date), y: it.rating}}),
borderColor: '#646cff',
tension: 0.1,
// TODO: Set X axis span to 3 months
}
]
}} options={CHARTJS_OPT} />
{/if}
</div>
</div>
{#if Object.entries(d.user.detailedRanks).length > 0}
<div class="info-bottom clickable" use:tooltip={t("UserHome.ShowRanksDetails")}
on:click={() => showDetailRank = !showDetailRank} role="button" tabindex="0"
on:keydown={e => e.key === "Enter" && (showDetailRank = !showDetailRank)}>
{#each d.user.ranks as r}
<div><span>{r.name}</span><span>{r.count}</span></div>
{/each}
</div>
{:else}
<div class="info-bottom">
{#each d.user.ranks as r}
<div><span>{r.name}</span><span>{r.count}</span></div>
{/each}
</div>
{/if}
</div>
<div class="other-info">
<div class="accuracy">
<span>{t('UserHome.Accuracy')}</span>
<span>{(d.user.accuracy).toFixed(2)}%</span>
</div>
<div class="max-combo">
<span>{t("UserHome.MaxCombo")}</span>
<span>{d.user.maxCombo}</span>
</div>
<div class="full-combo">
<span>{t("UserHome.FullCombo")}</span>
<span>{d.user.fullCombo}</span>
</div>
<div class="all-perfect">
<span>{t("UserHome.AllPerfect")}</span>
<span>{d.user.allPerfect}</span>
</div>
<div class="total-dx-score">
<span>{game === 'mai2' ? t('UserHome.DXScore') : t("UserHome.Score")}</span>
<span>{d.user.totalScore.toLocaleString()}</span>
</div>
</div>
</div>
</div>
{#if showDetailRank}<RankDetails g={d.user}/>{/if}
<div>
<h2>{t('UserHome.PlayActivity')}</h2>
<div class="activity-info">
<div class="hide-scrollbar" id="cal-heatmap" bind:this={calElement}></div>
<div class="info-bottom">
<div class="plays">
<span>{t("UserHome.Plays")}</span>
<span>{d.user.plays}</span>
</div>
<div class="time">
<span>{t('UserHome.PlayTime')}</span>
<span>{(d.user.totalPlayTime / 60).toFixed(1)} hr</span>
</div>
<div class="first-play">
<span>{t('UserHome.FirstSeen')}</span>
<span>{moment(d.user.joined).format("YYYY-MM-DD")}</span>
</div>
<div class="last-play">
<span>{t('UserHome.LastSeen')}</span>
<span>{moment(d.user.lastSeen).format("YYYY-MM-DD")}</span>
</div>
<div class="last-version">
<span>{t('UserHome.Version')}</span>
<span>{d.user.lastVersion}</span>
</div>
</div>
</div>
</div>
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} {game}/>
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} {game}/>
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} {game}/>
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} {game} top={10}/>
<div class="recent">
<h2>{t('UserHome.RecentScores')}</h2>
<div class="scores">
{#each d.recent as r, i}
<div class:alt={i % 2 === 0}>
<img src={`${DATA_HOST}/d/${game}/music/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="" on:error={coverNotFound} />
<div class="info">
<div>{r.name ?? t("UserHome.UnknownSong")}</div>
<div>
{#if r.isAllPerfect || r.isAllJustice}
<img src="/assets/imgs/All Perfect.png" alt="All Perfect" />
{:else if r.isFullCombo}
<img src="/assets/imgs/Full Combo.png" alt="Full Combo" />
{/if}
<span class={`lv level-${r.level === 10 ? 5 : r.level}`}>
<span>
{r.notes?.[r.level === 10 ? 0 : r.level]?.lv?.toFixed(1) ?? r.worldsEndTag ?? '-'}
</span>
</span>
<span class={`rank-${getMult(r.achievement, game)[2].toString()[0]}`}>
<span class="rank-text">{("" + getMult(r.achievement, game)[2]).replace("p", "+")}</span>
<span class="rank-num" use:tooltip={(r.achievement / 10000).toFixed(4)}>
{
rounding.value ?
roundFloor(r.achievement, game, 1) :
(r.achievement / 10000).toFixed(4)
}%
</span>
</span>
{#if game === 'mai2' || game === 'wacca'}
<span class:increased={r.afterRating - r.beforeRating > 0} class="dx-change">
{r.afterRating === r.beforeRating ? '-' : (r.afterRating - r.beforeRating).toFixed(0)}
</span>
{/if}
</div>
</div>
</div>
{/each}
</div>
</div>
{/if}
<StatusOverlays {error} loading={!d || isLoading} />
</main>
<style lang="sass">
@use "../vars"
#user-home
.user-pfp
display: flex
align-items: flex-end
gap: vars.$gap
margin-top: -72px
position: relative
h2
font-size: 2rem
margin: 0
white-space: nowrap
nav
position: absolute
display: flex
flex-direction: row
gap: 10px
top: 4px
right: 0
.setting-icon
font-size: 1.5rem
color: vars.$c-main
cursor: pointer
display: flex
align-items: center
.name-box
flex: 1
display: flex
align-items: center
justify-content: space-between
gap: 10px
.pfp
width: 100px
height: 100px
border-radius: vars.$border-radius
object-fit: cover
@media (max-width: vars.$w-mobile)
.user-pfp
margin-top: -68px
h2
font-size: 1.5rem
.pfp
width: 80px
height: 80px
.info-bottom, .info-top, .other-info
display: flex
gap: vars.$gap
> div
display: flex
flex-direction: column
> span:first-child
font-weight: bold
font-size: 0.8rem
// character spacing
letter-spacing: 0.1em
color: vars.$c-main
.info-bottom
width: max-content
.info-top > div > span:last-child
font-size: 1.5rem
.info-bottom
max-width: 100%
flex-wrap: wrap
row-gap: 0.5rem
.scoring-info
display: flex
gap: vars.$gap
max-height: 250px
.chart
flex: 0 1 790px
display: flex
flex-direction: column
.other-info
flex: 1 0 100px
flex-direction: column
gap: 0
justify-content: space-between
.trend
height: 300px
width: 100%
max-width: 790px
position: relative
> .chartjs-box-reference
position: absolute
inset: 0
display: flex
align-items: center
justify-content: center
.no-data
opacity: 0.5
user-select: none
@media (max-width: vars.$w-mobile)
flex-direction: column
max-height: unset
.chart
flex: 0
.trend
max-height: 130px
.other-info
> div
flex-direction: row
justify-content: space-between
.info-bottom
justify-content: space-between
.activity-info
display: flex
flex-direction: column
gap: vars.$gap
#cal-heatmap
overflow-x: auto
@media (max-width: vars.$w-mobile)
#cal-heatmap
width: 100%
.info-bottom
flex-direction: column
gap: 0
width: 100%
> div
flex-direction: row
justify-content: space-between
// Recent Scores section
.recent
.scores
display: flex
flex-direction: column
flex-wrap: wrap
gap: vars.$gap
> div.alt
background-color: rgba(white, 0.03)
border-radius: vars.$border-radius
// Image and song info
> div
display: flex
align-items: center
gap: vars.$gap
padding-right: 16px
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
align-items: center
overflow: hidden
// Limit song name to one line
> div:first-child
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
display: flex
align-items: center
gap: 10px
img
height: 1.5em
width: 1.5em
@media (max-width: vars.$w-mobile)
flex-direction: column
gap: 0
.rank-text
text-align: left
// Score and rank should be space-between on mobile
> div:last-child
display: flex
justify-content: space-between
gap: 10px
.lv
margin-right: auto
.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
min-width: 30px
text-align: center
background: rgba(var(--lv-color), 0.6)
padding: 0 6px
border-radius: vars.$border-radius
.lv.level-5 > span
color: transparent
background: var(--lv-text-clip)
background-clip: text
-webkit-background-clip: text
font-weight: bold
font-size: 1em
font-family: 'Arial Black', sans-serif
span
display: inline-block
text-align: right
// Vertical table-like alignment
span.rank-text
min-width: 38px
span.rank-num
min-width: 60px
span.dx-change
min-width: 30px
span.increased
&:before
content: "+"
color: vars.$c-good
</style>

View File

@@ -0,0 +1,251 @@
<script lang="ts">
import { Turnstile } from "svelte-turnstile";
import { slide } from 'svelte/transition';
import { TURNSTILE_SITE_KEY } from "../libs/config";
import Icon from "@iconify/svelte";
import { USER } from "../libs/sdk";
import { t } from "../libs/i18n"
let params = new URLSearchParams(window.location.search)
let state = "home"
$: isSignup = state === "signup"
let submitting = false
let email = ""
let password = ""
let username = ""
let turnstile = ""
let turnstileReset: () => void | undefined;
let error = ""
let verifyMsg = ""
if (params.get('confirm-email')) {
state = 'verify'
verifyMsg = t("welcome.verifying")
submitting = true
// Send request to server
USER.confirmEmail(params.get('confirm-email')!)
.then(() => {
verifyMsg = t('welcome.verified')
submitting = false
// Clear the query param
window.history.replaceState({}, document.title, window.location.pathname)
})
.catch(e => verifyMsg = t('welcome.verification-failed', { message: e.message }))
}
async function submit(): Promise<any> {
submitting = true
// Check if username and password are valid
if (email === "" || password === "") {
error = t("welcome.email-password-missing")
return submitting = false
}
if (turnstile === "") {
// Sleep for 100ms to allow Turnstile to finish
error = t("welcome.waiting-turnstile")
return setTimeout(submit, 100)
}
// Signup
if (isSignup) {
if (username === "") {
error = t("welcome.username-missing")
return submitting = false
}
// Send request to server
await USER.register({ username, email, password, turnstile })
.then(() => {
// Show verify email message
state = 'verify'
verifyMsg = t("welcome.verification-sent", { email })
})
.catch(e => {
error = e.message
submitting = false
turnstileReset()
})
}
else {
// Send request to server
await USER.login({ email, password, turnstile })
.then(() => window.location.href = "/home")
.catch(e => {
if (e.message === 'Email not verified - STATE_0') {
state = 'verify'
verifyMsg = t("welcome.verify-state-0")
}
else if (e.message === 'Email not verified - STATE_1') {
state = 'verify'
verifyMsg = t("welcome.verify-state-1")
}
else if (e.message === 'Email not verified - STATE_2') {
state = 'verify'
verifyMsg = t("welcome.verify-state-2")
}
else {
error = e.message
submitting = false
turnstileReset()
}
})
}
submitting = false
}
</script>
<main id="home" class="no-margin">
<div>
<h1 id="title">AquaNet</h1>
{#if state === "home"}
<div class="btn-group" transition:slide>
<button on:click={() => state = 'login'}>{t('welcome.btn-login')}</button>
<button on:click={() => state = 'signup'}>{t('welcome.btn-signup')}</button>
</div>
{:else if state === "login" || state === "signup"}
<div class="login-form" transition:slide>
{#if error}
<span class="error">{error}</span>
{/if}
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
role="button" tabindex="0" class="clickable">
<Icon icon="line-md:chevron-small-left" />
<span>{t('back')}</span>
</div>
{#if isSignup}
<input type="text" placeholder={t('username')} bind:value={username}>
{/if}
<input type="email" placeholder={t('email')} bind:value={email}>
<input type="password" placeholder={t('password')} bind:value={password}>
<button on:click={submit}>
{#if submitting}
<Icon icon="line-md:loading-twotone-loop"/>
{:else}
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
{/if}
</button>
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
on:turnstile-error={_ => console.log(error = t("welcome.turnstile-error"))}
on:turnstile-expired={_ => window.location.reload()}
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
</div>
{:else if state === "verify"}
<div class="login-form" transition:slide>
<span>{verifyMsg}</span>
{#if !submitting}
<button on:click={() => state = 'home'} transition:slide>{t('back')}</button>
{/if}
</div>
{/if}
</div>
<div class="light-pollution">
<div class="l1"></div>
<div class="l2"></div>
<div class="l3"></div>
</div>
</main>
<style lang="sass">
@use "../vars"
.login-form
display: flex
flex-direction: column
gap: 8px
width: calc(100% - 12px)
max-width: 300px
div.clickable
display: flex
align-items: center
#home
color: vars.$c-main
position: relative
width: 100%
height: 100%
padding-left: 100px
overflow: hidden
background-color: black
box-sizing: border-box
display: flex
flex-direction: column
justify-content: center
margin-top: -(vars.$nav-height)
// Content container
> div
display: flex
flex-direction: column
align-items: flex-start
width: max-content
// Switching state container
> div
transition: vars.$transition
#title
font-family: Quicksand, vars.$font
user-select: none
// Gap between text characters
letter-spacing: 0.2em
margin-top: 0
margin-bottom: 32px
opacity: 0.9
.btn-group
display: flex
gap: 8px
.light-pollution
pointer-events: none
opacity: 0.8
> div
position: absolute
z-index: 1
.l1
left: -560px
top: 90px
height: 1130px
width: 1500px
$color: rgb(158, 110, 230)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l2
left: -200px
top: 560px
height: 1200px
width: 1500px
$color: rgb(92, 195, 250)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
.l3
left: -600px
opacity: 0.7
top: -630px
width: 1500px
height: 1000px
$color: rgb(230, 110, 156)
background: radial-gradient(50% 50% at 50% 50%, rgba($color, 0.28) 0%, rgba(0,0,0,0) 100%)
@media (max-width: 500px)
align-items: center
padding-left: 0
</style>

24
AquaNet/src/vars.sass Normal file
View File

@@ -0,0 +1,24 @@
$font: Quicksand, Inter, LXGW Wenkai, Microsoft YaHei, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Avenir, Helvetica, Arial, sans-serif
$c-main: #b3c6ff
$c-sub: rgba(0, 0, 0, 0.77)
$c-good: #b3ffb9
$c-darker: #646cff
$c-bg: #242424
$c-error: #ff6b6b
$c-shadow: rgba(0, 0, 0, 0.1)
$ov-light: rgba(white, 0.04)
$ov-lighter: rgba(white, 0.08)
$ov-dark: rgba(black, 0.1)
$ov-darker: rgba(black, 0.18)
$nav-height: 4rem
$w-mobile: 560px
$w-max: 900px
$grad-special: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
$border-radius: 12px
$gap: 20px
$transition: all 0.25s

3
AquaNet/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
declare const APP_VERSION: string;

7
AquaNet/svelte.config.js Normal file
View File

@@ -0,0 +1,7 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),
}

76
AquaNet/tools/migrate.py Normal file
View File

@@ -0,0 +1,76 @@
import pathlib
import re
import subprocess
import tempfile
def extract_and_migrate_sass(file_path):
"""Extracts <style lang="sass"> block from a Svelte file, runs sass-migrator on it, and replaces the original style block."""
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# Regex pattern to match <style lang="sass">...</style>
style_pattern = re.compile(r'<style\s+lang\s*=\s*["\']sass["\']>(.*?)</style>', re.DOTALL)
# Extract all matching style blocks
matches = list(style_pattern.finditer(content))
if not matches:
print(f"No <style lang='sass'> block found in {file_path}")
return
updated_content = content
for match in matches:
original_style_block = match.group(0) # The full <style>...</style> block
sass_content = match.group(1) # The content inside the <style> block
# Create a temporary file in the same directory as the .svelte file
temp_file_path = pathlib.Path(file_path).parent / f"{pathlib.Path(file_path).stem}_temp.sass"
# Write the SASS content to the temporary file
with open(temp_file_path, 'w', encoding='utf-8') as temp_file:
temp_file.write(sass_content)
# Run the sass-migrator on the temporary file
try:
subprocess.run(['sass-migrator', 'module', str(temp_file_path)], check=True)
except subprocess.CalledProcessError as e:
print(f"Error while running sass-migrator on {file_path}: {e}")
continue
# Read back the migrated content
with open(temp_file_path, 'r', encoding='utf-8') as temp_file:
migrated_sass_content = temp_file.read()
# Create the new <style> block with the migrated SASS content
new_style_block = f'<style lang="sass">{migrated_sass_content}</style>'
# Replace the original style block with the new one
updated_content = updated_content.replace(original_style_block, new_style_block)
# Remove the temporary file
temp_file_path.unlink()
# Write the updated content back to the original file
with open(file_path, 'w', encoding='utf-8') as file:
file.write(updated_content)
print(f"Updated {file_path}")
def process_svelte_files(directory):
"""Recursively processes all .svelte files in the given directory."""
svelte_files = pathlib.Path(directory).rglob("*.svelte")
for svelte_file in svelte_files:
extract_and_migrate_sass(svelte_file)
def main():
"""Main function to process all .svelte files in the current directory."""
current_directory = pathlib.Path(__file__).parent
process_svelte_files(current_directory)
if __name__ == "__main__":
main()

20
AquaNet/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"resolveJsonModule": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable checkJs if you'd like to use dynamic types in JS.
* Note that setting allowJs false does not prevent the use
* of JS in `.svelte` files.
*/
"allowJs": true,
"checkJs": true,
"isolatedModules": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler"
},
"include": ["vite.config.ts"]
}

Some files were not shown because too many files have changed in this diff Show More