Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aquabox improvements #99

Merged
merged 12 commits into from
Jan 5, 2025
2 changes: 2 additions & 0 deletions AquaNet/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ dist-ssr
!.yarn/releases
!.yarn/sdks
!.yarn/versions

public/chu3
161 changes: 161 additions & 0 deletions AquaNet/src/components/settings/ChuniMatchingSettings.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<script lang="ts">
import { fade } from "svelte/transition";
import { CHU3_MATCHINGS } from "../../libs/config.js";
import type { ChusanMatchingOption, GameOption } from "../../libs/generalTypes.js";
import { t } from "../../libs/i18n.js";
import { SETTING } from "../../libs/sdk.js";
import StatusOverlays from "../StatusOverlays.svelte";
import GameSettingFields from "./GameSettingFields.svelte";

let custom = false
let overlay = false
let loading = false
let error = ""

let existingUrl = ""
SETTING.get().then(s => {
existingUrl = s.filter(it => it.key === 'chusanMatchingServer')[0]?.value

if (existingUrl && !CHU3_MATCHINGS.some(it => it.matching === existingUrl)) {
custom = true
}
})

// Click on "Custom" option"
function clickCustom() {
custom = true
overlay = false
}

// Click on a matching option, set the reflector and matching server
function clickOption(opt: ChusanMatchingOption) {
Promise.all([
SETTING.set('chusanMatchingReflector', opt.reflector),
SETTING.set('chusanMatchingServer', opt.matching),
]).then(() => {
overlay = false
custom = false
existingUrl = opt.matching
}).catch(e => error = e.message)
}
</script>

<StatusOverlays {error} {loading}/>

<div class="matching">
<h2>{t("userbox.header.matching")}</h2>
<p class="notice">{t("settings.cabNotice")}</p>

<div class="matching-selector">
<button on:click={_ => overlay = true}>{t('userbox.matching.select')}</button>
</div>

{#if custom}
<GameSettingFields game="chu3-matching"/>
{/if}
</div>

{#if overlay}
<div class="overlay" transition:fade>
<div>
<div>
<h2>{t('userbox.header.matching')}</h2>
<p>{t('userbox.matching.select.sub')}</p>
</div>
<div class="options">
<!-- Selectable options -->
{#each CHU3_MATCHINGS as option}
<div class="clickable option" on:click={() => clickOption(option)}
role="button" tabindex="0" on:keypress={e => e.key === 'Enter' && clickOption(option)}
class:selected={!custom && existingUrl === option.matching}>

<span class="name">{option.name}</span>
<div class="links">
<a href={option.ui} target="_blank" rel="noopener">{t('userbox.matching.option.ui')}</a> /
<a href={option.guide} target="_blank" rel="noopener">{t('userbox.matching.option.guide')}</a>
</div>

<div class="divider"></div>

<div class="coop">
<span>{t('userbox.matching.option.collab')}</span>
<div>
{#each option.coop as coop}
<span>{coop}</span>
{/each}
</div>
</div>
</div>
{/each}

<!-- Placeholder option for "Custom" -->
<div class="clickable option" on:click={clickCustom}
role="button" tabindex="0" on:keypress={e => e.key === 'Enter' && clickCustom()}
class:selected={custom}>

<span class="name">{t('userbox.matching.custom.name')}</span>
<p class="notice custom">{t('userbox.matching.custom.sub')}</p>
</div>
</div>
</div>
</div>
{/if}

<style lang="sass">
@use "../../vars"

.matching
display: flex
flex-direction: column
gap: 12px

h2
margin-bottom: 0

p.notice
opacity: 0.6
margin: 0

&.custom
font-size: 0.9rem

.options
display: flex
flex-wrap: wrap
gap: 1rem

.option
flex: 1
display: flex
flex-direction: column
align-items: center

border-radius: vars.$border-radius
background: vars.$ov-light
padding: 1rem
min-width: 150px

&.selected
border: 1px solid vars.$c-main

.divider
width: 100%
height: 0.5px
background: white
opacity: 0.2
margin: 0.8rem 0

.name
font-size: 1.1rem
font-weight: bold

.coop
text-align: center

div
display: flex
flex-direction: column
font-size: 0.9rem
opacity: 0.6

</style>
136 changes: 89 additions & 47 deletions AquaNet/src/components/settings/ChuniSettings.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
} from "../../libs/generalTypes";
import { DATA, USER, USERBOX } from "../../libs/sdk";
import { t, ts } from "../../libs/i18n";
import { DATA_HOST, FADE_IN, FADE_OUT, HAS_USERBOX_ASSETS } from "../../libs/config";
import { DATA_HOST, FADE_IN, FADE_OUT, USERBOX_DEFAULT_URL } from "../../libs/config";
import { fade, slide } from "svelte/transition";
import StatusOverlays from "../StatusOverlays.svelte";
import Icon from "@iconify/svelte";
Expand All @@ -23,6 +23,7 @@

import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
import { DDS } from "../../libs/userbox/dds";
import ChuniMatchingSettings from "./ChuniMatchingSettings.svelte";

let user: AquaNetUser
let [loading, error, submitting, preview] = [true, "", "", ""]
Expand Down Expand Up @@ -96,9 +97,11 @@

let USERBOX_PROGRESS = 0;
let USERBOX_SETUP_RUN = false;
let USERBOX_SETUP_MODE = false;
let USERBOX_SETUP_TEXT = t("userbox.new.setup");

let USERBOX_ENABLED = useLocalStorage("userboxNew", false);
let USERBOX_PROFILE_ENABLED = useLocalStorage("userboxNewProfile", false);
let USERBOX_INSTALLED = false;
let USERBOX_SUPPORT = "webkitGetAsEntry" in DataTransferItem.prototype;

Expand All @@ -116,13 +119,34 @@
}) ?? "";
}

let USERBOX_URL_STATE = useLocalStorage("userboxURL", USERBOX_DEFAULT_URL);
function userboxHandleInput(baseURL: string, isSetByServer: boolean = false) {
if (baseURL != "")
try {
// validate url
new URL(baseURL, location.href);
} catch(err) {
if (isSetByServer)
return;
return error = t("userbox.new.error.invalidUrl")
}
USERBOX_URL_STATE.value = baseURL;
USERBOX_ENABLED.value = true;
USERBOX_PROFILE_ENABLED.value = true;
location.reload();
}

if (USERBOX_DEFAULT_URL && !USERBOX_URL_STATE.value)
userboxHandleInput(USERBOX_DEFAULT_URL, true);

indexedDB.databases().then(async (dbi) => {
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
if (databaseExists) {
if (databaseExists)
await initializeDb();
if (databaseExists || USERBOX_URL_STATE.value) {
DDSreader = new DDS(ddsDB);
USERBOX_INSTALLED = databaseExists;
}
USERBOX_INSTALLED = databaseExists || USERBOX_URL_STATE.value != "";
}
})

</script>
Expand Down Expand Up @@ -155,9 +179,9 @@
</div>
{:else}
<div class="chuni-userbox-container">
<ChuniUserplateComponent on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level} chuniRating={userbox.playerRating / 100}
<ChuniUserplateComponent chuniIsUserbox={true} on:click={() => userboxSelected = "nameplateId"} chuniCharacter={userbox.characterId} chuniLevel={userbox.level.toString()} chuniRating={userbox.playerRating / 100}
chuniNameplate={userbox.nameplateId} chuniName={userbox.userName} chuniTrophyName={allItems.trophy[userbox.trophyId].name}></ChuniUserplateComponent>
<ChuniPenguinComponent classPassthrough="chuni-penguin-float" chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
<ChuniPenguinComponent chuniWear={userbox.avatarWear} chuniHead={userbox.avatarHead} chuniBack={userbox.avatarBack}
chuniFront={userbox.avatarFront} chuniFace={userbox.avatarFace} chuniItem={userbox.avatarItem}
chuniSkin={userbox.avatarSkin}></ChuniPenguinComponent>
</div>
Expand Down Expand Up @@ -209,60 +233,76 @@
{/each}
</div>
{/if}
{#if HAS_USERBOX_ASSETS}
{#if USERBOX_INSTALLED}
<!-- god this is a mess but idgaf atp -->
<div class="field boolean" style:margin-top="1em">
<input type="checkbox" bind:checked={USERBOX_ENABLED.value} id="newUserbox">
<label for="newUserbox">
<span class="name">{t("userbox.new.activate")}</span>
<span class="desc">{t(`userbox.new.activate_desc`)}</span>
</label>
{#if USERBOX_INSTALLED}
<!-- god this is a mess but idgaf atp -->
<div class="field boolean" style:margin-top="1em">
<input type="checkbox" bind:checked={USERBOX_ENABLED.value} id="newUserbox">
<label for="newUserbox">
<span class="name">{t("userbox.new.activate")}</span>
<span class="desc">{t(`userbox.new.activate_desc`)}</span>
</label>
</div>
<div class="field boolean" style:margin-top="1em">
<input type="checkbox" bind:checked={USERBOX_PROFILE_ENABLED.value} id="newUserboxProfile">
<label for="newUserboxProfile">
<span class="name">{t("userbox.new.activate_profile")}</span>
<span class="desc">{t(`userbox.new.activate_profile_desc`)}</span>
</label>
</div>
{/if}
{#if USERBOX_SUPPORT && !USERBOX_DEFAULT_URL}
<p>
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
</p>
{/if}
<ChuniMatchingSettings/>
<!--{#if !USERBOX_SUPPORT || !USERBOX_INSTALLED || !USERBOX_ENABLED.value}
<h2>{t("userbox.header.preview")}</h2>
<p class="notice">{t("userbox.preview.notice")}</p>
<input bind:value={preview} placeholder={t("userbox.preview.url")}/>
{#if preview}
<div class="preview">
{#each userItems.filter(v => v.iKey != 'trophy' && v.iKey != 'systemVoice') as { iKey, ubKey, items }, i}
<div>
<span>{ts(`userbox.${ubKey}`)}</span>
<img src={`${preview}/${iKey}/${userbox[ubKey].toString().padStart(8, '0')}.png`} alt="" on:error={coverNotFound} />
</div>
{/each}
</div>
{/if}
{#if USERBOX_SUPPORT}
<p>
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
</p>
{/if}
{#if !USERBOX_SUPPORT || !USERBOX_INSTALLED || !USERBOX_ENABLED.value}
<h2>{t("userbox.header.preview")}</h2>
<p class="notice">{t("userbox.preview.notice")}</p>
<input bind:value={preview} placeholder={t("userbox.preview.url")}/>
{#if preview}
<div class="preview">
{#each userItems.filter(v => v.iKey != 'trophy' && v.iKey != 'systemVoice') as { iKey, ubKey, items }, i}
<div>
<span>{ts(`userbox.${ubKey}`)}</span>
<img src={`${preview}/${iKey}/${userbox[ubKey].toString().padStart(8, '0')}.png`} alt="" on:error={coverNotFound} />
</div>
{/each}
</div>
{/if}
{/if}
{/if}
{/if}-->
</div>
{/if}

{#if USERBOX_SETUP_RUN && !error}
<div class="overlay" transition:fade>
<div>
<h2>{t('userbox.new.name')}</h2>
<span>{USERBOX_SETUP_TEXT}</span>
<span>{USERBOX_SETUP_MODE ? t('userbox.new.url_warning') : USERBOX_SETUP_TEXT}</span>
<div class="actions">
{#if USERBOX_PROGRESS != 0}
<div class="progress">
<div class="progress-bar" style="width: {USERBOX_PROGRESS}%"></div>
</div>
{#if USERBOX_SETUP_MODE}
<input type="text" on:keyup={e => {if (e.key == "Enter") userboxHandleInput((e.target as HTMLInputElement).value)}} class="add-margin" placeholder="Base URL">
{:else}
<button class="drop-btn">
<input type="file" on:input={userboxSafeDrop} on:click={e => e.preventDefault()}>
{t('userbox.new.drop')}
</button>
<p class="notice add-margin">
{t('userbox.new.setup.notice')}
</p>
{#if USERBOX_PROGRESS != 0}
<div class="progress">
<div class="progress-bar" style="width: {USERBOX_PROGRESS}%"></div>
</div>
{:else}
<button class="drop-btn">
<input type="file" on:input={userboxSafeDrop} on:click={e => e.preventDefault()}>
{t('userbox.new.drop')}
</button>
{/if}
{/if}
<button on:click={() => USERBOX_SETUP_RUN = false}>
{t('back')}
</button>
{/if}
<button on:click={() => USERBOX_SETUP_MODE = !USERBOX_SETUP_MODE}>
{t(USERBOX_SETUP_MODE ? 'userbox.new.switch.to_drop' : 'userbox.new.switch.to_url')}
</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -296,13 +336,15 @@ p.notice
border-radius: 25px


.add-margin, .drop-btn
margin-bottom: 1em

.drop-btn
position: relative
width: 100%
aspect-ratio: 3
background: transparent
box-shadow: 0 0 1px 1px vars.$ov-lighter
margin-bottom: 1em

> input
position: absolute
Expand Down
Loading