mirror of https://github.com/hykilpikonna/AquaDX
[+] Add change name for maimai and refactor settings page
parent
836f789fc9
commit
b32b0e970c
|
@ -0,0 +1,78 @@
|
|||
<script lang="ts">
|
||||
import { SETTING } from "../libs/sdk";
|
||||
import type { GameOption } from "../libs/generalTypes";
|
||||
import { ts } from "../libs/i18n";
|
||||
import StatusOverlays from "./StatusOverlays.svelte";
|
||||
|
||||
export let game: string;
|
||||
let gameFields: GameOption[] = []
|
||||
let submitting = ""
|
||||
let error: string;
|
||||
|
||||
SETTING.get().then(s => {
|
||||
gameFields = s.filter(it => it.game === game)
|
||||
})
|
||||
|
||||
function submitGameOption(field: string, value: any) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
SETTING.set(field, value).catch(e => error = e.message).finally(() => submitting = "")
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="fields">
|
||||
{#each gameFields as field}
|
||||
<div class="field">
|
||||
{#if field.type === "Boolean"}
|
||||
<div class="bool">
|
||||
<input id={field.key} type="checkbox" bind:checked={field.value}
|
||||
on:change={() => submitGameOption(field.key, field.value)}/>
|
||||
<label for={field.key}>
|
||||
<span class="name">{ts(`settings.fields.${field.key}.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!gameFields.length && !!submitting}/>
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.bool
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
label
|
||||
max-width: max-content
|
||||
|
||||
> div:not(.bool)
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
margin-top: 0.5rem
|
||||
|
||||
> input
|
||||
flex: 1
|
||||
</style>
|
|
@ -0,0 +1,87 @@
|
|||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { t } from "../libs/i18n.js";
|
||||
import Icon from "@iconify/svelte";
|
||||
import StatusOverlays from "./StatusOverlays.svelte";
|
||||
import { GAME } from "../libs/sdk";
|
||||
|
||||
const profileFields = [
|
||||
['name', t('settings.mai2.name')],
|
||||
]
|
||||
|
||||
export let username: string;
|
||||
let error: string
|
||||
let submitting = ""
|
||||
let values = Array(profileFields.length).fill('')
|
||||
let changed: string[] = []
|
||||
|
||||
GAME.userSummary(username, 'mai2').then(({name}) => {
|
||||
values = [name]
|
||||
}).catch(e => error = e.message)
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
switch (field) {
|
||||
case 'name':
|
||||
GAME.changeName('mai2', value).then(({newName}) => {
|
||||
changed = changed.filter(c => c !== field)
|
||||
values = [newName]
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
<label for={field}>{name}</label>
|
||||
<div>
|
||||
<input id={field} type="text"
|
||||
bind:value={values[i]} on:input={() => changed = [...changed, field]}
|
||||
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||
{#if changed.includes(field) && values[i]}
|
||||
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, values[i])}>
|
||||
{#if submitting === field}
|
||||
<Icon icon="line-md:loading-twotone-loop"/>
|
||||
{:else}
|
||||
{t('settings.profile.save')}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<GameSettingFields game="mai2"/>
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!values[0] || !!submitting}/>
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
label
|
||||
max-width: max-content
|
||||
|
||||
> div:not(.bool)
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
margin-top: 0.5rem
|
||||
|
||||
> input
|
||||
flex: 1
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script>
|
||||
import { fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<GameSettingFields game="wacca"/>
|
||||
</div>
|
||||
</main>
|
|
@ -39,6 +39,7 @@ export interface CardSummary {
|
|||
chu3: CardSummaryGame | null
|
||||
ongeki: CardSummaryGame | null
|
||||
diva: CardSummaryGame | null
|
||||
wacca: CardSummaryGame | null
|
||||
}
|
||||
|
||||
|
||||
|
@ -119,6 +120,7 @@ export interface GameOption {
|
|||
key: string
|
||||
value: any
|
||||
type: 'Boolean'
|
||||
game: string
|
||||
}
|
||||
|
||||
export interface UserBox {
|
||||
|
|
|
@ -115,20 +115,22 @@ export const EN_REF_SETTINGS = {
|
|||
'settings.title': 'Settings',
|
||||
'settings.tabs.profile': 'Profile',
|
||||
'settings.tabs.game': 'Game',
|
||||
'settings.tabs.userbox': 'Userbox',
|
||||
'settings.tabs.chu3': 'Chuni',
|
||||
'settings.tabs.mai2': 'Mai',
|
||||
'settings.tabs.wacca': 'Wacca',
|
||||
'settings.fields.unlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
|
||||
'settings.fields.unlockChara.name': 'Unlock All Characters',
|
||||
'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
|
||||
'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
|
||||
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game. ' +
|
||||
'This setting is not relevant in chusan because in-game user box is disabled.',
|
||||
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
|
||||
'settings.fields.unlockTickets.name': 'Unlock All Tickets',
|
||||
'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (note: maimai still limits which tickets can be used).',
|
||||
'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
|
||||
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
|
||||
'settings.mai2.name': 'Player Name',
|
||||
'settings.profile.picture': 'Profile Picture',
|
||||
'settings.profile.upload-new': 'Upload New',
|
||||
'settings.profile.save': 'Save',
|
||||
|
|
|
@ -124,18 +124,22 @@ const zhSettings: typeof EN_REF_SETTINGS = {
|
|||
'settings.title': '用户设置',
|
||||
'settings.tabs.profile': '个人资料',
|
||||
'settings.tabs.game': '游戏设置',
|
||||
'settings.tabs.chu3': '中二',
|
||||
'settings.tabs.mai2': '舞萌',
|
||||
'settings.tabs.wacca': 'Wacca',
|
||||
'settings.fields.unlockMusic.name': '解锁谱面',
|
||||
'settings.fields.unlockMusic.desc': '在游戏中解锁所有曲目和大师难度谱面。',
|
||||
'settings.fields.unlockChara.name': '解锁角色',
|
||||
'settings.fields.unlockChara.desc': '在游戏中解锁所有角色、语音和伙伴。',
|
||||
'settings.fields.unlockCollectables.name': '解锁收藏品',
|
||||
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图),此设置对中二不适用。',
|
||||
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图)。',
|
||||
'settings.fields.unlockTickets.name': '解锁游戏券',
|
||||
'settings.fields.unlockTickets.desc': '无限跑图券/解锁券(注:maimai 客户端仍限制一些券不能使用)。',
|
||||
'settings.fields.waccaInfiniteWp.name': 'Wacca: 无限 WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': 'Wacca: 永久会员',
|
||||
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
|
||||
'settings.mai2.name': '玩家名字',
|
||||
'settings.profile.picture': '头像',
|
||||
'settings.profile.upload-new': '上传',
|
||||
'settings.profile.save': '保存',
|
||||
|
|
|
@ -299,7 +299,8 @@ export const GAME = {
|
|||
post(`/api/v2/game/${game}/user-summary`, { username }),
|
||||
ranking: (game: GameName): Promise<GenericRanking[]> =>
|
||||
post(`/api/v2/game/${game}/ranking`, { }),
|
||||
|
||||
changeName: (game: GameName, newName: string): Promise<{ newName: string }> =>
|
||||
post(`/api/v2/game/${game}/change-name`, { newName }),
|
||||
}
|
||||
|
||||
export const DATA = {
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
import type { AquaNetUser, GameOption } from "../../libs/generalTypes";
|
||||
import {CARD, SETTING, USER} from "../../libs/sdk";
|
||||
import type { AquaNetUser } from "../../libs/generalTypes";
|
||||
import { CARD, USER } from "../../libs/sdk";
|
||||
import StatusOverlays from "../../components/StatusOverlays.svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import { pfp } from "../../libs/ui";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import UserBox from "../../components/UserBox.svelte";
|
||||
import Mai2Settings from "../../components/Mai2Settings.svelte";
|
||||
import WaccaSettings from "../../components/WaccaSettings.svelte";
|
||||
|
||||
USER.ensureLoggedIn()
|
||||
|
||||
|
@ -17,7 +19,7 @@
|
|||
let error: string;
|
||||
let submitting = ""
|
||||
let tab = 0
|
||||
let tabs = [ 'profile', 'game' ]
|
||||
let tabs = [ 'profile' ]
|
||||
|
||||
const profileFields = [
|
||||
[ 'displayName', t('settings.profile.name') ],
|
||||
|
@ -27,18 +29,20 @@
|
|||
[ 'profileBio', t('settings.profile.bio') ],
|
||||
]
|
||||
|
||||
let gameFields: GameOption[] = []
|
||||
|
||||
// Fetch user data
|
||||
const getMe = () => Promise.all([USER.me(), SETTING.get()]).then(([m, s]) => {
|
||||
gameFields = s
|
||||
const getMe = () => USER.me().then((m) => {
|
||||
me = m
|
||||
values = profileFields.map(([field]) => me[field as keyof AquaNetUser])
|
||||
|
||||
CARD.userGames(m.username).then(games => {
|
||||
|
||||
if (games.chu3 && !tabs.includes('userbox')) {
|
||||
tabs = [...tabs, 'userbox']
|
||||
if (games.chu3 && !tabs.includes('chu3')) {
|
||||
tabs = [...tabs, 'chu3']
|
||||
}
|
||||
if (games.mai2 && !tabs.includes('mai2')) {
|
||||
tabs = [...tabs, 'mai2']
|
||||
}
|
||||
if (games.wacca && !tabs.includes('wacca')) {
|
||||
tabs = [...tabs, 'wacca']
|
||||
}
|
||||
})
|
||||
}).catch(e => error = e.message)
|
||||
|
@ -57,13 +61,6 @@
|
|||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
}
|
||||
|
||||
function submitGameOption(field: string, value: any) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
SETTING.set(field, value).catch(e => error = e.message).finally(() => submitting = "")
|
||||
}
|
||||
|
||||
function uploadPfp(file: File) {
|
||||
if (submitting) return
|
||||
submitting = 'profilePicture'
|
||||
|
@ -135,27 +132,13 @@
|
|||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if tab === 1}
|
||||
<!-- Tab 1: Game settings -->
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
{#each gameFields as field}
|
||||
<div class="field">
|
||||
{#if field.type === "Boolean"}
|
||||
<div class="bool">
|
||||
<input id={field.key} type="checkbox" bind:checked={field.value}
|
||||
on:change={() => submitGameOption(field.key, field.value)} />
|
||||
<label for={field.key}>
|
||||
<span class="name">{ts(`settings.fields.${field.key}.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if tab === 2}
|
||||
<!-- Tab 2: Userbox settings -->
|
||||
{:else if tabs[tab] === 'chu3'}
|
||||
<!-- Userbox settings -->
|
||||
<UserBox />
|
||||
{:else if tabs[tab] === 'mai2'}
|
||||
<Mai2Settings username={me.username} />
|
||||
{:else if tabs[tab] === 'wacca'}
|
||||
<WaccaSettings />
|
||||
{/if}
|
||||
|
||||
<StatusOverlays {error} loading={!me || !!submitting} />
|
||||
|
@ -169,18 +152,6 @@
|
|||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.bool
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
|
|
@ -47,7 +47,9 @@ annotation class Doc(
|
|||
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class SettingField
|
||||
annotation class SettingField(
|
||||
val game: String
|
||||
)
|
||||
|
||||
// Reflection
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
|
|
@ -21,7 +21,8 @@ class SettingsApi(
|
|||
.mapNotNull { it.findAnnotation<SettingField>()?.let { an -> it to an } }
|
||||
val fieldMap = fields.associate { (f, _) -> f.name to f }
|
||||
val fieldDesc = fields.map { (f, _) -> mapOf(
|
||||
"key" to f.name, "type" to f.returnType.jvmErasure.simpleName
|
||||
"key" to f.name, "type" to f.returnType.jvmErasure.simpleName,
|
||||
"game" to f.findAnnotation<SettingField>()!!.game,
|
||||
) }
|
||||
|
||||
@API("get")
|
||||
|
@ -42,4 +43,4 @@ class SettingsApi(
|
|||
field.setCast(options, value)
|
||||
goRepo.save(options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,23 +14,23 @@ class AquaGameOptions(
|
|||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@SettingField
|
||||
@SettingField("mai2")
|
||||
var unlockMusic: Boolean = false,
|
||||
|
||||
@SettingField
|
||||
@SettingField("mai2")
|
||||
var unlockChara: Boolean = false,
|
||||
|
||||
@SettingField
|
||||
@SettingField("mai2")
|
||||
var unlockCollectables: Boolean = false,
|
||||
|
||||
@SettingField
|
||||
@SettingField("mai2")
|
||||
var unlockTickets: Boolean = false,
|
||||
|
||||
@SettingField
|
||||
@SettingField("wacca")
|
||||
var waccaInfiniteWp: Boolean = false,
|
||||
|
||||
@SettingField
|
||||
@SettingField("wacca")
|
||||
var waccaAlwaysVip: Boolean = false,
|
||||
)
|
||||
|
||||
interface AquaGameOptionsRepo : JpaRepository<AquaGameOptions, Long>
|
||||
interface AquaGameOptionsRepo : JpaRepository<AquaGameOptions, Long>
|
||||
|
|
|
@ -23,6 +23,18 @@ fun usernameCheck(chars: String): (IUserData, String) -> Unit = { u, v ->
|
|||
v.find { it !in chars }?.let { 400 - "Invalid character '$it' in username" }
|
||||
}
|
||||
|
||||
fun toFullWidth(input: String): String {
|
||||
val stringBuilder = StringBuilder()
|
||||
for (char in input.toCharArray()) {
|
||||
if (char.code in 33..126) {
|
||||
stringBuilder.append((char.code + 65248).toChar())
|
||||
} else {
|
||||
stringBuilder.append(char)
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString()
|
||||
}
|
||||
|
||||
data class TrendLog(val date: String, val rating: Int)
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@ import icu.samnyan.aqua.net.games.*
|
|||
import icu.samnyan.aqua.net.utils.*
|
||||
import icu.samnyan.aqua.sega.maimai2.model.*
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.util.*
|
||||
|
||||
|
@ -39,4 +40,15 @@ class Maimai2(
|
|||
|
||||
genericUserSummary(card, ratingComposition)
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("change-name")
|
||||
suspend fun changeName(@RP token: String, @RP newName: String) = us.jwt.auth(token) { u ->
|
||||
val newNameFull = toFullWidth(newName)
|
||||
us.cardByName(u.username) { card ->
|
||||
val user = userDataRepo.findByCard(card) ?: (404 - "User not found")
|
||||
settableFields["userName"]?.invoke(user, newNameFull)
|
||||
userDataRepo.save(user)
|
||||
}
|
||||
mapOf("newName" to newNameFull)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue