From 125d0ccc7dfe2c19b59b7b2a48d71b56231daf3e Mon Sep 17 00:00:00 2001
From: tajniak81 <13187254+tajniak81@users.noreply.github.com>
Date: Mon, 13 Jul 2026 22:58:19 +0200
Subject: [PATCH] Make air-traffic refresh interval configurable, resolved from
plan
Add a refresh-interval control to the Map settings popover: Auto (follows
the OpenSky plan) or a fixed 15/30/60/120s. The states endpoint now
resolves a recommended interval from the resolved plan (anonymous 60s,
standard 30s, contributor 15s) and returns it alongside the aircraft, so
"Auto" tracks the plan's credit budget and update rate. Polling reschedules
live when the effective interval changes; the map caption reflects it. New
airTrafficInterval preference (default 'auto', persisted + synced).
Co-Authored-By: Claude Opus 4.8 this.options.maxZoom)?this.setZoom(e):this},panInsideBounds:function(e,n){this._enforcingBounds=!0;var s=this.getCenter(),a=this._limitCenter(s,this._zoom,et(e));return s.equals(a)||this.panTo(a,n),this._enforcingBounds=!1,this},panInside:function(e,n){n=n||{};var s=me(n.paddingTopLeft||n.padding||[0,0]),a=me(n.paddingBottomRight||n.padding||[0,0]),c=this.project(this.getCenter()),m=this.project(e),C=this.getPixelBounds(),$=Je([C.min.add(s),C.max.subtract(a)]),N=$.getSize();if(!$.contains(m)){this._enforcingBounds=!0;var J=m.subtract($.getCenter()),ge=$.extend(m).getSize().subtract(N);c.x+=J.x<0?-ge.x:ge.x,c.y+=J.y<0?-ge.y:ge.y,this.panTo(this.unproject(c),n),this._enforcingBounds=!1}return this},invalidateSize:function(e){if(!this._loaded)return this;e=u({animate:!1,pan:!0},e===!0?{animate:!0}:e);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var s=this.getSize(),a=n.divideBy(2).round(),c=s.divideBy(2).round(),m=a.subtract(c);return!m.x&&!m.y?this:(e.animate&&e.pan?this.panBy(m):(e.pan&&this._rawPanBy(m),this.fire("move"),e.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(h(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:n,newSize:s}))},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(e){if(e=this._locateOptions=u({timeout:1e4,watch:!1},e),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var n=h(this._handleGeolocationResponse,this),s=h(this._handleGeolocationError,this);return e.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,s,e):navigator.geolocation.getCurrentPosition(n,s,e),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(e){if(this._container._leaflet_id){var n=e.code,s=e.message||(n===1?"permission denied":n===2?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:n,message:"Geolocation error: "+s+"."})}},_handleGeolocationResponse:function(e){if(this._container._leaflet_id){var n=e.coords.latitude,s=e.coords.longitude,a=new Ze(n,s),c=a.toBounds(e.coords.accuracy*2),m=this._locateOptions;if(m.setView){var C=this.getBoundsZoom(c);this.setView(a,m.maxZoom?Math.min(C,m.maxZoom):C)}var $={latlng:a,bounds:c,timestamp:e.timestamp};for(var N in e.coords)typeof e.coords[N]=="number"&&($[N]=e.coords[N]);this.fire("locationfound",$)}},addHandler:function(e,n){if(!n)return this;var s=this[e]=new n(this);return this._handlers.push(s),this.options[e]&&s.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch{this._container._leaflet_id=void 0,this._containerId=void 0}this._locationWatchId!==void 0&&this.stopLocate(),this._stop(),lt(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(te(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload");var e;for(e in this._layers)this._layers[e].remove();for(e in this._panes)lt(this._panes[e]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(e,n){var s="leaflet-pane"+(e?" leaflet-"+e.replace("Pane","")+"-pane":""),a=oe("div",s,n||this._mapPane);return e&&(this._panes[e]=a),a},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter.clone():this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var e=this.getPixelBounds(),n=this.unproject(e.getBottomLeft()),s=this.unproject(e.getTopRight());return new at(n,s)},getMinZoom:function(){return this.options.minZoom===void 0?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===void 0?this._layersMaxZoom===void 0?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(e,n,s){e=et(e),s=me(s||[0,0]);var a=this.getZoom()||0,c=this.getMinZoom(),m=this.getMaxZoom(),C=e.getNorthWest(),$=e.getSouthEast(),N=this.getSize().subtract(s),J=Je(this.project($,a),this.project(C,a)).getSize(),ge=we.any3d?this.options.zoomSnap:1,Be=N.x/J.x,qe=N.y/J.y,Kt=n?Math.max(Be,qe):Math.min(Be,qe);return a=this.getScaleZoom(Kt,a),ge&&(a=Math.round(a/(ge/100))*(ge/100),a=n?Math.ceil(a/ge)*ge:Math.floor(a/ge)*ge),Math.max(c,Math.min(m,a))},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new ie(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(e,n){var s=this._getTopLeftPoint(e,n);return new ze(s,s.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(e){return this.options.crs.getProjectedBounds(e===void 0?this.getZoom():e)},getPane:function(e){return typeof e=="string"?this._panes[e]:e},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(e,n){var s=this.options.crs;return n=n===void 0?this._zoom:n,s.scale(e)/s.scale(n)},getScaleZoom:function(e,n){var s=this.options.crs;n=n===void 0?this._zoom:n;var a=s.zoom(e*s.scale(n));return isNaN(a)?1/0:a},project:function(e,n){return n=n===void 0?this._zoom:n,this.options.crs.latLngToPoint(K(e),n)},unproject:function(e,n){return n=n===void 0?this._zoom:n,this.options.crs.pointToLatLng(me(e),n)},layerPointToLatLng:function(e){var n=me(e).add(this.getPixelOrigin());return this.unproject(n)},latLngToLayerPoint:function(e){var n=this.project(K(e))._round();return n._subtract(this.getPixelOrigin())},wrapLatLng:function(e){return this.options.crs.wrapLatLng(K(e))},wrapLatLngBounds:function(e){return this.options.crs.wrapLatLngBounds(et(e))},distance:function(e,n){return this.options.crs.distance(K(e),K(n))},containerPointToLayerPoint:function(e){return me(e).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(e){return me(e).add(this._getMapPanePos())},containerPointToLatLng:function(e){var n=this.containerPointToLayerPoint(me(e));return this.layerPointToLatLng(n)},latLngToContainerPoint:function(e){return this.layerPointToContainerPoint(this.latLngToLayerPoint(K(e)))},mouseEventToContainerPoint:function(e){return Eo(e,this._container)},mouseEventToLayerPoint:function(e){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e))},mouseEventToLatLng:function(e){return this.layerPointToLatLng(this.mouseEventToLayerPoint(e))},_initContainer:function(e){var n=this._container=Co(e);if(n){if(n._leaflet_id)throw new Error("Map container is already initialized.")}else throw new Error("Map container not found.");He(n,"scroll",this._onScroll,this),this._containerId=y(n)},_initLayout:function(){var e=this._container;this._fadeAnimated=this.options.fadeAnimation&&we.any3d,Fe(e,"leaflet-container"+(we.touch?" leaflet-touch":"")+(we.retina?" leaflet-retina":"")+(we.ielt9?" leaflet-oldie":"")+(we.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var n=zi(e,"position");n!=="absolute"&&n!=="relative"&&n!=="fixed"&&n!=="sticky"&&(e.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var e=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),_t(this._mapPane,new ie(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(Fe(e.markerPane,"leaflet-zoom-hide"),Fe(e.shadowPane,"leaflet-zoom-hide"))},_resetView:function(e,n,s){_t(this._mapPane,new ie(0,0));var a=!this._loaded;this._loaded=!0,n=this._limitZoom(n),this.fire("viewprereset");var c=this._zoom!==n;this._moveStart(c,s)._move(e,n)._moveEnd(c),this.fire("viewreset"),a&&this.fire("load")},_moveStart:function(e,n){return e&&this.fire("zoomstart"),n||this.fire("movestart"),this},_move:function(e,n,s,a){n===void 0&&(n=this._zoom);var c=this._zoom!==n;return this._zoom=n,this._lastCenter=e,this._pixelOrigin=this._getNewPixelOrigin(e),a?s&&s.pinch&&this.fire("zoom",s):((c||s&&s.pinch)&&this.fire("zoom",s),this.fire("move",s)),this},_moveEnd:function(e){return e&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return te(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(e){_t(this._mapPane,this._getMapPanePos().subtract(e))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){this._targets={},this._targets[y(this._container)]=this;var n=e?ut:He;n(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&n(window,"resize",this._onResize,this),we.any3d&&this.options.transform3DLimit&&(e?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){te(this._resizeRequest),this._resizeRequest=Te(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var e=this._getMapPanePos();Math.max(Math.abs(e.x),Math.abs(e.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(e,n){for(var s=[],a,c=n==="mouseout"||n==="mouseover",m=e.target||e.srcElement,C=!1;m;){if(a=this._targets[y(m)],a&&(n==="click"||n==="preclick")&&this._draggableMoved(a)){C=!0;break}if(a&&a.listens(n,!0)&&(c&&!Kn(m,e)||(s.push(a),c))||m===this._container)break;m=m.parentNode}return!s.length&&!C&&!c&&this.listens(n,!0)&&(s=[this]),s},_isClickDisabled:function(e){for(;e&&e!==this._container;){if(e._leaflet_disable_click)return!0;e=e.parentNode}},_handleDOMEvent:function(e){var n=e.target||e.srcElement;if(!(!this._loaded||n._leaflet_disable_events||e.type==="click"&&this._isClickDisabled(n))){var s=e.type;s==="mousedown"&&os(n),this._fireDOMEvent(e,s)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(e,n,s){if(e.type==="click"){var a=u({},e);a.type="preclick",this._fireDOMEvent(a,a.type,s)}var c=this._findEventTargets(e,n);if(s){for(var m=[],C=0;Cthis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),this.options.minZoom===void 0&&this._layersMinZoom&&this.getZoom()
- Live air traffic from OpenSky Network · updates every 30s + Live air traffic from OpenSky Network · updates every {{ airIntervalSeconds }}s
diff --git a/Web App/web/src/prefs.js b/Web App/web/src/prefs.js index a57c0c9..4355e96 100644 --- a/Web App/web/src/prefs.js +++ b/Web App/web/src/prefs.js @@ -29,6 +29,9 @@ const defaults = { // Live map: overlay live OpenSky air traffic (client-side toggle; the OpenSky // integration itself is still gated in Settings → Integrations). showAirTraffic: true, + // Air-traffic refresh cadence: 'auto' follows the plan-recommended interval + // (from the server), or a fixed number of seconds (15 | 30 | 60 | 120). + airTrafficInterval: 'auto', // Security (prototype) twoFactor: false, }