diff options
Diffstat (limited to 'src/routes/auctionprices/+page.svelte')
-rw-r--r-- | src/routes/auctionprices/+page.svelte | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/src/routes/auctionprices/+page.svelte b/src/routes/auctionprices/+page.svelte new file mode 100644 index 0000000..844af04 --- /dev/null +++ b/src/routes/auctionprices/+page.svelte @@ -0,0 +1,198 @@ +<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/environment' + import Item from '$lib/minecraft/Item.svelte' + import furfskyReborn from 'skyblock-assets/matchers/furfsky_reborn.json' + import type { PageData } from './$types' + import { fetchApi } from '$lib/api' + + export let data: PageData + let auctionPrices: ItemAuctionsSchema[] = data.prices + let auctionItems: Record<string, { display: { name: string }; vanillaId?: string }> = data.items + + 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 = `auctionprices` + if (itemIds !== null) { + if (itemIds.length === 0) return [] + url += `?items=${itemIds.join(',')}` + } + return await fetchApi(url, fetch).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" + description="View how many coins every SkyBlock item costs in the auction house." +/> +<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> |