add collaborator search controls
This commit is contained in:
@@ -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 & 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user