update
This commit is contained in:
@@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { abonnieren } from './live'
|
||||
import MovieCard from './components/MovieCard.vue'
|
||||
import FilterSidebar from './components/FilterSidebar.vue'
|
||||
import AehnlichTab from './components/AehnlichTab.vue'
|
||||
import MerklisteTab from './components/MerklisteTab.vue'
|
||||
import MovieDetail from './components/MovieDetail.vue'
|
||||
import NamensWahl from './components/NamensWahl.vue'
|
||||
@@ -20,6 +21,10 @@ const loading = ref(true)
|
||||
const error = ref('')
|
||||
const search = ref('')
|
||||
const sort = ref('random')
|
||||
// Suche und Ähnlichkeitsuche haben eine eigene Voreinstellung: TMDB liefert
|
||||
// dort eine Reihenfolge nach Verwandtschaft — dieselbe, die IMDb unter
|
||||
// "More like this" zeigt. Im Katalog gibt es die nicht.
|
||||
const suchSort = ref('relevanz')
|
||||
|
||||
// Kompakt zeigt viele Karten nebeneinander, Details dafür mehr je Film
|
||||
const ANSICHT_SCHLUESSEL = 'filmabend_ansicht'
|
||||
@@ -27,11 +32,29 @@ const ansicht = ref(localStorage.getItem(ANSICHT_SCHLUESSEL) || 'kompakt')
|
||||
|
||||
watch(ansicht, (wert) => localStorage.setItem(ANSICHT_SCHLUESSEL, wert))
|
||||
const selected = ref(null)
|
||||
|
||||
// Der Katalog liefert nur sechs Bildpfade je Film. Beim Öffnen holen wir
|
||||
// den vollen Datensatz nach — die Ansicht steht sofort, die restlichen
|
||||
// Bilder kommen gleich danach.
|
||||
//
|
||||
// Über /api/search, nicht über /api/movies: Empfehlungen führen auch zu
|
||||
// Filmen außerhalb des Katalogs. Dort greift der Rückgriff auf TMDB,
|
||||
// sonst fehlten Trailer, Bilder, Laufzeit und der IMDb-Knopf.
|
||||
async function filmOeffnen(movie) {
|
||||
selected.value = movie
|
||||
if (!movie?.id) return
|
||||
try {
|
||||
const resp = await fetch(`/api/search/${movie.id}`)
|
||||
if (resp.ok && selected.value?.id === movie.id) selected.value = await resp.json()
|
||||
} catch {
|
||||
/* dann eben mit den Listendaten */
|
||||
}
|
||||
}
|
||||
// Nach dem Neuladen soll derselbe Reiter offen sein
|
||||
const TAB_SCHLUESSEL = 'filmabend_tab'
|
||||
const TAB_IDS = [
|
||||
'netflix', 'canon', 'search', 'wheel', 'merkliste', 'watched', 'wuensche',
|
||||
'verwaltung', 'stream',
|
||||
'netflix', 'canon', 'search', 'aehnlich', 'wheel', 'merkliste', 'watched',
|
||||
'wuensche', 'verwaltung', 'stream',
|
||||
]
|
||||
const gemerkterTab = localStorage.getItem(TAB_SCHLUESSEL)
|
||||
const activeTab = ref(TAB_IDS.includes(gemerkterTab) ? gemerkterTab : 'netflix')
|
||||
@@ -54,7 +77,25 @@ const REGLER = [
|
||||
{ key: 'vote_average', label: 'Bewertung', feld: 'vote_average', step: 0.1, nachkomma: 1 },
|
||||
]
|
||||
|
||||
// Bei der Suche steht kein Katalog dahinter, aus dem sich Grenzen ableiten
|
||||
// ließen — TMDB reicht von den Anfängen bis heute
|
||||
const SUCH_GRENZEN = {
|
||||
vote_count: { min: 50, max: 50000 },
|
||||
runtime: { min: 0, max: 240 },
|
||||
year: { min: 1900, max: new Date().getFullYear() + 1 },
|
||||
vote_average: { min: 0, max: 10 },
|
||||
}
|
||||
|
||||
// Die 19 Genres, die TMDB kennt
|
||||
const SUCH_GENRES = [
|
||||
'Abenteuer', 'Action', 'Animation', 'Dokumentarfilm', 'Drama', 'Familie',
|
||||
'Fantasy', 'Historie', 'Horror', 'Komödie', 'Krimi', 'Kriegsfilm',
|
||||
'Liebesfilm', 'Musik', 'Mystery', 'Science Fiction', 'TV-Film', 'Thriller',
|
||||
'Western',
|
||||
].map((genre) => ({ genre, count: null }))
|
||||
|
||||
const grenzen = computed(() => {
|
||||
if (activeTab.value === 'search' || activeTab.value === 'aehnlich') return SUCH_GRENZEN
|
||||
const pool = activeTab.value === 'netflix' ? netflixMovies.value : canonMovies.value
|
||||
const out = {}
|
||||
for (const regler of REGLER) {
|
||||
@@ -135,6 +176,7 @@ function resetGenres() {
|
||||
|
||||
// Nur Genres anbieten, die im offenen Reiter auch Treffer haben
|
||||
const genreOptions = computed(() => {
|
||||
if (activeTab.value === 'search' || activeTab.value === 'aehnlich') return SUCH_GENRES
|
||||
const pool = activeTab.value === 'netflix' ? netflixMovies.value : canonMovies.value
|
||||
const counts = new Map()
|
||||
for (const movie of pool) {
|
||||
@@ -213,6 +255,7 @@ const tabs = computed(() => [
|
||||
{ id: 'netflix', label: 'Netflix', count: netflixMovies.value.length },
|
||||
{ id: 'canon', label: 'Horror-Kanon', count: canonGroups.value.length },
|
||||
{ id: 'search', label: 'Suche', count: null },
|
||||
{ id: 'aehnlich', label: 'Ähnlichkeitsuche', count: null },
|
||||
...(host.value
|
||||
? []
|
||||
: [{ id: 'merkliste', label: 'Sammlungen', count: null }]),
|
||||
@@ -223,6 +266,30 @@ const tabs = computed(() => [
|
||||
...(host.value ? [{ id: 'verwaltung', label: 'Verwaltung', count: null }] : []),
|
||||
])
|
||||
|
||||
// Was die Leiste eingestellt hat — der Suche-Reiter schickt es an TMDB
|
||||
const suchFilter = computed(() => {
|
||||
const spanne = (key) => ranges.value[key] || []
|
||||
return {
|
||||
sort: suchSort.value,
|
||||
include: include.value,
|
||||
exclude: exclude.value,
|
||||
stimmen: spanne('vote_count'),
|
||||
dauer: spanne('runtime'),
|
||||
jahr: spanne('year'),
|
||||
note: spanne('vote_average'),
|
||||
grenzen: grenzen.value,
|
||||
}
|
||||
})
|
||||
|
||||
// Aus der Filmansicht heraus zur Ähnlichkeitsuche springen
|
||||
const aehnlichStart = ref(null)
|
||||
|
||||
function zurAehnlichkeitsuche(movie) {
|
||||
aehnlichStart.value = movie
|
||||
selected.value = null
|
||||
activeTab.value = 'aehnlich'
|
||||
}
|
||||
|
||||
const rad = ref(null)
|
||||
|
||||
// Bei 2500 Karten kommt man sonst nicht mehr nach oben
|
||||
@@ -515,7 +582,7 @@ onUnmounted(() => {
|
||||
<template>
|
||||
<div class="page" :class="{
|
||||
breit: [
|
||||
'search', 'wheel', 'stream', 'watched', 'merkliste', 'wuensche', 'verwaltung',
|
||||
'wheel', 'stream', 'watched', 'merkliste', 'wuensche', 'verwaltung',
|
||||
].includes(activeTab),
|
||||
}">
|
||||
<nav class="tabs">
|
||||
@@ -533,7 +600,39 @@ onUnmounted(() => {
|
||||
<span v-else class="host">Host</span>
|
||||
</nav>
|
||||
|
||||
<SearchTab v-if="activeTab === 'search'" @open="selected = $event" />
|
||||
<div v-if="activeTab === 'search' || activeTab === 'aehnlich'" class="layout">
|
||||
<SearchTab
|
||||
v-if="activeTab === 'search'"
|
||||
:filter="suchFilter"
|
||||
:ansicht="ansicht"
|
||||
@open="selected = $event"
|
||||
/>
|
||||
|
||||
<AehnlichTab
|
||||
v-else
|
||||
:filter="suchFilter"
|
||||
:ansicht="ansicht"
|
||||
:start="aehnlichStart"
|
||||
@open="filmOeffnen"
|
||||
@gestartet="aehnlichStart = null"
|
||||
/>
|
||||
|
||||
<FilterSidebar
|
||||
v-model:sort="suchSort"
|
||||
v-model:ansicht="ansicht"
|
||||
v-model:ranges="ranges"
|
||||
:regler="REGLER"
|
||||
:grenzen="grenzen"
|
||||
:options="genreOptions"
|
||||
:include="include"
|
||||
:exclude="exclude"
|
||||
:ohne-suche="true"
|
||||
:mit-relevanz="true"
|
||||
@toggle="toggleGenre"
|
||||
@all="toggleAll"
|
||||
@reset="resetGenres"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<WheelTab
|
||||
v-else-if="activeTab === 'wheel'"
|
||||
@@ -591,7 +690,7 @@ onUnmounted(() => {
|
||||
:movie="entry.parts[0]"
|
||||
:auf-dem-rad="aufDemRad.includes(entry.parts[0].id)"
|
||||
:ansicht="ansicht"
|
||||
@open="selected = $event"
|
||||
@open="filmOeffnen"
|
||||
/>
|
||||
<SeriesCard
|
||||
v-else
|
||||
@@ -599,7 +698,7 @@ onUnmounted(() => {
|
||||
:start="entry.best"
|
||||
:auf-dem-rad="aufDemRad"
|
||||
:ansicht="ansicht"
|
||||
@open="selected = $event"
|
||||
@open="filmOeffnen"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -631,7 +730,7 @@ onUnmounted(() => {
|
||||
:movie="selected"
|
||||
:series="series"
|
||||
@close="selected = null"
|
||||
@open="selected = $event"
|
||||
@open="filmOeffnen"
|
||||
:mein-vorschlag="meinVorschlag"
|
||||
:host="host"
|
||||
:gesehen="gesehen.some((e) => e.movie_id === selected.id)"
|
||||
@@ -639,6 +738,7 @@ onUnmounted(() => {
|
||||
@gemerkt="ladeMerkliste"
|
||||
@suggested="nachVorschlag"
|
||||
@watched="ladeGesehen"
|
||||
@aehnlich="zurAehnlichkeitsuche"
|
||||
/>
|
||||
|
||||
</div>
|
||||
@@ -662,10 +762,11 @@ onUnmounted(() => {
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
align-items: stretch;
|
||||
/* Der negative Rand deckt das Polster der Seite ab, sonst scrollen
|
||||
Karten oberhalb der Leiste durchs Bild */
|
||||
margin: -1.5rem -1.25rem 1.25rem;
|
||||
padding: 0.7rem 1.25rem 0;
|
||||
padding: 0 1.25rem;
|
||||
background: var(--bg);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
@@ -694,7 +795,7 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.6rem 1rem;
|
||||
padding: 0.8rem 1rem 0.6rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
border-bottom: 2px solid transparent;
|
||||
|
||||
Reference in New Issue
Block a user