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