2014-09-22 18:28:51 +00:00
|
|
|
var FGFS = {};
|
|
|
|
|
|
|
|
FGFS.Property = function(path) {
|
|
|
|
if (path == null)
|
|
|
|
throw new Error('path is null');
|
|
|
|
// path must be absolute
|
|
|
|
if (path.lastIndexOf("/", 0) !== 0)
|
|
|
|
path = "/".concat(path);
|
|
|
|
|
|
|
|
this.path = path.lastIndexOf("/", 0) === 0 ? path : "/".concat(path);
|
|
|
|
this.value = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.Property.prototype.getPath = function() {
|
|
|
|
return this.path;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.Property.prototype.setValue = function(val) {
|
|
|
|
// TODO: send this out
|
|
|
|
this.value = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.Property.prototype.hasValue = function() {
|
|
|
|
return this.value != null;
|
|
|
|
}
|
|
|
|
|
2014-11-06 10:49:29 +00:00
|
|
|
FGFS.Property.prototype.getValue = function(dflt) {
|
|
|
|
if( this.value != null ) return this.value;
|
|
|
|
if( dflt != null ) return dflt;
|
|
|
|
return null;
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
2014-11-06 10:49:29 +00:00
|
|
|
FGFS.Property.prototype.getStringValue = function(dflt) {
|
|
|
|
if( this.value != null ) return this.value.toString();
|
|
|
|
if( dflt != null ) return dflt.toString();
|
|
|
|
return null;
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
2014-11-06 10:49:29 +00:00
|
|
|
FGFS.Property.prototype.getNumValue = function(dflt) {
|
|
|
|
var reply = this.value != null ? Number(this.value) : null;
|
|
|
|
if( reply == null && dflt != null ) reply = dflt;
|
2014-09-22 18:28:51 +00:00
|
|
|
return (isNaN(reply) || reply == null) ? 0 : reply;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.PropertyListener = function(arg) {
|
|
|
|
this._listeners = {};
|
|
|
|
this._nextId = 1;
|
|
|
|
this._ws = new WebSocket('ws://' + location.host + '/PropertyListener');
|
|
|
|
|
|
|
|
function defaultOnClose(ev) {
|
|
|
|
|
|
|
|
var msg = 'Lost connection to FlightGear. Please reload this page and/or restart FlightGear.';
|
|
|
|
alert(msg);
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
function defaultOnError(ev) {
|
|
|
|
var msg = 'Error communicating with FlightGear. Please reload this page and/or restart FlightGear.';
|
|
|
|
alert(msg);
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._ws.propertyListener = this;
|
|
|
|
this._ws.onopen = arg.onopen;
|
|
|
|
this._ws.onclose = defaultOnClose;
|
|
|
|
this._ws.onerror = defaultOnError;
|
|
|
|
this._ws.onmessage = function(ev) {
|
|
|
|
try {
|
|
|
|
this.propertyListener.fire(JSON.parse(ev.data));
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.fire = function(node) {
|
|
|
|
this._listeners[node.path].forEach(function(callback) {
|
|
|
|
callback.cb(node);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
this.addProperty = function(prop, callback) {
|
|
|
|
var path = prop.getPath();
|
|
|
|
|
|
|
|
var o = this._listeners[path];
|
|
|
|
var newProperty = false;
|
|
|
|
if (typeof (o) == 'undefined') {
|
|
|
|
newProperty = true;
|
|
|
|
o = [];
|
|
|
|
this._listeners[path] = o;
|
|
|
|
}
|
|
|
|
|
|
|
|
o.push({
|
|
|
|
cb : callback,
|
|
|
|
"prop" : prop,
|
|
|
|
id : this._nextId++
|
|
|
|
});
|
|
|
|
|
|
|
|
if (newProperty) {
|
|
|
|
this._ws.send(JSON.stringify({
|
|
|
|
command : 'addListener',
|
|
|
|
node : path
|
|
|
|
}));
|
|
|
|
this._ws.send(JSON.stringify({
|
|
|
|
command : 'get',
|
|
|
|
node : path
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
return this._nextId;
|
|
|
|
};
|
|
|
|
|
|
|
|
this.removeProperty = function(prop) {
|
|
|
|
throw new Error('removeProperty not yet implemented');
|
|
|
|
};
|
|
|
|
|
2014-09-25 20:50:52 +00:00
|
|
|
this.setProperty = function( path, val ) {
|
|
|
|
this._ws.send(JSON.stringify({
|
|
|
|
command : 'set',
|
|
|
|
node : path,
|
|
|
|
value: val
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// expects:
|
|
|
|
// [
|
|
|
|
// [ "key", "/fg/property/path" ],
|
|
|
|
// [ "key", "/another/fg/property/path" ],
|
|
|
|
// ]
|
|
|
|
FGFS.PropertyMirror = function(mirroredProperties) {
|
|
|
|
var mirror = {}
|
|
|
|
|
|
|
|
for( var i = 0; i < mirroredProperties.length; i++ ) {
|
|
|
|
var pair = mirroredProperties[i];
|
|
|
|
mirror[pair[0]] = new FGFS.Property(pair[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
var listener = new FGFS.PropertyListener({
|
|
|
|
onopen : function() {
|
|
|
|
var keys = Object.keys(mirror);
|
|
|
|
for (var i = 0; i < keys.length; i++) {
|
|
|
|
listener.addProperty(mirror[keys[i]], function(n) {
|
|
|
|
if (typeof (n.value) != 'undefined')
|
|
|
|
this.prop.value = n.value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.listener = listener;
|
|
|
|
this.mirror = mirror;
|
|
|
|
|
|
|
|
this.getNode = function(id) {
|
|
|
|
return this.mirror[id];
|
|
|
|
};
|
|
|
|
|
2014-09-25 20:50:52 +00:00
|
|
|
this.setProperty = function( key, value ) {
|
|
|
|
var node = this.mirror[key];
|
|
|
|
this.listener.setProperty( node.path, value );
|
|
|
|
}
|
|
|
|
|
2014-09-22 18:28:51 +00:00
|
|
|
// TODO: ugly static variable, change this!
|
|
|
|
FGFS.NodeProvider.mirror = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.interpolate = function(x, pairs) {
|
|
|
|
var n = pairs.length - 1;
|
|
|
|
if (x <= pairs[0][0]) {
|
|
|
|
return pairs[0][1];
|
|
|
|
}
|
|
|
|
if (x >= pairs[n][0]) {
|
|
|
|
return pairs[n][1];
|
|
|
|
}
|
|
|
|
for (var i = 0; i < n; i = i + 1) {
|
|
|
|
if (x > pairs[i][0] && x <= pairs[i + 1][0]) {
|
|
|
|
var x1 = pairs[i][0];
|
|
|
|
var x2 = pairs[i + 1][0];
|
|
|
|
var y1 = pairs[i][1];
|
|
|
|
var y2 = pairs[i + 1][1];
|
|
|
|
return (x - x1) / (x2 - x1) * (y2 - y1) + y1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pairs[i][1];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FGFS.NodeProvider = {
|
|
|
|
mirror: null,
|
|
|
|
getNode: function(id) {
|
|
|
|
if( this.mirror == null )
|
|
|
|
throw new Error('no nodes without a mirror');
|
|
|
|
return this.mirror.getNode(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.InputValue = function(arg) {
|
|
|
|
this.__proto__ = FGFS.NodeProvider;
|
|
|
|
this.value = 0;
|
|
|
|
this.property = null;
|
|
|
|
this.offset = 0;
|
|
|
|
this.scale = 1;
|
|
|
|
this.interpolationTable = null;
|
|
|
|
this.min = null;
|
|
|
|
this.max = null;
|
2014-09-24 19:07:19 +00:00
|
|
|
this.precision = 4;
|
2014-11-06 10:49:29 +00:00
|
|
|
this.func = null;
|
|
|
|
|
|
|
|
if( arg.precision != null ) this.precision = arg.precision;
|
2014-09-22 18:28:51 +00:00
|
|
|
|
|
|
|
this.getValue = function() {
|
|
|
|
var value = this.value;
|
|
|
|
if (this.property != null)
|
|
|
|
value = this.property.getNumValue();
|
2014-11-06 10:49:29 +00:00
|
|
|
else if( this.func != null )
|
|
|
|
value = this.func();
|
2014-09-22 18:28:51 +00:00
|
|
|
|
|
|
|
if (this.interpolationTable != null && this.interpolationTable.length > 0)
|
2014-09-24 19:07:19 +00:00
|
|
|
return FGFS.interpolate(value, this.interpolationTable).toPrecision(this.precision);
|
2014-09-22 18:28:51 +00:00
|
|
|
|
|
|
|
value = value * this.scale + this.offset;
|
|
|
|
if( this.min != null && value < this.min )
|
2014-09-24 19:07:19 +00:00
|
|
|
return this.min.toPrecision(this.precision);
|
2014-11-06 10:49:29 +00:00
|
|
|
if( this.max != null && value > this.max )
|
2014-09-24 19:07:19 +00:00
|
|
|
return this.max.toPrecision(this.precision);
|
|
|
|
return value.toPrecision(this.precision);
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof (arg) == 'number') {
|
|
|
|
this.value = Number(arg);
|
|
|
|
} else if (typeof (arg) == 'string') {
|
|
|
|
this.property = this.getNode(arg);
|
|
|
|
} else if (typeof (arg) == 'object') {
|
|
|
|
|
|
|
|
if (typeof (arg.property) != 'undefined')
|
|
|
|
this.property = this.getNode(arg.property);
|
|
|
|
|
|
|
|
if (typeof (arg.value) != 'undefined')
|
|
|
|
this.value = Number(arg.value);
|
|
|
|
|
|
|
|
if (typeof (arg.scale) != 'undefined')
|
|
|
|
this.scale = Number(arg.scale);
|
|
|
|
|
|
|
|
if (typeof (arg.offset) != 'undefined')
|
|
|
|
this.offset = Number(arg.offset);
|
|
|
|
|
2014-11-06 10:49:29 +00:00
|
|
|
if (typeof (arg.min) != 'undefined')
|
|
|
|
this.min = Number(arg.min);
|
|
|
|
|
|
|
|
if (typeof (arg.max) != 'undefined')
|
|
|
|
this.max = Number(arg.max);
|
|
|
|
|
|
|
|
if (typeof (arg.func) != 'undefined')
|
|
|
|
this.func = arg.func;
|
|
|
|
|
2014-09-22 18:28:51 +00:00
|
|
|
if (typeof (arg.interpolation == 'string')) {
|
|
|
|
|
|
|
|
var target = this;
|
|
|
|
if (typeof (arg.interpolation) == 'string') {
|
|
|
|
this.interpolationTable = [];
|
|
|
|
// load interpolation table
|
|
|
|
$.ajax({
|
|
|
|
type : "GET",
|
|
|
|
dataType : "XML",
|
|
|
|
url : arg.interpolation,
|
|
|
|
success : function(data, status, xhr) {
|
|
|
|
var entries = $(data).find("entry");
|
|
|
|
$(data).find("entry").each(function() {
|
|
|
|
var ind = $(this).find("ind").text();
|
|
|
|
var dep = $(this).find("dep").text();
|
|
|
|
target.interpolationTable.push([ Number(ind), Number(dep) ]);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error : function(xhr, status, msg) {
|
|
|
|
alert(status + " while reading '" + arg.interpolation + "': " + msg.toString());
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2014-11-06 10:49:29 +00:00
|
|
|
} else if (typeof (arg) == 'function') {
|
|
|
|
this.func = arg;
|
2014-09-22 18:28:51 +00:00
|
|
|
} else {
|
|
|
|
throw new Error('Dont know how to handle "' + arg.toString() + '"' );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.Transform = function(arg) {
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.RotateTransform = function(arg) {
|
|
|
|
this.__proto__ = new FGFS.Transform(arg);
|
|
|
|
|
|
|
|
this.a = new FGFS.InputValue(arg.a);
|
|
|
|
this.x = new FGFS.InputValue(arg.x);
|
|
|
|
this.y = new FGFS.InputValue(arg.y);
|
|
|
|
|
|
|
|
this.makeTransform = function() {
|
|
|
|
return {
|
|
|
|
type : "rotate",
|
|
|
|
props : {
|
|
|
|
a : this.a.getValue(),
|
|
|
|
x : this.x.getValue(),
|
|
|
|
y : this.y.getValue(),
|
|
|
|
context : this,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.TranslateTransform = function(arg) {
|
|
|
|
this.__proto__ = new FGFS.Transform(arg);
|
|
|
|
|
|
|
|
this.x = new FGFS.InputValue(arg.x);
|
|
|
|
this.y = new FGFS.InputValue(arg.y);
|
|
|
|
|
|
|
|
this.makeTransform = function() {
|
|
|
|
return {
|
|
|
|
type : "translate",
|
|
|
|
props : {
|
|
|
|
x : this.x.getValue(),
|
|
|
|
y : this.y.getValue(),
|
|
|
|
context : this,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.Animation = function(arg) {
|
|
|
|
this.element = arg.element;
|
|
|
|
this.type = arg.type;
|
2014-10-13 08:39:16 +00:00
|
|
|
this._element = null;
|
2014-09-22 18:28:51 +00:00
|
|
|
|
|
|
|
this.__proto__.update = function(svg) {
|
2014-10-13 08:39:16 +00:00
|
|
|
if (null == this._element) {
|
2014-09-22 18:28:51 +00:00
|
|
|
this._element = $(svg).find(this.element);
|
2014-10-13 08:39:16 +00:00
|
|
|
if( 0 == this._element.length ) {
|
|
|
|
this._element = null;
|
|
|
|
return;
|
|
|
|
}
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this._element.fgAnimateSVG(this.makeAnimation());
|
|
|
|
}
|
|
|
|
|
|
|
|
this.__proto__.makeAnimation = function() {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.TransformAnimation = function(arg) {
|
|
|
|
this.__proto__ = new FGFS.Animation(arg);
|
|
|
|
|
|
|
|
this.transforms = [];
|
|
|
|
for (var i = 0; i < arg.transforms.length; i++) {
|
|
|
|
var t = arg.transforms[i];
|
|
|
|
var transform = null;
|
|
|
|
switch (t.type) {
|
|
|
|
case 'rotate':
|
|
|
|
transform = new FGFS.RotateTransform(t);
|
|
|
|
break;
|
|
|
|
case 'translate':
|
|
|
|
transform = new FGFS.TranslateTransform(t);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (transform != null)
|
|
|
|
this.transforms[this.transforms.length] = transform;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.makeAnimation = function() {
|
|
|
|
var reply = {
|
|
|
|
type : 'transform',
|
|
|
|
transforms : [],
|
|
|
|
};
|
|
|
|
|
|
|
|
for (var i = 0; i < this.transforms.length; i++)
|
|
|
|
reply.transforms[reply.transforms.length] = this.transforms[i].makeTransform();
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
}
|
2014-11-06 10:49:29 +00:00
|
|
|
FGFS.TextAnimation = function(arg) {
|
|
|
|
this.__proto__ = new FGFS.Animation(arg);
|
|
|
|
this.text = new FGFS.InputValue(arg.text);
|
|
|
|
|
|
|
|
this.makeAnimation = function() {
|
|
|
|
var reply = {
|
|
|
|
type: 'text',
|
|
|
|
text: this.text.getValue(),
|
|
|
|
};
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
}
|
2014-09-22 18:28:51 +00:00
|
|
|
|
|
|
|
FGFS.Instrument = function(arg) {
|
|
|
|
|
|
|
|
// load svg into target
|
|
|
|
$.ajax({
|
|
|
|
type : "GET",
|
|
|
|
url : arg.src,
|
|
|
|
async: false,
|
|
|
|
dataType : "xml",
|
|
|
|
context: this,
|
|
|
|
success : function(xml, status, xhr) {
|
|
|
|
this.svg = $(xml).find("svg")[0];
|
|
|
|
},
|
|
|
|
error : function(xhr, status, msg) {
|
|
|
|
alert(status + " while reading '" + arg.src + "': " + msg.toString());
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
this.animations = [];
|
|
|
|
for (var i = 0; i < arg.animations.length; i++) {
|
|
|
|
var a = arg.animations[i];
|
|
|
|
var animation = null;
|
|
|
|
|
|
|
|
switch (a.type) {
|
|
|
|
case 'transform':
|
|
|
|
animation = new FGFS.TransformAnimation(a);
|
|
|
|
break;
|
|
|
|
|
2014-11-06 10:49:29 +00:00
|
|
|
case 'text':
|
|
|
|
animation = new FGFS.TextAnimation(a);
|
|
|
|
break;
|
|
|
|
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
if (animation != null)
|
|
|
|
this.animations[this.animations.length] = animation;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.__proto__.update = function() {
|
|
|
|
// noop if svg is not (yet) loaded
|
|
|
|
|
|
|
|
if (typeof (this.svg) == 'undefined')
|
|
|
|
return;
|
|
|
|
|
|
|
|
var svg = this.svg;
|
|
|
|
|
|
|
|
this.animations.forEach(function(animation) {
|
|
|
|
animation.update(svg);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FGFS.FGPanel = function( propUrl )
|
|
|
|
{
|
|
|
|
|
|
|
|
var defaultProps = {
|
|
|
|
instrumentSelector: ".instrument",
|
|
|
|
instrumentDataKey: "fgpanel-instrument",
|
|
|
|
updateInterval: 50,
|
|
|
|
};
|
|
|
|
|
|
|
|
this.props = Object.create( defaultProps );
|
|
|
|
|
|
|
|
if( typeof(propUrl) == 'string' ) {
|
|
|
|
$.ajax({
|
|
|
|
type : "GET",
|
|
|
|
url : propUrl,
|
|
|
|
async: false,
|
|
|
|
dataType : "json",
|
|
|
|
context: this,
|
|
|
|
success : function(data, status, xhr) {
|
|
|
|
data.__proto__ = defaultProps;
|
|
|
|
this.props = data;
|
|
|
|
},
|
|
|
|
error : function(xhr, status, msg) {
|
|
|
|
alert(status + " while reading '" + propUrl + "': " + msg.toString());
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-09-25 20:50:52 +00:00
|
|
|
this.mirror = new FGFS.PropertyMirror(this.props.propertyMirror);
|
2014-09-22 18:28:51 +00:00
|
|
|
|
2014-09-24 19:07:19 +00:00
|
|
|
this.instruments = $(this.props.instrumentSelector).fgLoadInstruments(this.props.instrumentDataKey);
|
2014-09-22 18:28:51 +00:00
|
|
|
|
2014-09-24 19:07:19 +00:00
|
|
|
this.update = function() {
|
|
|
|
for( var i = 0; i < this.instruments.length; i++ ) {
|
|
|
|
this.instruments[i].update();
|
|
|
|
}
|
|
|
|
window.setTimeout( $.proxy(this.update,this), this.props.updateInterval );
|
|
|
|
}
|
|
|
|
|
2014-09-25 20:50:52 +00:00
|
|
|
this.setProperty = function( key, value ) {
|
|
|
|
this.mirror.setProperty( key, value );
|
|
|
|
}
|
|
|
|
|
2014-09-24 19:07:19 +00:00
|
|
|
this.update();
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
|
2014-09-25 20:50:52 +00:00
|
|
|
|
2014-09-22 18:28:51 +00:00
|
|
|
$(document).ready(function() {
|
|
|
|
var hasFGPanel = $("body").data("fgpanel");
|
|
|
|
if( hasFGPanel ) {
|
|
|
|
var panelProps = $("body").data("fgpanel-props");
|
2014-09-25 20:50:52 +00:00
|
|
|
window.fgPanel = new FGFS.FGPanel( panelProps == null ? "fgpanel.json" : panelProps );
|
2014-09-22 18:28:51 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|