function MessagePanel(el) { this.el = el; this.render(); this.initClearButton(); } MessagePanel.prototype.supportsMessage = function(message) { return false; }; MessagePanel.prototype.render = function() { }; MessagePanel.prototype.pushMessage = function(message) { }; // automatic clearing is not enabled by default. call this method from the constructor to enable MessagePanel.prototype.initClearTimer = function() { var me = this; if (me.removalInterval) clearInterval(me.removalInterval); me.removalInterval = setInterval(function () { me.clearMessages(1000); }, 15000); }; MessagePanel.prototype.clearMessages = function(toRemain) { var $elements = $(this.el).find('tbody tr'); // limit to 1000 entries in the list since browsers get laggy at some point var toRemove = $elements.length - toRemain; if (toRemove <= 0) return; $elements.slice(0, toRemove).remove(); }; MessagePanel.prototype.initClearButton = function() { var me = this; me.clearButton = $( '
Clear
' ); me.clearButton.css({ position: 'absolute', top: '10px', right: '10px' }); me.clearButton.on('click', function() { me.clearMessages(0); }); $(me.el).append(me.clearButton); }; MessagePanel.prototype.htmlEscape = function(input) { return $('
').text(input).html() }; MessagePanel.prototype.scrollToBottom = function() { var $t = $(this.el).find('table'); $t.scrollTop($t[0].scrollHeight); }; function WsjtMessagePanel(el) { MessagePanel.call(this, el); this.initClearTimer(); this.qsoModes = ['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65', 'MSK144']; this.beaconModes = ['WSPR', 'FST4W']; this.modes = [].concat(this.qsoModes, this.beaconModes); } WsjtMessagePanel.prototype = new MessagePanel(); WsjtMessagePanel.prototype.supportsMessage = function(message) { return this.modes.indexOf(message['mode']) >= 0; }; WsjtMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '' + '' + '
UTCdBDTFreqMessage
' )); }; WsjtMessagePanel.prototype.pushMessage = function(msg) { var $b = $(this.el).find('tbody'); var t = new Date(msg['timestamp']); var pad = function (i) { return ('' + i).padStart(2, "0"); }; var linkedmsg = msg['msg']; var matches; if (this.qsoModes.indexOf(msg['mode']) >= 0) { matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/); if (matches && matches[2] !== 'RR73') { linkedmsg = this.htmlEscape(matches[1]) + '' + matches[2] + ''; } else { linkedmsg = this.htmlEscape(linkedmsg); } } else if (this.beaconModes.indexOf(msg['mode']) >= 0) { matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/); if (matches) { linkedmsg = this.htmlEscape(matches[1]) + '' + matches[2] + '' + this.htmlEscape(matches[3]); } else { linkedmsg = this.htmlEscape(linkedmsg); } } $b.append($( '' + '' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '' + '' + msg['db'] + '' + '' + msg['dt'] + '' + '' + msg['freq'] + '' + '' + linkedmsg + '' + '' )); this.scrollToBottom(); } $.fn.wsjtMessagePanel = function(){ if (!this.data('panel')) { this.data('panel', new WsjtMessagePanel(this)); } return this.data('panel'); }; function PacketMessagePanel(el) { MessagePanel.call(this, el); this.initClearTimer(); } PacketMessagePanel.prototype = new MessagePanel(); PacketMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'APRS'; }; PacketMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '' + '
UTCCallsignCoordComment
' )); }; PacketMessagePanel.prototype.pushMessage = function(msg) { var $b = $(this.el).find('tbody'); var pad = function (i) { return ('' + i).padStart(2, "0"); }; if (msg.type && msg.type === 'thirdparty' && msg.data) { msg = msg.data; } var source = msg.source; var callsign; if ('object' in source) { callsign = source.object; } else if ('item' in source) { callsign = source.item; } else { callsign = source.callsign; if ('ssid' in source) { callsign += '-' + source.ssid; } } var timestamp = ''; if (msg.timestamp) { var t = new Date(msg.timestamp); timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) } var link = ''; var classes = []; var styles = {}; var overlay = ''; var stylesToString = function (s) { return $.map(s, function (value, key) { return key + ':' + value + ';' }).join('') }; if (msg.symbol) { classes.push('aprs-symbol'); classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate')); styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px'; styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px'; if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') { var s = {}; s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px'; s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px'; overlay = '
'; } } else if (msg.lat && msg.lon) { classes.push('openwebrx-maps-pin'); overlay = ''; } var attrs = [ 'class="' + classes.join(' ') + '"', 'style="' + stylesToString(styles) + '"' ].join(' '); if (msg.lat && msg.lon) { link = '' + overlay + ''; } else { link = '
' + overlay + '
' } $b.append($( '' + '' + timestamp + '' + '' + callsign + '' + '' + link + '' + '' + this.htmlEscape(msg.comment || msg.message || '') + '' + '' )); this.scrollToBottom(); }; $.fn.packetMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new PacketMessagePanel(this)); } return this.data('panel'); }; PocsagMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); } PocsagMessagePanel.prototype = new MessagePanel(); PocsagMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'Pocsag'; }; PocsagMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '
AddressMessage
' )); }; PocsagMessagePanel.prototype.pushMessage = function(msg) { var $b = $(this.el).find('tbody'); $b.append($( '' + '' + msg.address + '' + '' + this.htmlEscape(msg.message) + '' + '' )); this.scrollToBottom(); }; $.fn.pocsagMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new PocsagMessagePanel(this)); } return this.data('panel'); }; AdsbMessagePanel = function(el) { MessagePanel.call(this, el); this.aircraft = {} this.aircraftTrackingService = false; this.initClearTimer(); } AdsbMessagePanel.prototype = new MessagePanel(); AdsbMessagePanel.prototype.supportsMessage = function(message) { return message["mode"] === "ADSB"; }; AdsbMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
ICAOFlightAltitudeSpeedTrackV/SPositionMessages
' )); }; AdsbMessagePanel.prototype.pushMessage = function(message) { if (!('icao' in message)) return; if (!(message.icao in this.aircraft)) { var el = $(""); $(this.el).find('tbody').append(el); this.aircraft[message.icao] = { el: el, messages: 0 } } var state = this.aircraft[message.icao]; Object.assign(state, message); state.lastSeen = Date.now(); state.messages += 1; var ifDefined = function(input, formatter) { if (typeof(input) !== 'undefined') { if (formatter) return formatter(input); return input; } return ""; } var coordRound = function(i) { return Math.round(i * 1000) / 1000; } var getPosition = function(state) { if (!('lat' in state) || !('lon') in state) return ''; return '' + coordRound(state.lat) + ', ' + coordRound(state.lon) + ''; } state.el.html( '' + this.linkify(state, state.icao) + '' + '' + this.linkify(state, ifDefined(state.identification)) + '' + '' + ifDefined(state.altitude) + '' + '' + ifDefined(state.groundspeed || state.IAS || state.TAS, Math.round) + '' + '' + ifDefined(state.groundtrack || state.heading, Math.round) + '' + '' + ifDefined(state.verticalspeed) + '' + '' + getPosition(state) + '' + '' + state.messages + '' ); }; AdsbMessagePanel.prototype.clearMessages = function(toRemain) { var now = Date.now(); var me = this; Object.entries(this.aircraft).forEach(function(e) { if (now - e[1].lastSeen > toRemain) { delete me.aircraft[e[0]]; e[1].el.remove(); } }) }; AdsbMessagePanel.prototype.initClearTimer = function() { var me = this; if (me.removalInterval) clearInterval(me.removalInterval); me.removalInterval = setInterval(function () { me.clearMessages(30000); }, 15000); }; AdsbMessagePanel.prototype.setAircraftTrackingService = function(service) { this.aircraftTrackingService = service; }; AdsbMessagePanel.prototype.linkify = function(state, text) { var link = false; switch (this.aircraftTrackingService) { case 'flightaware': link = 'https://flightaware.com/live/modes/' + state.icao; if (state.identification) link += "/ident/" + state.identification link += '/redirect'; break; case 'planefinder': if (state.identification) link = 'https://planefinder.net/flight/' + state.identification; break; } if (link) { return '' + text + ''; } return text; }; $.fn.adsbMessagePanel = function () { if (!this.data('panel')) { this.data('panel', new AdsbMessagePanel(this)); } return this.data('panel'); }; IsmMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); }; IsmMessagePanel.prototype = new MessagePanel(); IsmMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'ISM'; }; IsmMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '' + '
ModelIDChannelData
' )); }; IsmMessagePanel.prototype.pushMessage = function(message) { var $b = $(this.el).find('tbody'); var ifDefined = function(input, formatter) { if (typeof(input) !== 'undefined') { if (formatter) return formatter(input); return input; } return ""; } var mergeRemainingMessage = function(input, exclude) { return Object.entries(input).map(function(entry) { if (exclude.includes(entry[0])) return ''; return entry[0] + ': ' + entry[1] + ';'; }).join(' '); } $b.append($( '' + '' + ifDefined(message.model) + '' + '' + ifDefined(message.id) + '' + '' + ifDefined(message.channel) + '' + '' + this.htmlEscape(mergeRemainingMessage(message, ['model', 'id', 'channel', 'mode', 'time'])) + '' + '' )); this.scrollToBottom(); }; $.fn.ismMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new IsmMessagePanel(this)); } return this.data('panel'); };