Difference between revisions of "Widget:SpaceAPI"
m (implemented 1 beacon, added icon support)  | 
				m (Try and fix SpaceAPI popup alignment)  | 
				||
| (69 intermediate revisions by the same user not shown) | |||
| Line 10: | Line 10: | ||
|url=/spaceAPI/  | |url=/spaceAPI/  | ||
|width=260px  | |width=260px  | ||
| − | |height=  | + | |height=300px  | 
|padding=8px  | |padding=8px  | ||
|interval=20  | |interval=20  | ||
|float=right  | |float=right  | ||
| + | |features=beacon,annex  | ||
}}</nowiki>  | }}</nowiki>  | ||
This will give the following result:<br/>  | This will give the following result:<br/>  | ||
{{#widget:{{PAGENAME}}  | {{#widget:{{PAGENAME}}  | ||
| − | |url=/spaceAPI/  | + | |url=/spaceAPI/?beacon_log=HoT  | 
|width=260px  | |width=260px  | ||
| − | |height=  | + | |height=300px  | 
|padding=8px  | |padding=8px  | ||
|interval=20  | |interval=20  | ||
|float=right  | |float=right  | ||
| + | |features=beacon,annex  | ||
}}<br/>  | }}<br/>  | ||
'''Notes'''  | '''Notes'''  | ||
| Line 36: | Line 38: | ||
(function( )  | (function( )  | ||
{  | {  | ||
| − | + | 	"use strict";  | |
| − | + | 	/*  | |
| − | + | 	TODO:  | |
| − | + | 		separate rooms (get latlng)  | |
| − | + | 		zoom onto toolbox  | |
| − | + | 		table island not correct (4, not 5 tables)  | |
| − | + | ||
| − | + | 	*/  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 	if ( typeof SpaceAPI === "undefined" )  | |
| − | + | 	{  | |
| − | + | 		window.SpaceAPI = function( _width, _height, _float, _padding, _url, _interval, _features )  | |
| − | + | 		{  | |
| − | + | 			this._width = _width;  | |
| − | + | 			this._height = _height;  | |
| − | + | 			this._padding = _padding;  | |
| − | + | 			this._url = _url;  | |
| − | + | 			this._interval = 1000 * _interval;  | |
| − | + | 			this._float = _float;  | |
| − | + | 			this._features = _features;  | |
| − | + | 		}  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 		SpaceAPI.prototype.data			= null;  | |
| − | + | 		SpaceAPI.prototype._width		  = null;  | |
| − | + | 		SpaceAPI.prototype._height		 = null;  | |
| − | + | 		SpaceAPI.prototype._float		  = null;  | |
| − | + | 		SpaceAPI.prototype._url			= null;  | |
| − | + | 		SpaceAPI.prototype._features	   = null;  | |
| + | 		SpaceAPI.prototype._interval	   = null;  | ||
| + | 		SpaceAPI.prototype._intervalId	 = null;  | ||
| + | 		SpaceAPI.prototype._node		   = null;  | ||
| + | 		SpaceAPI.prototype._leaflet		= null;  | ||
| + | 		SpaceAPI.prototype._msgLoading	 = "Loading..";  | ||
| + | 		SpaceAPI.prototype._msgError	   = "Error";  | ||
| + | 		SpaceAPI.prototype._msgParserError = "Failed to parse space state information";  | ||
| + | 		SpaceAPI.prototype._msgOpen		= "Open";  | ||
| + | 		SpaceAPI.prototype._msgClosed	  = "Closed";  | ||
| + | 		SpaceAPI.prototype._msgUnknown	 = "Unknown";  | ||
| + | 		SpaceAPI.prototype._msgSince	   = "Since: ";  | ||
| + | 		SpaceAPI.prototype._colorOpen	  = "#0f0";  | ||
| + | 		SpaceAPI.prototype._colorClosed	= "#f00";  | ||
| + | 		SpaceAPI.prototype._colorUnknown   = "#f70";  | ||
| + | 		SpaceAPI.prototype._debug		  = null;  | ||
| − | + | 		SpaceAPI.prototype._drawBeaconPolyLine = function( )  | |
| − | + | 		{  | |
| − | + | 			if ( !this.data.sensors || !this.data.sensors.beacon || this.data.sensors.beacon.length < 50 )  | |
| − | + | 				return;  | |
| − | + | 			var points = [];  | |
| − | + | 			var bounds = this._leaflet.map.getBounds();  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 			// Only add points within the bounds  | |
| − | + | 			if ( this._leaflet.beaconCount !== this.data.sensors.beacon.length )  | |
| − | + | 			{  | |
| − | + | 				this._leaflet.beaconCount = this.data.sensors.beacon.length;  | |
| − | + | 				for ( var n = 0, len = this.data.sensors.beacon.length; n < len; ++n )  | |
| − | + | 				{  | |
| − | + | 					var point = L.latLng( this.data.sensors.beacon[n].location.lat, this.data.sensors.beacon[n].location.lon );  | |
| − | + | 					points.push( point );  | |
| − | + | 				}  | |
| − | |||
| − | |||
| − | + | 				if ( !this._leaflet.beaconline )  | |
| − | + | 					this._leaflet.beaconline = L.polyline(points, {color: 'green'}).addTo( this._leaflet.map );  | |
| − | + | 				else  | |
| − | + | 					this._leaflet.beaconline.setLatLngs( points );  | |
| − | + | 			}  | |
| − | + | 		};  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 		SpaceAPI.prototype.start = function( )  | |
| − | + | 		{  | |
| − | + | 			// Use interval timer id as image id  | |
| + | 			this._debug = ( location.hash.split("#").slice(1).indexOf("debug") !== -1 );  | ||
| − | + | 			this._intervalId = 0;  | |
| − | + | 			if ( this._interval > 0 )  | |
| + | 				this._intervalId = setInterval( this._fetchState.bind( this ), this._interval );  | ||
| − | + | 			document.write( '<div id="spaceAPI' + this._intervalId + '"></div>' );  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 			var node = document.getElementById( "spaceAPI" + this._intervalId );  | |
| − | + | 			if ( !node )  | |
| − | + | 			{  | |
| + | 				console && console.log( "node not found" );  | ||
| + | 				return;  | ||
| + | 			}  | ||
| + | 			node.style.width = this._width;  | ||
| + | 			node.style.height = this._height;  | ||
| − | + | 			node.style.textAlign = "center";  | |
| − | + | 			node.style.BoxShadow = "3px 3px 4px rgba(0,0,0,0.2)";  | |
| − | + | 			node.style.position = "relative";  | |
| − | + | 			if ( this._float )  | |
| − | + | 				node.style.float = this._float;  | |
| − | |||
| − | + | 			this._node = node.appendChild( document.createElement( "a" ) );  | |
| − | + | 			this._node.style.display = "block";  | |
| − | + | 			this._node.style.color = "inherit";  | |
| − | + | 			this._node.style.padding = this._padding;  | |
| − | + | 			this._node.textContent = this._msgLoading;  | |
| − | + | 			this._node.href = "/spaceAPI/statechanges.html";  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 			if ( this._features.split(",").indexOf( "beacon" ) >= 0 )  | |
| + | 			{  | ||
| + | 				var srcNode;  | ||
| + | 				var mapNode = node.appendChild( document.createElement( "div" ) );  | ||
| + | 				mapNode.style.width = "100%";  | ||
| + | 				mapNode.style.height = "calc(" + this._height + " + " + this._padding + " - 2em)";  | ||
| + | |||
| + | 				srcNode = document.createElement( "link" );  | ||
| + | 				srcNode.href = "/leaflet/leaflet.css";  | ||
| + | 				srcNode.rel = "stylesheet";  | ||
| + | 				srcNode.type = "text/css";  | ||
| − | + | 				// Load the css  | |
| − | + | 				( document.head || document.documentElement ).appendChild( srcNode );  | |
| − | + | ||
| + | 				srcNode = document.createElement( "script" );  | ||
| + | 				srcNode.src = "/leaflet/leaflet.js";  | ||
| + | 				srcNode.type = "text/javascript";  | ||
| + | 				srcNode.addEventListener( "load", function( _evt )  | ||
| + | 				{  | ||
| + | 					this._leaflet = {};  | ||
| + | 					this._leaflet.point = L.latLng( 50.8925,5.9713 );  | ||
| + | 					this._leaflet.map = L.map( mapNode ).panTo( this._leaflet.point );  | ||
| + | 					//this._leaflet.map = L.map( mapNode ).setView( this._leaflet.point, 16);  | ||
| − | + | 					L.CRS.CustomZoom = L.extend({}, L.CRS.EPSG3857,  | |
| − | + | 					{  | |
| − | + | 						scale: function( zoom ) {  | |
| + | 							if ( zoom < 24 )  | ||
| + | 								return 256 * Math.pow( 2, zoom );  | ||
| − | + | 							// Freeze the actual zooming above this level to show the different floors  | |
| − | + | 							// 256 * pow( 2, 23 )  | |
| − | + | 							return 2147483648;  | |
| − | + | 						}  | |
| − | + | 					});  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | /**********************************/  | |
| − | + | 					L.TileLayer.Functional = L.TileLayer.extend({  | |
| − | |||
| − | |||
| − | |||
| − | + | 					  _tileFunction: null,  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 					  initialize: function (tileFunction, options) {  | |
| − | + | 						this._tileFunction = tileFunction;  | |
| − | + | 						L.TileLayer.prototype.initialize.call(this, null, options);  | |
| − | + | 					  },  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 					  getTileUrl: function (tilePoint) {  | |
| − | + | 						var map = this._map,  | |
| − | + | 						  crs = map.options.crs,  | |
| − | + | 						  tileSize = this.options.tileSize,  | |
| − | + | 						  zoom = tilePoint.z,  | |
| − | + | 						  nwPoint = tilePoint.multiplyBy(tileSize),  | |
| + | 						  sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),  | ||
| + | 						  nw = crs.project(map.unproject(nwPoint, zoom)),  | ||
| + | 						  se = crs.project(map.unproject(sePoint, zoom)),  | ||
| + | 						  bbox = [nw.x, se.y, se.x, nw.y].join(',');  | ||
| − | + | 						// Setup object to send to tile function.  | |
| − | + | 						var view = {  | |
| − | + | 						  bbox: bbox,  | |
| − | + | 						  width: tileSize,  | |
| − | + | 						  height: tileSize,  | |
| − | + | 						  zoom: zoom,  | |
| − | + | 						  tile: {  | |
| − | + | 							row: this.options.tms ? this._tileNumBounds.max.y - tilePoint.y : tilePoint.y,  | |
| − | + | 							column: tilePoint.x  | |
| − | + | 						  },  | |
| − | + | 						  subdomain: this._getSubdomain(tilePoint)  | |
| − | + | 						};  | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | 						return this._tileFunction(view);  | |
| − | + | 					  },  | |
| − | |||
| − | |||
| − | |||
| − | + | 					  _loadTile: function (tile, tilePoint) {  | |
| − | + | 						tile._layer = this;  | |
| − | + | 						tile.onload = this._tileOnLoad;  | |
| − | + | 						tile.onerror = this._tileOnError;  | |
| − | |||
| − | |||
| − | + | 						this._adjustTilePoint(tilePoint);  | |
| − | + | 						var tileUrl = this.getTileUrl(tilePoint);  | |
| + | |||
| + | 						if (typeof tileUrl === 'string') {  | ||
| + | 						  tile.src = tileUrl;  | ||
| + | 						  this.fire('tileloadstart', {  | ||
| + | 							tile: tile,  | ||
| + | 							url: tile.src  | ||
| + | 						  });  | ||
| + | 						} else if (typeof tileUrl.then === 'function') {  | ||
| + | 						  // Assume we are dealing with a promise.  | ||
| + | 						  var self = this;  | ||
| + | 						  tileUrl.then(function (tileUrl) {  | ||
| + | 							tile.src = tileUrl;  | ||
| + | 							self.fire('tileloadstart', {  | ||
| + | 							  tile: tile,  | ||
| + | 							  url: tile.src  | ||
| + | 							});  | ||
| + | 						  });  | ||
| + | 						}  | ||
| + | 					  }  | ||
| + | 					});  | ||
| + | |||
| + | 					L.tileLayer.functional = function (tileFunction, options) {  | ||
| + | 					  return new L.TileLayer.Functional(tileFunction, options);  | ||
| + | 					};  | ||
| + | /*******************************/  | ||
| + | |||
| + | 					L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {  | ||
| + | 						attribution: '© <a href="//openstreetmap.org/copyright">OpenStreetMap</a> contributors',  | ||
| + | 						minZoom: 2,  | ||
| + | 						maxZoom: 28,  | ||
| + | 						maxNativeZoom: 19  | ||
| + | 					}).addTo( this._leaflet.map );  | ||
| + | |||
| + | 					// Generic zoom (note that this will cause 404s  | ||
| + | 					L.tileLayer("/images/ACK_{x}_{y}_{z}.png", {  | ||
| + | 						attribution: 'ACKspace',  | ||
| + | 						minZoom: 24,  | ||
| + | 						maxZoom: 28,  | ||
| + | 						maxNativeZoom: 23  | ||
| + | 					}).addTo( this._leaflet.map );  | ||
| + | |||
| + | 					// special zoom  | ||
| + | 					var funcLayer = new L.TileLayer.Functional( function( view )  | ||
| + | 					{  | ||
| + | 						var bounds = {  | ||
| + | 							16 : { cl:	 33855, ch:	 33855, rl:	21971, rh:	21971 },  | ||
| + | 							17 : { cl:	 67710, ch:	 67710, rl:	43942, rh:	43942 },  | ||
| + | 							18 : { cl:	135420, ch:	135420, rl:	87884, rh:	87884 },  | ||
| + | 							19 : { cl:	270840, ch:	270840, rl:   175768, rh:   175768 },  | ||
| + | 							20 : { cl:	541680, ch:	541680, rl:   351536, rh:   351537},  | ||
| + | 							21 : { cl:   1083360, ch:   1083361, rl:   703073, rh:   703074 },  | ||
| + | 							22 : { cl:   2166720, ch:   2166723, rl:  1406146, rh:  1406148 },  | ||
| + | 							23 : { cl:   4333441, ch:   4333446, rl:  2812292, rh:  2812296 },  | ||
| + | 							24 : { cl:   8666890, ch:   8666890, rl:  5624590, rh:  5624590 },  | ||
| + | 							25 : { cl:  17333781, ch:  17333781, rl: 11249181, rh: 11249181 },  | ||
| + | 							26 : { cl:  34667562, ch:  34667562, rl: 22498362, rh: 22498362 },  | ||
| + | 							27 : { cl:  69335125, ch:  69335125, rl: 44996725, rh: 44996725 },  | ||
| + | 							28 : { cl: 138670250, ch: 138670251, rl: 89993450, rh: 89993451 }  | ||
| + | 						}  | ||
| + | 						if ( bounds[ view.zoom ] )  | ||
| + | 						{  | ||
| + | 							if ( view.tile.column < bounds[ view.zoom ].cl || view.tile.column > bounds[ view.zoom ].ch )  | ||
| + | 								return "/images/blank.png";  | ||
| + | 							if ( view.tile.row < bounds[ view.zoom ].rl || view.tile.row > bounds[ view.zoom ].rh )  | ||
| + | 								return "/images/blank.png";  | ||
| + | 						}  | ||
| + | |||
| + | 						var url = "/images/ACK_{x}_{y}_{z}.png"  | ||
| + | 							.replace('{z}', view.zoom)  | ||
| + | 							.replace('{y}', view.tile.row)  | ||
| + | 							.replace('{x}', view.tile.column)  | ||
| + | 							.replace('{s}', view.subdomain);  | ||
| + | |||
| + | 						return url;  | ||
| + | 					},  | ||
| + | 					{  | ||
| + | 						attribution: 'ACKspace',  | ||
| + | 						minZoom: 16,  | ||
| + | 						maxZoom: 28  | ||
| + | 					} ).addTo( this._leaflet.map );  | ||
| + | |||
| + | 					this._leaflet.map.on('moveend', function( _event )  | ||
| + | 					{  | ||
| + | 						//this._drawBeaconPolyLine( _event );  | ||
| + | |||
| + | 					}, this );  | ||
| + | |||
| + | 					this._leaflet.map.on('zoomend', function( _event )  | ||
| + | 					{  | ||
| + | 						var o = 1, z = _event.target.getZoom();  | ||
| + | |||
| + | 						/*  | ||
| + | 						// Infinite zoom  | ||
| + | 						if ( z > 27 )  | ||
| + | 						{  | ||
| + | 							// boundingbox contains center and zoom > 27  | ||
| + | 							// then zoom out  | ||
| + | 							// setView/panTo/setZoom/fitBounds/etc.  | ||
| + | 							//_event.target.getCenter()  | ||
| + | 							_event.target.setZoom( 2, true );  | ||
| + | 						}  | ||
| + | 						*/  | ||
| + | |||
| + | |||
| + | 						//this._drawBeaconPolyLine( _event );  | ||
| + | |||
| + | |||
| + | 						if ( !this._leaflet.temperatures["000005667ABD"] )  | ||
| + | 							return;  | ||
| + | |||
| + | 						// zoom 18->23 opacity 1->0  | ||
| + | 						if ( z > 17 )  | ||
| + | 							o = Math.max( (23-z) / 5, 0 );  | ||
| + | 						this._leaflet.temperatures["000005667ABD"].setStyle( { fillOpacity: o } );  | ||
| + | 					}, this );  | ||
| + | |||
| + | 					if ( this._debug )  | ||
| + | 					{  | ||
| + | 						this._leaflet.map.on( "click", function( _e )  | ||
| + | 						{  | ||
| + | 							console.log( _e.latlng );  | ||
| + | 						});  | ||
| + | |||
| + | 						L.GridLayer.DebugCoords = L.GridLayer.extend({  | ||
| + | 							createTile: function (coords) {  | ||
| + | 								var tile = document.createElement('div');  | ||
| + | 								tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');  | ||
| + | 								tile.style.outline = '1px solid red';  | ||
| + | 								return tile;  | ||
| + | 							}  | ||
| + | 						});  | ||
| + | |||
| + | 						L.gridLayer.debugCoords = function(opts) {  | ||
| + | 							return new L.GridLayer.DebugCoords(opts);  | ||
| + | 						};  | ||
| + | |||
| + | 						this._leaflet.map.addLayer( L.gridLayer.debugCoords() );  | ||
| + | 					}  | ||
| + | |||
| + | 					// "Follow" control  | ||
| + | 					this._leaflet.follow = null;  | ||
| + | 					L.Control.Command = L.Control.extend(  | ||
| + | 					{  | ||
| + | 						options:  | ||
| + | 						{  | ||
| + | 							position: 'topleft',  | ||
| + | 						},  | ||
| + | |||
| + | 						onAdd: function( _map )  | ||
| + | 						{  | ||
| + | 							var controlDiv = L.DomUtil.create( "div", "leaflet-bar" );  | ||
| + | 							var controlUI = L.DomUtil.create( "a", "leaflet-clickable" + (this._leaflet.follow ? " toggle" : ""), controlDiv );  | ||
| + | 							controlUI.innerHTML = "⌖";  | ||
| + | 							controlUI.style.fontSize = "35px";  | ||
| + | 							controlUI.title = 'Follow beacon';  | ||
| + | |||
| + | 							L.DomEvent.addListener( controlUI, 'click', function( _evt )  | ||
| + | 							{  | ||
| + | 								this._leaflet.follow = !this._leaflet.follow;  | ||
| + | 								_evt.currentTarget.className = "leaflet-clickable" + (this._leaflet.follow ? " toggle" : "");  | ||
| + | |||
| + | 								// Update the map immediately  | ||
| + | 								if ( this._leaflet.follow )  | ||
| + | 								{  | ||
| + | 									// Determine the bounding box to 'follow  | ||
| + | 									var bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )  | ||
| + | 									{  | ||
| + | 										return _beacon.point;  | ||
| + | 									} ) );  | ||
| + | |||
| + | 									if ( !this._leaflet.beacons.length )  | ||
| + | 										bounds.extend( this._leaflet.point );  | ||
| + | |||
| + | 									// Depending on polyline: don't zoom  | ||
| + | 									//this._leaflet.map.fitBounds( bounds );  | ||
| + | 									//if ( this._leaflet.map.getZoom() > 18 )  | ||
| + | 										//this._leaflet.map.setZoom( 18 );  | ||
| + | 								}  | ||
| + | 							}.bind( this ) );  | ||
| + | |||
| + | 							return controlDiv;  | ||
| + | 						}.bind( this )  | ||
| + | 					} );  | ||
| + | |||
| + | 					this._leaflet.map.addControl( new L.Control.Command() );  | ||
| + | |||
| + | 					// Icons  | ||
| + | 					this._leaflet.icons = {  | ||
| + | 						"HoaB" : L.icon( {  | ||
| + | 							iconUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.png',  | ||
| + | 							iconSize: [32, 32],  | ||
| + | 							iconAnchor: [16, 26],  | ||
| + | 							popupAnchor: [0, -26],  | ||
| + | 							shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/cycling.shadow.png',  | ||
| + | 							shadowSize: [59, 32],  | ||
| + | 							shadowAnchor: [16, 26]  | ||
| + | 						} ),  | ||
| + | 						"HoT" : L.icon( {  | ||
| + | 							iconUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.png',  | ||
| + | 							iconSize: [32, 32],  | ||
| + | 							iconAnchor: [16, 26],  | ||
| + | 							popupAnchor: [0, -26],  | ||
| + | 							shadowUrl: '//maps.google.com/intl/en_us/mapfiles/ms/micons/bus.shadow.png',  | ||
| + | 							shadowSize: [59, 32],  | ||
| + | 							shadowAnchor: [16, 26]  | ||
| + | 						} )  | ||
| + | 					};  | ||
| + | 					this._leaflet.descriptions = {  | ||
| + | 						"HoaB" : "Hackers on a Bike",  | ||
| + | 						"HoT" : "Hackers on Tour"  | ||
| + | 					};  | ||
| + | |||
| + | 					this._leaflet.marker = L.marker( this._leaflet.point ).addTo( this._leaflet.map );  | ||
| + | 					this._leaflet.beacons = [];  | ||
| + | 					if ( this._debug )  | ||
| + | 						this._leaflet.temperatures = {};  | ||
| + | 					else  | ||
| + | 						this._leaflet.temperatures = {  | ||
| + | 						// outside  | ||
| + | 						"000005671715" : L.polygon([[50.892537811238,5.9711101056338], [50.892517508729,5.9710966945888], [50.892534427487,5.9710564614536], [50.892715457818,5.9712093473674], [50.892696847256,5.9712790848018], [50.892808510518,5.9713836909534], [50.892788208127,5.9714373351337], [50.892774673195,5.9714319707157], [50.892681620427,5.971697509408], [50.892641015525,5.9716653228998], [50.892559805614,5.971893310666], [50.892569956861,5.9719120861291], [50.892559805614,5.9719496370554], [50.892377082796,5.9717940689325], [50.892399077248,5.9717189670801], [50.89233140198,5.9716545940637], [50.892350012689,5.9716009498835], [50.892326326331,5.9715794922114], [50.892414304169,5.9713032246828], [50.892458293026,5.971340775609]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),  | ||
| + | 						// cold zone  | ||
| + | 						"000005667ABD" : L.polygon( [[50.892458293019,5.971340775608], [50.892410920402,5.9713032246818], [50.8924743319376, 5.9711186739150435], [50.89251872057557, 5.97115655487869]], {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),  | ||
| + | 						// barbecue  | ||
| + | 						"DEADBEEF0" : L.circle( [ 50.89277, 5.97134 ], 1, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map ),  | ||
| + | 						// hot zone  | ||
| + | 						"00000567138A" : L.circle( [ 50.89250, 5.9711867 ], 2, {stroke:0,fillOpacity:0.8} ).addTo( this._leaflet.map )  | ||
| + | 					};  | ||
| + | |||
| + | 					var linesRed = [[  | ||
| + | 						[50.892514028030334, 5.971161649524675], [50.89251319586179, 5.971160894608546]],[  | ||
| + | 						[50.89251114428003, 5.9711696663976], [50.89251024343285, 5.9711689967662105]],[  | ||
| + | 						[50.8924980950481, 5.971208224073053], [50.892496785531904, 5.971207094194142]],[  | ||
| + | 						[50.892495831897186, 5.971215394539513], [50.89249461680935, 5.971214339470522]],[  | ||
| + | 						[50.892482398287335, 5.971254612622375], [50.89248148152484, 5.971253695897759]],[  | ||
| + | 						[50.89248006555828, 5.971261391486564], [50.8924792609356, 5.971260443329812]],[  | ||
| + | 						[50.89246652683481, 5.971301496043679], [50.8924656730421, 5.971300341188908]],[  | ||
| + | 						[50.892464393586884, 5.971307247454547], [50.89246356324314, 5.971306176293467]]  | ||
| + | 					];  | ||
| + | |||
| + | 					var linesBlue = [[  | ||
| + | 						[50.892505941019564, 5.971185087508389], [50.89250524711004, 5.971184503287078]],[  | ||
| + | 						[50.892503858003316, 5.971191544085743], [50.892503132264245, 5.9711907897144565]],[  | ||
| + | 						[50.89249048160161, 5.971230519935489], [50.89248932268764, 5.971229581079457]],[  | ||
| + | 						[50.89248770274671, 5.971239335350448], [50.89248661219504, 5.97123836979705]],[  | ||
| + | 						[50.89247468364275, 5.9712776963039005], [50.892473913492424, 5.97127688527138]],[  | ||
| + | 						[50.892472469662096, 5.971284294786302], [50.89247146830519, 5.971283237998365]],[  | ||
| + | 						[50.89245897037926, 5.971323475241662], [50.89245824463948, 5.9713221760466695]],[  | ||
| + | 						[50.89245672874569, 5.971331032450281], [50.89245581256435, 5.971329635940493]]  | ||
| + | 					];  | ||
| + | |||
| + | 					if ( this._debug )  | ||
| + | 					{  | ||
| + | 						L.polyline(linesRed, {color: 'red'}).addTo( this._leaflet.map );  | ||
| + | 						L.polyline(linesBlue, {color: 'blue'}).addTo( this._leaflet.map );  | ||
| + | 					}  | ||
| + | 				}.bind( this ) );  | ||
| + | |||
| + | 				// Load the script  | ||
| + | 				( document.head || document.documentElement ).appendChild( srcNode );  | ||
| + | 			} // beacon  | ||
| + | |||
| + | 			// Update the space state immediately  | ||
| + | 			setTimeout( this._fetchState.bind( this ), 1 );  | ||
| + | 		};  | ||
| + | 		SpaceAPI.prototype._determineColor = function( _temperature )  | ||
| + | 		{  | ||
| + | 			var tempteratureColors = [  | ||
| + | 				[ -10,  0,  0,  0], // black  | ||
| + | 				[   0,  0,  0,255], // blue  | ||
| + | 				[  15, 255,255,  0], // yellow  | ||
| + | 				[  35, 255,  0,  0], // red  | ||
| + | 				[  45, 255,255,255], // white  | ||
| + | 				[5000, 255,255,255]  // white, blink  | ||
| + | 			];  | ||
| + | 			var index;  | ||
| + | 			var ratio;  | ||
| + | |||
| + | 			for ( var nTemp = tempteratureColors.length - 1; nTemp; nTemp-- )  | ||
| + | 			{  | ||
| + | 				if ( _temperature >= tempteratureColors[ nTemp ][0] )  | ||
| + | 					break;  | ||
| + | |||
| + | 				ratio = 0;  | ||
| + | 				if ( index = nTemp )  | ||
| + | 					ratio = (_temperature - tempteratureColors[ nTemp - 1 ][0]) / ( tempteratureColors[ nTemp ][0] - tempteratureColors[ nTemp - 1 ][0] );  | ||
| + | 			}  | ||
| + | |||
| + | 			var lo = tempteratureColors[ index ? index - 1 : 0 ];  | ||
| + | 			var hi = tempteratureColors[ index ];  | ||
| + | |||
| + | 			return "rgb("+Math.round( (lo[1] * (1 - ratio) + hi[1] * ratio) ) +","+Math.round( (lo[2] * (1 - ratio) + hi[2] * ratio))+","+Math.round( (lo[3] * (1 - ratio) + hi[3] * ratio))+")";  | ||
| + | 		};  | ||
| + | |||
| + | 		SpaceAPI.prototype._nlsTime = function( _time )  | ||
| + | 		{  | ||
| + | 			var postfix;  | ||
| + | 			if ( _time < 2 )  | ||
| + | 			{  | ||
| + | 				return "moments";  | ||
| + | 			}  | ||
| + | |||
| + | 			if ( _time > 31556952 )  | ||
| + | 			{  | ||
| + | 				_time /= 31556952;  | ||
| + | 				postfix = "Year";  | ||
| + | 			}  | ||
| + | 			else if ( _time > 2629746 )  | ||
| + | 			{  | ||
| + | 				_time /= 2629746;  | ||
| + | 				postfix = "Month";  | ||
| + | 			}  | ||
| + | 			else if ( _time > 604800 )  | ||
| + | 			{  | ||
| + | 				_time /= 604800;  | ||
| + | 				postfix = "Week";  | ||
| + | 			}  | ||
| + | 			else if ( _time > 86400 )  | ||
| + | 			{  | ||
| + | 				_time /= 86400;  | ||
| + | 				postfix = "day";  | ||
| + | 			}  | ||
| + | 			else if ( _time > 3600 )  | ||
| + | 			{  | ||
| + | 				_time /= 3600;  | ||
| + | 				postfix = "hour";  | ||
| + | 			}  | ||
| + | 			else if ( _time > 60 )  | ||
| + | 			{  | ||
| + | 				_time /= 60;  | ||
| + | 				postfix = "minute";  | ||
| + | 			}  | ||
| + | 			else  | ||
| + | 			{  | ||
| + | 				postfix = "second";  | ||
| + | 			}  | ||
| + | |||
| + | 			_time = Math.round( _time );  | ||
| + | 			return _time + " " + postfix + (_time !== 1 ? "s" : "");  | ||
| + | 		};  | ||
| + | |||
| + | 		SpaceAPI.prototype._fetchState = function( )  | ||
| + | 		{  | ||
| + | 			this._node.className = "processing";  | ||
| + | 			var xhr = new XMLHttpRequest( );  | ||
| + | 			if ( !!( "onload" in xhr ) )  | ||
| + | 			{  | ||
| + | 				xhr.onreadystatechange = function( _event )  | ||
| + | 				{  | ||
| + | 					if ( _event.target.readyState !== 4 )  | ||
| + | 						return;  | ||
| + | 					if ( _event.target.status === 200 )  | ||
| + | 						this._xhr_onload.apply( this, arguments );  | ||
| + | 					else  | ||
| + | 						this._xhr_onerror.apply( this, arguments );  | ||
| + | 				}.bind( this );  | ||
| + | 			}  | ||
| + | 			else  | ||
| + | 			{  | ||
| + | 				// Modern xhr  | ||
| + | 				xhr.onload = this._xhr_onload.bind( this );  | ||
| + | 				xhr.onerror = this._xhr_onerror.bind( this );  | ||
| + | 			}  | ||
| + | |||
| + | 			xhr.open( "GET", this._url, true );  | ||
| + | |||
| + | 			// Tells server that this call is made for ajax purposes.  | ||
| + | 			// Most libraries like jQuery/Prototype/Dojo do this  | ||
| + | 			xhr.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );  | ||
| + | |||
| + | 			// No data needs to be sent along with the request.  | ||
| + | 			xhr.send( null );  | ||
| + | 		};  | ||
| + | |||
| + | 		SpaceAPI.prototype._updateState = function( _message, _color, _title )  | ||
| + | 		{  | ||
| + | 			this._node.className = "";  | ||
| + | 			this._node.textContent = _message;  | ||
| + | 			this._node.style.backgroundColor = _color;  | ||
| + | 			if ( _title )  | ||
| + | 				this._node.title = _title;  | ||
| + | 			else  | ||
| + | 				this._node.title = "";  | ||
| + | 		};  | ||
| + | |||
| + | 		SpaceAPI.prototype._xhr_onload = function( _event )  | ||
| + | 		{  | ||
| + | 			var open = null;  | ||
| + | 			var message = null;  | ||
| + | 			var title = null;  | ||
| + | |||
| + | 			try  | ||
| + | 			{  | ||
| + | 				this.data = JSON.parse( _event.target.responseText );  | ||
| + | 				open = this.data.state.open;  | ||
| + | 				message = this.data.state.message;  | ||
| + | |||
| + | 				// Start as epoch timestamp (NOTE: check if timezone doesn't mess things up)  | ||
| + | 				var d = new Date( 0 );  | ||
| + | 				d.setUTCSeconds( this.data.state.lastchange );  | ||
| + | 				title = this._msgSince + d.toLocaleString( );  | ||
| + | 			}  | ||
| + | 			catch( _e )  | ||
| + | 			{  | ||
| + | 				message = this._msgParserError;  | ||
| + | 			}  | ||
| + | |||
| + | 			if ( open )  | ||
| + | 				this._updateState( message || this._msgOpen, this._colorOpen, title );  | ||
| + | 			else if ( open === false )  | ||
| + | 				this._updateState( message || this._msgClosed, this._colorClosed, title );  | ||
| + | 			else  | ||
| + | 				this._updateState( message || this._msgUnknown, this._colorUnknown, title );  | ||
| + | |||
| + | 			if ( this._leaflet )  | ||
| + | 			{  | ||
| + | 				// Handle temperatures  | ||
| + | 				if ( this.data.sensors && this.data.sensors.temperature && this.data.sensors.temperature.length )  | ||
| + | 				{  | ||
| + | 					// Iterate the sensors and match a local sensor name  | ||
| + | 					this.data.sensors.temperature.forEach( function( _apiTemp )  | ||
| + | 					{  | ||
| + | 						var temp = this._leaflet.temperatures[ _apiTemp.name ];  | ||
| + | 						if ( temp )  | ||
| + | 						{  | ||
| + | 							temp.setStyle({color: this._determineColor( _apiTemp.value )});  | ||
| + | |||
| + | 							var delta = (Date.now() - new Date( _apiTemp.ext_lastchange * 1000 )) / 1000;  | ||
| + | 							temp.custom = {  | ||
| + | 								description: _apiTemp.description | _apiTemp.name,  | ||
| + | 								location: _apiTemp.location | null,  | ||
| + | 								value: _apiTemp.value,  | ||
| + | 								lastchange: _apiTemp.ext_lastchange  | ||
| + | 							}  | ||
| + | 							//updatePopup( temp  | ||
| + | |||
| + | 							temp.bindPopup( "Description: " + _apiTemp.description + "<br/>Location: " + _apiTemp.location + "<br/>Value: " + _apiTemp.value + _apiTemp.unit + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );  | ||
| + | 						}  | ||
| + | 					}, this );  | ||
| + | 				}  | ||
| + | |||
| + | 				var bounds = null;  | ||
| + | 				var maxZoom = 18;  | ||
| + | |||
| + | 				// Handle beacons  | ||
| + | 				if ( this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length < 25 )  | ||
| + | 				{  | ||
| + | 					var bHoaB = false;  | ||
| + | 					var beacons = this.data.sensors.beacon.map( function( _apiBeacon )  | ||
| + | 					{  | ||
| + | 						var beacon = {};  | ||
| + | |||
| + | 						beacon.point  = L.latLng( _apiBeacon.location.lat,_apiBeacon.location.lon );  | ||
| + | 						beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ _apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map );  | ||
| + | 						beacon.circle = L.circle( beacon.point, _apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map );  | ||
| + | |||
| + | 						var delta = (Date.now() - new Date( _apiBeacon.ext_lastchange * 1000 )) / 1000;  | ||
| + | |||
| + | 						// Closure variable  | ||
| + | 						if ( ( delta < 3600 ) && ( _apiBeacon.name === "HoaB" || _apiBeacon.name === "HoT" ) )  | ||
| + | 							bHoaB = true;  | ||
| + | |||
| + | 						if ( this._leaflet.icons[ _apiBeacon.name ] )  | ||
| + | 							this._leaflet.icons[ _apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";  | ||
| + | |||
| + | 						var popup = beacon.marker.getPopup();  | ||
| + | 						if ( !popup )  | ||
| + | 							popup = beacon.marker.bindPopup().getPopup();  | ||
| + | |||
| + | 						popup.setContent( ( this._leaflet.descriptions[ _apiBeacon.name ] || _apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );  | ||
| + | |||
| + | 						return beacon;  | ||
| + | 					}, this );  | ||
| + | |||
| + | 					// TODO: clean up old beacons!!  | ||
| + | 					this._leaflet.beacons.forEach( function( _beacon )  | ||
| + | 					{  | ||
| + | 						// Destroy popup  | ||
| + | 						_beacon.marker.unbindPopup( );  | ||
| + | |||
| + | 						// Remove marker and circle  | ||
| + | 						this._leaflet.map.removeLayer( _beacon.marker );  | ||
| + | 						this._leaflet.map.removeLayer( _beacon.circle );  | ||
| + | 					}, this );  | ||
| + | 					/*  | ||
| + | 					for ( var b = 0; b < Math.min( this._leaflet.beacons.length, beacons, length ); b++ )  | ||
| + | 					{  | ||
| + | 						// Update position, icon, radius, tooltip  | ||
| + | 						this._leaflet.beacons[ b ]  | ||
| + | 					}  | ||
| + | 					*/  | ||
| + | 					this._leaflet.beacons = beacons;  | ||
| + | |||
| + | 					// Only follow beacons automatically initially if there is a HoaB among it  | ||
| + | 					if ( bHoaB && this._leaflet.follow === null )  | ||
| + | 					{  | ||
| + | 						this._leaflet.follow = true;  | ||
| + | 						document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle";  | ||
| + | 					}  | ||
| + | 				}  | ||
| + | 				else if (this.data.sensors && this.data.sensors.beacon && this.data.sensors.beacon.length)  | ||
| + | 				{  | ||
| + | 					var apiBeacon = this.data.sensors.beacon[ 0 ];  | ||
| + | 					var beacon = {};  | ||
| + | 					var bHoaB = false;  | ||
| + | |||
| + | 					beacon.point  = L.latLng( apiBeacon.location.lat,apiBeacon.location.lon );  | ||
| + | 					beacon.marker = L.marker( beacon.point, { icon: this._leaflet.icons[ apiBeacon.name ] || new L.Icon.Default() } ).addTo( this._leaflet.map );  | ||
| + | 					beacon.circle = L.circle( beacon.point, apiBeacon.location.accuracy, {stroke:0} ).addTo( this._leaflet.map );  | ||
| + | |||
| + | 					var delta = (Date.now() - new Date( apiBeacon.ext_lastchange * 1000 )) / 1000;  | ||
| + | |||
| + | 					// Closure variable  | ||
| + | 					if ( ( delta < 3600 ) && ( apiBeacon.name === "HoaB" || apiBeacon.name === "HoT" ) )  | ||
| + | 						bHoaB = true;  | ||
| + | |||
| + | 					if ( this._leaflet.icons[ apiBeacon.name ] )  | ||
| + | 						this._leaflet.icons[ apiBeacon.name ].options.className = delta > 60 ? "disconnected" : "";  | ||
| + | |||
| + | 					var popup = beacon.marker.getPopup();  | ||
| + | 					if ( !popup )  | ||
| + | 						popup = beacon.marker.bindPopup().getPopup();  | ||
| + | |||
| + | 					popup.setContent( ( this._leaflet.descriptions[ apiBeacon.name ] || apiBeacon.name ) + "<br/>Last update: " + this._nlsTime( delta ) + " ago" );  | ||
| + | |||
| + | 					// TODO: clean up old beacons!!  | ||
| + | 					this._leaflet.beacons.forEach( function( _beacon )  | ||
| + | 					{  | ||
| + | 						// Destroy popup  | ||
| + | 						_beacon.marker.unbindPopup( );  | ||
| + | |||
| + | 						// Remove marker and circle  | ||
| + | 						this._leaflet.map.removeLayer( _beacon.marker );  | ||
| + | 						this._leaflet.map.removeLayer( _beacon.circle );  | ||
| + | 					}, this );  | ||
| + | 					this._leaflet.beacons = [ beacon ];  | ||
| + | |||
| + | 					if ( bHoaB && this._leaflet.follow === null )  | ||
| + | 					{  | ||
| + | 						this._leaflet.follow = true;  | ||
| + | 						document.querySelector( "div.leaflet-bar > a.leaflet-clickable" ).className = "leaflet-clickable toggle";  | ||
| + | 					}  | ||
| + | |||
| + | 					this._drawBeaconPolyLine( );  | ||
| + | |||
| + | 					maxZoom = 10;  | ||
| + | 				}  | ||
| + | |||
| + | 				// TODO: Update if coordinate is incorrect  | ||
| + | 				if ( this._leaflet.follow === null )  | ||
| + | 				{  | ||
| + | 					this._leaflet.follow = false;  | ||
| + | |||
| + | 					// Update location  | ||
| + | 					this._leaflet.point = L.latLng( this.data.location.lat, this.data.location.lon );  | ||
| + | 					this._leaflet.marker.setLatLng( this._leaflet.point );  | ||
| + | 					this._leaflet.map.setZoom( 18 );  | ||
| + | |||
| + | |||
| + | 					// Set popup data and open it  | ||
| + | 					var popup = this._leaflet.marker.getPopup();  | ||
| + | 					if ( !popup )  | ||
| + | 						popup = this._leaflet.marker.bindPopup().getPopup();  | ||
| + | |||
| + | 					var info = '<img src="' + this.data.logo + '" style="max-width:'+this._width+'px;width:100%"><br/>';  | ||
| + | 					var l = this.data.location, s = this.data.spacefed;  | ||
| + | 					info += l.address+"<br/>";  | ||
| + | |||
| + | 					if ( l.ext_floor )  | ||
| + | 						info += "floor " + l.ext_floor;  | ||
| + | 					if ( l.ext_room )  | ||
| + | 						info += ", room " + l.ext_room;  | ||
| + | 					info += "<br/>☎ " + '<a target="blank" href="tel:+'+this.data.contact.phone+'">+'+this.data.contact.phone+'</a>';  | ||
| + | 					info += "<br/>" + this._tristate( s.spacenet ) + ' <a target="blank" href="Spacenet">spacenet</a>';  | ||
| + | 					info += "<br/>" + this._tristate( s.ext_spacenet5g ) + " spacenet (5GHz)";  | ||
| + | 					info += "<br/>" + this._tristate( s.spacesaml ) + " spacesaml";  | ||
| + | 					info += "<br/>" + this._tristate( s.ext_spaceconnect ) + " spaceconnect";  | ||
| + | 					info += "<br/>" + this._tristate( s.spacephone ) + ' <a target="blank" href="Spacephone">spacephone</a>';  | ||
| + | 					if ( s.ext_spacephone_extension )  | ||
| + | 						info += ": E" + s.ext_spacephone_extension;  | ||
| + | |||
| + | 					popup.setContent( info );  | ||
| + | |||
| + | 					this._leaflet.marker.openPopup();  | ||
| + | 					//popup.update();  | ||
| + | 				}  | ||
| + | |||
| + | 				if ( this._leaflet.follow )  | ||
| + | 				{  | ||
| + | 					// Determine the bounding box to 'follow  | ||
| + | 					bounds = L.latLngBounds( this._leaflet.beacons.map( function( _beacon )  | ||
| + | 					{  | ||
| + | 						return _beacon.point;  | ||
| + | 					} ) );  | ||
| + | |||
| + | 					// If we don't have Hackers on a Bike, include the home location together with the other beacons  | ||
| + | 					if ( !bHoaB )  | ||
| + | 						bounds.extend( this._leaflet.point );  | ||
| + | |||
| + | 					// Disable zoom (in) if already zoomed beyond set max  | ||
| + | 					if ( this._leaflet.map.getZoom() > maxZoom )  | ||
| + | 					{  | ||
| + | 						this._leaflet.map.panTo( bounds.getCenter() );  | ||
| + | 					}  | ||
| + | 					else  | ||
| + | 					{  | ||
| + | 						this._leaflet.map.fitBounds( bounds );  | ||
| + | 						if ( this._leaflet.map.getZoom() > maxZoom )  | ||
| + | 							this._leaflet.map.setZoom( maxZoom );  | ||
| + | 					}  | ||
| + | 				}  | ||
| + | 			} // leaflet  | ||
| + | |||
| + | 			if ( this._features.split(",").indexOf( "annex" ) >= 0 )  | ||
| + | 			{  | ||
| + | 				var node = this._node;  | ||
| + | 				[].forEach.call(document.querySelectorAll(".state.annex"),function(_n)  | ||
| + | 				{  | ||
| + | 					node.removeChild( _n );  | ||
| + | 				});  | ||
| + | |||
| + | 				(this.data.sensors&&this.data.sensors.service||[]).filter(function(_s)  | ||
| + | 				{  | ||
| + | 					return (_s.source==="annex" && _s.name!=="annex");  | ||
| + | 				}).forEach(function(_d,_i)  | ||
| + | 				{  | ||
| + | 					var anode = node.appendChild( document.createElement( "div" ) );  | ||
| + | 					anode.style.position = "absolute";  | ||
| + | 					anode.style.width = "0.4em";  | ||
| + | 					anode.style.height = "0.4em";  | ||
| + | 					anode.style.top = "calc(2em + 10px)";  | ||
| + | 					anode.style.right = (0.5 * _i) + "em";  | ||
| + | 					anode.style.backgroundColor = (_d.status|0)?"green":"red";  | ||
| + | 					anode.style.zIndex = 500;  | ||
| + | 					anode.className = "state annex";  | ||
| + | 					var t = new Date( 0 );  | ||
| + | 					t.setUTCSeconds( _d.ext_lastchange );  | ||
| + | 					anode.title = _d.name + " (updated " + t.toLocaleString( )+")";  | ||
| + | 				});  | ||
| + | 			}  | ||
| + | 		};  | ||
| + | |||
| + | 		SpaceAPI.prototype._tristate = function( _state )  | ||
| + | 		{  | ||
| + | 			if ( _state )  | ||
| + | 				return "✅";  | ||
| + | 			else if ( _state === null )  | ||
| + | 				return "❓";  | ||
| + | 			else  | ||
| + | 				return "❌";  | ||
| + | 		}  | ||
| + | |||
| + | 		SpaceAPI.prototype._xhr_onerror = function( )  | ||
| + | 		{  | ||
| + | 			// Something has failed, show the error  | ||
| + | 			this._updateState( this._msgError, this._colorUnknown );  | ||
| + | 		}  | ||
| + | 	}  | ||
| + | 	var state;  | ||
| + | 	//state = new SpaceAPI( "auto", "auto", "none", "8px-->", "//ackspace.nl/spaceAPI/", 15, "beacon" );  | ||
| + | 	state = new SpaceAPI( "<!--{$width|escape:html|default:auto}-->", "<!--{$height|escape:html|default:auto}-->", "<!--{$float|escape:html|default:none}-->", "<!--{$padding|escape:html|default:8px}-->", "<!--{$url}-->", <!--{$interval|validate:int|default:0}-->, "<!--{$features|escape:'quotes'}-->" );  | ||
| + | 	state.start();  | ||
}( ));  | }( ));  | ||
</script>  | </script>  | ||
</includeonly>  | </includeonly>  | ||
Latest revision as of 16:51, 24 April 2023
This widget allows you to display the Space API data (provided as JSON)
Created by Xopr
Using this widget
To insert this widget, use the following code:
{{#widget:SpaceAPI
|url=/spaceAPI/
|width=260px
|height=300px
|padding=8px
|interval=20
|float=right
|features=beacon,annex
}}
This will give the following result:
Notes
- url is mandatory, the rest is optional (leave out interval to make the data static).
- it also must be written without protocol since colon (:) is not allowed, and may be relative, for example: //ackspace.nl/spaceAPI/ or /spaceAPI/
 
 - You must provide a unit for the sizes (i.e. px, %, etc.)
 
Copy to your site
To use this widget on your site, just install MediaWiki Widgets extension and copy full source code of this page to your wiki as Widget:SpaceAPI article.