2021-04-26 16:09:03 -06:00
|
|
|
|
import * as Morse from "./morse.mjs"
|
2021-04-27 17:30:16 -06:00
|
|
|
|
import * as Inputs from "./inputs.mjs"
|
|
|
|
|
import * as Repeaters from "./repeaters.mjs"
|
2020-04-09 23:09:33 -06:00
|
|
|
|
|
2021-04-27 12:42:06 -06:00
|
|
|
|
const DefaultRepeater = "General Chaos"
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
/**
|
|
|
|
|
* Pop up a message, using an MDL snackbar.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} msg Message to display
|
|
|
|
|
*/
|
|
|
|
|
function toast(msg) {
|
|
|
|
|
let el = document.querySelector("#snackbar")
|
|
|
|
|
if (!el || !el.MaterialSnackbar) {
|
|
|
|
|
console.warn(msg)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
el.MaterialSnackbar.showSnackbar({
|
|
|
|
|
message: msg,
|
|
|
|
|
timeout: 2000
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class VailClient {
|
2020-05-01 15:07:09 -06:00
|
|
|
|
constructor() {
|
|
|
|
|
this.sent = []
|
|
|
|
|
this.lagTimes = [0]
|
|
|
|
|
this.rxDurations = [0]
|
2021-04-27 18:37:25 -06:00
|
|
|
|
this.clockOffset = "unknown" // How badly our clock is off of the server's
|
2020-05-01 15:07:09 -06:00
|
|
|
|
this.rxDelay = 0 // Milliseconds to add to incoming timestamps
|
|
|
|
|
this.beginTxTime = null // Time when we began transmitting
|
2020-06-29 19:28:51 -06:00
|
|
|
|
this.debug = localStorage.debug
|
2020-05-01 15:07:09 -06:00
|
|
|
|
|
2021-04-27 12:42:06 -06:00
|
|
|
|
// Redirect old URLs
|
|
|
|
|
if (window.location.search) {
|
|
|
|
|
let me = new URL(location)
|
|
|
|
|
let repeater = me.searchParams.get("repeater")
|
|
|
|
|
me.search = ""
|
2021-04-27 18:37:25 -06:00
|
|
|
|
me.hash = decodeURIComponent(repeater)
|
2021-04-27 12:42:06 -06:00
|
|
|
|
window.location = me
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
// Make helpers
|
|
|
|
|
this.buzzer = new Morse.Buzzer()
|
|
|
|
|
this.keyer = new Morse.Keyer(() => this.beginTx(), () => this.endTx())
|
2021-04-27 18:37:25 -06:00
|
|
|
|
this.roboKeyer = new Morse.Keyer(() => this.buzzer.Buzz(), () => this.buzzer.Silence())
|
2021-04-27 17:30:16 -06:00
|
|
|
|
|
|
|
|
|
// Set up various input methods
|
|
|
|
|
this.inputs = Inputs.SetupAll(this.keyer)
|
2021-01-18 14:32:48 -07:00
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
// Maximize button
|
|
|
|
|
for (let e of document.querySelectorAll("button.maximize")) {
|
|
|
|
|
e.addEventListener("click", e => this.maximize(e))
|
2020-05-04 22:20:16 -06:00
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
for (let e of document.querySelectorAll("#ck")) {
|
|
|
|
|
e.addEventListener("click", e => this.test())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set up sliders
|
|
|
|
|
this.sliderInit("#iambic-duration", e => {
|
|
|
|
|
this.keyer.SetIntervalDuration(e.target.value)
|
2021-04-27 18:37:25 -06:00
|
|
|
|
this.roboKeyer.SetIntervalDuration(e.target.value)
|
2021-04-27 17:30:16 -06:00
|
|
|
|
})
|
|
|
|
|
this.sliderInit("#rx-delay", e => {
|
|
|
|
|
this.rxDelay = Number(e.target.value)
|
|
|
|
|
})
|
2021-01-18 14:32:48 -07:00
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
// Fill in the name of our repeater
|
|
|
|
|
let repeaterElement = document.querySelector("#repeater").addEventListener("change", e => this.setRepeater(e.target.value.trim()))
|
2021-04-27 18:37:25 -06:00
|
|
|
|
this.setRepeater(decodeURI(decodeURIComponent(window.location.hash.split("#")[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.
|
|
|
|
|
*
|
|
|
|
|
* In the future this may do some fancy switching logic to provide multiple types of repeaters.
|
|
|
|
|
* For instance, I'd like to create a set of repeaters that run locally, for practice.
|
|
|
|
|
*
|
|
|
|
|
* @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
|
|
|
|
|
let hash = name
|
|
|
|
|
if (name == DefaultRepeater) {
|
|
|
|
|
hash = ""
|
|
|
|
|
}
|
|
|
|
|
if (hash != window.location.hash) {
|
|
|
|
|
window.location.hash = hash
|
|
|
|
|
}
|
2021-04-27 13:20:24 -06:00
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
// You can set the repeater name to "Fortunes: Pauses×10" for a nice and easy intro
|
|
|
|
|
if (name.startsWith("Fortunes")) {
|
|
|
|
|
let m = name.match(/[x×]([0-9]+)/)
|
|
|
|
|
let mult = 1
|
|
|
|
|
if (m) {
|
|
|
|
|
mult = Number(m[1])
|
|
|
|
|
}
|
|
|
|
|
this.roboKeyer.SetPauseMultiplier(mult)
|
|
|
|
|
this.repeater = new Repeaters.Fortune(rx, this.roboKeyer)
|
|
|
|
|
} else {
|
|
|
|
|
this.repeater = new Repeaters.Vail(name, rx)
|
|
|
|
|
}
|
2021-04-27 12:42:06 -06:00
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
toast(`Now using repeater: ${name}`)
|
2020-05-01 15:07:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
/**
|
|
|
|
|
* Set up a slider.
|
|
|
|
|
*
|
|
|
|
|
* This reads any previously saved value and sets the slider to that.
|
|
|
|
|
* When the slider is updated, it saves the value it's updated to,
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
sliderInit(selector, callback) {
|
2020-05-01 15:07:09 -06:00
|
|
|
|
let element = document.querySelector(selector)
|
2021-04-27 17:30:16 -06:00
|
|
|
|
if (!element) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-05-01 15:07:09 -06:00
|
|
|
|
let storedValue = localStorage[element.id]
|
|
|
|
|
if (storedValue) {
|
|
|
|
|
element.value = storedValue
|
|
|
|
|
}
|
|
|
|
|
let outputElement = document.querySelector(selector + "-value")
|
|
|
|
|
element.addEventListener("input", e => {
|
|
|
|
|
localStorage[element.id] = element.value
|
|
|
|
|
if (outputElement) {
|
|
|
|
|
outputElement.value = element.value
|
|
|
|
|
}
|
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)
|
2020-05-01 15:07:09 -06:00
|
|
|
|
this.buzzer.ErrorTone()
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
/**
|
|
|
|
|
* Start the side tone buzzer.
|
|
|
|
|
*/
|
2020-05-01 15:07:09 -06:00
|
|
|
|
beginTx() {
|
|
|
|
|
this.beginTxTime = Date.now()
|
|
|
|
|
this.buzzer.Buzz(true)
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
/**
|
|
|
|
|
* Stop the side tone buzzer, and send out how long it was active.
|
|
|
|
|
*/
|
2020-05-01 15:07:09 -06:00
|
|
|
|
endTx() {
|
|
|
|
|
let endTxTime = Date.now()
|
|
|
|
|
let duration = endTxTime - this.beginTxTime
|
|
|
|
|
this.buzzer.Silence(true)
|
2021-04-27 17:30:16 -06:00
|
|
|
|
this.repeater.Transmit(this.beginTxTime, duration)
|
2020-05-01 15:07:09 -06:00
|
|
|
|
this.beginTxTime = null
|
|
|
|
|
}
|
|
|
|
|
|
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-27 18:37:25 -06:00
|
|
|
|
this.clockOffset = stats.clockOffset || "unknown"
|
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) {
|
|
|
|
|
this.error("Packet requested playback " + (now - when) + "ms in the past. Increase receive delay!")
|
|
|
|
|
return
|
2020-05-01 15:07:09 -06:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
this.buzzer.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
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 15:07:09 -06:00
|
|
|
|
/**
|
2021-04-27 17:30:16 -06:00
|
|
|
|
* Send "CK" to server, and don't squelch the echo
|
2020-05-01 15:07:09 -06:00
|
|
|
|
*/
|
2021-04-27 17:30:16 -06:00
|
|
|
|
test() {
|
|
|
|
|
let when = Date.now()
|
2020-05-01 15:07:09 -06:00
|
|
|
|
let dit = Number(document.querySelector("#iambic-duration-value").value)
|
|
|
|
|
let dah = dit * 3
|
|
|
|
|
let s = dit
|
2021-04-27 17:30:16 -06:00
|
|
|
|
let message = [
|
2020-05-01 15:07:09 -06:00
|
|
|
|
dah, s, dit, s, dah, s, dit,
|
|
|
|
|
s * 3,
|
|
|
|
|
dah, s, dit, s, dah
|
|
|
|
|
]
|
2021-04-26 17:55:46 -06:00
|
|
|
|
|
2021-04-27 17:30:16 -06:00
|
|
|
|
this.repeater.Transmit(when, 0) // Get round-trip time
|
|
|
|
|
for (let i in message) {
|
|
|
|
|
let duration = message[i]
|
|
|
|
|
if (i % 2 == 0) {
|
|
|
|
|
this.repeater.Transmit(when, duration, false)
|
2020-05-19 08:21:33 -06:00
|
|
|
|
}
|
2021-04-27 17:30:16 -06:00
|
|
|
|
when += duration
|
2020-05-19 08:21:33 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-10 08:59:15 -06:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-26 13:08:43 -06:00
|
|
|
|
function vailInit() {
|
2020-05-26 20:52:48 -06:00
|
|
|
|
if (navigator.serviceWorker) {
|
|
|
|
|
navigator.serviceWorker.register("sw.js")
|
|
|
|
|
}
|
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
|
|
|
|
}
|
2020-04-09 23:09:33 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (document.readyState === "loading") {
|
2020-05-01 15:07:09 -06:00
|
|
|
|
document.addEventListener("DOMContentLoaded", vailInit)
|
2020-04-09 23:09:33 -06:00
|
|
|
|
} else {
|
2020-05-01 15:07:09 -06:00
|
|
|
|
vailInit()
|
2020-04-09 23:09:33 -06:00
|
|
|
|
}
|
2020-05-20 22:56:22 -06:00
|
|
|
|
|
|
|
|
|
// vim: noet sw=2 ts=2
|