Frontend, README und Konfigurationsvorlage
React-Oberfläche mit Übersicht, Match-Erstellung, Beitritt über Team- und Zuschauercodes, Teamkarten, visuellem Map-Veto, Serie und Serverpanel. README beschreibt Einrichtung, Umgebungsvariablen, Rollen, Veto-Abläufe, Serveradapter und CSTV-Zuschauerzugang.
This commit is contained in:
1 parent
f08297dd0d
commit
f7520a6ca7
24 files changed
+1938
-1
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { io } from 'socket.io-client';
|
||||
import { api } from './api.js';
|
||||
|
||||
let shared = null;
|
||||
export function getSocket() {
|
||||
if (!shared) shared = io({ path: '/socket.io', transports: ['websocket', 'polling'] });
|
||||
return shared;
|
||||
}
|
||||
|
||||
/** Abonniert den Live-Zustand eines Matches. Meldet sich nach jedem Reconnect neu an. */
|
||||
export function useMatch(matchId, token) {
|
||||
const [state, setState] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
const tokenRef = useRef(token);
|
||||
tokenRef.current = token;
|
||||
|
||||
useEffect(() => {
|
||||
const socket = getSocket();
|
||||
const subscribe = () => socket.emit('match:subscribe', { matchId, token: tokenRef.current });
|
||||
const onState = (s) => {
|
||||
if (s.id !== matchId) return;
|
||||
setState(s);
|
||||
setError(null);
|
||||
};
|
||||
const onError = (e) => setError(e);
|
||||
const onConnect = () => {
|
||||
setConnected(true);
|
||||
subscribe();
|
||||
};
|
||||
const onDisconnect = () => setConnected(false);
|
||||
|
||||
socket.on('match:state', onState);
|
||||
socket.on('match:error', onError);
|
||||
socket.on('connect', onConnect);
|
||||
socket.on('disconnect', onDisconnect);
|
||||
if (socket.connected) onConnect();
|
||||
|
||||
// Erster Stand per REST, falls der Socket noch verbindet; neuere Socket-Stände haben Vorrang.
|
||||
let active = true;
|
||||
api(`/matches/${matchId}`, { token })
|
||||
.then((s) => active && setState((prev) => (prev && prev.updatedAt >= s.updatedAt ? prev : s)))
|
||||
.catch((e) => active && setError({ error: e.message, code: e.code }));
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
socket.off('match:state', onState);
|
||||
socket.off('match:error', onError);
|
||||
socket.off('connect', onConnect);
|
||||
socket.off('disconnect', onDisconnect);
|
||||
};
|
||||
}, [matchId, token]);
|
||||
|
||||
return { state, error, connected };
|
||||
}
|
||||
|
||||
export function useOverview() {
|
||||
const [matches, setMatches] = useState(null);
|
||||
const [connected, setConnected] = useState(false);
|
||||
useEffect(() => {
|
||||
const socket = getSocket();
|
||||
const onConnect = () => {
|
||||
setConnected(true);
|
||||
socket.emit('overview:subscribe');
|
||||
};
|
||||
const onOverview = (d) => setMatches(d.matches);
|
||||
const onDisconnect = () => setConnected(false);
|
||||
socket.on('overview', onOverview);
|
||||
socket.on('connect', onConnect);
|
||||
socket.on('disconnect', onDisconnect);
|
||||
if (socket.connected) onConnect();
|
||||
return () => {
|
||||
socket.off('overview', onOverview);
|
||||
socket.off('connect', onConnect);
|
||||
socket.off('disconnect', onDisconnect);
|
||||
};
|
||||
}, []);
|
||||
return { matches, connected };
|
||||
}
|
||||
Reference in new issue
Block a user