add collaborator search controls
This commit is contained in:
@@ -60,6 +60,11 @@
|
|||||||
let showCollaboration = false;
|
let showCollaboration = false;
|
||||||
let collaborationGuestId = '';
|
let collaborationGuestId = '';
|
||||||
let collaborators: Array<{ twitch_user_id: string; display_name: string; favorite: boolean; last_invited_at: string | null }> = [];
|
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 sharedChatSession: { session_id: string; host_broadcaster_id: string; participants: Array<{ broadcaster_id: string }> } | null = null;
|
||||||
let oauthLink = '';
|
let oauthLink = '';
|
||||||
let oauthIdentity: 'streamer' | 'bot' = 'streamer';
|
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]) {
|
async function toggleCollaboratorFavorite(collaborator: (typeof collaborators)[number]) {
|
||||||
const method = collaborator.favorite ? 'DELETE' : 'POST';
|
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`;
|
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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</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>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -879,3 +879,25 @@ label {
|
|||||||
min-height: 280px;
|
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