aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/utils.ts20
-rw-r--r--src/routes/leaderboard/[name].ts10
-rw-r--r--src/routes/leaderboard/index.ts8
-rw-r--r--src/routes/leaderboards/[name].svelte86
-rw-r--r--src/routes/leaderboards/index.svelte54
5 files changed, 178 insertions, 0 deletions
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index c581af0..aabd981 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -114,6 +114,13 @@ export function cleanId(id: string) {
.replace(/_/g, ' ')
}
+export function toTitleCase(s: string) {
+ return s.replace(
+ /\w\S*/g,
+ w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
+ )
+}
+
export function toRomanNumerals(number: number) {
return ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX'][number]
}
@@ -151,4 +158,17 @@ export function formatNumber(n: number, digits = 3) {
]
const item = numberSymbolsLookup.slice().reverse().find(item => n >= item.value)
return (n / (item?.value ?? 1)).toPrecision(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + (item?.symbol ?? '')
+}
+
+export function formatNumberFromUnit(n: number, unit: null | 'date' | 'time' | string) {
+ switch (unit) {
+ case null:
+ return n.toLocaleString()
+ case 'date':
+ return (new Date(n * 1000)).toUTCString()
+ case 'time':
+ return millisecondsToTime(Math.abs(n))
+ default:
+ return `${n.toLocaleString()} ${unit}`
+ }
} \ No newline at end of file
diff --git a/src/routes/leaderboard/[name].ts b/src/routes/leaderboard/[name].ts
new file mode 100644
index 0000000..fac1ee9
--- /dev/null
+++ b/src/routes/leaderboard/[name].ts
@@ -0,0 +1,10 @@
+// The route /leaderboard/<name> was moved to /leaderboards/<name> for
+// consistency.
+export async function get({ params }) {
+ return {
+ status: 303,
+ headers: {
+ location: `/leaderboards/${params.name}`
+ }
+ }
+}
diff --git a/src/routes/leaderboard/index.ts b/src/routes/leaderboard/index.ts
new file mode 100644
index 0000000..beda8d6
--- /dev/null
+++ b/src/routes/leaderboard/index.ts
@@ -0,0 +1,8 @@
+export async function get({ request }) {
+ return {
+ status: 303,
+ headers: {
+ location: '/leaderboards'
+ }
+ }
+}
diff --git a/src/routes/leaderboards/[name].svelte b/src/routes/leaderboards/[name].svelte
new file mode 100644
index 0000000..2aec4b3
--- /dev/null
+++ b/src/routes/leaderboards/[name].svelte
@@ -0,0 +1,86 @@
+<script lang="ts" context="module">
+ import type { Load } from '@sveltejs/kit'
+ import { API_URL } from '$lib/api'
+
+ export const load: Load = async ({ params, fetch }) => {
+ const data = await fetch(`${API_URL}leaderboard/${params.name}`).then(r => r.json())
+
+ if (data.list.length === 0) return { fallthrough: true } as unknown
+
+ return {
+ props: {
+ data,
+ },
+ } as any
+ }
+</script>
+
+<script lang="ts">
+ import Header from '$lib/Header.svelte'
+ import Head from '$lib/Head.svelte'
+ import Toc from '$lib/Toc.svelte'
+ import Collapsible from '$lib/Collapsible.svelte'
+ import { skyblockItemNameToItem, skyblockItemToUrl } from '$lib/minecraft/inventory'
+ import { cleanId, formatNumberFromUnit, toTitleCase } from '$lib/utils'
+ import ListItemWithIcon from '$lib/ListItemWithIcon.svelte'
+ import Leaderboards from '$lib/sections/Leaderboards.svelte'
+ import Username from '$lib/minecraft/Username.svelte'
+
+ export let data
+
+ $: imageUrl = data.name.startsWith('collection_') ? skyblockItemToUrl(data.name.slice(11)) : null
+</script>
+
+<Head title={`${cleanId(data.name)} - SkyBlock Leaderboards`} />
+<Header backArrowHref="/leaderboards" />
+
+<main>
+ <h1>
+ {#if imageUrl}
+ <img src={imageUrl} alt={cleanId(data.name.slice(11))} />
+ {/if}
+ {toTitleCase(cleanId(data.name))}
+ </h1>
+ {#if data.info}
+ <p class="leaderboard-info">{data.info}</p>
+ {/if}
+
+ <ol class="leaderboard-profile-list">
+ {#each data.list as leaderboardItem}
+ <li>
+ <span>
+ {formatNumberFromUnit(
+ leaderboardItem.value,
+ leaderboardItem.unit ?? cleanId(data.name).toLowerCase()
+ )}
+ </span>
+ {#if leaderboardItem.player}
+ <Username
+ player={leaderboardItem.player}
+ headType="2d"
+ hyperlinkToProfile={leaderboardItem.profileUuid}
+ />
+ {:else if leaderboardItem.players}
+ {#each leaderboardItem.players as player}
+ <span class="leaderboard-profile-player">
+ <Username {player} headType="2d" hyperlinkToProfile />
+ </span>
+ {/each}
+ {:else}
+ Unknown player
+ {/if}
+ </li>
+ {/each}
+ </ol>
+</main>
+
+<style>
+ h1 > img {
+ height: 1em;
+ vertical-align: text-top;
+ }
+
+ .leaderboard-profile-player {
+ margin-right: 0.5em;
+ }
+</style>
diff --git a/src/routes/leaderboards/index.svelte b/src/routes/leaderboards/index.svelte
new file mode 100644
index 0000000..b6815e5
--- /dev/null
+++ b/src/routes/leaderboards/index.svelte
@@ -0,0 +1,54 @@
+<script lang="ts" context="module">
+ import type { Load } from '@sveltejs/kit'
+ import { API_URL } from '$lib/api'
+
+ export const load: Load = async ({ params, fetch }) => {
+ const data = await fetch(`${API_URL}leaderboards`).then(r => r.json())
+
+ return {
+ props: {
+ data,
+ },
+ }
+ }
+</script>
+
+<script lang="ts">
+ import Header from '$lib/Header.svelte'
+ import Head from '$lib/Head.svelte'
+ import Toc from '$lib/Toc.svelte'
+ import Collapsible from '$lib/Collapsible.svelte'
+ import { skyblockItemNameToItem, skyblockItemToUrl } from '$lib/minecraft/inventory'
+ import { cleanId } from '$lib/utils'
+ import ListItemWithIcon from '$lib/ListItemWithIcon.svelte'
+
+ export let data: { [category: string]: string[] }
+</script>
+
+<Head title="Hypixel SkyBlock Leaderboards" />
+<Header />
+
+<main>
+ <Toc categories={Object.keys(data)} />
+
+ {#each Object.entries(data) as [category, leaderboards]}
+ <section>
+ <Collapsible id={category}>
+ <ul>
+ {#each leaderboards as leaderboard}
+ {@const imageUrl = leaderboard.startsWith('collection_')
+ ? skyblockItemToUrl(leaderboard.slice(11))
+ : null}
+ {#if imageUrl}
+ <ListItemWithIcon url={imageUrl}>
+ <a href="/leaderboards/{leaderboard}">{cleanId(leaderboard)}</a>
+ </ListItemWithIcon>
+ {:else}
+ <li><a href="/leaderboards/{leaderboard}">{cleanId(leaderboard)}</a></li>
+ {/if}
+ {/each}
+ </ul>
+ </Collapsible>
+ </section>
+ {/each}
+</main>