diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 3b499a2..59d9ab7 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,6 +5,29 @@ + +
+
+ ⚡ Reconnecting... +
+
+ {{ task.id }} + + {{ Math.round(task.progress * 100) }}% + + {{ task.detail }} + {{ task.status }} +
+
+
⚠️

Failed to load media index

{{ error }}

- @@ -57,6 +80,7 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue'; import { useRouter, useRoute } from 'vue-router'; -import type { MediaIndex, Movie, Series, MediaItem, EpisodeWithSeries, MatchedPerson, MatchedEpisode } from './types'; -import { loadMediaIndex, playMedia, openFolder } from './api'; +import type { Movie, Series, MediaItem, EpisodeWithSeries, MatchedPerson, MatchedEpisode, TaskInfo } from './types'; +import { playMedia, openFolder } from './api'; import { useKeyboardNavigation } from './composables/useKeyboardNavigation'; +import { useMediaWebSocket } from './composables/useMediaWebSocket'; import Header from './components/Header.vue'; import CollageHero from './components/CollageHero.vue'; import MediaRow from './components/MediaRow.vue'; @@ -166,9 +193,12 @@ const { getFocusState, restoreFocusState, focusAt } = useKeyboardNavigation(); const router = useRouter(); const route = useRoute(); -const loading = ref(true); -const error = ref(null); -const mediaIndex = ref(null); +// WebSocket-driven media index +const { mediaIndex, loading, error, connected: wsConnected, tasks } = useMediaWebSocket(); + +// Active tasks for the debug overlay +const activeTasks = computed(() => Array.from(tasks.value.values())); + const searchResults = ref([]); const isSearching = ref(false); @@ -438,10 +468,11 @@ function seriesToMediaItem(series: Series): MediaItem { // priority: lower number = higher matching priority (movies assigned to highest priority match) // exclude: if item has any of these genres, it won't match this category (negative match) const GENRE_CATEGORIES = [ - { name: 'Action', keywords: ['Action', 'Adventure', 'Crime'], priority: 40, exclude: [] }, + { name: 'Action', keywords: ['Action', 'Adventure'], priority: 40, exclude: [] }, { name: 'Comedy', keywords: ['Comedy'], priority: 30, exclude: ['Drama'] }, { name: 'Romance', keywords: ['Romance'], priority: 20, exclude: [] }, { name: 'Drama', keywords: ['Drama'], priority: 50, exclude: [] }, + { name: 'Crime', keywords: ['Crime'], priority: 45, exclude: [] }, { name: 'Thriller', keywords: ['Thriller', 'Mystery'], priority: 20, exclude: [] }, { name: 'Horror', keywords: ['Horror'], priority: 10, exclude: [] }, { name: 'Science Fiction', keywords: ['Science Fiction', 'Sci-Fi'], priority: 15, exclude: [] }, @@ -1049,16 +1080,8 @@ const searchFeaturedItem = computed(() => { return withCovers[0] || searchResults.value[0] || null; }); -async function loadIndex() { - loading.value = true; - error.value = null; - try { - mediaIndex.value = await loadMediaIndex(); - } catch (e) { - error.value = e instanceof Error ? e.message : String(e); - } finally { - loading.value = false; - } +function reloadPage() { + window.location.reload(); } async function handlePlay(filePath: string) { @@ -1077,9 +1100,7 @@ async function handleOpenFolder(folderPath: string) { } } -onMounted(() => { - loadIndex(); -}); +