2015-01-25 12:02:20 +00:00
|
|
|
require.config({
|
|
|
|
baseUrl : '.',
|
|
|
|
paths : {
|
|
|
|
jquery : '3rdparty/jquery/jquery-1.11.2.min',
|
|
|
|
'jquery-ui' : '3rdparty/jquery/ui',
|
|
|
|
knockout : '3rdparty/knockout/knockout-3.2.0',
|
|
|
|
kojqui : '3rdparty/knockout-jqueryui',
|
|
|
|
sprintf : '3rdparty/sprintf/sprintf.min',
|
|
|
|
leaflet : '3rdparty/leaflet-0.7.3/leaflet',
|
|
|
|
text : '3rdparty/require/text',
|
|
|
|
flot : '3rdparty/flot/jquery.flot',
|
2015-02-26 15:21:21 +00:00
|
|
|
flotresize : '3rdparty/flot/jquery.flot.resize',
|
2015-03-01 17:22:56 +00:00
|
|
|
flottime : '3rdparty/flot/jquery.flot.time',
|
2015-02-06 12:08:32 +00:00
|
|
|
fgcommand : 'lib/fgcommand',
|
2015-03-04 11:16:17 +00:00
|
|
|
sammy: '3rdparty/sammy-latest.min'
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
require([
|
2015-03-04 11:16:17 +00:00
|
|
|
'knockout', 'jquery','sammy', 'themeswitch', 'kojqui/button', 'flot', 'leaflet'
|
|
|
|
], function(ko, jquery, Sammy) {
|
2015-01-25 12:02:20 +00:00
|
|
|
|
|
|
|
function KnockProps(aliases) {
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.initWebsocket = function() {
|
|
|
|
self.ws = new WebSocket('ws://' + location.host + '/PropertyListener');
|
2015-01-25 12:02:20 +00:00
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.ws.onclose = function(ev) {
|
|
|
|
var msg = 'Lost connection to FlightGear. Should I try to reconnect?';
|
|
|
|
if (confirm(msg)) {
|
|
|
|
// try reconnect
|
|
|
|
self.initWebsocket();
|
|
|
|
} else {
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
2015-02-17 12:19:35 +00:00
|
|
|
|
|
|
|
self.ws.onerror = function(ev) {
|
|
|
|
var msg = 'Error communicating with FlightGear. Please reload this page and/or restart FlightGear.';
|
|
|
|
alert(msg);
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.ws.onmessage = function(ev) {
|
|
|
|
try {
|
|
|
|
self.fire(JSON.parse(ev.data));
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.openCache = [];
|
|
|
|
self.ws.onopen = function(ev) {
|
|
|
|
// send subscriptions when the socket is open
|
|
|
|
var c = self.openCache;
|
|
|
|
delete self.openCache;
|
|
|
|
c.forEach(function(e) {
|
|
|
|
self.addListener(e.prop, e.koObservable);
|
|
|
|
});
|
2015-03-01 17:22:56 +00:00
|
|
|
for ( var p in self.listeners) {
|
|
|
|
self.addListener(p, self.listeners[p]);
|
2015-02-17 12:19:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.initWebsocket();
|
|
|
|
|
|
|
|
self.fire = function(json) {
|
2015-03-02 14:48:09 +00:00
|
|
|
var value = json.value;
|
|
|
|
var listeners = self.listeners[json.path] || [];
|
|
|
|
listeners.forEach(function(koObservable) {
|
|
|
|
koObservable(value)
|
|
|
|
});
|
2015-01-25 12:02:20 +00:00
|
|
|
koObservable(json.value);
|
|
|
|
}
|
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
function resolvePropertyPath(self, pathOrAlias) {
|
|
|
|
if (pathOrAlias in self.aliases)
|
|
|
|
return self.aliases[pathOrAlias];
|
|
|
|
if (pathOrAlias.charAt(0) == '/')
|
|
|
|
return pathOrAlias;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.listeners = {}
|
2015-01-25 12:02:20 +00:00
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
self.removeListener = function(pathOrAlias, koObservable) {
|
|
|
|
var path = resolvePropertyPath(self, pathOrAlias);
|
|
|
|
if (path == null) {
|
|
|
|
console.log("can't remove listener for " + pathOrAlias + ": unknown alias or invalid path.");
|
|
|
|
return self;
|
2015-03-01 17:22:56 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
var listeners = self.listeners[path] || [];
|
|
|
|
var idx = listeners.indexOf(koObservable);
|
|
|
|
if (idx == -1) {
|
|
|
|
console.log("can't remove listener for " + path + ": not a listener.");
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
listeners.splice(idx, 1);
|
2015-03-01 17:22:56 +00:00
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
if (0 == listeners.length) {
|
|
|
|
self.ws.send(JSON.stringify({
|
|
|
|
command : 'removeListener',
|
|
|
|
node : path
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
2015-03-01 17:22:56 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.addListener = function(alias, koObservable) {
|
2015-01-25 12:02:20 +00:00
|
|
|
if (self.openCache) {
|
|
|
|
// socket not yet open, just cache the request
|
|
|
|
self.openCache.push({
|
2015-02-17 12:19:35 +00:00
|
|
|
"prop" : alias,
|
2015-01-25 12:02:20 +00:00
|
|
|
"koObservable" : koObservable
|
|
|
|
});
|
2015-03-02 14:48:09 +00:00
|
|
|
return self;
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
var path = resolvePropertyPath(self, alias);
|
|
|
|
if (path == null) {
|
|
|
|
console.log("can't listen to " + alias + ": unknown alias or invalid path.");
|
|
|
|
return self;
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
var listeners = self.listeners[path] = (self.listeners[path] || []);
|
|
|
|
if (listeners.indexOf(koObservable) != -1) {
|
|
|
|
console.log("won't listen to " + path + ": duplicate.");
|
|
|
|
return self;
|
|
|
|
}
|
2015-01-25 12:02:20 +00:00
|
|
|
|
2015-03-02 14:48:09 +00:00
|
|
|
listeners.push(koObservable);
|
|
|
|
|
|
|
|
if (1 == listeners.length) {
|
|
|
|
self.ws.send(JSON.stringify({
|
|
|
|
command : 'addListener',
|
|
|
|
node : path
|
|
|
|
}));
|
|
|
|
}
|
2015-01-25 12:02:20 +00:00
|
|
|
self.ws.send(JSON.stringify({
|
|
|
|
command : 'get',
|
|
|
|
node : path
|
|
|
|
}));
|
2015-03-02 14:48:09 +00:00
|
|
|
|
|
|
|
return self;
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.aliases = aliases || {};
|
|
|
|
self.setAliases = function(arg) {
|
2015-01-25 12:02:20 +00:00
|
|
|
arg.forEach(function(a) {
|
|
|
|
self.aliases[a[0]] = a[1];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.props = {};
|
2015-01-25 12:02:20 +00:00
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.get = function(target, prop) {
|
2015-01-25 12:02:20 +00:00
|
|
|
if (self.props[prop]) {
|
|
|
|
return self.props[prop];
|
|
|
|
}
|
|
|
|
|
|
|
|
var p = (self.props[prop] = ko.pureComputed({
|
|
|
|
read : target,
|
|
|
|
write : function(newValue) {
|
2015-02-06 12:08:32 +00:00
|
|
|
if (newValue == target())
|
|
|
|
return;
|
2015-01-25 12:02:20 +00:00
|
|
|
target(newValue);
|
|
|
|
target.notifySubscribers(newValue);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
self.addListener(prop, p);
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
2015-02-06 12:08:32 +00:00
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.write = function(prop, value) {
|
2015-01-25 12:02:20 +00:00
|
|
|
var path = this.aliases[prop] || "";
|
|
|
|
if (path.length == 0) {
|
|
|
|
console.log("can't write " + prop + ": unknown alias.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.ws.send(JSON.stringify({
|
|
|
|
command : 'set',
|
|
|
|
node : path,
|
2015-02-06 12:08:32 +00:00
|
|
|
value : value
|
|
|
|
}));
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-02-17 12:19:35 +00:00
|
|
|
self.propsToObject = function(prop, map, result) {
|
2015-02-06 12:08:32 +00:00
|
|
|
result = result || {}
|
|
|
|
prop.children.forEach(function(prop) {
|
|
|
|
var target = map[prop.name] || null;
|
|
|
|
if (target) {
|
|
|
|
if (typeof (result[target]) === 'function') {
|
|
|
|
result[target](prop.value);
|
|
|
|
} else {
|
|
|
|
result[target] = prop.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ko.extenders.fgprop = function(target, prop) {
|
|
|
|
return ko.utils.knockprops.get(target, prop);
|
|
|
|
};
|
|
|
|
|
2015-03-10 20:48:11 +00:00
|
|
|
ko.extenders.fgPropertyGetSet = function(target,option) {
|
|
|
|
|
|
|
|
fgCommand.getPropertyValue(option, function(value) {
|
|
|
|
target(value);
|
|
|
|
}, self);
|
|
|
|
|
|
|
|
var p = ko.pureComputed({
|
|
|
|
read : target,
|
|
|
|
write : function(newValue) {
|
|
|
|
if (newValue == target())
|
|
|
|
return;
|
|
|
|
target(newValue);
|
|
|
|
target.notifySubscribers(newValue);
|
|
|
|
fgCommand.setPropertyValue(option, newValue );
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-25 12:02:20 +00:00
|
|
|
ko.utils.knockprops = new KnockProps();
|
|
|
|
|
|
|
|
ko.utils.knockprops.setAliases([
|
|
|
|
// time
|
|
|
|
[
|
|
|
|
"gmt", "/sim/time/gmt"
|
|
|
|
], [
|
|
|
|
"local-offset", "/sim/time/local-offset"
|
|
|
|
],
|
|
|
|
|
|
|
|
// flight
|
|
|
|
[
|
|
|
|
"pitch", "/orientation/pitch-deg"
|
|
|
|
], [
|
|
|
|
"roll", "/orientation/roll-deg"
|
|
|
|
], [
|
|
|
|
"heading", "/orientation/heading-magnetic-deg"
|
2015-02-16 13:51:54 +00:00
|
|
|
], [
|
|
|
|
"true-heading", "/orientation/heading-deg"
|
2015-01-25 12:02:20 +00:00
|
|
|
], [
|
|
|
|
"altitude", "/position/altitude-ft"
|
|
|
|
], [
|
|
|
|
"latitude", "/position/latitude-deg"
|
|
|
|
], [
|
|
|
|
"longitude", "/position/longitude-deg"
|
|
|
|
], [
|
|
|
|
"airspeed", "/velocities/airspeed-kt"
|
2015-02-16 13:51:54 +00:00
|
|
|
], [
|
|
|
|
"groundspeed", "/velocities/groundspeed-kt"
|
2015-01-25 12:02:20 +00:00
|
|
|
], [
|
|
|
|
"slip", "/instrumentation/slip-skid-ball/indicated-slip-skid"
|
|
|
|
], [
|
|
|
|
"cg", "/fdm/jsbsim/inertia/cg-x-in"
|
|
|
|
], [
|
|
|
|
"weight", "/fdm/jsbsim/inertia/weight-lbs"
|
|
|
|
],
|
|
|
|
// radio settings
|
|
|
|
[
|
2015-02-13 21:09:27 +00:00
|
|
|
"com1stn", "/instrumentation/comm/station-name"
|
|
|
|
], [
|
2015-01-25 12:02:20 +00:00
|
|
|
"com1use", "/instrumentation/comm/frequencies/selected-mhz"
|
|
|
|
], [
|
|
|
|
"com1sby", "/instrumentation/comm/frequencies/standby-mhz"
|
|
|
|
], [
|
|
|
|
"com1stn", "/instrumentation/comm/station-name"
|
2015-02-13 21:09:27 +00:00
|
|
|
], [
|
|
|
|
"com2stn", "/instrumentation/comm[1]/station-name"
|
2015-01-25 12:02:20 +00:00
|
|
|
], [
|
|
|
|
"com2use", "/instrumentation/comm[1]/frequencies/selected-mhz"
|
|
|
|
], [
|
|
|
|
"com2sby", "/instrumentation/comm[1]/frequencies/standby-mhz"
|
|
|
|
], [
|
|
|
|
"com2stn", "/instrumentation/comm[1]/station-name"
|
|
|
|
], [
|
|
|
|
"nav1use", "/instrumentation/nav/frequencies/selected-mhz"
|
|
|
|
], [
|
|
|
|
"nav1sby", "/instrumentation/nav/frequencies/standby-mhz"
|
|
|
|
], [
|
|
|
|
"nav1stn", "/instrumentation/nav/nav-id"
|
|
|
|
], [
|
|
|
|
"nav2use", "/instrumentation/nav[1]/frequencies/selected-mhz"
|
|
|
|
], [
|
|
|
|
"nav2sby", "/instrumentation/nav[1]/frequencies/standby-mhz"
|
|
|
|
], [
|
|
|
|
"nav2stn", "/instrumentation/nav[1]/nav-id"
|
|
|
|
], [
|
|
|
|
"adf1use", "/instrumentation/adf/frequencies/selected-khz"
|
|
|
|
], [
|
|
|
|
"adf1sby", "/instrumentation/adf/frequencies/standby-khz"
|
|
|
|
], [
|
|
|
|
"adf1stn", "/instrumentation/adf/ident"
|
|
|
|
], [
|
|
|
|
"dme1use", "/instrumentation/dme/frequencies/selected-mhz"
|
|
|
|
], [
|
|
|
|
"dme1dst", "/instrumentation/dme/indicated-distance-nm"
|
|
|
|
], [
|
|
|
|
"xpdrcod", "/instrumentation/transponder/id-code"
|
|
|
|
],
|
|
|
|
// weather
|
|
|
|
[
|
|
|
|
"ac-wdir", "/environment/wind-from-heading-deg"
|
|
|
|
], [
|
|
|
|
"ac-wspd", "/environment/wind-speed-kt"
|
|
|
|
], [
|
|
|
|
"ac-visi", "/environment/visibility-m"
|
|
|
|
], [
|
|
|
|
"ac-temp", "/environment/temperature-degc"
|
|
|
|
], [
|
|
|
|
"ac-dewp", "/environment/dewpoint-degc"
|
|
|
|
], [
|
|
|
|
"gnd-wdir", "/environment/config/boundary/entry/wind-from-heading-deg"
|
|
|
|
], [
|
|
|
|
"gnd-wspd", "/environment/config/boundary/entry/wind-speed-kt"
|
|
|
|
], [
|
|
|
|
"gnd-visi", "/environment/config/boundary/entry/visibility-m"
|
|
|
|
], [
|
|
|
|
"gnd-temp", "/environment/config/boundary/entry/temperature-degc"
|
|
|
|
], [
|
|
|
|
"gnd-dewp", "/environment/config/boundary/entry/dewpoint-degc"
|
2015-02-06 12:08:32 +00:00
|
|
|
], [
|
|
|
|
"metar-valid", "/environment/metar/valid"
|
2015-01-25 12:02:20 +00:00
|
|
|
],
|
|
|
|
]);
|
|
|
|
|
|
|
|
function PhiViewModel(props) {
|
|
|
|
var self = this;
|
|
|
|
self.props = props;
|
|
|
|
self.widgets = ko.observableArray([
|
2015-03-05 13:58:48 +00:00
|
|
|
"metar", "efis", "radiostack", "map"
|
2015-01-25 12:02:20 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
self.topics = [
|
|
|
|
'Aircraft', 'Environment', 'Map', 'Tools', 'Simulator', 'Help',
|
|
|
|
];
|
|
|
|
|
|
|
|
self.selectedTopic = ko.observable();
|
2015-03-04 11:16:17 +00:00
|
|
|
self.selectedSubtopic = ko.observable();
|
2015-01-25 12:02:20 +00:00
|
|
|
|
|
|
|
self.selectTopic = function(topic) {
|
2015-03-04 11:16:17 +00:00
|
|
|
location.hash = topic;
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
2015-02-22 20:18:23 +00:00
|
|
|
self.refresh = function() {
|
|
|
|
location.reload();
|
|
|
|
}
|
2015-03-04 11:16:17 +00:00
|
|
|
|
|
|
|
// Client-side routes
|
|
|
|
Sammy(function() {
|
|
|
|
this.get('#:topic', function() {
|
|
|
|
self.selectedTopic( this.params.topic );
|
|
|
|
self.selectedSubtopic('');
|
|
|
|
});
|
|
|
|
|
|
|
|
this.get('#:topic/:subtopic', function() {
|
|
|
|
self.selectedTopic( this.params.topic );
|
|
|
|
self.selectedSubtopic( this.params.subtopic );
|
|
|
|
});
|
|
|
|
// empty route
|
|
|
|
this.get('', function() {
|
|
|
|
this.app.runRoute( 'get', '#' + self.topics[0] );
|
|
|
|
});
|
|
|
|
}).run();
|
2015-01-25 12:02:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ko.components.register('Aircraft', {
|
|
|
|
require : 'topics/Aircraft'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('Environment', {
|
|
|
|
require : 'topics/Environment'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('Map', {
|
|
|
|
require : 'topics/Map'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('Tools', {
|
|
|
|
require : 'topics/Tools'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('Simulator', {
|
|
|
|
require : 'topics/Simulator'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('Help', {
|
|
|
|
require : 'topics/Help'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('map', {
|
|
|
|
require : 'widgets/map'
|
|
|
|
});
|
|
|
|
|
|
|
|
ko.components.register('radiostack', {
|
|
|
|
require : 'widgets/radiostack'
|
|
|
|
});
|
|
|
|
|
2015-03-05 13:58:48 +00:00
|
|
|
ko.components.register('metar', {
|
|
|
|
require : 'widgets/metar'
|
|
|
|
});
|
|
|
|
|
2015-01-25 12:02:20 +00:00
|
|
|
ko.components.register('efis', {
|
|
|
|
require : 'widgets/efis'
|
|
|
|
});
|
|
|
|
|
2015-02-18 11:31:00 +00:00
|
|
|
ko.components.register('stopwatch', {
|
|
|
|
require : 'widgets/Stopwatch'
|
|
|
|
});
|
|
|
|
|
2015-03-01 17:22:56 +00:00
|
|
|
ko.bindingHandlers.flotchart = {
|
|
|
|
init : function(element, valueAccessor, allBindings) {
|
|
|
|
// This will be called when the binding is first applied to an
|
|
|
|
// element
|
|
|
|
// Set up any initial state, event handlers, etc. here
|
|
|
|
var value = valueAccessor() || {};
|
|
|
|
|
|
|
|
if (value.hover && typeof (value.hover) === 'function') {
|
|
|
|
$(element).bind("plothover", function(event, pos, item) {
|
|
|
|
value.hover(pos, item);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
update : function(element, valueAccessor, allBindings) {
|
|
|
|
var value = valueAccessor() || {};
|
2015-03-02 14:48:09 +00:00
|
|
|
var data = ko.unwrap(value.data);
|
|
|
|
var options = ko.unwrap(value.options);
|
|
|
|
jquery.plot(element, data, options);
|
2015-03-01 17:22:56 +00:00
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
2015-03-03 16:13:52 +00:00
|
|
|
ko.applyBindings(new PhiViewModel(),document.getElementById('wrapper'));
|
2015-02-07 22:16:34 +00:00
|
|
|
|
2015-01-25 12:02:20 +00:00
|
|
|
});
|