Difference between revisions of "Widget:SpaceAPI"
| m (replaced silly unit prefix logic) | m (Try and fix SpaceAPI popup alignment) | ||
| (61 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.
