aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-02-20 21:38:14 -0600
committermat <github@matdoes.dev>2022-02-20 21:38:14 -0600
commit13e5974114f759bae73f3bfd68c62ce9cfaf785e (patch)
tree8a196b27b8d4dece1dc2187332422a4e41423dfa /src/lib
parent582409e7cb1598b65bee6d1023b77620bb3791af (diff)
downloadskyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.gz
skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.bz2
skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.zip
add more stuff to profile and fix bugs
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/APITypes.d.ts96
-rw-r--r--src/lib/Collapsible.svelte40
-rw-r--r--src/lib/Emoji.svelte11
-rw-r--r--src/lib/GlobalTooltip.svelte94
-rw-r--r--src/lib/GlobalTooltip.ts76
-rw-r--r--src/lib/minecraft/Inventory.svelte14
-rw-r--r--src/lib/minecraft/Item.svelte21
-rw-r--r--src/lib/minecraft/MinecraftTooltip.svelte18
-rw-r--r--src/lib/minecraft/Username.svelte9
-rw-r--r--src/lib/minecraft/heads/Head2d.svelte6
-rw-r--r--src/lib/minecraft/heads/Head3d.svelte6
-rw-r--r--src/lib/minecraft/inventory.ts5
-rw-r--r--src/lib/profile.ts61
-rw-r--r--src/lib/sections/Infobox.svelte36
-rw-r--r--src/lib/sections/Inventories.svelte2
-rw-r--r--src/lib/sections/Skills.svelte1
-rw-r--r--src/lib/sections/StatList.svelte42
-rw-r--r--src/lib/utils.ts8
18 files changed, 368 insertions, 178 deletions
diff --git a/src/lib/APITypes.d.ts b/src/lib/APITypes.d.ts
new file mode 100644
index 0000000..04f9220
--- /dev/null
+++ b/src/lib/APITypes.d.ts
@@ -0,0 +1,96 @@
+export interface CleanMemberProfile {
+ member: CleanMemberProfilePlayer
+ profile: CleanFullProfileBasicMembers
+ customization?: AccountCustomization
+}
+
+export interface CleanMemberProfilePlayer extends CleanPlayer {
+ profileName: string
+ first_join: number
+ last_save: number
+ bank?: Bank
+ purse?: number
+ stats?: StatItem[]
+ rawHypixelStats?: {
+ [key: string]: number
+ }
+ minions?: CleanMinion[]
+ fairy_souls?: FairySouls
+ inventories?: Inventories
+ objectives?: Objective[]
+ skills?: Skill[]
+ visited_zones?: Zone[]
+ collections?: Collection[]
+ slayers?: SlayerData
+}
+
+export interface CleanBasicPlayer {
+ uuid: string
+ username: string
+}
+
+export interface CleanPlayer extends CleanBasicPlayer {
+ rank: CleanRank
+ socials: CleanSocialMedia
+ profiles?: CleanBasicProfile[]
+}
+
+export interface StatItem {
+ rawName: string
+ value: number
+ categorizedName: string
+ category: string | null
+ unit: string | null
+}
+
+interface Item {
+ id: string
+ count: number
+ vanillaId: string
+ display: {
+ name: string
+ lore: string[]
+ glint: boolean
+ }
+ reforge?: string
+ anvil_uses?: number
+ timestamp?: string
+ enchantments?: {
+ [name: string]: number
+ }
+ head_texture?: string
+}
+export declare type Inventory = Item[]
+export declare const INVENTORIES: {
+ armor: string
+ inventory: string
+ ender_chest: string
+ talisman_bag: string
+ potion_bag: string
+ fishing_bag: string
+ quiver: string
+ trick_or_treat_bag: string
+ wardrobe: string
+}
+export declare type Inventories = {
+ [name in keyof typeof INVENTORIES]: Item[]
+}
+
+
+export interface CleanUser {
+ player: CleanPlayer | null
+ profiles?: CleanProfile[]
+ activeProfile?: string
+ online?: boolean
+ customization?: AccountCustomization
+}
+
+export interface CleanProfile extends CleanBasicProfile {
+ members?: CleanBasicMember[]
+}
+
+/** A basic profile that only includes the profile uuid and name */
+export interface CleanBasicProfile {
+ uuid: string
+ name?: string
+}
diff --git a/src/lib/Collapsible.svelte b/src/lib/Collapsible.svelte
new file mode 100644
index 0000000..f3a28d2
--- /dev/null
+++ b/src/lib/Collapsible.svelte
@@ -0,0 +1,40 @@
+<!--
+ @component
+
+ Non-JS collapsible content.
+ -->
+
+<details>
+ <summary>
+ <slot name="title">
+ <h2>Details</h2>
+ </slot>
+ </summary>
+ <div>
+ <slot />
+ </div>
+</details>
+
+<style>
+ summary > :global(*) {
+ display: inline;
+ }
+ summary {
+ cursor: pointer;
+ }
+ summary::marker {
+ content: '';
+ }
+ summary::before {
+ /* the background image is an arrow pointing down */
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDIwIDIwIj48cGF0aCBkPSJNMTcuNSA0Ljc1bC03LjUgNy41LTcuNS03LjVMMSA2LjI1bDkgOSA5LTl6IiBmaWxsPSIjYWFhIi8+IDwvc3ZnPg==);
+ width: 20px;
+ height: 20px;
+ display: inline-block;
+ margin-right: 1em;
+ content: '';
+ }
+ details[open] summary::before {
+ transform: rotate(180deg);
+ }
+</style>
diff --git a/src/lib/Emoji.svelte b/src/lib/Emoji.svelte
index 0e8f4a7..1869f37 100644
--- a/src/lib/Emoji.svelte
+++ b/src/lib/Emoji.svelte
@@ -1,17 +1,20 @@
<!--
@component
- All the emojis inside this component will be turned into Twemojis.
+ All the emojis inside the value will be turned into Twemojis.
-->
<script lang="ts">
+ // Interestingly, the comment above adds whitespace so we don't need to add
+ // padding before the emoji like we usually would.
+ // This is very likely a SvelteKit bug, so when it's fixed we should add
+ // `margin-left: .25em` to .profile-emoji
+
import { twemojiHtml } from './utils'
export let value: string
</script>
-<span>
- {@html twemojiHtml(value)}
-</span>
+<span>{@html twemojiHtml(value)}</span>
<style>
:global(.emoji) {
diff --git a/src/lib/GlobalTooltip.svelte b/src/lib/GlobalTooltip.svelte
index 2f7b738..f504607 100644
--- a/src/lib/GlobalTooltip.svelte
+++ b/src/lib/GlobalTooltip.svelte
@@ -1,94 +1,20 @@
<script lang="ts">
import { onMount } from 'svelte'
+ import { onMouseMove, registerItem, setTooltipEl } from './GlobalTooltip'
- let tooltipEl
+ let tooltipEl: HTMLDivElement
+ $: setTooltipEl(tooltipEl)
- // this script handles the item hover lore tooltip
- onMount(() => {
- // TODO: have something that automatically registers the event listener when we create a new MinecraftTooltip
- const itemEls = document.getElementsByClassName('minecraft-tooltip')
- let tooltipLocked = false
- function moveTooltipToMouse(e) {
- const mouseX = e.pageX
- const mouseY = e.pageY
- console.log(mouseY + tooltipEl.offsetHeight, window.innerHeight + window.scrollY - 10)
- // if it's going to be off the bottom of the screen, move it up
- if (mouseY + tooltipEl.offsetHeight > window.innerHeight + window.scrollY - 10) {
- // put it at the bottom of the screen
- tooltipEl.style.top = `${
- window.innerHeight + window.scrollY - 10 - tooltipEl.offsetHeight
- }px`
- } else {
- // otherwise, put it at the mouse's y position
- tooltipEl.style.top = mouseY + 'px'
- }
- // if it's going to be off the right of the screen, move it left
- if (mouseX + tooltipEl.offsetWidth > window.innerWidth + window.scrollX - 10) {
- // put it at the right of the screen
- tooltipEl.style.left = `${
- window.innerWidth + window.scrollX - 10 - tooltipEl.offsetWidth
- }px`
- } else {
- // otherwise, put it at the mouse's x position
- tooltipEl.style.left = mouseX + 'px'
- }
- }
- document.addEventListener('mousemove', e => {
- if (!tooltipLocked && tooltipEl.style.display !== 'none') {
- moveTooltipToMouse(e)
- }
- })
+ // // this script handles the item hover lore tooltip
+ // onMount(() => {
+ // // TODO: have something that automatically registers the event listener when we create a new MinecraftTooltip
+ // const itemEls = document.getElementsByClassName('minecraft-tooltip')
- for (const itemEl of itemEls as unknown as HTMLElement[]) {
- // if (!(itemEl instanceof HTMLElement)) continue
-
- // if the item doesn't have lore or a name, that must mean it's air
- // if (!itemEl.dataset.loreHtml && !itemEl.dataset.nameHtml) continue
-
- itemEl.addEventListener('mouseover', e => {
- if (!tooltipLocked) {
- moveTooltipToMouse(e)
- // copy the lore and name from the tooltip-lore and
- // tooltip-name elements inside the item el
- const loreHtml = itemEl.getElementsByClassName('tooltip-lore')[0].innerHTML
- const nameHtml = itemEl.getElementsByClassName('tooltip-name')[0].innerHTML
- tooltipEl.innerHTML = `<p class="item-lore-name">${nameHtml}</p><p class="item-lore-text">${loreHtml}</p>`
- }
- tooltipEl.style.display = 'block'
- })
- itemEl.addEventListener('mouseout', () => {
- if (!tooltipLocked) {
- tooltipEl.innerHTML = ''
- tooltipEl.style.display = 'none'
- }
- })
- itemEl.addEventListener('click', e => {
- tooltipLocked = !tooltipLocked
- moveTooltipToMouse(e)
- tooltipEl.style.display = 'block'
- if (tooltipLocked) {
- tooltipEl.style.userSelect = 'auto'
- tooltipEl.style.pointerEvents = 'auto'
- } else {
- tooltipEl.style.userSelect = null
- tooltipEl.style.pointerEvents = null
- }
- const loreHtml = itemEl.getElementsByClassName('tooltip-lore')[0].innerHTML
- const nameHtml = itemEl.getElementsByClassName('tooltip-name')[0].innerHTML
- tooltipEl.innerHTML = `<p class="item-lore-name">${nameHtml}</p><p class="item-lore-text">${loreHtml}</p>`
- })
- document.addEventListener('mousedown', e => {
- if (tooltipLocked && !tooltipEl.contains(e.target)) {
- tooltipLocked = false
- tooltipEl.style.userSelect = null
- tooltipEl.style.pointerEvents = null
- tooltipEl.style.display = 'none'
- }
- })
- }
- })
+ // for (const itemEl of itemEls as unknown as HTMLElement[]) registerItem(itemEl)
+ // })
</script>
+<svelte:window on:mousemove={onMouseMove} />
<div id="global-tooltip" style="display: none" bind:this={tooltipEl} />
<style>
diff --git a/src/lib/GlobalTooltip.ts b/src/lib/GlobalTooltip.ts
new file mode 100644
index 0000000..d2c1020
--- /dev/null
+++ b/src/lib/GlobalTooltip.ts
@@ -0,0 +1,76 @@
+let tooltipEl: HTMLDivElement
+let tooltipLocked = false
+
+export function setTooltipEl(el: HTMLDivElement) {
+ tooltipEl = el
+}
+
+export function onMouseMove(e: MouseEvent) {
+ if (!tooltipLocked && tooltipEl.style.display !== 'none') {
+ moveTooltipToMouse(e)
+ }
+}
+
+function moveTooltipToMouse(e: MouseEvent) {
+ const mouseX = e.pageX
+ const mouseY = e.pageY
+ // if it's going to be off the bottom of the screen, move it up
+ if (mouseY + tooltipEl.offsetHeight > window.innerHeight + window.scrollY - 10) {
+ // put it at the bottom of the screen
+ tooltipEl.style.top = `${window.innerHeight + window.scrollY - 10 - tooltipEl.offsetHeight}px`
+ } else {
+ // otherwise, put it at the mouse's y position
+ tooltipEl.style.top = mouseY + 'px'
+ }
+ // if it's going to be off the right of the screen, move it left
+ if (mouseX + tooltipEl.offsetWidth > window.innerWidth + window.scrollX - 10) {
+ // put it at the right of the screen
+ tooltipEl.style.left = `${window.innerWidth + window.scrollX - 10 - tooltipEl.offsetWidth}px`
+ } else {
+ // otherwise, put it at the mouse's x position
+ tooltipEl.style.left = mouseX + 'px'
+ }
+}
+
+export function registerItem(itemEl: HTMLElement) {
+ itemEl.addEventListener('mouseover', e => {
+ if (!tooltipLocked) {
+ moveTooltipToMouse(e)
+ // copy the lore and name from the tooltip-lore and
+ // tooltip-name elements inside the item el
+ const loreHtml = itemEl.getElementsByClassName('tooltip-lore')[0].innerHTML
+ const nameHtml = itemEl.getElementsByClassName('tooltip-name')[0].innerHTML
+ tooltipEl.innerHTML = `<p class="item-lore-name">${nameHtml}</p><p class="item-lore-text">${loreHtml}</p>`
+ }
+ tooltipEl.style.display = 'block'
+ })
+ itemEl.addEventListener('mouseout', () => {
+ if (!tooltipLocked) {
+ tooltipEl.innerHTML = ''
+ tooltipEl.style.display = 'none'
+ }
+ })
+ itemEl.addEventListener('click', e => {
+ tooltipLocked = !tooltipLocked
+ moveTooltipToMouse(e)
+ tooltipEl.style.display = 'block'
+ if (tooltipLocked) {
+ tooltipEl.style.userSelect = 'auto'
+ tooltipEl.style.pointerEvents = 'auto'
+ } else {
+ tooltipEl.style.userSelect = ''
+ tooltipEl.style.pointerEvents = ''
+ }
+ const loreHtml = itemEl.getElementsByClassName('tooltip-lore')[0].innerHTML
+ const nameHtml = itemEl.getElementsByClassName('tooltip-name')[0].innerHTML
+ tooltipEl.innerHTML = `<p class="item-lore-name">${nameHtml}</p><p class="item-lore-text">${loreHtml}</p>`
+ })
+ document.addEventListener('mousedown', e => {
+ if (tooltipLocked && !tooltipEl.contains(e.target as Node)) {
+ tooltipLocked = false
+ tooltipEl.style.userSelect = ''
+ tooltipEl.style.pointerEvents = ''
+ tooltipEl.style.display = 'none'
+ }
+ })
+}
diff --git a/src/lib/minecraft/Inventory.svelte b/src/lib/minecraft/Inventory.svelte
index d29b1e0..3068f67 100644
--- a/src/lib/minecraft/Inventory.svelte
+++ b/src/lib/minecraft/Inventory.svelte
@@ -1,22 +1,24 @@
<script lang="ts">
+ import type { Inventory, Item as APIItem } from '$lib/APITypes'
+
import Item from './Item.svelte'
- export let items
+ export let items: Inventory
export let name = ''
export let pack = ''
export let groupLimit = 9
- if (name === 'inventory')
- // in the inventory, the first 9 items are the hotbar and should be at the end
- items = items.slice(9).concat(items.slice(0, 9))
-
// each item group has 9 items
- let itemGroups = []
+ let itemGroups: APIItem[][] = []
$: {
itemGroups = []
for (let i = 0; i < items.length; i += groupLimit) {
itemGroups.push(items.slice(i, i + groupLimit))
}
+ if (name === 'inventory') {
+ // in the inventory, the first 9 items are the hotbar and should be at the end
+ itemGroups = itemGroups.slice(1).concat(itemGroups.slice(0, 1))
+ }
}
</script>
diff --git a/src/lib/minecraft/Item.svelte b/src/lib/minecraft/Item.svelte
index c944f1b..9f2dcc3 100644
--- a/src/lib/minecraft/Item.svelte
+++ b/src/lib/minecraft/Item.svelte
@@ -17,12 +17,12 @@
$: imageUrl = item ? itemToUrl(item, pack) : null
</script>
-<MinecraftTooltip>
- <span slot="name">{@html itemNameHtml}</span>
- <span slot="lore">{@html itemLoreHtml}</span>
- <span class="item" class:item-slot={isslot}>
- <!-- we have an if here because the item might be air -->
- {#if item}
+{#if item}
+ <MinecraftTooltip>
+ <span slot="name">{@html itemNameHtml}</span>
+ <span slot="lore">{@html itemLoreHtml}</span>
+ <span class="item" class:item-slot={isslot}>
+ <!-- we have an if here because the item might be air -->
{#if imageUrl}
<img
loading="lazy"
@@ -34,9 +34,12 @@
{#if item.count !== 1}
<span class="item-count">{item.count}</span>
{/if}
- {/if}
- </span>
-</MinecraftTooltip>
+ </span>
+ </MinecraftTooltip>
+{:else}
+ <!-- don't do all that if the item doesn't actually exist -->
+ <span class="item" class:item-slot={isslot} />
+{/if}
<style>
.item {
diff --git a/src/lib/minecraft/MinecraftTooltip.svelte b/src/lib/minecraft/MinecraftTooltip.svelte
index 9c4e274..ae68f8b 100644
--- a/src/lib/minecraft/MinecraftTooltip.svelte
+++ b/src/lib/minecraft/MinecraftTooltip.svelte
@@ -3,15 +3,23 @@
A tooltip that looks like when you hover over a Minecraft item in an inventory. This requires JavaScript.
-->
+<script lang="ts">
+ import { registerItem } from '$lib/GlobalTooltip'
+ import { onMount } from 'svelte'
-<span class="minecraft-tooltip">
+ let el
+
+ onMount(() => {
+ registerItem(el)
+ })
+</script>
+
+<span class="minecraft-tooltip" bind:this={el}>
<span class="tooltip-name">
<slot name="name" />
- </span>
- <span class="tooltip-lore">
+ </span><span class="tooltip-lore">
<slot name="lore" />
- </span>
- <slot />
+ </span><slot />
</span>
<style>
diff --git a/src/lib/minecraft/Username.svelte b/src/lib/minecraft/Username.svelte
index 555a226..701e50c 100644
--- a/src/lib/minecraft/Username.svelte
+++ b/src/lib/minecraft/Username.svelte
@@ -22,16 +22,13 @@
<ConditionalLink href="/player/{player.username}" isWrapped={hyperlinkToProfile}>
{#if headType == '3d'}
- <Head3d {player} isPartOfUsername={true} />
- {:else if headType == '2d'}
+ <Head3d {player} isPartOfUsername={true} />{:else if headType == '2d'}
<Head2d {player} isPartOfUsername={true} />
- {/if}
- {#if prefix}
+ {/if}{#if prefix}
<span class="username-rank-prefix">
{@html formattingCodeToHtml(player.rank.colored)}
</span>
- {/if}
- <span class="username" style="color: {player.rank.color}">{player.username}</span>
+ {/if}<span class="username" style="color: {player.rank.color}">{player.username}</span>
</ConditionalLink>
<style>
diff --git a/src/lib/minecraft/heads/Head2d.svelte b/src/lib/minecraft/heads/Head2d.svelte
index d4e9ca8..9d71551 100644
--- a/src/lib/minecraft/heads/Head2d.svelte
+++ b/src/lib/minecraft/heads/Head2d.svelte
@@ -6,7 +6,7 @@
<img
loading="lazy"
class="head head2d"
- class:userHead={isPartOfUsername}
+ class:player-head={isPartOfUsername}
src="https://crafatar.com/avatars/{player.uuid}?size=8&overlay"
alt="{player.username}'s face"
/>
@@ -27,4 +27,8 @@
height: 1em;
width: 1em;
}
+
+ .player-head {
+ margin-right: 0.2em;
+ }
</style>
diff --git a/src/lib/minecraft/heads/Head3d.svelte b/src/lib/minecraft/heads/Head3d.svelte
index f8d2657..2400f4b 100644
--- a/src/lib/minecraft/heads/Head3d.svelte
+++ b/src/lib/minecraft/heads/Head3d.svelte
@@ -6,7 +6,7 @@
<img
loading="lazy"
class="head head3d"
- class:userHead={isPartOfUsername}
+ class:player-head={isPartOfUsername}
src="https://www.mc-heads.net/head/{player.uuid}/128"
alt="{player.username}'s head"
/>
@@ -24,4 +24,8 @@
height: 1em;
width: 1em;
}
+
+ .player-head {
+ margin-right: 0.2em;
+ }
</style>
diff --git a/src/lib/minecraft/inventory.ts b/src/lib/minecraft/inventory.ts
index cb926b4..faaea85 100644
--- a/src/lib/minecraft/inventory.ts
+++ b/src/lib/minecraft/inventory.ts
@@ -1,6 +1,7 @@
import * as skyblockAssets from 'skyblock-assets'
import vanilla from 'skyblock-assets/matchers/vanilla.json'
import packshq from 'skyblock-assets/matchers/vanilla.json'
+import furfsky_reborn from 'skyblock-assets/matchers/furfsky_reborn.json'
interface Item {
@@ -52,10 +53,8 @@ export function itemToUrl(item: Item, packName?: string): string {
textureUrl = skyblockAssets.getTextureUrl({
id: item.vanillaId,
nbt: itemNbt,
- packs: [packshq, vanilla]
+ packs: [furfsky_reborn, vanilla]
})
- if (!textureUrl)
- console.log('no texture', item)
return textureUrl
}
diff --git a/src/lib/profile.ts b/src/lib/profile.ts
index 320a5dc..c2c945e 100644
--- a/src/lib/profile.ts
+++ b/src/lib/profile.ts
@@ -1,3 +1,4 @@
+import type { CleanMemberProfile, StatItem } from './APITypes'
import { cleanId, millisecondsToTime } from './utils'
/**
@@ -10,7 +11,7 @@ export function prettyTimestamp(ms: number) {
return timeAsString
}
-export function generateInfobox(data, opts: { meta: boolean }): string[] {
+export function generateInfobox(data: CleanMemberProfile): string[] {
const result: string[] = []
result.push(`๐Ÿ’พ Last save: ${prettyTimestamp(data.member.last_save * 1000)}`)
@@ -22,35 +23,37 @@ export function generateInfobox(data, opts: { meta: boolean }): string[] {
if (data.profile.minion_count >= data.profile.maxUniqueMinions)
result.push(`๐Ÿค– Minion count: ${data.profile.minion_count}`)
- let mostSignificantKillsStat = null
- let mostSignificantDeathsStat = null
-
- for (const stat of data.member.stats) {
- if (
- stat.category === 'kills'
- && stat.rawName != 'kills'
- && stat.value >= 200_000
- && stat.value > (mostSignificantKillsStat?.value ?? 0)
- )
- mostSignificantKillsStat = stat
- if (
- stat.category === 'deaths'
- && stat.rawName != 'deaths'
- && stat.value > 1_000_000
- && stat.value > (mostSignificantDeathsStat?.value ?? 0)
- )
- mostSignificantDeathsStat = stat
+ if (data.member.stats) {
+ let mostSignificantKillsStat: StatItem | null = null
+ let mostSignificantDeathsStat: StatItem | null = null
+
+ for (const stat of data.member.stats) {
+ if (
+ stat.category === 'kills'
+ && stat.rawName != 'kills'
+ && stat.value >= 200_000
+ && stat.value > (mostSignificantKillsStat?.value ?? 0)
+ )
+ mostSignificantKillsStat = stat
+ if (
+ stat.category === 'deaths'
+ && stat.rawName != 'deaths'
+ && stat.value > 1_000_000
+ && stat.value > (mostSignificantDeathsStat?.value ?? 0)
+ )
+ mostSignificantDeathsStat = stat
+ }
+
+ if (mostSignificantKillsStat)
+ result.push(
+ `โš”๏ธ ${mostSignificantKillsStat.value.toLocaleString()} ${mostSignificantKillsStat.unit || cleanId(mostSignificantKillsStat.rawName).toLowerCase()}`
+ )
+
+ if (mostSignificantDeathsStat)
+ result.push(
+ `โ˜  ${mostSignificantDeathsStat.value.toLocaleString()} ${mostSignificantDeathsStat.unit || cleanId(mostSignificantDeathsStat.rawName).toLowerCase()}`
+ )
}
- if (mostSignificantKillsStat)
- result.push(
- `โš”๏ธ ${mostSignificantKillsStat.value.toLocaleString()} ${mostSignificantKillsStat.unit || cleanId(mostSignificantKillsStat.rawName).toLowerCase()}`
- )
-
- if (mostSignificantDeathsStat)
- result.push(
- `โ˜  ${mostSignificantDeathsStat.value.toLocaleString()} ${mostSignificantDeathsStat.unit || cleanId(mostSignificantDeathsStat.rawName).toLowerCase()}`
- )
-
return result
} \ No newline at end of file
diff --git a/src/lib/sections/Infobox.svelte b/src/lib/sections/Infobox.svelte
index 756987d..7670dec 100644
--- a/src/lib/sections/Infobox.svelte
+++ b/src/lib/sections/Infobox.svelte
@@ -2,37 +2,23 @@
import { generateInfobox } from '$lib/profile'
import Username from '$lib/minecraft/Username.svelte'
import Emoji from '$lib/Emoji.svelte'
+ import { onMount } from 'svelte'
export let data
-</script>
-<!-- <div id="infobox">
- <h2>{{ render.username(data.member, prefix=true) }} ({{ data.member.profileName }})</h2>
- <p>{{ '๐Ÿ’พ'|twemojiHtml|safe }} Last save: {% if getTime() - data.member.last_save < 60 * 60 * 24 * 7 %}{{ ((getTime() - data.member.last_save) * 1000)|cleannumber('time') }} ago {% else %}{{ data.member.last_save|cleannumber('date') }}{% endif %}</p>
- <p>{{ '๐Ÿšถ'|twemojiHtml|safe }} Profile created: {% if getTime() - data.member.first_join < 60 * 60 * 24 * 7 %}{{ ((getTime() - data.member.first_join) * 1000)|cleannumber('time') }} ago {% else %}{{ data.member.first_join|cleannumber('date') }}{% endif %}</p>
- <p>{{ 'โœจ'|twemojiHtml|safe }} Fairy souls: {{ data.member.fairy_souls.total }}/{{ getConstants().max_fairy_souls }}</p>
-{%- if data.profile.minion_count == getConstants().max_minions -%}<p>{{ '๐Ÿค–'|twemojiHtml|safe }} Minion count: {{ data.profile.minion_count }}</p>{% endif %}
-{%- set mostSignificantKillsStat = {} -%}
-{%- set mostSignificantDeathsStat = {} -%}
-{%- for stat in data.member.stats -%}
-{%- if stat.category == 'kills' and stat.rawName != 'kills' and stat.value >= 200000 and stat.value > (mostSignificantKillsStat.value or 0) -%}
-{%- set mostSignificantKillsStat = stat -%}
-{%- endif -%}
-{%- if stat.category == 'deaths' and stat.rawName != 'deaths' and stat.value >= 1000000 and stat.value > (mostSignificantDeathsStat.value or 0) -%}
-{%- set mostSignificantDeathsStat = stat -%}
-{%- endif -%}
-{%- endfor -%}
-{%- if mostSignificantKillsStat.value -%}
- <p>{{ 'โš”๏ธ'|twemojiHtml|safe }} {{ mostSignificantKillsStat.value|cleannumber(mostSignificantKillsStat.unit or mostSignificantKillsStat.rawName|clean|lower) }}</p>
-{%- endif -%}
-{%- if mostSignificantDeathsStat.value -%}
- <p>{{ 'โ˜ '|twemojiHtml|safe }} {{ mostSignificantDeathsStat.value|cleannumber(mostSignificantDeathsStat.unit or mostSignificantDeathsStat.rawName|clean|lower) }}</p>
-{%- endif -%}
-</div> -->
+ // onMount(() => {
+ // // reload the data every second so the infobox updates
+ // const interval = setInterval(() => {
+ // data = data
+ // }, 1000)
+
+ // return () => clearInterval(interval)
+ // })
+</script>
<div id="infobox">
<h2><Username player={data.member} prefix /> ({data.member.profileName})</h2>
- {#each generateInfobox(data, { meta: false }) as item}
+ {#each generateInfobox(data) as item}
<p><Emoji value={item} /></p>
{/each}
</div>
diff --git a/src/lib/sections/Inventories.svelte b/src/lib/sections/Inventories.svelte
index 49a00c2..42607b6 100644
--- a/src/lib/sections/Inventories.svelte
+++ b/src/lib/sections/Inventories.svelte
@@ -27,7 +27,7 @@
{#each displayingInventories as inventoryName}
{#if inventoryName === selectedInventoryName}
<div id={inventoryName} class="inventory-content">
- <Inventory items={data.member.inventories[inventoryName]} {pack} />
+ <Inventory items={data.member.inventories[inventoryName]} {pack} name={inventoryName} />
</div>
{/if}
{/each}
diff --git a/src/lib/sections/Skills.svelte b/src/lib/sections/Skills.svelte
index 7111c43..6e1efdb 100644
--- a/src/lib/sections/Skills.svelte
+++ b/src/lib/sections/Skills.svelte
@@ -70,5 +70,6 @@
}
ul > li {
width: 10em;
+ margin: 0.25em 0.25em 0 0;
}
</style>
diff --git a/src/lib/sections/StatList.svelte b/src/lib/sections/StatList.svelte
new file mode 100644
index 0000000..266ceb3
--- /dev/null
+++ b/src/lib/sections/StatList.svelte
@@ -0,0 +1,42 @@
+<!--
+ @component
+
+ A sorted list of a user's stats, with the total sometimes being at the top.
+-->
+<script lang="ts">
+ import { cleanId, millisecondsToTime } from '$lib/utils'
+ import type { StatItem } from '$lib/APITypes'
+
+ export let stats: StatItem[]
+</script>
+
+<ul>
+ {#each stats as stat}
+ <li class:total-stat={stat.categorizedName === 'total'}>
+ <span class="stat-name">{cleanId(stat.categorizedName)}</span>:
+ {#if stat.unit === 'time'}
+ {millisecondsToTime(stat.value)}
+ {:else}
+ {stat.value.toLocaleString()}
+ {/if}
+ </li>
+ {/each}
+</ul>
+
+<style>
+ .total-stat .stat-name {
+ font-weight: bold;
+ }
+
+ .total-stat {
+ font-size: 1.2em;
+ list-style-type: none;
+ position: relative;
+ right: 1em;
+ bottom: 0.2em;
+ }
+
+ ul {
+ margin-top: 0.5em;
+ }
+</style>
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index f16021d..197bf6b 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -26,7 +26,7 @@ const colorCodeCharacter = 'ยง'
export function formattingCodeToHtml(formatted: string): string {
let htmlOutput = ''
// we store the hex code, not the formatting code
- let currentColor = null
+ let currentColor: null | string = null
// we store the css code, not the formatting code
const activeSpecialCodes: string[] = []
function reset() {
@@ -50,7 +50,7 @@ export function formattingCodeToHtml(formatted: string): string {
// if there's already a color, close that tag
if (currentColor) htmlOutput += '</span>'
currentColor = colorCodes[colorCharacter]
- htmlOutput += `<span style="color: ${currentColor}">`
+ htmlOutput += `<span style="color:${currentColor}">`
}
} else if (specialCodes[colorCharacter]) {
if (!activeSpecialCodes.includes(specialCodes[colorCharacter])) {
@@ -133,7 +133,7 @@ export function twemojiHtml(s: string) {
const htmlEncoded = s.replace('<', '&lt;').replace('>', '&gt;').replace('&', '&amp;')
// replace unicode emojis with <img src="/emoji/[hex].svg">
const asTwemoji = htmlEncoded.replace(emojiRegex, (match) => {
- return `<img src="/emoji/${[...match].map(p => p.codePointAt(0).toString(16)).join('-')}.svg" class="emoji">`
+ return `<img src="/emoji/${[...match].map(p => p.codePointAt(0)!.toString(16)).join('-')}.svg" class="emoji">`
})
return asTwemoji
}
@@ -150,5 +150,5 @@ export function formatNumber(n: number, digits = 3) {
{ value: 1e18, symbol: 'E' },
]
const item = numberSymbolsLookup.slice().reverse().find(item => n >= item.value)
- return (n / item.value).toPrecision(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + item.symbol
+ return (n / (item?.value ?? 1)).toPrecision(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + (item?.symbol ?? '')
} \ No newline at end of file