aboutsummaryrefslogtreecommitdiff
path: root/src/routes
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/auctionprices.svelte210
-rw-r--r--src/routes/index.svelte2
2 files changed, 211 insertions, 1 deletions
diff --git a/src/routes/auctionprices.svelte b/src/routes/auctionprices.svelte
new file mode 100644
index 0000000..9e7615c
--- /dev/null
+++ b/src/routes/auctionprices.svelte
@@ -0,0 +1,210 @@
+<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 auctionItemsPromise = fetch(`${API_URL}auctionitems`).then(r => r.json())
+ const data = await fetch(`${API_URL}auctionprices`).then(r => r.json())
+ const auctionItems = await auctionItemsPromise
+
+ return {
+ props: {
+ data,
+ auctionItems,
+ },
+ }
+ }
+</script>
+
+<script lang="ts">
+ import Header from '$lib/Header.svelte'
+ import Head from '$lib/Head.svelte'
+ import { cleanId, removeFormattingCode, toTitleCase, type PreviewedAuctionData } from '$lib/utils'
+ import type { ItemAuctionsSchema } from '$lib/APITypes'
+ import AuctionPriceScatterplot from '$lib/AuctionPriceScatterplot.svelte'
+ import AuctionPreviewTooltip from '$lib/AuctionPreviewTooltip.svelte'
+ import { browser } from '$app/env'
+ import Item from '$lib/minecraft/Item.svelte'
+ import furfskyReborn from 'skyblock-assets/matchers/furfsky_reborn.json'
+
+ export let data: ItemAuctionsSchema[]
+ export let auctionItems: Record<string, { display: { name: string }; vanillaId?: string }>
+
+ let currentlyPreviewedAuction: PreviewedAuctionData | null = null
+
+ let query: string = ''
+
+ $: queryNormalized = query.toLowerCase()
+
+ let allMatchingItemIds: string[]
+ $: {
+ pageNumber = 0
+ allMatchingItemIds = Object.entries(auctionItems)
+ .filter(a => removeFormattingCode(a[1]?.display.name.toLowerCase()).includes(queryNormalized))
+ .map(a => a[0])
+ }
+ $: {
+ if (browser) fetchAndSetItems(allMatchingItemIds.slice(0, 100))
+ }
+
+ async function fetchAndSetItems(itemIds: string[]) {
+ const localQuery = query
+ const localData = await fetchItems(query.length > 0 ? itemIds : null)
+ // if the query hasn't changed, update the data
+ if (query === localQuery) data = localData
+ }
+ async function fetchItems(itemIds: null | string[]): Promise<ItemAuctionsSchema[]> {
+ let url = `${API_URL}auctionprices`
+ if (itemIds !== null) {
+ if (itemIds.length === 0) return []
+ url += `?items=${itemIds.join(',')}`
+ }
+ return await fetch(url).then(r => r.json())
+ }
+
+ let pageHeight = 0
+ $: {
+ pageHeight = 0
+ }
+
+ // 0 indexed
+ let pageNumber = 0
+ let loadingPage = false
+
+ async function checkScroll() {
+ if (loadingPage) return
+
+ let pageHeightTemp = window.scrollY + window.innerHeight
+ if (pageHeightTemp <= pageHeight) return
+ pageHeight = pageHeightTemp
+ if (pageHeight >= document.body.scrollHeight - 1000) {
+ loadingPage = true
+ pageNumber++
+ const itemIds = allMatchingItemIds.slice(pageNumber * 100, (pageNumber + 1) * 100)
+ if (itemIds.length > 0) {
+ const shownIds = data.map(d => d.id)
+ const items = (await fetchItems(itemIds)).filter(i => !shownIds.includes(i.id))
+ if (items.length > 0) data = [...data, ...items]
+ }
+ loadingPage = false
+ }
+ }
+
+ $: {
+ if (browser && !currentlyPreviewedAuction) {
+ for (const el of document.getElementsByClassName('selected-auction'))
+ el.classList.remove('selected-auction')
+ }
+ }
+</script>
+
+<Head title="SkyBlock Auction Prices" />
+<Header />
+
+<svelte:window on:scroll={checkScroll} />
+
+<AuctionPreviewTooltip bind:preview={currentlyPreviewedAuction} />
+
+<main>
+ <h1>SkyBlock Auction Prices</h1>
+ <div class="filter-items-settings">
+ <input type="text" id="filter-items-tier" placeholder="Search..." bind:value={query} />
+ </div>
+ <div class="item-list">
+ {#each data as item (item.id)}
+ {@const binAuctions = item.auctions.filter(i => i.bin)}
+ {@const normalAuctions = item.auctions.filter(i => !i.bin)}
+ {@const itemData = auctionItems[item.sbId]}
+ <div class="item-container">
+ {#if itemData && itemData.vanillaId}
+ <div class="item-slot-container">
+ <Item item={{ ...itemData, id: item.id }} pack={furfskyReborn} headSize={50} isslot />
+ </div>
+ {/if}
+ <h2>
+ {removeFormattingCode(
+ auctionItems[item.id]?.display.name ?? toTitleCase(cleanId(item.id.toLowerCase()))
+ )}
+ </h2>
+ <div class="auctions-info-text">
+ {#if binAuctions.length > 0}
+ <p>
+ Cheapest recent BIN: <b>
+ {binAuctions.reduce((a, b) => (a.coins < b.coins ? a : b)).coins.toLocaleString()} coins
+ </b>
+ </p>
+ {/if}
+ {#if normalAuctions.length > 0}
+ <p>
+ Cheapest recent auction: <b>
+ {normalAuctions
+ .reduce((a, b) => (a.coins < b.coins ? a : b))
+ .coins.toLocaleString()} coins
+ </b>
+ </p>
+ {/if}
+ {#if item.auctions.length >= 2}
+ <p>
+ Median:
+ <b>
+ {[...item.auctions]
+ .sort((a, b) => a.coins - b.coins)
+ [Math.floor(item.auctions.length / 2)].coins.toLocaleString()} coins
+ </b>
+ </p>
+ <p>
+ Volume:
+ <b>
+ {parseFloat(
+ (
+ (24 * 60 * 60) /
+ ((Date.now() / 1000 - item.auctions[0].ts) / item.auctions.length)
+ ).toPrecision(2)
+ ).toLocaleString()}/day
+ </b>
+ </p>
+ {/if}
+ </div>
+ <div class="item-scatterplot">
+ <AuctionPriceScatterplot {item} bind:currentlyPreviewedAuction />
+ </div>
+ </div>
+ {/each}
+ {#if data.length === 0}
+ No results
+ {/if}
+ </div>
+</main>
+
+<style>
+ p {
+ margin: 0;
+ }
+ .item-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));
+ grid-gap: 1em;
+ margin-top: 1em;
+ }
+ .item-container {
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ background: rgba(0, 0, 0, 0.1);
+ padding: 0.75em;
+ border-radius: 1em;
+ }
+
+ .item-scatterplot {
+ margin-top: 1em;
+ }
+
+ .auctions-info-text {
+ color: var(--theme-darker-text);
+ }
+ .auctions-info-text b {
+ color: var(--theme-main-text);
+ }
+
+ .item-slot-container {
+ float: right;
+ }
+</style>
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
index 4c11a88..d22f854 100644
--- a/src/routes/index.svelte
+++ b/src/routes/index.svelte
@@ -49,7 +49,7 @@
<section>
<h2>Other SkyBlock tools</h2>
<ul id="other-tools-list">
- <li><a>Auction prices (coming soon)</a></li>
+ <li><a href="/auctionprices">Auction prices</a></li>
<li><a href="/leaderboards">Leaderboards</a></li>
<li><a>Bazaar (coming soon)</a></li>
<li><a href="/chat">Fake chat generator</a></li>