feat: add stream information editor

This commit is contained in:
2026-10-25 04:46:12 +01:00
parent f48618f94b
commit c81748da2c
2 changed files with 60 additions and 1 deletions
+51 -1
View File
@@ -16,6 +16,14 @@
let realtimeState = 'DISCONNECTED';
let realtimeSocket: WebSocket | undefined;
let activityEvents: Array<{ event_id: string; event_type: string; occurred_at: string }> = [];
let showStreamEditor = false;
let streamBusy = false;
let streamError = '';
let streamTitle = '';
let streamGameId = '';
let streamLanguage = 'en';
let streamTags = '';
let streamClassificationLabels = '';
let oauthLink = '';
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
let oauthBusy = false;
@@ -64,6 +72,7 @@
mustReplaceCredentials = result.must_replace_credentials;
connectionState = 'CONNECTED';
await loadActivity();
await loadStreamInfo();
connectRealtime();
if (!mustReplaceCredentials) password = '';
if (mustReplaceCredentials) newUsername = username;
@@ -156,6 +165,47 @@
}
}
async function loadStreamInfo() {
try {
const response = await fetch(`${serverAddress}/api/v1/twitch/stream-info`, {
headers: { authorization: `Bearer ${sessionToken}` },
});
if (!response.ok) return;
const info: { title: string; game_id: string; language: string; tags: string[]; content_classification_labels: string[] } = await response.json();
streamTitle = info.title;
streamGameId = info.game_id;
streamLanguage = info.language;
streamTags = info.tags.join(', ');
streamClassificationLabels = info.content_classification_labels.join(', ');
} catch {
// A missing Twitch streamer identity is shown when the editor is opened.
}
}
async function saveStreamInfo() {
streamBusy = true;
streamError = '';
try {
const response = await fetch(`${serverAddress}/api/v1/twitch/stream-info`, {
method: 'PATCH',
headers: { 'content-type': 'application/json', authorization: `Bearer ${sessionToken}` },
body: JSON.stringify({
title: streamTitle,
game_id: streamGameId,
language: streamLanguage,
tags: streamTags.split(',').map((tag) => tag.trim()).filter(Boolean),
content_classification_labels: streamClassificationLabels.split(',').map((label) => label.trim()).filter(Boolean),
}),
});
if (!response.ok) throw new Error('The server could not update stream information.');
showStreamEditor = false;
} catch (error) {
streamError = error instanceof Error ? error.message : 'Stream information update failed.';
} finally {
streamBusy = false;
}
}
async function createOAuthLink(identity: 'streamer' | 'bot') {
oauthBusy = true;
oauthError = '';
@@ -265,7 +315,7 @@
<section class="panel preview-panel"><div class="panel-header"><span>STREAM PREVIEW</span><span class="panel-menu">⋮</span></div><div class="preview-stage"><div class="offline-mark">STREAM OFFLINE</div><div class="offline-subtitle">CONNECT TWITCH TO BEGIN</div></div><div class="panel-footer"><span class="micro">VIDEO PLAYER</span><span class="badge">OFFLINE</span></div></section>
<section class="panel activity-panel"><div class="panel-header"><span>ACTIVITY FEED</span><span class="panel-menu">⋮</span></div><div class="panel-tabs"><button class="tab active">ALL ACTIVITY</button><button class="tab">MODERATION</button></div>{#if activityEvents.length}<div class="activity-list">{#each activityEvents as event}<div class="activity-row"><span class="activity-type">{event.event_type}</span><span class="activity-time">{new Date(event.occurred_at).toLocaleTimeString()}</span></div>{/each}</div>{:else}<div class="empty-state"><div class="empty-glyph">◌</div><strong>Nothing here yet</strong><span>Follows, subscriptions, raids, and server events will appear here.</span></div>{/if}</section>
<section class="panel chat-panel"><div class="panel-header"><span>MY CHAT</span><span class="panel-menu">⋮</span></div><div class="chat-empty"><div class="empty-glyph">⌁</div><strong>Chat is waiting</strong><span>Connect a streamer or bot account to begin.</span></div><div class="chat-composer"><input class="control" placeholder="Send a message" disabled /><button class="primary" disabled>SEND</button></div></section>
<section class="panel quick-panel"><div class="panel-header"><span>QUICK ACTIONS</span><span class="panel-menu">⋮</span></div><div class="quick-grid"><button class="quick-action accent">EDIT STREAM INFO</button><button class="quick-action">MODERATION</button><button class="quick-action">BOT COMMANDS</button><button class="quick-action">OVERLAYS</button></div></section>
<section class="panel quick-panel"><div class="panel-header"><span>QUICK ACTIONS</span><span class="panel-menu">⋮</span></div><div class="quick-grid"><button class="quick-action accent" onclick={() => (showStreamEditor = !showStreamEditor)}>EDIT STREAM INFO</button><button class="quick-action">MODERATION</button><button class="quick-action">BOT COMMANDS</button><button class="quick-action">OVERLAYS</button></div>{#if showStreamEditor}<div class="stream-editor"><label class="micro" for="stream-title">TITLE</label><input id="stream-title" class="control" bind:value={streamTitle} maxlength="140" /><label class="micro" for="stream-game-id">CATEGORY / GAME ID</label><input id="stream-game-id" class="control" bind:value={streamGameId} maxlength="64" /><label class="micro" for="stream-language">LANGUAGE</label><input id="stream-language" class="control" bind:value={streamLanguage} maxlength="10" /><label class="micro" for="stream-tags">TAGS</label><input id="stream-tags" class="control" bind:value={streamTags} placeholder="comma, separated" /><label class="micro" for="stream-labels">CLASSIFICATION LABELS</label><input id="stream-labels" class="control" bind:value={streamClassificationLabels} placeholder="comma, separated" /><button class="primary control-button" onclick={saveStreamInfo} disabled={streamBusy}>SAVE STREAM INFO</button>{#if streamError}<div class="auth-error">{streamError}</div>{/if}</div>{/if}</section>
</div>
{/if}
</section>
+9
View File
@@ -468,6 +468,15 @@ label {
color: var(--ed-dim);
font: 10px var(--font-data);
}
.stream-editor {
display: grid;
gap: 6px;
padding: 10px;
border-top: 1px solid var(--ui-divider);
}
.stream-editor label {
margin-top: 4px;
}
.quick-action:hover,
.quick-action.accent {
border-color: var(--ui-line-focus);