2022-05-22 21:37:36 -06:00
import * as Keyers from "./keyers.mjs"
import * as Outputs from "./outputs.mjs"
2021-04-27 17:30:16 -06:00
import * as Inputs from "./inputs.mjs"
import * as Repeaters from "./repeaters.mjs"
2022-04-24 19:24:56 -06:00
import * as Chart from "./chart.mjs"
2022-06-06 21:32:04 -06:00
import * as I18n from "./i18n.mjs"
2023-01-17 12:25:20 -07:00
import * as time from "./time.mjs"
2023-01-16 17:29:40 -07:00
import * as Music from "./music.mjs"
2023-01-28 17:10:28 -07:00
import * as Icon from "./icon.mjs"
2023-01-29 16:00:59 -07:00
import * as Noise from "./noise.mjs"
2024-11-03 11:17:10 -07:00
import { Decoder } from "./decoder.js" ;
2020-04-09 23:09:33 -06:00
2022-04-21 22:05:51 -06:00
const DefaultRepeater = "General"
2021-04-27 12:42:06 -06:00
2023-01-16 17:29:40 -07:00
console . warn ( "Chrome will now complain about an AudioContext not being allowed to start. This is normal, and there is no way to make Chrome stop complaining about this." )
const globalAudioContext = new AudioContext ( {
latencyHint : "interactive" ,
} )
2023-03-18 12:50:34 -06:00
function initLog ( message ) {
for ( let modal of document . querySelectorAll ( ".modal.init" ) ) {
if ( ! message ) {
modal . remove ( )
} else {
let ul = modal . querySelector ( "ul" )
while ( ul . childNodes . length > 5 ) {
ul . firstChild . remove ( )
}
let li = ul . appendChild ( document . createElement ( "li" ) )
li . textContent = message
}
}
}
2021-04-27 17:30:16 -06:00
/ * *
2022-05-22 21:37:36 -06:00
* Pop up a message , using an notification .
2021-04-27 17:30:16 -06:00
*
* @ param { string } msg Message to display
* /
2023-01-17 12:25:20 -07:00
function toast ( msg , timeout = 4 * time . Second ) {
2022-04-26 12:49:35 -06:00
console . info ( msg )
2022-05-14 18:51:05 -06:00
let errors = document . querySelector ( "#errors" )
let p = errors . appendChild ( document . createElement ( "p" ) )
p . textContent = msg
setTimeout ( ( ) => p . remove ( ) , timeout )
2021-04-27 17:30:16 -06:00
}
2022-05-08 11:33:25 -06:00
// iOS kludge
if ( ! window . AudioContext ) {
window . AudioContext = window . webkitAudioContext
}
2021-04-27 17:30:16 -06:00
class VailClient {
2020-05-01 15:07:09 -06:00
constructor ( ) {
this . sent = [ ]
this . lagTimes = [ 0 ]
this . rxDurations = [ 0 ]
2021-04-28 10:17:23 -06:00
this . clockOffset = null // How badly our clock is off of the server's
2023-01-17 12:25:20 -07:00
this . rxDelay = 0 * time . Millisecond // Time to add to incoming timestamps
2020-05-01 15:07:09 -06:00
this . beginTxTime = null // Time when we began transmitting
2023-03-18 12:50:34 -06:00
initLog ( "Initializing outputs" )
2023-01-16 17:29:40 -07:00
this . outputs = new Outputs . Collection ( globalAudioContext )
this . outputs . connect ( globalAudioContext . destination )
2023-01-29 16:00:59 -07:00
2023-03-18 12:50:34 -06:00
initLog ( "Starting up noise" )
2023-01-29 16:00:59 -07:00
this . noise = new Noise . Noise ( globalAudioContext )
this . noise . connect ( globalAudioContext . destination )
2023-03-18 12:50:34 -06:00
initLog ( "Setting app icon name" )
2023-01-28 17:10:28 -07:00
this . icon = new Icon . Icon ( )
2022-05-22 21:37:36 -06:00
2023-03-18 12:50:34 -06:00
initLog ( "Initializing keyers" )
2022-05-22 21:37:36 -06:00
this . straightKeyer = new Keyers . Keyers . straight ( this )
this . keyer = new Keyers . Keyers . straight ( this )
this . roboKeyer = new Keyers . Keyers . robo ( ( ) => this . Buzz ( ) , ( ) => this . Silence ( ) )
2021-04-27 17:30:16 -06:00
2022-04-24 19:24:56 -06:00
// Send this as the keyer so we can intercept dit and dah events for charts
2023-03-18 12:50:34 -06:00
initLog ( "Setting up input methods" )
2022-05-22 21:37:36 -06:00
this . inputs = new Inputs . Collection ( this )
2021-01-18 14:32:48 -07:00
2024-11-03 11:17:10 -07:00
// Instantiate the Decoder
initLog ( "Setting up the decoder" )
this . decoder = new Decoder ( letter => this . updateDecodedText ( letter ) ) ;
2023-03-18 12:50:34 -06:00
initLog ( "Listening on AudioContext" )
2023-01-16 17:29:40 -07:00
document . body . addEventListener (
"click" ,
e => globalAudioContext . resume ( ) ,
true ,
)
2023-03-18 12:50:34 -06:00
initLog ( 'Setting up maximize button' )
2021-04-27 17:30:16 -06:00
for ( let e of document . querySelectorAll ( "button.maximize" ) ) {
e . addEventListener ( "click" , e => this . maximize ( e ) )
2020-05-04 22:20:16 -06:00
}
2022-04-22 18:14:55 -06:00
for ( let e of document . querySelectorAll ( "#reset" ) ) {
e . addEventListener ( "click" , e => this . reset ( ) )
}
2021-04-27 17:30:16 -06:00
2023-03-18 12:50:34 -06:00
initLog ( "Initializing knobs" )
2022-05-14 18:51:05 -06:00
this . inputInit ( "#keyer-mode" , e => this . setKeyer ( e . target . value ) )
this . inputInit ( "#keyer-rate" , e => {
let rate = e . target . value
2023-01-17 12:25:20 -07:00
this . ditDuration = Math . round ( time . Minute / rate / 50 )
2022-05-14 21:17:44 -06:00
for ( let e of document . querySelectorAll ( "[data-fill='keyer-ms']" ) ) {
2022-06-10 23:16:58 -06:00
e . textContent = this . ditDuration
2022-05-14 21:17:44 -06:00
}
this . keyer . SetDitDuration ( this . ditDuration )
this . roboKeyer . SetDitDuration ( this . ditDuration )
2022-05-22 21:37:36 -06:00
this . inputs . SetDitDuration ( this . ditDuration )
2021-04-27 17:30:16 -06:00
} )
2022-04-19 22:41:09 -06:00
this . inputInit ( "#rx-delay" , e => {
2023-01-17 12:25:20 -07:00
this . rxDelay = e . target . value * time . Second
2021-04-27 17:30:16 -06:00
} )
2023-01-16 17:29:40 -07:00
this . inputInit ( "#masterGain" , e => {
this . outputs . SetGain ( e . target . value / 100 )
} )
2023-01-29 16:00:59 -07:00
this . inputInit ( "#noiseGain" , e => {
this . noise . SetGain ( e . target . value / 100 )
} )
2023-01-16 18:42:07 -07:00
let toneTransform = {
note : Music . MIDINoteName ,
freq : Music . MIDINoteFrequency ,
}
2023-01-16 17:29:40 -07:00
this . inputInit (
"#rx-tone" ,
2023-02-25 18:12:17 -07:00
e => {
this . noise . SetNoiseFrequency ( 1 , Music . MIDINoteFrequency ( e . target . value ) )
this . outputs . SetMIDINote ( false , e . target . value )
} ,
2023-01-16 18:42:07 -07:00
toneTransform ,
2023-01-16 17:29:40 -07:00
)
this . inputInit (
"#tx-tone" ,
e => this . outputs . SetMIDINote ( true , e . target . value ) ,
2023-01-16 18:42:07 -07:00
toneTransform ,
2023-01-16 17:29:40 -07:00
)
2022-04-21 18:31:33 -06:00
this . inputInit ( "#telegraph-buzzer" , e => {
this . setTelegraphBuzzer ( e . target . checked )
} )
2022-05-14 18:51:05 -06:00
this . inputInit ( "#notes" )
2023-03-18 12:50:34 -06:00
initLog ( "Filling in repeater name" )
2022-04-21 18:31:33 -06:00
document . querySelector ( "#repeater" ) . addEventListener ( "change" , e => this . setRepeater ( e . target . value . trim ( ) ) )
2021-04-28 14:28:59 -06:00
window . addEventListener ( "hashchange" , ( ) => this . hashchange ( ) )
this . hashchange ( )
2022-04-22 18:14:55 -06:00
2023-03-18 12:50:34 -06:00
initLog ( "Starting timing charts" )
2022-05-14 18:51:05 -06:00
this . setTimingCharts ( true )
2023-03-18 12:50:34 -06:00
initLog ( "Setting up mute icon" )
2023-01-16 17:29:40 -07:00
globalAudioContext . resume ( )
2022-04-22 18:14:55 -06:00
. then ( ( ) => {
2023-01-16 17:29:40 -07:00
for ( let e of document . querySelectorAll ( ".muted" ) ) {
e . classList . add ( "is-hidden" )
}
2022-04-22 18:14:55 -06:00
} )
2021-04-28 14:28:59 -06:00
}
2022-04-24 19:24:56 -06:00
/ * *
* Straight key change ( keyer shim )
*
* @ param down If key has been depressed
* /
Straight ( down ) {
2022-05-08 11:33:25 -06:00
this . straightKeyer . Key ( 0 , down )
2022-04-24 19:24:56 -06:00
}
/ * *
2022-05-14 18:51:05 -06:00
* Key / paddle change
2022-04-24 19:24:56 -06:00
*
2022-05-14 18:51:05 -06:00
* @ param { Number } key Key which was pressed
* @ param { Boolean } down True if key was pressed
2022-04-24 19:24:56 -06:00
* /
2022-05-14 18:51:05 -06:00
Key ( key , down ) {
this . keyer . Key ( key , down )
if ( this . keyCharts ) this . keyCharts [ key ] . Set ( down ? 1 : 0 )
2022-04-24 19:24:56 -06:00
}
2022-05-14 18:51:05 -06:00
setKeyer ( keyerName ) {
2022-05-22 21:37:36 -06:00
let newKeyerClass = Keyers . Keyers [ keyerName ]
let newKeyerNumber = Keyers . Numbers [ keyerName ]
2022-05-14 18:51:05 -06:00
if ( ! newKeyerClass ) {
console . error ( "Keyer not found" , keyerName )
return
}
2022-05-22 21:37:36 -06:00
let newKeyer = new newKeyerClass ( this )
2022-05-14 18:51:05 -06:00
let i = 0
for ( let keyName of newKeyer . KeyNames ( ) ) {
let e = document . querySelector ( ` .key[data-key=" ${ i } "] ` )
e . textContent = keyName
i += 1
}
this . keyer . Release ( )
this . keyer = newKeyer
2022-05-22 21:37:36 -06:00
this . inputs . SetKeyerMode ( newKeyerNumber )
2022-05-14 18:51:05 -06:00
document . querySelector ( "#keyer-rate" ) . dispatchEvent ( new Event ( "input" ) )
2022-04-24 19:24:56 -06:00
}
Buzz ( ) {
2022-05-22 21:37:36 -06:00
this . outputs . Buzz ( false )
2023-01-28 17:10:28 -07:00
this . icon . Set ( "rx" )
2023-01-22 16:34:29 -07:00
2024-11-03 11:17:10 -07:00
// Start decoding the tone
this . decoder . keyOn ( ) ; // Let the decoder know a tone is being played
2022-04-24 19:24:56 -06:00
if ( this . rxChart ) this . rxChart . Set ( 1 )
}
Silence ( ) {
2022-05-22 21:37:36 -06:00
this . outputs . Silence ( )
2022-04-24 19:24:56 -06:00
if ( this . rxChart ) this . rxChart . Set ( 0 )
2024-11-03 11:17:10 -07:00
// Stop decoding the tone
this . decoder . keyOff ( ) ; // Let the decoder know the tone has stopped
2022-04-24 19:24:56 -06:00
}
BuzzDuration ( tx , when , duration ) {
2022-05-22 21:37:36 -06:00
this . outputs . BuzzDuration ( tx , when , duration )
2022-04-24 19:24:56 -06:00
2023-01-28 17:10:28 -07:00
let chart
if ( tx ) {
chart = this . txChart
} else {
chart = this . rxChart
this . icon . SetAt ( "rx" , when )
}
2022-04-24 19:24:56 -06:00
if ( chart ) {
chart . SetAt ( 1 , when )
2022-04-24 19:42:57 -06:00
chart . SetAt ( 0 , when + duration )
2022-04-24 19:24:56 -06:00
}
2024-11-03 11:17:10 -07:00
// Inform the decoder about tone events
if ( ! tx ) {
// For received tones
setTimeout ( ( ) => {
this . decoder . keyOn ( ) ; // Start decoding the tone
setTimeout ( ( ) => {
this . decoder . keyOff ( ) ; // Stop decoding the tone after the duration
} , duration ) ;
} , when - Date . now ( ) ) ;
}
2022-04-24 19:24:56 -06:00
}
/ * *
* Start the side tone buzzer .
*
* Called from the keyer .
* /
2022-05-22 21:37:36 -06:00
BeginTx ( ) {
2022-04-24 19:24:56 -06:00
this . beginTxTime = Date . now ( )
2022-05-22 21:37:36 -06:00
this . outputs . Buzz ( true )
2024-11-03 11:17:10 -07:00
// Inform the decoder about tone start
this . decoder . keyOn ( ) ;
2022-04-24 19:24:56 -06:00
if ( this . txChart ) this . txChart . Set ( 1 )
2022-05-22 21:37:36 -06:00
2022-04-24 19:24:56 -06:00
}
/ * *
* Stop the side tone buzzer , and send out how long it was active .
*
* Called from the keyer
* /
2022-05-22 21:37:36 -06:00
EndTx ( ) {
2022-04-24 19:24:56 -06:00
if ( ! this . beginTxTime ) {
return
}
let endTxTime = Date . now ( )
let duration = endTxTime - this . beginTxTime
2022-05-22 21:37:36 -06:00
this . outputs . Silence ( true )
2022-04-24 19:24:56 -06:00
this . repeater . Transmit ( this . beginTxTime , duration )
2024-11-03 11:17:10 -07:00
// Inform the decoder about tone stop
this . decoder . keyOff ( ) ;
2022-04-24 19:24:56 -06:00
this . beginTxTime = null
if ( this . txChart ) this . txChart . Set ( 0 )
}
2021-04-28 14:28:59 -06:00
2022-04-24 17:13:56 -06:00
/ * *
* Toggle timing charts .
*
* @ param enable True to enable charts
* /
setTimingCharts ( enable ) {
// XXX: UI code shouldn't be in the Keyer class.
// Actually, the charts calls should be in vail
let chartsContainer = document . querySelector ( "#charts" )
2022-05-14 21:17:44 -06:00
if ( ! chartsContainer ) {
return
}
2022-04-24 17:13:56 -06:00
if ( enable ) {
chartsContainer . classList . remove ( "hidden" )
2022-05-14 18:51:05 -06:00
this . keyCharts = [
Chart . FromSelector ( "#key0Chart" ) ,
Chart . FromSelector ( "#key1Chart" )
]
2022-04-24 19:24:56 -06:00
this . txChart = Chart . FromSelector ( "#txChart" )
this . rxChart = Chart . FromSelector ( "#rxChart" )
2022-04-24 17:13:56 -06:00
} else {
chartsContainer . classList . add ( "hidden" )
2022-05-14 18:51:05 -06:00
this . keyCharts = [ ]
2022-04-24 19:24:56 -06:00
this . txChart = null
this . rxChart = null
2022-04-24 17:13:56 -06:00
}
}
2022-04-21 18:31:33 -06:00
/ * *
* Toggle the clicktastic buzzer , instead of the beeptastic one .
*
* @ param { bool } enable true to enable clicky buzzer
* /
setTelegraphBuzzer ( enable ) {
if ( enable ) {
2022-05-22 21:37:36 -06:00
this . outputs . SetAudioType ( "telegraph" )
2022-04-23 21:22:38 -06:00
toast ( "Telegraphs only make sound when receiving!" )
2022-04-21 18:31:33 -06:00
} else {
2022-05-22 21:37:36 -06:00
this . outputs . SetAudioType ( )
2022-04-21 18:31:33 -06:00
}
}
2021-04-28 14:28:59 -06:00
/ * *
* Called when the hash part of the URL has changed .
* /
hashchange ( ) {
let hashParts = window . location . hash . split ( "#" )
this . setRepeater ( decodeURIComponent ( hashParts [ 1 ] || "" ) )
2020-05-05 20:10:16 -06:00
}
2021-01-18 14:32:48 -07:00
2021-04-27 17:30:16 -06:00
/ * *
* Connect to a repeater by name .
*
2021-04-28 14:28:59 -06:00
* This does some switching logic to provide multiple types of repeaters ,
* like the Fortunes repeaters .
2021-04-27 17:30:16 -06:00
*
* @ param { string } name Repeater name
* /
2021-04-27 12:42:06 -06:00
setRepeater ( name ) {
2021-04-27 13:01:46 -06:00
if ( ! name || ( name == "" ) ) {
2021-04-27 18:37:25 -06:00
name = DefaultRepeater
2021-04-27 13:01:46 -06:00
}
2021-04-27 12:42:06 -06:00
this . repeaterName = name
2021-04-27 13:20:24 -06:00
// Set value of repeater element
let repeaterElement = document . querySelector ( "#repeater" )
let paps = repeaterElement . parentElement
if ( paps . MaterialTextfield ) {
paps . MaterialTextfield . change ( name )
} else {
repeaterElement . value = name
}
2021-04-27 12:42:06 -06:00
// Set window URL
2021-04-28 14:28:59 -06:00
let prevHash = window . location . hash
window . location . hash = ( name == DefaultRepeater ) ? "" : name
if ( window . location . hash != prevHash ) {
// We're going to get a hashchange event, which will re-run this method
return
2021-04-27 12:42:06 -06:00
}
2021-04-27 13:20:24 -06:00
2023-01-22 16:11:46 -07:00
this . Silence ( )
2021-04-27 17:30:16 -06:00
if ( this . repeater ) {
this . repeater . Close ( )
}
2021-04-27 18:37:25 -06:00
let rx = ( w , d , s ) => this . receive ( w , d , s )
2021-04-28 14:28:59 -06:00
// If there's a number in the name, store that for potential later use
let numberMatch = name . match ( /[0-9]+/ )
let number = 0
if ( numberMatch ) {
number = Number ( numberMatch [ 0 ] )
}
2022-06-06 16:52:22 -06:00
if ( name . startsWith ( "Fortunes" ) ) {
2021-04-28 14:28:59 -06:00
this . roboKeyer . SetPauseMultiplier ( number || 1 )
2021-04-27 18:37:25 -06:00
this . repeater = new Repeaters . Fortune ( rx , this . roboKeyer )
2021-04-28 14:28:59 -06:00
} else if ( name . startsWith ( "Echo" ) ) {
this . repeater = new Repeaters . Echo ( rx )
} else if ( name == "Null" ) {
this . repeater = new Repeaters . Null ( rx )
2021-04-27 18:37:25 -06:00
} else {
2021-04-28 14:28:59 -06:00
this . repeater = new Repeaters . Vail ( rx , name )
2021-04-27 18:37:25 -06:00
}
2020-05-01 15:07:09 -06:00
}
2021-04-27 17:30:16 -06:00
/ * *
2022-04-24 19:24:56 -06:00
* Set up an HTML input element .
2021-04-27 17:30:16 -06:00
*
2022-04-19 22:41:09 -06:00
* This reads any previously saved value and sets the input value to that .
* When the input is updated , it saves the value it ' s updated to ,
2021-04-27 17:30:16 -06:00
* and calls the provided callback with the new value .
*
* @ param { string } selector CSS path to the element
* @ param { function } callback Callback to call with any new value that is set
2023-01-16 18:42:07 -07:00
* @ param { Object . < string , function > } transform Transform functions
2021-04-27 17:30:16 -06:00
* /
2023-01-16 18:42:07 -07:00
inputInit ( selector , callback , transform = { } ) {
2020-05-01 15:07:09 -06:00
let element = document . querySelector ( selector )
2021-04-27 17:30:16 -06:00
if ( ! element ) {
2022-04-24 17:13:56 -06:00
console . warn ( "Unable to find an input to init" , selector )
2021-04-27 17:30:16 -06:00
return
}
2020-05-01 15:07:09 -06:00
let storedValue = localStorage [ element . id ]
2022-04-19 22:41:09 -06:00
if ( storedValue != null ) {
2020-05-01 15:07:09 -06:00
element . value = storedValue
2022-04-21 18:31:33 -06:00
element . checked = ( storedValue == "on" )
2020-05-01 15:07:09 -06:00
}
2022-05-14 18:51:05 -06:00
let id = element . id
2023-01-16 18:42:07 -07:00
let outputElements = document . querySelectorAll ( ` [for=" ${ id } "] ` )
2022-04-19 22:41:09 -06:00
2020-05-01 15:07:09 -06:00
element . addEventListener ( "input" , e => {
2022-04-19 22:41:09 -06:00
let value = element . value
2022-04-22 18:14:55 -06:00
if ( element . type == "checkbox" ) {
value = element . checked ? "on" : "off"
2022-04-19 22:41:09 -06:00
}
2022-04-22 18:14:55 -06:00
localStorage [ element . id ] = value
2023-01-16 18:42:07 -07:00
for ( let e of outputElements ) {
if ( e . dataset . transform ) {
let tf = transform [ e . dataset . transform ]
e . value = tf ( value )
} else {
e . value = value
}
2020-05-01 15:07:09 -06:00
}
2021-04-27 17:30:16 -06:00
if ( callback ) {
callback ( e )
}
2020-05-01 15:07:09 -06:00
} )
element . dispatchEvent ( new Event ( "input" ) )
}
2021-01-18 14:32:48 -07:00
2021-04-27 17:30:16 -06:00
/ * *
* Make an error sound and pop up a message
*
* @ param { string } msg The message to pop up
* /
2020-05-01 15:07:09 -06:00
error ( msg ) {
2021-04-27 17:30:16 -06:00
toast ( msg )
2022-05-22 21:37:36 -06:00
this . outputs . Error ( )
2020-05-01 15:07:09 -06:00
}
2021-04-27 17:30:16 -06:00
/ * *
* Called by a repeater class when there ' s something received .
*
* @ param { number } when When to play the tone
* @ param { number } duration How long to play the tone
* @ param { dict } stats Stuff the repeater class would like us to know about
* /
receive ( when , duration , stats ) {
2021-04-28 10:17:23 -06:00
this . clockOffset = stats . clockOffset || "?"
2020-05-01 15:07:09 -06:00
let now = Date . now ( )
2021-04-27 17:30:16 -06:00
when += this . rxDelay
2020-05-01 15:07:09 -06:00
2021-04-27 17:30:16 -06:00
if ( duration > 0 ) {
if ( when < now ) {
2022-04-24 19:58:24 -06:00
console . warn ( "Too old" , when , duration )
2021-04-27 17:30:16 -06:00
this . error ( "Packet requested playback " + ( now - when ) + "ms in the past. Increase receive delay!" )
return
2020-05-01 15:07:09 -06:00
}
2022-04-24 19:24:56 -06:00
this . BuzzDuration ( false , when , duration )
2021-01-18 14:32:48 -07:00
2021-04-27 17:30:16 -06:00
this . rxDurations . unshift ( duration )
this . rxDurations . splice ( 20 , 2 )
2020-05-01 15:07:09 -06:00
}
2022-06-06 16:52:22 -06:00
if ( stats . notice ) {
toast ( stats . notice )
}
2021-04-27 17:30:16 -06:00
let averageLag = ( stats . averageLag || 0 ) . toFixed ( 2 )
let longestRxDuration = this . rxDurations . reduce ( ( a , b ) => Math . max ( a , b ) )
let suggestedDelay = ( ( averageLag + longestRxDuration ) * 1.2 ) . toFixed ( 0 )
2020-05-04 22:20:16 -06:00
2022-06-06 10:55:11 -06:00
if ( stats . connected !== undefined ) {
this . outputs . SetConnected ( stats . connected )
}
2022-06-06 13:49:52 -06:00
this . updateReading ( "#note" , stats . note || stats . clients || "😎" )
2021-04-27 17:30:16 -06:00
this . updateReading ( "#lag-value" , averageLag )
this . updateReading ( "#longest-rx-value" , longestRxDuration )
this . updateReading ( "#suggested-delay-value" , suggestedDelay )
this . updateReading ( "#clock-off-value" , this . clockOffset )
2020-05-01 15:07:09 -06:00
}
2024-11-03 11:17:10 -07:00
updateDecodedText ( letter ) {
const decodedTextArea = document . getElementById ( 'decodedMorse' ) ;
if ( decodedTextArea && letter ) {
decodedTextArea . value += letter ; // Append the new letter to the decoded Morse text area
decodedTextArea . scrollTop = decodedTextArea . scrollHeight ; // Scroll to the bottom
}
}
2021-04-27 17:30:16 -06:00
/ * *
* Update an element with a value , if that element exists
*
* @ param { string } selector CSS path to the element
* @ param value Value to set
* /
updateReading ( selector , value ) {
let e = document . querySelector ( selector )
if ( e ) {
e . value = value
2020-05-19 08:21:33 -06:00
}
}
2021-01-18 14:32:48 -07:00
2021-04-27 17:30:16 -06:00
/ * *
* Maximize / minimize a card
*
* @ param e Event
* /
maximize ( e ) {
let element = e . target
while ( ! element . classList . contains ( "mdl-card" ) ) {
element = element . parentElement
if ( ! element ) {
console . log ( "Maximize button: couldn't find parent card" )
return
2020-05-21 20:32:23 -06:00
}
2020-05-19 08:21:33 -06:00
}
2021-04-27 17:30:16 -06:00
element . classList . toggle ( "maximized" )
console . log ( element )
2020-05-19 08:21:33 -06:00
}
2022-04-22 18:14:55 -06:00
/** Reset to factory defaults */
reset ( ) {
localStorage . clear ( )
location . reload ( )
}
2020-04-10 08:59:15 -06:00
}
2023-03-18 12:50:34 -06:00
async function init ( ) {
initLog ( "Starting service worker" )
2020-05-26 20:52:48 -06:00
if ( navigator . serviceWorker ) {
2023-01-17 12:25:20 -07:00
navigator . serviceWorker . register ( "scripts/sw.js" )
2020-05-26 20:52:48 -06:00
}
2023-03-18 12:50:34 -06:00
initLog ( "Setting up internationalization" )
await I18n . Setup ( )
initLog ( "Creating client" )
2020-05-17 15:34:09 -06:00
try {
2021-04-27 17:30:16 -06:00
window . app = new VailClient ( )
2020-05-17 15:34:09 -06:00
} catch ( err ) {
console . log ( err )
2021-04-27 17:30:16 -06:00
toast ( err )
2020-05-17 15:34:09 -06:00
}
2023-03-18 12:50:34 -06:00
initLog ( false )
2020-04-09 23:09:33 -06:00
}
if ( document . readyState === "loading" ) {
2022-05-14 18:51:05 -06:00
document . addEventListener ( "DOMContentLoaded" , init )
2020-04-09 23:09:33 -06:00
} else {
2022-05-14 18:51:05 -06:00
init ( )
2020-04-09 23:09:33 -06:00
}
2020-05-20 22:56:22 -06:00
// vim: noet sw=2 ts=2