Beforeunload-Warnung + Vollbild-Button fuer Viewer

- beforeunload Event verhindert versehentliches Verlassen/Reload
  waehrend Broadcasting oder Viewing aktiv ist
- Vollbild-Button im Viewer-Header (Fullscreen API)
- Fullscreen-State wird korrekt getrackt und Icon wechselt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-07 01:37:21 +01:00
parent 2ee36789b2
commit 3ef25fc10a
5 changed files with 109 additions and 51 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View file

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gaming Hub</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎮</text></svg>" />
<script type="module" crossorigin src="/assets/index-C3FFX5uU.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CcoMcI3c.css">
<script type="module" crossorigin src="/assets/index-B0xoTjru.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BL4zgtRP.css">
</head>
<body>
<div id="root"></div>

View file

@ -456,6 +456,37 @@ export default function StreamingTab({ data }: { data: any }) {
setViewing(null);
}, [cleanupViewer, wsSend]);
// ── Warn before leaving page while active ──
useEffect(() => {
const handler = (e: BeforeUnloadEvent) => {
if (isBroadcastingRef.current || viewingRef.current) {
e.preventDefault();
}
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, []);
// ── Fullscreen toggle for viewer ──
const viewerContainerRef = useRef<HTMLDivElement | null>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const toggleFullscreen = useCallback(() => {
const el = viewerContainerRef.current;
if (!el) return;
if (!document.fullscreenElement) {
el.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen().catch(() => {});
}
}, []);
useEffect(() => {
const handler = () => setIsFullscreen(!!document.fullscreenElement);
document.addEventListener('fullscreenchange', handler);
return () => document.removeEventListener('fullscreenchange', handler);
}, []);
// ── Cleanup on unmount ──
useEffect(() => {
return () => {
@ -473,7 +504,7 @@ export default function StreamingTab({ data }: { data: any }) {
if (viewing) {
const stream = streams.find(s => s.id === viewing.streamId);
return (
<div className="stream-viewer-overlay">
<div className="stream-viewer-overlay" ref={viewerContainerRef}>
<div className="stream-viewer-header">
<div className="stream-viewer-header-left">
<span className="stream-live-badge"><span className="stream-live-dot" /> LIVE</span>
@ -484,8 +515,13 @@ export default function StreamingTab({ data }: { data: any }) {
</div>
</div>
</div>
<div className="stream-viewer-header-right">
<button className="stream-viewer-fullscreen" onClick={toggleFullscreen} title={isFullscreen ? 'Vollbild verlassen' : 'Vollbild'}>
{isFullscreen ? '\u2716' : '\u26F6'}
</button>
<button className="stream-viewer-close" onClick={leaveViewing}>Verlassen</button>
</div>
</div>
<div className="stream-viewer-video">
{viewing.phase === 'connecting' ? (
<div className="stream-viewer-connecting">

View file

@ -229,6 +229,28 @@
font-size: 13px;
color: var(--text-muted);
}
.stream-viewer-header-right {
display: flex;
align-items: center;
gap: 8px;
}
.stream-viewer-fullscreen {
background: rgba(255, 255, 255, 0.1);
border: none;
color: #fff;
width: 36px;
height: 36px;
border-radius: var(--radius);
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
transition: background var(--transition);
}
.stream-viewer-fullscreen:hover {
background: rgba(255, 255, 255, 0.25);
}
.stream-viewer-close {
background: rgba(255, 255, 255, 0.1);
border: none;