Base Usage
import { h } from "onejs-preact"
import { VirtualList } from "@/comps/echo/virtual"
import { useEffect, useState } from "onejs-preact/hooks"
const items = Array.from({ length: 1000 }, (_, i) => `${['Solar', 'Lunar', 'Stellar', 'Galactic', 'Nebular'][i % 5]} ${['Voyager', 'Ranger', 'Explorer', 'Pilot', 'Navigator'][i % 5]}`)
const palette = [ "bg-amber-200", "bg-stone-200" ]
export const VirtualBaseDemo = () => (
<div class="echo-theme w-full h-full p-20 justify-center items-center bold">
<VirtualList
class="w-[340px]"
data={items}
rowHeight={50}
overscanCount={2}
renderRow={Row}
scrollviewProps={{ mouseWheelScrollSize: 200 }}
/>
</div>
)
function Row(item: string, index: number) {
const [ready, setReady] = useState(false)
useEffect(() => { // Simulate a delay to show the loading state
const id = setTimeout(() => { setReady(true) }, 200)
return () => { setReady(false); clearTimeout(id) }
}, [])
return ready ? (
<div class={`h-[50px] flex-row items-center px-4 rounded mb-1 ${palette[index % palette.length]}`}>
<div class="mr-2">{`${index + 1}.`}</div>
<div class="grow shrink">{item}</div>
</div>
) : <div class="h-[50px] mb-1 rounded bg-gray-200"></div>
}
Path of Exile 2 Leaderboard Sample
import { h } from "preact"
import { VirtualList } from "@/comps/echo/virtual"
import { useEffect, useState } from "preact/hooks"
const webapi = new CS.OneJS.WebApi()
export const PoELeaderboardSimple = () => {
const [entries, setEntries] = useState<Entry[]>([])
const [ladder, setLadder] = useState<string>("standard")
useEffect(() => {
const endpoint = "https://pathofexile2.com/internal-api/content/game-ladder/id/Standard"
webapi.getText(endpoint, text => {
const res: LeaderboardResponseData = JSON.parse(text)
setEntries(res.context.ladder.entries)
})
}, [])
useEffect(() => {
setEntries([])
fetchLeaderboard(ladder, (res: LeaderboardResponseData) => {
setEntries(res.context.ladder.entries)
})
}, [ladder])
return <div class="echo-theme h-[300px] w-[340px]">
<div class="flex-row mb-2 justify-between">
<button onClick={() => setLadder("standard")}>Standard</button>
<button onClick={() => setLadder("hardcore")}>Harcore</button>
<button onClick={() => setLadder("ssf")}>SSF</button>
<button onClick={() => setLadder("hardcore_ssf")}>Harcore SSF</button>
</div>
<div class="flex-row h-[48px] mb-[2px] text-[#eee] bold">
<Cell class="w-[60px] bg-[#306c9d]">Rank</Cell>
<Cell class="grow shrink bg-[#306c9d]">Account</Cell>
<Cell class="w-[65px] bg-[#306c9d]">Level</Cell>
</div>
{entries.length ? (
<VirtualList
class="h-full [&_Scroller]:absolute [&_ScrollView_Scroller]:right-[-12px]"
data={entries}
rowHeight={48}
overscanCount={2}
renderRow={Row}
/>
) : (
<div class="flex-row justify-center items-center h-[300px] text-white text-xl">
Loading...
</div>
)}
</div>
}
function Row(entry: Entry) {
const [ready, setReady] = useState(false)
useEffect(() => { // Simulate a delay to show the loading state
const id = setTimeout(() => { setReady(true) }, 150)
return () => { setReady(false); clearTimeout(id) }
}, [])
return ready ? (
<div class="flex-row h-[48px] mb-[1px] bg-[#212f4bbc] text-[#fff]">
<Cell class="w-[60px]">{entry.rank}</Cell>
<Cell class="grow shrink">{entry.account.name}</Cell>
<Cell class="w-[65px]">{entry.character.level}</Cell>
</div>
) : <div class="h-[50px] mb-1 bg-[#212f4bbc]"></div>
}
function Cell({ children, class: cn }: any) {
return <div class={`p-3 mr-[1px] text-sm text-center bold ${cn}`}>{children}</div>
}
function fetchLeaderboard(ladder: string, callback: Function) {
const endpoints = {
"standard": "https://pathofexile2.com/internal-api/content/game-ladder/id/Standard",
"hardcore": "https://pathofexile2.com/internal-api/content/game-ladder/id/Hardcore",
"ssf": "https://pathofexile2.com/internal-api/content/game-ladder/id/Solo%20Self-Found",
"hardcore_ssf": "https://pathofexile2.com/internal-api/content/game-ladder/id/Hardcore%20SSF"
} as { [key: string]: string }
const endpoint = endpoints[ladder]
webapi.getText(endpoint, (text) => {
const resObj = JSON.parse(text)
callback(resObj)
}, JSON.stringify({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0"
}))
}
interface LeaderboardResponseData {
context: { ladder: { entries: Entry[] } }
}
interface Entry {
rank: number
account: { name: string }
character: { level: number }
}