Files
leaguerecorder/tauri-app/src/components/GameHistory.vue
Valentin Haudiquet fc7ba40b30
Some checks failed
record-daemon / Build, check and test (push) Failing after 53s
tauri-app: initial tests
2026-03-22 14:13:10 +01:00

457 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { invoke } from "@tauri-apps/api/core";
import type { GameHistoryItem } from "../types/timeline";
import { formatRelativeTime } from "../types/timeline";
const games = ref<GameHistoryItem[]>([]);
const loading = ref(true);
const error = ref<string | null>(null);
const selectedGame = ref<GameHistoryItem | null>(null);
async function loadGameHistory() {
try {
loading.value = true;
error.value = null;
games.value = await invoke<GameHistoryItem[]>("get_game_history");
} catch (e) {
error.value = `Failed to load game history: ${e}`;
console.error("Failed to load game history:", e);
} finally {
loading.value = false;
}
}
function selectGame(game: GameHistoryItem) {
selectedGame.value = game;
}
function closeDetail() {
selectedGame.value = null;
}
onMounted(() => {
loadGameHistory();
});
</script>
<template>
<div class="game-history">
<header class="header">
<h1>League Recorder</h1>
<p class="subtitle">Game History</p>
</header>
<!-- Loading State -->
<div v-if="loading" class="loading">
<div class="spinner"></div>
<p>Loading game history...</p>
</div>
<!-- Error State -->
<div v-else-if="error" class="error">
<p>{{ error }}</p>
<button @click="loadGameHistory">Retry</button>
</div>
<!-- Empty State -->
<div v-else-if="games.length === 0" class="empty">
<div class="empty-icon">🎮</div>
<h2>No Games Recorded</h2>
<p>Start playing to see your game history here.</p>
</div>
<!-- Game List -->
<div v-else class="game-list">
<div
v-for="game in games"
:key="game.id"
class="game-card"
:class="{ victory: game.result === 'Victory', defeat: game.result === 'Defeat' }"
@click="selectGame(game)"
>
<div class="game-result-badge" v-if="game.result">
{{ game.result }}
</div>
<div class="game-main">
<div class="game-champion" v-if="game.champion">
{{ game.champion }}
</div>
<div class="game-champion unknown" v-else>
Unknown Champion
</div>
<div class="game-duration">
{{ game.duration_formatted }}
</div>
</div>
<div class="game-meta">
<span class="game-time">{{ formatRelativeTime(game.start_time) }}</span>
<span class="game-events">{{ game.event_count }} events</span>
</div>
</div>
</div>
<!-- Game Detail Modal -->
<div v-if="selectedGame" class="modal-overlay" @click.self="closeDetail">
<div class="modal">
<button class="modal-close" @click="closeDetail">×</button>
<div class="modal-header">
<h2>Game Details</h2>
<div class="modal-result" :class="selectedGame.result?.toLowerCase()">
{{ selectedGame.result || 'Unknown Result' }}
</div>
</div>
<div class="modal-body">
<div class="detail-row">
<span class="detail-label">Champion:</span>
<span class="detail-value">{{ selectedGame.champion || 'Unknown' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">Duration:</span>
<span class="detail-value">{{ selectedGame.duration_formatted }}</span>
</div>
<div class="detail-row">
<span class="detail-label">Started:</span>
<span class="detail-value">{{ new Date(selectedGame.start_time).toLocaleString() }}</span>
</div>
<div class="detail-row">
<span class="detail-label">Events:</span>
<span class="detail-value">{{ selectedGame.event_count }}</span>
</div>
<div class="detail-row" v-if="selectedGame.video_path">
<span class="detail-label">Video:</span>
<span class="detail-value video-path">{{ selectedGame.video_path }}</span>
</div>
</div>
<div class="modal-actions">
<button class="btn-secondary" @click="closeDetail">Close</button>
<button class="btn-primary" v-if="selectedGame.video_path">
Open Video
</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.game-history {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
padding: 2rem;
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.header h1 {
font-size: 2.5rem;
margin: 0;
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
color: #888;
margin: 0.5rem 0 0 0;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 50vh;
color: #888;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #333;
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.error {
text-align: center;
padding: 2rem;
color: #ff6b6b;
}
.error button {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #ff6b6b;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.empty {
text-align: center;
padding: 4rem 2rem;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty h2 {
margin: 0 0 0.5rem 0;
color: #fff;
}
.empty p {
color: #888;
margin: 0;
}
.game-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
max-width: 1200px;
margin: 0 auto;
}
.game-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 1.25rem;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.game-card:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.game-card.victory {
border-left: 4px solid #4ade80;
}
.game-card.defeat {
border-left: 4px solid #f87171;
}
.game-result-badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
padding: 0.25rem 0.5rem;
border-radius: 4px;
background: rgba(255, 255, 255, 0.1);
}
.game-card.victory .game-result-badge {
background: rgba(74, 222, 128, 0.2);
color: #4ade80;
}
.game-card.defeat .game-result-badge {
background: rgba(248, 113, 113, 0.2);
color: #f87171;
}
.game-main {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.game-champion {
font-size: 1.25rem;
font-weight: 600;
}
.game-champion.unknown {
color: #888;
font-style: italic;
}
.game-duration {
font-size: 1.1rem;
font-family: monospace;
color: #00d4ff;
}
.game-meta {
display: flex;
justify-content: space-between;
font-size: 0.85rem;
color: #888;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal {
background: #1a1a2e;
border-radius: 16px;
padding: 2rem;
max-width: 500px;
width: 100%;
position: relative;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.modal-close {
position: absolute;
top: 1rem;
right: 1rem;
background: none;
border: none;
color: #888;
font-size: 1.5rem;
cursor: pointer;
padding: 0;
line-height: 1;
}
.modal-close:hover {
color: #fff;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.modal-header h2 {
margin: 0;
font-size: 1.5rem;
}
.modal-result {
font-size: 0.9rem;
font-weight: 600;
padding: 0.25rem 0.75rem;
border-radius: 4px;
}
.modal-result.victory {
background: rgba(74, 222, 128, 0.2);
color: #4ade80;
}
.modal-result.defeat {
background: rgba(248, 113, 113, 0.2);
color: #f87171;
}
.modal-body {
margin-bottom: 1.5rem;
}
.detail-row {
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.detail-row:last-child {
border-bottom: none;
}
.detail-label {
color: #888;
}
.detail-value {
font-weight: 500;
}
.video-path {
font-size: 0.85rem;
color: #00d4ff;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.modal-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
}
.btn-primary,
.btn-secondary {
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
border: none;
transition: all 0.2s ease;
}
.btn-primary {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: #fff;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.15);
}
</style>