add prettier formatter

js-formatter
Afonso 2024-02-12 18:04:56 +01:00
parent e3f931d4f5
commit 38bddf1763
21 changed files with 1141 additions and 1018 deletions

3
.gitignore vendored
View File

@ -76,3 +76,6 @@ gradle-app.setting
### Gradle Patch ###
# Java heap dump
*.hprof
### Docker ###
/db/*

8
.prettierrc 100644
View File

@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"bracketSpacing": false,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -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=true
SinglePlayer=false
# !!EXPERIMENTAL!! Skip from the card-scanning screen directly to music selection screen
SkipToMusicSelection=false
SkipToMusicSelection=true

View File

@ -19,4 +19,3 @@ Finally, run:
yarn install
yarn dev
```

View File

@ -30,6 +30,6 @@ If you have state that's important to retain within a component, consider creati
```ts
// store.ts
// An extremely simple external store
import { writable } from 'svelte/store'
import {writable} from 'svelte/store'
export default writable(0)
```

View File

@ -6,15 +6,36 @@
<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>

View File

@ -7,13 +7,16 @@
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json"
"check": "svelte-check --tsconfig ./tsconfig.json",
"format": "prettier --write ."
},
"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",
"sass": "^1.70.0",
"svelte": "^4.2.10",
"svelte-check": "^3.6.4",

View File

@ -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"
}

View File

@ -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>

View File

@ -1,5 +1,5 @@
export interface TrendEntry {
date: string
rating: number
plays: number
date: string
rating: number
plays: number
}

View File

@ -1,51 +1,54 @@
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]
achievement /= 10000
for (let i = 0; i < multTable.length; i++) {
if (achievement >= (multTable[i][0] as number)) return multTable[i]
}
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())
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())
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())
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())
}
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})
}

View File

@ -1,115 +1,115 @@
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
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
}

View File

@ -1,94 +1,107 @@
import {
Chart as ChartJS,
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
TimeScale,
type ChartOptions,
type LineOptions,
} from 'chart.js'
import moment from 'moment/moment'
// @ts-ignore
import CalHeatmap from 'cal-heatmap'
// @ts-ignore
import CalTooltip from 'cal-heatmap/plugins/Tooltip'
import type {Line} from 'svelte-chartjs'
export function title(t: string) {
document.title = `AquaNet - ${t}`
}
export function registerChart() {
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale, TimeScale, type ChartOptions, type LineOptions,
} from 'chart.js';
import moment from "moment/moment";
// @ts-ignore
import CalHeatmap from "cal-heatmap";
// @ts-ignore
import CalTooltip from 'cal-heatmap/plugins/Tooltip';
import type {Line} from "svelte-chartjs";
export function title(t: string) {
document.title = `AquaNet - ${t}`
CategoryScale,
TimeScale
)
}
export function registerChart() {
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
LinearScale,
PointElement,
CategoryScale,
TimeScale
);
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',
},
[
[
CalTooltip,
{
text: (_: Date, v: number, d: any) =>
`${v ?? 'No'} songs played on ${d.format('MMMM D, YYYY')}`,
},
],
]
)
}
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",
}, [
[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
}
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,
}
},
scales: {
xAxis: {
type: 'time',
display: false,
},
plugins: {
legend: {
display: false
},
tooltip: {
mode: "index",
intersect: false
}
y: {
display: false,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
mode: 'index',
intersect: false,
},
},
}
/**
@ -96,6 +109,8 @@ 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(' ')
}

View File

@ -1,59 +1,62 @@
<script lang="ts">
import {data_host} from "../libs/config";
import {getMaimaiAllMusic, getMaimai, getMult} from "../libs/maimai";
import type {ParsedRating, Rating} from "../libs/maimaiTypes";
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")
if (!userId) console.error('No user ID provided')
Promise.all([
getMaimai("GetUserRatingApi", {userId}),
getMaimaiAllMusic().then(it => it.json())
getMaimai('GetUserRatingApi', {userId}),
getMaimaiAllMusic(),
]).then(([rating, music]) => {
data = rating
musicInfo = music
if (!data || !musicInfo) {
console.error("Failed to fetch data")
console.error('Failed to fetch data')
return
}
parsedRatings = {
old: parseRating(data.userRating.ratingList),
new: parseRating(data.userRating.newRatingList)
new: parseRating(data.userRating.newRatingList),
}
})
function parseRating(arr: Rating[]) {
return arr.map(x => {
const music = musicInfo[x.musicId]
return arr
.map((x) => {
const music = musicInfo[x.musicId]
if (!music) {
console.error(`Music not found: ${x.musicId}`)
return null
}
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[]
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[],
old: ParsedRating[]
new: ParsedRating[]
} | null = null
let data: {
userRating: {
rating: number,
ratingList: Rating[],
rating: number
ratingList: Rating[]
newRatingList: Rating[]
}
} | null = null
@ -64,22 +67,39 @@
<main>
<!-- Display all parsed ratings -->
{#if parsedRatings}
{#each [{title: "Old", data: parsedRatings.old}, {title: "New", data: parsedRatings.new}] as section}
{#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="">
<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.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="">
<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}

View File

@ -1,17 +1,26 @@
<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';
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;
export let userId: any
userId = +userId
let calElement: HTMLElement
@ -20,7 +29,7 @@
interface MusicAndPlay extends MaimaiMusic, MaimaiUserPlaylog {}
let d: {
user: MaimaiUserSummaryEntry,
user: MaimaiUserSummaryEntry
trend: TrendEntry[]
recent: MusicAndPlay[]
} | null = null
@ -28,22 +37,37 @@
Promise.all([
getMaimaiUser(userId),
getMaimaiTrend(userId),
getMaimaiAllMusic()
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}}))
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">
<img
src={`${data_host}/maimai/assetbundle/icon/${d.user.iconId.toString().padStart(6, '0')}.png`}
alt=""
class="pfp"
/>
<h2>{d.user.name}</h2>
</div>
@ -66,18 +90,23 @@
<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,
<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} />
// TODO: Set X axis span to 3 months
},
],
}}
options={CHARTJS_OPT}
/>
</div>
</div>
@ -138,12 +167,12 @@
<div class="first-play">
<span>First Seen</span>
<span>{moment(d.user.joined).format("YYYY-MM-DD")}</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>
<span>{moment(d.user.lastSeen).format('YYYY-MM-DD')}</span>
</div>
<div class="last-version">
@ -159,15 +188,27 @@
<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="">
<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 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})}>
<span
class={'dx-change ' +
clazz({
increased: r.afterDeluxRating - r.beforeDeluxRating > 0,
})}
>
{r.afterDeluxRating - r.beforeDeluxRating}
</span>
</div>

View File

@ -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

View File

@ -16,5 +16,5 @@
"isolatedModules": true
},
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
"references": [{ "path": "./tsconfig.node.json" }]
"references": [{"path": "./tsconfig.node.json"}]
}

View File

@ -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({

View File

@ -1047,6 +1047,16 @@ postcss@^8.4.35:
picocolors "^1.0.0"
source-map-js "^1.0.2"
prettier-plugin-svelte@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-3.1.2.tgz#2e050eb56dbb467a42c45ad6ce18bb277d28ffa0"
integrity sha512-7xfMZtwgAWHMT0iZc8jN4o65zgbAQ3+O32V6W7pXrqNvKnHnkoyQCGCbKeUyXKZLbYE0YhFRnamfxfkEGxm8qA==
prettier@^3.2.5:
version "3.2.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"