Data structures cleanup.

This commit is contained in:
2026-02-09 04:03:24 +00:00
parent f65c4ff3de
commit 3e2fb15f0f
17 changed files with 511 additions and 611 deletions
+10 -10
View File
@@ -16,7 +16,7 @@
<img
v-if="getImageUrl(item)"
:src="getImageUrl(item)!"
:alt="item.title"
:alt="item.title || 'Unknown'"
class="hex-clip"
/>
</div>
@@ -28,7 +28,7 @@
<img
v-if="index !== 0 && getImageUrl(item)"
:src="getImageUrl(item)!"
:alt="item.title"
:alt="item.title || 'Unknown'"
class="collage-media"
/>
<video
@@ -444,9 +444,9 @@ function getVideoUrl(item: MediaItem): string | undefined {
function getRating(item: MediaItem): number | null {
if (item.type === 'movies') {
return (item.data as Movie).rating ?? null;
return (item.data as Movie).info?.rating ?? null;
}
return (item.data as Series).rating ?? null;
return (item.data as Series).info?.rating ?? null;
}
function getRatingClass(item: MediaItem): string {
@@ -460,15 +460,15 @@ function getRatingClass(item: MediaItem): string {
function getResolution(item: MediaItem): string | null {
if (item.type === 'movies') {
const movie = item.data as Movie;
return movie.versions?.[0]?.resolution ?? null;
return Object.values(movie.torrents || {})[0]?.resolution ?? null;
}
return null;
}
function getOverview(item: MediaItem): string | null {
const overview = item.type === 'movies'
? (item.data as Movie).overview
: (item.data as Series).overview;
? (item.data as Movie).info?.overview
: (item.data as Series).info?.overview;
if (!overview) return null;
return overview.length > 150 ? overview.slice(0, 150) + '...' : overview;
}
@@ -476,13 +476,13 @@ function getOverview(item: MediaItem): string | null {
function getPlayableFile(item: MediaItem): string | null {
if (item.type === 'movies') {
const movie = item.data as Movie;
return movie.versions?.[0]?.playable_file ?? null;
return Object.values(movie.torrents || {})[0]?.playable_file ?? null;
}
const series = item.data as Series;
for (const season of series.seasons || []) {
for (const episode of season.episodes || []) {
for (const release of episode.releases || []) {
if (release.playable_file) return release.playable_file;
for (const torrent of Object.values(episode.torrents || {})) {
if (torrent.playable_file) return torrent.playable_file;
}
}
}
+15 -15
View File
@@ -1,6 +1,6 @@
<template>
<section class="hero">
<div
<div
v-if="coverUrl"
class="hero-background"
:style="{ backgroundImage: `url('${coverUrl}')` }"
@@ -53,7 +53,8 @@ const coverUrl = computed(() => {
const resolution = computed(() => {
if (props.item.type === 'movies') {
const movie = props.item.data as Movie;
return movie.versions && movie.versions.length > 0 ? movie.versions[0].resolution : null;
const torrents = Object.values(movie.torrents || {});
return torrents.length > 0 ? torrents[0].resolution : null;
}
return null;
});
@@ -61,16 +62,17 @@ const resolution = computed(() => {
const quality = computed(() => {
if (props.item.type === 'movies') {
const movie = props.item.data as Movie;
return movie.versions && movie.versions.length > 0 ? movie.versions[0].quality : null;
const torrents = Object.values(movie.torrents || {});
return torrents.length > 0 ? torrents[0].quality : null;
}
return null;
});
const rating = computed(() => {
if (props.item.type === 'movies') {
return (props.item.data as Movie).rating;
return (props.item.data as Movie).info?.rating;
}
return (props.item.data as Series).rating;
return (props.item.data as Series).info?.rating;
});
const ratingClass = computed(() => {
@@ -82,29 +84,27 @@ const ratingClass = computed(() => {
const overview = computed(() => {
if (props.item.type === 'movies') {
const o = (props.item.data as Movie).overview;
const o = (props.item.data as Movie).info?.overview;
return o ? (o.length > 200 ? o.slice(0, 200) + '...' : o) : null;
}
const o = (props.item.data as Series).overview;
const o = (props.item.data as Series).info?.overview;
return o ? (o.length > 200 ? o.slice(0, 200) + '...' : o) : null;
});
const playableFile = computed(() => {
if (props.item.type === 'movies') {
const movie = props.item.data as Movie;
// Get the first version's playable file
if (movie.versions && movie.versions.length > 0) {
return movie.versions[0].playable_file;
}
return null;
const torrents = Object.values(movie.torrents || {});
return torrents.length > 0 ? torrents[0].playable_file : null;
}
// For series, get first available file from episodes
const series = props.item.data as Series;
for (const season of series.seasons || []) {
for (const episode of season.episodes || []) {
for (const release of episode.releases || []) {
if (release.playable_file) {
return release.playable_file;
const torrents = Object.values(episode.torrents || {});
for (const torrent of torrents) {
if (torrent.playable_file) {
return torrent.playable_file;
}
}
}
+7 -7
View File
@@ -14,7 +14,7 @@
<img
v-if="coverUrl && !imageError"
:src="coverUrl"
:alt="item.title"
:alt="item.title || 'Unknown'"
loading="lazy"
@error="imageError = true"
/>
@@ -90,13 +90,13 @@ const coverUrl = computed(() => {
const rating = computed(() => {
if (props.item.type === 'movies') {
return (props.item.data as Movie).rating;
return (props.item.data as Movie).info?.rating;
}
if (props.item.type === 'episode') {
const epData = props.item.data as EpisodeWithSeries;
return epData.episode.rating ?? epData.series.rating;
return epData.episode.rating ?? epData.series.info?.rating;
}
return (props.item.data as Series).rating;
return (props.item.data as Series).info?.rating;
});
const ratingClass = computed(() => {
@@ -121,7 +121,7 @@ const subtitle = computed(() => {
}
// For series, show creators
if (props.item.type === 'series') {
const creators = (props.item.data as Series).creators;
const creators = (props.item.data as Series).info?.creators;
return creators && creators.length > 0 ? creators.join(', ') : null;
}
return null;
@@ -130,7 +130,7 @@ const subtitle = computed(() => {
// Director for movies
const director = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).director;
return (props.item.data as Movie).info?.director;
});
// Check if we have director and/or cast to display
@@ -142,7 +142,7 @@ const directorAndCast = computed(() => {
// Cast names, excluding director if they appear in cast
const filteredCastNames = computed(() => {
if (props.item.type !== 'movies') return null;
const cast = (props.item.data as Movie).cast;
const cast = (props.item.data as Movie).info?.cast;
if (!cast || cast.length === 0) return null;
const directorName = director.value?.toLowerCase();
+17 -17
View File
@@ -99,7 +99,7 @@
<button
class="btn btn-small btn-secondary"
v-bind="navAttrs(2, index * 2 + 1)"
@click="handleOpenFolder(version.path)"
@click="handleOpenFolder(version.playable_file || '')"
>📁</button>
</div>
</div>
@@ -140,7 +140,7 @@
<script setup lang="ts">
import { computed, ref, watch, onMounted } from 'vue';
import type { MediaItem, Movie, MovieVersion, Series } from '../types';
import type { MediaItem, Movie, Series, Torrent } from '../types';
import { getCoverUrl } from '../api';
import SeriesFullView from './SeriesFullView.vue';
import { navAttrs } from '../composables/useKeyboardNavigation';
@@ -270,10 +270,10 @@ function getShowreelUrl(path: string): string {
return getCoverUrl(path);
}
// Movie versions
const movieVersions = computed((): MovieVersion[] => {
const movieVersions = computed((): Torrent[] => {
if (props.item.type !== 'movies') return [];
const movie = props.item.data as Movie;
return movie.versions || [];
return Object.values(movie.torrents || {});
});
const headerStyle = computed(() => {
@@ -299,7 +299,7 @@ const backdropStyle = computed(() => {
});
// Check if a specific version is a disc format (Blu-ray disc has index.bdmv)
function isVersionDisc(version: MovieVersion): boolean {
function isVersionDisc(version: Torrent): boolean {
if (!version.playable_file) return false;
const filename = version.playable_file.toLowerCase();
return filename.endsWith('index.bdmv') || filename.endsWith('.iso');
@@ -307,42 +307,42 @@ function isVersionDisc(version: MovieVersion): boolean {
const movieGenres = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).genres;
return (props.item.data as Movie).info?.genres;
});
const movieTagline = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).tagline;
return (props.item.data as Movie).info?.tagline;
});
const movieDirector = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).director;
return (props.item.data as Movie).info?.director;
});
const movieCast = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).cast;
return (props.item.data as Movie).info?.cast;
});
const movieRuntime = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).runtime;
return (props.item.data as Movie).info?.runtime;
});
const movieReleaseDate = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).release_date;
return (props.item.data as Movie).info?.release_date;
});
const movieStatus = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).status;
return (props.item.data as Movie).info?.status;
});
const movieKeywords = computed(() => {
if (props.item.type !== 'movies') return null;
return (props.item.data as Movie).keywords;
return (props.item.data as Movie).info?.keywords;
});
function formatRuntime(minutes: number): string {
@@ -354,16 +354,16 @@ function formatRuntime(minutes: number): string {
const rating = computed(() => {
if (props.item.type === 'movies') {
return (props.item.data as Movie).rating;
return (props.item.data as Movie).info?.rating;
}
return (props.item.data as Series).rating;
return (props.item.data as Series).info?.rating;
});
const overview = computed(() => {
if (props.item.type === 'movies') {
return (props.item.data as Movie).overview;
return (props.item.data as Movie).info?.overview;
}
return (props.item.data as Series).overview;
return (props.item.data as Series).info?.overview;
});
const ratingClass = computed(() => {
+24 -24
View File
@@ -5,7 +5,7 @@
<section class="series-hero">
<div class="hero-bg">
<!-- Use backdrop if available, otherwise create collage from season posters -->
<img v-if="backdropUrl" :src="backdropUrl" class="hero-img" :alt="series.title" />
<img v-if="backdropUrl" :src="backdropUrl" class="hero-img" :alt="series.title || 'Unknown'" />
<div v-else class="hero-collage">
<div
v-for="(season, i) in seasonsWithPosters.slice(0, 5)"
@@ -19,12 +19,12 @@
<div class="hero-content">
<h1 class="series-title">{{ series.title }}</h1>
<div class="series-meta">
<span v-if="series.rating" class="meta-rating" :class="ratingClass"> {{ series.rating.toFixed(1) }}</span>
<span v-if="series.number_of_seasons" class="meta-item">{{ series.number_of_seasons }} Seasons</span>
<span v-if="series.status" class="meta-badge">{{ series.status }}</span>
<span v-if="series.genres?.length" class="meta-genres">{{ series.genres.slice(0, 3).join(' ') }}</span>
<span v-if="series.info?.rating" class="meta-rating" :class="ratingClass"> {{ series.info.rating.toFixed(1) }}</span>
<span v-if="series.info?.number_of_seasons" class="meta-item">{{ series.info.number_of_seasons }} Seasons</span>
<span v-if="series.info?.status" class="meta-badge">{{ series.info.status }}</span>
<span v-if="series.info?.genres?.length" class="meta-genres">{{ series.info.genres.slice(0, 3).join(' ') }}</span>
</div>
<p v-if="series.overview" class="series-overview">{{ series.overview }}</p>
<p v-if="series.info?.overview" class="series-overview">{{ series.info.overview }}</p>
</div>
</section>
@@ -125,24 +125,24 @@
<div class="context-menu-header">
{{ contextMenu.episode.name || `Episode ${contextMenu.episode.episode_number}` }}
</div>
<div v-if="contextMenu.episode.releases && contextMenu.episode.releases.length > 0">
<div v-if="Object.values(contextMenu.episode.torrents || {}).length > 0">
<div
v-for="(release, index) in contextMenu.episode.releases"
v-for="(torrent, index) in Object.values(contextMenu.episode.torrents || {})"
:key="index"
class="context-menu-version"
>
<div class="version-label">{{ getVersionLabel(release) }}</div>
<div class="version-label">{{ getVersionLabel(torrent) }}</div>
<div class="version-actions">
<button
class="ctx-btn ctx-btn-play"
tabindex="0"
@click="handlePlayVersion(release.playable_file)"
:disabled="!release.playable_file"
@click="handlePlayVersion(torrent.playable_file)"
:disabled="!torrent.playable_file"
> Play</button>
<button
class="ctx-btn ctx-btn-folder"
tabindex="0"
@click="handleOpenFolder(release.path)"
@click="handleOpenFolder(torrent.playable_file || '')"
>📁</button>
</div>
</div>
@@ -157,7 +157,7 @@
<script setup lang="ts">
import { computed, ref, nextTick, watch } from 'vue';
import type { Series, Season, Episode, EpisodeRelease } from '../types';
import type { Series, Season, Episode, Torrent } from '../types';
import { getCoverUrl } from '../api';
import { navAttrs } from '../composables/useKeyboardNavigation';
@@ -309,12 +309,12 @@ function handleOpenFolder(folderPath: string) {
}
// Get version display label
function getVersionLabel(release: EpisodeRelease): string {
function getVersionLabel(torrent: Torrent): string {
const parts: string[] = [];
if (release.resolution) parts.push(release.resolution);
if (release.quality) parts.push(release.quality);
if (release.codec) parts.push(release.codec);
if (release.audio) parts.push(release.audio);
if (torrent.resolution) parts.push(torrent.resolution);
if (torrent.quality) parts.push(torrent.quality);
if (torrent.codec) parts.push(torrent.codec);
if (torrent.audio) parts.push(torrent.audio);
return parts.length > 0 ? parts.join(' • ') : 'Unknown';
}
@@ -390,8 +390,8 @@ function handleEpisodeHover(key: string, isEntering: boolean) {
// Backdrop URL - only use backdrop_path, fall back to collage (handled in template)
const backdropUrl = computed(() => {
if (props.series.backdrop_path) {
return getCoverUrl(props.series.backdrop_path);
if (props.series.info?.backdrop_path) {
return getCoverUrl(props.series.info.backdrop_path);
}
return null;
});
@@ -403,9 +403,9 @@ const seasonsWithPosters = computed(() => {
// Rating class
const ratingClass = computed(() => {
if (!props.series.rating) return '';
if (props.series.rating >= 7.5) return 'rating-high';
if (props.series.rating >= 6) return 'rating-medium';
if (!props.series.info?.rating) return '';
if (props.series.info.rating >= 7.5) return 'rating-high';
if (props.series.info.rating >= 6) return 'rating-medium';
return 'rating-low';
});
@@ -450,7 +450,7 @@ function truncate(text: string, maxLength: number): string {
// Handle play
function handlePlay(episode: Episode) {
const playableFile = episode.releases?.[0]?.playable_file;
const playableFile = Object.values(episode.torrents || {})[0]?.playable_file;
if (playableFile) {
emit('play', playableFile);
}