From 9f8a5a5ecc93c699f17f4a9d32ddd4043b6afc68 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 06:18:37 +0100 Subject: [PATCH] Expose overlay alert playback settings --- src/App.svelte | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index b84e308..375e917 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -192,7 +192,7 @@ let pluginError = ''; let pluginBusy = false; let pluginDropActive = false; - let overlays: Array<{ id: string; name: string; slug: string; draft_html: string; draft_css: string; draft_javascript: string; published: boolean; enabled: boolean; disabled_design: string }> = []; + let overlays: Array<{ id: string; name: string; slug: string; draft_html: string; draft_css: string; draft_javascript: string; published: boolean; enabled: boolean; disabled_design: string; alert_group_window_ms: number; alert_group_size: number; alert_display_ms: number }> = []; let selectedOverlayId = ''; let overlayName = ''; let overlaySlug = ''; @@ -201,11 +201,14 @@ let overlayJavascript = ''; let overlayEnabled = true; let overlayDisabledDesign = 'transparent'; + let overlayAlertGroupWindowMs = 200; + let overlayAlertGroupSize = 5; + let overlayAlertDisplayMs = 4000; let overlayError = ''; let overlayBusy = false; let overlaySavedSignature = ''; let overlayAutosaveAttemptedSignature = ''; - let pendingOverlayDraft: { overlayName: string; overlaySlug: string; overlayHtml: string; overlayCss: string; overlayJavascript: string; overlayEnabled: boolean; overlayDisabledDesign: string } | null = null; + let pendingOverlayDraft: { overlayName: string; overlaySlug: string; overlayHtml: string; overlayCss: string; overlayJavascript: string; overlayEnabled: boolean; overlayDisabledDesign: string; overlayAlertGroupWindowMs: number; overlayAlertGroupSize: number; overlayAlertDisplayMs: number } | null = null; let overlayDiagnosticsOpen = false; let overlayAutosaveTimer: number | undefined; let oauthScopes: Record<'streamer' | 'bot', { connected: boolean; missing_scopes: string[]; reauthorization_required: boolean }> = { @@ -1197,13 +1200,16 @@ overlayJavascript = overlay.draft_javascript; overlayEnabled = overlay.enabled; overlayDisabledDesign = overlay.disabled_design; + overlayAlertGroupWindowMs = overlay.alert_group_window_ms ?? 200; + overlayAlertGroupSize = overlay.alert_group_size ?? 5; + overlayAlertDisplayMs = overlay.alert_display_ms ?? 4000; overlaySavedSignature = overlayDraftSignature(); overlayAutosaveAttemptedSignature = overlaySavedSignature; restoreOverlayDraft(overlay.id); } function overlayDraftSignature() { - return JSON.stringify({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign }); + return JSON.stringify({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign, overlayAlertGroupWindowMs, overlayAlertGroupSize, overlayAlertDisplayMs }); } function overlayVariableWarnings() { @@ -1228,7 +1234,7 @@ function cacheOverlayDraft() { if (!selectedOverlayId) return; try { - localStorage.setItem(overlayDraftCacheKey(selectedOverlayId), JSON.stringify({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign })); + localStorage.setItem(overlayDraftCacheKey(selectedOverlayId), JSON.stringify({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign, overlayAlertGroupWindowMs, overlayAlertGroupSize, overlayAlertDisplayMs })); } catch { // Local storage is an optional recovery aid; server persistence remains authoritative. } @@ -1236,7 +1242,7 @@ function restoreOverlayDraft(id: string) { try { - const cached = JSON.parse(localStorage.getItem(overlayDraftCacheKey(id)) || 'null') as Partial<{ overlayName: string; overlaySlug: string; overlayHtml: string; overlayCss: string; overlayJavascript: string; overlayEnabled: boolean; overlayDisabledDesign: string }> | null; + const cached = JSON.parse(localStorage.getItem(overlayDraftCacheKey(id)) || 'null') as Partial<{ overlayName: string; overlaySlug: string; overlayHtml: string; overlayCss: string; overlayJavascript: string; overlayEnabled: boolean; overlayDisabledDesign: string; overlayAlertGroupWindowMs: number; overlayAlertGroupSize: number; overlayAlertDisplayMs: number }> | null; if (!cached || typeof cached.overlayName !== 'string') return; const cachedSignature = JSON.stringify(cached); if (cachedSignature !== overlayDraftSignature()) pendingOverlayDraft = { @@ -1247,6 +1253,9 @@ overlayJavascript: cached.overlayJavascript ?? overlayJavascript, overlayEnabled: cached.overlayEnabled ?? overlayEnabled, overlayDisabledDesign: cached.overlayDisabledDesign ?? overlayDisabledDesign, + overlayAlertGroupWindowMs: cached.overlayAlertGroupWindowMs ?? overlayAlertGroupWindowMs, + overlayAlertGroupSize: cached.overlayAlertGroupSize ?? overlayAlertGroupSize, + overlayAlertDisplayMs: cached.overlayAlertDisplayMs ?? overlayAlertDisplayMs, }; } catch { // Ignore malformed or unavailable local cache entries. @@ -1267,11 +1276,14 @@ overlayJavascript = "const alertBox = document.getElementById('trixie-follow-alert'); const alertName = document.getElementById('trixie-follow-name'); window.addEventListener('trixie-alert', (event) => { const detail = event.detail || {}; const payload = detail.payload || {}; if (detail.event_type !== 'follow') return; alertName.textContent = String(payload.user_name || payload.user_login || 'Someone'); alertBox.classList.add('visible'); clearTimeout(window.trixieAlertTimer); window.trixieAlertTimer = setTimeout(() => alertBox.classList.remove('visible'), 6000); });"; overlayEnabled = true; overlayDisabledDesign = 'transparent'; + overlayAlertGroupWindowMs = 200; + overlayAlertGroupSize = 5; + overlayAlertDisplayMs = 4000; } function restorePendingOverlayDraft() { if (!pendingOverlayDraft) return; - ({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign } = pendingOverlayDraft); + ({ overlayName, overlaySlug, overlayHtml, overlayCss, overlayJavascript, overlayEnabled, overlayDisabledDesign, overlayAlertGroupWindowMs, overlayAlertGroupSize, overlayAlertDisplayMs } = pendingOverlayDraft); pendingOverlayDraft = null; overlaySavedSignature = ''; overlayAutosaveAttemptedSignature = ''; @@ -1287,7 +1299,7 @@ overlayBusy = true; overlayError = ''; try { - const body = JSON.stringify({ name: overlayName, slug: overlaySlug, html: overlayHtml, css: overlayCss, javascript: overlayJavascript, disabled_design: overlayDisabledDesign, enabled: overlayEnabled }); + const body = JSON.stringify({ name: overlayName, slug: overlaySlug, html: overlayHtml, css: overlayCss, javascript: overlayJavascript, disabled_design: overlayDisabledDesign, enabled: overlayEnabled, alert_group_window_ms: Number(overlayAlertGroupWindowMs), alert_group_size: Number(overlayAlertGroupSize), alert_display_ms: Number(overlayAlertDisplayMs) }); const response = await fetch(selectedOverlayId ? `${serverAddress}/api/v1/overlays/${selectedOverlayId}` : `${serverAddress}/api/v1/overlays`, { method: selectedOverlayId ? 'PATCH' : 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` }, body }); if (!response.ok) throw new Error('The overlay draft could not be saved.'); const saved = await response.json(); @@ -1509,6 +1521,7 @@ {/if} {:else if activeView === 'Overlays'}
OVERLAYS
{#if selectedOverlayId || overlayName}
LIVE PREVIEW
{#if overlayError}
{overlayError}
{/if}{:else}
Select or create an overlayDrafts autosave when explicitly saved; publishing controls the browser source.
{/if}
+
ALERT PLAYBACK

Configure grouping and playback for the selected browser-source overlay. Changes are saved with the next draft save.

{:else if activeView === 'Settings'}
SERVER SETTINGS
DATABASE BACKUPS

Daily backups use the server's local schedule. Restore replaces the current database and is permanently destructive.

{#if backupError}
{backupError}
{/if}{#if backups.length}
{#each backups as backup}
{backup.filename}{Math.round(backup.size_bytes / 1024)} KiB{backup.modified_at ? new Date(backup.modified_at).toLocaleString() : 'UNKNOWN TIME'}
{/each}
{:else}
No backup archives are available.
{/if}
{:else}