@@ -2,27 +2,48 @@
( async ( ) => {
// bind to the html
const c onnectWebUsbSerialBtn = document . getElementById ( 'connect_webusb_serial_btn' ) ;
const c onnectSerialBtn = document . getElementById ( 'connect_serial_btn' ) ;
const d isconnectBtn = document . getElementById ( 'disconnect_btn' ) ;
const uiC onnectWebUsbSerialBtn = document . getElementById ( 'connect_webusb_serial_btn' ) ;
const uiC onnectSerialBtn = document . getElementById ( 'connect_serial_btn' ) ;
const uiD isconnectBtn = document . getElementById ( 'disconnect_btn' ) ;
const n ewlineModeSelect = document . getElementById ( 'newline_mode_select' ) ;
const a utoReconnectCheckbox = document . getElementById ( 'auto_reconnect_checkbox' ) ;
const f orgetDeviceBtn = document . getElementById ( 'forget_device_btn' ) ;
const f orgetAllDevicesBtn = document . getElementById ( 'forget_all_devices_btn' ) ;
const r esetAllBtn = document . getElementById ( 'reset_all_btn' ) ;
const reset OutputBtn = document . getElementById ( 'reset _output_btn' ) ;
const copy OutputBtn = document . getElementById ( 'copy _output_btn' ) ;
const uiN ewlineModeSelect = document . getElementById ( 'newline_mode_select' ) ;
const uiA utoReconnectCheckbox = document . getElementById ( 'auto_reconnect_checkbox' ) ;
const uiF orgetDeviceBtn = document . getElementById ( 'forget_device_btn' ) ;
const uiF orgetAllDevicesBtn = document . getElementById ( 'forget_all_devices_btn' ) ;
const uiR esetAllBtn = document . getElementById ( 'reset_all_btn' ) ;
const uiCopy OutputBtn = document . getElementById ( 'copy _output_btn' ) ;
const uiDownload OutputCsv Btn = document . getElementById ( 'download_csv _output_btn' ) ;
const s tatusSpan = document . getElementById ( 'status_span' ) ;
const uiS tatusSpan = document . getElementById ( 'status_span' ) ;
const c ommandHistoryScrollbox = document . getElementById ( 'command_history_scrollbox ' ) ;
const c ommandLineInput = document . getElementById ( 'command_line_input ' ) ;
const sendModeBtn = document . getElementById ( 'send_mode_btn ' ) ;
const uiC ommandHistoryClearBtn = document . getElementById ( 'clear_ command_history_btn ' ) ;
const uiC ommandHistoryScrollbox = document . getElementById ( 'command_history_scrollbox ' ) ;
const uiCommandLineInput = document . getElementById ( 'command_line_input ' ) ;
const uiSendModeBtn = document . getElementById ( 'send_mode_btn' ) ;
const r eceivedDataScrollbox = document . getElementById ( 'received_data_scrollbox ' ) ;
const uiR eceivedDataClearBtn = document . getElementById ( 'clear_ received_data_btn ' ) ;
const uiReceivedDataScrollbox = document . getElementById ( 'received_data_scrollbox' ) ;
const n earTheBottomThreshold = 100 ; // pixels from the bottom to trigger scroll
const uiN earTheBottomThreshold = 100 ; // pixels from the bottom to trigger scroll
const maxCommandHistoryLength = 123 ; // max number of command history entries
const maxReceivedDataLength = 8192 / 8 ; // max number of received data entries
class CommandHistoryEntry {
constructor ( text ) {
this . text = text ;
this . time = Date . now ( ) ;
this . count = 1 ;
}
}
class ReceivedDataEntry {
constructor ( text ) {
this . text = text ;
this . time = Date . now ( ) ;
this . terminated = false ;
}
}
class Application {
constructor ( ) {
@@ -33,122 +54,251 @@
this . reconnectTimeoutId = null ;
this . commandHistory = [ ] ;
this . c ommandHistoryIndex = - 1 ;
this . lastCommandCount = 0 ;
this . lastCommand = null ;
this . lastCommandBtn = null ;
this . uiC ommandHistoryIndex = - 1 ;
this . receivedData = [ ] ;
// bind the UI elements
c onnectWebUsbSerialBtn. addEventListener ( 'click' , ( ) => this . connectWebUsbSerialPort ( ) ) ;
c onnectSerialBtn. addEventListener ( 'click' , ( ) => this . connectSerialPort ( ) ) ;
d isconnectBtn. addEventListener ( 'click' , ( ) => this . disconnectPort ( ) ) ;
n ewlineModeSelect. addEventListener ( 'change' , ( ) => this . setNewlineMode ( ) ) ;
a utoReconnectCheckbox. addEventListener ( 'change' , ( ) => this . autoReconnectChanged ( ) ) ;
f orgetDeviceBtn. addEventListener ( 'click' , ( ) => this . forgetPort ( ) ) ;
f orgetAllDevicesBtn. addEventListener ( 'click' , ( ) => this . forgetAllPorts ( ) ) ;
r esetAllBtn. addEventListener ( 'click' , ( ) => this . resetAll ( ) ) ;
reset OutputBtn. addEventListener ( 'click' , ( ) => this . reset Output( ) ) ;
copy OutputBtn. addEventListener ( 'click' , ( ) => this . copy Output( ) ) ;
c ommandLineInput . addEventListener ( 'keydown ' , ( e ) => this . handle CommandLineInput ( e ) ) ;
sendModeBtn . addEventListener ( 'click ' , ( ) => this . toggleSendMode ( ) ) ;
uiC onnectWebUsbSerialBtn. addEventListener ( 'click' , ( ) => this . connectWebUsbSerialPort ( ) ) ;
uiC onnectSerialBtn. addEventListener ( 'click' , ( ) => this . connectSerialPort ( ) ) ;
uiD isconnectBtn. addEventListener ( 'click' , ( ) => this . disconnectPort ( ) ) ;
uiN ewlineModeSelect. addEventListener ( 'change' , ( ) => this . setNewlineMode ( ) ) ;
uiA utoReconnectCheckbox. addEventListener ( 'change' , ( ) => this . autoReconnectChanged ( ) ) ;
uiF orgetDeviceBtn. addEventListener ( 'click' , ( ) => this . forgetPort ( ) ) ;
uiF orgetAllDevicesBtn. addEventListener ( 'click' , ( ) => this . forgetAllPorts ( ) ) ;
uiR esetAllBtn. addEventListener ( 'click' , ( ) => this . resetAll ( ) ) ;
uiCopy OutputBtn. addEventListener ( 'click' , ( ) => this . copy Output( ) ) ;
uiDownload OutputCsv Btn. addEventListener ( 'click' , ( ) => this . download OutputCsv ( ) ) ;
uiC ommandHistoryClearBtn . addEventListener ( 'click ' , ( ) => this . clear CommandHistory ( ) ) ;
uiCommandLineInput . addEventListener ( 'keydown ' , ( e ) => this . handleCommandLineInput ( e ) ) ;
uiSendModeBtn . addEventListener ( 'click' , ( ) => this . toggleSendMode ( ) ) ;
uiReceivedDataClearBtn . addEventListener ( 'click' , ( ) => this . clearReceivedData ( ) ) ;
// restore state from localStorage
try {
this . restoreState ( ) ;
} catch ( error ) {
console . error ( 'Failed to restore state from localStorage' , error ) ;
this . resetAll ( ) ;
this . restoreState ( ) ;
}
this . updateUIConnectionState ( ) ;
this . connectWebUsbSerialPort ( true ) ;
}
restoreState ( ) {
// Restore command history
let savedCommandHistory = JSON . parse ( localStorage . getItem ( 'commandHistory' ) || '[]' ) ;
for ( const cmd of savedCommandHistory ) {
this . appendCommandToHistory ( cmd ) ;
}
// Restore received data
let savedReceivedData = JSON . parse ( localStorage . getItem ( 'receivedData' ) || '[]' ) ;
for ( let line of savedReceivedData ) {
line . terminated = true ;
this . appendReceivedData ( line ) ;
}
this . sendMode = localStorage . getItem ( 'sendMode' ) || 'command' ;
this . setSendMode ( this . sendMode ) ;
a utoReconnectCheckbox. checked = localStorage . getItem ( 'autoReconnect' ) === 'true' ;
uiA utoReconnectCheckbox. checked = ! ( localStorage . getItem ( 'autoReconnect' ) === 'false' ) ;
let savedNewlineMode = localStorage . getItem ( 'newlineMode' ) ;
if ( savedNewlineMode ) {
n ewlineModeSelect. value = savedNewlineMode ;
uiN ewlineModeSelect. value = savedNewlineMode ;
}
this . connectWebUsbSerialPort ( true ) ;
}
appendCommandToHistory ( text ) {
if ( text === this . lastCommand ) {
// Increment count and update button
this . lastCommandCount ++ ;
this . lastCommandBtn . textContent = ` ${ text } × ${ this . lastCommandCount } ` ;
} else {
// Add a new entry to the command history
this . commandHistory . push ( text ) ;
localStorage . setItem ( 'commandHistory' , JSON . stringify ( this . commandHistory ) ) ;
this . commandHistoryIndex = - 1 ;
appendCommandToHistory ( commandHistoryEntry ) {
const wasNearBottom = uiCommandHistoryScrollbox . scrollHeight - uiCommandHistoryScrollbox . scrollTop <= uiCommandHistoryScrollbox . clientHeight + uiNearTheBottomThreshold ;
cons t commandHistoryEntryBtn = document . createElement ( 'button' ) ;
commandHistoryEntryBtn . className = 'command-history-entry' ;
commandHistoryEntryBtn . type = 'button' ;
commandHistoryEntryBtn . textContent = text ;
commandHistoryEntryBtn . addEventListener ( 'click' , ( ) => {
if ( commandLineInput . disabled ) return ;
commandLineInput . value = text ;
commandLineInput . focus ( ) ;
} ) ;
commandHistoryScrollbox . appendChild ( commandHistoryEntryBtn ) ;
le t commandHistoryEntryBtn = null ;
this . lastCommand = text ;
this . lastCommandBtn = commandHistoryEntryBtn ;
let lastCommandMatched = false ;
if ( this . commandHistory . length > 0 ) {
let lastCommandEntry = this . commandHistory [ this . commandHistory . length - 1 ] ;
if ( lastCommandEntry . text === commandHistoryEntry . text ) {
lastCommandEntry . count ++ ;
lastCommandEntry . time = Date . now ( ) ;
lastCommandMatched = true ;
// Scroll to the new entry if near the bottom
const distanceFromBottom = commandHistoryScrollbox . scrollHeight - ( c ommandHistoryScrollbox. scrollTop + commandHistoryScrollbox . clientHeight ) ;
if ( distanceFromBottom < nearTheBottomThreshold ) {
requestAnimationFra me( ( ) => {
commandHistoryEntryBtn . scrollIntoView ( { behavior : 'instant' } ) ;
} ) ;
// Update the last command entry
commandHistoryEntryBtn = uiC ommandHistoryScrollbox. lastElementChild ;
let time _str = new Date ( lastCommandEntry . time ) . toLocaleString ( ) ;
commandHistoryEntryBtn . querySelector ( '.command-history-entry-ti me' ) . textContent = time _str ;
commandHistoryEntryBtn . querySelector ( '.command-history-entry-text' ) . textContent = lastCommandEntry . text ;
commandHistoryEntryBtn . querySelector ( '.command-history-entry-count' ) . textContent = '× ' + lastCommandEntry . count ;
}
}
}
if ( ! lastCommandMatched ) {
this . commandHistory . push ( commandHistoryEntry ) ;
appendLineToReceived ( text ) {
const div = document . createElement ( 'div ' ) ;
div . textContent = text ;
receivedDataScrollbox . appendChild ( div ) ;
// Create a new command history entry
commandHistoryEntryBtn = document . createElement ( 'button ' ) ;
commandHistoryEntryBtn . className = 'command-history-entry' ;
commandHistoryEntryBtn . type = 'button' ;
let time _str = new Date ( commandHistoryEntry . time ) . toLocaleString ( ) ;
commandHistoryEntryBtn . innerHTML = `
<span class="command-history-entry-time"> ${ time _str } </span>
<span class="command-history-entry-text"> ${ commandHistoryEntry . text } </span>
<span class="command-history-entry-count">× ${ commandHistoryEntry . count } </span>
` ;
commandHistoryEntryBtn . addEventListener ( 'click' , ( ) => {
if ( uiCommandLineInput . disabled ) return ;
uiCommandLineInput . value = commandHistoryEntry . text ;
uiCommandLineInput . focus ( ) ;
} ) ;
uiCommandHistoryScrollbox . appendChild ( commandHistoryEntryBtn ) ;
}
// Limit the command history length
while ( this . commandHistory . length > maxCommandHistoryLength ) {
this . commandHistory . shift ( ) ;
uiCommandHistoryScrollbox . removeChild ( uiCommandHistoryScrollbox . firstElementChild ) ;
}
// Save the command history to localStorage
localStorage . setItem ( 'commandHistory' , JSON . stringify ( this . commandHistory ) ) ;
// Scroll to the new entry if near the bottom
const distanceFromBottom = receivedDataScrollbox . scrollHeight - ( receivedDataScrollbox . scrollTop + receivedDataScrollbox . clientHeight ) ;
if ( distanceFromBottom < nearTheBottomThreshold ) {
if ( wasNearBottom ) {
requestAnimationFrame ( ( ) => {
div . scrollIntoView ( { behavior : 'instant' } ) ;
uiCommandHistoryScrollbox . scrollTop = uiCommandHistoryScrollbox . scrollHeight ;
} ) ;
}
}
clearCommandHistory ( ) {
this . commandHistory = [ ] ;
uiCommandHistoryScrollbox . innerHTML = '' ;
localStorage . removeItem ( 'commandHistory' ) ;
this . setStatus ( 'Command history cleared' , 'info' ) ;
}
appendReceivedData ( receivedDataEntry ) {
const wasNearBottom = uiReceivedDataScrollbox . scrollHeight - uiReceivedDataScrollbox . scrollTop <= uiReceivedDataScrollbox . clientHeight + uiNearTheBottomThreshold ;
let newReceivedDataEntries = [ ] ;
let updateLastReceivedDataEntry = false ;
if ( this . receivedData . length <= 0 ) {
newReceivedDataEntries . push ( receivedDataEntry ) ;
} else {
let lastReceivedDataEntry = this . receivedData [ this . receivedData . length - 1 ] ;
// Check if the last entry is terminated
if ( lastReceivedDataEntry . terminated ) {
newReceivedDataEntries . push ( receivedDataEntry ) ;
} else {
if ( ! lastReceivedDataEntry . terminated ) {
updateLastReceivedDataEntry = true ;
this . receivedData . pop ( ) ;
receivedDataEntry . text = lastReceivedDataEntry . text + receivedDataEntry . text ;
}
// split the text into lines
let lines = receivedDataEntry . text . split ( /\r?\n/ ) ;
// check if the last line is terminated by checking if it ends with an empty string
let lastLineTerminated = lines [ lines . length - 1 ] === '' ;
if ( lastLineTerminated ) {
lines . pop ( ) ; // remove the last empty line
}
// create new entries for each line
for ( let i = 0 ; i < lines . length ; i ++ ) {
let line = lines [ i ] ;
let entry = new ReceivedDataEntry ( line ) ;
if ( i === lines . length - 1 ) {
entry . terminated = lastLineTerminated ;
} else {
entry . terminated = true ;
}
newReceivedDataEntries . push ( entry ) ;
}
// if the last line is terminated, modify the last entry
if ( lastLineTerminated ) {
newReceivedDataEntries [ newReceivedDataEntries . length - 1 ] . terminated = true ;
} else {
newReceivedDataEntries [ newReceivedDataEntries . length - 1 ] . terminated = false ;
}
}
}
this . receivedData . push ( ... newReceivedDataEntries ) ;
if ( updateLastReceivedDataEntry ) {
// update the rendering of the last entry
let lastReceivedDataEntryBtn = uiReceivedDataScrollbox . lastElementChild ;
lastReceivedDataEntryBtn . querySelector ( '.received-data-entry-text' ) . textContent = newReceivedDataEntries [ 0 ] . text ;
lastReceivedDataEntryBtn . querySelector ( '.received-data-entry-time' ) . textContent = new Date ( newReceivedDataEntries [ 0 ] . time ) . toLocaleString ( ) ;
newReceivedDataEntries . shift ( ) ;
}
// render the new entries
let documentFragment = document . createDocumentFragment ( ) ;
for ( const entry of newReceivedDataEntries ) {
let receivedDataEntryBtn = document . createElement ( 'div' ) ;
receivedDataEntryBtn . className = 'received-data-entry' ;
receivedDataEntryBtn . innerHTML = `
<span class="received-data-entry-time"> ${ new Date ( entry . time ) . toLocaleString ( ) } </span>
<span class="received-data-entry-text"> ${ entry . text } </span>
` ;
documentFragment . appendChild ( receivedDataEntryBtn ) ;
}
uiReceivedDataScrollbox . appendChild ( documentFragment ) ;
// Limit the received data length
while ( this . receivedData . length > maxReceivedDataLength ) {
this . receivedData . shift ( ) ;
uiReceivedDataScrollbox . removeChild ( uiReceivedDataScrollbox . firstElementChild ) ;
}
// Save the received data to localStorage
localStorage . setItem ( 'receivedData' , JSON . stringify ( this . receivedData ) ) ;
// Scroll to the new entry if near the bottom
if ( wasNearBottom ) {
requestAnimationFrame ( ( ) => {
uiReceivedDataScrollbox . scrollTop = uiReceivedDataScrollbox . scrollHeight ;
} ) ;
}
}
clearReceivedData ( ) {
this . receivedData = [ ] ;
uiReceivedDataScrollbox . innerHTML = '' ;
localStorage . removeItem ( 'receivedData' ) ;
this . setStatus ( 'Received data cleared' , 'info' ) ;
}
setStatus ( msg , level = 'info' ) {
console . log ( msg ) ;
s tatusSpan. textContent = msg ;
s tatusSpan. className = 'status status-' + level ;
uiS tatusSpan. textContent = msg ;
uiS tatusSpan. className = 'status status-' + level ;
}
updateUIConnectionState ( ) {
if ( this . currentPort && this . currentPort . isConnected ) {
c onnectWebUsbSerialBtn. style . display = 'none' ;
c onnectSerialBtn. style . display = 'none' ;
d isconnectBtn. style . display = 'block' ;
c ommandLineInput. disabled = false ;
c ommandLineInput. focus ( ) ;
uiC onnectWebUsbSerialBtn. style . display = 'none' ;
uiC onnectSerialBtn. style . display = 'none' ;
uiD isconnectBtn. style . display = 'block' ;
uiC ommandLineInput. disabled = false ;
uiC ommandLineInput. focus ( ) ;
} else {
if ( serial . isWebUsbSupported ( ) ) {
c onnectWebUsbSerialBtn. style . display = 'block' ;
uiC onnectWebUsbSerialBtn. style . display = 'block' ;
}
if ( serial . isWebSerialSupported ( ) ) {
c onnectSerialBtn. style . display = 'block' ;
uiC onnectSerialBtn. style . display = 'block' ;
}
if ( ! serial . isWebUsbSupported ( ) && ! serial . isWebSerialSupported ( ) ) {
this . setStatus ( 'Your browser does not support WebUSB or WebSerial' , 'error' ) ;
}
d isconnectBtn. style . display = 'none' ;
c ommandLineInput. disabled = true ;
c ommandLineInput. value = '' ;
c ommandLineInput. blur ( ) ;
uiD isconnectBtn. style . display = 'none' ;
uiC ommandLineInput. disabled = true ;
uiC ommandLineInput. value = '' ;
uiC ommandLineInput. blur ( ) ;
}
}
@@ -168,9 +318,10 @@
async onReceive ( dataView ) {
this . updateUIConnectionState ( ) ;
let text = this . textDecoder . decode ( dataView ) ;
text = this . normalizeNewlines ( text ) ;
this . appendLineTo Received ( text ) ;
let receivedDataEntry = new ReceivedDataEntry ( text ) ;
this . appendReceivedData ( receivedDataEntry ) ;
}
async onReceiveError ( error ) {
@@ -210,7 +361,7 @@
let first _time _connection = false ;
let grantedDevices = await serial . getWebUsbSerialPorts ( ) ;
if ( initial ) {
if ( ! a utoReconnectCheckbox. checked || grantedDevices . length === 0 ) {
if ( ! uiA utoReconnectCheckbox. checked || grantedDevices . length === 0 ) {
return false ;
}
@@ -303,18 +454,18 @@
}
setNewlineMode ( ) {
localStorage . setItem ( 'newlineMode' , n ewlineModeSelect. value ) ;
localStorage . setItem ( 'newlineMode' , uiN ewlineModeSelect. value ) ;
}
autoReconnectChanged ( ) {
if ( a utoReconnectCheckbox. checked ) {
if ( uiA utoReconnectCheckbox. checked ) {
this . setStatus ( 'Auto-reconnect enabled' , 'info' ) ;
this . tryAutoReconnect ( ) ;
} else {
this . setStatus ( 'Auto-reconnect disabled' , 'info' ) ;
this . stopAutoReconnect ( ) ;
}
localStorage . setItem ( 'autoReconnect' , a utoReconnectCheckbox. checked ) ;
localStorage . setItem ( 'autoReconnect' , uiA utoReconnectCheckbox. checked ) ;
}
stopAutoReconnect ( ) {
@@ -327,12 +478,12 @@
tryAutoReconnect ( ) {
this . updateUIConnectionState ( ) ;
if ( ! a utoReconnectCheckbox. checked ) return ;
if ( ! uiA utoReconnectCheckbox. checked ) return ;
if ( this . reconnectTimeoutId !== null ) return ; // already trying
this . setStatus ( 'Attempting to auto-reconnect...' , 'info' ) ;
this . reconnectTimeoutId = setTimeout ( async ( ) => {
this . reconnectTimeoutId = null ;
if ( ! a utoReconnectCheckbox. checked ) {
if ( ! uiA utoReconnectCheckbox. checked ) {
this . setStatus ( 'Auto-reconnect stopped.' , 'info' ) ;
return ;
}
@@ -362,7 +513,7 @@
let sendText = '' ;
switch ( e . key ) {
case 'Enter' :
switch ( n ewlineModeSelect. value ) {
switch ( uiN ewlineModeSelect. value ) {
case 'CR' : sendText = '\r' ; break ;
case 'CRLF' : sendText = '\r\n' ; break ;
default : sendText = '\n' ; break ;
@@ -387,7 +538,7 @@
sendText = e . key ;
}
try {
await p ort. send ( this . textEncoder . encode ( sendText ) ) ;
await this . currentP ort. send ( this . textEncoder . encode ( sendText ) ) ;
} catch ( error ) {
this . setStatus ( ` Send error: ${ error . message } ` , 'error' ) ;
this . tryAutoReconnect ( ) ;
@@ -402,24 +553,24 @@
e . preventDefault ( ) ;
if ( this . commandHistory . length === 0 ) return ;
if ( e . key === 'ArrowUp' ) {
if ( this . c ommandHistoryIndex === - 1 ) this . c ommandHistoryIndex = this . commandHistory . length - 1 ;
else if ( this . c ommandHistoryIndex > 0 ) this . c ommandHistoryIndex-- ;
if ( this . uiC ommandHistoryIndex === - 1 ) this . uiC ommandHistoryIndex = this . commandHistory . length - 1 ;
else if ( this . uiC ommandHistoryIndex > 0 ) this . uiC ommandHistoryIndex-- ;
} else if ( e . key === 'ArrowDown' ) {
if ( this . c ommandHistoryIndex !== - 1 ) this . c ommandHistoryIndex++ ;
if ( this . c ommandHistoryIndex >= this . commandHistory . length ) this . c ommandHistoryIndex = - 1 ;
if ( this . uiC ommandHistoryIndex !== - 1 ) this . uiC ommandHistoryIndex++ ;
if ( this . uiC ommandHistoryIndex >= this . commandHistory . length ) this . uiC ommandHistoryIndex = - 1 ;
}
c ommandLineInput. value = this . c ommandHistoryIndex === - 1 ? '' : this . commandHistory [ this . c ommandHistoryIndex] ;
uiC ommandLineInput. value = this . uiC ommandHistoryIndex === - 1 ? '' : this . commandHistory [ this . uiC ommandHistoryIndex] . text ;
return ;
}
if ( e . key !== 'Enter' || ! this . currentPort . isConnected ) return ;
e . preventDefault ( ) ;
const text = c ommandLineInput. value ;
const text = uiC ommandLineInput. value ;
if ( ! text ) return ;
// Convert to Uint8Array with newline based on config
let sendText = text ;
switch ( n ewlineModeSelect. value ) {
switch ( uiN ewlineModeSelect. value ) {
case 'CR' :
sendText += '\r' ;
break ;
@@ -434,9 +585,11 @@
try {
await this . currentPort . send ( data ) ;
this . c ommandHistoryIndex = - 1 ;
this . appendCommandToHistory ( sendText . replace ( /[\r\n]+$/ , '' ) ) ;
commandLineInput . value = '' ;
this . uiC ommandHistoryIndex = - 1 ;
let history _cmd _text = sendText . replace ( /[\r\n]+$/ , '' ) ;
let history _entry = new CommandHistoryEntry ( history _cmd _text ) ;
this . appendCommandToHistory ( history _entry ) ;
uiCommandLineInput . value = '' ;
} catch ( error ) {
this . setStatus ( ` Send error: ${ error . message } ` , 'error' ) ;
this . tryAutoReconnect ( ) ;
@@ -454,32 +607,26 @@
setSendMode ( mode ) {
this . sendMode = mode ;
if ( mode === 'instant' ) {
s endModeBtn. classList . remove ( 'send-mode-command' ) ;
s endModeBtn. classList . add ( 'send-mode-instant' ) ;
s endModeBtn. textContent = 'Instant mode' ;
uiS endModeBtn. classList . remove ( 'send-mode-command' ) ;
uiS endModeBtn. classList . add ( 'send-mode-instant' ) ;
uiS endModeBtn. textContent = 'Instant mode' ;
} else {
s endModeBtn. classList . remove ( 'send-mode-instant' ) ;
s endModeBtn. classList . add ( 'send-mode-command' ) ;
s endModeBtn. textContent = 'Command mode' ;
uiS endModeBtn. classList . remove ( 'send-mode-instant' ) ;
uiS endModeBtn. classList . add ( 'send-mode-command' ) ;
uiS endModeBtn. textContent = 'Command mode' ;
}
localStorage . setItem ( 'sendMode' , this . sendMode ) ;
}
normalizeNewlines ( text ) {
switch ( newlineModeSelect . value ) {
case 'CR' :
return text . replace ( /\r?\n/g , '\r' ) ;
case 'CRLF' :
return text . replace ( /\r\n|[\r\n]/g , '\r\n' ) ;
case 'ANY' :
return text . replace ( /\r\n|\r/g , '\n' ) ;
default :
return text ;
}
}
copyOutput ( ) {
cons t text = receivedDataScrollbox . innerText ;
le t text = '' ;
for ( const entry of this . receivedData ) {
text += entry . text ;
if ( entry . terminated ) {
text += '\n' ;
}
}
if ( text ) {
navigator . clipboard . writeText ( text ) . then ( ( ) => {
this . setStatus ( 'Output copied to clipboard' , 'info' ) ;
@@ -491,8 +638,22 @@
}
}
reset Output( ) {
receivedDataScrollbox . innerHTML = '' ;
download OutputCsv ( ) {
// save <iso_date_time>,<received_line>
let csvContent = 'data:text/csv;charset=utf-8,' ;
for ( const entry of this . receivedData ) {
let line = new Date ( entry . time ) . toISOString ( ) + ',"' + entry . text . replace ( /[\r\n]+$/ , '' ) + '"' ;
csvContent += line + '\n' ;
}
const encodedUri = encodeURI ( csvContent ) ;
const link = document . createElement ( 'a' ) ;
link . setAttribute ( 'href' , encodedUri ) ;
const filename = new Date ( ) . toISOString ( ) + '_tinyusb_received_serial_data.csv' ;
link . setAttribute ( 'download' , filename ) ;
document . body . appendChild ( link ) ;
link . click ( ) ;
document . body . removeChild ( link ) ;
}
async resetAll ( ) {