This commit is contained in:
team3
2026-08-31 11:54:20 +02:00
parent f29a4106bb
commit e7216138c7
21 changed files with 1517 additions and 87 deletions

View File

@@ -1,8 +1,10 @@
<script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { posterUrl } from '../images'
import { abonnieren } from '../live'
const emit = defineEmits(['open'])
defineProps({ host: Boolean })
const emit = defineEmits(['open', 'reset'])
const items = ref([])
const mine = ref(null)
@@ -26,16 +28,14 @@ const FARBEN = [
]
// Ohne Server-Ziehung sähe jede Seite ein anderes Ergebnis
let quelle = null
let abbestellen = null
function verbinden() {
quelle = new EventSource('/api/events')
quelle.onmessage = (ereignis) => {
const nachricht = JSON.parse(ereignis.data)
abbestellen = abonnieren((nachricht) => {
if (nachricht.type === 'suggestions') laden_()
else if (nachricht.type === 'spin') abspielen(nachricht.data)
}
// Der Browser verbindet nach einem Abbruch von selbst neu
else if (nachricht.type === 'reset') zurueckgesetzt()
})
}
function abspielen(ergebnis, uebersprungen = 0) {
@@ -101,7 +101,7 @@ onMounted(async () => {
verbinden()
})
onUnmounted(() => quelle?.close())
onUnmounted(() => abbestellen?.())
defineExpose({ neu: laden_ })
@@ -165,6 +165,25 @@ async function drehen() {
}
}
function zurueckgesetzt() {
// Ohne Dauer 0 würde das Rad die Strecke zurück sichtbar abfahren
dauer.value = 0
winkel.value = 0
gewinner.value = null
dreht.value = false
emit('reset')
laden_()
}
async function zuruecksetzen() {
// Der Server meldet den Reset an alle zurück, auch an uns
try {
await fetch('/api/suggestions/alle', { method: 'DELETE' })
} catch {
fehler.value = 'Zurücksetzen hat nicht geklappt.'
}
}
async function oeffnen(item) {
const resp = await fetch(`/api/search/${item.movie_id}`)
if (resp.ok) emit('open', await resp.json())
@@ -174,64 +193,70 @@ async function oeffnen(item) {
<template>
<div class="wheel-tab">
<div class="buehne">
<p v-if="laden" class="note">Lade Vorschläge…</p>
<p v-else-if="fehler" class="note error">{{ fehler }}</p>
<p v-else-if="!items.length" class="note">
Noch keine Vorschläge. Öffne einen Film und klicke auf „Vorschlagen“.
</p>
<p v-if="fehler" class="note error">{{ fehler }}</p>
<template v-else>
<div class="rad-halter">
<div class="zeiger"></div>
<svg
class="rad"
:class="{ dreht }"
:viewBox="`0 0 ${GROESSE} ${GROESSE}`"
:style="{
transform: `rotate(${winkel}deg)`,
transitionDuration: `${dauer}s`,
}"
>
<g v-for="(seg, i) in segmente" :key="seg.item.id">
<path :d="seg.pfad" :fill="seg.farbe" stroke="#0a0a0c" stroke-width="2" />
<text
v-if="seg.breit"
:transform="`rotate(${seg.textDreh} ${MITTE} ${MITTE})`"
:x="seg.gespiegelt ? MITTE - 40 : MITTE + 40"
:y="MITTE + 4"
:text-anchor="seg.gespiegelt ? 'end' : 'start'"
class="beschriftung"
>
{{ seg.kurz }}
</text>
</g>
<circle :cx="MITTE" :cy="MITTE" r="26" fill="#0a0a0c" />
</svg>
</div>
<div class="rad-halter">
<div class="zeiger"></div>
<svg
class="rad"
:class="{ dreht }"
:viewBox="`0 0 ${GROESSE} ${GROESSE}`"
:style="{
transform: `rotate(${winkel}deg)`,
transitionDuration: `${dauer}s`,
}"
>
<circle :cx="MITTE" :cy="MITTE" :r="RADIUS" fill="var(--bg-soft)" />
<g v-for="(seg, i) in segmente" :key="seg.item.id">
<path :d="seg.pfad" :fill="seg.farbe" stroke="#0a0a0c" stroke-width="2" />
<text
v-if="seg.breit"
:transform="`rotate(${seg.textDreh} ${MITTE} ${MITTE})`"
:x="seg.gespiegelt ? MITTE - 40 : MITTE + 40"
:y="MITTE + 4"
:text-anchor="seg.gespiegelt ? 'end' : 'start'"
class="beschriftung"
>
{{ seg.kurz }}
</text>
</g>
<circle :cx="MITTE" :cy="MITTE" r="26" fill="#0a0a0c" />
</svg>
</div>
<button class="dreh" type="button" :disabled="dreht" @click="drehen">
<div v-if="host" class="knoepfe">
<button class="dreh" type="button" :disabled="dreht || !items.length" @click="drehen">
{{ dreht ? 'dreht…' : 'Drehen' }}
</button>
<button
class="reset"
type="button"
:disabled="dreht || !items.length"
@click="zuruecksetzen"
>
Zurücksetzen
</button>
</div>
<div v-if="gewinner" class="gewinner" @click="oeffnen(gewinner)">
<img
v-if="posterUrl(gewinner.poster_path, 'w154')"
:src="posterUrl(gewinner.poster_path, 'w154')"
:alt="gewinner.title"
/>
<div>
<p class="label">Der Film des Abends</p>
<h2>{{ gewinner.title }}</h2>
<p class="jahr">
{{ gewinner.year }}<span v-if="gewinner.name"> · von {{ gewinner.name }}</span>
</p>
</div>
<div v-if="gewinner" class="gewinner" @click="oeffnen(gewinner)">
<img
v-if="posterUrl(gewinner.poster_path, 'w154')"
:src="posterUrl(gewinner.poster_path, 'w154')"
:alt="gewinner.title"
/>
<div>
<p class="label">Der Film des Abends</p>
<h2>{{ gewinner.title }}</h2>
<p class="jahr">
{{ gewinner.year }}<span v-if="gewinner.name"> · von {{ gewinner.name }}</span>
</p>
</div>
</template>
</div>
</div>
<aside v-if="items.length" class="liste">
<aside class="liste">
<h3>Vorschläge <span class="anzahl">{{ items.length }}</span></h3>
<p v-if="!items.length && !laden" class="leer">Noch nichts vorgeschlagen.</p>
<ul>
<li v-for="(item, i) in items" :key="item.id">
<button
@@ -300,6 +325,35 @@ async function oeffnen(item) {
border-left: 11px solid transparent;
}
.knoepfe {
display: flex;
gap: 0.7rem;
}
.reset {
padding: 0.7rem 1.4rem;
color: var(--muted);
font-size: 0.95rem;
background: var(--bg-soft);
border: 1px solid var(--line);
border-radius: 8px;
}
.reset:hover:not(:disabled) {
color: var(--text);
border-color: var(--muted);
}
.reset:disabled {
opacity: 0.4;
cursor: default;
}
.leer {
color: var(--muted);
font-size: 0.85rem;
}
.dreh {
padding: 0.7rem 2.2rem;
color: #fff;