(()=>{"use strict";const t=JSON.parse('{"eR":60,"II":30,"HG":"/climate/api"}');class e{constructor(t){this.timeseries=[],this.valRange={high:-1/0,low:1/0},this.tickCache=[],this.tickCacheDirty=!0,this.bounds=t}updateIndexRange(t){this.valRange.high=-1/0,this.valRange.low=1/0;for(const e of this.timeseries){const i=e.getExtremaInRange(t.start,t.stop);i.maxVal>this.valRange.high&&(this.valRange.high=i.maxVal),i.minValnew Date(1e3*t).toLocaleTimeString(),this.resolution=1,this.dragging=!1,this.highlightedTimeseries=null,this.subscriptions={scroll:[],mousemove:[],drag:[]},this.ctx=t,this.initLayout(),this.updateDimensions(),this.ctx.fillStyle="rgb(255,255,255)",this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.ctx.fill(),this.ctx.translate(.5,.5),this.ctx.canvas.onmousemove=t=>this.handleMouseMove(t),this.ctx.canvas.onmousedown=t=>this.dragging=!0,this.ctx.canvas.onmouseup=t=>this.dragging=!1,this.ctx.canvas.onmouseleave=t=>this.dragging=!1,this.ctx.canvas.onmouseout=t=>this.dragging=!1,this.ctx.canvas.onwheel=t=>this.handleScroll(t)}initLayout(){const t=this.margins.bottom+this.margins.top,i=this.margins.left+this.margins.right;this.leftScale=new e({top:this.margins.top,left:this.margins.left,height:this.ctx.canvas.height-t,width:50}),this.chartBounds={top:this.margins.top,left:this.margins.left+50,height:this.ctx.canvas.height-t,width:this.ctx.canvas.width-(i+50+50)},this.rightScale=new e({top:this.margins.top,left:this.ctx.canvas.width-this.margins.right-50,height:this.ctx.canvas.height-t,width:50})}updateDimensions(){this.chartBounds.width=Number(getComputedStyle(this.ctx.canvas).width.slice(0,-2))-(this.margins.left+this.margins.right+this.rightScale.getBounds().width+this.leftScale.getBounds().width),this.chartBounds.height=Number(getComputedStyle(this.ctx.canvas).height.slice(0,-2))-(this.margins.bottom+this.margins.top)}addTimeseries(t,e){this.timeseries.push(t),e===i.Left?this.leftScale.addTimeseries(t):this.rightScale.addTimeseries(t)}setRange(t){this.indexRange.start=t.start,this.indexRange.stop=t.stop}handleMouseMove(t){const{left:e,top:i}=this.ctx.canvas.getBoundingClientRect(),s=this.lastMousePos.x;this.lastMousePos.x=t.clientX-e,this.lastMousePos.y=t.clientY-i,this.render(),this.dragging&&this.emit("drag",t.movementX,t.movementY,this.getIndex(s)-this.getIndex(this.lastMousePos.x))}handleScroll(t){this.emit("scroll",t.deltaY>0?1:-1,Math.abs(t.deltaY),this.getIndex(this.lastMousePos.x))}emit(t,...e){for(const i of this.subscriptions[t])i(...e)}highlightTimeseries(t){if(!t)return this.highlightedTimeseries=null,void this.render();for(const e of this.timeseries)if(e.getName()===t)return this.highlightedTimeseries=t,void this.render();throw new Error(`The timeseries ${t} could not be highlighted because it doesn't exist on the chart!`)}on(t,e){this.subscriptions[t].push(e)}render(){this.updateDimensions(),this.clearCanvas(),this.updateResolution(),this.renderGuides(),this.leftScale.updateIndexRange(this.indexRange),this.rightScale.updateIndexRange(this.indexRange),this.leftScale.listTimeseries().forEach((t=>this.renderTimeseries(t,i.Left))),this.rightScale.listTimeseries().forEach((t=>this.renderTimeseries(t,i.Right))),this.leftScale.render(this.ctx),this.rightScale.render(this.ctx),this.renderTooltips()}clearCanvas(){this.ctx.fillStyle="rgb(255,255,255)",this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.ctx.fill()}updateResolution(){const t=(this.chartBounds.width-this.rightScale.getBounds().width-this.leftScale.getBounds().width)/(this.timeseries[0]?.cachedBetween(this.indexRange.start,this.indexRange.stop,1).length/2??0);this.resolution=t<5?Math.ceil(5/t):1}renderGuides(){this.ctx.strokeStyle="rgb(230, 230, 230)",this.ctx.lineWidth=1;for(const t of this.rightScale.getTicks()){const e=this.rightScale.getY(t);this.ctx.beginPath(),this.ctx.moveTo(this.chartBounds.left,e),this.ctx.lineTo(this.chartBounds.left+this.chartBounds.width,e),this.ctx.stroke()}}renderTooltips(t=20){let e=t,i=this.timeseries[0],s=0,n=0,a=this.leftScale;for(const o of[this.leftScale,this.rightScale])for(const r of o.listTimeseries()){const h=r.cachedBetween(this.getIndex(this.lastMousePos.x-t/2),this.getIndex(this.lastMousePos.x+t/2),this.resolution);for(let l=0;l=this.lastMousePos.y&&d-t/2<=this.lastMousePos.y){const t=this.getX(h[l+1]),c=Math.sqrt((d-this.lastMousePos.y)**2+(t-this.lastMousePos.x)**2);cthis.ctx.canvas.width&&(e-=r+4),i-o<0&&(i+=o+4),this.ctx.fillStyle="rgb(255,255,255)",this.ctx.strokeStyle="rgb(0,0,0)",this.ctx.fillRect(Math.round(e),Math.round(i),Math.round(r),Math.round(o)),this.ctx.strokeRect(Math.round(e),Math.round(i),Math.round(r),Math.round(o)),this.ctx.fillStyle=s,this.ctx.beginPath(),this.ctx.arc(Math.round(e+10),Math.round(i+o/2),5,0,2*Math.PI),this.ctx.fill(),this.ctx.fillStyle="rgb(0,0,0)",this.ctx.textAlign="left",this.ctx.fillText(t,Math.round(e+20),Math.round(i+a+5))}}class n extends Error{constructor(t){super(t),this.name="AppStateError"}}function a(){const e=(new Date).getTime()/1e3;return{overlayText:"",lastUpdateTime:e,minutesDisplayed:t.eR,utcOffset:-(new Date).getTimezoneOffset()/60,dataEndpointBase:t.HG,isLoading:!1,updateIntervalSeconds:t.II,displayMode:"pastMins",fatalError:null,displayWindow:{start:e-60*t.eR,stop:e},documentReady:!1,leftTimeseries:[],rightTimeseries:[],highlightedTimeseries:null}}class o{constructor(t){this.loaders=0,this.state={...a(),...t};const e={};for(const t in this.state)e[t]=[];this.eventCallbacks={newTimeseries:[],timeseriesUpdated:[],stateChange:[]},this.subscriptions=e,this.init(),setInterval((()=>this.getNewTimeseriesData().catch((t=>h().fatalError(t)))),1e3*this.state.updateIntervalSeconds)}async init(){await this.updateTimeseriesFromSettings(),await this.getNewTimeseriesData()}addTimeseriesToScale(t,e){const s=e===i.Left?this.state.leftTimeseries:this.state.rightTimeseries;if(s.indexOf(t)>=0)throw new n("Timeseries has already been added!");i.Left,s.push(t),this.notifyStoreVal(e===i.Left?"leftTimeseries":"rightTimeseries"),this.emit("newTimeseries",t,e),this.updateTimeseriesFromSettings()}notifyStoreVal(t,e,i){this.emit("stateChange",t,e,i);for(const s of this.subscriptions[t])s(e,i)}emit(t,...e){for(const i of this.eventCallbacks[t])i(...e)}async updateTimeseriesFromSettings(){let t,e;"window"===this.state.displayMode?(t=this.state.displayWindow.start,e=this.state.displayWindow.stop):(t=this.state.lastUpdateTime-60*this.state.minutesDisplayed,e=this.state.lastUpdateTime),this.addLoad();try{for(const i of this.state.leftTimeseries)await i.updateFromWindow(t,e);for(const i of this.state.rightTimeseries)await i.updateFromWindow(t,e)}catch(t){h().fatalError(t)}this.finishLoad(),this.notifyAllTimeseriesUpdated()}async getNewTimeseriesData(){const t=(new Date).getTime()/1e3;this.addLoad();try{for(const t of this.state.leftTimeseries)await t.getLatest();for(const t of this.state.rightTimeseries)await t.getLatest()}catch(t){h().fatalError(t)}this.finishLoad(),this.setLastUpdateTime(t),this.notifyAllTimeseriesUpdated()}notifyAllTimeseriesUpdated(){for(const t of this.state.leftTimeseries)this.notifyStoreVal("leftTimeseries"),this.emit("timeseriesUpdated",t);for(const t of this.state.rightTimeseries)this.notifyStoreVal("rightTimeseries"),this.emit("timeseriesUpdated",t)}getState(){return this.state}subscribeStoreVal(t,e){this.subscriptions[t].push(e)}on(t,e){this.eventCallbacks[t].push(e)}setDisplayMode(t){this.state.displayMode=t,this.updateTimeseriesFromSettings(),this.notifyStoreVal("displayMode")}setDisplayWindow(t){t.start0))throw new n("Invalid minutes passed: "+t);this.state.minutesDisplayed=Math.ceil(t),this.notifyStoreVal("minutesDisplayed"),this.updateTimeseriesFromSettings()}setUtcOffset(t){Math.floor(t)===t&&t<=14&&t>=-12?this.state.utcOffset=t:(console.warn("Invalid UTC offset: "+t),this.state.utcOffset=t>14?14:t<-12?-12:Math.floor(t)),this.notifyStoreVal("utcOffset")}setLastUpdateTime(t){if(!(this.state.lastUpdateTime<=t))throw new n(`Bad new update time was before last update time. Old: ${this.state.lastUpdateTime}, New: ${t}`);this.state.lastUpdateTime=t,this.notifyStoreVal("lastUpdateTime")}setOverlayText(t){this.state.overlayText=t,this.notifyStoreVal("overlayText")}addLoad(){this.loaders+=1,this.state.isLoading=this.loaders>0,this.notifyStoreVal("isLoading")}finishLoad(){this.loaders-=1,this.state.isLoading=this.loaders>0,this.notifyStoreVal("isLoading")}fatalError(t){this.state.fatalError||(this.state.fatalError=t,this.notifyStoreVal("fatalError"))}setDocumentReady(t){this.state.documentReady=t,this.notifyStoreVal("documentReady")}setHighlightedTimeseries(t){this.state.highlightedTimeseries=t,this.notifyStoreVal("highlightedTimeseries",t)}serialiseState(){const t=[];return"pastMins"===this.state.displayMode?60!==this.state.minutesDisplayed&&t.push("minutesDisplayed="+this.state.minutesDisplayed):t.push(`displayWindow=[${this.state.displayWindow.start},${this.state.displayWindow.stop}]`),this.state.utcOffset!==a().utcOffset&&t.push("utcOffset="+this.state.utcOffset),t.join("&")}deserialise(t){if(t.get("minutesDisplayed")&&t.get("displayWindow")&&console.warn("Options 'minutesDisplayed' and 'displayWindow' should not be used together. Defaulting to 'displayWindow'."),t.get("minutesDisplayed")&&(this.setDisplayMode("pastMins"),this.setMinutesDisplayed(Number(t.get("minutesDisplayed")))),t.get("utcOffset")&&this.setUtcOffset(Number(t.get("utcOffset"))),t.get("displayWindow")){const e=t.get("displayWindow").split(",");2===e.length&&(this.setDisplayMode("window"),this.setDisplayWindow({start:Number(e[0].slice(1)),stop:Number(e[1].slice(0,-1))}))}this.emit("stateChange")}}let r;function h(){if(r)return r;throw new n("Store not yet initialised!")}function l(){if(r)return r.getState();throw new n("Store not yet initialised!")}class d{constructor(){this.id=d.componentCount,d.componentCount++}makeRef(t){return d.reffedComponents.push(t),d.reffedComponentCount++}fromRef(t){return d.reffedComponents[t]??null}}d.componentCount=0,d.reffedComponentCount=0,d.reffedComponents=[];const c=class extends d{constructor(t){super(),this.container=document.createElement("div"),this.title=document.createElement("h2"),this.body=document.createElement("div"),this.container.className="widget"+(t.className?" "+t.className:""),this.title.className="widget-title",this.body.className="widget-body",this.setTitle(t.title),this.setPosition({row:t.row,col:t.col}),this.setSize({width:t.width,height:t.height}),t.title&&this.container.append(this.title),t.body&&this.body.append(t.body),this.container.append(this.body)}setPosition(t){this.container.style.gridRowStart=""+t.row,this.container.style.gridColumnStart=""+t.col}setSize(t){this.container.style.gridRowEnd="span "+t.height,this.container.style.gridColumnEnd="span "+t.width}setTitle(t){this.title.innerText=t}replaceBody(t){this.body.replaceWith(t)}current(){return this.container}};function u(t,e,...i){return"function"==typeof t?i.length>=1?t({...e},i):t({...e}):function(t,e,...i){const s=document.createElement(t);for(const t in e){const i=e[t];t.startsWith("on")&&"function"==typeof i?s.addEventListener(t.substring(2),i):"boolean"==typeof i&&!0===i?s.setAttribute(t,""):"string"==typeof i&&("className"===t?s.setAttribute("class",e[t]):s.setAttribute(t,i))}return s.append(...m(i)),s}(t,e,...i)}function m(t){const e=[];for(const i of t)null!=i&&"boolean"!=typeof i&&(Array.isArray(i)?e.push(...m(i)):"string"==typeof i?e.push(document.createTextNode(String(i))):i instanceof Node&&e.push(i));return e}const p=class extends d{constructor(t){super(),this.display=document.createElement("span"),this.display=u(this.MainBody,{ctx:this}),this.skeleton=new c({...t,title:"Displayed Timezone:",body:this.display}),h().subscribeStoreVal("utcOffset",(()=>this.updateDisplay())),this.updateDisplay()}updateDisplay(){const t=h().getState().utcOffset;this.fromRef(this.timezoneDisplayRef).innerText=`${t>0?"+":"−"} ${Math.abs(t)}`,this.fromRef(this.timezoneInputRef).value=`${t>0?"":"-"}${Math.abs(t)}`}MainBody({ctx:t}){return u("div",{className:"timezone-widget",onclick:()=>t.onTimezoneClick()},u("span",null,"UTC "),u(t.TimezoneDisplay,{ctx:t}),u("span",null,":00"))}TimezoneDisplay({ctx:t}){return t.timezoneDisplayRef=t.makeRef(u("span",null)),t.timezoneInputRef=t.makeRef(u("input",{type:"text",onblur:()=>t.onTimezoneInputBlur()})),t.fromRef(t.timezoneDisplayRef)}onTimezoneInputBlur(){const t=this.fromRef(this.timezoneInputRef),e=this.fromRef(this.timezoneDisplayRef);h().setUtcOffset(Number(t.value)),t.replaceWith(e),this.updateDisplay()}onTimezoneClick(){const t=this.fromRef(this.timezoneInputRef);this.fromRef(this.timezoneDisplayRef).replaceWith(t),t.focus(),t.selectionStart=0,t.selectionEnd=t.value.length}current(){return this.skeleton.current()}};const f=class extends d{constructor(t){super(),this.mainDisplay=this.MainDisplay({ctx:this}),this.skeleton=new c({...t,title:"Displaying:",body:this.mainDisplay}),h().subscribeStoreVal("minutesDisplayed",(()=>this.updateDisplay())),h().subscribeStoreVal("displayMode",(()=>this.updateDisplay())),h().subscribeStoreVal("displayWindow",(()=>this.updateDisplay())),h().subscribeStoreVal("utcOffset",(()=>this.updateDisplay())),this.updateDisplay()}WindowStartTime({ctx:t}){return t.windowStartTimeInputRef=t.makeRef(u("input",{type:"datetime-local",onblur:()=>t.onWindowStartInputBlur()})),t.windowStartTimeRef=t.makeRef(u("div",{className:"display-mode-widget-date",onwheel:e=>t.onStartTimeInputScroll(e),onclick:()=>t.onWindowStartDisplayClick()},new Date(l().displayWindow.start+60*l().utcOffset*60*1e3).toLocaleString())),t.fromRef(t.windowStartTimeRef)}WindowStopTime({ctx:t}){return t.windowStopTimeInputRef=t.makeRef(u("input",{value:new Date,type:"datetime-local",onblur:()=>t.onWindowStopInputBlur()})),t.windowStopTimeRef=t.makeRef(u("div",{className:"display-mode-widget-date",onwheel:e=>t.onStopTimeInputScroll(e),onclick:()=>t.onWindowStopDisplayClick()},new Date(l().displayWindow.stop+60*l().utcOffset*60*1e3).toLocaleString())),t.fromRef(t.windowStopTimeRef)}MinutesCounter({ctx:t,onclick:e}){return t.minsInputRef=t.makeRef(u("input",{value:l().minutesDisplayed.toString(),onblur:e=>t.onMinutesCounterInputBlur(e)})),t.minsCounterRef=t.makeRef(u("div",{className:"min-count",onclick:e,onwheel:e=>t.onMinutesCounterInputScroll(e)},l().minutesDisplayed.toString())),t.fromRef(t.minsCounterRef)}onMinutesCounterInputScroll(t){h().setMinutesDisplayed(l().minutesDisplayed+t.deltaY)}onStopTimeInputScroll(t){const e=l().displayWindow;h().setDisplayWindow({start:e.start,stop:e.stop-60*t.deltaY})}onStartTimeInputScroll(t){const e=l().displayWindow;h().setDisplayWindow({start:e.start-60*t.deltaY,stop:e.stop})}onMinutesCounterInputBlur(t){const e=Number(t.target.value);isNaN(e)?t.target.value=l().minutesDisplayed.toString():e>=1&&h().setMinutesDisplayed(e),this.fromRef(this.minsInputRef).replaceWith(this.fromRef(this.minsCounterRef))}MinutesDisplay({ctx:t}){return u("div",{className:"display-mode-widget-mins"},u("div",null,"Last"),u(t.MinusButton,{onclick:()=>{const t=h().getState().minutesDisplayed;h().setMinutesDisplayed(t-1)}}),u(t.MinutesCounter,{ctx:t,onclick:()=>t.onMinutesCounterClick()}),u(t.PlusButton,{onclick:()=>{const t=h().getState().minutesDisplayed;h().setMinutesDisplayed(t+1)}}),u("div",null,"minutes"))}onMinutesCounterClick(){const t=this.fromRef(this.minsInputRef);this.fromRef(this.minsCounterRef).replaceWith(t),t.focus(),t.selectionStart=0,t.selectionEnd=t.value.length}onWindowStopDisplayClick(){const t=this.fromRef(this.windowStopTimeRef);t.valueAsDate=new Date(l().displayWindow.stop);const e=this.fromRef(this.windowStopTimeInputRef);t.replaceWith(e);const i=new Date(1e3*l().displayWindow.stop+60*l().utcOffset*60*1e3);e.value=`${i.toLocaleDateString()}, ${i.toLocaleTimeString()}`,e.focus()}onWindowStopInputBlur(){const t=this.fromRef(this.windowStopTimeInputRef),e=new Date(t.value).getTime()/1e3;isNaN(e)||h().setDisplayWindow({start:l().displayWindow.start,stop:e}),t.replaceWith(this.fromRef(this.windowStopTimeRef))}onWindowStartDisplayClick(){const t=this.fromRef(this.windowStartTimeRef);t.valueAsDate=new Date(l().displayWindow.start);const e=this.fromRef(this.windowStartTimeInputRef);t.replaceWith(e);const i=new Date(1e3*l().displayWindow.start+60*l().utcOffset*60*1e3);e.value=`${i.toLocaleDateString()}, ${i.toLocaleTimeString()}`,e.focus()}onWindowStartInputBlur(){const t=this.fromRef(this.windowStartTimeInputRef),e=new Date(t.value).getTime()/1e3;isNaN(e)||h().setDisplayWindow({start:e,stop:l().displayWindow.stop}),t.replaceWith(this.fromRef(this.windowStartTimeRef))}MinusButton(t){return u("div",{className:"minus-button",onclick:t.onclick})}PlusButton(t){return u("div",{className:"plus-button",onclick:t.onclick})}WindowedDisplay({ctx:t}){return u("div",null,u("div",null,"From"),u(t.MinusButton,{onclick:()=>{const t=h().getState().displayWindow;h().setDisplayWindow({start:t.start-60,stop:t.stop})}}),u(t.WindowStartTime,{ctx:t}),u(t.PlusButton,{onclick:()=>{const t=h().getState().displayWindow;h().setDisplayWindow({start:t.start+60,stop:t.stop})}}),u("div",null,"to"),u(t.MinusButton,{onclick:()=>{const t=h().getState().displayWindow;h().setDisplayWindow({start:t.start,stop:t.stop-60})}}),u(t.WindowStopTime,{ctx:t}),u(t.PlusButton,{onclick:()=>{const t=h().getState().displayWindow;h().setDisplayWindow({start:t.start,stop:t.stop+60})}}))}MainDisplay({ctx:t}){const e="window"===l().displayMode;return t.windowedDisplayRef=t.makeRef(u(t.WindowedDisplay,{ctx:t})),t.minsDisplayRef=t.makeRef(u(t.MinutesDisplay,{ctx:t})),u("div",{className:"display-mode-widget"},e?t.fromRef(t.windowedDisplayRef):t.fromRef(t.minsDisplayRef))}onSelectMode(t){h().setDisplayMode(t)}updateDisplay(){if("window"===l().displayMode){this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.windowedDisplayRef));const t=60*l().utcOffset*60,e=new Date(1e3*(l().displayWindow.start+t)),i=new Date(1e3*(l().displayWindow.stop+t));this.fromRef(this.windowStartTimeRef).innerText=e.toLocaleString(),this.fromRef(this.windowStopTimeRef).innerText=i.toLocaleString()}else this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.minsDisplayRef)),this.fromRef(this.minsCounterRef).innerText=l().minutesDisplayed.toString(),this.fromRef(this.minsInputRef).value=l().minutesDisplayed.toString()}current(){return this.skeleton.current()}};const g=class extends d{constructor(t){super(),this.display=u(this.MainDisplay,{ctx:this}),this.skeleton=new c({...t,className:"timer-widget",title:"Next update in:",body:this.display}),h().subscribeStoreVal("lastUpdateTime",(()=>this.resetTimer())),h().subscribeStoreVal("utcOffset",(()=>this.resetTimer())),setInterval((()=>this.refreshTimer()),10),this.resetTimer()}resetTimer(){this.nextUpdateTime=l().lastUpdateTime+l().updateIntervalSeconds,this.updateUpdateText(),this.refreshTimer()}updateUpdateText(){this.fromRef(this.lastUpdateRef).innerText=new Date(1e3*l().lastUpdateTime+60*l().utcOffset*60*1e3).toLocaleString()}MainDisplay({ctx:t}){return t.timerRef=t.makeRef(u("div",{className:"countdown"})),t.lastUpdateRef=t.makeRef(u("span",{className:"last-update"},new Date(l().lastUpdateTime).toLocaleString())),u("div",null,t.fromRef(t.timerRef),u("div",null,u("div",{className:"last-update"},"Last update was at:"),u("div",null,t.fromRef(t.lastUpdateRef))))}refreshTimer(){const t=(new Date).getTime()/1e3;t<=this.nextUpdateTime?this.fromRef(this.timerRef).innerText=(this.nextUpdateTime-t).toFixed(2)+"s":this.fromRef(this.timerRef).innerText="0.00s"}current(){return this.skeleton.current()}};const w=class extends d{constructor(t){super(),this.chart=null,this.displayMode="pastMins",this.canvasElement=document.createElement("canvas"),this.initialised=!1,this.canvasElement.className="chart-canvas",this.skeleton=new c({...t,body:this.canvasElement});const e=(new Date).getTime()/1e3;this.latestSnapshotInChartTime=e-60*l().minutesDisplayed,this.setupListeners(),this.updateDisplayMode()}updateDimensions(){const t=getComputedStyle(this.skeleton.current());this.canvasElement.height=this.skeleton.current().clientHeight-Number(t.paddingTop.slice(0,-2))-Number(t.paddingBottom.slice(0,-2)),this.canvasElement.width=this.skeleton.current().clientWidth-Number(t.paddingLeft.slice(0,-2))-Number(t.paddingRight.slice(0,-2))}setupListeners(){h().subscribeStoreVal("displayMode",(()=>this.updateDisplayMode())),h().subscribeStoreVal("minutesDisplayed",(()=>this.rerender())),h().subscribeStoreVal("displayWindow",(()=>this.rerender())),h().on("timeseriesUpdated",(()=>this.rerender())),h().on("newTimeseries",(t=>this.chart.addTimeseries(t))),h().subscribeStoreVal("documentReady",(()=>this.initChart())),h().subscribeStoreVal("utcOffset",(()=>this.updateTimezone())),h().subscribeStoreVal("highlightedTimeseries",(t=>this.chart.highlightTimeseries(t)))}handleScroll(t,e,i){let s=l().displayWindow;if("pastMins"===l().displayMode){h().setDisplayMode("window");const t=(new Date).getTime()/1e3;s={start:t-60*l().minutesDisplayed,stop:t}}const n=1===t?1.1:.9,a=n*(i-s.start),o=n*(s.stop-i);h().setDisplayWindow({start:i-a,stop:i+o})}handleDrag(t,e,i){"pastMins"===l().displayMode&&h().setDisplayMode("window");const s=l().displayWindow;h().setDisplayWindow({start:s.start+i,stop:s.stop+i})}updateTimezone(){const t=60*l().utcOffset*60*1e3;this.chart.setTimestampFormatter((e=>new Date(1e3*e+t).toLocaleTimeString()))}async initChart(){try{h().addLoad();const t=this.canvasElement.getContext("2d",{alpha:!1});this.chart=new s(t),l().leftTimeseries.forEach((t=>this.chart.addTimeseries(t,i.Left))),l().rightTimeseries.forEach((t=>this.chart.addTimeseries(t,i.Right))),this.chart.on("scroll",((...t)=>this.handleScroll(...t))),this.chart.on("drag",((...t)=>this.handleDrag(...t))),await this.rerender(),this.initialised=!0}catch(t){h().fatalError(t)}finally{h().finishLoad()}}async updateDisplayMode(){this.displayMode=l().displayMode,await this.rerender()}async rerender(){if(!this.initialised)return;let t,e;if("window"===this.displayMode)t=l().displayWindow.start,e=l().displayWindow.stop;else if("pastMins"===this.displayMode){const i=l().minutesDisplayed;t=l().lastUpdateTime-60*i,e=l().lastUpdateTime}this.chart.setRange({start:t,stop:e}),this.chart.render()}current(){return this.skeleton.current()}};const y=class extends d{constructor(){super(),this.showingError=!1,this.build(),h().subscribeStoreVal("overlayText",(()=>this.update())),h().subscribeStoreVal("isLoading",(()=>this.update())),h().subscribeStoreVal("fatalError",(()=>this.showError())),this.update()}build(){this.element=document.createElement("div"),this.element.classList.add("overlay","center"),this.textElement=document.createElement("span"),this.textElement.innerText="",this.element.appendChild(this.textElement)}show(){this.element.classList.remove("hidden")}hide(){this.element.classList.add("hidden")}showError(){const t=l().fatalError;this.showingError=!0,this.element.innerText=`${t.name}: ${t.message}!`,this.show()}update(){if(!this.showingError){let t;l().isLoading?t="Loading...":l().overlayText&&(t=l().overlayText),t?(this.textElement.innerText=t,this.show()):this.hide()}}current(){return this.element}};class x extends d{constructor(t){super(),this.mainBody=this.MainBody({ctx:this}),this.gridWidgetSkeleton=new c({...t,title:"Display Mode:",body:this.mainBody}),h().subscribeStoreVal("displayMode",(()=>this.update()))}selectMode(t){h().setDisplayMode(t)}update(){const t="window"===l().displayMode;this.fromRef(this.windowInputRef).checked=t,this.fromRef(this.minSpanInputRef).checked=!t,t?(this.fromRef(this.minSpanInputContainerRef).classList.remove("selected"),this.fromRef(this.windowInputContainerRef).classList.add("selected")):(this.fromRef(this.minSpanInputContainerRef).classList.add("selected"),this.fromRef(this.windowInputContainerRef).classList.remove("selected"))}MainBody({ctx:t}){const e="window"===l().displayMode;return t.windowInputRef=this.makeRef(u("input",{type:"radio",id:"window",name:"display-mode",checked:e})),t.minSpanInputRef=this.makeRef(u("input",{type:"radio",id:"min-span",name:"display-mode",checked:!e})),t.windowInputContainerRef=this.makeRef(u("div",{className:"display-mode-option"+(e?" selected":""),onclick:()=>t.selectMode("window")},this.fromRef(t.windowInputRef),u("label",{htmlFor:"window"},"Time Window"))),t.minSpanInputContainerRef=this.makeRef(u("div",{className:"display-mode-option"+(e?"":" selected"),onclick:()=>t.selectMode("pastMins")},this.fromRef(t.minSpanInputRef),u("label",{htmlFor:"minSpan"},"Rolling Minute Span"))),u("div",null,this.fromRef(t.windowInputContainerRef),this.fromRef(t.minSpanInputContainerRef))}current(){return this.gridWidgetSkeleton.current()}}const R=class extends d{constructor(t){super(),this.display=document.createElement("span"),this.display=u(this.MainBody,{ctx:this}),this.skeleton=new c({...t,title:"Legend:",className:"legend-widget",body:this.display}),h().subscribeStoreVal("highlightedTimeseries",(()=>this.updateDisplay())),this.updateDisplay()}updateDisplay(){this.fromRef(this.bodyRef).replaceWith(u(this.MainBody,{ctx:this}))}MainBody({ctx:t}){return t.bodyRef=t.makeRef(u("div",null,u(t.TimeseriesList,{ctx:t}))),t.fromRef(t.bodyRef)}TimeseriesList({ctx:t}){const e=l().highlightedTimeseries;return u("ul",null,l().rightTimeseries.map((i=>u(t.TimeseriesLegendEntry,{timeseries:i,highlighted:i.getName()===e}))),l().leftTimeseries.map((i=>u(t.TimeseriesLegendEntry,{timeseries:i,highlighted:i.getName()===e}))))}TimeseriesLegendEntry({timeseries:t,highlighted:e}){const i=new Option;return i.style.color=t.getColour(),u("li",{style:"color: "+i.style.color,className:e?"highlighted":"",onmouseover:()=>h().setHighlightedTimeseries(t.getName()),onmouseout:()=>h().setHighlightedTimeseries(null)},t.getName())}current(){return this.skeleton.current()}};const T=class extends d{constructor(){super(),this.element=document.createElement("div"),this.grid=document.createElement("div"),this.messageOverlay=new y,this.setupGrid({width:5,height:10}),this.element.append(Object.assign(document.createElement("h1"),{innerText:"Ledda's Room Climate"}),this.grid,this.messageOverlay.current()),this.element.className="center"}setupGrid(t){this.setupWidgets(),this.grid.append(this.legendWidget.current(),this.chartWidget.current(),this.displayModeSettingsWidget.current(),this.selectModeWidget.current(),this.timerWidget.current(),this.timezoneWidget.current()),this.grid.className="main-content-grid",this.grid.style.gridTemplateRows=`repeat(${t.height}, 1fr)`,this.grid.style.gridTemplateColumns=`repeat(${t.width}, 1fr)`}setupWidgets(){this.displayModeSettingsWidget=new f({row:"auto",col:5,width:1,height:3}),this.selectModeWidget=new x({row:"auto",col:5,width:1,height:2}),this.timezoneWidget=new p({row:"auto",col:5,width:1,height:1}),this.timerWidget=new g({row:"auto",col:5,width:1,height:2}),this.legendWidget=new R({row:"auto",col:5,width:1,height:2}),this.chartWidget=new w({row:1,col:1,width:4,height:10})}bootstrap(t){document.getElementById(t).append(this.element),this.chartWidget.updateDimensions()}current(){return this.element}};const S=class{constructor(t){let e;if(this.fetching=!1,this.extrema={minVal:1/0,maxVal:-1/0,minIndex:1/0,maxIndex:-1/0},this.cache=new Int32Array,this.loader=t.loader,this.name=t.name,this.tolerance=t.tolerance??0,t.colour){const i=(new Option).style;i.color=t.colour,e=i.color===t.colour?t.colour:null}this.colour=e??`rgb(${150*Math.random()},${150*Math.random()},${150*Math.random()})`,t.valueRangeOverride&&(this.valExtremaOverride={...t.valueRangeOverride})}getExtrema(){return Object.assign({},this.extrema)}getExtremaInRange(t,e){let i=-1/0,s=1/0;for(let n=this.findIndexInCache(t)-1;ni&&(i=this.cache[n]);return{minIndex:this.extrema.minIndex,maxIndex:this.extrema.maxIndex,maxVal:this.valExtremaOverride.high>i?this.valExtremaOverride.high:i,minVal:this.valExtremaOverride.lowt+this.tolerance&&(this.fetching=!0,await this.fetchPrior(this.cache[1],t)),this.cache[this.currentEndPointer-1]t+i&&(i=e-t);const s=await this.loader(t,t+i),n=new Int32Array(this.cache.length+s.length);n.set(this.cache,0),n.set(s,this.currentEndPointer),this.cache=n,this.currentEndPointer+=s.length,this.updateExtremaFrom(s)}catch(t){throw new Error("Error fetching anterior data: "+t)}}async fetchPrior(t,e){try{let i=2*(this.cache[this.currentEndPointer-1]-this.cache[1]);ethis.extrema.maxVal&&(this.extrema.maxVal=t[e]);for(let e=1;ethis.extrema.maxIndex&&(this.extrema.maxIndex=t[e])}findIndexInCache(t){return this.findIndexInCacheBinary(t)}findIndexInCacheLinear(t){for(let e=1;e3?e-3:e-1;return this.cache.length-2}findIndexInCacheBinary(t,e=0,i=this.currentEndPointer/2){if(i-e==1)return 2*e+1;{const s=Math.floor((i+e)/2),n=this.cache[2*s+1];return n>t?this.findIndexInCacheBinary(t,e,s):nb("co2",t,e),tolerance:t,valueRangeOverride:{high:800,low:400}})),i.Right),h().addTimeseriesToScale((t=>new S({name:"Temperature (°C)",loader:(t,e)=>b("temp",t,e),tolerance:t,valueRangeOverride:{high:30,low:10}}))(l().updateIntervalSeconds),i.Left),h().addTimeseriesToScale((t=>new S({name:"Humidity (%)",loader:(t,e)=>b("humidity",t,e),tolerance:t,valueRangeOverride:{high:75,low:40}}))(l().updateIntervalSeconds),i.Left);(new T).bootstrap("root")}let M;document.onreadystatechange=async()=>{await D(),h().setDocumentReady(!0),h().on("stateChange",(()=>function(t,e=300){return(...i)=>{clearTimeout(M),M=setTimeout((()=>{t.apply(this,i)}),e)}}((()=>function(){const t=h().serialiseState(),e=`${window.location.pathname}${""!==t?"?"+t:""}`;window.history.replaceState("","",e)}()))())),window.store=h(),document.onreadystatechange=null}})();