windowgame / static /js /app.js
WACATW's picture
Upload 8 files
4543e35 verified
Raw History Blame Contribute Delete
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);