From e929b182af9c365e1d1dad097eaf441faee256f4 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Mon, 22 Sep 2014 20:28:51 +0200 Subject: [PATCH] WebPanel: Move libraries out of aircraft to common folder --- webgui/lib/fgfs.js | 435 +++++++++++++++++++++++++++++++++ webgui/lib/jquery.fganimate.js | 83 +++++++ 2 files changed, 518 insertions(+) create mode 100644 webgui/lib/fgfs.js create mode 100644 webgui/lib/jquery.fganimate.js diff --git a/webgui/lib/fgfs.js b/webgui/lib/fgfs.js new file mode 100644 index 000000000..b66f718af --- /dev/null +++ b/webgui/lib/fgfs.js @@ -0,0 +1,435 @@ +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; +} + +FGFS.Property.prototype.getValue = function(val) { + return this.value; +} + +FGFS.Property.prototype.getStringValue = function(val) { + return this.value == null ? null : this.value.toString(); +} + +FGFS.Property.prototype.getNumValue = function(val) { + var reply = this.value == null ? null : Number(this.value); + 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'); + }; + +} + +// 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]; + }; + + // 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; + + this.getValue = function() { + var value = this.value; + if (this.property != null) + value = this.property.getNumValue(); + + if (this.interpolationTable != null && this.interpolationTable.length > 0) + return FGFS.interpolate(value, this.interpolationTable); + + value = value * this.scale + this.offset; + if( this.min != null && value < this.min ) + return this.min; + if( this.max != null && value > this.min ) + return this.max; + return value; + } + + 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); + + 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()); + }, + }); + } + } + } 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; + + this.__proto__.update = function(svg) { + var t = typeof (this._element); + if (typeof (this._element) == 'undefined') { + this._element = $(svg).find(this.element); + } + + 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; + } +} + +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; + + } + 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()); + }, + }); + } + + var mirror = new FGFS.PropertyMirror(this.props.propertyMirror); + + var instruments = $(this.props.instrumentSelector).fgLoadInstruments(this.props.instrumentDataKey); + + window.setInterval( function() { + instruments.forEach(function(instrument) { + instrument.update(); + }); + }, this.props.updateInterval ); +} + +$(document).ready(function() { + var hasFGPanel = $("body").data("fgpanel"); + if( hasFGPanel ) { + var panelProps = $("body").data("fgpanel-props"); + new FGFS.FGPanel( panelProps == null ? "fgpanel.json" : panelProps ); + } +}); + diff --git a/webgui/lib/jquery.fganimate.js b/webgui/lib/jquery.fganimate.js new file mode 100644 index 000000000..78c2d65f7 --- /dev/null +++ b/webgui/lib/jquery.fganimate.js @@ -0,0 +1,83 @@ +(function($) { + + function makeTranslate(a) { + return makeTransform("translate", a); + } + + function makeRotate(a) { + return makeTransform("rotate", a); + } + + function makeTransform( type, a ) { + var t = type.concat("("); + if( a != null ) { + a.forEach( function(ele) { + t = t.concat(ele).concat(" "); + }); + } + return t.concat(") "); + } + + function evaluate( context,exp ) { + if( typeof(exp) == 'function' ) + return exp.call(context); + return exp; + } + + $.fn.fgAnimateSVG = function(props) { + if (props) { + if (props.type == "transform" && props.transforms) { + var a = ""; + props.transforms.forEach(function(transform) { + switch (transform.type) { + case "rotate": + a = a.concat(makeRotate([ + evaluate(transform.props.context,transform.props.a), + evaluate(transform.props.context,transform.props.x), + evaluate(transform.props.context,transform.props.y) ])); + break; + case "translate": + a = a.concat(makeTranslate([ + evaluate(transform.props.context,transform.props.x), + evaluate(transform.props.context,transform.props.y) ])); + break; + } + }); + this.attr("transform", a); + + } else if( props.type == "text" ) { + var tspans = this.children("tspan"); + if( 0 == tspans.length ) { + this.text(props.text); + } else { + tspans.text(props.text); + } + } + } + return this; + }; + + $.fn.fgLoadInstruments = function( dataTag ) { + var reply = []; + + this.each(function() { + var instrumentDefinitionFile = $(this).data(dataTag); + $.ajax({ + type: "GET", + url: instrumentDefinitionFile, + context: this, + async: false, + success: function (data,status,xhr) { + var i = new FGFS.Instrument(data); + reply.push(i); + $(this).append(i.svg); + }, + error: function(xhr,status,msg) { + alert(status + " while reading '" + instrumentDefinitionFile + "': " + msg.toString() ); + }, + }); + }); + return reply; +} + +}(jQuery));