This commit is contained in:
team3
2026-09-10 18:00:51 +02:00
parent 7191388787
commit aa5c64ae23
8 changed files with 79 additions and 5 deletions

View File

@@ -122,7 +122,14 @@ def ziehen():
if not eintraege:
return None
treffer = random.randrange(len(eintraege))
# Schon gelaufene Filme überspringen. Sind alle Vorschläge gesehen,
# bleibt es bei der vollen Auswahl — sonst gäbe es keinen Gewinner.
with connect() as conn:
gesehen = {
r["movie_id"] for r in conn.execute("SELECT movie_id FROM watched")
}
frisch = [i for i, e in enumerate(eintraege) if e["movie_id"] not in gesehen]
treffer = random.choice(frisch) if frisch else random.randrange(len(eintraege))
schritt = 360 / len(eintraege)
runden = 5 + random.randrange(3)
# Der Zeiger steht oben, also muss die Mitte des Zielsegments dorthin

View File

@@ -331,6 +331,11 @@ async function ladeGesehen() {
}
}
// Für die Karten: nur die Kennungen, als Menge zum schnellen Nachschlagen
const gesehenIds = computed(
() => new Set(gesehen.value.map((e) => e.movie_id))
)
// Ausgeblendete Filme zählen für Gäste nicht mit
const gesehenZahl = computed(
() => gesehen.value.filter((e) => host.value || !e.hidden).length
@@ -659,12 +664,14 @@ onUnmounted(() => {
<div v-if="activeTab === 'search' || activeTab === 'aehnlich'" class="layout">
<SearchTab
v-if="activeTab === 'search'"
:gesehen-ids="gesehenIds"
:filter="suchFilter"
:ansicht="ansicht"
@open="selected = $event"
/>
<AehnlichTab
:gesehen-ids="gesehenIds"
v-else
:filter="suchFilter"
:ansicht="ansicht"
@@ -696,6 +703,7 @@ onUnmounted(() => {
:host="host"
:merkliste="merkliste"
:mine="meinVorschlag?.id ?? null"
:gesehen-ids="gesehenIds"
@open="selected = $event"
@reset="meinVorschlag = null"
/>
@@ -709,6 +717,7 @@ onUnmounted(() => {
<MerklisteTab
v-else-if="activeTab === 'merkliste'"
:gesehen-ids="gesehenIds"
:items="merkliste"
:ich="ich"
@open="selected = $event"
@@ -743,6 +752,7 @@ onUnmounted(() => {
<MovieCard
v-if="entry.parts.length === 1"
:movie="entry.parts[0]"
:gesehen="gesehenIds.has(entry.parts[0].id)"
:auf-dem-rad="aufDemRad.includes(entry.parts[0].id)"
:ansicht="ansicht"
@open="filmOeffnen"
@@ -750,6 +760,7 @@ onUnmounted(() => {
<SeriesCard
v-else
:parts="entry.parts"
:gesehen-ids="gesehenIds"
:start="entry.best"
:auf-dem-rad="aufDemRad"
:ansicht="ansicht"

View File

@@ -13,6 +13,7 @@ const VORSCHLAEGE = { kompakt: 20, details: 8 }
const props = defineProps({
filter: { type: Object, default: () => ({}) },
ansicht: { type: String, default: 'kompakt' },
gesehenIds: { type: Set, default: () => new Set() },
// Kommt aus der Filmansicht: von diesem Film soll es losgehen
start: { type: Object, default: null },
})
@@ -177,6 +178,7 @@ async function oeffnen(movie) {
:key="film.id"
:movie="film"
:ansicht="ansicht"
:gesehen="gesehenIds.has(film.id)"
@open="waehlen"
/>
</div>
@@ -203,6 +205,7 @@ async function oeffnen(movie) {
:key="film.id"
:movie="film"
:ansicht="ansicht"
:gesehen="gesehenIds.has(film.id)"
@open="oeffnen"
/>
</div>

View File

@@ -5,6 +5,7 @@ import { posterUrl } from '../images'
const props = defineProps({
items: { type: Array, default: () => [] },
ich: { type: Object, default: null },
gesehenIds: { type: Set, default: () => new Set() },
})
// Nach Person gruppiert, die eigene Sammlung zuerst
@@ -87,7 +88,12 @@ async function oeffnen(item) {
</h3>
<div class="grid">
<article v-for="item in gefiltert(gruppe)" :key="item.id" class="karte">
<article
v-for="item in gefiltert(gruppe)"
:key="item.id"
class="karte"
:class="{ gesehen: gesehenIds.has(item.movie_id) }"
>
<button type="button" class="cover" @click="oeffnen(item)">
<img
v-if="posterUrl(item.poster_path, 'w342')"
@@ -126,6 +132,15 @@ async function oeffnen(item) {
gap: 1rem;
}
/* Schon gelaufen — die Karte tritt zurück, kommt beim Überfahren wieder */
.karte.gesehen {
opacity: 0.45;
}
.karte.gesehen:hover {
opacity: 1;
}
.karte {
display: flex;
gap: 0.9rem;

View File

@@ -5,6 +5,8 @@ import { backdropUrl, posterUrl } from '../images'
const props = defineProps({
movie: { type: Object, required: true },
aufDemRad: { type: Boolean, default: false },
// Schon gelaufen — die Karte tritt dann zurück
gesehen: { type: Boolean, default: false },
ansicht: { type: String, default: 'kompakt' },
})
@@ -30,7 +32,7 @@ const meta = computed(() =>
<template>
<button
class="card"
:class="[ansicht, { 'auf-dem-rad': aufDemRad }]"
:class="[ansicht, { 'auf-dem-rad': aufDemRad, gesehen }]"
type="button"
@click="$emit('open', movie)"
>
@@ -69,6 +71,16 @@ const meta = computed(() =>
</template>
<style scoped>
/* Schon gelaufen — die Karte tritt zurück, bleibt aber lesbar.
Beim Überfahren kommt sie zurück. */
.card.gesehen {
opacity: 0.45;
}
.card.gesehen:hover {
opacity: 1;
}
/* Details: breite Karte, Poster links, alles lesbar statt abgeschnitten */
.haupt {
display: flex;

View File

@@ -7,6 +7,7 @@ const props = defineProps({
// Was die Filterleiste eingestellt hat
filter: { type: Object, default: () => ({}) },
ansicht: { type: String, default: 'kompakt' },
gesehenIds: { type: Set, default: () => new Set() },
})
const emit = defineEmits(['open'])
@@ -122,6 +123,7 @@ async function open(movie) {
:key="movie.id"
:movie="movie"
:ansicht="ansicht"
:gesehen="gesehenIds.has(movie.id)"
@open="open"
/>
</div>

View File

@@ -7,6 +7,8 @@ const props = defineProps({
// Die Karte öffnet mit dem stärksten Teil, nicht mit dem ersten
start: { type: Number, default: 0 },
aufDemRad: { type: Array, default: () => [] },
// Kennungen der schon gelaufenen Filme
gesehenIds: { type: Set, default: () => new Set() },
ansicht: { type: String, default: 'kompakt' },
})
@@ -21,6 +23,8 @@ const rating = computed(() =>
const stehtAufDemRad = computed(() => props.aufDemRad.includes(current.value.id))
const schonGesehen = computed(() => props.gesehenIds.has(current.value.id))
const shots = computed(() =>
(current.value.images || []).slice(0, 4).map((pfad) => backdropUrl(pfad, 'w300'))
)
@@ -38,7 +42,7 @@ function step(delta) {
</script>
<template>
<article class="card" :class="[ansicht, { 'auf-dem-rad': stehtAufDemRad }]">
<article class="card" :class="[ansicht, { 'auf-dem-rad': stehtAufDemRad, gesehen: schonGesehen }]">
<span v-if="stehtAufDemRad" class="rad-marke" title="Steht auf dem Rad">Rad</span>
<div class="haupt">
<button class="poster" type="button" @click="$emit('open', current)">
@@ -91,6 +95,16 @@ function step(delta) {
</template>
<style scoped>
/* Schon gelaufen — die Karte tritt zurück, bleibt aber lesbar.
Beim Überfahren kommt sie zurück. */
.card.gesehen {
opacity: 0.45;
}
.card.gesehen:hover {
opacity: 1;
}
.card {
position: relative;
display: flex;

View File

@@ -8,6 +8,7 @@ const props = defineProps({
// Beides hält die App schon aktuell — auch nach einem Namenswechsel
merkliste: { type: Array, default: () => [] },
mine: { type: Number, default: null },
gesehenIds: { type: Set, default: () => new Set() },
})
const emit = defineEmits(['open', 'reset'])
@@ -319,7 +320,7 @@ async function oeffnen(item) {
<button
type="button"
class="eintrag"
:class="{ eigen: item.id === mine }"
:class="{ eigen: item.id === mine, gesehen: gesehenIds.has(item.movie_id) }"
@click="oeffnen(item)"
>
<span
@@ -563,6 +564,15 @@ async function oeffnen(item) {
border-radius: 8px;
}
/* Schon gelaufen — die Ziehung überspringt diesen Film */
.eintrag.gesehen {
opacity: 0.45;
}
.eintrag.gesehen:hover {
opacity: 1;
}
.eintrag:hover {
background: var(--bg-soft);
}