aboutsummaryrefslogtreecommitdiff
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
parent582409e7cb1598b65bee6d1023b77620bb3791af (diff)
downloadskyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.gz
skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.tar.bz2
skyblock-stats-13e5974114f759bae73f3bfd68c62ce9cfaf785e.zip
add more stuff to profile and fix bugs
-rw-r--r--package.json4
-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
-rw-r--r--src/routes/player/[player]/[profile].svelte73
-rw-r--r--src/routes/player/[player]/index.svelte39
-rw-r--r--src/routes/todos/_api.ts22
-rw-r--r--src/routes/todos/index.svelte186
-rw-r--r--src/routes/todos/index.ts52
-rw-r--r--svelte.config.js6
-rw-r--r--tsconfig.json3
-rw-r--r--yarn.lock137
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('<', '&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
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"]
}
diff --git a/yarn.lock b/yarn.lock
index 0b712e8..ad3d240 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"