From f4bb1101bf2e3586cbbd968fb84f81d55872ad4a Mon Sep 17 00:00:00 2001 From: alexay7 Date: Wed, 5 Jun 2024 18:57:48 +0200 Subject: [PATCH 1/3] Added UserBox page --- AquaNet/src/app.sass | 11 + AquaNet/src/components/UserBox.svelte | 674 +++++++++++++++++++++++++ AquaNet/src/libs/config.ts | 4 +- AquaNet/src/libs/generalTypes.ts | 65 ++- AquaNet/src/libs/i18n/en_ref.ts | 100 ++-- AquaNet/src/libs/sdk.ts | 477 +++++++++++------ AquaNet/src/pages/User/Settings.svelte | 6 +- 7 files changed, 1134 insertions(+), 203 deletions(-) create mode 100644 AquaNet/src/components/UserBox.svelte 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..42f6b630 --- /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} From 7377386ee227333c6c004aead3705ecc6ee85515 Mon Sep 17 00:00:00 2001 From: alexay7 Date: Wed, 5 Jun 2024 19:43:40 +0200 Subject: [PATCH 2/3] Fix layout breaks when options are large --- AquaNet/src/components/UserBox.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AquaNet/src/components/UserBox.svelte b/AquaNet/src/components/UserBox.svelte index 42f6b630..dd08e81f 100644 --- a/AquaNet/src/components/UserBox.svelte +++ b/AquaNet/src/components/UserBox.svelte @@ -654,10 +654,14 @@ label display: flex flex-direction: column + + select + width: 100% .field display: flex flex-direction: column + width: 100% label max-width: max-content From bca5130020958474ad6bc521eea39f1e98ac83cb Mon Sep 17 00:00:00 2001 From: alexay7 <43906716+alexay7@users.noreply.github.com> Date: Sun, 28 Jul 2024 03:45:22 +0200 Subject: [PATCH 3/3] Modify way to get user luid --- AquaNet/src/components/UserBox.svelte | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/AquaNet/src/components/UserBox.svelte b/AquaNet/src/components/UserBox.svelte index dd08e81f..c5b44edd 100644 --- a/AquaNet/src/components/UserBox.svelte +++ b/AquaNet/src/components/UserBox.svelte @@ -154,14 +154,8 @@ }); } - async function fetchData(card: string) { - const userId = await USERBOX.getAimeId(card); - - if (!userId) return; - - aimeId = userId.luid; - - const currentValues = await USERBOX.getProfile(userId.luid).catch((e) => { + async function fetchData() { + const currentValues = await USERBOX.getProfile(aimeId).catch((e) => { loading = false; error = t("userbox.error.noprofile") return; @@ -195,7 +189,7 @@ await Promise.all( userBoxItems.map(async (kind) => { // Populate info about the items - return USERBOX.getUnlockedItems(userId.luid, kind).then((items) => { + return USERBOX.getUnlockedItems(aimeId, kind).then((items) => { switch (kind) { case UserBoxItemKind.nameplate: // Add the item id and the label to the available options @@ -347,8 +341,10 @@ user = u; const card = user.cards.length > 0 ? user.cards[0].luid : ""; - if (card) { - fetchData(card); + aimeId = card; + + if (aimeId) { + fetchData(); } else { loading = false; }