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); }; 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; var html_escape = function(input) { return $('
').text(input).html() }; 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 = html_escape(matches[1]) + '' + matches[2] + ''; } else { linkedmsg = html_escape(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 = html_escape(matches[1]) + '' + matches[2] + '' + html_escape(matches[3]); } else { linkedmsg = html_escape(linkedmsg); } } $b.append($( '' + '' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '' + '' + msg['db'] + '' + '' + msg['dt'] + '' + '' + msg['freq'] + '' + '' + linkedmsg + '' + '' )); $b.scrollTop($b[0].scrollHeight); } $.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') || (message['mode'] === 'AIS'); }; 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; if (msg.type) { if (msg.type === 'nmea') { // Do not show AIS-specific stuff for now return; } if (msg.type === 'item') { source = msg.item; } if (msg.type === 'object') { source = msg.object; } } 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 + '' + '' + source + '' + '' + link + '' + '' + (msg.comment || msg.message || '') + '' + '' )); $b.scrollTop($b[0].scrollHeight); }; $.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 + '' + '' + msg.message + '' + '' )); $b.scrollTop($b[0].scrollHeight); }; $.fn.pocsagMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new PocsagMessagePanel(this)); } return this.data('panel'); }; PageMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); } PageMessagePanel.prototype = new MessagePanel(); PageMessagePanel.prototype.supportsMessage = function(message) { return (message['mode'] === 'FLEX') || (message['mode'] === 'POCSAG'); }; PageMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '
CapCodeModeTime
' )); }; PageMessagePanel.prototype.pushMessage = function(msg) { var html_escape = function(input) { return $('
').text(input).html() }; // Get color from the message, default to white var color = msg.hasOwnProperty('color')? msg.color : "#FFF"; // Append message header (address, time, etc) var $b = $(this.el).find('tbody'); $b.append($( '' + '' + msg.address + '' + '' + msg.mode + msg.baud + '' + '' + msg.timestamp + '' + '' ).css('background-color', color).css('color', '#000')); // Append message body (text) if (msg.hasOwnProperty('message')) { $b.append($( '' + html_escape(msg.message) + '' )); } // Jump list to the last received message $b.scrollTop($b[0].scrollHeight); }; $.fn.pageMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new PageMessagePanel(this)); } return this.data('panel'); }; HfdlMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); this.flight_url = null; } HfdlMessagePanel.prototype = new MessagePanel(); HfdlMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'HFDL'; }; HfdlMessagePanel.prototype.setFlightUrl = function(url) { this.flight_url = url; }; HfdlMessagePanel.prototype.linkify = function(id) { var url = null; // Do not linkify empty strings if (id.len<=0) return id; // 6 hexadecimal digits are an ICAO aircraft ID, not linkifying if (id.match(new RegExp('^[0-9A-F]{6}$'))) return id; // Everything else is a flight ID url = this.flight_url; // Must have valid lookup URL if ((url == null) || (url == '')) return id; else return '' + id + ''; }; HfdlMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '' + '
TimeFlightAircraftData
' )); }; HfdlMessagePanel.prototype.pushMessage = function(msg) { var color = msg.hasOwnProperty('color')? msg.color : '#00000000'; var aircraft = msg.hasOwnProperty('aircraft')? msg.aircraft : ''; var flight = msg.hasOwnProperty('flight')? msg.flight : ''; var tstamp = msg.hasOwnProperty('msgtime')? '' + msg.time + '' : msg.hasOwnProperty('time')? msg.time : ''; var data = msg.hasOwnProperty('lat') && msg.hasOwnProperty('lon')? '@ ' + msg.lat + ', ' + msg.lon : msg.hasOwnProperty('type')? msg.type : ''; // Append report var $b = $(this.el).find('tbody'); $b.append($( '' + '' + tstamp + '' + '' + this.linkify(flight) + '' + '' + this.linkify(aircraft) + '' + '' + data + '' + '' ).css('background-color', color).css('color', '#000')); // Append messsage if present if (msg.hasOwnProperty('message') && (msg.message.length>0)) { $b.append($( '' + msg.message + '' )) } // Jump list to the last received message $b.scrollTop($b[0].scrollHeight); }; $.fn.hfdlMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new HfdlMessagePanel(this)); } return this.data('panel'); }; IsmMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); // These are basic message attributes this.basicInfo = ['mode', 'id', 'model', 'time', 'color']; } IsmMessagePanel.prototype = new MessagePanel(); IsmMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'ISM'; }; IsmMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '' + '' + '
IDDeviceTime
' )); }; IsmMessagePanel.prototype.formatAttr = function(msg, key) { return('' + '
' + '' + key + '' + '' + msg[key] + '' + '
' ); }; IsmMessagePanel.prototype.pushMessage = function(msg) { // Get basic information, assume white color if missing var address = msg.hasOwnProperty('id')? msg.id : "???"; var device = msg.hasOwnProperty('model')? msg.model : ""; var tstamp = msg.hasOwnProperty('time')? msg.time : ""; var color = msg.hasOwnProperty('color')? msg.color : "#FFF"; // Append message header (address, time, etc) var $b = $(this.el).find('tbody'); $b.append($( '' + '' + address + '' + '' + device + '' + '' + tstamp + '' + '' ).css('background-color', color).css('color', '#000')); // Append attributes in pairs, skip basic information var last = null; for (var key in msg) { if (this.basicInfo.indexOf(key) < 0) { var cell = this.formatAttr(msg, key); if (!last) { last = cell; } else { $b.append($('' + last + cell + '')); last = null; } } } // Last row if (last) $b.append($('' + last + '')); // Jump list to the last received message $b.scrollTop($b[0].scrollHeight); }; $.fn.ismMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new IsmMessagePanel(this)); } return this.data('panel'); }; SstvMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); } SstvMessagePanel.prototype = new MessagePanel(); SstvMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'SSTV'; }; SstvMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '
TV
' )); }; SstvMessagePanel.prototype.pushMessage = function(msg) { var $b = $(this.el).find('tbody'); if(msg.hasOwnProperty('message')) { // Append a new debug message text // See service log for debug output instead // $b.append($('' + msg.message + '')); // $b.scrollTop($b[0].scrollHeight); } else if(msg.width>0 && msg.height>0 && !msg.hasOwnProperty('line')) { var f = msg.frequency>0? ' at ' + Math.floor(msg.frequency/1000) + 'kHz' : ''; var h = '
' + msg.timestamp + ' ' + msg.width + 'x' + msg.height + ' ' + msg.sstvMode + f + '
'; var c = '
' + '
'; // Append a new canvas $b.append($('' + h + c + '')); $b.scrollTop($b[0].scrollHeight); // Save canvas context and dimensions for future use this.ctx = $(this.el).find('canvas').get(-1).getContext("2d"); this.width = msg.width; this.height = msg.height; } else if(msg.width>0 && msg.height>0 && msg.line>=0 && msg.hasOwnProperty('pixels')) { // Will copy pixels to img var pixels = atob(msg.pixels); var img = this.ctx.createImageData(msg.width, 1); // Convert BMP BGR pixels into HTML RGBA pixels for (var x = 0; x < msg.width; x++) { img.data[x*4 + 0] = pixels.charCodeAt(x*3 + 2); img.data[x*4 + 1] = pixels.charCodeAt(x*3 + 1); img.data[x*4 + 2] = pixels.charCodeAt(x*3 + 0); img.data[x*4 + 3] = 0xFF; } // Render scanline this.ctx.putImageData(img, 0, msg.line); } }; $.fn.sstvMessagePanel = function() { if (!this.data('panel')) { this.data('panel', new SstvMessagePanel(this)); } return this.data('panel'); }; FaxMessagePanel = function(el) { MessagePanel.call(this, el); this.initClearTimer(); } FaxMessagePanel.prototype = new MessagePanel(); FaxMessagePanel.prototype.supportsMessage = function(message) { return message['mode'] === 'Fax'; }; FaxMessagePanel.prototype.render = function() { $(this.el).append($( '' + '' + '' + '' + '' + '
Fax
' )); }; FaxMessagePanel.prototype.pushMessage = function(msg) { var $b = $(this.el).find('tbody'); if(msg.hasOwnProperty('message')) { // Append a new debug message text // See service log for debug output instead // $b.append($('' + msg.message + '')); // $b.scrollTop($b[0].scrollHeight); } else if(msg.width>0 && msg.height>0 && !msg.hasOwnProperty('line')) { var f = msg.frequency>0? ' at ' + Math.floor(msg.frequency/1000) + 'kHz' : ''; var h = '
' + msg.timestamp + ' ' + msg.width + 'x' + msg.height + ' ' + msg.faxMode + f + '
'; var c = '
' + '
'; // Append a new canvas $b.append($('' + h + c + '')); $b.scrollTop($b[0].scrollHeight); // Save canvas context and dimensions for future use this.ctx = $(this.el).find('canvas').get(-1).getContext("2d"); this.width = msg.width; this.height = msg.height; } else if(msg.width>0 && msg.height>0 && msg.line>=0 && msg.hasOwnProperty('pixels')) { // Will copy pixels to img var img = this.ctx.createImageData(msg.width, 1); var pixels; // Unpack RLE-compressed line of pixels if(!msg.rle) { pixels = atob(msg.pixels); } else { var rle = atob(msg.pixels); pixels = ''; for(var x=0 ; x