mirror of
https://github.com/hykilpikonna/AquaDX.git
synced 2026-04-24 23:07:22 -05:00
feat: add github support, remove easter egg and declutter
This commit is contained in:
parent
1b5601db45
commit
c2ac01d940
|
|
@ -7,3 +7,4 @@ VITE_TURNSTILE_SITE_KEY=0x4AAAAAAASGA2KQEIelo9P9
|
|||
VITE_DISCORD_INVITE=https://discord.gg/FNgveqFF7s
|
||||
VITE_TELEGRAM_INVITE=https://t.me/+zBL4RZdyfvUzZGU1
|
||||
VITE_QQ_INVITE=https://qm.qq.com/q/dpYmGoVHnG
|
||||
VITE_GITHUB_REPOSITORY=https://github.com/MewoLab/AquaDX
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
|
||||
Happy April Fools!
|
||||
This theme will stay here.
|
||||
Note that I made it with Stylish in mind, it's quite jank.
|
||||
|
||||
*/
|
||||
* {
|
||||
font-family: "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, "MS Pゴシック",
|
||||
"MS P Gothic", sans-serif;
|
||||
}
|
||||
nav > a,
|
||||
nav > *.active,
|
||||
.setting-icon path {
|
||||
color: unset !important;
|
||||
}
|
||||
.aqua-tooltip {
|
||||
background: black;
|
||||
}
|
||||
.fw-block {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
#app {
|
||||
background: url(/assets/theme/cn/logo.bin),
|
||||
#f9f9db;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 4px;
|
||||
max-width: 528px !important;
|
||||
margin: 0 auto;
|
||||
padding: 100px 0 0 0 !important;
|
||||
height: unset !important;
|
||||
box-shadow: -8px 0 0 0 #fdd500, -12px 0 0 0 #f9f9db, 8px 0 0 0 #fdd500,
|
||||
12px 0 0 0 #f9f9db;
|
||||
}
|
||||
nav:has(.logo) {
|
||||
position: absolute !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% - 96px);
|
||||
}
|
||||
nav {
|
||||
color: black;
|
||||
}
|
||||
.user-pfp {
|
||||
margin-top: -56px !important;
|
||||
}
|
||||
.outer-title-options,
|
||||
.outer-title-options *,
|
||||
nav.tabs {
|
||||
color: white !important;
|
||||
}
|
||||
.outer-title-options {
|
||||
margin-top: 0 !important;
|
||||
display: unset !important;
|
||||
}
|
||||
.outer-title-options h2 {
|
||||
width: 460px;
|
||||
position: relative;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0 0 10px 0 !important;
|
||||
background: url(/assets/theme/cn/header.bin);
|
||||
}
|
||||
.chuni-userbox-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chuni-userbox button {
|
||||
width: calc(100% / 4) !important;
|
||||
font-size: 0px;
|
||||
}
|
||||
.chuni-userbox-row button {
|
||||
width: unset !important;
|
||||
flex: 0 1 calc(100% / 3) !important;
|
||||
}
|
||||
.chuni-userbox-row button img {
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
}
|
||||
.chuni-nameplate {
|
||||
background: none !important;
|
||||
position: relative !important;
|
||||
left: 20px;
|
||||
}
|
||||
.chuni-userbox {
|
||||
background: none !important;
|
||||
}
|
||||
main {
|
||||
max-width: calc(460px - 40px) !important;
|
||||
margin: 16px auto 0 auto !important;
|
||||
background: #2c4056 !important;
|
||||
border-radius: unset !important;
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
main:has(.user-pfp) {
|
||||
margin: 64px auto 0 auto !important;
|
||||
}
|
||||
.rating-composition {
|
||||
display: flex !important;
|
||||
flex-wrap: wrap;
|
||||
gap: 0 !important;
|
||||
}
|
||||
.rating-composition > div {
|
||||
width: 47.5%;
|
||||
margin: 1.25%;
|
||||
}
|
||||
.map-detail-container {
|
||||
background: none !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.lv {
|
||||
border-radius: 0 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 !important;
|
||||
width: 50px !important;
|
||||
}
|
||||
.rank-text {
|
||||
min-width: 20px !important;
|
||||
}
|
||||
.chuni-userbox-container {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.profile-bio-text {
|
||||
white-space: unset !important;
|
||||
}
|
||||
.chuni-penguin-container {
|
||||
padding: 64px 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(249, 249, 219, 1) 0%,
|
||||
rgba(249, 249, 219, 1) 69%,
|
||||
rgba(231, 231, 202, 1) 70%,
|
||||
rgba(231, 231, 202, 1) 100%
|
||||
);
|
||||
}
|
||||
body {
|
||||
background: #fdd500 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
#app {
|
||||
background-position: 50% 60px !important;
|
||||
padding-top: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1028px) {
|
||||
#app {
|
||||
background-size: 90%;
|
||||
}
|
||||
|
||||
.user-pfp {
|
||||
margin-top: -36px !important;
|
||||
}
|
||||
.user-pfp nav {
|
||||
top: -10px !important;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB |
|
|
@ -16,6 +16,7 @@
|
|||
import Communities from "./pages/Home/Communities.svelte";
|
||||
import LinkCard from "./pages/Home/LinkCard.svelte";
|
||||
import SetupInstructions from "./pages/Home/SetupInstructions.svelte";
|
||||
import PageNotFound from "./pages/PageNotFound.svelte";
|
||||
|
||||
console.log(`%c
|
||||
┏━┓ ┳━┓━┓┏━
|
||||
|
|
@ -41,15 +42,6 @@
|
|||
playedMai = !!game.mai2
|
||||
})
|
||||
}).catch(e => console.error(e))
|
||||
|
||||
const themeStyle = document.createElement("link");
|
||||
themeStyle.rel = "stylesheet";
|
||||
switch (localStorage.getItem("theme")) {
|
||||
case "cn":
|
||||
themeStyle.href = "/assets/theme/cn.css";
|
||||
};
|
||||
if (themeStyle.href)
|
||||
document.head.appendChild(themeStyle);
|
||||
}
|
||||
let path = window.location.pathname;
|
||||
</script>
|
||||
|
|
@ -93,8 +85,10 @@
|
|||
<Route path="/u/:username" component={UserHome} />
|
||||
<Route path="/u/:username/:game" component={UserHome} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/settings/:page" component={Settings} />
|
||||
<Route path="/pictures" component={MaiPhoto} />
|
||||
<Route path="/transfer" component={Transfer} />
|
||||
<Route component={PageNotFound} />
|
||||
</Router>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
|||
|
|
@ -381,6 +381,4 @@ nav
|
|||
position: absolute
|
||||
padding: 4px 8px
|
||||
background: vars.$ov-lighter
|
||||
backdrop-filter: blur(5px)
|
||||
|
||||
|
||||
backdrop-filter: blur(5px)
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
export let color: string = '179, 198, 255'
|
||||
export let icon: string
|
||||
export let href: string | undefined
|
||||
export let href: string | undefined = undefined
|
||||
export let isSmall: boolean = false
|
||||
|
||||
// Manually positioned icons
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
font-size: 1.2rem
|
||||
display: block
|
||||
margin-bottom: 0.5rem
|
||||
color: color-mix(in oklab, rgb(var(--card-color)) 25%, rgba(255, 255, 255, 0.75))
|
||||
|
||||
.icon
|
||||
position: absolute
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { t } from "../../libs/i18n";
|
||||
import { t } from "../libs/i18n";
|
||||
|
||||
const tabs: Record<string, string> = {
|
||||
[t('home.nav.portal')]: `/home`,
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
</nav>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
@use "../vars"
|
||||
.tabs
|
||||
display: flex
|
||||
gap: 1rem
|
||||
|
|
@ -242,16 +242,6 @@
|
|||
link.click();
|
||||
}
|
||||
|
||||
function g(v: string) {
|
||||
if (v != ("\x63\x68\x75\x6E\x69\x74\x68\x6D ").repeat(3).trim()) return;
|
||||
const t = v.substring(5, 6) + v.substring(1, 2) + "eme";
|
||||
if (!localStorage.getItem(t)) {
|
||||
localStorage.setItem(t, v.substring(0, 1) + "\x6E");
|
||||
} else
|
||||
localStorage.removeItem(t);
|
||||
setTimeout(location.reload, 1000); // ?
|
||||
}
|
||||
|
||||
let DDSreader: DDS | undefined;
|
||||
|
||||
let USERBOX_PROGRESS = 0;
|
||||
|
|
@ -316,8 +306,7 @@
|
|||
|
||||
<StatusOverlays {error} loading={loading || !!submitting} />
|
||||
{#if !loading && !error}
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<h2>{t("userbox.header.general")}</h2>
|
||||
<div>
|
||||
<div class="general-options">
|
||||
<GameSettingFields game="chu3"/>
|
||||
|
||||
|
|
@ -477,10 +466,6 @@
|
|||
input
|
||||
width: 100%
|
||||
|
||||
|
||||
h2
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
.general-options
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
const rounding = useLocalStorage("rounding", true);
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
<div class="fields">
|
||||
<blockquote class="info">
|
||||
{ts("settings.siteNotice")}
|
||||
</blockquote>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { t } from "../../libs/i18n.js";
|
||||
import Icon from "@iconify/svelte";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
import { GAME } from "../../libs/sdk";
|
||||
import { GAME, USER } from "../../libs/sdk";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { download } from "../../libs/ui";
|
||||
|
||||
|
|
@ -12,15 +12,15 @@
|
|||
['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)
|
||||
USER.me().then(me =>
|
||||
GAME.userSummary(me.username, 'mai2').then(({name}) => {
|
||||
values = [name]
|
||||
}).catch(e => error = e.message));
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
if (submitting) return
|
||||
|
|
@ -112,14 +112,14 @@
|
|||
}
|
||||
try {
|
||||
musicData = await fetch(`${DATA_HOST}/d/mai2/00/all-music.json`).then(res => res.json())
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
error = e.message;
|
||||
submitting = ""
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data = await GAME.export('mai2');
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
error = e.message;
|
||||
submitting = ""
|
||||
return;
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<div class="fields">
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
<label for={field}>{name}</label>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<div>
|
||||
<GameSettingFields game="ongeki"/>
|
||||
</div>
|
||||
|
|
|
|||
233
AquaNet/src/components/settings/UserSettings.svelte
Normal file
233
AquaNet/src/components/settings/UserSettings.svelte
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
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 Cropper from "svelte-easy-crop";
|
||||
|
||||
let me: AquaNetUser;
|
||||
let error: string;
|
||||
let submitting = ""
|
||||
let loading = false;
|
||||
|
||||
const profileFields = [
|
||||
[ 'displayName', t('settings.profile.name') ],
|
||||
[ 'username', t('settings.profile.username') ],
|
||||
[ 'password', t('settings.profile.password') ],
|
||||
[ 'profileBio', t('settings.profile.bio') ],
|
||||
] as const
|
||||
|
||||
// Fetch user data
|
||||
const getMe = () => {
|
||||
loading = true;
|
||||
USER.me().then((m) => {
|
||||
if (pfpCropURL != null) {
|
||||
URL.revokeObjectURL(pfpCropURL);
|
||||
pfpField.value = "";
|
||||
pfpCropURL = null;
|
||||
}; me = m;
|
||||
loading = false;
|
||||
}).catch(e => error = e.message)
|
||||
}
|
||||
getMe();
|
||||
|
||||
let changed: string[] = []
|
||||
let pfpField: HTMLInputElement
|
||||
let pfpCropURL: string | null = null;
|
||||
let pfpCrop = { width: 0, height: 0, x: 0, y: 0 };
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
USER.setting(field, value).then(() => {
|
||||
changed = changed.filter(c => c !== field)
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
}
|
||||
|
||||
function uploadPfp() {
|
||||
if (submitting) return
|
||||
let canvas = document.createElement("canvas");
|
||||
let ctx = canvas.getContext("2d");
|
||||
const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024));
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
let img = document.createElement("img");
|
||||
img.onload = () => {
|
||||
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size);
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) return;
|
||||
submitting = 'profilePicture'
|
||||
USER.uploadPfp(blob as File).then(() => {
|
||||
me.profilePicture = me.username
|
||||
setTimeout(getMe, 200);
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
});
|
||||
}
|
||||
img.src = pfpCropURL ?? "";
|
||||
}
|
||||
function handlePfpUpload(e: Event & { currentTarget: HTMLInputElement }) {
|
||||
if (!e.target) return;
|
||||
let files = e?.currentTarget?.files;
|
||||
if (!files || files.length <= 0) return;
|
||||
let file = files[0];
|
||||
console.log(me.username, me);
|
||||
switch (file.type) {
|
||||
case "image/gif":
|
||||
USER.uploadPfp(file).then(() => {
|
||||
me.profilePicture = me.username
|
||||
// reload
|
||||
setTimeout(getMe, 200);
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
break;
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/webp":
|
||||
pfpCropURL = URL.createObjectURL(file);
|
||||
break;
|
||||
default:
|
||||
error = t("settings.profile.bad-format");
|
||||
}
|
||||
};
|
||||
function logOut() {
|
||||
localStorage.removeItem("token");
|
||||
location.href = "/";
|
||||
}
|
||||
|
||||
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
||||
if (whether) node.type = 'password'
|
||||
}
|
||||
</script>
|
||||
<StatusOverlays {error} loading={!!submitting || loading} />
|
||||
{#if !submitting && !error && me}
|
||||
<div>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="profile-upload">{t('settings.profile.picture')}</label>
|
||||
<div>
|
||||
{#if me && me.profilePicture}
|
||||
<div on:click={() => pfpField.click()} on:keydown={e => e.key === 'Enter' && pfpField.click()}
|
||||
role="button" tabindex="0" class="clickable">
|
||||
<img use:pfp={me} alt="Profile" />
|
||||
</div>
|
||||
{:else}
|
||||
<button on:click={() => pfpField.click()}>
|
||||
{t('settings.profile.upload-new')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<input id="profile-upload" type="file" accept="image/gif,image/png,image/jpeg,image/webp" hidden bind:this={pfpField}
|
||||
on:change={handlePfpUpload} />
|
||||
</div>
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
<label for={field}>{name}</label>
|
||||
<div>
|
||||
{#if field == "profileBio"}
|
||||
<textarea id={field} bind:value={me[field]} on:input={() => changed = [...changed, field]} maxlength=255 placeholder={t('settings.profile.unset')}></textarea>
|
||||
{:else}
|
||||
<input id={field} type="text" use:passwordAction={field === 'password'}
|
||||
bind:value={me[field]} on:input={() => changed = [...changed, field]}
|
||||
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||
{/if}
|
||||
|
||||
{#if changed.includes(field) && me[field]}
|
||||
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, me[field])}>
|
||||
{#if submitting === field}
|
||||
<Icon icon="line-md:loading-twotone-loop" />
|
||||
{:else}
|
||||
{t('settings.profile.save')}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="field m-t">
|
||||
<div class="bool">
|
||||
<input id="optOutOfLeaderboard" type="checkbox" bind:checked={me.optOutOfLeaderboard}
|
||||
on:change={() => submit('optOutOfLeaderboard', me.optOutOfLeaderboard.toString())}/>
|
||||
<label for="optOutOfLeaderboard">
|
||||
<span class="name">{ts(`settings.fields.optOutOfLeaderboard.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.optOutOfLeaderboard.desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field m-t">
|
||||
<div>
|
||||
<button on:click={logOut}>{ts(`settings.profile.logout`)}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if pfpCropURL != null}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<div class="cropper-container">
|
||||
<Cropper maxZoom={1e9} oncropcomplete={(e) => pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"></Cropper>
|
||||
</div>
|
||||
<button on:click={uploadPfp}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
<button on:click={getMe}>
|
||||
{t("back")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.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, > textarea
|
||||
flex: 1
|
||||
|
||||
img
|
||||
max-width: 100px
|
||||
max-height: 100px
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
aspect-ratio: 1
|
||||
|
||||
|
||||
|
||||
.cropper-container
|
||||
position: relative
|
||||
width: 400px
|
||||
aspect-ratio: 1
|
||||
</style>
|
||||
|
|
@ -4,6 +4,6 @@
|
|||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
<div>
|
||||
<GameSettingFields game="wacca"/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@
|
|||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2 class="error">{t('status.error')}</h2>
|
||||
{#if !expected}
|
||||
<span>{t('status.error.hint')}<a href={DISCORD_INVITE}>{t('status.error.hint.link')}</a></span>
|
||||
{/if}
|
||||
<span class="detail">{error}</span>
|
||||
|
||||
<a class="hint" href="/support">{t("status.error.hint")}</a>
|
||||
<div class="actions">
|
||||
<button on:click={() => location.reload()} class="error">
|
||||
{t('action.refresh')}
|
||||
</button>
|
||||
<button on:click={() => location.href = "/home"} class="error">
|
||||
{t('action.home')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -26,14 +26,19 @@
|
|||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
gap: 0.5em
|
||||
|
||||
button
|
||||
width: 100%
|
||||
|
||||
.detail
|
||||
white-space: pre-line
|
||||
font-size: 0.9em
|
||||
line-height: 1.2
|
||||
opacity: 0.8
|
||||
|
||||
.hint
|
||||
font-size: 0.875em
|
||||
|
||||
.overlay > div
|
||||
min-width: 15rem
|
||||
max-width: 25rem
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY
|
|||
export const DISCORD_INVITE = import.meta.env.VITE_DISCORD_INVITE
|
||||
export const TELEGRAM_INVITE = import.meta.env.VITE_TELEGRAM_INVITE
|
||||
export const QQ_INVITE = import.meta.env.VITE_QQ_INVITE
|
||||
export const GITHUB_REPOSITORY = import.meta.env.VITE_GITHUB_REPOSITORY
|
||||
|
||||
// UI
|
||||
export const FADE_OUT = { duration: 200 }
|
||||
|
|
|
|||
|
|
@ -77,17 +77,19 @@ export const EN_REF_GENERAL = {
|
|||
'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.error.hint': 'Support',
|
||||
'status.detail': 'Detail: ${detail}',
|
||||
'action.refresh': 'Refresh',
|
||||
'action.refresh': 'Retry',
|
||||
'action.cancel': 'Cancel',
|
||||
'action.confirm': 'Confirm',
|
||||
'action.home': 'Home',
|
||||
'navigation.profile': 'Profile',
|
||||
'navigation.maps': 'Maps',
|
||||
'navigation.home': 'Home',
|
||||
'navigation.rankings': 'Rankings',
|
||||
'navigation.notice': 'Notice'
|
||||
'navigation.notice': 'Notice',
|
||||
'loading': `Please wait...`,
|
||||
'404': 'Page not found (${pathname}). If this is in error, please report it via an appropriate support channel.'
|
||||
}
|
||||
|
||||
export const EN_REF_HOME = {
|
||||
|
|
@ -102,7 +104,7 @@ export const EN_REF_HOME = {
|
|||
'home.manage-cards-description': 'Link, unlink, and manage your cards.',
|
||||
'home.link-card': 'Link Card',
|
||||
'home.link-cards-description': 'Link your Amusement IC / Aime card to play games.',
|
||||
'home.join-community': 'Join Community',
|
||||
'home.join-community': 'Community & Support',
|
||||
'home.join-community-description': 'Join our community for support and chatting with other players.',
|
||||
'home.setup': 'Setup Network Connection',
|
||||
'home.setup-description': 'Configure a game to connect to our servers.',
|
||||
|
|
@ -131,13 +133,13 @@ export const EN_REF_HOME = {
|
|||
'home.community.discord': 'Discord',
|
||||
'home.community.telegram': 'Telegram (Chinese)',
|
||||
'home.community.qq': 'QQ (Chinese)',
|
||||
'home.community.github': 'GitHub Repository',
|
||||
'home.import.unknown-game': 'Unknown game type. Currently only Mai and Chuni are supported for importing.',
|
||||
'home.import.new-data': 'Data to import',
|
||||
'home.import.data-conflict': 'Proceed will override your current data',
|
||||
}
|
||||
|
||||
export const EN_REF_SETUP = {
|
||||
'loading': `Please wait...`,
|
||||
'setup.welcome': `Welcome! If you have a game set up, please follow the instructions below to set up the connection with AquaDX.`,
|
||||
'setup.keychip-warning': `Your keychip is linked to your account and should be kept secure. Do not give others your keychip.`,
|
||||
'setup.steps.one': `Pick a method of setting up network communications. Some browsers may not be able to do automatic setup.`,
|
||||
|
|
@ -157,6 +159,7 @@ export const EN_REF_SETUP = {
|
|||
|
||||
export const EN_REF_SETTINGS = {
|
||||
'settings.title': 'Settings',
|
||||
'settings.page-title': '${page} Settings',
|
||||
'settings.tabs.profile': 'Profile',
|
||||
'settings.tabs.global': 'Global',
|
||||
'settings.tabs.chu3': 'Chuni',
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import ActionCard from "../components/ActionCard.svelte";
|
||||
import { t } from "../libs/i18n";
|
||||
import ImportDataAction from "./Home/ImportDataAction.svelte";
|
||||
import DashboardTabs from "./Home/DashboardTabs.svelte";
|
||||
import DashboardTabs from "../components/DashboardTabs.svelte";
|
||||
|
||||
USER.ensureLoggedIn();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,30 +3,36 @@
|
|||
<script lang="ts">
|
||||
import { t } from "../../libs/i18n";
|
||||
import CommunityCard from "../../components/CommunityCard.svelte";
|
||||
import { DISCORD_INVITE, QQ_INVITE, TELEGRAM_INVITE } from "../../libs/config";
|
||||
import DashboardTabs from "./DashboardTabs.svelte";
|
||||
import { DISCORD_INVITE, GITHUB_REPOSITORY, QQ_INVITE, TELEGRAM_INVITE } from "../../libs/config";
|
||||
import DashboardTabs from "../../components/DashboardTabs.svelte";
|
||||
</script>
|
||||
|
||||
<main class="content">
|
||||
<DashboardTabs />
|
||||
<div class="setup-instructions">
|
||||
<h2>{t('home.join-community')}</h2>
|
||||
<div class="grid cols-3 gap-4">
|
||||
<div class="communities grid cols-2 gap-4">
|
||||
{#if DISCORD_INVITE}
|
||||
<CommunityCard color="82, 93, 233" icon="ic:baseline-discord" href={DISCORD_INVITE}>
|
||||
<h3>{t('home.community.discord')}</h3>
|
||||
<span>{t('home.community.discord')}</span>
|
||||
</CommunityCard>
|
||||
{/if}
|
||||
|
||||
{#if TELEGRAM_INVITE}
|
||||
<CommunityCard color="46, 163, 224" icon="mingcute:telegram-fill" href={TELEGRAM_INVITE}>
|
||||
<h3>{t('home.community.telegram')}</h3>
|
||||
<span>{t('home.community.telegram')}</span>
|
||||
</CommunityCard>
|
||||
{/if}
|
||||
|
||||
{#if QQ_INVITE}
|
||||
<CommunityCard color="226, 60, 68" icon="ri:qq-fill" href={QQ_INVITE}>
|
||||
<h3>{t('home.community.qq')}</h3>
|
||||
<span>{t('home.community.qq')}</span>
|
||||
</CommunityCard>
|
||||
{/if}
|
||||
|
||||
{#if GITHUB_REPOSITORY}
|
||||
<CommunityCard color="245, 245, 249" icon="ri:github-fill" href={GITHUB_REPOSITORY}>
|
||||
<span>{t('home.community.github')}</span>
|
||||
</CommunityCard>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -34,5 +40,6 @@
|
|||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
|
||||
.communities
|
||||
margin: 0 1rem
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import Icon from "@iconify/svelte"
|
||||
import StatusOverlays from "../../components/StatusOverlays.svelte"
|
||||
import { t } from "../../libs/i18n"
|
||||
import DashboardTabs from "./DashboardTabs.svelte";
|
||||
import DashboardTabs from "../../components/DashboardTabs.svelte";
|
||||
|
||||
// State
|
||||
let state: 'ready' | 'linking-AC' | 'linking-SN' | 'loading' = "loading"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { codeToHtml } from 'shiki'
|
||||
import { AQUA_CONNECTION, DISCORD_INVITE, FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import { t } from "../../libs/i18n";
|
||||
import DashboardTabs from "./DashboardTabs.svelte";
|
||||
import DashboardTabs from "../../components/DashboardTabs.svelte";
|
||||
import { patchUserSegatools } from "../../libs/setup";
|
||||
|
||||
let user: AquaNetUser
|
||||
|
|
|
|||
5
AquaNet/src/pages/PageNotFound.svelte
Normal file
5
AquaNet/src/pages/PageNotFound.svelte
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import StatusOverlays from "../components/StatusOverlays.svelte";
|
||||
import { t } from "../libs/i18n";
|
||||
</script>
|
||||
<StatusOverlays error={t("404", {pathname: new URL(location.href).pathname})} />
|
||||
|
|
@ -5,281 +5,58 @@
|
|||
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 Cropper from "svelte-easy-crop";
|
||||
import UserBox from "../../components/settings/ChuniSettings.svelte";
|
||||
import ChuniSettings from "../../components/settings/ChuniSettings.svelte";
|
||||
import Mai2Settings from "../../components/settings/Mai2Settings.svelte";
|
||||
import WaccaSettings from "../../components/settings/WaccaSettings.svelte";
|
||||
import GeneralGameSettings from "../../components/settings/GeneralGameSettings.svelte";
|
||||
import OngekiSettings from "../../components/settings/OngekiSettings.svelte";
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import UserSettings from "../../components/settings/UserSettings.svelte";
|
||||
import type { Component } from "svelte";
|
||||
import { EN_REF } from "../../libs/i18n/en_ref";
|
||||
|
||||
USER.ensureLoggedIn()
|
||||
|
||||
let me: AquaNetUser;
|
||||
let error: string;
|
||||
let submitting = ""
|
||||
let tab = 0
|
||||
let tabs = ['profile']
|
||||
export let page: string = "profile";
|
||||
|
||||
const profileFields = [
|
||||
[ 'displayName', t('settings.profile.name') ],
|
||||
[ 'username', t('settings.profile.username') ],
|
||||
[ 'password', t('settings.profile.password') ],
|
||||
/* Neither of these did anything of importance
|
||||
[ 'country', t('settings.profile.country') ],
|
||||
[ 'profileLocation', t('settings.profile.location') ],*/
|
||||
[ 'profileBio', t('settings.profile.bio') ],
|
||||
] as const
|
||||
|
||||
// Fetch user data
|
||||
const getMe = () => USER.me().then((m) => {
|
||||
if (pfpCropURL != null) {
|
||||
URL.revokeObjectURL(pfpCropURL);
|
||||
pfpField.value = "";
|
||||
pfpCropURL = null;
|
||||
}
|
||||
me = m
|
||||
|
||||
CARD.userGames(m.username).then(games => {
|
||||
tabs = [
|
||||
...tabs,
|
||||
...['chu3', 'mai2','wacca', 'ongeki'].filter(v => games[v as keyof typeof games]), // :xdx:
|
||||
'global'
|
||||
]
|
||||
})
|
||||
}).catch(e => error = e.message)
|
||||
getMe()
|
||||
|
||||
let changed: string[] = []
|
||||
let pfpField: HTMLInputElement
|
||||
let pfpCropURL: string | null = null;
|
||||
let pfpCrop = { width: 0, height: 0, x: 0, y: 0 };
|
||||
|
||||
function submit(field: string, value: string) {
|
||||
if (submitting) return
|
||||
submitting = field
|
||||
|
||||
USER.setting(field, value).then(() => {
|
||||
changed = changed.filter(c => c !== field)
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
const pages: Record<string, Component> = {
|
||||
"profile": UserSettings, "chu3": ChuniSettings,
|
||||
"mai2": Mai2Settings, "wacca": WaccaSettings,
|
||||
"ongeki": OngekiSettings, "global": GeneralGameSettings
|
||||
}
|
||||
|
||||
function uploadPfp() {
|
||||
if (submitting) return
|
||||
// Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame
|
||||
let canvas = document.createElement("canvas");
|
||||
let ctx = canvas.getContext("2d");
|
||||
const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024));
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
let img = document.createElement("img");
|
||||
img.onload = () => {
|
||||
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size);
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) return;
|
||||
submitting = 'profilePicture'
|
||||
USER.uploadPfp(blob as File).then(() => {
|
||||
me.profilePicture = me.username
|
||||
// reload
|
||||
// this doesn't work btw
|
||||
setTimeout(getMe, 200);
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
});
|
||||
}
|
||||
img.src = pfpCropURL ?? "";
|
||||
}
|
||||
function handlePfpUpload(e: Event & { target: HTMLInputElement }) {
|
||||
if (!e.target) return;
|
||||
let files = e?.target?.files;
|
||||
if (!files || files.length <= 0) return;
|
||||
let file = files[0];
|
||||
console.log(me.username, me);
|
||||
switch (file.type) {
|
||||
case "image/gif":
|
||||
USER.uploadPfp(file).then(() => {
|
||||
me.profilePicture = me.username
|
||||
// reload
|
||||
setTimeout(getMe, 200);
|
||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||
break;
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/webp":
|
||||
pfpCropURL = URL.createObjectURL(file);
|
||||
break;
|
||||
default:
|
||||
error = t("settings.profile.bad-format");
|
||||
}
|
||||
};
|
||||
function logOut() {
|
||||
localStorage.removeItem("token");
|
||||
location.href = "/";
|
||||
}
|
||||
if (!pages[page] && page)
|
||||
error = t("404", {pathname: new URL(location.href).pathname});
|
||||
|
||||
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
||||
if (whether) node.type = 'password'
|
||||
}
|
||||
USER.me().then(m => me = m)
|
||||
.catch(e => error = e.message)
|
||||
</script>
|
||||
|
||||
<main class="content">
|
||||
<div class="outer-title-options">
|
||||
<h2>{t('settings.title')}</h2>
|
||||
<nav>
|
||||
{#each tabs as tabName, i}
|
||||
<div transition:slide={{axis: 'x'}} class:active={tab === i}
|
||||
on:click={() => tab = i} on:keydown={e => e.key === 'Enter' && (tab = i)}
|
||||
role="button" tabindex="0">
|
||||
{ts(`settings.tabs.${tabName}`)}
|
||||
</div>
|
||||
{#each Object.entries(pages) as tab}
|
||||
<a href={`/settings/${tab[0] != "profile" ? tab[0] : ""}`} transition:slide={{axis: 'x'}}
|
||||
class:active={tab[0] == page || (tab[0] == "profile" && !page)} role="button" tabindex="0">
|
||||
{ts(`settings.tabs.${tab[0]}`)}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{#if tab === 0 && me}
|
||||
<!-- Tab 0: Profile settings -->
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
<div class="field">
|
||||
<label for="profile-upload">{t('settings.profile.picture')}</label>
|
||||
<div>
|
||||
{#if me && me.profilePicture}
|
||||
<div on:click={() => pfpField.click()} on:keydown={e => e.key === 'Enter' && pfpField.click()}
|
||||
role="button" tabindex="0" class="clickable">
|
||||
<img use:pfp={me} alt="Profile" />
|
||||
</div>
|
||||
{:else}
|
||||
<button on:click={() => pfpField.click()}>
|
||||
{t('settings.profile.upload-new')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Genuinely don't know why this is giving me an intellisense error. Works fine. -->
|
||||
<input id="profile-upload" type="file" accept="image/gif,image/png,image/jpeg,image/webp" style="display: none" bind:this={pfpField}
|
||||
on:change={handlePfpUpload} />
|
||||
</div>
|
||||
<h2 class="header">
|
||||
{t('settings.page-title', {page: ts(`settings.tabs.${page}`)})}
|
||||
</h2>
|
||||
|
||||
{#each profileFields as [field, name], i (field)}
|
||||
<div class="field">
|
||||
<label for={field}>{name}</label>
|
||||
<div>
|
||||
{#if field == "profileBio"}
|
||||
<textarea id={field} bind:value={me[field]} on:input={() => changed = [...changed, field]} maxlength=255 placeholder={t('settings.profile.unset')}></textarea>
|
||||
{:else}
|
||||
<input id={field} type="text" use:passwordAction={field === 'password'}
|
||||
bind:value={me[field]} on:input={() => changed = [...changed, field]}
|
||||
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
|
||||
{/if}
|
||||
|
||||
{#if changed.includes(field) && me[field]}
|
||||
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, me[field])}>
|
||||
{#if submitting === field}
|
||||
<Icon icon="line-md:loading-twotone-loop" />
|
||||
{:else}
|
||||
{t('settings.profile.save')}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="field m-t">
|
||||
<div class="bool">
|
||||
<input id="optOutOfLeaderboard" type="checkbox" bind:checked={me.optOutOfLeaderboard}
|
||||
on:change={() => submit('optOutOfLeaderboard', me.optOutOfLeaderboard.toString())}/>
|
||||
<label for="optOutOfLeaderboard">
|
||||
<span class="name">{ts(`settings.fields.optOutOfLeaderboard.name`)}</span>
|
||||
<span class="desc">{ts(`settings.fields.optOutOfLeaderboard.desc`)}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field m-t">
|
||||
<div>
|
||||
<button on:click={logOut}>{ts(`settings.profile.logout`)}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if tabs[tab] === 'chu3'}
|
||||
<!-- Userbox settings -->
|
||||
<UserBox />
|
||||
{:else if tabs[tab] === 'mai2'}
|
||||
<Mai2Settings username={me.username} />
|
||||
{:else if tabs[tab] === 'wacca'}
|
||||
<WaccaSettings />
|
||||
{:else if tabs[tab] === 'ongeki'}
|
||||
<OngekiSettings />
|
||||
{:else if tabs[tab] === 'global'}
|
||||
<GeneralGameSettings />
|
||||
{#if pages[page]}
|
||||
<svelte:component this={pages[page]} />
|
||||
{/if}
|
||||
|
||||
<StatusOverlays {error} loading={!me || !!submitting} />
|
||||
</main>
|
||||
|
||||
{#if pfpCropURL != null}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<div class="cropper-container">
|
||||
<Cropper maxZoom={1e9} oncropcomplete={(e) => pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"></Cropper>
|
||||
</div>
|
||||
<button on:click={uploadPfp}>
|
||||
{t("settings.profile.save")}
|
||||
</button>
|
||||
<button on:click={getMe}>
|
||||
{t("back")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<StatusOverlays {error} />
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.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, > textarea
|
||||
flex: 1
|
||||
|
||||
img
|
||||
max-width: 100px
|
||||
max-height: 100px
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
aspect-ratio: 1
|
||||
|
||||
|
||||
|
||||
.cropper-container
|
||||
position: relative
|
||||
width: 400px
|
||||
aspect-ratio: 1
|
||||
</style>
|
||||
h2.header
|
||||
margin: 0 0 0.5rem 0
|
||||
</style>
|
||||
|
|
@ -30,15 +30,13 @@
|
|||
registerChart()
|
||||
|
||||
export let username: string;
|
||||
export let game: GameName | "auto" = "auto"
|
||||
export let game: GameName;
|
||||
let calElement: HTMLElement
|
||||
let error: string;
|
||||
let me: AquaNetUser
|
||||
title(`User ${username}`)
|
||||
const rounding = useLocalStorage("rounding", true);
|
||||
|
||||
const titleText = game != "auto" ? GAME_TITLE[game] : "?"
|
||||
|
||||
interface MusicAndPlay extends MusicMeta, GenericGamePlaylog {}
|
||||
|
||||
let d: {
|
||||
|
|
@ -57,7 +55,7 @@
|
|||
USER.isLoggedIn() && USER.me().then(u => me = u)
|
||||
|
||||
CARD.userGames(username).then(games => {
|
||||
if (game == "auto") {
|
||||
if (!game) {
|
||||
let targetGames = Object.entries(games)
|
||||
.map(d => {
|
||||
if (d[1])
|
||||
|
|
@ -66,9 +64,7 @@
|
|||
}).sort((a,b) => {
|
||||
return b[1]?.lastLogin - a[1]?.lastLogin;
|
||||
});
|
||||
if (targetGames[0])
|
||||
window.location.href = `/u/${username}/${targetGames[0][0]}`
|
||||
return;
|
||||
game = targetGames[0][0] as GameName;
|
||||
}
|
||||
if (!games[game]) {
|
||||
// Find a valid game
|
||||
|
|
@ -118,11 +114,10 @@
|
|||
}).catch((e) => { error = e.message; console.error(e) } );
|
||||
}
|
||||
|
||||
if (Object.keys(GAME_TITLE).includes(game) || game == "auto") init()
|
||||
if (Object.keys(GAME_TITLE).includes(game) || !game) init()
|
||||
else error = t("UserHome.InvalidGame", {game})
|
||||
|
||||
const setRival = (isAdd: boolean) => {
|
||||
if (game == "auto") return;
|
||||
isLoading = true
|
||||
GAME.setRival(game, username, isAdd).then(() => {
|
||||
d!.user.rival = isAdd
|
||||
|
|
@ -167,7 +162,7 @@
|
|||
{/each}
|
||||
|
||||
{#if me && me.username === username}
|
||||
<a class="setting-icon clickable" use:tooltip={t("UserHome.Settings")} href="/settings">
|
||||
<a class="setting-icon clickable" use:tooltip={t("UserHome.Settings")} href={`/settings/${game}`}>
|
||||
<Icon icon="eos-icons:rotating-gear"/>
|
||||
</a>
|
||||
{/if}
|
||||
|
|
@ -188,7 +183,7 @@
|
|||
<ChuniUserboxDisplay {game} {username} bind:error={error} />
|
||||
|
||||
<div>
|
||||
<h2>{titleText} {t('UserHome.Statistics')}</h2>
|
||||
<h2>{GAME_TITLE[game] ?? "?"} {t('UserHome.Statistics')}</h2>
|
||||
<div class="scoring-info">
|
||||
<div class="chart">
|
||||
<div class="info-top">
|
||||
|
|
@ -313,16 +308,16 @@
|
|||
|
||||
<!-- I don't like doing this but it may be preferable to gaslighting the types -->
|
||||
|
||||
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} game={game != "auto" ? game : "mai2"}/>
|
||||
<RatingComposition title="B30" comp={d.user.ratingComposition.best30} {allMusics} {game}/>
|
||||
<RatingComposition title="B35" comp={d.user.ratingComposition.best35} {allMusics} {game}/>
|
||||
<RatingComposition title="B15" comp={d.user.ratingComposition.best15} {allMusics} {game}/>
|
||||
<!-- <RatingComposition title="Hot 10" comp={d.user.ratingComposition.hot10} {allMusics} {game}/> -->
|
||||
<!-- <RatingComposition title="N10" comp={d.user.ratingComposition.next10} {allMusics} {game}/> -->
|
||||
<!-- Chuni -->
|
||||
{#if d.user.ratingComposition.new}
|
||||
<RatingComposition title="New 20" comp={d.user.ratingComposition.new} {allMusics} game="chu3"/>
|
||||
{:else}
|
||||
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
|
||||
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} {game} top={10}/>
|
||||
{/if}
|
||||
|
||||
<div class="recent">
|
||||
|
|
@ -344,12 +339,12 @@
|
|||
{r.notes?.[r.level === 10 ? 0 : r.level]?.lv?.toFixed(1) ?? r.worldsEndTag ?? '-'}
|
||||
</span>
|
||||
</span>
|
||||
<span class={`rank-${getMult(r.achievement, game != "auto" ? game : "mai2")[2].toString()[0]}`}>
|
||||
<span class="rank-text">{("" + getMult(r.achievement, game != "auto" ? game : "mai2")[2]).replace("p", "+")}</span>
|
||||
<span class={`rank-${getMult(r.achievement, game)[2].toString()[0]}`}>
|
||||
<span class="rank-text">{("" + getMult(r.achievement, game)[2]).replace("p", "+")}</span>
|
||||
<span class="rank-num" use:tooltip={(r.achievement / 10000).toFixed(4)}>
|
||||
{
|
||||
rounding.value ?
|
||||
roundFloor(r.achievement, game != "auto" ? game : "mai2", 1) :
|
||||
roundFloor(r.achievement, game, 1) :
|
||||
(r.achievement / 10000).toFixed(4)
|
||||
}%
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user