add collaborator search controls

This commit is contained in:
2026-10-25 05:28:00 +01:00
parent 8b8ba4b618
commit 46fb8e2cc3
2 changed files with 89 additions and 0 deletions
+67
View File
@@ -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 @@
</div>
{/if}
</section>
{#if authenticated && showCollaboration}
<section class="panel collaboration-search-panel">
<div class="panel-header"><span>COLLABORATOR SEARCH</span><span class="panel-menu">⋮</span></div>
<div class="stream-editor">
<label class="micro" for="collaborator-search">EXACT TWITCH LOGIN</label>
<div class="chat-send-row">
<input id="collaborator-search" 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 || !collaborationSession}>INVITE</button></div>
{/each}
</div>
{:else if collaboratorSearch && !collaboratorSearchBusy && !collaboratorSearchError}<div class="empty-row">No exact Twitch user found.</div>{/if}
<div class="micro">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><button class="primary" onclick={() => inviteCollaborator({ id: collaborator.twitch_user_id, display_name: collaborator.display_name })} disabled={collaborationBusy || !collaborationSession}>INVITE</button></div>
{/each}
</div>
{:else}<div class="empty-row">No saved collaborators yet.</div>{/if}
</div>
</section>
{/if}
</main>
</div>
+22
View File
@@ -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;
}