Ein Namensfeld für alles, oben rechts

Bisher fragten Vorschlag und Wertung getrennt nach dem Namen — einmal in der
Filmansicht, einmal über der Liste. Jetzt steht er einmal in der
Reiterleiste und gilt für beides. Wer keinen einträgt, bleibt anonym.

Dazu: make stream geht jetzt an den Server und beendet vorher ein laufendes
OBS. Der lokale Weg heißt make stream-local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XVnRop5c3or3B8xE2hpgZ
This commit is contained in:
team3
2026-08-31 13:48:40 +02:00
parent 56c3f10026
commit bbd1270fd9
5 changed files with 46 additions and 56 deletions

View File

@@ -224,6 +224,13 @@ const gesehenZahl = computed(
// Wer den Abend leitet, darf drehen und die Runde leeren. Kein Login —
// die Tastenkombination reicht, die Seite steht ohnehin nur Freunden offen.
// Ein Name für alles: Vorschläge und Wertungen. Ohne Namen läuft es
// anonym, das ist ausdrücklich erlaubt.
const NAME_SCHLUESSEL = 'filmabend_name'
const name = ref(localStorage.getItem(NAME_SCHLUESSEL) || '')
watch(name, (wert) => localStorage.setItem(NAME_SCHLUESSEL, wert))
const HOST_SCHLUESSEL = 'filmabend_host'
const host = ref(localStorage.getItem(HOST_SCHLUESSEL) === '1')
@@ -389,6 +396,13 @@ onUnmounted(() => window.removeEventListener('keydown', tastatur))
{{ tab.label }}
<span v-if="tab.count !== null" class="count">{{ tab.count }}</span>
</button>
<input
v-model.trim="name"
type="text"
class="name"
placeholder="Dein Name"
maxlength="40"
/>
<span v-if="host" class="host">Host</span>
</nav>
@@ -405,6 +419,7 @@ onUnmounted(() => window.removeEventListener('keydown', tastatur))
<WatchedTab
v-else-if="activeTab === 'watched'"
:host="host"
:name="name"
@open="selected = $event"
@changed="ladeGesehen"
/>
@@ -466,6 +481,7 @@ onUnmounted(() => window.removeEventListener('keydown', tastatur))
@open="selected = $event"
:mein-vorschlag="meinVorschlag"
:host="host"
:name="name"
:gesehen="gesehen.some((e) => e.movie_id === selected.id)"
@suggested="nachVorschlag"
@watched="ladeGesehen"
@@ -512,9 +528,15 @@ onUnmounted(() => window.removeEventListener('keydown', tastatur))
border-bottom-color: var(--accent);
}
.tabs .name {
width: 11rem;
margin: 0 0 0.35rem auto;
padding: 0.35rem 0.6rem;
font-size: 0.82rem;
}
.host {
align-self: center;
margin-left: auto;
padding: 0.15rem 0.5rem;
color: var(--accent);
font-size: 0.7rem;

View File

@@ -7,33 +7,35 @@ const props = defineProps({
series: { type: Array, default: () => [] },
meinVorschlag: { type: Object, default: null },
host: { type: Boolean, default: false },
name: { type: String, default: '' },
gesehen: { type: Boolean, default: false },
})
const emit = defineEmits(['close', 'open', 'suggested', 'watched'])
const vorschlagen = ref({ zustand: 'bereit', name: '' })
const vorschlagen = ref({ zustand: 'bereit' })
const schonVorgeschlagen = computed(
() => props.meinVorschlag?.movie_id === props.movie.id
)
async function abschicken() {
vorschlagen.value = { ...vorschlagen.value, zustand: 'laeuft' }
vorschlagen.value = { zustand: 'laeuft' }
try {
const resp = await fetch('/api/suggestions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
movie_id: props.movie.id,
name: vorschlagen.value.name || null,
// Der Name steht oben rechts, einmal für alles
name: props.name || null,
}),
})
if (!resp.ok) throw new Error()
vorschlagen.value = { ...vorschlagen.value, zustand: 'fertig' }
vorschlagen.value = { zustand: 'fertig' }
emit('suggested')
} catch {
vorschlagen.value = { ...vorschlagen.value, zustand: 'fehler' }
vorschlagen.value = { zustand: 'fehler' }
}
}
@@ -145,12 +147,6 @@ onUnmounted(() => {
Du hast „{{ meinVorschlag.title }}“ vorgeschlagen. Ein neuer
Vorschlag ersetzt ihn.
</span>
<input
v-model="vorschlagen.name"
type="text"
placeholder="Dein Name (optional)"
maxlength="40"
/>
<button
type="button"
:disabled="vorschlagen.zustand === 'laeuft'"
@@ -361,12 +357,6 @@ h2 {
border-top: 1px solid var(--line);
}
.vorschlag input {
width: 11rem;
padding: 0.45rem 0.7rem;
font-size: 0.85rem;
}
.vorschlag button {
padding: 0.45rem 1rem;
font-size: 0.85rem;

View File

@@ -3,7 +3,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
import { posterUrl } from '../images'
import { abonnieren } from '../live'
const props = defineProps({ host: Boolean })
const props = defineProps({ host: Boolean, name: { type: String, default: '' } })
const emit = defineEmits(['open', 'changed'])
const items = ref([])
@@ -16,10 +16,6 @@ const sichtbar = computed(() =>
const fehler = ref('')
const laden = ref(true)
// Der Name gilt für alle Bewertungen und überlebt das Neuladen
const NAME_SCHLUESSEL = 'filmabend_name'
const name = ref(localStorage.getItem(NAME_SCHLUESSEL) || '')
const STERNE = [1, 2, 3, 4, 5]
// Beim Schweben füllen sich alle Sterne bis zum Zeiger, wie gewohnt
@@ -45,14 +41,13 @@ async function laden_() {
}
async function bewerten(item, sterne) {
localStorage.setItem(NAME_SCHLUESSEL, name.value)
// Sofort anzeigen, der Live-Kanal liefert gleich die richtigen Zahlen
item.mine = sterne
try {
await fetch(`/api/watched/${item.id}/rating`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ stars: sterne, name: name.value || null }),
body: JSON.stringify({ stars: sterne, name: props.name || null }),
})
} catch {
fehler.value = 'Bewerten hat nicht geklappt.'
@@ -114,16 +109,6 @@ onUnmounted(() => abbestellen?.())
<template>
<div class="watched-tab">
<div class="kopf">
<input
v-model="name"
type="text"
class="name"
placeholder="Dein Name (optional)"
maxlength="40"
/>
</div>
<p v-if="fehler" class="note error">{{ fehler }}</p>
<p v-else-if="!laden && !sichtbar.length" class="note">
Noch kein Film geschaut.
@@ -223,19 +208,6 @@ onUnmounted(() => abbestellen?.())
</template>
<style scoped>
.kopf {
display: flex;
gap: 0.7rem;
align-items: center;
margin-bottom: 1.5rem;
}
.name {
width: 14rem;
padding: 0.55rem 0.8rem;
font-size: 0.9rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));