mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-04 23:57:29 +08:00
Compare commits
16 Commits
js-formatt
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7e493d7f5 | ||
|
|
759519d374 | ||
|
|
3d713b13da | ||
|
|
20468e612d | ||
|
|
c175173821 | ||
|
|
52e9285551 | ||
|
|
f4280c0768 | ||
|
|
295ae14658 | ||
|
|
ccc2bcffce | ||
|
|
a47ed71799 | ||
|
|
006a49cfdb | ||
|
|
9794ee259a | ||
|
|
643e0e0c1f | ||
|
|
6afcb364d1 | ||
|
|
6d4a38404c | ||
|
|
b925c2ef20 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -75,7 +75,4 @@ gradle-app.setting
|
||||
|
||||
### Gradle Patch ###
|
||||
# Java heap dump
|
||||
*.hprof
|
||||
|
||||
### Docker ###
|
||||
/db/*
|
||||
*.hprof
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": false,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
@@ -11,6 +11,6 @@ TicketUnlock=true
|
||||
# Skip the warning screen and logo shown after the POST sequence
|
||||
SkipWarningScreen=true
|
||||
# Single player: Show 1P only, at the center of the screen
|
||||
SinglePlayer=false
|
||||
SinglePlayer=true
|
||||
# !!EXPERIMENTAL!! Skip from the card-scanning screen directly to music selection screen
|
||||
SkipToMusicSelection=true
|
||||
SkipToMusicSelection=false
|
||||
41
AquaNet/.eslintrc.cjs
Normal file
41
AquaNet/.eslintrc.cjs
Normal file
@@ -0,0 +1,41 @@
|
||||
// ..eslintrc.cjs example
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
es2023: true
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '..eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module'
|
||||
},
|
||||
rules: {
|
||||
// Custom styling rules
|
||||
'comma-dangle': ['warn', 'only-multiline'],
|
||||
'indent': ['warn', 2],
|
||||
'semi': ['warn', 'never'],
|
||||
'quotes': ['warn', 'single'],
|
||||
'arrow-parens': ['warn', 'as-needed'],
|
||||
'linebreak-style': ['warn', 'unix'],
|
||||
'object-curly-spacing': ['warn', 'always'],
|
||||
'array-bracket-spacing': ["error", "always", {
|
||||
"singleValue": false,
|
||||
"objectsInArrays": false,
|
||||
"arraysInArrays": false
|
||||
}],
|
||||
|
||||
// Disabled rules
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# AquaNet
|
||||
|
||||
This is the codebase for the new frontend of AquaDX.
|
||||
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
|
||||
@@ -12,7 +12,8 @@ Please check out [SVELTE.md](SVELTE.md) for more details on the technical aspect
|
||||
### Running locally
|
||||
|
||||
First, you would need to install Node.js and yarn.
|
||||
Then, you would need to start your testing AquaDX server and configure the `src/libs/config.ts` to use your URL.
|
||||
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
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import {writable} from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
@@ -6,36 +6,15 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AquaNet</title>
|
||||
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/assets/icons/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/assets/icons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/assets/icons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/assets/icons/site.webmanifest" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/assets/icons/safari-pinned-tab.svg"
|
||||
color="#b3c6ff"
|
||||
/>
|
||||
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||
<meta
|
||||
name="msapplication-config"
|
||||
content="/assets/icons/browserconfig.xml"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -8,21 +8,22 @@
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"format": "prettier --write ."
|
||||
"lint": "eslint . --ext ts,tsx,svelte --max-warnings 0 --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/svelte": "^3.1.6",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"chartjs-adapter-moment": "^1.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.1.2",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"sass": "^1.70.0",
|
||||
"svelte": "^4.2.10",
|
||||
"svelte-check": "^3.6.4",
|
||||
"svelte-routing": "^2.12.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^7.0.1",
|
||||
"vite": "^5.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,19 +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"
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {Router, Route} from 'svelte-routing'
|
||||
import Home from './pages/Home.svelte'
|
||||
import MaimaiRating from './pages/MaimaiRating.svelte'
|
||||
import UserHome from './pages/UserHome.svelte'
|
||||
import Icon from '@iconify/svelte'
|
||||
import { Router, Route } from "svelte-routing";
|
||||
import Home from "./pages/Home.svelte";
|
||||
import MaimaiRating from "./pages/MaimaiRating.svelte";
|
||||
import UserHome from "./pages/UserHome.svelte";
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
export let url = ''
|
||||
export let url = "";
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
@@ -53,4 +53,4 @@
|
||||
@media (max-width: $w-mobile)
|
||||
justify-content: center
|
||||
|
||||
</style>
|
||||
</style>
|
||||
@@ -2,4 +2,4 @@ export interface TrendEntry {
|
||||
date: string
|
||||
rating: number
|
||||
plays: number
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,51 @@
|
||||
import {aqua_host, data_host} from './config'
|
||||
import type {TrendEntry} from './generalTypes'
|
||||
import type {MaimaiUserSummaryEntry} from './maimaiTypes'
|
||||
import { aqua_host, data_host } from './config'
|
||||
import type { TrendEntry } from './generalTypes'
|
||||
import type { MaimaiUserSummaryEntry } from './maimaiTypes'
|
||||
|
||||
|
||||
const multTable = [
|
||||
[100.5, 22.4, 'SSSp'],
|
||||
[100, 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'],
|
||||
[ 100.5, 22.4, 'SSSp' ],
|
||||
[ 100, 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' ]
|
||||
]
|
||||
|
||||
|
||||
export function getMult(achievement: number) {
|
||||
achievement /= 10000
|
||||
for (let i = 0; i < multTable.length; i++) {
|
||||
if (achievement >= (multTable[i][0] as number)) return multTable[i]
|
||||
}
|
||||
return [0, 0, 0]
|
||||
return [ 0, 0, 0 ]
|
||||
}
|
||||
|
||||
|
||||
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())
|
||||
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()
|
||||
)
|
||||
export async function getMaimaiAllMusic(): Promise<{ [key: string]: any }> {
|
||||
return fetch(`${data_host}/maimai/meta/00/all-music.json`).then(it => it.json())
|
||||
}
|
||||
|
||||
export async function getMaimaiApi(endpoint: string, params: any) {
|
||||
let url = new URL(`${aqua_host}/api/game/maimai2new/${endpoint}`)
|
||||
Object.keys(params).forEach((key) =>
|
||||
url.searchParams.append(key, params[key])
|
||||
)
|
||||
return await fetch(url).then((res) => res.json())
|
||||
const url = new URL(`${aqua_host}/api/game/maimai2new/${endpoint}`)
|
||||
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
|
||||
return await fetch(url).then(res => res.json())
|
||||
}
|
||||
|
||||
export async function getMaimaiTrend(userId: number): Promise<TrendEntry[]> {
|
||||
return await getMaimaiApi('trend', {userId})
|
||||
return await getMaimaiApi('trend', { userId })
|
||||
}
|
||||
|
||||
export async function getMaimaiUser(
|
||||
userId: number
|
||||
): Promise<MaimaiUserSummaryEntry> {
|
||||
return await getMaimaiApi('user-summary', {userId})
|
||||
}
|
||||
export async function getMaimaiUser(userId: number): Promise<MaimaiUserSummaryEntry> {
|
||||
return await getMaimaiApi('user-summary', { userId })
|
||||
}
|
||||
@@ -1,115 +1,118 @@
|
||||
export interface Rating {
|
||||
musicId: number
|
||||
level: number
|
||||
achievement: number
|
||||
musicId: number
|
||||
level: number
|
||||
achievement: number
|
||||
}
|
||||
|
||||
export interface ParsedRating extends Rating {
|
||||
music: MaimaiMusic
|
||||
calc: number
|
||||
rank: string
|
||||
music: MaimaiMusic,
|
||||
calc: number,
|
||||
rank: string
|
||||
}
|
||||
|
||||
export interface MaimaiMusic {
|
||||
name: string
|
||||
composer: string
|
||||
bpm: number
|
||||
ver: number
|
||||
note: {
|
||||
lv: number
|
||||
designer: string
|
||||
lv_id: number
|
||||
notes: number
|
||||
}
|
||||
name: string,
|
||||
composer: string,
|
||||
bpm: number,
|
||||
ver: number,
|
||||
note: {
|
||||
lv: number
|
||||
designer: string
|
||||
lv_id: number
|
||||
notes: number
|
||||
}
|
||||
}
|
||||
|
||||
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[]
|
||||
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
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,17 +6,13 @@ import {
|
||||
LineElement,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
TimeScale,
|
||||
type ChartOptions,
|
||||
type LineOptions,
|
||||
CategoryScale, TimeScale, type ChartOptions, type LineOptions,
|
||||
} from 'chart.js'
|
||||
import moment from 'moment/moment'
|
||||
// @ts-ignore
|
||||
// @ts-expect-error Cal-heatmap does not have proper types
|
||||
import CalHeatmap from 'cal-heatmap'
|
||||
// @ts-ignore
|
||||
// @ts-expect-error Cal-heatmap does not have proper types
|
||||
import CalTooltip from 'cal-heatmap/plugins/Tooltip'
|
||||
import type {Line} from 'svelte-chartjs'
|
||||
|
||||
export function title(t: string) {
|
||||
document.title = `AquaNet - ${t}`
|
||||
@@ -35,72 +31,62 @@ export function registerChart() {
|
||||
)
|
||||
}
|
||||
|
||||
export function renderCal(el: HTMLElement, d: {date: any; value: any}[]) {
|
||||
export function renderCal(el: HTMLElement, d: {date: any, value: any}[]) {
|
||||
const cal = new CalHeatmap()
|
||||
return cal.paint(
|
||||
{
|
||||
itemSelector: el,
|
||||
domain: {
|
||||
type: 'month',
|
||||
label: {text: 'MMM', textAlign: 'start', position: 'top'},
|
||||
},
|
||||
subDomain: {
|
||||
type: 'ghDay',
|
||||
radius: 2,
|
||||
width: 11,
|
||||
height: 11,
|
||||
gutter: 4,
|
||||
},
|
||||
range: 12,
|
||||
data: {source: d, 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',
|
||||
return cal.paint({
|
||||
itemSelector: el,
|
||||
domain: {
|
||||
type: 'month',
|
||||
label: { text: 'MMM', textAlign: 'start', position: 'top' },
|
||||
},
|
||||
[
|
||||
[
|
||||
CalTooltip,
|
||||
{
|
||||
text: (_: Date, v: number, d: any) =>
|
||||
`${v ?? 'No'} songs played on ${d.format('MMMM D, YYYY')}`,
|
||||
},
|
||||
],
|
||||
]
|
||||
)
|
||||
subDomain: {
|
||||
type: 'ghDay',
|
||||
radius: 2, width: 11, height: 11, gutter: 4
|
||||
},
|
||||
range: 12,
|
||||
data: { source: d, 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')}` }]
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
export const CHARTJS_OPT: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
// TODO: Show point on hover
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
},
|
||||
radius: 0
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
display: false,
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,8 +95,6 @@ export const CHARTJS_OPT: ChartOptions<'line'> = {
|
||||
*
|
||||
* @param obj HashMap<string, boolean>
|
||||
*/
|
||||
export function clazz(obj: {[key: string]: boolean}) {
|
||||
return Object.keys(obj)
|
||||
.filter((k) => obj[k])
|
||||
.join(' ')
|
||||
export function clazz(obj: { [key: string]: boolean }) {
|
||||
return Object.keys(obj).filter(k => obj[k]).join(' ')
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import './app.sass'
|
||||
import App from './App.svelte'
|
||||
|
||||
// @ts-ignore
|
||||
const app = new App({target: document.getElementById('app')})
|
||||
const app = new App({ target: document.getElementById('app')! })
|
||||
|
||||
export default app
|
||||
export default app
|
||||
@@ -1,83 +1,83 @@
|
||||
<main id="home" class="no-margin">
|
||||
<h1>AquaNet</h1>
|
||||
<div class="btn-group">
|
||||
<button>Login</button>
|
||||
<button>Sign Up</button>
|
||||
</div>
|
||||
|
||||
<div class="light-pollution">
|
||||
<div class="l1"></div>
|
||||
<div class="l2"></div>
|
||||
<div class="l3"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
@import "../vars"
|
||||
|
||||
#home
|
||||
color: $c-main
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 100%
|
||||
padding-left: 100px
|
||||
overflow: hidden
|
||||
|
||||
box-sizing: border-box
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
|
||||
margin-top: -$nav-height
|
||||
|
||||
> h1
|
||||
font-family: Quicksand, $font
|
||||
user-select: none
|
||||
|
||||
// Gap between text characters
|
||||
letter-spacing: 0.2em
|
||||
margin-top: 0
|
||||
opacity: 0.9
|
||||
|
||||
.btn-group
|
||||
display: flex
|
||||
gap: 8px
|
||||
|
||||
.light-pollution
|
||||
pointer-events: none
|
||||
opacity: 0.6
|
||||
|
||||
> 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($color, 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($color, 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($color, 0) 100%)
|
||||
|
||||
@media (max-width: 500px)
|
||||
align-items: center
|
||||
padding-left: 0
|
||||
</style>
|
||||
<main id="home" class="no-margin">
|
||||
<h1>AquaNet</h1>
|
||||
<div class="btn-group">
|
||||
<button>Login</button>
|
||||
<button>Sign Up</button>
|
||||
</div>
|
||||
|
||||
<div class="light-pollution">
|
||||
<div class="l1"></div>
|
||||
<div class="l2"></div>
|
||||
<div class="l3"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
@import "../vars"
|
||||
|
||||
#home
|
||||
color: $c-main
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 100%
|
||||
padding-left: 100px
|
||||
overflow: hidden
|
||||
|
||||
box-sizing: border-box
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
|
||||
margin-top: -$nav-height
|
||||
|
||||
> h1
|
||||
font-family: Quicksand, $font
|
||||
user-select: none
|
||||
|
||||
// Gap between text characters
|
||||
letter-spacing: 0.2em
|
||||
margin-top: 0
|
||||
opacity: 0.9
|
||||
|
||||
.btn-group
|
||||
display: flex
|
||||
gap: 8px
|
||||
|
||||
.light-pollution
|
||||
pointer-events: none
|
||||
opacity: 0.6
|
||||
|
||||
> 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($color, 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($color, 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($color, 0) 100%)
|
||||
|
||||
@media (max-width: 500px)
|
||||
align-items: center
|
||||
padding-left: 0
|
||||
</style>
|
||||
@@ -1,219 +1,199 @@
|
||||
<script lang="ts">
|
||||
import {data_host} from '../libs/config'
|
||||
import {getMaimaiAllMusic, getMaimai, getMult} from '../libs/maimai'
|
||||
import type {ParsedRating, Rating} from '../libs/maimaiTypes'
|
||||
|
||||
export let userId: any
|
||||
userId = +userId
|
||||
|
||||
if (!userId) console.error('No user ID provided')
|
||||
|
||||
Promise.all([
|
||||
getMaimai('GetUserRatingApi', {userId}),
|
||||
getMaimaiAllMusic(),
|
||||
]).then(([rating, music]) => {
|
||||
data = rating
|
||||
musicInfo = music
|
||||
|
||||
if (!data || !musicInfo) {
|
||||
console.error('Failed to fetch data')
|
||||
return
|
||||
}
|
||||
|
||||
parsedRatings = {
|
||||
old: parseRating(data.userRating.ratingList),
|
||||
new: parseRating(data.userRating.newRatingList),
|
||||
}
|
||||
})
|
||||
|
||||
function parseRating(arr: Rating[]) {
|
||||
return arr
|
||||
.map((x) => {
|
||||
const music = musicInfo[x.musicId]
|
||||
|
||||
if (!music) {
|
||||
console.error(`Music not found: ${x.musicId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
music.note = music.notes[x.level]
|
||||
const mult = getMult(x.achievement)
|
||||
return {
|
||||
...x,
|
||||
music: music,
|
||||
calc: (mult[1] as number) * music.note.lv,
|
||||
rank: mult[2],
|
||||
}
|
||||
})
|
||||
.filter((x) => x != null) as ParsedRating[]
|
||||
}
|
||||
|
||||
let parsedRatings: {
|
||||
old: ParsedRating[]
|
||||
new: ParsedRating[]
|
||||
} | null = null
|
||||
|
||||
let data: {
|
||||
userRating: {
|
||||
rating: number
|
||||
ratingList: Rating[]
|
||||
newRatingList: Rating[]
|
||||
}
|
||||
} | null = null
|
||||
|
||||
let musicInfo: any = null
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<!-- Display all parsed ratings -->
|
||||
{#if parsedRatings}
|
||||
{#each [{title: 'Old', data: parsedRatings.old}, {title: 'New', data: parsedRatings.new}] as section}
|
||||
<h2>{section.title}</h2>
|
||||
<div class="rating-cards">
|
||||
{#each section.data as rating}
|
||||
<div class="level-{rating.level}">
|
||||
<img
|
||||
class="cover"
|
||||
src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId
|
||||
.toString()
|
||||
.padStart(6, '0')
|
||||
.substring(2)}.png`}
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div class="detail">
|
||||
<span class="name">{rating.music.name}</span>
|
||||
<span class="rating">
|
||||
<span>{(rating.achievement / 10000).toFixed(2)}%</span>
|
||||
<img
|
||||
class="rank"
|
||||
src={`${data_host}/maimai/sprites/rankimage/UI_GAM_Rank_${rating.rank}.png`}
|
||||
alt=""
|
||||
/>
|
||||
</span>
|
||||
<span>{rating.calc.toFixed(1)}</span>
|
||||
</div>
|
||||
<img
|
||||
class="ver"
|
||||
src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver
|
||||
.toString()
|
||||
.substring(0, 3)}.png`}
|
||||
alt=""
|
||||
/>
|
||||
<div class="lv">{rating.music.note.lv}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
.rating-cards
|
||||
display: grid
|
||||
gap: 2rem
|
||||
width: 100%
|
||||
|
||||
// Fill as many columns as possible
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
||||
|
||||
// Center the cards
|
||||
justify-items: center
|
||||
align-items: center
|
||||
|
||||
// Style each card
|
||||
> div
|
||||
$border-radius: 20px
|
||||
width: 200px
|
||||
height: 200px
|
||||
border-radius: $border-radius
|
||||
|
||||
display: flex
|
||||
position: relative
|
||||
|
||||
// Difficulty border
|
||||
border: 5px solid var(--lv-color, #60aaff)
|
||||
&.level-1
|
||||
--lv-color: #aaff60
|
||||
&.level-2
|
||||
--lv-color: #f25353
|
||||
&.level-3
|
||||
--lv-color: #e881ff
|
||||
|
||||
img
|
||||
object-fit: cover
|
||||
pointer-events: none
|
||||
|
||||
img.cover
|
||||
width: 100%
|
||||
height: 100%
|
||||
border-radius: calc($border-radius - 3px)
|
||||
|
||||
img.ver
|
||||
position: absolute
|
||||
top: -20px
|
||||
left: -30px
|
||||
height: 50px
|
||||
|
||||
// Information
|
||||
.detail
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
padding: 10px
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
|
||||
|
||||
// Blur
|
||||
backdrop-filter: blur(3px)
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
text-align: left
|
||||
|
||||
> span
|
||||
// Disable text wrapping, max 2 lines
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.name
|
||||
font-size: 1.2em
|
||||
font-weight: bold
|
||||
|
||||
.rating
|
||||
display: flex
|
||||
img
|
||||
height: 1.5em
|
||||
|
||||
.lv
|
||||
position: absolute
|
||||
bottom: 0
|
||||
right: 0
|
||||
padding: 5px 10px
|
||||
background: var(--lv-color)
|
||||
// Top left border radius
|
||||
border-radius: 10px 0
|
||||
|
||||
font-size: 1.3em
|
||||
|
||||
&:before
|
||||
content: "Lv"
|
||||
font-size: 0.8em
|
||||
|
||||
// Mobile
|
||||
@media (max-width: 500px)
|
||||
margin-left: -1rem
|
||||
margin-right: -1rem
|
||||
width: calc(100% + 2rem)
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr))
|
||||
font-size: 0.8em
|
||||
> div
|
||||
width: 150px
|
||||
height: 150px
|
||||
|
||||
img.ver
|
||||
height: 45px
|
||||
left: -20px
|
||||
</style>
|
||||
<script lang="ts">
|
||||
import {data_host} from "../libs/config";
|
||||
import {getMaimaiAllMusic, getMaimai, getMult} from "../libs/maimai";
|
||||
import type {ParsedRating, Rating} from "../libs/maimaiTypes";
|
||||
|
||||
export let userId: any
|
||||
userId = +userId
|
||||
|
||||
if (!userId) console.error("No user ID provided")
|
||||
|
||||
Promise.all([
|
||||
getMaimai("GetUserRatingApi", {userId}),
|
||||
getMaimaiAllMusic()
|
||||
]).then(([rating, music]) => {
|
||||
data = rating
|
||||
musicInfo = music
|
||||
|
||||
if (!data || !musicInfo) {
|
||||
console.error("Failed to fetch data")
|
||||
return
|
||||
}
|
||||
|
||||
parsedRatings = {
|
||||
old: parseRating(data.userRating.ratingList),
|
||||
new: parseRating(data.userRating.newRatingList)
|
||||
}
|
||||
})
|
||||
|
||||
function parseRating(arr: Rating[]) {
|
||||
return arr.map(x => {
|
||||
const music = musicInfo[x.musicId]
|
||||
|
||||
if (!music) {
|
||||
console.error(`Music not found: ${x.musicId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
music.note = music.notes[x.level]
|
||||
const mult = getMult(x.achievement)
|
||||
return {
|
||||
...x,
|
||||
music: music,
|
||||
calc: (mult[1] as number) * music.note.lv,
|
||||
rank: mult[2]
|
||||
}
|
||||
}).filter(x => x != null) as ParsedRating[]
|
||||
}
|
||||
|
||||
let parsedRatings: {
|
||||
old: ParsedRating[],
|
||||
new: ParsedRating[]
|
||||
} | null = null
|
||||
|
||||
let data: {
|
||||
userRating: {
|
||||
rating: number,
|
||||
ratingList: Rating[],
|
||||
newRatingList: Rating[]
|
||||
}
|
||||
} | null = null
|
||||
|
||||
let musicInfo: any = null
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<!-- Display all parsed ratings -->
|
||||
{#if parsedRatings}
|
||||
{#each [{title: "Old", data: parsedRatings.old}, {title: "New", data: parsedRatings.new}] as section}
|
||||
<h2>{section.title}</h2>
|
||||
<div class="rating-cards">
|
||||
{#each section.data as rating}
|
||||
<div class="level-{rating.level}">
|
||||
<img class="cover"
|
||||
src={`${data_host}/maimai/assetbundle/jacket_s/00${rating.musicId.toString().padStart(6, '0').substring(2)}.png`}
|
||||
alt="">
|
||||
|
||||
<div class="detail">
|
||||
<span class="name">{rating.music.name}</span>
|
||||
<span class="rating">
|
||||
<span>{(rating.achievement / 10000).toFixed(2)}%</span>
|
||||
<img class="rank" src={`${data_host}/maimai/sprites/rankimage/UI_GAM_Rank_${rating.rank}.png`} alt="">
|
||||
</span>
|
||||
<span>{rating.calc.toFixed(1)}</span>
|
||||
</div>
|
||||
<img class="ver"
|
||||
src={`${data_host}/maimai/sprites/tab/title/UI_CMN_TabTitle_MaimaiTitle_Ver${rating.music.ver.toString().substring(0, 3)}.png`}
|
||||
alt="">
|
||||
<div class="lv">{rating.music.note.lv}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
.rating-cards
|
||||
display: grid
|
||||
gap: 2rem
|
||||
width: 100%
|
||||
|
||||
// Fill as many columns as possible
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
|
||||
|
||||
// Center the cards
|
||||
justify-items: center
|
||||
align-items: center
|
||||
|
||||
// Style each card
|
||||
> div
|
||||
$border-radius: 20px
|
||||
width: 200px
|
||||
height: 200px
|
||||
border-radius: $border-radius
|
||||
|
||||
display: flex
|
||||
position: relative
|
||||
|
||||
// Difficulty border
|
||||
border: 5px solid var(--lv-color)
|
||||
|
||||
img
|
||||
object-fit: cover
|
||||
pointer-events: none
|
||||
|
||||
img.cover
|
||||
width: 100%
|
||||
height: 100%
|
||||
border-radius: calc($border-radius - 3px)
|
||||
|
||||
img.ver
|
||||
position: absolute
|
||||
top: -20px
|
||||
left: -30px
|
||||
height: 50px
|
||||
|
||||
// Information
|
||||
.detail
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
padding: 10px
|
||||
background: rgba(0, 0, 0, 0.5)
|
||||
border-radius: 0 0 calc($border-radius - 3px) calc($border-radius - 3px)
|
||||
|
||||
// Blur
|
||||
backdrop-filter: blur(3px)
|
||||
|
||||
display: flex
|
||||
flex-direction: column
|
||||
text-align: left
|
||||
|
||||
> span
|
||||
// Disable text wrapping, max 2 lines
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.name
|
||||
font-size: 1.2em
|
||||
font-weight: bold
|
||||
|
||||
.rating
|
||||
display: flex
|
||||
|
||||
img
|
||||
height: 1.5em
|
||||
|
||||
.lv
|
||||
position: absolute
|
||||
bottom: 0
|
||||
right: 0
|
||||
padding: 5px 10px
|
||||
background: var(--lv-color)
|
||||
// Top left border radius
|
||||
border-radius: 10px 0
|
||||
|
||||
font-size: 1.3em
|
||||
|
||||
&:before
|
||||
content: "Lv"
|
||||
font-size: 0.8em
|
||||
|
||||
// Mobile
|
||||
@media (max-width: 500px)
|
||||
margin-left: -1rem
|
||||
margin-right: -1rem
|
||||
width: calc(100% + 2rem)
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr))
|
||||
font-size: 0.8em
|
||||
> div
|
||||
width: 150px
|
||||
height: 150px
|
||||
|
||||
img.ver
|
||||
height: 45px
|
||||
left: -20px
|
||||
</style>
|
||||
@@ -1,426 +1,398 @@
|
||||
<script lang="ts">
|
||||
import {CHARTJS_OPT, clazz, registerChart, renderCal, title} from '../libs/ui'
|
||||
import {
|
||||
getMaimaiAllMusic,
|
||||
getMaimaiTrend,
|
||||
getMaimaiUser,
|
||||
getMult,
|
||||
} from '../libs/maimai'
|
||||
import type {
|
||||
MaimaiMusic,
|
||||
MaimaiUserPlaylog,
|
||||
MaimaiUserSummaryEntry,
|
||||
} from '../libs/maimaiTypes'
|
||||
import type {TrendEntry} from '../libs/generalTypes'
|
||||
import {data_host} from '../libs/config'
|
||||
import 'cal-heatmap/cal-heatmap.css'
|
||||
import {Line} from 'svelte-chartjs'
|
||||
import moment from 'moment'
|
||||
import 'chartjs-adapter-moment'
|
||||
|
||||
registerChart()
|
||||
|
||||
export let userId: any
|
||||
userId = +userId
|
||||
let calElement: HTMLElement
|
||||
|
||||
title(`User ${userId}`)
|
||||
|
||||
interface MusicAndPlay extends MaimaiMusic, MaimaiUserPlaylog {}
|
||||
|
||||
let d: {
|
||||
user: MaimaiUserSummaryEntry
|
||||
trend: TrendEntry[]
|
||||
recent: MusicAndPlay[]
|
||||
} | null = null
|
||||
|
||||
Promise.all([
|
||||
getMaimaiUser(userId),
|
||||
getMaimaiTrend(userId),
|
||||
getMaimaiAllMusic(),
|
||||
]).then(([user, trend, music]) => {
|
||||
console.log(user)
|
||||
console.log(trend)
|
||||
console.log(music)
|
||||
|
||||
d = {
|
||||
user,
|
||||
trend,
|
||||
recent: user.recent.map((it) => {
|
||||
return {...music[it.musicId], ...it}
|
||||
}),
|
||||
}
|
||||
localStorage.setItem('tmp-user-details', JSON.stringify(d))
|
||||
renderCal(
|
||||
calElement,
|
||||
trend.map((it) => {
|
||||
return {date: it.date, value: it.plays}
|
||||
})
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<main id="user-home">
|
||||
{#if d !== null}
|
||||
<div class="user-pfp">
|
||||
<img
|
||||
src={`${data_host}/maimai/assetbundle/icon/${d.user.iconId.toString().padStart(6, '0')}.png`}
|
||||
alt=""
|
||||
class="pfp"
|
||||
/>
|
||||
<h2>{d.user.name}</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Rating Statistics</h2>
|
||||
<div class="scoring-info">
|
||||
<div class="chart">
|
||||
<div class="info-top">
|
||||
<div class="rating">
|
||||
<span>DX Rating</span>
|
||||
<span>{d.user.rating.toLocaleString()}</span>
|
||||
</div>
|
||||
|
||||
<div class="rank">
|
||||
<span>Server Rank</span>
|
||||
<span>#{d.user.serverRank.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">
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-bottom">
|
||||
{#each d.user.ranks as r}
|
||||
<div>
|
||||
<span>{r.name}</span>
|
||||
<span>{r.count}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="other-info">
|
||||
<div class="accuracy">
|
||||
<span>Accuracy</span>
|
||||
<span>{(d.user.accuracy / 10000).toFixed(2)}%</span>
|
||||
</div>
|
||||
|
||||
<div class="max-combo">
|
||||
<span>Max Combo</span>
|
||||
<span>{d.user.maxCombo}</span>
|
||||
</div>
|
||||
|
||||
<div class="full-combo">
|
||||
<span>Full Combo</span>
|
||||
<span>{d.user.fullCombo}</span>
|
||||
</div>
|
||||
|
||||
<div class="all-perfect">
|
||||
<span>All Perfect</span>
|
||||
<span>{d.user.allPerfect}</span>
|
||||
</div>
|
||||
|
||||
<div class="total-dx-score">
|
||||
<span>DX Score</span>
|
||||
<span>{d.user.totalDxScore.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Play Activity</h2>
|
||||
<div class="activity-info">
|
||||
<div id="cal-heatmap" bind:this={calElement} />
|
||||
|
||||
<div class="info-bottom">
|
||||
<div class="plays">
|
||||
<span>Plays</span>
|
||||
<span>{d.user.plays}</span>
|
||||
</div>
|
||||
|
||||
<div class="time">
|
||||
<span>Play Time</span>
|
||||
<span>{(d.user.totalPlayTime / 60 / 60).toFixed(1)} hr</span>
|
||||
</div>
|
||||
|
||||
<div class="first-play">
|
||||
<span>First Seen</span>
|
||||
<span>{moment(d.user.joined).format('YYYY-MM-DD')}</span>
|
||||
</div>
|
||||
|
||||
<div class="last-play">
|
||||
<span>Last Seen</span>
|
||||
<span>{moment(d.user.lastSeen).format('YYYY-MM-DD')}</span>
|
||||
</div>
|
||||
|
||||
<div class="last-version">
|
||||
<span>Last Version</span>
|
||||
<span>{d.user.lastVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent">
|
||||
<h2>Recent Scores</h2>
|
||||
<div class="scores">
|
||||
{#each d.recent as r, i}
|
||||
<div class={clazz({alt: i % 2 === 0})}>
|
||||
<img
|
||||
src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`}
|
||||
alt=""
|
||||
/>
|
||||
<div class="info">
|
||||
<span class="name">{r.name}</span>
|
||||
<div>
|
||||
<span class={'rank-' + ('' + getMult(r.achievement)[2])[0]}>
|
||||
<span class="rank-text"
|
||||
>{('' + getMult(r.achievement)[2]).replace('p', '+')}</span
|
||||
>
|
||||
<span class="rank-num"
|
||||
>{(r.achievement / 10000).toFixed(2)}%</span
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class={'dx-change ' +
|
||||
clazz({
|
||||
increased: r.afterDeluxRating - r.beforeDeluxRating > 0,
|
||||
})}
|
||||
>
|
||||
{r.afterDeluxRating - r.beforeDeluxRating}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
@import "../vars"
|
||||
|
||||
$gap: 20px
|
||||
|
||||
#user-home
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: $gap
|
||||
margin: 100px auto 0
|
||||
padding: 0 32px 32px
|
||||
min-height: 100%
|
||||
max-width: $w-max
|
||||
|
||||
background-color: rgba(black, 0.2)
|
||||
border-radius: 16px 16px 0 0
|
||||
|
||||
@media (max-width: #{$w-max + (64px) * 2})
|
||||
margin: 100px 32px 0
|
||||
padding: 0 32px 16px
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
margin: 100px 0 0
|
||||
padding: 0 32px 16px
|
||||
|
||||
.user-pfp
|
||||
display: flex
|
||||
align-items: flex-end
|
||||
gap: $gap
|
||||
margin-top: -40px
|
||||
|
||||
h2
|
||||
font-size: 2rem
|
||||
margin: 0
|
||||
|
||||
.pfp
|
||||
width: 100px
|
||||
height: 100px
|
||||
border-radius: 5px
|
||||
object-fit: cover
|
||||
|
||||
.info-bottom, .info-top, .other-info
|
||||
display: flex
|
||||
gap: $gap
|
||||
|
||||
> div
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
> span:first-child
|
||||
font-weight: bold
|
||||
font-size: 0.8rem
|
||||
|
||||
// character spacing
|
||||
letter-spacing: 0.1em
|
||||
color: $c-main
|
||||
|
||||
.info-top > div > span:last-child
|
||||
font-size: 1.5rem
|
||||
|
||||
.scoring-info
|
||||
display: flex
|
||||
gap: $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
|
||||
|
||||
@media (max-width: $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: $gap
|
||||
|
||||
#cal-heatmap
|
||||
overflow-x: auto
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
#cal-heatmap
|
||||
width: 100%
|
||||
|
||||
.info-bottom
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
> div
|
||||
flex-direction: row
|
||||
justify-content: space-between
|
||||
|
||||
// Recent Scores section
|
||||
.recent
|
||||
.scores
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-wrap: wrap
|
||||
gap: $gap
|
||||
|
||||
> div.alt
|
||||
background-color: rgba(white, 0.03)
|
||||
border-radius: 10px
|
||||
|
||||
// Image and song info
|
||||
> div
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: $gap
|
||||
padding-right: 16px
|
||||
max-width: 100%
|
||||
box-sizing: border-box
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: 10px
|
||||
object-fit: cover
|
||||
|
||||
// Song info and score
|
||||
> div
|
||||
flex: 1
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
|
||||
// Limit song name to one line
|
||||
overflow: hidden
|
||||
.name
|
||||
overflow: hidden
|
||||
overflow-wrap: anywhere
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
span
|
||||
text-align: left
|
||||
|
||||
.rank-S
|
||||
// Gold green gradient on text
|
||||
background: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
|
||||
-webkit-background-clip: text
|
||||
color: transparent
|
||||
|
||||
.rank-A
|
||||
color: #ff8a8a
|
||||
|
||||
.rank-B
|
||||
color: #6ba6ff
|
||||
|
||||
span
|
||||
display: inline-block
|
||||
text-align: right
|
||||
|
||||
// Vertical table-like alignment
|
||||
span.rank-text
|
||||
min-width: 30px
|
||||
span.rank-num
|
||||
min-width: 60px
|
||||
span.dx-change
|
||||
min-width: 30px
|
||||
|
||||
span.increased
|
||||
&:before
|
||||
content: "+"
|
||||
color: $c-good
|
||||
</style>
|
||||
<script lang="ts">
|
||||
import {CHARTJS_OPT, clazz, registerChart, renderCal, title} from "../libs/ui";
|
||||
import {getMaimaiAllMusic, getMaimaiTrend, getMaimaiUser, getMult} from "../libs/maimai";
|
||||
import type {MaimaiMusic, MaimaiUserPlaylog, MaimaiUserSummaryEntry} from "../libs/maimaiTypes";
|
||||
import type {TrendEntry} from "../libs/generalTypes";
|
||||
import {data_host} from "../libs/config";
|
||||
import 'cal-heatmap/cal-heatmap.css';
|
||||
import { Line } from 'svelte-chartjs';
|
||||
import moment from "moment";
|
||||
import 'chartjs-adapter-moment';
|
||||
|
||||
registerChart()
|
||||
|
||||
export let userId: any;
|
||||
userId = +userId
|
||||
let calElement: HTMLElement
|
||||
|
||||
title(`User ${userId}`)
|
||||
|
||||
interface MusicAndPlay extends MaimaiMusic, MaimaiUserPlaylog {}
|
||||
|
||||
let d: {
|
||||
user: MaimaiUserSummaryEntry,
|
||||
trend: TrendEntry[]
|
||||
recent: MusicAndPlay[]
|
||||
} | null = null
|
||||
|
||||
Promise.all([
|
||||
getMaimaiUser(userId),
|
||||
getMaimaiTrend(userId),
|
||||
getMaimaiAllMusic()
|
||||
]).then(([user, trend, music]) => {
|
||||
console.log(user)
|
||||
console.log(trend)
|
||||
console.log(music)
|
||||
|
||||
// Sort recent by date
|
||||
user.recent.sort((a, b) => b.userPlayDate < a.userPlayDate ? -1 : 1)
|
||||
|
||||
d = {user, trend, recent: user.recent.map(it => {return {...music[it.musicId], ...it}})}
|
||||
localStorage.setItem("tmp-user-details", JSON.stringify(d))
|
||||
renderCal(calElement, trend.map(it => {return {date: it.date, value: it.plays}}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<main id="user-home">
|
||||
{#if d !== null}
|
||||
<div class="user-pfp">
|
||||
<img src={`${data_host}/maimai/assetbundle/icon/${d.user.iconId.toString().padStart(6, "0")}.png`} alt="" class="pfp">
|
||||
<h2>{d.user.name}</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Rating Statistics</h2>
|
||||
<div class="scoring-info">
|
||||
<div class="chart">
|
||||
<div class="info-top">
|
||||
<div class="rating">
|
||||
<span>DX Rating</span>
|
||||
<span>{d.user.rating.toLocaleString()}</span>
|
||||
</div>
|
||||
|
||||
<div class="rank">
|
||||
<span>Server Rank</span>
|
||||
<span>#{d.user.serverRank.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">
|
||||
<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} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-bottom">
|
||||
{#each d.user.ranks as r}
|
||||
<div>
|
||||
<span>{r.name}</span>
|
||||
<span>{r.count}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="other-info">
|
||||
<div class="accuracy">
|
||||
<span>Accuracy</span>
|
||||
<span>{(d.user.accuracy / 10000).toFixed(2)}%</span>
|
||||
</div>
|
||||
|
||||
<div class="max-combo">
|
||||
<span>Max Combo</span>
|
||||
<span>{d.user.maxCombo}</span>
|
||||
</div>
|
||||
|
||||
<div class="full-combo">
|
||||
<span>Full Combo</span>
|
||||
<span>{d.user.fullCombo}</span>
|
||||
</div>
|
||||
|
||||
<div class="all-perfect">
|
||||
<span>All Perfect</span>
|
||||
<span>{d.user.allPerfect}</span>
|
||||
</div>
|
||||
|
||||
<div class="total-dx-score">
|
||||
<span>DX Score</span>
|
||||
<span>{d.user.totalDxScore.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Play Activity</h2>
|
||||
<div class="activity-info">
|
||||
<div id="cal-heatmap" bind:this={calElement} />
|
||||
|
||||
<div class="info-bottom">
|
||||
<div class="plays">
|
||||
<span>Plays</span>
|
||||
<span>{d.user.plays}</span>
|
||||
</div>
|
||||
|
||||
<div class="time">
|
||||
<span>Play Time</span>
|
||||
<span>{(d.user.totalPlayTime / 60 / 60).toFixed(1)} hr</span>
|
||||
</div>
|
||||
|
||||
<div class="first-play">
|
||||
<span>First Seen</span>
|
||||
<span>{moment(d.user.joined).format("YYYY-MM-DD")}</span>
|
||||
</div>
|
||||
|
||||
<div class="last-play">
|
||||
<span>Last Seen</span>
|
||||
<span>{moment(d.user.lastSeen).format("YYYY-MM-DD")}</span>
|
||||
</div>
|
||||
|
||||
<div class="last-version">
|
||||
<span>Last Version</span>
|
||||
<span>{d.user.lastVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent">
|
||||
<h2>Recent Scores</h2>
|
||||
<div class="scores">
|
||||
{#each d.recent as r, i}
|
||||
<div class={clazz({alt: i % 2 === 0})}>
|
||||
<img src={`${data_host}/maimai/assetbundle/jacket_s/00${r.musicId.toString().padStart(6, '0').substring(2)}.png`} alt="">
|
||||
<div class="info">
|
||||
<div>
|
||||
<span class="name">{r.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class={`lv level-${r.level}`}>{r.notes[r.level].lv}</span>
|
||||
<span class={"rank-" + ("" + getMult(r.achievement)[2])[0]}>
|
||||
<span class="rank-text">{("" + getMult(r.achievement)[2]).replace("p", "+")}</span>
|
||||
<span class="rank-num">{(r.achievement / 10000).toFixed(2)}%</span>
|
||||
</span>
|
||||
<span class={"dx-change " + clazz({increased: r.afterDeluxRating - r.beforeDeluxRating > 0})}>
|
||||
{r.afterDeluxRating - r.beforeDeluxRating}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
@import "../vars"
|
||||
|
||||
$gap: 20px
|
||||
|
||||
#user-home
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: $gap
|
||||
margin: 100px auto 0
|
||||
padding: 0 32px 32px
|
||||
min-height: 100%
|
||||
max-width: $w-max
|
||||
|
||||
background-color: rgba(black, 0.2)
|
||||
border-radius: 16px 16px 0 0
|
||||
|
||||
@media (max-width: #{$w-max + (64px) * 2})
|
||||
margin: 100px 32px 0
|
||||
padding: 0 32px 16px
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
margin: 100px 0 0
|
||||
padding: 0 32px 16px
|
||||
|
||||
.user-pfp
|
||||
display: flex
|
||||
align-items: flex-end
|
||||
gap: $gap
|
||||
margin-top: -40px
|
||||
|
||||
h2
|
||||
font-size: 2rem
|
||||
margin: 0
|
||||
|
||||
.pfp
|
||||
width: 100px
|
||||
height: 100px
|
||||
border-radius: 5px
|
||||
object-fit: cover
|
||||
|
||||
.info-bottom, .info-top, .other-info
|
||||
display: flex
|
||||
gap: $gap
|
||||
|
||||
> div
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
> span:first-child
|
||||
font-weight: bold
|
||||
font-size: 0.8rem
|
||||
|
||||
// character spacing
|
||||
letter-spacing: 0.1em
|
||||
color: $c-main
|
||||
|
||||
.info-top > div > span:last-child
|
||||
font-size: 1.5rem
|
||||
|
||||
.scoring-info
|
||||
display: flex
|
||||
gap: $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
|
||||
|
||||
@media (max-width: $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: $gap
|
||||
|
||||
#cal-heatmap
|
||||
overflow-x: auto
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
#cal-heatmap
|
||||
width: 100%
|
||||
|
||||
.info-bottom
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
> div
|
||||
flex-direction: row
|
||||
justify-content: space-between
|
||||
|
||||
// Recent Scores section
|
||||
.recent
|
||||
.scores
|
||||
display: flex
|
||||
flex-direction: column
|
||||
flex-wrap: wrap
|
||||
gap: $gap
|
||||
|
||||
> div.alt
|
||||
background-color: rgba(white, 0.03)
|
||||
border-radius: 10px
|
||||
|
||||
// Image and song info
|
||||
> div
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: $gap
|
||||
padding-right: 16px
|
||||
max-width: 100%
|
||||
box-sizing: border-box
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: 10px
|
||||
object-fit: cover
|
||||
|
||||
// Song info and score
|
||||
> div
|
||||
flex: 1
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
|
||||
// Limit song name to one line
|
||||
overflow: hidden
|
||||
.name
|
||||
overflow: hidden
|
||||
overflow-wrap: anywhere
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
|
||||
@media (max-width: $w-mobile)
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
.rank-text
|
||||
text-align: left
|
||||
|
||||
.rank-S
|
||||
// Gold green gradient on text
|
||||
background: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
|
||||
-webkit-background-clip: text
|
||||
color: transparent
|
||||
|
||||
.rank-A
|
||||
color: #ff8a8a
|
||||
|
||||
.rank-B
|
||||
color: #6ba6ff
|
||||
|
||||
.lv
|
||||
background: var(--lv-color)
|
||||
padding: 0 6px
|
||||
border-radius: 10px
|
||||
opacity: 0.8
|
||||
margin-right: 10px
|
||||
|
||||
span
|
||||
display: inline-block
|
||||
text-align: right
|
||||
|
||||
// Vertical table-like alignment
|
||||
span.rank-text
|
||||
min-width: 30px
|
||||
span.rank-num
|
||||
min-width: 60px
|
||||
span.dx-change
|
||||
min-width: 30px
|
||||
|
||||
span.increased
|
||||
&:before
|
||||
content: "+"
|
||||
color: $c-good
|
||||
</style>
|
||||
@@ -6,4 +6,13 @@ $c-bg: #242424
|
||||
|
||||
$nav-height: 4rem
|
||||
$w-mobile: 560px
|
||||
$w-max: 900px
|
||||
$w-max: 900px
|
||||
|
||||
.level-0
|
||||
--lv-color: #6ED43E
|
||||
.level-1
|
||||
--lv-color: #F7B807
|
||||
.level-2
|
||||
--lv-color: #FF828D
|
||||
.level-3
|
||||
--lv-color: #A051DC
|
||||
@@ -1,4 +1,4 @@
|
||||
import {vitePreprocess} from '@sveltejs/vite-plugin-svelte'
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{"path": "./tsconfig.node.json"}]
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import {svelte} from '@sveltejs/vite-plugin-svelte'
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
30
README.md
30
README.md
@@ -1,6 +1,6 @@
|
||||
# AquaDX
|
||||
|
||||
Multipurpose game server powered by Spring Boot, for ALL.Net based games
|
||||
Multipurpose game server powered by Spring Boot, for ALL.Net-based games
|
||||
|
||||
This is an attempt to rebuild the [original Aqua server](https://dev.s-ul.net/NeumPhis/aqua)
|
||||
|
||||
@@ -26,9 +26,9 @@ Check out these docs for more information.
|
||||
* [Frequently asked questions](docs/frequently_asked_questions.md)
|
||||
|
||||
### Notes
|
||||
* Some game may require additional patches and these will not provided in this project and repository. You already found this, so you know where to find related resources too.
|
||||
* This repository may contain untested, experimental implementation for few games which I can't test properly. If you couldn't find your wanted game in the above list, do not expect support.
|
||||
* This server also provides a simple API for viewing play records and edit settings for some games.
|
||||
* Some games may require additional patches and these will not provided in this project and repository. You already found this, so you know where to find related resources too.
|
||||
* This repository may contain untested, experimental implementations for a few games which I can't test properly. If you couldn't find your wanted game in the above list, do not expect support.
|
||||
* This server also provides a simple API for viewing play records and editing settings for some games.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -37,28 +37,28 @@ Check out these docs for more information.
|
||||
3. Extract the zip file to a folder.
|
||||
4. Run `java -jar aqua.jar` in the folder.
|
||||
|
||||
By default, Aqua will use sqlite and save user data in data/db.sqlite.
|
||||
By default, Aqua will use SQLite and save user data in `data/db.sqlite`.
|
||||
|
||||
If you want to use optional databases, edit configuration file then it will auto create the table and import some initial data.
|
||||
If you want to use optional databases, please edit the configuration file then it will auto-create the table and import some initial data.
|
||||
|
||||
### Configuration
|
||||
Configuration is saved in `config/application.properties`, spring loads this file automatically.
|
||||
|
||||
* The host and port of game title servers can be overritten in `allnet.server.host` and `allnet.server.port`. By default it will send the same host and port the client used the request this information.
|
||||
This will be sent to the game at booting and being used by following request.
|
||||
* You can switch to MariaDB (or MySQL) database by commenting the Sqlite part.
|
||||
* For some game, you might need to change some game specific config entries.
|
||||
* The host and port of game title servers can be overwritten in `allnet.server.host` and `allnet.server.port`. By default it will send the same host and port the client used the request this information.
|
||||
This will be sent to the game at booting and being used by the following request.
|
||||
* You can switch to the MariaDB (or MySQL) database by commenting the Sqlite part.
|
||||
* For some games, you might need to change some game-specific config entries.
|
||||
|
||||
### Building
|
||||
You need to install JDK on your system. However, you don't need to care about Gradle, as wrapper script is included.
|
||||
You need to install JDK on your system. However, you don't need to install Gradle separately, as the `gradlew` wrapper script is included.
|
||||
```
|
||||
gradlew clean build
|
||||
```
|
||||
The `build/libs` folder will contain an jar file.
|
||||
The `build/libs` folder will contain a jar file.
|
||||
|
||||
### Credit
|
||||
* **samnyan**: The creator and developer of the original Aqua server
|
||||
* **Akasaka Ryuunosuke** : providing all the DIVA protocol information
|
||||
* Dom Eori : Developer of forked Aqua server, from v0.0.17 and up
|
||||
* **Akasaka Ryuunosuke**: providing all the DIVA protocol information
|
||||
* Dom Eori: Developer of forked Aqua server, from v0.0.17 and up
|
||||
* All devs who contribute to the [MiniMe server](https://dev.s-ul.net/djhackers/minime)
|
||||
* All contributors by merge request, issues and other channels
|
||||
* All contributors by merge requests, issues and other channels
|
||||
|
||||
@@ -36,9 +36,9 @@ dependencies {
|
||||
testImplementation("org.springframework.security:spring-security-test")
|
||||
|
||||
// Database
|
||||
runtimeOnly("com.mysql:mysql-connector-j:8.0.33")
|
||||
runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.1.3")
|
||||
runtimeOnly("org.xerial:sqlite-jdbc:3.41.2.1")
|
||||
runtimeOnly("com.mysql:mysql-connector-j:8.3.0")
|
||||
runtimeOnly("org.mariadb.jdbc:mariadb-java-client:3.3.2")
|
||||
runtimeOnly("org.xerial:sqlite-jdbc:3.45.1.0")
|
||||
implementation("com.github.gwenn:sqlite-dialect:0.1.4")
|
||||
|
||||
// JSR305 for nullable
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
184
gradlew.bat
vendored
184
gradlew.bat
vendored
@@ -1,92 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
||||
@@ -153,4 +153,6 @@ public class UserDetail implements Serializable {
|
||||
@Transient
|
||||
private int cmLastEmoneyCredit = 69;
|
||||
private int mapStock;
|
||||
private int currentPlayCount;
|
||||
private int renameCredit;
|
||||
}
|
||||
|
||||
@@ -255,4 +255,9 @@ public class UserPlaylog implements Serializable {
|
||||
|
||||
private int extNum2;
|
||||
|
||||
private int extNum4;
|
||||
|
||||
@JsonProperty("extBool1")
|
||||
private boolean extBool1;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
17
src/main/resources/db/migration/mariadb/V255_2__fix_null.sql
Normal file
17
src/main/resources/db/migration/mariadb/V255_2__fix_null.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- maimai2_user_playlog
|
||||
-- Set ext_bool1 as NOT NULL and give it a default value (e.g., FALSE)
|
||||
UPDATE maimai2_user_playlog SET ext_bool1 = FALSE WHERE ext_bool1 IS NULL;
|
||||
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_bool1 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Set ext_num4 as NOT NULL (assuming it already has a default value of 0)
|
||||
UPDATE maimai2_user_playlog SET ext_num4 = 0 WHERE ext_num4 IS NULL;
|
||||
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_num4 INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- maimai2_user_detail
|
||||
-- Add default value for current_play_count and set it as NOT NULL
|
||||
UPDATE maimai2_user_detail SET current_play_count = 0 WHERE current_play_count IS NULL;
|
||||
ALTER TABLE maimai2_user_detail MODIFY COLUMN current_play_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Add default value for rename_credit and set it as NOT NULL
|
||||
UPDATE maimai2_user_detail SET rename_credit = 0 WHERE rename_credit IS NULL;
|
||||
ALTER TABLE maimai2_user_detail MODIFY COLUMN rename_credit INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- maimai2_user_playlog
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||
|
||||
-- maimai2_user_detail
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||
@@ -0,0 +1,14 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
17
src/main/resources/db/migration/mysql/V255_2__fix_null.sql
Normal file
17
src/main/resources/db/migration/mysql/V255_2__fix_null.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- maimai2_user_playlog
|
||||
-- Set ext_bool1 as NOT NULL and give it a default value (e.g., FALSE)
|
||||
UPDATE maimai2_user_playlog SET ext_bool1 = FALSE WHERE ext_bool1 IS NULL;
|
||||
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_bool1 BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Set ext_num4 as NOT NULL (assuming it already has a default value of 0)
|
||||
UPDATE maimai2_user_playlog SET ext_num4 = 0 WHERE ext_num4 IS NULL;
|
||||
ALTER TABLE maimai2_user_playlog MODIFY COLUMN ext_num4 INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- maimai2_user_detail
|
||||
-- Add default value for current_play_count and set it as NOT NULL
|
||||
UPDATE maimai2_user_detail SET current_play_count = 0 WHERE current_play_count IS NULL;
|
||||
ALTER TABLE maimai2_user_detail MODIFY COLUMN current_play_count INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- Add default value for rename_credit and set it as NOT NULL
|
||||
UPDATE maimai2_user_detail SET rename_credit = 0 WHERE rename_credit IS NULL;
|
||||
ALTER TABLE maimai2_user_detail MODIFY COLUMN rename_credit INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- maimai2_user_playlog
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||
|
||||
-- maimai2_user_detail
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||
@@ -0,0 +1,14 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(23120811, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120821, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120822, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120823, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120824, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120825, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120841, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120842, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120843, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120844, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120851, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23120852, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(23122271, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
@@ -0,0 +1,5 @@
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (1, 1, 2, 1, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (2, 2, 3, 2, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (3, 3, 4, 3, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (4, 4, 5, 4, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
INSERT INTO maimai2_game_charge (id, order_id, charge_id, price, start_date, end_date) VALUES (5, 5, 6, 5, '2019-01-01 00:00:00.000000', '2099-01-01 00:00:00.000000');
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO `maimai2_game_event` (`id`, `end_date`, `start_date`, `type`, `enable`) VALUES
|
||||
(24011111, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011121, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1'),
|
||||
(24011141, '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', 0, '1');
|
||||
@@ -0,0 +1,3 @@
|
||||
UPDATE maimai2_user_playlog SET ext_bool1=false;
|
||||
UPDATE maimai2_user_detail SET current_play_count=0;
|
||||
UPDATE maimai2_user_detail SET rename_credit=0;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- maimai2_user_playlog
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_bool1 BOOLEAN;
|
||||
ALTER TABLE maimai2_user_playlog ADD COLUMN ext_num4 INTEGER;
|
||||
UPDATE maimai2_user_playlog SET ext_num4=0;
|
||||
|
||||
-- maimai2_user_detail
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN current_play_count INTEGER;
|
||||
ALTER TABLE maimai2_user_detail ADD COLUMN rename_credit INTEGER;
|
||||
Reference in New Issue
Block a user