[F] Fix build warnings

This commit is contained in:
Azalea
2024-12-23 18:56:44 -05:00
parent 9cffb19332
commit dd573945ed
9 changed files with 560 additions and 590 deletions

View File

@@ -1,76 +1,76 @@
<!-- Svelte 4.2.11 --> <!-- Svelte 4.2.11 -->
<script lang="ts"> <script lang="ts">
import Icon from "@iconify/svelte"; import Icon from "@iconify/svelte";
export let color: string = '179, 198, 255' export let color: string = '179, 198, 255'
export let icon: string export let icon: string
// Manually positioned icons // Manually positioned icons
const iconPos = [ const iconPos = [
[1, 0.5, 2], [1, 0.5, 2],
[6, 2, 1.5], [6, 2, 1.5],
[-0.5, 4.5, 1.3], [-0.5, 4.5, 1.3],
[5, -0.5], [5, -0.5],
[3.5, 4.5], [3.5, 4.5],
[9.5, 0.3, 1.2], [9.5, 0.3, 1.2],
[12.5, 2.5, 0.8], [12.5, 2.5, 0.8],
[10, 4.4, 0.8], [10, 4.4, 0.8],
] ]
</script> </script>
<div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown> <div class="action-card" style="--card-color: {color}" on:click role="button" tabindex="0" on:keydown>
<slot/> <slot/>
<div class="icons"> <div class="icons">
{#each iconPos as [x, y, size], i} {#each iconPos as [x, y, size], i}
<Icon icon={icon} style={`top: ${y}rem; right: ${x}rem; font-size: ${size || 1}em`} /> <Icon icon={icon} style={`top: ${y}rem; right: ${x}rem; font-size: ${size || 1}em`} />
{/each} {/each}
</div> </div>
</div> </div>
<style lang="sass"> <style lang="sass">
@use '../vars' @use '../vars'
.action-card .action-card
overflow: hidden overflow: hidden
padding: 1rem padding: 1rem
border-radius: vars.$border-radius border-radius: vars.$border-radius
box-shadow: 0 5px 5px 1px vars.$c-shadow box-shadow: 0 5px 5px 1px vars.$c-shadow
transition: all 0.2s ease transition: all 0.2s ease
cursor: pointer cursor: pointer
position: relative position: relative
background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%) background: linear-gradient(45deg, transparent 20%, rgba(var(--card-color), 0.5) 100%)
outline: 1px solid transparent outline: 1px solid transparent
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0)) filter: drop-shadow(0 0 12px rgba(var(--card-color), 0))
&:hover &:hover
box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow box-shadow: 0 0 0.5rem 0.2rem vars.$c-shadow
transform: translateY(-3px) transform: translateY(-3px)
// Drop shadow glow // Drop shadow glow
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5)) filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
outline-color: rgba(var(--card-color), 0.5) outline-color: rgba(var(--card-color), 0.5)
span :global(span)
font-size: 1.2rem font-size: 1.2rem
display: block display: block
margin-bottom: 0.5rem margin-bottom: 0.5rem
.icons .icons
position: absolute position: absolute
inset: 0 inset: 0
color: rgba(var(--card-color), 0.5) color: rgba(var(--card-color), 0.5)
font-size: 2rem font-size: 2rem
transition: all 0.2s ease transition: all 0.2s ease
z-index: -1 z-index: -1
mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%) mask-image: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.5) 70%, white 100%)
opacity: 0.8 opacity: 0.8
@media (max-width: vars.$w-mobile) @media (max-width: vars.$w-mobile)
opacity: 0.6 opacity: 0.6
:global(> svg) :global(> svg)
position: absolute position: absolute
rotate: 20deg rotate: 20deg
</style> </style>

View File

@@ -39,7 +39,7 @@
filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5)) filter: drop-shadow(0 0 12px rgba(var(--card-color), 0.5))
outline-color: rgba(var(--card-color), 0.5) outline-color: rgba(var(--card-color), 0.5)
span :global(span)
font-size: 1.2rem font-size: 1.2rem
display: block display: block
margin-bottom: 0.5rem margin-bottom: 0.5rem

View File

@@ -26,16 +26,10 @@
<style lang="sass"> <style lang="sass">
@use "../vars" @use "../vars"
.rating-composition .rating-composition
display: grid display: grid
// 3 columns // 3 columns
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)) grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
gap: vars.$gap gap: vars.$gap
.rating-composition-2
display: grid
// 2 columns
grid-template-columns: repeat(auto-fill, minmax(290px, 1fr))
gap: vars.$gap
</style> </style>

View File

@@ -434,37 +434,10 @@
<style lang="sass"> <style lang="sass">
@use "../vars" @use "../vars"
.outer-container
display: flex
flex-direction: column
gap: 1rem
nav
display: flex
gap: 1rem
div
padding: 0.5rem 1rem
border-radius: 0.4rem
cursor: pointer
transition: background-color 0.2s
font-weight: 500
&.active
color: vars.$c-main
img img
width: 100% width: 100%
height: auto height: auto
.container
display: flex
flex-direction: row
gap: 3rem
@media (max-width: vars.$w-max)
flex-direction: column
.preview .preview
display: flex display: flex
flex-direction: column flex-direction: column

View File

@@ -23,7 +23,7 @@
<div>Error: {error}</div> <div>Error: {error}</div>
{:else} {:else}
<div class="user-card"> <div class="user-card">
<img use:pfp={data.aquaUser} alt="Profile Picture" /> <img use:pfp={data.aquaUser} alt="Profile" />
<div class="details"> <div class="details">
<span class="in-game-name">{data.name}</span> <span class="in-game-name">{data.name}</span>
<span class="username">@{username}</span> <span class="username">@{username}</span>

View File

@@ -1,98 +1,98 @@
<script lang="ts"> <script lang="ts">
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import LinkCard from "./Home/LinkCard.svelte"; import LinkCard from "./Home/LinkCard.svelte";
import SetupInstructions from "./Home/SetupInstructions.svelte"; import SetupInstructions from "./Home/SetupInstructions.svelte";
import { DISCORD_INVITE, FADE_IN, FADE_OUT } from "../libs/config"; import { DISCORD_INVITE, FADE_IN, FADE_OUT } from "../libs/config";
import { USER } from "../libs/sdk.js"; import { USER } from "../libs/sdk.js";
import type { AquaNetUser } from "../libs/generalTypes"; import type { AquaNetUser } from "../libs/generalTypes";
import StatusOverlays from "../components/StatusOverlays.svelte"; import StatusOverlays from "../components/StatusOverlays.svelte";
import ActionCard from "../components/ActionCard.svelte"; import ActionCard from "../components/ActionCard.svelte";
import { t } from "../libs/i18n"; import { t } from "../libs/i18n";
import ImportDataAction from "./Home/ImportDataAction.svelte"; import ImportDataAction from "./Home/ImportDataAction.svelte";
import Communities from "./Home/Communities.svelte"; import Communities from "./Home/Communities.svelte";
USER.ensureLoggedIn(); USER.ensureLoggedIn();
let me: AquaNetUser let me: AquaNetUser
let error = "" let error = ""
let tab = 0; let tab = 0;
let tabs = [t('home.nav.portal'), t('home.nav.link-card'), t('home.nav.game-setup')] let tabs = [t('home.nav.portal'), t('home.nav.link-card'), t('home.nav.game-setup')]
USER.me().then((m) => me = m).catch(e => error = e.message) USER.me().then((m) => me = m).catch(e => error = e.message)
</script> </script>
<main class="content"> <main class="content">
<!-- <h2 class="outer-title">&nbsp;</h2>--> <!-- <h2 class="outer-title">&nbsp;</h2>-->
<nav class="tabs"> <nav class="tabs">
{#each tabs as t, i} {#each tabs as t, i}
<div class="clickable" <div class="clickable"
class:active={tab === i} class:active={tab === i}
on:click={() => tab = i} on:click={() => tab = i}
on:keydown={(e) => e.key === "Enter" && (tab = i)} on:keydown={(e) => e.key === "Enter" && (tab = i)}
role="button" tabindex={i}>{t} role="button" tabindex={i}>{t}
</div> </div>
{/each} {/each}
</nav> </nav>
{#if tab === 0} {#if tab === 0}
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="action-cards"> <div out:fade={FADE_OUT} in:fade={FADE_IN} class="action-cards">
<ActionCard color="255, 192, 203" icon="solar:card-bold-duotone" on:click={() => tab = 1}> <ActionCard color="255, 192, 203" icon="solar:card-bold-duotone" on:click={() => tab = 1}>
{#if me && me.cards.length > 1} {#if me && me.cards.length > 1}
<h3>{t('home.manage-cards')}</h3> <h3>{t('home.manage-cards')}</h3>
<span>{t('home.manage-cards-description')}</span> <span>{t('home.manage-cards-description')}</span>
{:else if me} {:else if me}
<h3>{t('home.link-card')}</h3> <h3>{t('home.link-card')}</h3>
<span>{t('home.link-cards-description')}</span> <span>{t('home.link-cards-description')}</span>
{/if} {/if}
</ActionCard> </ActionCard>
<ActionCard color="82, 93, 233" icon="fluent:chat-12-filled" on:click={() => tab = 3}> <ActionCard color="82, 93, 233" icon="fluent:chat-12-filled" on:click={() => tab = 3}>
<h3>{t('home.join-community')}</h3> <h3>{t('home.join-community')}</h3>
<span>{t('home.join-community-description')}</span> <span>{t('home.join-community-description')}</span>
</ActionCard> </ActionCard>
<ActionCard on:click={() => tab = 2} icon="uil:link-alt"> <ActionCard on:click={() => tab = 2} icon="uil:link-alt">
<h3>{t('home.setup')}</h3> <h3>{t('home.setup')}</h3>
<span>{t('home.setup-description')}</span> <span>{t('home.setup-description')}</span>
</ActionCard> </ActionCard>
<ImportDataAction/> <ImportDataAction/>
</div> </div>
{:else if tab === 1} {:else if tab === 1}
<div out:fade={FADE_OUT} in:fade={FADE_IN}> <div out:fade={FADE_OUT} in:fade={FADE_IN}>
<LinkCard/> <LinkCard/>
</div> </div>
{:else if tab === 2} {:else if tab === 2}
<div out:fade={FADE_OUT} in:fade={FADE_IN}> <div out:fade={FADE_OUT} in:fade={FADE_IN}>
<SetupInstructions/> <SetupInstructions/>
</div> </div>
{:else if tab === 3} {:else if tab === 3}
<div out:fade={FADE_OUT} in:fade={FADE_IN}> <div out:fade={FADE_OUT} in:fade={FADE_IN}>
<Communities/> <Communities/>
</div> </div>
{/if} {/if}
</main> </main>
<StatusOverlays {error} loading={!me}/> <StatusOverlays {error} loading={!me}/>
<style lang="sass"> <style lang="sass">
@use "../vars" @use "../vars"
.tabs .tabs
display: flex display: flex
gap: 1rem gap: 1rem
div div
&.active &.active
color: vars.$c-main color: vars.$c-main
h3 h3
font-size: 1.3rem font-size: 1.3rem
margin: 0 margin: 0
.action-cards .action-cards
display: flex display: flex
flex-direction: column flex-direction: column
gap: 1rem gap: 1rem
</style> </style>

View File

@@ -1,375 +1,375 @@
<!-- Svelte 4.2.11 --> <!-- Svelte 4.2.11 -->
<script lang="ts"> <script lang="ts">
import { fade, slide } from "svelte/transition" import { fade, slide } from "svelte/transition"
import type { Card, CardSummary, CardSummaryGame, ConfirmProps, AquaNetUser } from "../../libs/generalTypes"; import type { Card, CardSummary, CardSummaryGame, ConfirmProps, AquaNetUser } from "../../libs/generalTypes";
import { CARD, USER } from "../../libs/sdk"; import { CARD, USER } from "../../libs/sdk";
import moment from "moment" import moment from "moment"
import Icon from "@iconify/svelte"; import Icon from "@iconify/svelte";
import StatusOverlays from "../../components/StatusOverlays.svelte"; import StatusOverlays from "../../components/StatusOverlays.svelte";
import { t } from "../../libs/i18n"; import { t } from "../../libs/i18n";
// State // State
let state: 'ready' | 'linking-AC' | 'linking-SN' | 'loading' = "loading" let state: 'ready' | 'linking-AC' | 'linking-SN' | 'loading' = "loading"
let showConfirm: ConfirmProps | null = null let showConfirm: ConfirmProps | null = null
let error: string = "" let error: string = ""
let me: AquaNetUser | null = null let me: AquaNetUser | null = null
let accountCardSummary: CardSummary | null = null let accountCardSummary: CardSummary | null = null
// Fetch data for current user // Fetch data for current user
const updateMe = () => USER.me().then(m => { const updateMe = () => USER.me().then(m => {
me = m me = m
m.cards.sort((a, b) => a.registerTime < b.registerTime ? 1 : -1) m.cards.sort((a, b) => a.registerTime < b.registerTime ? 1 : -1)
CARD.summary(m.ghostCard.luid).then(s => accountCardSummary = s.summary) CARD.summary(m.ghostCard.luid).then(s => accountCardSummary = s.summary)
// Always put the ghost card at the top // Always put the ghost card at the top
m.cards.sort((a, b) => a.isGhost ? -1 : 1) m.cards.sort((a, b) => a.isGhost ? -1 : 1)
state = "ready" state = "ready"
}).catch(e => error = e.message) }).catch(e => error = e.message)
updateMe() updateMe()
// Data conflict overlay // Data conflict overlay
let conflictCardID: string = "" let conflictCardID: string = ""
let conflictSummary: CardSummary | null = null let conflictSummary: CardSummary | null = null
let conflictGame: string = "" let conflictGame: string = ""
let conflictNew: CardSummaryGame | null = null let conflictNew: CardSummaryGame | null = null
let conflictOld: CardSummaryGame | null = null let conflictOld: CardSummaryGame | null = null
let conflictToMigrate: string[] = [] let conflictToMigrate: string[] = []
function setError(msg: string, type: 'AC' | 'SN') { function setError(msg: string, type: 'AC' | 'SN') {
type === 'AC' ? errorAC = msg : errorSN = msg type === 'AC' ? errorAC = msg : errorSN = msg
} }
async function doLink(id: string, migrate: string) { async function doLink(id: string, migrate: string) {
await CARD.link({cardId: id, migrate}) await CARD.link({cardId: id, migrate})
await updateMe() await updateMe()
state = "ready" state = "ready"
} }
async function link(type: 'AC' | 'SN') { async function link(type: 'AC' | 'SN') {
if (state !== 'ready' || accountCardSummary === null) return if (state !== 'ready' || accountCardSummary === null) return
state = "linking-" + type state = "linking-" + type
const id = type === 'AC' ? inputAC : inputSN const id = type === 'AC' ? inputAC : inputSN
console.log("linking card", id) console.log("linking card", id)
// Check if this card is already linked in the account // Check if this card is already linked in the account
if (me?.cards?.some(c => formatLUID(c.luid, c.isGhost).toLowerCase() === id.toLowerCase())) { if (me?.cards?.some(c => formatLUID(c.luid, c.isGhost).toLowerCase() === id.toLowerCase())) {
setError(t('home.linkcard.linked-own'), type) setError(t('home.linkcard.linked-own'), type)
state = "ready" state = "ready"
return return
} }
// First, lookup the card summary // First, lookup the card summary
const card = (await CARD.summary(id).catch(e => { const card = (await CARD.summary(id).catch(e => {
// If card is not found, create a card and link it // If card is not found, create a card and link it
if (e.message === t('home.linkcard.notfound')) { if (e.message === t('home.linkcard.notfound')) {
doLink(id, "") doLink(id, "")
return return
} }
setError(e.message, type) setError(e.message, type)
state = "ready" state = "ready"
return return
}))! }))!
const summary = card.summary const summary = card.summary
// Check if it's already linked // Check if it's already linked
if (card.card.linked) { if (card.card.linked) {
setError(t('home.linkcard.linked-another'), type) setError(t('home.linkcard.linked-another'), type)
state = "ready" state = "ready"
return return
} }
// If all games in summary are null or doesn't conflict with the ghost card, // If all games in summary are null or doesn't conflict with the ghost card,
// we can link the card directly // we can link the card directly
if (Object.keys(summary).every(k => summary[k as keyof CardSummary] === null if (Object.keys(summary).every(k => summary[k as keyof CardSummary] === null
|| accountCardSummary!![k as keyof CardSummary] === null)) { || accountCardSummary!![k as keyof CardSummary] === null)) {
console.log("linking card directly") console.log("linking card directly")
await doLink(id, Object.keys(summary).filter(k => summary[k as keyof CardSummary] !== null).join(",")) await doLink(id, Object.keys(summary).filter(k => summary[k as keyof CardSummary] !== null).join(","))
} }
// For each conflicting game, ask the user if they want to migrate the data // For each conflicting game, ask the user if they want to migrate the data
else { else {
conflictSummary = summary conflictSummary = summary
conflictCardID = id conflictCardID = id
await linkConflictContinue(null) await linkConflictContinue(null)
} }
} }
async function linkConflictContinue(choose: "old" | "new" | null) { async function linkConflictContinue(choose: "old" | "new" | null) {
if (accountCardSummary === null || conflictSummary === null) return if (accountCardSummary === null || conflictSummary === null) return
console.log("linking card with migration") console.log("linking card with migration")
if (choose) { if (choose) {
// If old is chosen, nothing needs to be migrated // If old is chosen, nothing needs to be migrated
// If new is chosen, we need to migrate the data // If new is chosen, we need to migrate the data
if (choose === "new") { if (choose === "new") {
conflictToMigrate.push(conflictGame) conflictToMigrate.push(conflictGame)
} }
// Continue to the next card // Continue to the next card
conflictSummary[conflictGame as keyof CardSummary] = null conflictSummary[conflictGame as keyof CardSummary] = null
} }
let isConflict = false let isConflict = false
for (const k in conflictSummary) { for (const k in conflictSummary) {
conflictNew = conflictSummary[k as keyof CardSummary] conflictNew = conflictSummary[k as keyof CardSummary]
conflictOld = accountCardSummary[k as keyof CardSummary] conflictOld = accountCardSummary[k as keyof CardSummary]
conflictGame = k conflictGame = k
if (!conflictNew || !conflictOld) continue if (!conflictNew || !conflictOld) continue
isConflict = true isConflict = true
break break
} }
// If there are no longer conflicts, we can link the card // If there are no longer conflicts, we can link the card
if (!isConflict) { if (!isConflict) {
await doLink(conflictCardID, conflictToMigrate.join(",")) await doLink(conflictCardID, conflictToMigrate.join(","))
// Reset the conflict state // Reset the conflict state
linkConflictCancel() linkConflictCancel()
} }
} }
function linkConflictCancel() { function linkConflictCancel() {
state = "ready" state = "ready"
conflictSummary = null conflictSummary = null
conflictCardID = "" conflictCardID = ""
conflictGame = "" conflictGame = ""
conflictNew = null conflictNew = null
conflictOld = null conflictOld = null
conflictToMigrate = [] conflictToMigrate = []
} }
async function unlink(card: Card) { async function unlink(card: Card) {
showConfirm = { showConfirm = {
title: t('home.linkcard.unlink'), title: t('home.linkcard.unlink'),
message: t('home.linkcard.unlink-notice'), message: t('home.linkcard.unlink-notice'),
confirm: async () => { confirm: async () => {
await CARD.unlink(card.luid) await CARD.unlink(card.luid)
await updateMe() await updateMe()
showConfirm = null showConfirm = null
}, },
cancel: () => showConfirm = null, cancel: () => showConfirm = null,
dangerous: true dangerous: true
} }
} }
// Access code input // Access code input
const inputACRegex = /^(\d{4} ){0,4}\d{0,4}$/ const inputACRegex = /^(\d{4} ){0,4}\d{0,4}$/
let inputAC = "" let inputAC = ""
let errorAC = "" let errorAC = ""
function inputACChange(e: any) { function inputACChange(e: any) {
e = e as InputEvent e = e as InputEvent
// Add spaces to the input // Add spaces to the input
const old = inputAC const old = inputAC
if (e.inputType === "insertText" && inputAC.length % 5 === 4 && inputAC.length < 24) if (e.inputType === "insertText" && inputAC.length % 5 === 4 && inputAC.length < 24)
inputAC += " " inputAC += " "
inputAC = inputAC.slice(0, 24) inputAC = inputAC.slice(0, 24)
if (inputAC !== old) errorAC = "" if (inputAC !== old) errorAC = ""
} }
// Serial number input // Serial number input
const inputSNRegex = /^([0-9A-Fa-f]{0,2}:){0,7}[0-9A-Fa-f]{0,2}$/ const inputSNRegex = /^([0-9A-Fa-f]{0,2}:){0,7}[0-9A-Fa-f]{0,2}$/
let inputSN = "" let inputSN = ""
let errorSN = "" let errorSN = ""
function inputSNChange(e: any) { function inputSNChange(e: any) {
e = e as InputEvent e = e as InputEvent
// Add colons to the input // Add colons to the input
const old = inputSN const old = inputSN
if (e.inputType === "insertText" && inputSN.length % 3 === 2 && inputSN.length < 23) if (e.inputType === "insertText" && inputSN.length % 3 === 2 && inputSN.length < 23)
inputSN += ":" inputSN += ":"
inputSN = inputSN.toUpperCase().slice(0, 23) inputSN = inputSN.toUpperCase().slice(0, 23)
if (inputSN !== old) errorSN = "" if (inputSN !== old) errorSN = ""
} }
function formatLUID(luid: string, ghost: boolean = false) { function formatLUID(luid: string, ghost: boolean = false) {
if (ghost) return luid.slice(0, 6) + " " + (luid.slice(6).match(/.{4}/g)?.join(" ") ?? "") if (ghost) return luid.slice(0, 6) + " " + (luid.slice(6).match(/.{4}/g)?.join(" ") ?? "")
switch (cardType(luid)) { switch (cardType(luid)) {
case "Felica SN": case "Felica SN":
return BigInt(luid).toString(16).toUpperCase().padStart(16, "0").match(/.{1,2}/g)!.join(":") return BigInt(luid).toString(16).toUpperCase().padStart(16, "0").match(/.{1,2}/g)!.join(":")
case "Access Code": case "Access Code":
return luid.match(/.{4}/g)!.join(" ") return luid.match(/.{4}/g)!.join(" ")
default: default:
return luid return luid
} }
} }
function cardType(luid: string) { function cardType(luid: string) {
if (luid.startsWith("00")) return "Felica SN" if (luid.startsWith("00")) return "Felica SN"
if (luid.length === 20) return "Access Code" if (luid.length === 20) return "Access Code"
if (luid.includes(":")) return "Felica SN" if (luid.includes(":")) return "Felica SN"
if (luid.includes(" ")) return "Access Code" if (luid.includes(" ")) return "Access Code"
return "Unknown" return "Unknown"
} }
function isInput(e: KeyboardEvent) { function isInput(e: KeyboardEvent) {
return e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey return e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !e.shiftKey
} }
</script> </script>
<div class="link-card"> <div class="link-card">
<h2>{t('home.linkcard.cards')}</h2> <h2>{t('home.linkcard.cards')}</h2>
<p>{t('home.linkcard.description')}:</p> <p>{t('home.linkcard.description')}:</p>
{#if me} {#if me}
<div class="existing-cards" transition:slide> <div class="existing-cards" transition:slide>
{#each me.cards as card (card.luid)} {#each me.cards as card (card.luid)}
<div class:ghost={card.isGhost} class='existing card' transition:fade|global> <div class:ghost={card.isGhost} class='existing card' transition:fade|global>
<span class="type">{card.isGhost ? t('home.linkcard.account-card') : cardType(card.luid)}</span> <span class="type">{card.isGhost ? t('home.linkcard.account-card') : cardType(card.luid)}</span>
<span class="register">{t('home.linkcard.registered')}: {moment(card.registerTime).format("YYYY MMM DD")}</span> <span class="register">{t('home.linkcard.registered')}: {moment(card.registerTime).format("YYYY MMM DD")}</span>
<span class="last">{t('home.linkcard.lastused')}: {moment(card.accessTime).format("YYYY MMM DD")}</span> <span class="last">{t('home.linkcard.lastused')}: {moment(card.accessTime).format("YYYY MMM DD")}</span>
<div/> <div></div>
<span class="id">{formatLUID(card.luid, card.isGhost)}</span> <span class="id">{formatLUID(card.luid, card.isGhost)}</span>
{#if !card.isGhost} {#if !card.isGhost}
<button class="icon error" on:click={() => unlink(card)}><Icon icon="tabler:trash-x-filled"/></button> <button class="icon error" on:click={() => unlink(card)}><Icon icon="tabler:trash-x-filled"/></button>
{/if} {/if}
</div> </div>
{/each} {/each}
</div> </div>
{/if} {/if}
<h2>{t('home.link-card')}</h2> <h2>{t('home.link-card')}</h2>
<p>{t('home.linkcard.enter-info')}:</p> <p>{t('home.linkcard.enter-info')}:</p>
{#if !inputSN} {#if !inputSN}
<div out:slide={{ duration: 250 }}> <div out:slide={{ duration: 250 }}>
<p>{t('home.linkcard.access-code')}</p> <p>{t('home.linkcard.access-code')}</p>
<label> <label>
<!-- DO NOT change the order of bind:value and on:input. Their order determines the order of reactivity --> <!-- DO NOT change the order of bind:value and on:input. Their order determines the order of reactivity -->
<input placeholder="e.g. 5200 1234 5678 9012 3456" <input placeholder="e.g. 5200 1234 5678 9012 3456"
on:keydown={(e) => { on:keydown={(e) => {
e.key === "Enter" && link('AC') e.key === "Enter" && link('AC')
// Ensure key is numeric // Ensure key is numeric
if (isInput(e) && !/[\d ]/.test(e.key)) e.preventDefault() if (isInput(e) && !/[\d ]/.test(e.key)) e.preventDefault()
}} }}
bind:value={inputAC} bind:value={inputAC}
on:input={inputACChange} on:input={inputACChange}
class:error={inputAC && (!inputACRegex.test(inputAC) || errorAC)}> class:error={inputAC && (!inputACRegex.test(inputAC) || errorAC)}>
{#if inputAC.length > 0} {#if inputAC.length > 0}
<button transition:slide={{axis: 'x'}} on:click={() => {link('AC');inputAC=''}}>{t('home.linkcard.link')}</button> <button transition:slide={{axis: 'x'}} on:click={() => {link('AC');inputAC=''}}>{t('home.linkcard.link')}</button>
{/if} {/if}
</label> </label>
{#if errorAC} {#if errorAC}
<p class="error" transition:slide>{errorAC}</p> <p class="error" transition:slide>{errorAC}</p>
{/if} {/if}
</div> </div>
{/if} {/if}
{#if !inputAC} {#if !inputAC}
<div out:slide={{ duration: 250 }}> <div out:slide={{ duration: 250 }}>
<p>{t('home.linkcard.enter-sn1')} <p>{t('home.linkcard.enter-sn1')}
(<a href="https://play.google.com/store/apps/details?id=com.wakdev.wdnfc">Android</a> / (<a href="https://play.google.com/store/apps/details?id=com.wakdev.wdnfc">Android</a> /
<a href="https://apps.apple.com/us/app/nfc-tools/id1252962749">Apple</a>) <a href="https://apps.apple.com/us/app/nfc-tools/id1252962749">Apple</a>)
{t('home.linkcard.enter-sn2')} {t('home.linkcard.enter-sn2')}
</p> </p>
<label> <label>
<input placeholder="e.g. 01:2E:1A:2B:3C:4D:5E:6F" <input placeholder="e.g. 01:2E:1A:2B:3C:4D:5E:6F"
on:keydown={(e) => { on:keydown={(e) => {
e.key === "Enter" && link('SN') e.key === "Enter" && link('SN')
// Ensure key is hex or colon // Ensure key is hex or colon
if (isInput(e) && !/[0-9A-Fa-f:]/.test(e.key)) e.preventDefault() if (isInput(e) && !/[0-9A-Fa-f:]/.test(e.key)) e.preventDefault()
}} }}
bind:value={inputSN} bind:value={inputSN}
on:input={inputSNChange} on:input={inputSNChange}
class:error={inputSN && (!inputSNRegex.test(inputSN) || errorSN)}> class:error={inputSN && (!inputSNRegex.test(inputSN) || errorSN)}>
{#if inputSN.length > 0} {#if inputSN.length > 0}
<button transition:slide={{axis: 'x'}} on:click={() => {link('SN'); inputSN = ''}}>{t('home.linkcard.link')}</button> <button transition:slide={{axis: 'x'}} on:click={() => {link('SN'); inputSN = ''}}>{t('home.linkcard.link')}</button>
{/if} {/if}
</label> </label>
{#if errorSN} {#if errorSN}
<p class="error" transition:slide>{errorSN}</p> <p class="error" transition:slide>{errorSN}</p>
{/if} {/if}
</div> </div>
{/if} {/if}
{#if conflictOld && conflictNew && me} {#if conflictOld && conflictNew && me}
<div class="overlay" transition:fade> <div class="overlay" transition:fade>
<div> <div>
<h2>{t('home.linkcard.data-conflict')}</h2> <h2>{t('home.linkcard.data-conflict')}</h2>
<p></p> <p></p>
<div class="conflict-cards"> <div class="conflict-cards">
<div class="old card clickable" on:click={() => linkConflictContinue('old')} <div class="old card clickable" on:click={() => linkConflictContinue('old')}
role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('old')}> role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('old')}>
<span class="type">{t('home.linkcard.account-card')}</span> <span class="type">{t('home.linkcard.account-card')}</span>
<span>{t('home.linkcard.name')}: {conflictOld.name}</span> <span>{t('home.linkcard.name')}: {conflictOld.name}</span>
<span>{t('home.linkcard.rating')}: {conflictOld.rating}</span> <span>{t('home.linkcard.rating')}: {conflictOld.rating}</span>
<span>{t('home.linkcard.last-login')}: {moment(conflictOld.lastLogin).format("YYYY MMM DD")}</span> <span>{t('home.linkcard.last-login')}: {moment(conflictOld.lastLogin).format("YYYY MMM DD")}</span>
<span class="id">{formatLUID(me.ghostCard.luid, true)}</span> <span class="id">{formatLUID(me.ghostCard.luid, true)}</span>
</div> </div>
<div class="new card clickable" on:click={() => linkConflictContinue('new')} <div class="new card clickable" on:click={() => linkConflictContinue('new')}
role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('new')}> role="button" tabindex="0" on:keydown={e => e.key === "Enter" && linkConflictContinue('new')}>
<span class="type">{cardType(conflictCardID)}</span> <span class="type">{cardType(conflictCardID)}</span>
<span>{t('home.linkcard.name')}: {conflictNew.name}</span> <span>{t('home.linkcard.name')}: {conflictNew.name}</span>
<span>{t('home.linkcard.rating')}: {conflictNew.rating}</span> <span>{t('home.linkcard.rating')}: {conflictNew.rating}</span>
<span>{t('home.linkcard.last-login')}: {moment(conflictNew.lastLogin).format("YYYY MMM DD")}</span> <span>{t('home.linkcard.last-login')}: {moment(conflictNew.lastLogin).format("YYYY MMM DD")}</span>
<span class="id">{conflictCardID}</span> <span class="id">{conflictCardID}</span>
</div> </div>
</div> </div>
<button class="error" on:click={linkConflictCancel}>{t('action.cancel')}</button> <button class="error" on:click={linkConflictCancel}>{t('action.cancel')}</button>
</div> </div>
</div> </div>
{/if} {/if}
<StatusOverlays bind:confirm={showConfirm} bind:error={error} loading={!me} /> <StatusOverlays bind:confirm={showConfirm} bind:error={error} loading={!me} />
</div> </div>
<style lang="sass"> <style lang="sass">
@use "../../vars" @use "../../vars"
.link-card .link-card
input input
width: 100% width: 100%
label label
display: flex display: flex
button button
margin-left: 1rem margin-left: 1rem
.existing-cards, .conflict-cards .existing-cards, .conflict-cards
display: grid display: grid
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)) grid-template-columns: repeat(auto-fill, minmax(250px, 1fr))
gap: 1rem gap: 1rem
.existing.card .existing-cards .existing.card
min-height: 90px min-height: 90px
position: relative position: relative
overflow: hidden overflow: hidden
* *
white-space: nowrap white-space: nowrap
&.ghost &.ghost
background: rgba(vars.$c-darker, 0.8) background: rgba(vars.$c-darker, 0.8)
.register, .last .register, .last
opacity: 0.7 opacity: 0.7
span:not(.type) span:not(.type)
font-size: 0.8rem font-size: 0.8rem
> div > div
flex: 1 flex: 1
button button
position: absolute position: absolute
right: 10px right: 10px
bottom: 10px bottom: 10px
.conflict-cards .conflict-cards
.card .card
transition: vars.$transition transition: vars.$transition
.card:hover .card:hover
background: vars.$c-darker background: vars.$c-darker
span:not(.type) span:not(.type)
font-size: 0.8rem font-size: 0.8rem
.id .id
opacity: 0.7 opacity: 0.7
</style> </style>

View File

@@ -38,7 +38,7 @@
{#if d} {#if d}
<div class="leaderboard-container"> <div class="leaderboard-container">
<div class="lb-user" on:mouseenter={() => hoveringUser = d.users[0].username}> <div class="lb-user" on:mouseenter={() => hoveringUser = d.users[0].username} role="heading" aria-level="2">
<span class="rank">{t("Leaderboard.Rank")}</span> <span class="rank">{t("Leaderboard.Rank")}</span>
<span class="name"></span> <span class="name"></span>
<span class="rating">{t("Leaderboard.Rating")}</span> <span class="rating">{t("Leaderboard.Rating")}</span>
@@ -47,7 +47,9 @@
<span class="ap">{t("Leaderboard.AP")}</span> <span class="ap">{t("Leaderboard.AP")}</span>
</div> </div>
{#each d.users as user, i (user.rank)} {#each d.users as user, i (user.rank)}
<div class="lb-user" class:alternate={i % 2 === 1} on:mouseover={() => hoveringUser = user.username}> <div class="lb-user" class:alternate={i % 2 === 1} role="listitem"
on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}>
<span class="rank">#{user.rank}</span> <span class="rank">#{user.rank}</span>
<span class="name"> <span class="name">
{#if user.username !== ""} {#if user.username !== ""}

View File

@@ -109,9 +109,10 @@
<div class="name-box"> <div class="name-box">
<h2>{d.user.name}</h2> <h2>{d.user.name}</h2>
{#if typeof d.user.rival === 'boolean' && game === 'mai2'} {#if typeof d.user.rival === 'boolean' && game === 'mai2'}
<a class="clickable" on:click={()=>setRival(!d.user.rival)}> <span class="clickable" on:click={() => setRival(!d?.user.rival)} role="button" tabindex="0"
on:keydown={e => e.key === "Enter" && setRival(!d?.user.rival)}>
{d.user.rival ? t("UserHome.RemoveRival") : t("UserHome.AddRival")} {d.user.rival ? t("UserHome.RemoveRival") : t("UserHome.AddRival")}
</a> </span>
{/if} {/if}
{#if me && me.username === username} {#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">
@@ -219,7 +220,7 @@
<div> <div>
<h2>{t('UserHome.PlayActivity')}</h2> <h2>{t('UserHome.PlayActivity')}</h2>
<div class="activity-info"> <div class="activity-info">
<div class="hide-scrollbar" id="cal-heatmap" bind:this={calElement} /> <div class="hide-scrollbar" id="cal-heatmap" bind:this={calElement}></div>
<div class="info-bottom"> <div class="info-bottom">
<div class="plays"> <div class="plays">