From 46fb8e2cc3eba17c94b4eedf30f4ecf792d976a3 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Sun, 25 Oct 2026 05:28:00 +0100 Subject: [PATCH] add collaborator search controls --- src/App.svelte | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/styles.css | 22 +++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/src/App.svelte b/src/App.svelte index 3e93aa0..071eabc 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -60,6 +60,11 @@ let showCollaboration = false; let collaborationGuestId = ''; let collaborators: Array<{ twitch_user_id: string; display_name: string; favorite: boolean; last_invited_at: string | null }> = []; + let collaboratorSearch = ''; + let collaboratorResults: Array<{ id: string; login: string; display_name: string }> = []; + let collaboratorSearchBusy = false; + let collaboratorSearchError = ''; + let collaboratorSearchTimer: number | undefined; let sharedChatSession: { session_id: string; host_broadcaster_id: string; participants: Array<{ broadcaster_id: string }> } | null = null; let oauthLink = ''; let oauthIdentity: 'streamer' | 'bot' = 'streamer'; @@ -322,6 +327,39 @@ } } + async function searchCollaborators(value: string) { + collaboratorSearch = value; + collaboratorSearchError = ''; + collaboratorResults = []; + if (collaboratorSearchTimer) window.clearTimeout(collaboratorSearchTimer); + if (!value.trim()) return; + collaboratorSearchTimer = window.setTimeout(async () => { + collaboratorSearchBusy = true; + try { + const response = await fetch(`${serverAddress}/api/v1/twitch/collaboration/search?query=${encodeURIComponent(value.trim())}`, { headers: { authorization: `Bearer ${sessionToken}` } }); + if (!response.ok) throw new Error('Twitch user search failed.'); + collaboratorResults = await response.json(); + } catch (error) { + collaboratorSearchError = error instanceof Error ? error.message : 'Twitch user search failed.'; + } finally { + collaboratorSearchBusy = false; + } + }, 200); + } + + function clearCollaboratorSearch() { + if (collaboratorSearchTimer) window.clearTimeout(collaboratorSearchTimer); + collaboratorSearch = ''; + collaboratorResults = []; + collaboratorSearchError = ''; + } + + async function inviteCollaborator(user: { id: string; display_name: string }) { + collaborationGuestId = user.id; + await sendCollaborationInvite(); + clearCollaboratorSearch(); + } + async function toggleCollaboratorFavorite(collaborator: (typeof collaborators)[number]) { const method = collaborator.favorite ? 'DELETE' : 'POST'; const url = collaborator.favorite ? `${serverAddress}/api/v1/twitch/collaboration/favorites?twitch_user_id=${encodeURIComponent(collaborator.twitch_user_id)}` : `${serverAddress}/api/v1/twitch/collaboration/favorites`; @@ -844,5 +882,34 @@ {/if} + {#if authenticated && showCollaboration} +
+
COLLABORATOR SEARCH⋮
+
+ +
+ searchCollaborators(event.currentTarget.value)} autocomplete="off" placeholder="Search Twitch" /> + +
+ {#if collaboratorSearchBusy}
SEARCHING TWITCH…
{/if} + {#if collaboratorSearchError}
{collaboratorSearchError}
{/if} + {#if collaboratorResults.length} +
+ {#each collaboratorResults as result} +
{result.display_name} @{result.login}
+ {/each} +
+ {:else if collaboratorSearch && !collaboratorSearchBusy && !collaboratorSearchError}
No exact Twitch user found.
{/if} +
FAVORITES & RECENT
+ {#if collaborators.length} +
+ {#each collaborators as collaborator} +
{collaborator.display_name}
+ {/each} +
+ {:else}
No saved collaborators yet.
{/if} +
+
+ {/if} diff --git a/src/styles.css b/src/styles.css index fd578fc..e03a119 100644 --- a/src/styles.css +++ b/src/styles.css @@ -879,3 +879,25 @@ label { min-height: 280px; } } + +.collaboration-search-panel { + margin-top: 10px; +} +.collaborator-results { + display: grid; + gap: 4px; + margin: 8px 0 14px; +} +.collaborator-result { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 6px 8px; + border: 1px solid var(--ui-divider); + background: var(--ui-surface); +} +.collaborator-result .secondary { + min-width: 28px; + padding: 2px 6px; +}