mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-09-22 22:38:14 +03:00
User Options Web UI (#214)
This commit is contained in:
@@ -3,10 +3,11 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
type AquaNetUser,
|
||||
type GameUserOption,
|
||||
type UserBox,
|
||||
type UserItem,
|
||||
} from "../../libs/generalTypes";
|
||||
import { DATA, USER, USERBOX, GAME } from "../../libs/sdk";
|
||||
import { DATA, USER, USERBOX, GAME, SETTING } from "../../libs/sdk";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import { FADE_IN, FADE_OUT, USERBOX_DEFAULT_URL } from "../../libs/config";
|
||||
import { fade, slide } from "svelte/transition";
|
||||
@@ -23,11 +24,15 @@
|
||||
import { DDS } from "../../libs/userbox/dds";
|
||||
import ChuniMatchingSettings from "./ChuniMatchingSettings.svelte";
|
||||
import InputField from "../ui/InputField.svelte";
|
||||
import UserOptionSlider from "./UserOptionSlider.svelte";
|
||||
|
||||
let user: AquaNetUser
|
||||
let [loading, error, submitting, preview] = [true, "", "", ""]
|
||||
let changed: string[] = [];
|
||||
|
||||
let userOptions: GameUserOption = {};
|
||||
let userIsUsingPreset = false;
|
||||
|
||||
// Available (unlocked) options for each kind of item
|
||||
// In allItems: 'namePlate', 'frame', 'trophy', 'mapIcon', 'systemVoice', 'avatarAccessory'
|
||||
let allItems: Record<string, Record<string, { name: string }>> = {}
|
||||
@@ -89,6 +94,13 @@
|
||||
console.log("All items", allItems)
|
||||
console.log("Userbox", userbox)
|
||||
|
||||
userOptions = await SETTING.optionGet('chu3').catch(_ => {
|
||||
loading = false
|
||||
error = t("userbox.error.nodata")
|
||||
}) as GameUserOption
|
||||
if (userOptions["optionSet"] != 3)
|
||||
userIsUsingPreset = true;
|
||||
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -312,6 +324,39 @@
|
||||
|
||||
<InputField bind:field={userNameField}
|
||||
callback={() => USERBOX.setUserBox({ field: "userName", value: userNameField.value })}/>
|
||||
|
||||
<!-- User Options -->
|
||||
{#if userIsUsingPreset}
|
||||
<blockquote class="info">
|
||||
{t('settings.options.all.preset-warning')}
|
||||
</blockquote>
|
||||
{/if}
|
||||
<div class="fields-ranges">
|
||||
<UserOptionSlider
|
||||
game="chu3" type="headphone"
|
||||
minValue={0} maxValue={60}
|
||||
defaultValue={userOptions["headphone"]}
|
||||
getTextFunction={(value: number) => `${((value / 60) * 100).toFixed(0)}%`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="chu3" type="trackSkip"
|
||||
minValue={0} maxValue={7}
|
||||
defaultValue={userOptions["trackSkip"]}
|
||||
getTextFunction={(value: number) => [t('settings.options.all.none'), `S`, `S+`, `SS`, `SS+`, `SSS`, `SSS+`, t('settings.options.all.personal-best')][value]}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="chu3" type="speed"
|
||||
minValue={0} maxValue={61}
|
||||
defaultValue={userOptions["speed"]}
|
||||
getTextFunction={(value: number) => value >= 61 ? "SONIC" : value < 55 ? `${(value / 4) + 1}` : `${value - 40}`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="chu3" type="speed_120"
|
||||
minValue={0} maxValue={61}
|
||||
defaultValue={userOptions["speed_120"]}
|
||||
getTextFunction={(value: number) => value >= 61 ? "SONIC" : value < 55 ? `${(value / 4) + 1}` : `${value - 40}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h2>{t("userbox.header.userbox")}</h2>
|
||||
{#if !USERBOX_ENABLED.value || !USERBOX_INSTALLED}
|
||||
@@ -394,6 +439,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if USERBOX_INSTALLED}
|
||||
<!-- god this is a mess but idgaf atp -->
|
||||
<div class="field boolean" style:margin-top="1em">
|
||||
@@ -594,6 +640,18 @@ p.notice
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
|
||||
.fields-ranges
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
margin: 0.5rem 0
|
||||
gap: 0 1rem
|
||||
justify-content: center
|
||||
|
||||
:global(.field)
|
||||
flex: calc(50% - 1rem)
|
||||
width: 50%
|
||||
|
||||
/* AquaBox */
|
||||
|
||||
.chuni-userbox-row
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
import { t } from "../../libs/i18n.js";
|
||||
import Icon from "@iconify/svelte";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import { GAME, USER } from "../../libs/sdk";
|
||||
import { GAME, SETTING, USER } from "../../libs/sdk";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { download } from "../../libs/ui";
|
||||
import UserOptionSlider from "./UserOptionSlider.svelte";
|
||||
import type { GameUserOption } from "../../libs/generalTypes";
|
||||
|
||||
const profileFields = [
|
||||
['name', t('settings.mai2.name')],
|
||||
@@ -14,12 +16,24 @@
|
||||
|
||||
let error: string
|
||||
let submitting = ""
|
||||
let loading = true
|
||||
let values = Array(profileFields.length).fill('')
|
||||
let changed: string[] = []
|
||||
|
||||
let userOptions: GameUserOption = {}
|
||||
let userIsUsingPreset = false
|
||||
|
||||
USER.me().then(me =>
|
||||
GAME.userSummary(me.username, 'mai2').then(({name}) => {
|
||||
GAME.userSummary(me.username, 'mai2').then(async ({name}) => {
|
||||
values = [name]
|
||||
|
||||
userOptions = await SETTING.optionGet('mai2').catch(_ => {
|
||||
error = t("userbox.error.nodata")
|
||||
loading = true
|
||||
}) as GameUserOption
|
||||
if (userOptions["optionKind"] != 3)
|
||||
userIsUsingPreset = true
|
||||
loading = false
|
||||
}).catch(e => error = e.message));
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
@@ -208,6 +222,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !loading}
|
||||
<div class="fields">
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
@@ -228,6 +243,55 @@
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if userIsUsingPreset}
|
||||
<blockquote class="info">
|
||||
{t('settings.options.all.preset-warning')}
|
||||
</blockquote>
|
||||
{/if}
|
||||
<div class="fields-ranges">
|
||||
<!-- TODO: determine min & max -->
|
||||
<UserOptionSlider
|
||||
game="mai2" type="noteSpeed"
|
||||
minValue={0} maxValue={37}
|
||||
defaultValue={userOptions["noteSpeed"]}
|
||||
getTextFunction={(value: number) => value >= 37 ? "SONIC" : `${(value / 4) + 1}`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="mai2" type="touchSpeed"
|
||||
minValue={0} maxValue={37}
|
||||
defaultValue={userOptions["touchSpeed"]}
|
||||
getTextFunction={(value: number) => value >= 37 ? "SONIC" : `${(value / 4) + 1}`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="mai2" type="slideSpeed"
|
||||
minValue={0} maxValue={20}
|
||||
defaultValue={userOptions["slideSpeed"]}
|
||||
getTextFunction={(value: number) => value == 10 ? "Normal" : `${value > 10 ? "Late" : "Fast"} ${(Math.abs(value - 10) / 10).toFixed(1)}`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="mai2" type="headPhoneVolume"
|
||||
minValue={0} maxValue={19}
|
||||
defaultValue={userOptions["headPhoneVolume"]}
|
||||
getTextFunction={(value: number) => `${((value / 19) * 100).toFixed(0)}%`}
|
||||
/>
|
||||
<UserOptionSlider
|
||||
game="mai2" type="trackSkip"
|
||||
minValue={0} maxValue={11}
|
||||
defaultValue={userOptions["trackSkip"]}
|
||||
getTextFunction={(value: number) => [
|
||||
t('settings.options.all.none'),
|
||||
t('settings.options.all.push'),
|
||||
`S`, `SS`, `SSS`,
|
||||
t('settings.options.all.personal-best'),
|
||||
t('settings.options.all.rival-score'),
|
||||
`${t('settings.options.all.life')} 300`,
|
||||
`${t('settings.options.all.life')} 100`,
|
||||
`${t('settings.options.all.life')} 50`,
|
||||
`${t('settings.options.all.life')} 10`,
|
||||
`${t('settings.options.all.life')} 1`
|
||||
][value]}
|
||||
/>
|
||||
</div>
|
||||
<GameSettingFields game="mai2"/>
|
||||
<button class="exportButton" on:click={exportData}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
@@ -238,8 +302,9 @@
|
||||
{t('settings.batchManualExport')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<StatusOverlays {error} loading={!values[0] || !!submitting}/>
|
||||
<StatusOverlays {error} loading={!values[0] || !!submitting || loading}/>
|
||||
|
||||
<style lang="sass">
|
||||
.fields
|
||||
@@ -263,6 +328,17 @@
|
||||
> input
|
||||
flex: 1
|
||||
|
||||
.fields-ranges
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
margin: 0.5rem 0
|
||||
gap: 0 1rem
|
||||
justify-content: center
|
||||
|
||||
:global(.field)
|
||||
flex: calc(50% - 1rem)
|
||||
width: 50%
|
||||
|
||||
.exportButton
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { t } from "../../libs/i18n";
|
||||
import { EN_REF } from "../../libs/i18n/en_ref";
|
||||
import type { GameName } from "../../libs/scoring";
|
||||
import { SETTING } from "../../libs/sdk";
|
||||
|
||||
export let getTextFunction: (value: number) => string
|
||||
export let defaultValue: number = 0
|
||||
|
||||
export let minValue: number = 0
|
||||
export let maxValue: number = 0
|
||||
export let type: string = "speed";
|
||||
export let game: GameName;
|
||||
|
||||
export let disabled: boolean = false;
|
||||
|
||||
let value = defaultValue;
|
||||
let rationalizedValue = defaultValue / maxValue;
|
||||
|
||||
async function submit() {
|
||||
await SETTING.optionSet(game, type, value);
|
||||
}
|
||||
</script>
|
||||
<div class="field range">
|
||||
<div class="range-label">
|
||||
{t(`settings.options.${game}.${type}` as keyof typeof EN_REF)} <!-- TODO: i18n -->
|
||||
</div>
|
||||
<div class="range-value">
|
||||
<input
|
||||
type="range" min={minValue} {disabled}
|
||||
on:change={submit} max={maxValue} bind:value={value}
|
||||
>
|
||||
<div>
|
||||
{getTextFunction(value)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.field
|
||||
width: 100%
|
||||
|
||||
.range-value
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
div
|
||||
min-width: 4rem
|
||||
white-space: nowrap
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
input
|
||||
flex: 100%
|
||||
|
||||
input
|
||||
-webkit-appearance: none
|
||||
width: 100%
|
||||
height: 5px !important
|
||||
outline: none
|
||||
transition: opacity 200ms
|
||||
padding: 4px
|
||||
border-radius: 4px
|
||||
opacity: 80%
|
||||
background: linear-gradient(to right, transparent 0%, #caf1 10%, #caf1 90%, transparent 100%)
|
||||
|
||||
margin: 16px 0
|
||||
|
||||
&:hover
|
||||
opacity: 100%
|
||||
|
||||
*:focus
|
||||
outline: none
|
||||
|
||||
input::-webkit-slider-thumb
|
||||
-webkit-appearance: none
|
||||
appearance: none
|
||||
width: 8px
|
||||
height: 30px
|
||||
background: vars.$c-main
|
||||
border-radius: 8px
|
||||
cursor: pointer
|
||||
box-shadow: 0 0 2px 0 vars.$c-main
|
||||
|
||||
</style>
|
||||
@@ -137,6 +137,8 @@ export interface GameOption {
|
||||
changed?: boolean
|
||||
}
|
||||
|
||||
export type GameUserOption = Record<string, number>
|
||||
|
||||
export interface UserItem { itemKind: number, itemId: number, stock: number }
|
||||
export interface UserBox {
|
||||
userName: string,
|
||||
|
||||
@@ -167,6 +167,24 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.tabs.mai2': 'Mai',
|
||||
'settings.tabs.ongeki': 'Ongeki',
|
||||
'settings.tabs.wacca': 'Wacca',
|
||||
'settings.options.all.preset-warning': 'You are using a preset option set. Options may be reflected incorrectly.',
|
||||
'settings.options.all.personal-best': 'Record',
|
||||
'settings.options.all.rival-score': 'Rival',
|
||||
'settings.options.all.life': 'Life',
|
||||
'settings.options.all.push': 'Push',
|
||||
'settings.options.all.none': 'None',
|
||||
// Chuni User Options
|
||||
'settings.options.chu3.speed': 'Lane Speed',
|
||||
'settings.options.chu3.speed_120': 'Lane Speed (120fps)',
|
||||
'settings.options.chu3.trackSkip': 'Track Skip Type',
|
||||
'settings.options.chu3.headphone': 'Headphone Volume',
|
||||
// Maimai User Options
|
||||
'settings.options.mai2.noteSpeed': 'Note Speed',
|
||||
'settings.options.mai2.touchSpeed': 'Touch Speed',
|
||||
'settings.options.mai2.slideSpeed': 'Slide Speed',
|
||||
'settings.options.mai2.headPhoneVolume': 'Headphone Volume',
|
||||
'settings.options.mai2.trackSkip': 'Track Skip Type',
|
||||
|
||||
'settings.fields.mai2UnlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.mai2UnlockMusic.desc': 'Unlock all music and master difficulty.',
|
||||
'settings.fields.mai2UnlockChara.name': 'Unlock All Characters',
|
||||
|
||||
@@ -162,6 +162,23 @@ const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.tabs.mai2': '舞萌',
|
||||
'settings.tabs.ongeki': '音击',
|
||||
'settings.tabs.wacca': '华卡',
|
||||
|
||||
"settings.options.all.preset-warning": "您正在使用预设选项配置,部分选项可能显示不正确。",
|
||||
"settings.options.all.personal-best": "记录",
|
||||
"settings.options.all.rival-score": "对手",
|
||||
"settings.options.all.life": "生命值",
|
||||
"settings.options.all.push": "按压",
|
||||
"settings.options.all.none": "无",
|
||||
"settings.options.chu3.speed": "轨道速度",
|
||||
"settings.options.chu3.speed_120": "轨道速度(120帧)",
|
||||
"settings.options.chu3.trackSkip": "跳过曲目类型",
|
||||
"settings.options.chu3.headphone": "耳机音量",
|
||||
"settings.options.mai2.noteSpeed": "音符速度",
|
||||
"settings.options.mai2.touchSpeed": "触控速度",
|
||||
"settings.options.mai2.slideSpeed": "滑动速度",
|
||||
"settings.options.mai2.headPhoneVolume": "耳机音量",
|
||||
"settings.options.mai2.trackSkip": "跳过曲目类型",
|
||||
|
||||
'settings.fields.mai2UnlockMusic.name': '解锁谱面',
|
||||
'settings.fields.mai2UnlockMusic.desc': '解锁所有曲目和大师难度谱面。',
|
||||
'settings.fields.mai2UnlockChara.name': '解锁角色',
|
||||
|
||||
@@ -9,7 +9,8 @@ import type {
|
||||
AquaNetUser, GameOption,
|
||||
UserBox,
|
||||
UserItem,
|
||||
Dict
|
||||
Dict,
|
||||
GameUserOption
|
||||
} from './generalTypes'
|
||||
import type { GameName } from './scoring'
|
||||
|
||||
@@ -255,6 +256,10 @@ export const SETTING = {
|
||||
post('/api/v2/settings/set', { key, value: `${value}` }),
|
||||
detailSet: (game: string, field: string, value: any) =>
|
||||
post(`/api/v2/game/${game}/user-detail-set`, { field, value }),
|
||||
optionGet: (game: string): Promise<GameUserOption> =>
|
||||
post(`/api/v2/game/${game}/user-option`),
|
||||
optionSet: (game: string, field: string, value: number): Promise<void> =>
|
||||
post(`/api/v2/game/${game}/user-option-set`, { field, value }),
|
||||
}
|
||||
|
||||
export const TRANSFER = {
|
||||
|
||||
Reference in New Issue
Block a user