Files
mediahive/frontend/src/components/HeroSection.vue
T
LeoVasanko 76c426546b refactor: media cards use proper <a href> links instead of JS navigation
MediaCard, MediaRow, CollageHero, HeroSection, and MediaDetail cast
cards now render as <a> tags with real hrefs. Modified clicks
(ctrl+click, middle-click) navigate natively, enabling open-in-new-tab
and link copying. Plain left-clicks continue through the existing
showDetail flow for side-effects like focus-state saving.
2026-05-25 14:21:13 +00:00

154 lines
3.8 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.
<template>
<section class="hero">
<div
v-if="coverUrl"
class="hero-background"
:style="{ backgroundImage: `url('${coverUrl}')` }"
></div>
<div class="hero-content">
<h1 class="hero-title">{{ item.title }}</h1>
<div class="hero-meta">
<span v-if="item.year" class="hero-year">{{ item.year }}</span>
<span v-if="rating" class="hero-rating" :class="ratingClass">
{{ rating.toFixed(1) }}
</span>
<span v-if="resolution" class="hero-quality">{{ resolution }}</span>
<span v-if="quality" class="hero-quality">{{ quality }}</span>
</div>
<p v-if="overview" class="hero-overview">{{ overview }}</p>
<div class="hero-buttons">
<button class="btn btn-primary" @click="handlePlay" :disabled="!playableFile">
Play
</button>
<a
class="btn btn-secondary"
:href="detailHref"
@click.prevent="$emit('info', item)"
>
More Info
</a>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from "vue"
import type { MediaItem, Movie, Series } from "../types"
import { getCoverUrl } from "../api"
const props = defineProps<{
item: MediaItem
}>()
const emit = defineEmits<{
play: [string]
info: [MediaItem]
}>()
const coverUrl = computed(() => {
return getCoverUrl(props.item.cover_path, props.item.root_id)
})
const resolution = computed(() => {
if (props.item.type === "movies") {
const movie = props.item.data as Movie
const torrents = Object.values(movie.torrents || {})
return torrents.length > 0 ? torrents[0].resolution : null
}
return null
})
const quality = computed(() => {
if (props.item.type === "movies") {
const movie = props.item.data as Movie
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).info?.rating
}
return (props.item.data as Series).info?.rating
})
const ratingClass = computed(() => {
if (!rating.value) return ""
if (rating.value >= 7.5) return "rating-high"
if (rating.value >= 6) return "rating-medium"
return "rating-low"
})
const overview = computed(() => {
if (props.item.type === "movies") {
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).info?.overview
return o ? (o.length > 200 ? o.slice(0, 200) + "..." : o) : null
})
const detailHref = computed(() => {
return `#/${props.item.type}/${props.item.id}`
})
const playableFile = computed(() => {
if (props.item.type === "movies") {
const movie = props.item.data as Movie
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 || []) {
const torrents = Object.values(episode.torrents || {})
for (const torrent of torrents) {
if (torrent.playable_file) {
return torrent.playable_file
}
}
}
}
return null
})
function handlePlay() {
if (playableFile.value) {
emit("play", playableFile.value)
}
}
</script>
<style scoped>
.hero-rating {
font-weight: 600;
padding: 4px 10px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.6);
}
.rating-high {
color: #46d369;
}
.rating-medium {
color: #f9a825;
}
.rating-low {
color: #e53935;
}
.hero-overview {
max-width: 500px;
color: var(--text-secondary);
font-size: 0.95rem;
line-height: 1.5;
margin-top: 12px;
}
</style>