// Whiteboard tab: source URL (owner-gated, see api_frames.py's // api_whiteboard_source), preview, and take control. window.FRAME_API is // set by the template. const whiteboardForm = document.getElementById('whiteboard-source-form'); if (whiteboardForm) { whiteboardForm.addEventListener('submit', async (e) => { e.preventDefault(); const url = document.getElementById('whiteboard-url-input').value.trim(); if (!url) return; try { const resp = await fetch(`${window.FRAME_API}/whiteboard-source`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }), }); if (!resp.ok) throw new Error(await apiError(resp)); showStatus(true, 'Saved. Reload to see the updated source.'); loadWhiteboardPreview(); } catch (e) { showStatus(false, e.message); } }); } const whiteboardClearBtn = document.getElementById('whiteboard-source-clear'); if (whiteboardClearBtn) { whiteboardClearBtn.addEventListener('click', async () => { try { const resp = await fetch(`${window.FRAME_API}/whiteboard-source`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: null }), }); if (!resp.ok) throw new Error(await apiError(resp)); showStatus(true, 'Cleared. Reload to see the change.'); loadWhiteboardPreview(); } catch (e) { showStatus(false, e.message); } }); } function loadWhiteboardPreview(force) { const forceParam = force ? '&force=1' : ''; document.getElementById('whiteboard-preview').src = `${window.FRAME_API}/preview/whiteboard?_=${Date.now()}${forceParam}`; } // Loading the tab shows whatever's already cached (cheap, no refetch); // the button is the one place that means "no really, go check now" -- // bypasses the fetch throttle server-side (see api_frames.py's `force`). document.getElementById('whiteboard-preview-refresh').addEventListener('click', () => loadWhiteboardPreview(true)); loadWhiteboardPreview(false); // --- file picker (Browse...) --- const browseToggle = document.getElementById('whiteboard-browse-toggle'); if (browseToggle) { const browsePanel = document.getElementById('whiteboard-browser'); const browseList = document.getElementById('whiteboard-browse-list'); const browseCurrent = document.getElementById('whiteboard-browse-current'); const browseUp = document.getElementById('whiteboard-browse-up'); const browseError = document.getElementById('whiteboard-browse-error'); const urlInput = document.getElementById('whiteboard-url-input'); let opened = false; async function browseTo(url) { browseError.style.display = 'none'; browseList.innerHTML = '
  • Loading...
  • '; try { const qs = url ? `?url=${encodeURIComponent(url)}` : ''; const resp = await fetch(`${window.FRAME_API}/whiteboard-browse${qs}`); if (!resp.ok) throw new Error(await apiError(resp)); const data = await resp.json(); browseCurrent.textContent = data.current_url; browseUp.disabled = !data.parent_url; browseUp.onclick = data.parent_url ? () => browseTo(data.parent_url) : null; browseList.innerHTML = ''; if (data.entries.length === 0) { browseList.innerHTML = '
  • (empty folder)
  • '; } for (const entry of data.entries) { const li = document.createElement('li'); const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'btn-inline secondary'; btn.style.margin = '2px 0'; btn.textContent = (entry.is_dir ? '📁 ' : '📄 ') + entry.name; if (entry.is_dir) { btn.addEventListener('click', () => browseTo(entry.url)); } else { btn.addEventListener('click', () => { urlInput.value = entry.url; browsePanel.style.display = 'none'; }); } li.appendChild(btn); browseList.appendChild(li); } } catch (e) { browseList.innerHTML = ''; browseError.textContent = e.message; browseError.style.display = 'block'; } } browseToggle.addEventListener('click', () => { opened = !opened; browsePanel.style.display = opened ? 'block' : 'none'; if (opened && !browseCurrent.textContent) { browseTo(null); } }); } async function takeControl() { try { const resp = await fetch(`${window.FRAME_API}/take-control`, { method: 'POST' }); if (!resp.ok) throw new Error(await apiError(resp)); showStatus(true, 'You have control now.'); loadControl(); } catch (e) { showStatus(false, e.message); } } async function loadControl() { const banner = document.getElementById('control-banner'); try { const resp = await fetch(`${window.FRAME_API}/queue`); if (!resp.ok) return; // unconfigured frame: control still works via 409s const data = await resp.json(); if (data.control && !data.control.you) { banner.style.display = 'flex'; document.getElementById('control-holder').textContent = data.control.controller ? `${data.control.controller} currently has control of this frame.` : 'Nobody has control of this frame yet.'; } else { banner.style.display = 'none'; } } catch (e) { /* banner is best-effort */ } } document.getElementById('take-control').addEventListener('click', takeControl); loadControl();