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:
16
Makefile
16
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: install dev stop sync build prod logs down push pull stream stream-stop mtx mtx-stop
|
||||
.PHONY: install dev stop sync build prod logs down push pull stream stream-local stream-stop mtx mtx-stop
|
||||
|
||||
install:
|
||||
cd backend && python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
|
||||
@@ -48,13 +48,19 @@ down:
|
||||
docker compose down
|
||||
|
||||
# ── Senden ───────────────────────────────────────────────────────────────────
|
||||
# Ohne Argument an den lokalen MediaMTX, sonst an den Server:
|
||||
# make stream ZIEL=horror.marha.de
|
||||
ZIEL ?= 127.0.0.1
|
||||
# Der Regelfall geht an den Server. Ein anderes Ziel bei Bedarf:
|
||||
# make stream ZIEL=anderer.host
|
||||
ZIEL ?= horror.marha.de
|
||||
|
||||
stream: mtx
|
||||
# Räumt vorher auf: ein noch laufendes OBS belegt sonst die Sendestrecke,
|
||||
# und der Tonweg wird sauber neu aufgebaut.
|
||||
stream: stream-stop
|
||||
@scripts/stream.sh $(ZIEL)
|
||||
|
||||
# Zum Ausprobieren ohne Server, gegen das lokale MediaMTX
|
||||
stream-local: mtx
|
||||
@scripts/stream.sh 127.0.0.1
|
||||
|
||||
stream-stop:
|
||||
-@pkill -f "obs --profile filmabend" 2>/dev/null
|
||||
-@scripts/ton.sh ab
|
||||
|
||||
@@ -129,9 +129,9 @@ OBS ──RTMP──► MediaMTX ──LL-HLS──► FastAPI ──► Browser
|
||||
**Senden:**
|
||||
|
||||
```bash
|
||||
make stream # an den lokalen MediaMTX
|
||||
make stream ZIEL=horror.marha.de # an den Server
|
||||
make stream-stop # OBS beenden
|
||||
make stream # an den Server, beendet vorher ein laufendes OBS
|
||||
make stream-local # an den lokalen MediaMTX, zum Ausprobieren
|
||||
make stream-stop # OBS beenden und den Tonweg zurückstellen
|
||||
```
|
||||
|
||||
Strg-C im Terminal beendet OBS nicht zuverlässig — deshalb `make stream-stop`
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user