This commit is contained in:
team3
2026-09-09 17:58:52 +02:00
parent 2bd03000e8
commit 847b747870
8 changed files with 419 additions and 55 deletions

View File

@@ -349,7 +349,13 @@ async function michSetzen(id) {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: id ?? null }),
})
ladeRunde()
// Sammlung, Vorschlag, Wertungen und Wünsche gehören zum Namen. Ohne
// Nachladen zeigt die Seite weiter die des vorigen Benutzers.
await ladeRunde()
ladeVorschlag()
ladeMerkliste()
ladeGesehen()
ladeWuensche()
}
async function ladeRunde() {
@@ -688,6 +694,7 @@ onUnmounted(() => {
v-else-if="activeTab === 'wheel'"
ref="rad"
:host="host"
:ich="ich"
@open="selected = $event"
@reset="meinVorschlag = null"
/>

View File

@@ -1,9 +1,13 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { posterUrl } from '../images'
import { abonnieren } from '../live'
defineProps({ host: Boolean })
const props = defineProps({
host: Boolean,
// Wer gerade angemeldet ist. Beim Wechsel gehört eine andere Sammlung dazu.
ich: { type: Object, default: null },
})
const emit = defineEmits(['open', 'reset'])
const items = ref([])
@@ -95,8 +99,14 @@ async function laden_() {
}
}
watch(() => props.ich?.id, () => {
sammlungLaden()
laden_()
})
onMounted(async () => {
await laden_()
await sammlungLaden()
await laufendenDrehHolen()
verbinden()
})
@@ -184,6 +194,50 @@ async function zuruecksetzen() {
}
}
// Aus der eigenen Sammlung würfeln. Der Schalter trennt Horror von allem
// anderen — für Abende, an denen es keiner gruselig mag.
const nurHorror = ref(true)
const sammlung = ref([])
const wuerfelt = ref(false)
async function sammlungLaden() {
try {
const resp = await fetch('/api/wishlist')
if (resp.ok) sammlung.value = (await resp.json()).filter((e) => e.mine)
} catch {
sammlung.value = []
}
}
function nachGenre(horror) {
return sammlung.value.filter(
(e) => (e.genres || []).includes('Horror') === horror
)
}
const auswahl = computed(() => nachGenre(nurHorror.value))
const anzahlHorror = computed(() => nachGenre(true).length)
const anzahlOhne = computed(() => nachGenre(false).length)
async function wuerfeln() {
const treffer = auswahl.value
if (!treffer.length || wuerfelt.value) return
wuerfelt.value = true
const film = treffer[Math.floor(Math.random() * treffer.length)]
try {
const resp = await fetch('/api/suggestions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ movie_id: film.movie_id }),
})
if (!resp.ok) throw new Error()
} catch {
fehler.value = 'Eintragen hat nicht geklappt.'
} finally {
wuerfelt.value = false
}
}
async function oeffnen(item) {
const resp = await fetch(`/api/search/${item.movie_id}`)
if (resp.ok) emit('open', await resp.json())
@@ -255,6 +309,25 @@ async function oeffnen(item) {
</div>
<aside class="liste">
<div v-if="!host" class="sammlung-wuerfeln">
<div class="schalter">
<button type="button" :class="{ an: nurHorror }" @click="nurHorror = true">
Horror ({{ anzahlHorror }})
</button>
<button type="button" :class="{ an: !nurHorror }" @click="nurHorror = false">
Kein Horror ({{ anzahlOhne }})
</button>
</div>
<button
type="button"
class="wuerfeln"
:disabled="!auswahl.length || wuerfelt"
@click="wuerfeln"
>
Zufällig aus Sammlung
</button>
</div>
<h3>Vorschläge <span class="anzahl">{{ items.length }}</span></h3>
<p v-if="!items.length && !laden" class="leer">Noch nichts vorgeschlagen.</p>
<ul>
@@ -265,9 +338,27 @@ async function oeffnen(item) {
:class="{ eigen: item.id === mine }"
@click="oeffnen(item)"
>
<span class="punkt" :style="{ background: FARBEN[i % FARBEN.length] }"></span>
<span class="titel">{{ item.title }}</span>
<span class="von">{{ item.name || '—' }}</span>
<span
class="cover"
:style="{ borderColor: FARBEN[i % FARBEN.length] }"
>
<img
v-if="posterUrl(item.poster_path, 'w154')"
:src="posterUrl(item.poster_path, 'w154')"
:alt="item.title"
loading="lazy"
/>
</span>
<span class="daten">
<span class="titel">{{ item.title }}</span>
<span class="meta">
{{ item.year }}<template v-if="item.runtime"> · {{ item.runtime }} Min.</template>
</span>
<span v-if="item.genres?.length" class="genres">
{{ item.genres.join(' · ') }}
</span>
<span class="von">{{ item.name || 'Anonym' }}</span>
</span>
</button>
</li>
</ul>
@@ -369,6 +460,53 @@ async function oeffnen(item) {
cursor: default;
}
.sammlung-wuerfeln {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1.6rem;
}
.schalter {
display: flex;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
}
.schalter button {
flex: 1;
padding: 0.45rem 0.9rem;
color: var(--muted);
font-size: 0.85rem;
font-weight: 600;
background: var(--bg);
}
.schalter button.an {
color: var(--text);
background: var(--bg-soft);
}
.wuerfeln {
padding: 0.5rem 1.1rem;
font-size: 0.88rem;
font-weight: 600;
background: var(--bg);
border: 1px solid var(--line);
border-radius: 8px;
}
.wuerfeln:hover:not(:disabled) {
border-color: var(--accent);
}
.wuerfeln:disabled {
color: var(--muted);
cursor: not-allowed;
opacity: 0.5;
}
.gewinner {
display: flex;
gap: 1rem;
@@ -431,42 +569,73 @@ async function oeffnen(item) {
.eintrag {
display: flex;
align-items: center;
gap: 0.5rem;
gap: 0.7rem;
align-items: flex-start;
width: 100%;
padding: 0.4rem 0.5rem;
padding: 0.55rem;
color: var(--text);
font-size: 0.86rem;
text-align: left;
border-radius: 6px;
border-radius: 8px;
}
.eintrag:hover {
background: var(--bg-soft);
}
.eintrag.eigen .titel {
color: var(--accent);
font-weight: 600;
/* Der farbige Rand verbindet den Eintrag mit seinem Stück auf dem Rad */
.cover {
display: block;
width: 46px;
flex-shrink: 0;
overflow: hidden;
background: var(--bg-soft);
border-left: 3px solid;
border-radius: 3px;
}
.punkt {
width: 0.6rem;
height: 0.6rem;
flex-shrink: 0;
border-radius: 50%;
.cover img {
display: block;
width: 100%;
aspect-ratio: 2 / 3;
object-fit: cover;
}
.daten {
display: flex;
min-width: 0;
flex-direction: column;
gap: 0.15rem;
padding-top: 0.1rem;
}
.titel {
flex: 1;
overflow: hidden;
font-weight: 600;
line-height: 1.25;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.eintrag.eigen .titel {
color: var(--accent);
}
.meta,
.genres,
.von {
overflow: hidden;
color: var(--muted);
font-size: 0.74rem;
white-space: nowrap;
text-overflow: ellipsis;
}
.von {
color: var(--muted);
font-size: 0.76rem;
margin-top: 0.15rem;
font-size: 0.72rem;
opacity: 0.75;
}
.note {