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(250);
}, 15000);
};
// Clear all currently shown messages.
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();
};
// Add CLEAR button to the message list.
MessagePanel.prototype.initClearButton = function() {
var me = this;
me.clearButton = $(
'
'
));
};
PacketMessagePanel.prototype.pushMessage = function(msg) {
var $b = $(this.el).find('tbody');
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 = msg.timestamp? Utils.HHMMSS(msg.timestamp) : '';
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 = Utils.linkToMap(source, overlay, attrs);
} else {
link = '
' + overlay + '
'
}
// Linkify source based on what it is (vessel or HAM callsign)
source = msg.mode === 'AIS'?
Utils.linkifyVessel(source) : Utils.linkifyCallsign(source);
$b.append($(
'
'
));
};
PageMessagePanel.prototype.pushMessage = function(msg) {
// Get color from the message, default to white
var color = msg.hasOwnProperty('color')? msg.color : '#FFF';
// Get channel from the message (FLEX only)
var channel = msg.hasOwnProperty('channel')? '/' + msg.channel : '';
// Append message header (address, time, etc)
var $b = $(this.el).find('tbody');
$b.append($(
'
' +
'
' + msg.address + '
' +
'
' + msg.mode + msg.baud + channel + '
' +
'
' + Utils.HHMMSS(msg.timestamp) + '
' +
'
'
).css('background-color', color).css('color', '#000'));
// Append message body (text)
if (msg.hasOwnProperty('message')) {
$b.append($(
'
' +
Utils.htmlEscape(msg.message) +
'
'
));
}
// Jump list to the last received message
this.scrollToBottom();
};
$.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.modes = ['HFDL', 'VDL2', 'ADSB', 'ACARS'];
}
HfdlMessagePanel.prototype = Object.create(MessagePanel.prototype);
HfdlMessagePanel.prototype.supportsMessage = function(message) {
return this.modes.indexOf(message['mode']) >= 0;
};
HfdlMessagePanel.prototype.render = function() {
$(this.el).append($(
'
' +
'
' +
'
Time
' +
'
Flight
' +
'
Aircraft
' +
'
Data
' +
'
' +
'' +
'
'
));
};
HfdlMessagePanel.prototype.pushMessage = function(msg) {
var bcolor = msg.color? msg.color : '#000';
var fcolor = msg.color? '#000' : '#FFF';
var data = msg.type? msg.type : '';
// Only linkify ICAO-compliant flight IDs
var flight =
!msg.flight? ''
: !msg.flight.match(/^[A-Z]{3}[0-9]+[A-Z]*$/)? msg.flight
: Utils.linkifyFlight(msg.flight);
var aircraft =
msg.aircraft? Utils.linkifyFlight(msg.aircraft)
: msg.icao? Utils.linkifyIcao(msg.icao)
: '';
var tstamp =
msg.msgtime? '' + msg.msgtime + ''
: msg.timestamp? Utils.HHMMSS(msg.timestamp)
: '';
// Add location, altitude, speed, etc
var data = '';
if (msg.lat && msg.lon) {
data += '@' + msg.lat.toFixed(4) + ',' + msg.lon.toFixed(4);
}
if (msg.altitude) data += ' ⤒' + msg.altitude + 'ft';
if (msg.vspeed>0) data += ' ↗' + msg.vspeed + 'ft/m';
if (msg.vspeed<0) data += ' ↘' + (-msg.vspeed) + 'ft/m';
if (msg.speed) data += ' →' + msg.speed + 'kt';
if (msg.origin) data += ' ↰' + msg.origin;
if (msg.destination) data += ' ↳' + msg.destination;
// If no location data in the message, use message type as data
if (!data.length && msg.type) data = msg.type;
// Make data point to the map
if (data.length && msg.mapid) data = Utils.linkToMap(msg.mapid, data);
// Append report
var $b = $(this.el).find('tbody');
$b.append($(
'
' +
'
' + tstamp + '
' +
'
' + flight + '
' +
'
' + aircraft + '
' +
'
' + data + '
' +
'
'
).css('background-color', bcolor).css('color', fcolor));
// Append messsage if present
if (msg.message) {
$b.append($(
'
' + Utils.htmlEscape(msg.message) + '
'
));
}
// Jump list to the last received message
this.scrollToBottom();
};
$.fn.hfdlMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new HfdlMessagePanel(this));
}
return this.data('panel');
};
AdsbMessagePanel = function(el) {
MessagePanel.call(this, el);
this.clearButton.css('display', 'none');
}
AdsbMessagePanel.prototype = Object.create(MessagePanel.prototype);
AdsbMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'ADSB-LIST';
};
AdsbMessagePanel.prototype.render = function() {
$(this.el).append($(
'
' +
'
' +
'
Flight
' +
'
Aircraft
' +
'
Squawk
' +
'
Dist
' +
'
Alt (ft)
' +
'
Speed (kt)
' +
'
Signal
' +
'
' +
'' +
'
'
));
};
AdsbMessagePanel.prototype.pushMessage = function(msg) {
// Must have list of aircraft
if (!msg.aircraft) return;
// Create new table body
var body = '';
var odd = false;
msg.aircraft.forEach(entry => {
// Signal strength
var rssi = entry.rssi? entry.rssi + ' dB' : '';
// Flight identificators
var flight =
entry.flight? Utils.linkifyFlight(entry.flight)
: '';
var aircraft =
entry.aircraft? Utils.linkifyFlight(entry.aircraft)
: entry.icao? Utils.linkifyIcao(entry.icao)
: '';
// Altitude and climb / descent
var alt = entry.altitude? '' + entry.altitude : '';
if (entry.vspeed) {
var vspeed = entry.vspeed;
vspeed = vspeed>0? vspeed + '↑' : (-vspeed) + '↓';
alt = vspeed + ' '.repeat(6 - alt.length) + alt;
}
// Speed and direction
var speed = entry.speed? '' + entry.speed : '';
if (entry.course) {
var dir = Utils.degToCompass(entry.course);
speed = dir + ' '.repeat(5 - speed.length) + speed;
}
// Replace squawk with emergency status, if present
var squawk = entry.squawk? entry.squawk : '';
if (entry.emergency && (entry.emergency!=='NONE')) {
squawk = '
'
+ entry.emergency + '
';
}
// Compute distance to the receiver
var distance = '';
var receiver_pos = Utils.getReceiverPos();
if (receiver_pos && entry.lat && entry.lon) {
var id = entry.icao? entry.icao
: entry.aircraft? entry.aircraft
: entry.flight? entry.flight
: null;
distance = Utils.distanceKm(entry, receiver_pos) + ' km';
if (id) distance = Utils.linkToMap(id, distance);
}
body += '
'
);
};
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('timestamp')? Utils.HHMMSS(msg.timestamp) : '';
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
this.scrollToBottom();
};
$.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 = Object.create(MessagePanel.prototype);
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 + '
'));
// this.scrollToBottom();
}
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 = '
'));
$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 = Object.create(MessagePanel.prototype);
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 + '
'));
// this.scrollToBottom();
}
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 = '
'));
this.scrollToBottom();
// 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' +
'
' +
'
Freq
' +
'
Text
' +
'
' +
'' +
''
));
};
CwSkimmerMessagePanel.prototype.pushMessage = function(msg) {
// Must have some text
if (!msg.text) return;
// Clear cache if requested
if (msg.changed) this.texts = [];
// Current time
var now = Date.now();
// Modify or add a new entry
var j = this.texts.findIndex(function(x) { return x.freq >= msg.freq });
if (j < 0) {
// Append a new entry
this.texts.push({ freq: msg.freq, text: msg.text, ts: now });
} else if (this.texts[j].freq == msg.freq) {
// Update existing entry
this.texts[j].text = (this.texts[j].text + msg.text).slice(-64);
this.texts[j].ts = now;
} else {
// Insert a new entry
this.texts.splice(j, 0, { freq: msg.freq, text: msg.text, ts: now });
}
// Generate table body
var body = '';
for (var j = 0 ; j < this.texts.length ; j++) {
// Limit the lifetime of entries depending on their length
var cutoff = 5000 * this.texts[j].text.length;
if (now - this.texts[j].ts >= cutoff) {
this.texts.splice(j--, 1);
} else {
var f = Math.floor(this.texts[j].freq / 100.0) / 10.0;
body +=
'
' + f.toFixed(1) +
'
' + this.texts[j].text + '
\n';
}
}
// Assign new table body
$(this.el).find('tbody').html(body);
};
$.fn.cwskimmerMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new CwSkimmerMessagePanel(this));
}
return this.data('panel');
};