Minor bug fixes.

Persist settings.
This commit is contained in:
raldone01
2025-07-05 19:42:44 +02:00
parent ff18dbd238
commit 98b975202c
2 changed files with 94 additions and 54 deletions

View File

@@ -25,47 +25,10 @@
let lastCommandCount = 0; let lastCommandCount = 0;
let lastCommandButton = null; let lastCommandButton = null;
let sendMode = 'command'; // default mode let sendMode = localStorage.getItem('sendMode') || 'command';
let reconnectIntervalId = null; // track reconnect interval // track reconnect interval
let reconnectIntervalId = null;
// Format incoming data to string based on newline mode
const decodeData = (() => {
const decoder = new TextDecoder();
return dataView => decoder.decode(dataView);
})();
// Normalize newline if mode is ANY
const normalizeNewlines = (text, mode) => {
switch (mode) {
case 'CR':
// Only \r: Replace all \n with \r
return text.replace(/\r?\n/g, '\r');
case 'CRLF':
// Replace lone \r or \n with \r\n
return text.replace(/\r\n|[\r\n]/g, '\r\n');
case 'ANY':
// Accept any \r, \n, \r\n. Normalize as \n for display
return text.replace(/\r\n|\r/g, '\n');
default:
return text;
}
};
// Append line to container, optionally scroll to bottom
const appendLineToReceiver = (container, text, className = '') => {
const div = document.createElement('div');
if (className) div.className = className;
div.textContent = text;
container.appendChild(div);
const distanceFromBottom = container.scrollHeight - (container.scrollTop + container.clientHeight);
if (distanceFromBottom < nearTheBottomThreshold) {
requestAnimationFrame(() => {
div.scrollIntoView({ behavior: "instant" });
});
}
};
// Append sent command to sender container as a clickable element // Append sent command to sender container as a clickable element
const appendCommandToSender = (container, text) => { const appendCommandToSender = (container, text) => {
@@ -98,6 +61,55 @@
} }
}; };
// Restore command history
history.push(...(JSON.parse(localStorage.getItem('commandHistory') || '[]')));
for (const cmd of history) {
appendCommandToSender(senderLines, cmd);
}
// Restore auto reconnect checkbox
autoReconnectCheckbox.checked = localStorage.getItem('autoReconnect') === 'true';
// Restore newline mode
const savedNewlineMode = localStorage.getItem('newlineMode');
if (savedNewlineMode) newlineModeSelect.value = savedNewlineMode;
// Format incoming data
const decodeData = (() => {
const decoder = new TextDecoder();
return dataView => decoder.decode(dataView);
})();
const normalizeNewlines = (text, mode) => {
switch (mode) {
case 'CR':
// Only \r: Replace all \n with \r
return text.replace(/\r?\n/g, '\r');
case 'CRLF':
// Replace lone \r or \n with \r\n
return text.replace(/\r\n|[\r\n]/g, '\r\n');
case 'ANY':
// Accept any \r, \n, \r\n. Normalize as \n for display
return text.replace(/\r\n|\r/g, '\n');
default:
return text;
}
};
// Append line to container, optionally scroll to bottom
const appendLineToReceiver = (container, text, className = '') => {
const div = document.createElement('div');
if (className) div.className = className;
div.textContent = text;
container.appendChild(div);
const distanceFromBottom = container.scrollHeight - (container.scrollTop + container.clientHeight);
if (distanceFromBottom < nearTheBottomThreshold) {
requestAnimationFrame(() => {
div.scrollIntoView({ behavior: "instant" });
});
}
};
// Update status text and style // Update status text and style
const setStatus = (msg, level = 'info') => { const setStatus = (msg, level = 'info') => {
console.log(msg); console.log(msg);
@@ -120,7 +132,7 @@
}; };
// Connect helper // Connect helper
const connectPort = async (initial=false) => { const connectPort = async (initial = false) => {
try { try {
let grantedDevices = await serial.getPorts(); let grantedDevices = await serial.getPorts();
if (grantedDevices.length === 0 && initial) { if (grantedDevices.length === 0 && initial) {
@@ -148,24 +160,27 @@
} }
await port.connect(); await port.connect();
lastPort = port; // save for reconnecting // save for reconnecting
lastPort = port;
setStatus(`Connected to ${port.device.productName || 'device'}`, 'info'); setStatus(`Connected to ${port.device.productName || 'device'}`, 'info');
connectBtn.textContent = 'Disconnect'; connectBtn.textContent = 'Disconnect';
commandLine.disabled = false; commandLine.disabled = false;
commandLine.focus(); commandLine.focus();
port.onReceiveError = async error => {
setStatus(`Read error: ${error.message}`, 'error');
await disconnectPort();
// Start auto reconnect on error if enabled
await tryAutoReconnect();
};
port.onReceive = dataView => { port.onReceive = dataView => {
let text = decodeData(dataView); let text = decodeData(dataView);
text = normalizeNewlines(text, newlineModeSelect.value); text = normalizeNewlines(text, newlineModeSelect.value);
appendLineToReceiver(receiverLines, text, 'received'); appendLineToReceiver(receiverLines, text, 'received');
}; };
port.onReceiveError = error => {
setStatus(`Read error: ${error.message}`, 'error');
// Start auto reconnect on error if enabled
tryAutoReconnect();
};
return true; return true;
} catch (error) { } catch (error) {
setStatus(`Connection failed: ${error.message}`, 'error'); setStatus(`Connection failed: ${error.message}`, 'error');
@@ -177,7 +192,7 @@
}; };
// Start auto reconnect interval if checkbox is checked and not already running // Start auto reconnect interval if checkbox is checked and not already running
const tryAutoReconnect = () => { const tryAutoReconnect = async () => {
if (!autoReconnectCheckbox.checked) return; if (!autoReconnectCheckbox.checked) return;
if (reconnectIntervalId !== null) return; // already trying if (reconnectIntervalId !== null) return; // already trying
setStatus('Attempting to auto-reconnect...', 'info'); setStatus('Attempting to auto-reconnect...', 'info');
@@ -238,13 +253,16 @@
}); });
// Checkbox toggle stops auto reconnect if unchecked // Checkbox toggle stops auto reconnect if unchecked
autoReconnectCheckbox.addEventListener('change', () => { autoReconnectCheckbox.addEventListener('change', async () => {
localStorage.setItem('autoReconnect', autoReconnectCheckbox.checked);
if (!autoReconnectCheckbox.checked) { if (!autoReconnectCheckbox.checked) {
stopAutoReconnect(); stopAutoReconnect();
} else { } else {
// Start auto reconnect immediately if not connected // Start auto reconnect immediately if not connected
if (!port) { console.log(port);
tryAutoReconnect(); console.log(lastPort);
if (!port && lastPort) {
await tryAutoReconnect();
} }
} }
}); });
@@ -263,8 +281,16 @@
sendModeBtn.classList.add('send-mode-command'); sendModeBtn.classList.add('send-mode-command');
sendModeBtn.textContent = 'Command mode'; sendModeBtn.textContent = 'Command mode';
} }
localStorage.setItem('sendMode', sendMode);
}); });
// Set initial sendMode button state
if (sendMode === 'instant') {
sendModeBtn.classList.remove('send-mode-command');
sendModeBtn.classList.add('send-mode-instant');
sendModeBtn.textContent = 'Instant mode';
}
// Send command line input on Enter // Send command line input on Enter
commandLine.addEventListener('keydown', async e => { commandLine.addEventListener('keydown', async e => {
if (!port) return; if (!port) return;
@@ -314,7 +340,8 @@
await port.send(encoder.encode(sendText)); await port.send(encoder.encode(sendText));
} catch (error) { } catch (error) {
setStatus(`Send error: ${error.message}`, 'error'); setStatus(`Send error: ${error.message}`, 'error');
tryAutoReconnect(); await disconnectPort();
await tryAutoReconnect();
} }
} }
@@ -344,6 +371,7 @@
// Add command to history, ignore duplicate consecutive // Add command to history, ignore duplicate consecutive
if (history.length === 0 || history[history.length - 1] !== text) { if (history.length === 0 || history[history.length - 1] !== text) {
history.push(text); history.push(text);
localStorage.setItem('commandHistory', JSON.stringify(history));
} }
historyIndex = -1; historyIndex = -1;
@@ -369,10 +397,15 @@
commandLine.value = ''; commandLine.value = '';
} catch (error) { } catch (error) {
setStatus(`Send error: ${error.message}`, 'error'); setStatus(`Send error: ${error.message}`, 'error');
tryAutoReconnect(); await disconnectPort();
await tryAutoReconnect();
} }
}); });
newlineModeSelect.addEventListener('change', () => {
localStorage.setItem('newlineMode', newlineModeSelect.value);
});
// Forget device button clears stored device info // Forget device button clears stored device info
forgetDeviceBtn.addEventListener('click', async () => { forgetDeviceBtn.addEventListener('click', async () => {
if (port) { if (port) {
@@ -415,6 +448,13 @@
lastCommand = null; lastCommand = null;
lastCommandCount = 0; lastCommandCount = 0;
lastCommandButton = null; lastCommandButton = null;
history.length = 0;
historyIndex = -1;
// iterate and delete localStorage items
for (const key in localStorage) {
localStorage.removeItem(key);
}
}); });

View File

@@ -50,7 +50,7 @@
<div id="sender_lines" class="scrollbox monospaced"></div> <div id="sender_lines" class="scrollbox monospaced"></div>
</div> </div>
<div class="send-container"> <div class="send-container">
<input id="command_line" class="input" placeholder="Start typing..." /> <input id="command_line" class="input" placeholder="Start typing..." autocomplete="off" />
<button id="send_mode" class="btn send-mode-command">Command Mode</button> <button id="send_mode" class="btn send-mode-command">Command Mode</button>
</div> </div>
</section> </section>