let selectStreamerRequestId = 0; let vodRenderTaskId = 0; const VOD_RENDER_CHUNK_SIZE = 64; function buildVodCardHtml(vod: VOD, streamer: string): string { const thumb = vod.thumbnail_url.replace('%{width}', '320').replace('%{height}', '180'); const date = formatUiDate(vod.created_at); const escapedTitle = vod.title.replace(/'/g, "\\'").replace(/\"/g, '"'); const safeDisplayTitle = escapeHtml(vod.title || UI_TEXT.vods.untitled); return `
${safeDisplayTitle}
${date} ${vod.duration} ${formatUiNumber(vod.view_count)} ${UI_TEXT.vods.views}
`; } function renderStreamers(): void { const list = byId('streamerList'); list.innerHTML = ''; (config.streamers ?? []).forEach((streamer: string) => { const item = document.createElement('div'); item.className = 'streamer-item' + (currentStreamer === streamer ? ' active' : ''); item.innerHTML = ` ${streamer} x `; item.onclick = () => { void selectStreamer(streamer); }; list.appendChild(item); }); } async function addStreamer(): Promise { const input = byId('newStreamer'); const name = input.value.trim().toLowerCase(); if (!name || (config.streamers ?? []).includes(name)) { return; } config.streamers = [...(config.streamers ?? []), name]; config = await window.api.saveConfig({ streamers: config.streamers }); input.value = ''; renderStreamers(); await selectStreamer(name); } async function removeStreamer(name: string): Promise { config.streamers = (config.streamers ?? []).filter((s: string) => s !== name); config = await window.api.saveConfig({ streamers: config.streamers }); renderStreamers(); if (currentStreamer !== name) { return; } currentStreamer = null; byId('vodGrid').innerHTML = `

${UI_TEXT.vods.noneTitle}

${UI_TEXT.vods.noneText}

`; } async function selectStreamer(name: string, forceRefresh = false): Promise { const requestId = ++selectStreamerRequestId; const isStaleRequest = () => requestId !== selectStreamerRequestId || currentStreamer !== name; currentStreamer = name; renderStreamers(); byId('pageTitle').textContent = name; if (!isConnected) { await connect(); if (isStaleRequest()) { return; } } if (!isConnected) { updateStatus(UI_TEXT.status.noLogin, false); } byId('vodGrid').innerHTML = `

${UI_TEXT.vods.loading}

`; const userId = await window.api.getUserId(name); if (isStaleRequest()) { return; } if (!userId) { byId('vodGrid').innerHTML = `

${UI_TEXT.vods.notFound}

`; return; } const vods = await window.api.getVODs(userId, forceRefresh); if (isStaleRequest()) { return; } renderVODs(vods, name); } function renderVODs(vods: VOD[] | null | undefined, streamer: string): void { const grid = byId('vodGrid'); const renderTaskId = ++vodRenderTaskId; const scheduleNextChunk = (nextStartIndex: number): void => { const delayMs = document.hidden ? 16 : 0; window.setTimeout(() => { renderChunk(nextStartIndex); }, delayMs); }; if (!vods || vods.length === 0) { grid.innerHTML = `

${UI_TEXT.vods.noResultsTitle}

${UI_TEXT.vods.noResultsText}

`; return; } grid.innerHTML = ''; const renderChunk = (startIndex: number): void => { if (renderTaskId !== vodRenderTaskId) { return; } const chunk = vods.slice(startIndex, startIndex + VOD_RENDER_CHUNK_SIZE); if (!chunk.length) { return; } grid.insertAdjacentHTML('beforeend', chunk.map((vod) => buildVodCardHtml(vod, streamer)).join('')); if (startIndex + chunk.length < vods.length) { scheduleNextChunk(startIndex + chunk.length); } }; renderChunk(0); } async function refreshVODs(): Promise { if (!currentStreamer) { return; } await selectStreamer(currentStreamer, true); }