Support displaying the latest Steam games
This adds some types and support on the homepage for displaying Steam games on the homepage. We'll fix the design later.
This commit is contained in:
parent
09c4b756b7
commit
2dc1e2a538
4 changed files with 148 additions and 42 deletions
94
src/lib/components/Game.svelte
Normal file
94
src/lib/components/Game.svelte
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts">
|
||||
import { spring } from 'svelte/motion'
|
||||
|
||||
interface GameProps {
|
||||
game?: SerializableGameInfo
|
||||
}
|
||||
|
||||
let { game = undefined }: GameProps = $props()
|
||||
|
||||
let isHovering = $state(false)
|
||||
|
||||
const scale = spring(1, {
|
||||
stiffness: 0.2,
|
||||
damping: 0.145
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (isHovering) {
|
||||
scale.set(1.1)
|
||||
} else {
|
||||
scale.set(1)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="game">
|
||||
{#if game}
|
||||
<a
|
||||
href={`https://store.steampowered.com/app/${game.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onmouseenter={() => (isHovering = true)}
|
||||
onmouseleave={() => (isHovering = false)}
|
||||
>
|
||||
<img src={game.coverURL} alt={game.name} style="transform: scale({$scale})" />
|
||||
<div class="info">
|
||||
<span class="game-name">
|
||||
{game.name}
|
||||
</span>
|
||||
<p class="game-playtime">
|
||||
{game.playtime} minutes played
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
{:else}
|
||||
<p>No album provided</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.game {
|
||||
flex-basis: 100%;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
gap: $unit * 1.5;
|
||||
text-decoration: none;
|
||||
transition: gap 0.125s ease-in-out;
|
||||
|
||||
img {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: $unit;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-fourth;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.game-name {
|
||||
font-size: $font-size;
|
||||
font-weight: $font-weight-med;
|
||||
color: $accent-color;
|
||||
}
|
||||
|
||||
.game-playtime {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-med;
|
||||
color: $grey-40;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
6
src/lib/types/steam.ts
Normal file
6
src/lib/types/steam.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
type SerializableGameInfo = {
|
||||
id: number
|
||||
name: string
|
||||
playtime: number
|
||||
coverURL: string
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import Album from '$components/Album.svelte'
|
||||
import Avatar from '$components/Avatar.svelte'
|
||||
import Game from '$components/Game.svelte'
|
||||
import MentionList from '$components/MentionList.svelte'
|
||||
import Page from '$components/Page.svelte'
|
||||
import ProjectList from '$components/ProjectList.svelte'
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
export let data: PageData
|
||||
|
||||
$: ({ albums, error } = data)
|
||||
$: ({ albums, games, error } = data)
|
||||
</script>
|
||||
|
||||
<Page>
|
||||
|
|
@ -67,6 +68,18 @@
|
|||
<p>Loading albums...</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="latest-games">
|
||||
{#if games.length > 0}
|
||||
<ul>
|
||||
{#each games.slice(0, 3) as game}
|
||||
<Game {game} />
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<p>Loading games...</p>
|
||||
{/if}
|
||||
</section>
|
||||
</Page>
|
||||
|
||||
<footer>
|
||||
|
|
@ -106,43 +119,14 @@
|
|||
flex-direction: row;
|
||||
gap: $unit-4x;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li {
|
||||
.latest-games ul {
|
||||
display: flex;
|
||||
flex-basis: 100%;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-fourth;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.album-name {
|
||||
font-size: $font-size;
|
||||
font-weight: $font-weight-med;
|
||||
}
|
||||
|
||||
.artist-name {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-med;
|
||||
color: $grey-40;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: $unit;
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
||||
flex-direction: row;
|
||||
gap: $unit-4x;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 0.85rem;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,36 @@
|
|||
import type { PageLoad } from './$types'
|
||||
import type { Album } from '$lib/types/lastfm'
|
||||
import type { GameInfoExtended, UserPlaytime } from 'steamapi'
|
||||
|
||||
export const load: PageLoad = async ({ fetch }) => {
|
||||
try {
|
||||
const response = await fetch('/api/lastfm')
|
||||
if (!response.ok) throw new Error(`Failed to fetch albums: ${response.status}`)
|
||||
const data: { albums: Album[] } = await response.json()
|
||||
return { albums: data.albums }
|
||||
const [albums, games] = await Promise.all([fetchRecentAlbums(fetch), fetchRecentGames(fetch)])
|
||||
console.log(games[0])
|
||||
return {
|
||||
albums,
|
||||
games
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching albums:', err)
|
||||
return { albums: [], error: err instanceof Error ? err.message : 'An unknown error occurred' }
|
||||
console.error('Error fetching data:', err)
|
||||
return {
|
||||
albums: [],
|
||||
games: [],
|
||||
error: err instanceof Error ? err.message : 'An unknown error occurred'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRecentAlbums(fetch: typeof window.fetch): Promise<Album[]> {
|
||||
const response = await fetch('/api/lastfm')
|
||||
if (!response.ok) throw new Error(`Failed to fetch albums: ${response.status}`)
|
||||
const musicData: { albums: Album[] } = await response.json()
|
||||
return musicData.albums
|
||||
}
|
||||
|
||||
async function fetchRecentGames(fetch: typeof window.fetch): Promise<SerializableGameInfo[]> {
|
||||
const response = await fetch('/api/steam')
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch recent game: ${response.status}`)
|
||||
}
|
||||
return await response.json()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue