mirror of
https://github.com/Kizuren/kizuren.dev.git
synced 2025-12-21 21:16:12 +01:00
Implement everything
This commit is contained in:
parent
d966dd9562
commit
30d747e046
26 changed files with 1069 additions and 6 deletions
142
pages/games.vue
Normal file
142
pages/games.vue
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="flex flex-col items-center justify-center gap-8 pt-8">
|
||||
<h1 class="font-bold text-5xl text-[--ui-primary] mt-10">
|
||||
Games
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl">
|
||||
<UCard
|
||||
v-for="game in games"
|
||||
:key="game.title"
|
||||
class="bg-gray-900 text-white shadow-lg"
|
||||
:ui="{
|
||||
root: 'bg-gray-900 text-white flex flex-col h-full shadow-lg rounded-lg ring-1 ring-gray-700',
|
||||
header: 'p-4 pb-0',
|
||||
body: 'flex-grow p-4 pt-2',
|
||||
footer: 'p-4 pt-0'
|
||||
}"
|
||||
>
|
||||
<!-- Game Logo and Status -->
|
||||
<div class="flex items-center justify-between">
|
||||
<img
|
||||
:src="getImageSrc(game)"
|
||||
alt="Game Logo"
|
||||
class="w-12 h-12 object-contain"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<UBadge
|
||||
:color="getBadgeColor(game.status)"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
class="font-semibold"
|
||||
>
|
||||
{{ game.status }}
|
||||
</UBadge>
|
||||
</div>
|
||||
|
||||
<!-- Game Title -->
|
||||
<h2 class="text-xl font-bold mt-4">{{ game.title }}</h2>
|
||||
|
||||
<!-- Game Description -->
|
||||
<p class="text-gray-400 mt-2">{{ game.description }}</p>
|
||||
|
||||
<!-- Separator -->
|
||||
<USeparator class="my-3" />
|
||||
|
||||
<!-- Game Links -->
|
||||
<div class="flex gap-4 mt-2">
|
||||
<a
|
||||
v-for="link in game.url || []"
|
||||
:key="link.url"
|
||||
:href="link.url"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 flex items-center gap-2 transition-colors"
|
||||
>
|
||||
<UIcon :name="link.logo" class="text-lg" />
|
||||
<span class="text-sm">{{ link.host }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import siteConfig from '~/public/site-config.json'
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const games = siteConfig.games
|
||||
const imageCache = ref(new Map())
|
||||
|
||||
function getBadgeColor(status: string) {
|
||||
if (status === 'Released') return 'success'
|
||||
if (status === 'Abandoned') return 'error'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
// Check if image exists and cache result
|
||||
function getImageSrc(game: { [x: string]: any }) {
|
||||
// During SSR, just return the URL or fallback
|
||||
if (typeof window === 'undefined') {
|
||||
return game['logo-url'] || '/cancel.svg'
|
||||
}
|
||||
|
||||
const url = game['logo-url']
|
||||
|
||||
// If no URL or already checked and failed, use cancel.svg
|
||||
if (!url || imageCache.value.get(url) === false) {
|
||||
return '/cancel.svg'
|
||||
}
|
||||
|
||||
// If not checked yet, check it now
|
||||
if (!imageCache.value.has(url)) {
|
||||
checkImage(url)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
// Function to check if image exists
|
||||
function checkImage(url: string | null) {
|
||||
// Skip this function if not in browser or url is null
|
||||
if (typeof window === 'undefined' || url === null) return
|
||||
|
||||
const img = new window.Image()
|
||||
img.onload = () => {
|
||||
imageCache.value.set(url, true)
|
||||
}
|
||||
img.onerror = () => {
|
||||
imageCache.value.set(url, false)
|
||||
// Force a component update for this URL
|
||||
const affectedGames = games.filter(g => g['logo-url'] === url)
|
||||
if (affectedGames.length) {
|
||||
// This will trigger a re-render
|
||||
imageCache.value = new Map(imageCache.value)
|
||||
}
|
||||
}
|
||||
img.src = url
|
||||
}
|
||||
|
||||
function handleImageError(event: Event) {
|
||||
const img = event.target as HTMLImageElement
|
||||
img.src = '/cancel.svg'
|
||||
|
||||
// Also cache this failure for future renders
|
||||
if (img.dataset.originalSrc) {
|
||||
imageCache.value.set(img.dataset.originalSrc, false)
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-check all images when component is mounted
|
||||
onMounted(() => {
|
||||
games.forEach(game => {
|
||||
if (game['logo-url']) {
|
||||
checkImage(game['logo-url'])
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
useHead({
|
||||
title: 'Games'
|
||||
})
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue