diff --git a/AquaNet/src/app.sass b/AquaNet/src/app.sass index d2130818..b86796d9 100644 --- a/AquaNet/src/app.sass +++ b/AquaNet/src/app.sass @@ -133,6 +133,17 @@ input transition: $transition box-sizing: border-box +select + border-radius: $border-radius + border: 1px solid transparent + padding: 0.6em 1.2em + font-size: 1em + font-weight: 500 + font-family: inherit + background-color: $ov-lighter + transition: $transition + box-sizing: border-box + input[type="checkbox"] width: 1.2em height: 1.2em diff --git a/AquaNet/src/components/UserBox.svelte b/AquaNet/src/components/UserBox.svelte new file mode 100644 index 00000000..c5b44edd --- /dev/null +++ b/AquaNet/src/components/UserBox.svelte @@ -0,0 +1,674 @@ + + + + +{#if !loading && !error} +
+ + {#if tab === 0} +
+
+ {#each userBoxFields as { key, label, kind }, i (key)} +
+ +
+ + {#if changed.includes(key)} + + {/if} +
+
+ {/each} +
+ {#if HAS_USERBOX_ASSETS} +
+

{t("userbox.preview.ui")}

+ + {#if values.frame} + Preview + {/if} + +
+ + {#if values.mapicon} +
+ Preview +
+ {/if} + + + {#if values.voice} +
+ Preview +
+ {/if} +
+ +

{t("userbox.preview.nameplate")}

+ + {#if values.nameplate} +
+ Preview +

+ {availableOptions.trophy.find((x) => x.id === values.trophy) + ?.label} +

+
+

+ {user.displayName} +

+
+
+ {/if} + +

{t("userbox.preview.avatar")}

+
+
+ Preview +
+
+ Preview +
+
+ Preview +
+
+ Preview +
+
+ Preview +
+
+ Preview +
+
+ Preview +
+
+
+ {/if} +
+ {:else} +
+

WIP

+
+ {/if} +
+{/if} + + + diff --git a/AquaNet/src/libs/config.ts b/AquaNet/src/libs/config.ts index 20e1eba5..93120a0a 100644 --- a/AquaNet/src/libs/config.ts +++ b/AquaNet/src/libs/config.ts @@ -11,5 +11,7 @@ export const DISCORD_INVITE = 'https://discord.gg/FNgveqFF7s' // UI export const FADE_OUT = { duration: 200 } export const FADE_IN = { delay: 400 } -export const DEFAULT_PFP = "/assets/imgs/no_profile.png" +export const DEFAULT_PFP = '/assets/imgs/no_profile.png' +// USERBOX_ASSETS +export const HAS_USERBOX_ASSETS = true \ No newline at end of file diff --git a/AquaNet/src/libs/generalTypes.ts b/AquaNet/src/libs/generalTypes.ts index 82112ed5..2fa0ad28 100644 --- a/AquaNet/src/libs/generalTypes.ts +++ b/AquaNet/src/libs/generalTypes.ts @@ -118,5 +118,68 @@ export type AllMusic = { [key: string]: MusicMeta } export interface GameOption { key: string value: any - type: "Boolean" + type: 'Boolean' } + +export interface UserBox { + userName:string, + level:number, + exp:string, + point:number, + totalPoint:number, + playerRating:number, + highestRating:number, + nameplateId:number, + frameId:number, + characterId:number, + trophyId:number, + totalMapNum:number, + totalHiScore: number, + totalBasicHighScore:number, + totalAdvancedHighScore:number, + totalExpertHighScore:number, + totalMasterHighScore:number, + totalUltimaHighScore:number, + friendCount:number, + firstPlayDate:Date, + lastPlayDate:Date, + courseClass:number, + overPowerPoint:number, + overPowerRate:number, + mapIconId:number, + voiceId:number, + avatarWear: number, + avatarHead: number, + avatarFace: number, + avatarSkin: number, + avatarItem: number, + avatarFront: number, + avatarBack: number, +} + +// Assign a number to each kind of user box item with an enum +export enum UserBoxItemKind { + nameplate = 1, + frame = 2, + trophy = 3, + mapicon = 8, + sysvoice = 9, + avatar = 11, +} + +// Define type only with the keys +export type UserBoxItemKindStr = keyof typeof UserBoxItemKind; + +type ChangePlateReq = {kind:'plate', nameplateId:number} +type ChangeFrameReq = {kind:'frame', frameId:number} +type ChangeTrophyReq = {kind:'trophy',trophyId:number} +type ChangeMapIconReq = {kind:'mapicon',mapiconid:number} +type ChangeVoiceReq = {kind:'sysvoice',voiceId:number} +type ChangeAvatarReq = { + kind:'avatar', + accId:number, + category:number +} + +export type ChangeUserBoxReq = {aimeId:string} & (ChangePlateReq | ChangeFrameReq | ChangeTrophyReq | ChangeMapIconReq | ChangeVoiceReq | ChangeAvatarReq); + diff --git a/AquaNet/src/libs/i18n/en_ref.ts b/AquaNet/src/libs/i18n/en_ref.ts index 7cbf6192..13d75e88 100644 --- a/AquaNet/src/libs/i18n/en_ref.ts +++ b/AquaNet/src/libs/i18n/en_ref.ts @@ -17,13 +17,13 @@ export const EN_REF_USER = { 'UserHome.Version': 'Last Version', 'UserHome.RecentScores': 'Recent Scores', 'UserHome.NoData': 'No data in the past ${days} days', - 'UserHome.UnknownSong': "(unknown song)", + 'UserHome.UnknownSong': '(unknown song)', 'UserHome.Settings': 'Settings', - 'UserHome.NoValidGame': "The user hasn't played any game yet.", - 'UserHome.ShowRanksDetails': "Click to show details", + 'UserHome.NoValidGame': 'The user hasn\'t played any game yet.', + 'UserHome.ShowRanksDetails': 'Click to show details', 'UserHome.RankDetail.Title': 'Achievement Details', - 'UserHome.RankDetail.Level': "Level", - 'UserHome.B50': "B50", + 'UserHome.RankDetail.Level': 'Level', + 'UserHome.B50': 'B50', } export const EN_REF_Welcome = { @@ -57,11 +57,11 @@ export const EN_REF_LEADERBOARD = { } export const EN_REF_GENERAL = { - 'game.mai2': "Mai", - 'game.chu3': "Chuni", - 'game.ongeki': "Ongeki", - 'game.wacca': "Wacca", - 'status.error': "Error", + 'game.mai2': 'Mai', + 'game.chu3': 'Chuni', + 'game.ongeki': 'Ongeki', + 'game.wacca': 'Wacca', + 'status.error': 'Error', 'status.error.hint': 'Something went wrong, please try again later or ', 'status.error.hint.link': 'join our discord for support.', 'status.detail': 'Detail: ${detail}', @@ -82,39 +82,40 @@ export const EN_REF_HOME = { 'home.join-discord-description': 'Join our Discord server to chat with other players and get help.', 'home.setup': 'Setup Connection', 'home.setup-description': 'If you own a cab or arcade setup, begin setting up the connection.', - 'home.linkcard.cards': "Your Cards", - 'home.linkcard.description': "Here are the cards you have linked to your account", - 'home.linkcard.account-card': "Account Card", - 'home.linkcard.registered': "Registered", - 'home.linkcard.lastused': "Last used", - 'home.linkcard.enter-info': "Please enter the following information", - 'home.linkcard.access-code': "The 20-digit access code on the back of your card. (If it doesn't work, please try scanning your card in game and enter the access code shown on screen)", - 'home.linkcard.enter-sn1': "Download the NFC Tools app on your phone", - 'home.linkcard.enter-sn2': "and scan your card. Then, enter the Serial Number.", - 'home.linkcard.link': "Link", - 'home.linkcard.data-conflict': "Data Conflict", - 'home.linkcard.name': "Name", - 'home.linkcard.rating': "Rating", - 'home.linkcard.last-login': "Last Login", - 'home.linkcard.linked-own': "This card is already linked to your account", - 'home.linkcard.linked-another': "This card is already linked to another account", - 'home.linkcard.notfound': "Card not found", - 'home.linkcard.unlink': "Unlink Card", - 'home.linkcard.unlink-notice': "Are you sure you want to unlink this card?", - 'home.setup.welcome': "Welcome! If you own an arcade cabinet or game setup, please follow the instructions below to set up the connection with AquaDX.", - 'home.setup.blockquote': "We assume that you already have the required files and can run the game (e.g. ROM and segatools) that come with the cabinet or game setup. If not, please contact the seller of your device for the required files, as we will not provide them for copyright reasons.", - 'home.setup.get': "Get started", - 'home.setup.edit': "Please edit your segatools.ini file and modify the following lines", - 'home.setup.test': "Then, after you restart the game, you should be able to connect to AquaDX. Please verify that the network tests are all GOOD in the test menu.", - 'home.setup.ask': "If you have any questions, please ask in our", - 'home.setup.support': "server", - 'home.setup.keychip-tips': "This is your unique keychip, do not share it with anyone", + 'home.linkcard.cards': 'Your Cards', + 'home.linkcard.description': 'Here are the cards you have linked to your account', + 'home.linkcard.account-card': 'Account Card', + 'home.linkcard.registered': 'Registered', + 'home.linkcard.lastused': 'Last used', + 'home.linkcard.enter-info': 'Please enter the following information', + 'home.linkcard.access-code': 'The 20-digit access code on the back of your card. (If it doesn\'t work, please try scanning your card in game and enter the access code shown on screen)', + 'home.linkcard.enter-sn1': 'Download the NFC Tools app on your phone', + 'home.linkcard.enter-sn2': 'and scan your card. Then, enter the Serial Number.', + 'home.linkcard.link': 'Link', + 'home.linkcard.data-conflict': 'Data Conflict', + 'home.linkcard.name': 'Name', + 'home.linkcard.rating': 'Rating', + 'home.linkcard.last-login': 'Last Login', + 'home.linkcard.linked-own': 'This card is already linked to your account', + 'home.linkcard.linked-another': 'This card is already linked to another account', + 'home.linkcard.notfound': 'Card not found', + 'home.linkcard.unlink': 'Unlink Card', + 'home.linkcard.unlink-notice': 'Are you sure you want to unlink this card?', + 'home.setup.welcome': 'Welcome! If you own an arcade cabinet or game setup, please follow the instructions below to set up the connection with AquaDX.', + 'home.setup.blockquote': 'We assume that you already have the required files and can run the game (e.g. ROM and segatools) that come with the cabinet or game setup. If not, please contact the seller of your device for the required files, as we will not provide them for copyright reasons.', + 'home.setup.get': 'Get started', + 'home.setup.edit': 'Please edit your segatools.ini file and modify the following lines', + 'home.setup.test': 'Then, after you restart the game, you should be able to connect to AquaDX. Please verify that the network tests are all GOOD in the test menu.', + 'home.setup.ask': 'If you have any questions, please ask in our', + 'home.setup.support': 'server', + 'home.setup.keychip-tips': 'This is your unique keychip, do not share it with anyone', } export const EN_REF_SETTINGS = { 'settings.title': 'Settings', 'settings.tabs.profile': 'Profile', 'settings.tabs.game': 'Game', + 'settings.tabs.userbox': 'Userbox', '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', @@ -140,7 +141,30 @@ export const EN_REF_SETTINGS = { 'settings.profile.unchanged': 'Unchanged', } +export const EN_REF_USERBOX = { + 'userbox.tabs.chusan':'Chuni', + 'userbox.tabs.maimai':'Mai (WIP)', + 'userbox.tabs.ongeki':'Ongeki (WIP)', + 'userbox.nameplate': 'Nameplate', + 'userbox.frame': 'Frame', + 'userbox.trophy': 'Trophy (Title)', + 'userbox.mapicon': 'Map Icon', + 'userbox.voice':'System Voice', + 'userbox.wear':'Avatar Wear', + 'userbox.head':'Avatar Head', + 'userbox.face':'Avatar Face', + 'userbox.skin':'Avatar Skin', + 'userbox.item':'Avatar Item', + 'userbox.front':'Avatar Front', + 'userbox.back':'Avatar Back', + 'userbox.preview.avatar':'Avatar Preview', + 'userbox.preview.nameplate':'Nameplate Preview', + 'userbox.preview.ui':'Interface Preview', + 'userbox.error.noprofile':'No profile was found for this game', + 'userbox.error.nodata':'No data was found for this game', +} + export const EN_REF = { ...EN_REF_USER, ...EN_REF_Welcome, ...EN_REF_GENERAL, - ...EN_REF_LEADERBOARD, ...EN_REF_HOME, ...EN_REF_SETTINGS } + ...EN_REF_LEADERBOARD, ...EN_REF_HOME, ...EN_REF_SETTINGS, ...EN_REF_USERBOX } export type LocalizedMessages = typeof EN_REF diff --git a/AquaNet/src/libs/sdk.ts b/AquaNet/src/libs/sdk.ts index 65af3e75..d9da5612 100644 --- a/AquaNet/src/libs/sdk.ts +++ b/AquaNet/src/libs/sdk.ts @@ -1,162 +1,315 @@ -import { AQUA_HOST, DATA_HOST } from "./config"; -import type { - AllMusic, - Card, - CardSummary, - GenericGameSummary, - GenericRanking, - TrendEntry, - AquaNetUser, GameOption -} from "./generalTypes"; -import type { GameName } from "./scoring"; - -interface RequestInitWithParams extends RequestInit { - params?: { [index: string]: string } - localCache?: boolean -} - -/** - * Modify a fetch url - * - * @param input Fetch url input - * @param callback Callback for modification - */ -export function reconstructUrl(input: URL | RequestInfo, callback: (url: URL) => URL | void): RequestInfo | URL { - let u = new URL((input instanceof Request) ? input.url : input); - const result = callback(u) - if (result) u = result - if (input instanceof Request) { - // @ts-ignore - return { url: u, ...input } - } - return u -} - -/** - * Fetch with url parameters - */ -export function fetchWithParams(input: URL | RequestInfo, init?: RequestInitWithParams): Promise { - return fetch(reconstructUrl(input, u => { - u.search = new URLSearchParams(init?.params ?? {}).toString() - }), init) -} - -let cache: { [index: string]: any } = {} - -export async function post(endpoint: string, params: any, init?: RequestInitWithParams): Promise { - // Add token if exists - const token = localStorage.getItem('token') - if (token && !('token' in params)) params = { ...(params ?? {}), token } - - if (init?.localCache) { - const cached = cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] - if (cached) return cached - } - - let res = await fetchWithParams(AQUA_HOST + endpoint, { - method: 'POST', - params, - ...init - }).catch(e => { - console.error(e) - throw new Error('Network error') - }) - - if (!res.ok) { - const text = await res.text() - console.error(`${res.status}: ${text}`) - - // If 400 invalid token is caught, should invalidate the token and redirect to signin - if (text === 'Invalid token') { - localStorage.removeItem('token') - window.location.href = '/' - } - - // Try to parse as json - let json - try { - json = JSON.parse(text) - } catch (e) { - throw new Error(text) - } - if (json.error) throw new Error(json.error) - } - - const ret = res.json() - cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] = ret - - return ret -} - -/** - * aqua.net.UserRegistrar - * - * @param user - */ -async function register(user: { username: string, email: string, password: string, turnstile: string }) { - return await post('/api/v2/user/register', user) -} -async function login(user: { email: string, password: string, turnstile: string }) { - const data = await post('/api/v2/user/login', user) - - // Put token into local storage - localStorage.setItem('token', data.token) -} - -const isLoggedIn = () => !!localStorage.getItem('token') -const ensureLoggedIn = () => !isLoggedIn() && (window.location.href = '/') - -export const USER = { - register, - login, - confirmEmail: (token: string) => - post('/api/v2/user/confirm-email', { token }), - me: (): Promise => { - ensureLoggedIn() - return post('/api/v2/user/me', {}) - }, - keychip: (): Promise => - post('/api/v2/user/keychip', {}).then(it => it.keychip), - setting: (key: string, value: string) => - post('/api/v2/user/setting', { key: key === 'password' ? 'pwHash' : key, value }), - uploadPfp: (file: File) => { - const formData = new FormData() - formData.append('file', file) - return post('/api/v2/user/upload-pfp', { }, { method: 'POST', body: formData }) - }, - isLoggedIn, - ensureLoggedIn, -} - -export const CARD = { - summary: (cardId: string): Promise<{card: Card, summary: CardSummary}> => - post('/api/v2/card/summary', { cardId }), - link: (props: { cardId: string, migrate: string }) => - post('/api/v2/card/link', props), - unlink: (cardId: string) => - post('/api/v2/card/unlink', { cardId }), - userGames: (username: string): Promise => - post('/api/v2/card/user-games', { username }), -} - -export const GAME = { - trend: (username: string, game: GameName): Promise => - post(`/api/v2/game/${game}/trend`, { username }), - userSummary: (username: string, game: GameName): Promise => - post(`/api/v2/game/${game}/user-summary`, { username }), - ranking: (game: GameName): Promise => - post(`/api/v2/game/${game}/ranking`, { }), - -} - -export const DATA = { - allMusic: (game: GameName): Promise => - fetch(`${DATA_HOST}/d/${game}/00/all-music.json`).then(it => it.json()) -} - -export const SETTING = { - get: (): Promise => - post('/api/v2/settings/get', {}), - set: (key: string, value: any) => - post('/api/v2/settings/set', { key, value: `${value}` }), -} +import { AQUA_HOST, DATA_HOST } from './config' +import type { + AllMusic, + Card, + CardSummary, + GenericGameSummary, + GenericRanking, + TrendEntry, + AquaNetUser, GameOption, + UserBox, + ChangeUserBoxReq, + UserBoxItemKind +} from './generalTypes' +import type { GameName } from './scoring' + +interface RequestInitWithParams extends RequestInit { + params?: { [index: string]: string } + localCache?: boolean +} + +/** + * Modify a fetch url + * + * @param input Fetch url input + * @param callback Callback for modification + */ +export function reconstructUrl(input: URL | RequestInfo, callback: (url: URL) => URL | void): RequestInfo | URL { + let u = new URL((input instanceof Request) ? input.url : input) + const result = callback(u) + if (result) u = result + if (input instanceof Request) { + // @ts-ignore + return { url: u, ...input } + } + return u +} + +/** + * Fetch with url parameters + */ +export function fetchWithParams(input: URL | RequestInfo, init?: RequestInitWithParams): Promise { + return fetch(reconstructUrl(input, u => { + u.search = new URLSearchParams(init?.params ?? {}).toString() + }), init) +} + +const cache: { [index: string]: any } = {} + +export async function post(endpoint: string, params: any, init?: RequestInitWithParams): Promise { + // Add token if exists + const token = localStorage.getItem('token') + if (token && !('token' in params)) params = { ...(params ?? {}), token } + + if (init?.localCache) { + const cached = cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] + if (cached) return cached + } + + const res = await fetchWithParams(AQUA_HOST + endpoint, { + method: 'POST', + params, + ...init + }).catch(e => { + console.error(e) + throw new Error('Network error') + }) + + if (!res.ok) { + const text = await res.text() + console.error(`${res.status}: ${text}`) + + // If 400 invalid token is caught, should invalidate the token and redirect to signin + if (text === 'Invalid token') { + localStorage.removeItem('token') + window.location.href = '/' + } + + // Try to parse as json + let json + try { + json = JSON.parse(text) + } catch (e) { + throw new Error(text) + } + if (json.error) throw new Error(json.error) + } + + const ret = res.json() + cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] = ret + + return ret +} + +export async function get(endpoint: string, params:any,init?: RequestInitWithParams): Promise { + // Add token if exists + const token = localStorage.getItem('token') + + if (init?.localCache) { + const cached = cache[endpoint + JSON.stringify(init)] + if (cached) return cached + } + + const res = await fetchWithParams(AQUA_HOST + endpoint, { + method: 'GET', + params, + ...init + }).catch(e => { + console.error(e) + throw new Error('Network error') + }) + + if (!res.ok) { + const text = await res.text() + console.error(`${res.status}: ${text}`) + + // If 400 invalid token is caught, should invalidate the token and redirect to signin + if (text === 'Invalid token') { + localStorage.removeItem('token') + window.location.href = '/' + } + + // Try to parse as json + let json + try { + json = JSON.parse(text) + } catch (e) { + throw new Error(text) + } + if (json.error) throw new Error(json.error) + } + + const ret = res.json() + cache[endpoint + JSON.stringify(init)] = ret + + return ret +} + +export async function put(endpoint: string, params: any, init?: RequestInitWithParams): Promise { + // Add token if exists + const token = localStorage.getItem('token') + if (token && !('token' in params)) params = { ...(params ?? {}), token } + + if (init?.localCache) { + const cached = cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] + if (cached) return cached + } + + const res = await fetchWithParams(AQUA_HOST + endpoint, { + method: 'PUT', + body: JSON.stringify(params), + headers:{ + 'Content-Type':'application/json', + ...init?.headers + }, + ...init + }).catch(e => { + console.error(e) + throw new Error('Network error') + }) + + if (!res.ok) { + const text = await res.text() + console.error(`${res.status}: ${text}`) + + // If 400 invalid token is caught, should invalidate the token and redirect to signin + if (text === 'Invalid token') { + localStorage.removeItem('token') + window.location.href = '/' + } + + // Try to parse as json + let json + try { + json = JSON.parse(text) + } catch (e) { + throw new Error(text) + } + if (json.error) throw new Error(json.error) + } + + const ret = res.json() + cache[endpoint + JSON.stringify(params) + JSON.stringify(init)] = ret + + return ret +} + +export async function realPost(endpoint: string, params: any, init?: RequestInitWithParams): Promise { + const res = await fetchWithParams(AQUA_HOST + endpoint, { + method: 'POST', + body: JSON.stringify(params), + headers:{ + 'Content-Type':'application/json', + ...init?.headers + }, + ...init + }).catch(e => { + console.error(e) + throw new Error('Network error') + }) + + if (!res.ok) { + const text = await res.text() + console.error(`${res.status}: ${text}`) + + // If 400 invalid token is caught, should invalidate the token and redirect to signin + if (text === 'Invalid token') { + localStorage.removeItem('token') + window.location.href = '/' + } + + // Try to parse as json + let json + try { + json = JSON.parse(text) + } catch (e) { + throw new Error(text) + } + if (json.error) throw new Error(json.error) + } + + return res.json() +} + +/** + * aqua.net.UserRegistrar + * + * @param user + */ +async function register(user: { username: string, email: string, password: string, turnstile: string }) { + return await post('/api/v2/user/register', user) +} +async function login(user: { email: string, password: string, turnstile: string }) { + const data = await post('/api/v2/user/login', user) + + // Put token into local storage + localStorage.setItem('token', data.token) +} + +const isLoggedIn = () => !!localStorage.getItem('token') +const ensureLoggedIn = () => !isLoggedIn() && (window.location.href = '/') + +export const USER = { + register, + login, + confirmEmail: (token: string) => + post('/api/v2/user/confirm-email', { token }), + me: (): Promise => { + ensureLoggedIn() + return post('/api/v2/user/me', {}) + }, + keychip: (): Promise => + post('/api/v2/user/keychip', {}).then(it => it.keychip), + setting: (key: string, value: string) => + post('/api/v2/user/setting', { key: key === 'password' ? 'pwHash' : key, value }), + uploadPfp: (file: File) => { + const formData = new FormData() + formData.append('file', file) + return post('/api/v2/user/upload-pfp', { }, { method: 'POST', body: formData }) + }, + isLoggedIn, + ensureLoggedIn, +} + +export const USERBOX = { + getAimeId:(cardId:string):Promise<{luid:string}|null> =>realPost('/api/sega/aime/getByAccessCode',{ accessCode:cardId }), + getProfile:(aimeId:string):Promise =>get('/api/game/chuni/v2/profile',{ aimeId }), + getUnlockedItems:(aimeId:string, itemId: UserBoxItemKind):Promise<{itemKind:number, itemId:number,stock:number,isValid:boolean}[]> => + get(`/api/game/chuni/v2/item/${itemId}`,{ aimeId }), + getItemLabels:() => { + const kinds = [ 'nameplate', 'frame', 'trophy', 'mapicon', 'sysvoice', 'avatar' ] + + return Promise.all(kinds.map(it => + get(`/api/game/chuni/v2/data/${it}`,{}).then((res:{id:number,name:string}[]) => + // Use the id as the key + res.reduce((acc, cur) => ({ ...acc, [cur.id]: cur.name }), {}) as { [index: number]: string } + ))).then(([ nameplate, frame, trophy, mapicon, sysvoice, avatar ]) => ({ + nameplate, frame, trophy, mapicon, sysvoice, avatar + })) + }, + setUserBox:({ kind,...body }:ChangeUserBoxReq) => + put(`/api/game/chuni/v2/profile/${kind}`, body), +} + +export const CARD = { + summary: (cardId: string): Promise<{card: Card, summary: CardSummary}> => + post('/api/v2/card/summary', { cardId }), + link: (props: { cardId: string, migrate: string }) => + post('/api/v2/card/link', props), + unlink: (cardId: string) => + post('/api/v2/card/unlink', { cardId }), + userGames: (username: string): Promise => + post('/api/v2/card/user-games', { username }), +} + +export const GAME = { + trend: (username: string, game: GameName): Promise => + post(`/api/v2/game/${game}/trend`, { username }), + userSummary: (username: string, game: GameName): Promise => + post(`/api/v2/game/${game}/user-summary`, { username }), + ranking: (game: GameName): Promise => + post(`/api/v2/game/${game}/ranking`, { }), + +} + +export const DATA = { + allMusic: (game: GameName): Promise => + fetch(`${DATA_HOST}/d/${game}/00/all-music.json`).then(it => it.json()) +} + +export const SETTING = { + get: (): Promise => + post('/api/v2/settings/get', {}), + set: (key: string, value: any) => + post('/api/v2/settings/set', { key, value: `${value}` }), +} diff --git a/AquaNet/src/pages/User/Settings.svelte b/AquaNet/src/pages/User/Settings.svelte index 411a335f..a1ba6a60 100644 --- a/AquaNet/src/pages/User/Settings.svelte +++ b/AquaNet/src/pages/User/Settings.svelte @@ -9,6 +9,7 @@ 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"; USER.ensureLoggedIn() @@ -16,7 +17,7 @@ let error: string; let submitting = "" let tab = 0 - const tabs = [ 'profile', 'game' ] + const tabs = [ 'profile', 'game', 'userbox'] const profileFields = [ [ 'displayName', t('settings.profile.name') ], @@ -145,6 +146,9 @@ {/each} + {:else if tab === 2} + + {/if}