Show collaboration action notices
Client checks / checks (push) Canceled after 0s
Client dependency and security checks / audit (push) Canceled after 0s

This commit is contained in:
2026-10-25 06:16:07 +01:00
parent b4bc860533
commit d1636a3a2a
2 changed files with 12 additions and 2 deletions
+7 -2
View File
@@ -138,6 +138,7 @@
let collaborationInvites: Array<{ user_id: string; invited_at: string; status: string }> = [];
let collaborationBusy = false;
let collaborationError = '';
let collaborationNotice = '';
let showCollaboration = false;
let collaborationGuestId = '';
let collaborators: Array<{ twitch_user_id: string; display_name: string; favorite: boolean; last_invited_at: string | null }> = [];
@@ -562,6 +563,7 @@
const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/session`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } });
if (!response.ok) throw new Error('Twitch could not start a Guest Star session.');
collaborationSession = await response.json();
collaborationNotice = 'Guest Star session started.';
await loadCollaboration();
} catch (error) {
collaborationError = error instanceof Error ? error.message : 'Collaboration session failed.';
@@ -580,6 +582,7 @@
if (!response.ok) throw new Error('Twitch could not end the Guest Star session.');
collaborationSession = null;
collaborationInvites = [];
collaborationNotice = 'Guest Star session ended.';
} catch (error) {
collaborationError = error instanceof Error ? error.message : 'Collaboration session could not end.';
} finally {
@@ -596,6 +599,7 @@
const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/invites/send?${query}`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } });
if (!response.ok) throw new Error('Twitch could not send the collaboration invite.');
collaborationGuestId = '';
collaborationNotice = 'Collaboration invite sent.';
await loadCollaboration();
} catch (error) {
collaborationError = error instanceof Error ? error.message : 'Invite could not be sent.';
@@ -613,6 +617,7 @@
const query = `session_id=${encodeURIComponent(collaborationSession.id)}&guest_id=${encodeURIComponent(guestId)}`;
const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/invites/cancel?${query}`, { method: 'POST', headers: { authorization: `Bearer ${sessionToken}` } });
if (!response.ok) throw new Error('Twitch could not cancel the collaboration invite.');
collaborationNotice = 'Collaboration invite cancelled.';
await loadCollaboration();
} catch (error) {
collaborationError = error instanceof Error ? error.message : 'Invite could not be cancelled.';
@@ -1490,7 +1495,7 @@
<div class="collaboration-call-placeholder"><div class="offline-mark">GUEST STAR CALL</div><div class="offline-subtitle">The call is controlled by Twitch</div><p class="settings-note">Twitch does not expose the call media as an embeddable Trixie API. Use Twitch for the live call controls while Trixie keeps the session, slots, invites, and shared-chat state synchronized.</p><button class="secondary" onclick={() => window.open('https://www.twitch.tv', '_blank', 'noopener,noreferrer')}>OPEN ON TWITCH</button></div>
<div class="collaboration-state-panel"><div class="micro">PARTICIPANTS</div>{#each collaborationSession.guests as guest}<div class="collaboration-invite"><span>{guest.user_display_name || guest.user_id}</span><span class="micro">{guest.is_live ? 'LIVE' : 'WAITING'}{guest.slot_id ? ` · SLOT ${guest.slot_id}` : ''}</span></div>{:else}<div class="empty-row">No participants have joined.</div>{/each}<div class="micro collaboration-subheading">SHARED CHAT</div>{#if sharedChatSession}<div class="collaboration-status">ACTIVE · {sharedChatSession.participants.length} CHANNELS</div>{:else}<div class="empty-row">Shared Chat is not active.</div>{/if}{#if collaborationSettings}<div class="micro collaboration-subheading">CHANNEL SETTINGS</div><div class="collaboration-status">{collaborationSettings.slot_count} SLOTS · {collaborationSettings.group_layout}</div><div class="micro">AUDIO {collaborationSettings.is_browser_source_audio_enabled ? 'ENABLED' : 'DISABLED'} · MODERATOR LIVE CONTROL {collaborationSettings.is_moderator_send_live_enabled ? 'ENABLED' : 'DISABLED'}</div>{/if}<button class="danger control-button" onclick={endCollaboration} disabled={collaborationBusy}>END SESSION</button></div>
</div>
{#if collaborationError}<div class="auth-error">{collaborationError}</div>{/if}
{#if collaborationNotice}<div class="collaboration-notice">{collaborationNotice}</div>{/if}{#if collaborationError}<div class="auth-error">{collaborationError}</div>{/if}
</section>
{:else}
<section class="collaboration-page panel">
@@ -1499,7 +1504,7 @@
<div class="collaboration-search-panel"><div class="micro">FIND A COLLABORATOR</div><label class="micro" for="collaboration-search-tab">EXACT TWITCH LOGIN</label><div class="chat-send-row"><input id="collaboration-search-tab" class="control" value={collaboratorSearch} oninput={(event) => searchCollaborators(event.currentTarget.value)} autocomplete="off" placeholder="Search Twitch" /><button class="secondary" onclick={clearCollaboratorSearch} disabled={!collaboratorSearch}>CLEAR</button></div>{#if collaboratorSearchBusy}<div class="micro">SEARCHING TWITCH…</div>{/if}{#if collaboratorSearchError}<div class="auth-error">{collaboratorSearchError}<button class="secondary" onclick={() => searchCollaborators(collaboratorSearch)}>RETRY</button></div>{/if}{#if collaboratorResults.length}<div class="collaborator-results">{#each collaboratorResults as result}<div class="collaborator-result"><span><strong>{result.display_name}</strong><span class="micro"> @{result.login}</span></span><button class="primary" onclick={() => inviteCollaborator(result)} disabled={collaborationBusy}>INVITE</button></div>{/each}</div>{:else if collaboratorSearch && !collaboratorSearchBusy && !collaboratorSearchError}<div class="empty-row">No exact Twitch user found.</div>{/if}<div class="micro collaboration-subheading">FAVORITES &amp; RECENT</div>{#if collaborators.length}<div class="collaborator-results">{#each collaborators as collaborator}<div class="collaborator-result"><span><button class="secondary" onclick={() => toggleCollaboratorFavorite(collaborator)}>{collaborator.favorite ? '★' : '☆'}</button><strong>{collaborator.display_name}</strong></span><span class="micro">{collaborator.favorite ? 'FAVORITE' : 'RECENT'}</span></div>{/each}</div>{:else}<div class="empty-row">No saved collaborators yet.</div>{/if}</div>
<div class="collaboration-incoming-panel"><div class="micro">INCOMING INVITATIONS</div><div class="empty-state"><div class="empty-glyph">⌁</div><strong>Waiting for a collaboration invite</strong><span>Accept or decline invitations in Twitch. Trixie will reflect the authoritative status here when Twitch reports it.</span></div></div>
</div>
{#if collaborationError}<div class="auth-error">{collaborationError}</div>{/if}
{#if collaborationNotice}<div class="collaboration-notice">{collaborationNotice}</div>{/if}{#if collaborationError}<div class="auth-error">{collaborationError}</div>{/if}
</section>
{/if}
{:else if activeView === 'Overlays'}
+5
View File
@@ -416,6 +416,11 @@ label {
color: var(--ed-warn);
font: 10px/1.4 var(--font-data);
}
.collaboration-notice {
margin-top: 8px;
color: var(--ed-good);
font: 10px/1.4 var(--font-data);
}
.auth-gate {
min-height: 300px;
}