216 lines
6.3 KiB
JavaScript
216 lines
6.3 KiB
JavaScript
'use strict';
|
|
|
|
// ── WebSocket Setup ──────────────────────────────────────────────────────────
|
|
|
|
let ws;
|
|
let connected = false;
|
|
|
|
function connectWS() {
|
|
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
|
ws = new WebSocket(`${proto}://${location.host}/ws`);
|
|
|
|
ws.onopen = () => {
|
|
connected = true;
|
|
updateStatus();
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
connected = false;
|
|
updateStatus();
|
|
setTimeout(connectWS, 2000);
|
|
};
|
|
|
|
ws.onerror = () => {};
|
|
|
|
ws.onmessage = (e) => {
|
|
let msg;
|
|
try { msg = JSON.parse(e.data); } catch { return; }
|
|
handleServerMessage(msg);
|
|
};
|
|
}
|
|
|
|
function handleServerMessage(msg) {
|
|
if (msg.type === 'status') {
|
|
updateStatus();
|
|
}
|
|
}
|
|
|
|
function updateStatus() {
|
|
const dot = document.getElementById('status-dot');
|
|
const lbl = document.getElementById('status-label');
|
|
if (connected) {
|
|
dot.className = 'ok';
|
|
lbl.textContent = 'Ready';
|
|
} else {
|
|
dot.className = '';
|
|
lbl.textContent = 'Connecting…';
|
|
}
|
|
}
|
|
|
|
// ── Connection Management ────────────────────────────────────────────────────
|
|
|
|
let selectedRobot = null;
|
|
let selectedMode = null;
|
|
let connections = [];
|
|
|
|
const STORAGE_KEY = 're-commander-connections';
|
|
|
|
// Load connections from localStorage
|
|
function loadConnections() {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
connections = stored ? JSON.parse(stored) : [];
|
|
renderConnections();
|
|
}
|
|
|
|
// Save connections to localStorage
|
|
function saveConnections() {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(connections));
|
|
}
|
|
|
|
// Render connections list
|
|
function renderConnections() {
|
|
const list = document.getElementById('connections-list');
|
|
|
|
if (connections.length === 0) {
|
|
list.innerHTML = '<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px 0;">No saved connections</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = connections.map((conn, idx) => `
|
|
<div class="connection-item" data-index="${idx}">
|
|
<div class="connection-info">
|
|
<div class="connection-name">${conn.name}</div>
|
|
<div class="connection-ip">${conn.ip}</div>
|
|
</div>
|
|
<button class="connection-delete" data-index="${idx}">Delete</button>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Add click handlers to select connection
|
|
document.querySelectorAll('.connection-item').forEach(item => {
|
|
const idx = parseInt(item.dataset.index);
|
|
item.addEventListener('click', (e) => {
|
|
if (!e.target.classList.contains('connection-delete')) {
|
|
selectConnection(idx);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add click handlers to delete button
|
|
document.querySelectorAll('.connection-delete').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const idx = parseInt(btn.dataset.index);
|
|
deleteConnection(idx);
|
|
});
|
|
});
|
|
|
|
// Highlight selected connection
|
|
if (selectedRobot !== null && selectedRobot < connections.length) {
|
|
document.querySelector(`.connection-item[data-index="${selectedRobot}"]`)?.classList.add('selected');
|
|
}
|
|
}
|
|
|
|
function selectConnection(idx) {
|
|
selectedRobot = idx;
|
|
const conn = connections[idx];
|
|
sessionStorage.setItem('selectedRobot', conn.ip);
|
|
sessionStorage.setItem('selectedRobotName', conn.name);
|
|
renderConnections();
|
|
updateLaunchButton();
|
|
}
|
|
|
|
function deleteConnection(idx) {
|
|
connections.splice(idx, 1);
|
|
saveConnections();
|
|
if (selectedRobot === idx) {
|
|
selectedRobot = null;
|
|
sessionStorage.removeItem('selectedRobot');
|
|
sessionStorage.removeItem('selectedRobotName');
|
|
}
|
|
renderConnections();
|
|
updateLaunchButton();
|
|
}
|
|
|
|
// Add new connection
|
|
document.getElementById('btn-add-connection').addEventListener('click', () => {
|
|
const ip = document.getElementById('robot-ip').value.trim();
|
|
|
|
if (!ip) {
|
|
alert('Please enter a robot IP or hostname');
|
|
return;
|
|
}
|
|
|
|
// Check if already exists
|
|
if (connections.find(c => c.ip === ip)) {
|
|
alert('This connection already exists');
|
|
return;
|
|
}
|
|
|
|
// Generate a name
|
|
const name = `Robot (${ip})`;
|
|
connections.push({ name, ip });
|
|
saveConnections();
|
|
renderConnections();
|
|
|
|
// Clear input and select the new connection
|
|
document.getElementById('robot-ip').value = '';
|
|
selectConnection(connections.length - 1);
|
|
});
|
|
|
|
// Handle Enter key in IP input
|
|
document.getElementById('robot-ip').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') {
|
|
document.getElementById('btn-add-connection').click();
|
|
}
|
|
});
|
|
|
|
// ── Mode Selection ───────────────────────────────────────────────────────────
|
|
|
|
document.querySelectorAll('.mode-btn').forEach(btn => {
|
|
btn.addEventListener('click', function () {
|
|
const mode = this.dataset.mode;
|
|
selectedMode = mode;
|
|
|
|
// Remove active class from all mode buttons
|
|
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
updateLaunchButton();
|
|
});
|
|
});
|
|
|
|
function updateLaunchButton() {
|
|
const btn = document.getElementById('btn-launch');
|
|
if (selectedRobot !== null && selectedMode) {
|
|
btn.disabled = false;
|
|
} else {
|
|
btn.disabled = true;
|
|
}
|
|
}
|
|
|
|
// ── Launch ───────────────────────────────────────────────────────────────────
|
|
|
|
document.getElementById('btn-launch').addEventListener('click', () => {
|
|
if (selectedRobot === null || !selectedMode) {
|
|
alert('Please select both a robot and a mode');
|
|
return;
|
|
}
|
|
|
|
const conn = connections[selectedRobot];
|
|
|
|
// Store selection in sessionStorage
|
|
sessionStorage.setItem('selectedRobot', conn.ip);
|
|
sessionStorage.setItem('selectedRobotName', conn.name);
|
|
sessionStorage.setItem('selectedMode', selectedMode);
|
|
|
|
// Navigate to the appropriate page
|
|
const modeFile = selectedMode + '.html';
|
|
window.location.href = modeFile;
|
|
});
|
|
|
|
// ── Initialization ───────────────────────────────────────────────────────────
|
|
|
|
connectWS();
|
|
loadConnections();
|