tauri-app: filters, review on click, smaller video player
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m4s
All checks were successful
record-daemon / Build, check and test (push) Successful in 2m4s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { GameHistoryItem, TimestampedEvent, ItemInfo } from "../types/timeline";
|
||||
|
||||
@@ -40,11 +40,57 @@ function getVideoTimestampSecs(event: TimestampedEvent): number {
|
||||
return event.video_timestamp[0];
|
||||
}
|
||||
|
||||
// Queue filter types
|
||||
type QueueFilter = "all" | "ranked_solo" | "ranked_flex" | "normals" | "aram" | "custom";
|
||||
const activeFilter = ref<QueueFilter>("all");
|
||||
|
||||
const queueFilterOptions: { value: QueueFilter; label: string }[] = [
|
||||
{ value: "all", label: "All" },
|
||||
{ value: "ranked_solo", label: "Ranked Solo" },
|
||||
{ value: "ranked_flex", label: "Ranked Flex" },
|
||||
{ value: "normals", label: "Normals" },
|
||||
{ value: "aram", label: "ARAM" },
|
||||
{ value: "custom", label: "Custom" },
|
||||
];
|
||||
|
||||
// Categorize a game into a queue filter category
|
||||
function getQueueCategory(game: GameHistoryItem): QueueFilter {
|
||||
const queueType = getQueueType(game);
|
||||
const queueId = getQueueId(game);
|
||||
|
||||
// Check by queue type name first
|
||||
if (queueType) {
|
||||
if (queueType.includes("Ranked Solo")) return "ranked_solo";
|
||||
if (queueType.includes("Ranked Flex")) return "ranked_flex";
|
||||
if (queueType.includes("ARAM")) return "aram";
|
||||
if (queueType.includes("Normal") || queueType.includes("Draft") || queueType.includes("Blind")) return "normals";
|
||||
if (queueType.includes("Custom") || queueType.includes("Practice Tool")) return "custom";
|
||||
}
|
||||
|
||||
// Fallback to queue ID
|
||||
if (queueId !== null) {
|
||||
if (queueId === 420) return "ranked_solo";
|
||||
if (queueId === 440) return "ranked_flex";
|
||||
if (queueId === 400 || queueId === 430) return "normals";
|
||||
if (queueId === 450) return "aram";
|
||||
if (queueId === 600 || queueId === 610) return "custom";
|
||||
}
|
||||
|
||||
// Default: treat unknown as custom
|
||||
return "custom";
|
||||
}
|
||||
|
||||
const games = ref<GameHistoryItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref<string | null>(null);
|
||||
const selectedGame = ref<GameHistoryItem | null>(null);
|
||||
|
||||
// Filtered games based on active filter
|
||||
const filteredGames = computed(() => {
|
||||
if (activeFilter.value === "all") return games.value;
|
||||
return games.value.filter(g => getQueueCategory(g) === activeFilter.value);
|
||||
});
|
||||
|
||||
async function loadGameHistory() {
|
||||
try {
|
||||
loading.value = true;
|
||||
@@ -91,6 +137,19 @@ onMounted(() => {
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Queue Filters -->
|
||||
<div v-if="!loading && !error && games.length > 0" class="filter-bar">
|
||||
<button
|
||||
v-for="opt in queueFilterOptions"
|
||||
:key="opt.value"
|
||||
class="filter-btn"
|
||||
:class="{ active: activeFilter === opt.value }"
|
||||
@click="activeFilter = opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
@@ -110,10 +169,17 @@ onMounted(() => {
|
||||
<p>Start playing to see your match history here.</p>
|
||||
</div>
|
||||
|
||||
<!-- No results for filter -->
|
||||
<div v-else-if="filteredGames.length === 0" class="empty">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<h2>No Games Found</h2>
|
||||
<p>No games match the selected filter.</p>
|
||||
</div>
|
||||
|
||||
<!-- Game List - Single Column -->
|
||||
<div v-else class="game-list">
|
||||
<div
|
||||
v-for="game in games"
|
||||
v-for="game in filteredGames"
|
||||
:key="game.recording_id"
|
||||
class="game-card"
|
||||
:class="{
|
||||
@@ -121,7 +187,7 @@ onMounted(() => {
|
||||
defeat: getGameResult(game) === 'Defeat',
|
||||
terminated: getGameResult(game) === 'Terminated'
|
||||
}"
|
||||
@click="selectGame(game)"
|
||||
@click="openReview(game)"
|
||||
>
|
||||
<!-- Result Banner -->
|
||||
<div class="result-banner">
|
||||
@@ -367,11 +433,11 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Events Timeline -->
|
||||
<div class="modal-section" v-if="selectedGame.events.length > 0">
|
||||
<h3>Events ({{ selectedGame.events.length }})</h3>
|
||||
<div class="modal-section" v-if="selectedGame.events.filter(e => e.event_type !== 'unknown').length > 0">
|
||||
<h3>Events ({{ selectedGame.events.filter(e => e.event_type !== 'unknown').length }})</h3>
|
||||
<div class="events-list">
|
||||
<div
|
||||
v-for="(event, idx) in selectedGame.events.slice(0, 10)"
|
||||
v-for="(event, idx) in selectedGame.events.filter(e => e.event_type !== 'unknown').slice(0, 10)"
|
||||
:key="idx"
|
||||
class="event-item"
|
||||
>
|
||||
@@ -379,8 +445,8 @@ onMounted(() => {
|
||||
<span class="event-type">{{ event.event_type }}</span>
|
||||
<span class="event-desc">{{ event.description }}</span>
|
||||
</div>
|
||||
<div v-if="selectedGame.events.length > 10" class="events-more">
|
||||
+{{ selectedGame.events.length - 10 }} more events
|
||||
<div v-if="selectedGame.events.filter(e => e.event_type !== 'unknown').length > 10" class="events-more">
|
||||
+{{ selectedGame.events.filter(e => e.event_type !== 'unknown').length - 10 }} more events
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -424,6 +490,40 @@ onMounted(() => {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* Filter Bar */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 2rem;
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #888;
|
||||
padding: 0.4rem 0.9rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: rgba(200, 170, 110, 0.25);
|
||||
border-color: #c8aa6e;
|
||||
color: #c8aa6e;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
|
||||
@@ -65,11 +65,13 @@ const videoUrl = computed(() => {
|
||||
|
||||
// Events sorted by timestamp
|
||||
const sortedEvents = computed(() => {
|
||||
return [...props.game.events].sort((a, b) => {
|
||||
const aTime = a.video_timestamp[0] + a.video_timestamp[1] / 1e9;
|
||||
const bTime = b.video_timestamp[0] + b.video_timestamp[1] / 1e9;
|
||||
return aTime - bTime;
|
||||
});
|
||||
return [...props.game.events]
|
||||
.filter(e => e.event_type !== "unknown")
|
||||
.sort((a, b) => {
|
||||
const aTime = a.video_timestamp[0] + a.video_timestamp[1] / 1e9;
|
||||
const bTime = b.video_timestamp[0] + b.video_timestamp[1] / 1e9;
|
||||
return aTime - bTime;
|
||||
});
|
||||
});
|
||||
|
||||
// Get event position on timeline (0-100%)
|
||||
@@ -900,7 +902,8 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped>
|
||||
.game-review {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, #0a0a13 0%, #0f0f1a 100%);
|
||||
color: #fff;
|
||||
display: flex;
|
||||
@@ -1026,6 +1029,7 @@ onUnmounted(() => {
|
||||
.review-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -1035,6 +1039,8 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-section.with-sidebar {
|
||||
@@ -1044,11 +1050,13 @@ onUnmounted(() => {
|
||||
.video-container {
|
||||
position: relative;
|
||||
background: #000;
|
||||
flex: 1;
|
||||
aspect-ratio: 16/9;
|
||||
max-height: 55vh;
|
||||
width: min(100%, calc(55vh * 16 / 9));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
|
||||
Reference in New Issue
Block a user