diff options
author | mat <github@matdoes.dev> | 2022-02-20 21:38:14 -0600 |
---|---|---|
committer | mat <github@matdoes.dev> | 2022-02-20 21:38:14 -0600 |
commit | 13e5974114f759bae73f3bfd68c62ce9cfaf785e (patch) | |
tree | 8a196b27b8d4dece1dc2187332422a4e41423dfa | |
parent | 582409e7cb1598b65bee6d1023b77620bb3791af (diff) | |
download | skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.gz skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.bz2 skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.zip |
add more stuff to profile and fix bugs
27 files changed, 558 insertions, 510 deletions
diff --git a/package.json b/package.json index d4f22e9..53fa608 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "tslib": "^2.3.1", "typescript": "~4.5.4", "vite": "^2.8.3", - "vite-plugin-html": "^2" + "vite-plugin-html": "^3.0.6" }, "type": "module", "dependencies": { @@ -37,7 +37,7 @@ "@sveltejs/adapter-node": "^1.0.0-next.68", "@sveltejs/adapter-vercel": "^1.0.0-next.43", "cookie": "^0.4.1", - "skyblock-assets": "^2.0.3" + "skyblock-assets": "^2.0.4" }, "packageManager": "yarn@3.1.1" } 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('<', '<').replace('>', '>').replace('&', '&') // 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 diff --git a/src/routes/player/[player]/[profile].svelte b/src/routes/player/[player]/[profile].svelte index e28dae7..1fed489 100644 --- a/src/routes/player/[player]/[profile].svelte +++ b/src/routes/player/[player]/[profile].svelte @@ -23,6 +23,7 @@ <script lang="ts"> import Inventories from '$lib/sections/Inventories.svelte' import Username from '$lib/minecraft/Username.svelte' + import StatList from '$lib/sections/StatList.svelte' import Infobox from '$lib/sections/Infobox.svelte' import Skills from '$lib/sections/Skills.svelte' import { generateInfobox } from '$lib/profile' @@ -32,11 +33,14 @@ import Head from '$lib/Head.svelte' import Toc from '$lib/Toc.svelte' - export let data + import type { CleanMemberProfile } from '$lib/APITypes' + import { cleanId } from '$lib/utils' + import Collapsible from '$lib/Collapsible.svelte' + + export let data: CleanMemberProfile export let pack: string const categories = [ - 'skills', 'deaths', 'kills', 'auctions', @@ -59,7 +63,7 @@ <Head title="{data.member.username}'s SkyBlock profile ({data.member.profileName})" - description={generateInfobox(data, { meta: true }).join('\n')} + description={generateInfobox(data).join('\n')} metaTitle={(data.member.rank.name ? `[${data.member.rank.name}] ` : '') + `${data.member.username}\'s SkyBlock profile (${data.member.profileName})`} /> @@ -67,9 +71,10 @@ <main> <h1> - <Username player={data.member} headType="3d" /> - {#if data.customization?.emoji} - <span class="profile-emoji"><Emoji value={data.customization.emoji} /></span> + <!-- this is weird like this so svelte doesn't add whitespace --> + <Username player={data.member} headType="3d" />{#if data.customization?.emoji}<span + class="profile-emoji"><Emoji value={data.customization.emoji} /></span + > {/if} ({data.member.profileName}) </h1> @@ -78,7 +83,7 @@ <Toc {categories} /> - {#if data.member.skills.length > 0} + {#if data.member.skills && data.member.skills.length > 0} <section id="skills" class="profile-skills"> <h2>Skills</h2> <Skills {data} /> @@ -89,38 +94,30 @@ <div> <div id="categories"> - {#if data.member.inventories.armor} + {#if data.member.inventories?.armor} <section id="armor" class:armor-float={data.member.inventories.inventory}> <h2>Armor</h2> <Armor {data} {pack} /> </section> {/if} - {#if data.member.inventories.inventory} + {#if data.member.inventories?.inventory} <section id="inventories"> <h2>Inventories</h2> <Inventories {data} {pack} /> </section> {/if} - - <!-- {%- if data.member.inventories.inventory -%} - <section id="inventories"> - <h2>Inventories</h2> - {%- include 'sections/inventories.njk' -%} - </section> - {%- endif -%} - {%- asyncAll category in categories -%} - {%- set sectionContents -%} - {% with { data: data, category: category } %} - {%- include 'sections/' + category + '.njk' -%} - {% endwith %} - {%- endset -%} - {%- if sectionContents|trim and sectionContents|trim != '<ul></ul>' -%} - <section id="{{ category }}" class="collapsible"> - <h2>{{ category|replace('_', ' ')|title }}</h2> - {{- sectionContents|safe -}} - </section> - {%- endif -%} - {%- endall -%} --> + {#if data.member.stats} + {#each categories as category} + {#if data.member.stats?.find(s => s.category === category)} + <section id={category}> + <Collapsible> + <h2 slot="title">{cleanId(category)}</h2> + <StatList stats={data.member.stats.filter(s => s.category === category)} /> + </Collapsible> + </section> + {/if} + {/each} + {/if} </div> </div> </main> @@ -132,4 +129,22 @@ margin: 1em; margin-top: 1.6em; } + + #armor.armor-float { + float: left; + } + + #armor { + margin-right: 2em; + height: 16em; + } + + #inventories { + display: inline-block; + min-height: 16em; + } + + section { + margin-bottom: 0.5em; + } </style> diff --git a/src/routes/player/[player]/index.svelte b/src/routes/player/[player]/index.svelte index 974f74b..8242887 100644 --- a/src/routes/player/[player]/index.svelte +++ b/src/routes/player/[player]/index.svelte @@ -15,7 +15,7 @@ return { redirect: `/player/${data.player.username}`, status: 302, - } + } as any } return { @@ -27,23 +27,26 @@ </script> <script lang="ts"> + import type { CleanProfile, CleanUser } from '$lib/APITypes' import Username from '$lib/minecraft/Username.svelte' import Header from '$lib/Header.svelte' import Head from '$lib/Head.svelte' - export let data + export let data: CleanUser - let activeProfile = null + let activeProfile: CleanProfile | null = null let activeProfileLastSave: number = 0 - for (const profile of data.profiles) { - for (const member of profile.members) { - if (member.uuid === data.player.uuid && member.last_save > activeProfileLastSave) { - activeProfile = profile - activeProfileLastSave = member.last_save - } + if (data.profiles) + for (const profile of data.profiles) { + if (profile.members) + for (const member of profile.members) { + if (member.uuid === data.player?.uuid && member.last_save > activeProfileLastSave) { + activeProfile = profile + activeProfileLastSave = member.last_save + } + } } - } const isActiveProfileOnline = Date.now() / 1000 - 60 < activeProfileLastSave @@ -55,35 +58,35 @@ {@html bodyStyle} </svelte:head> -<Head title="{data.player.username}'s SkyBlock profiles" /> +<Head title={data.player ? `${data.player.username}'s SkyBlock profiles` : 'Invalid player'} /> <Header /> <main> <h1><Username player={data.player} headType="3d" />'s profiles</h1> <ul class="profile-list"> - {#each data.profiles as profile} + {#each data.profiles ?? [] as profile} <li class="profile-list-item" - class:profile-list-item-active={profile.uuid === activeProfile.uuid} - class:profile-list-item-online={profile.uuid === activeProfile.uuid && + class:profile-list-item-active={profile.uuid === activeProfile?.uuid} + class:profile-list-item-online={profile.uuid === activeProfile?.uuid && isActiveProfileOnline} > <a class="profile-name" - href="/player/{data.player.username}/{profile.name}" + href="/player/{data.player?.username}/{profile.name}" sveltekit:prefetch > {profile.name} </a> <span class="profile-members"> - {#if profile.members.length > 1} - {#each profile.members as player} + {#if (profile.members?.length ?? 0) > 1} + {#each profile.members ?? [] as player} <span class="member"> <Username {player} headType="2d" - hyperlinkToProfile={player.uuid != data.player.uuid} + hyperlinkToProfile={player.uuid != data.player?.uuid} /> </span> {/each} diff --git a/src/routes/todos/_api.ts b/src/routes/todos/_api.ts deleted file mode 100644 index f8bcf73..0000000 --- a/src/routes/todos/_api.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - This module is used by the /todos and /todos/[uid] - endpoints to make calls to api.svelte.dev, which stores todos - for each user. The leading underscore indicates that this is - a private module, _not_ an endpoint โ visiting /todos/_api - will net you a 404 response. - - (The data on the todo app will expire periodically; no - guarantees are made. Don't use it to organise your life.) -*/ - -const base = 'https://api.svelte.dev'; - -export async function api(request: Request, resource: string, data?: Record<string, unknown>) { - return fetch(`${base}/${resource}`, { - method: request.method, - headers: { - 'content-type': 'application/json' - }, - body: data && JSON.stringify(data) - }); -} diff --git a/src/routes/todos/index.svelte b/src/routes/todos/index.svelte deleted file mode 100644 index e23c1a1..0000000 --- a/src/routes/todos/index.svelte +++ /dev/null @@ -1,186 +0,0 @@ -<script lang="ts"> - import { enhance } from '$lib/form' - import { scale } from 'svelte/transition' - import { flip } from 'svelte/animate' - - type Todo = { - uid: string - created_at: Date - text: string - done: boolean - pending_delete: boolean - } - - export let todos: Todo[] -</script> - -<svelte:head> - <title>Todos</title> -</svelte:head> - -<div class="todos"> - <h1>Todos</h1> - - <form - class="new" - action="/todos" - method="post" - use:enhance={{ - result: async ({ form }) => { - form.reset() - }, - }} - > - <input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" /> - </form> - - {#each todos as todo (todo.uid)} - <div - class="todo" - class:done={todo.done} - transition:scale|local={{ start: 0.7 }} - animate:flip={{ duration: 200 }} - > - <form - action="/todos?_method=PATCH" - method="post" - use:enhance={{ - pending: ({ data }) => { - todo.done = !!data.get('done') - }, - }} - > - <input type="hidden" name="uid" value={todo.uid} /> - <input type="hidden" name="done" value={todo.done ? '' : 'true'} /> - <button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" /> - </form> - - <form class="text" action="/todos?_method=PATCH" method="post" use:enhance> - <input type="hidden" name="uid" value={todo.uid} /> - <input aria-label="Edit todo" type="text" name="text" value={todo.text} /> - <button class="save" aria-label="Save todo" /> - </form> - - <form - action="/todos?_method=DELETE" - method="post" - use:enhance={{ - pending: () => (todo.pending_delete = true), - }} - > - <input type="hidden" name="uid" value={todo.uid} /> - <button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} /> - </form> - </div> - {/each} -</div> - -<style> - .todos { - width: 100%; - max-width: var(--column-width); - margin: var(--column-margin-top) auto 0 auto; - line-height: 1; - } - - .new { - margin: 0 0 0.5rem 0; - } - - input { - border: 1px solid transparent; - } - - input:focus-visible { - box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1); - border: 1px solid #ff3e00 !important; - outline: none; - } - - .new input { - font-size: 28px; - width: 100%; - padding: 0.5em 1em 0.3em 1em; - box-sizing: border-box; - background: rgba(255, 255, 255, 0.05); - border-radius: 8px; - text-align: center; - } - - .todo { - display: grid; - grid-template-columns: 2rem 1fr 2rem; - grid-gap: 0.5rem; - align-items: center; - margin: 0 0 0.5rem 0; - padding: 0.5rem; - background-color: white; - border-radius: 8px; - filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1)); - transform: translate(-1px, -1px); - transition: filter 0.2s, transform 0.2s; - } - - .done { - transform: none; - opacity: 0.4; - filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1)); - } - - form.text { - position: relative; - display: flex; - align-items: center; - flex: 1; - } - - .todo input { - flex: 1; - padding: 0.5em 2em 0.5em 0.8em; - border-radius: 3px; - } - - .todo button { - width: 2em; - height: 2em; - border: none; - background-color: transparent; - background-position: 50% 50%; - background-repeat: no-repeat; - } - - button.toggle { - border: 1px solid rgba(0, 0, 0, 0.2); - border-radius: 50%; - box-sizing: border-box; - background-size: 1em auto; - } - - .done .toggle { - background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); - } - - .delete { - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A"); - opacity: 0.2; - } - - .delete:hover, - .delete:focus { - transition: opacity 0.2s; - opacity: 1; - } - - .save { - position: absolute; - right: 0; - opacity: 0; - background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A"); - } - - .todo input:focus + .save, - .save:focus { - transition: opacity 0.2s; - opacity: 1; - } -</style> diff --git a/src/routes/todos/index.ts b/src/routes/todos/index.ts deleted file mode 100644 index 129b60a..0000000 --- a/src/routes/todos/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { api } from './_api'; -import type { RequestHandler } from '@sveltejs/kit'; - -export const get: RequestHandler = async ({ request, locals }) => { - // locals.userid comes from src/hooks.js - const response = await api(request, `todos/${locals.userid}`); - - if (response.status === 404) { - // user hasn't created a todo list. - // start with an empty array - return { - body: { - todos: [] - } - }; - } - - if (response.ok) { - return { - body: { - todos: await response.json() - } - }; - } - - return { - status: response.status - }; -}; - -export const post: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}`, { - text: form.get('text') - }); -}; - -export const patch: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}/${form.get('uid')}`, { - text: form.has('text') ? form.get('text') : undefined, - done: form.has('done') ? !!form.get('done') : undefined - }); -}; - -export const del: RequestHandler = async ({ request, locals }) => { - const form = await request.formData(); - - return api(request, `todos/${locals.userid}/${form.get('uid')}`); -}; diff --git a/svelte.config.js b/svelte.config.js index 94e8aac..056d7f0 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -2,7 +2,7 @@ import adapter from '@sveltejs/adapter-node' // import adapter from '@sveltejs/adapter-vercel' import preprocess from 'svelte-preprocess' -import { minifyHtml } from 'vite-plugin-html' +// import { createHtmlPlugin } from 'vite-plugin-html' /** @type {import('@sveltejs/kit').Config} */ const config = { @@ -28,7 +28,9 @@ const config = { // https://vitejs.dev/config/ // vite: { - // plugins: [minifyHtml()], + // plugins: [createHtmlPlugin({ + // minify: true + // })], // build: { // rollupOptions: { // external: ['discord-api-types/payloads/v9', 'discord-api-types', 'discord-api-types/v9'], diff --git a/tsconfig.json b/tsconfig.json index 5ea97dc..dec5596 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,7 +30,8 @@ "paths": { "$lib": ["src/lib"], "$lib/*": ["src/lib/*"] - } + }, + "strictNullChecks": true }, "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] } @@ -365,6 +365,11 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -466,6 +471,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.16: + version "2.0.16" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -481,6 +491,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +consola@^2.15.3: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -495,6 +515,22 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + debug@^4.0.1, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" @@ -526,6 +562,36 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -534,15 +600,15 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv-expand@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.1.tgz#332aa17c14b12e28e2e230f8d183eecc1c014fdc" + integrity sha512-j/Ih7bIERDR5PzI89Zu8ayd3tXZ6E3dbY0ljQ9Db0K87qBO8zdLsi2dIvDHMWtjC3Yxb8XixOTHAtia0fDHRpg== -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== ejs@^3.1.6: version "3.1.6" @@ -563,6 +629,11 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + es6-promise@^3.1.2: version "3.3.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613" @@ -1072,7 +1143,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.7, fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -1233,7 +1304,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -he@^1.2.0: +he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -1484,11 +1555,26 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-html-parser@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.2.0.tgz#6f29fd00d79f65334e7e20200964644207925607" + integrity sha512-fmiwLfQu+J2A0zjwSEkztSHexAf5qq/WoiL/Hgo1K7JpfEP+OGWY5maG0kGaM+IFVdixF/1QbyXaQ3h4cGfeLw== + dependencies: + css-select "^4.1.3" + he "1.2.0" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1551,6 +1637,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.2.0.tgz#30fd7bbe0a0d91f0e60bae621f5d19e9e225c339" + integrity sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -1705,10 +1796,10 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -skyblock-assets@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/skyblock-assets/-/skyblock-assets-2.0.3.tgz#7e31288406bb3a4e9a5eb2e8bbd1dfcb2ea4aa1b" - integrity sha512-5XB7/yhb8gwPagTkgFxodS4riYkjCuHoHhtVWtDredEwqGkzTMPnLoISidArSlPeRJRYrAp4Dz7Cou+Mbenx0g== +skyblock-assets@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/skyblock-assets/-/skyblock-assets-2.0.4.tgz#a4d5677ec4bbc38647eb447de50649ad1b34d27d" + integrity sha512-DM55zASqtf4/24dgPmzvEg17ADXvCZl+S8YPHCJCjECQgxVHkKGTJoOE6mUeHhEuUIEOlX4jyWMaZdtmuFDFnQ== slash@^3.0.0: version "3.0.0" @@ -1942,17 +2033,23 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite-plugin-html@^2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-2.1.2.tgz#aaf846a806b37e4884870e5ce1918166e9ce9125" - integrity sha512-7HXkL6n7M2qDEaUV4Vnz8yM2glW4gV36d5HSBIM5gOoAG1PkuQb4Vv9FTPgPiQxq4sPRf/6IgABX0MeLVW+CyQ== +vite-plugin-html@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-3.0.6.tgz#8edebff956aa05529606a9753c71979bb311f634" + integrity sha512-B6ZAufvqUqdfPhcV+El8NNI3qV0d3ZntIur2UnP4tcHBV/O2d+6wHF79bJWuqR4WsvmsV7dejCNS3rAYFCisWw== dependencies: "@rollup/pluginutils" "^4.1.2" - dotenv "^10.0.0" - dotenv-expand "^5.1.0" + colorette "^2.0.16" + connect-history-api-fallback "^1.6.0" + consola "^2.15.3" + dotenv "^16.0.0" + dotenv-expand "^8.0.1" ejs "^3.1.6" + fast-glob "^3.2.11" fs-extra "^10.0.0" html-minifier-terser "^6.1.0" + node-html-parser "^5.2.0" + pathe "^0.2.0" vite@^2.8.0: version "2.8.2" |