mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-10-04 12:48:08 +03:00
feat: add github support, remove easter egg and declutter
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { t } from "../libs/i18n";
|
||||
|
||||
const tabs: Record<string, string> = {
|
||||
[t('home.nav.portal')]: `/home`,
|
||||
[t('home.nav.link-card')]: `/cards`,
|
||||
[t('home.nav.game-setup')]: `/setup`,
|
||||
[t('home.nav.support')]: `/support`
|
||||
}
|
||||
</script>
|
||||
|
||||
<nav class="tabs">
|
||||
{#each Object.entries(tabs) as t, i}
|
||||
<a class="clickable"
|
||||
href={t[1]}
|
||||
class:active={
|
||||
new URL(location.href).pathname === t[1]
|
||||
}>
|
||||
{t[0]}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
.tabs
|
||||
display: flex
|
||||
gap: 1rem
|
||||
|
||||
div
|
||||
&.active
|
||||
color: vars.$c-main
|
||||
</style>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user