Spaces:
Paused
Paused
Download static/js/app.js from WACATW/windowgame: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/WACATW/windowgame/resolve/main/static/js/app.js
- Command line
-
hf download hf://spaces/WACATW/windowgame/static/js/app.js
-
curl -L -o app.js https://huggingface.co/spaces/WACATW/windowgame/resolve/main/static/js/app.js
11.8 kB
| // DOM Elements | |
| const screenshotImg = document.getElementById('screenshotImg'); | |
| const browserViewport = document.getElementById('browserViewport'); | |
| const loadingOverlay = document.getElementById('loadingOverlay'); | |
| const loadingText = document.getElementById('loadingText'); | |
| const urlInput = document.getElementById('urlInput'); | |
| const goBtn = document.getElementById('goBtn'); | |
| const backBtn = document.getElementById('backBtn'); | |
| const forwardBtn = document.getElementById('forwardBtn'); | |
| const reloadBtn = document.getElementById('reloadBtn'); | |
| const keyboardCapture = document.getElementById('keyboardCapture'); | |
| const statusDot = document.querySelector('.status-dot'); | |
| const statusText = document.getElementById('statusText'); | |
| const currentUrlEl = document.getElementById('currentUrl'); | |
| const pageTitleEl = document.getElementById('pageTitle'); | |
| const latencyEl = document.getElementById('latency'); | |
| // Toast | |
| const toast = document.getElementById('toast'); | |
| const toastIcon = document.getElementById('toastIcon'); | |
| const toastMessage = document.getElementById('toastMessage'); | |
| // State | |
| let isLoading = false; | |
| let lastRequestTime = 0; | |
| let isBrowserReady = false; | |
| let autoRefreshInterval = null; | |
| // Toast function | |
| function showToast(message, type = 'success') { | |
| toastMessage.textContent = message; | |
| toastIcon.className = 'toast-icon fas '; | |
| if (type === 'success') { | |
| toastIcon.classList.add('fa-check-circle', 'success'); | |
| } else if (type === 'error') { | |
| toastIcon.classList.add('fa-exclamation-circle', 'error'); | |
| } else { | |
| toastIcon.classList.add('fa-info-circle', 'info'); | |
| } | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Update status | |
| function updateStatus(status, text) { | |
| statusDot.className = 'status-dot'; | |
| if (status === 'connected') { | |
| statusDot.classList.add('connected'); | |
| } else if (status === 'error') { | |
| statusDot.classList.add('error'); | |
| } | |
| statusText.textContent = text; | |
| } | |
| // Show loading | |
| function showLoading(text = '加载中...') { | |
| loadingText.textContent = text; | |
| loadingOverlay.classList.remove('hidden'); | |
| isLoading = true; | |
| } | |
| // Hide loading | |
| function hideLoading() { | |
| loadingOverlay.classList.add('hidden'); | |
| isLoading = false; | |
| } | |
| // Update screenshot | |
| function updateScreenshot(data) { | |
| if (data.screenshot) { | |
| screenshotImg.src = 'data:image/jpeg;base64,' + data.screenshot; | |
| } | |
| if (data.url) { | |
| currentUrlEl.textContent = data.url; | |
| urlInput.value = data.url; | |
| } | |
| if (data.title) { | |
| pageTitleEl.textContent = data.title; | |
| } | |
| // Calculate latency | |
| if (lastRequestTime > 0) { | |
| const latency = Date.now() - lastRequestTime; | |
| latencyEl.textContent = latency + 'ms'; | |
| } | |
| } | |
| // Navigate to URL | |
| async function navigateTo(url) { | |
| if (isLoading) return; | |
| showLoading('正在加载页面...'); | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/navigate', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ url: url }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| hideLoading(); | |
| showToast('页面加载成功', 'success'); | |
| } else { | |
| hideLoading(); | |
| showToast(data.error || '加载失败', 'error'); | |
| } | |
| } catch (error) { | |
| hideLoading(); | |
| showToast('网络错误: ' + error.message, 'error'); | |
| console.error('Navigate error:', error); | |
| } | |
| } | |
| // Get screenshot | |
| async function refreshScreenshot() { | |
| if (isLoading) return; | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/screenshot'); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| return true; | |
| } | |
| return false; | |
| } catch (error) { | |
| console.error('Screenshot error:', error); | |
| return false; | |
| } | |
| } | |
| // Handle click | |
| async function handleClick(x, y) { | |
| if (isLoading || !isBrowserReady) return; | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/click', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ x: x, y: y }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| } | |
| } catch (error) { | |
| console.error('Click error:', error); | |
| } | |
| } | |
| // Handle type | |
| async function handleType(text) { | |
| if (isLoading || !isBrowserReady) return; | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/type', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ text: text }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| } | |
| } catch (error) { | |
| console.error('Type error:', error); | |
| } | |
| } | |
| // Handle keypress | |
| async function handleKeypress(key) { | |
| if (isLoading || !isBrowserReady) return; | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/keypress', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ key: key }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| } | |
| } catch (error) { | |
| console.error('Keypress error:', error); | |
| } | |
| } | |
| // Handle scroll | |
| async function handleScroll(deltaX, deltaY) { | |
| if (isLoading || !isBrowserReady) return; | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/scroll', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ deltaX: deltaX, deltaY: deltaY }) | |
| }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| } | |
| } catch (error) { | |
| console.error('Scroll error:', error); | |
| } | |
| } | |
| // Navigation functions | |
| async function goBack() { | |
| if (isLoading) return; | |
| showLoading('后退中...'); | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/go-back', { method: 'POST' }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| hideLoading(); | |
| } else { | |
| hideLoading(); | |
| showToast(data.error || '操作失败', 'error'); | |
| } | |
| } catch (error) { | |
| hideLoading(); | |
| showToast('网络错误', 'error'); | |
| } | |
| } | |
| async function goForward() { | |
| if (isLoading) return; | |
| showLoading('前进中...'); | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/go-forward', { method: 'POST' }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| hideLoading(); | |
| } else { | |
| hideLoading(); | |
| showToast(data.error || '操作失败', 'error'); | |
| } | |
| } catch (error) { | |
| hideLoading(); | |
| showToast('网络错误', 'error'); | |
| } | |
| } | |
| async function reload() { | |
| if (isLoading) return; | |
| showLoading('刷新中...'); | |
| lastRequestTime = Date.now(); | |
| try { | |
| const response = await fetch('/api/reload', { method: 'POST' }); | |
| const data = await response.json(); | |
| if (data.success) { | |
| updateScreenshot(data); | |
| hideLoading(); | |
| } else { | |
| hideLoading(); | |
| showToast(data.error || '刷新失败', 'error'); | |
| } | |
| } catch (error) { | |
| hideLoading(); | |
| showToast('网络错误', 'error'); | |
| } | |
| } | |
| // Calculate click coordinates | |
| function getClickCoordinates(event) { | |
| const rect = screenshotImg.getBoundingClientRect(); | |
| const x = event.clientX - rect.left; | |
| const y = event.clientY - rect.top; | |
| // Calculate scale | |
| const scaleX = viewportWidth / rect.width; | |
| const scaleY = viewportHeight / rect.height; | |
| // Use the smaller scale to maintain aspect ratio | |
| const scale = Math.min(scaleX, scaleY); | |
| // Calculate actual coordinates in the viewport | |
| const actualX = Math.round(x * scale); | |
| const actualY = Math.round(y * scale); | |
| return { x: actualX, y: actualY }; | |
| } | |
| // Event Listeners | |
| // URL input | |
| goBtn.addEventListener('click', () => { | |
| const url = urlInput.value.trim(); | |
| if (url) { | |
| navigateTo(url); | |
| } | |
| }); | |
| urlInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| const url = urlInput.value.trim(); | |
| if (url) { | |
| navigateTo(url); | |
| } | |
| } | |
| }); | |
| // Navigation buttons | |
| backBtn.addEventListener('click', goBack); | |
| forwardBtn.addEventListener('click', goForward); | |
| reloadBtn.addEventListener('click', reload); | |
| // Viewport click | |
| browserViewport.addEventListener('click', (e) => { | |
| if (!isBrowserReady) return; | |
| // Focus the hidden input to capture keyboard | |
| keyboardCapture.focus(); | |
| const coords = getClickCoordinates(e); | |
| handleClick(coords.x, coords.y); | |
| }); | |
| // Keyboard input | |
| keyboardCapture.addEventListener('input', (e) => { | |
| const text = e.target.value; | |
| if (text) { | |
| handleType(text); | |
| e.target.value = ''; | |
| } | |
| }); | |
| keyboardCapture.addEventListener('keydown', (e) => { | |
| // Handle special keys | |
| const specialKeys = ['Enter', 'Backspace', 'Tab', 'Escape', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']; | |
| if (specialKeys.includes(e.key)) { | |
| e.preventDefault(); | |
| handleKeypress(e.key); | |
| } | |
| }); | |
| // Scroll | |
| browserViewport.addEventListener('wheel', (e) => { | |
| e.preventDefault(); | |
| handleScroll(e.deltaX, e.deltaY); | |
| }, { passive: false }); | |
| // Initialize | |
| async function init() { | |
| updateStatus('connecting', '连接中...'); | |
| showLoading('正在启动云浏览器...'); | |
| // Check browser status | |
| try { | |
| const response = await fetch('/api/status'); | |
| const data = await response.json(); | |
| if (data.ready) { | |
| isBrowserReady = true; | |
| updateStatus('connected', '已连接'); | |
| currentUrlEl.textContent = data.url || '-'; | |
| pageTitleEl.textContent = data.title || '-'; | |
| // Get initial screenshot | |
| await refreshScreenshot(); | |
| hideLoading(); | |
| } else { | |
| // Try to initialize by getting screenshot | |
| showLoading('正在初始化浏览器...'); | |
| const success = await refreshScreenshot(); | |
| if (success) { | |
| isBrowserReady = true; | |
| updateStatus('connected', '已连接'); | |
| hideLoading(); | |
| } else { | |
| updateStatus('error', '连接失败'); | |
| loadingText.textContent = '浏览器启动失败,请刷新页面重试'; | |
| } | |
| } | |
| } catch (error) { | |
| updateStatus('error', '连接失败'); | |
| loadingText.textContent = '无法连接到服务器'; | |
| console.error('Init error:', error); | |
| } | |
| } | |
| // Start | |
| document.addEventListener('DOMContentLoaded', init); | |