diff --git a/app-dist/static/dashboard.js b/app-dist/static/dashboard.js index de7d6a5..3727f49 100644 --- a/app-dist/static/dashboard.js +++ b/app-dist/static/dashboard.js @@ -1,1259 +1 @@ -/* - * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). - * This devtool is not neither made for production nor for readable output files. - * It uses "eval()" calls to create a separate source file in the browser devtools. - * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) - * or disable the default devtool with "devtool: false". - * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). - */ -/******/ (() => { // webpackBootstrap -/******/ "use strict"; -/******/ var __webpack_modules__ = ({ - -/***/ "./src/ClimateChart.ts": -/*!*****************************!*\ - !*** ./src/ClimateChart.ts ***! - \*****************************/ -/*! namespace exports */ -/*! export ScaleId [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"ScaleId\": () => /* binding */ ScaleId,\n/* harmony export */ \"default\": () => /* binding */ ClimateChart\n/* harmony export */ });\nvar ScaleId;\n(function (ScaleId) {\n ScaleId[ScaleId[\"Left\"] = 0] = \"Left\";\n ScaleId[ScaleId[\"Right\"] = 1] = \"Right\";\n})(ScaleId || (ScaleId = {}));\nconst MIN_PIXELS_PER_POINT = 3;\nclass ClimateChart {\n constructor(context) {\n this.lastMousePos = { x: 0, y: 0 };\n this.indexRange = { start: 0, stop: 0 };\n this.margins = { top: 20, bottom: 20 };\n this.formatTimestamp = (timestamp) => new Date(timestamp * 1000).toLocaleTimeString();\n this.width = 0;\n this.height = 0;\n this.resolution = 1;\n this.ctx = context;\n this.ctx.fillStyle = \"rgb(255,255,255)\";\n this.updateDimensions();\n this.ctx.fillRect(0, 0, this.width, this.height);\n this.ctx.fill();\n this.ctx.translate(0.5, 0.5);\n this.ctx.canvas.onmousemove = (e) => this.handleMouseMove(e);\n this.leftScale = {\n timeseries: [],\n valRange: { high: -Infinity, low: Infinity },\n width: 0,\n };\n this.rightScale = {\n timeseries: [],\n valRange: { high: -Infinity, low: Infinity },\n width: 0,\n };\n }\n updateDimensions() {\n this.width = Number(getComputedStyle(this.ctx.canvas).width.slice(0, -2));\n this.height = Number(getComputedStyle(this.ctx.canvas).height.slice(0, -2));\n }\n addTimeseries(timeseries, scale) {\n if (scale === ScaleId.Left) {\n this.leftScale.timeseries.push(timeseries);\n }\n else {\n this.rightScale.timeseries.push(timeseries);\n }\n }\n setRange(range) {\n this.indexRange.start = range.start;\n this.indexRange.stop = range.stop;\n }\n handleMouseMove(event) {\n const { left: canvasX, top: canvasY } = this.ctx.canvas.getBoundingClientRect();\n this.lastMousePos.x = event.clientX - canvasX;\n this.lastMousePos.y = event.clientY - canvasY;\n this.render();\n }\n render() {\n this.updateDimensions();\n this.clearCanvas();\n this.updateResolution();\n this.setDisplayRangeForScale(this.leftScale);\n this.setDisplayRangeForScale(this.rightScale);\n this.renderRightScale();\n this.leftScale.timeseries.forEach(timeseries => this.renderTimeseries(timeseries, ScaleId.Left));\n this.rightScale.timeseries.forEach(timeseries => this.renderTimeseries(timeseries, ScaleId.Right));\n this.renderLeftScale();\n this.renderTooltips();\n }\n clearCanvas() {\n this.ctx.fillStyle = \"rgb(255,255,255)\";\n this.ctx.fillRect(0, 0, this.width, this.height);\n this.ctx.fill();\n }\n updateResolution() {\n const chartWidth = (this.width - this.rightScale.width - this.leftScale.width);\n const points = this.rightScale.timeseries[0].cachedBetween(this.indexRange.start, this.indexRange.stop).length / 2;\n const pixelsPerPoint = chartWidth / points;\n if (pixelsPerPoint < MIN_PIXELS_PER_POINT) {\n this.resolution = Math.ceil(MIN_PIXELS_PER_POINT / pixelsPerPoint);\n }\n else {\n this.resolution = 1;\n }\n }\n renderLeftScale() {\n this.ctx.fillStyle = \"rgb(255,255,255)\";\n this.ctx.fillRect(0, 0, this.leftScale.width, this.height);\n this.ctx.fill();\n this.ctx.strokeStyle = \"rgb(230,230,230)\";\n this.ctx.fillStyle = \"black\";\n const ticks = 20;\n const tickHeight = (this.leftScale.valRange.high - this.leftScale.valRange.low) / ticks;\n let currentTick = this.leftScale.valRange.low - tickHeight;\n for (let i = 0; i <= ticks; i++) {\n currentTick += tickHeight;\n const text = currentTick.toFixed(2);\n const textWidth = this.ctx.measureText(text).width;\n if (textWidth > this.leftScale.width) {\n this.leftScale.width = textWidth + 10;\n }\n const pos = Math.round(this.getY(currentTick, ScaleId.Left));\n this.ctx.fillText(text, 0, pos + 4);\n }\n }\n renderRightScale() {\n this.ctx.strokeStyle = \"rgb(230,230,230)\";\n this.ctx.fillStyle = \"black\";\n const ticks = 20;\n const tickHeight = (this.rightScale.valRange.high - this.rightScale.valRange.low) / ticks;\n let currentTick = this.rightScale.valRange.low - tickHeight;\n for (let i = 0; i <= ticks; i++) {\n currentTick += tickHeight;\n const pos = Math.round(this.getY(currentTick, ScaleId.Right));\n const text = currentTick.toFixed(2);\n const textWidth = this.ctx.measureText(text).width;\n if (textWidth > this.rightScale.width) {\n this.rightScale.width = textWidth;\n }\n this.ctx.fillText(text, this.width - textWidth, pos + 4);\n this.ctx.beginPath();\n this.ctx.moveTo(this.leftScale.width, pos);\n this.ctx.lineTo(this.width - textWidth - 5, pos);\n this.ctx.stroke();\n }\n }\n setDisplayRangeForScale(scale) {\n for (const timeseries of scale.timeseries) {\n const extrema = timeseries.getExtrema();\n if (extrema.maxVal > scale.valRange.high) {\n scale.valRange.high = extrema.maxVal;\n }\n if (extrema.minVal < scale.valRange.low) {\n scale.valRange.low = extrema.minVal;\n }\n }\n }\n renderTooltips(radius = 20) {\n let bestDist = radius;\n let bestTimeseries = this.rightScale.timeseries[0];\n let bestIndex = 0;\n let bestVal = 0;\n let bestScale = ScaleId.Right;\n for (const scaleId of [ScaleId.Left, ScaleId.Right]) {\n for (const timeseries of (scaleId === ScaleId.Right ? this.rightScale : this.leftScale).timeseries) {\n const cache = timeseries.cachedBetween(this.getIndex(this.lastMousePos.x - radius / 2), this.getIndex(this.lastMousePos.x + radius / 2));\n for (let i = 0; i < cache.length; i += 2) {\n const y = this.getY(cache[i], scaleId);\n if (y + radius / 2 >= this.lastMousePos.y && y - radius / 2 <= this.lastMousePos.y) {\n const x = this.getX(cache[i + 1]);\n const dist = Math.sqrt((y - this.lastMousePos.y) ** 2 + (x - this.lastMousePos.x) ** 2);\n if (dist < bestDist) {\n bestDist = dist;\n bestTimeseries = timeseries;\n bestIndex = cache[i + 1];\n bestVal = cache[i];\n bestScale = scaleId;\n }\n }\n }\n }\n }\n if (bestDist < 20) {\n this.renderTooltip(`${bestTimeseries.getName()} - (${bestVal.toFixed(2)}, ${this.formatTimestamp(bestIndex)})`, this.getX(bestIndex), this.getY(bestVal, bestScale));\n }\n }\n setTimestampFormatter(formatter) {\n this.formatTimestamp = formatter;\n }\n getX(index) {\n return (index - this.indexRange.start) / (this.indexRange.stop - this.indexRange.start) * (this.width - this.rightScale.width - this.leftScale.width) + this.leftScale.width;\n }\n getY(value, scale) {\n const valRange = scale === ScaleId.Left ? this.leftScale.valRange : this.rightScale.valRange;\n return this.height - (value - valRange.low) / (valRange.high - valRange.low) * (this.height - this.margins.bottom - this.margins.top) - this.margins.top;\n }\n getIndex(x) {\n return ((x - this.leftScale.width) / (this.width - this.leftScale.width - this.rightScale.width)) * (this.indexRange.stop - this.indexRange.start) + this.indexRange.start;\n }\n getValue(y, scale) {\n const valRange = scale === ScaleId.Left ? this.leftScale.valRange : this.rightScale.valRange;\n return ((this.height - y) / this.height) * (valRange.high - valRange.low) + valRange.low;\n }\n renderTimeseries(timeseries, scale) {\n const timeseriesPoints = timeseries.cachedBetween(this.indexRange.start, this.indexRange.stop);\n this.ctx.strokeStyle = timeseries.getColour();\n let y = this.getY(timeseriesPoints[0], scale);\n let x = this.getX(timeseriesPoints[1]);\n for (let i = 0; i < timeseriesPoints.length; i += 2 * this.resolution) {\n this.ctx.beginPath();\n this.ctx.moveTo(Math.round(x), Math.round(y));\n y = 0;\n x = 0;\n for (let j = 0; j < this.resolution * 2 && (j + 2 < timeseriesPoints.length); j += 2) {\n y += timeseriesPoints[i + j];\n x += timeseriesPoints[i + 1 + j];\n }\n y = this.getY(y / this.resolution, scale);\n x = this.getX(x / this.resolution);\n this.ctx.lineTo(Math.round(x), Math.round(y));\n this.ctx.stroke();\n if (this.resolution === 1) {\n this.ctx.beginPath();\n this.ctx.ellipse(x, y, 2, 2, 0, 0, 2 * Math.PI);\n this.ctx.stroke();\n }\n }\n }\n renderTooltip(text, x, y) {\n this.ctx.strokeStyle = \"rgb(255,0,0)\";\n this.ctx.beginPath();\n this.ctx.ellipse(x, y, 5, 5, 0, 0, 2 * Math.PI);\n this.ctx.stroke();\n const measurements = this.ctx.measureText(text);\n const textHeight = measurements.actualBoundingBoxAscent + measurements.actualBoundingBoxDescent;\n const height = textHeight + 10;\n const width = measurements.width + 10;\n if (x + width > this.width) {\n x -= width;\n }\n if (y + height > this.height) {\n y -= height;\n }\n this.ctx.fillStyle = \"rgb(255,255,255)\";\n this.ctx.strokeStyle = \"rgb(0,0,0)\";\n this.ctx.fillRect(Math.round(x), Math.round(y), Math.round(width), Math.round(height));\n this.ctx.strokeRect(Math.round(x), Math.round(y), Math.round(width), Math.round(height));\n this.ctx.fillStyle = \"rgb(0,0,0)\";\n this.ctx.fillText(text, Math.round(x + 5), Math.round(y + textHeight + 5));\n }\n}\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ClimateChart.ts?"); - -/***/ }), - -/***/ "./src/JSXFactory.ts": -/*!***************************!*\ - !*** ./src/JSXFactory.ts ***! - \***************************/ -/*! namespace exports */ -/*! export createElement [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"createElement\": () => /* binding */ createElement\n/* harmony export */ });\nfunction createElement(tagName, attributes, ...children) {\n if (typeof tagName === \"function\") {\n if (children.length >= 1) {\n return tagName({ ...attributes }, children);\n }\n else {\n return tagName({ ...attributes });\n }\n }\n else {\n return standardElement(tagName, attributes, ...children);\n }\n}\nfunction standardElement(tagName, attributes, ...children) {\n const element = document.createElement(tagName);\n for (const key in attributes) {\n const attributeValue = attributes[key];\n if (key.startsWith(\"on\") && typeof attributeValue === \"function\") {\n element.addEventListener(key.substring(2), attributeValue);\n }\n else if (typeof attributeValue === \"boolean\" && attributeValue === true) {\n element.setAttribute(key, \"\");\n }\n else if (typeof attributeValue === \"string\") {\n if (key === \"className\") {\n element.setAttribute(\"class\", attributes[key]);\n }\n else {\n element.setAttribute(key, attributeValue);\n }\n }\n }\n element.append(...createChildren(children));\n return element;\n}\nfunction createChildren(children) {\n const childrenNodes = [];\n for (const child of children) {\n if (typeof child === \"undefined\" || child === null || typeof child === \"boolean\") {\n continue;\n }\n if (Array.isArray(child)) {\n childrenNodes.push(...createChildren(child));\n }\n else if (typeof child === \"string\") {\n childrenNodes.push(document.createTextNode(String(child)));\n }\n else if (child instanceof Node) {\n childrenNodes.push(child);\n }\n }\n return childrenNodes;\n}\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/JSXFactory.ts?"); - -/***/ }), - -/***/ "./src/StateStore.ts": -/*!***************************!*\ - !*** ./src/StateStore.ts ***! - \***************************/ -/*! namespace exports */ -/*! export AppStateError [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export AppStore [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export getAppState [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export initStore [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"AppStateError\": () => /* binding */ AppStateError,\n/* harmony export */ \"initStore\": () => /* binding */ initStore,\n/* harmony export */ \"AppStore\": () => /* binding */ AppStore,\n/* harmony export */ \"getAppState\": () => /* binding */ getAppState\n/* harmony export */ });\n/* harmony import */ var _ClimateChart__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ClimateChart */ \"./src/ClimateChart.ts\");\n;\nclass AppStateError extends Error {\n constructor(message) {\n super(message);\n this.name = \"AppStateError\";\n }\n}\nclass AppStateStore {\n constructor(initialState) {\n this.loaders = 0;\n this.state = initialState;\n const subscriptions = {};\n for (const key in this.state) {\n subscriptions[key] = [];\n }\n this.eventCallbacks = { newTimeseries: [], timeseriesUpdated: [] };\n this.subscriptions = subscriptions;\n this.init();\n setInterval(() => this.getNewTimeseriesData(), this.state.updateIntervalSeconds * 1000);\n }\n async init() {\n await this.updateTimeseriesFromSettings();\n await this.getNewTimeseriesData();\n }\n addTimeseries(timeseries, scale) {\n const group = scale === _ClimateChart__WEBPACK_IMPORTED_MODULE_0__.ScaleId.Left ? this.state.leftTimeseries : this.state.rightTimeseries;\n if (group.indexOf(timeseries) >= 0) {\n throw new AppStateError(\"Timeseries has already been added!\");\n }\n if (scale === _ClimateChart__WEBPACK_IMPORTED_MODULE_0__.ScaleId.Left) {\n group.push(timeseries);\n }\n else {\n group.push(timeseries);\n }\n this.notifyStoreVal(scale === _ClimateChart__WEBPACK_IMPORTED_MODULE_0__.ScaleId.Left ? \"leftTimeseries\" : \"rightTimeseries\");\n this.eventCallbacks[\"newTimeseries\"].forEach(cb => cb(timeseries, scale));\n this.updateTimeseriesFromSettings();\n }\n notifyStoreVal(subscribedValue, newValue, oldValue) {\n for (const subscriptionCallback of this.subscriptions[subscribedValue]) {\n new Promise(() => subscriptionCallback(newValue, oldValue));\n }\n }\n async updateTimeseriesFromSettings() {\n let start;\n let stop;\n if (this.state.displayMode === \"window\") {\n start = this.state.displayWindow.start;\n stop = this.state.displayWindow.stop;\n }\n else {\n start = this.state.lastUpdateTime - this.state.minutesDisplayed * 60;\n stop = this.state.lastUpdateTime;\n }\n this.addLoad();\n for (const timeseries of this.state.leftTimeseries) {\n await timeseries.updateFromWindow(start, stop);\n }\n for (const timeseries of this.state.rightTimeseries) {\n await timeseries.updateFromWindow(start, stop);\n }\n this.finishLoad();\n this.notifyAllTimeseriesUpdated();\n }\n async getNewTimeseriesData() {\n const updateTime = new Date().getTime() / 1000;\n this.addLoad();\n for (const timeseries of this.state.leftTimeseries) {\n await timeseries.getLatest();\n }\n for (const timeseries of this.state.rightTimeseries) {\n await timeseries.getLatest();\n }\n this.finishLoad();\n this.setLastUpdateTime(updateTime);\n this.notifyAllTimeseriesUpdated();\n }\n notifyAllTimeseriesUpdated() {\n for (const timeseries of this.state.leftTimeseries) {\n this.notifyStoreVal(\"leftTimeseries\");\n this.eventCallbacks[\"timeseriesUpdated\"].forEach(cb => cb(timeseries));\n }\n for (const timeseries of this.state.rightTimeseries) {\n this.notifyStoreVal(\"rightTimeseries\");\n this.eventCallbacks[\"timeseriesUpdated\"].forEach(cb => cb(timeseries));\n }\n }\n getState() {\n return this.state;\n }\n subscribeStoreVal(dataName, callback) {\n this.subscriptions[dataName].push(callback);\n }\n on(event, callback) {\n this.eventCallbacks[event].push(callback);\n }\n setDisplayMode(mode) {\n this.state.displayMode = mode;\n this.notifyStoreVal(\"displayMode\");\n }\n setDisplayWindow(newWin) {\n if (newWin.start < newWin.stop) {\n if (newWin.stop < this.state.lastUpdateTime) {\n this.state.displayWindow = { ...newWin };\n this.notifyStoreVal(\"displayWindow\");\n this.updateTimeseriesFromSettings();\n }\n }\n else {\n console.warn(`Invalid display window from ${newWin.start} to ${newWin.stop}`);\n }\n }\n setMinutesDisplayed(mins) {\n if (mins > 0) {\n this.state.minutesDisplayed = Math.ceil(mins);\n this.notifyStoreVal(\"minutesDisplayed\");\n this.updateTimeseriesFromSettings();\n }\n else {\n throw new AppStateError(`Invalid minutes passed: ${mins}`);\n }\n }\n setUtcOffset(newOffset) {\n if (Math.floor(newOffset) === newOffset && newOffset <= 14 && newOffset >= -12) {\n this.state.utcOffset = newOffset;\n }\n else {\n console.warn(`Invalid UTC offset: ${newOffset}`);\n if (newOffset > 14) {\n this.state.utcOffset = 14;\n }\n else if (newOffset < -12) {\n this.state.utcOffset = -12;\n }\n else {\n this.state.utcOffset = Math.floor(newOffset);\n }\n }\n this.notifyStoreVal(\"utcOffset\");\n }\n setLastUpdateTime(newTime) {\n if (this.state.lastUpdateTime <= newTime) {\n this.state.lastUpdateTime = newTime;\n this.notifyStoreVal(\"lastUpdateTime\");\n }\n else {\n throw new AppStateError(`Bad new update time was before last update time. Old: ${this.state.lastUpdateTime}, New: ${newTime}`);\n }\n }\n setOverlayText(text) {\n this.state.overlayText = text;\n this.notifyStoreVal(\"overlayText\");\n }\n addLoad() {\n this.loaders += 1;\n this.state.isLoading = this.loaders > 0;\n this.notifyStoreVal(\"isLoading\");\n }\n finishLoad() {\n this.loaders -= 1;\n this.state.isLoading = this.loaders > 0;\n this.notifyStoreVal(\"isLoading\");\n }\n fatalError(err) {\n if (!this.state.fatalError) {\n this.state.fatalError = err;\n this.notifyStoreVal(\"fatalError\");\n }\n }\n setDocumentReady(isReady) {\n this.state.documentReady = isReady;\n this.notifyStoreVal(\"documentReady\");\n }\n}\nlet store;\nasync function initStore(initialState) {\n store = new AppStateStore(initialState);\n return store;\n}\nfunction AppStore() {\n if (store) {\n return store;\n }\n else {\n throw new AppStateError(\"Store not yet initialised!\");\n }\n}\nfunction getAppState() {\n if (store) {\n return store.getState();\n }\n else {\n throw new AppStateError(\"Store not yet initialised!\");\n }\n}\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/StateStore.ts?"); - -/***/ }), - -/***/ "./src/Timeseries.ts": -/*!***************************!*\ - !*** ./src/Timeseries.ts ***! - \***************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\nclass Timeseries {\n constructor(name, loader, tolerance) {\n this.fetching = false;\n this.extrema = {\n minVal: Infinity,\n maxVal: -Infinity,\n minIndex: Infinity,\n maxIndex: -Infinity,\n };\n this.cache = new Int32Array();\n this.loader = loader;\n this.name = name;\n this.tolerance = tolerance ?? 0;\n this.colour = `rgb(${Math.random() * 150},${Math.random() * 150},${Math.random() * 150})`;\n }\n getExtrema() {\n return Object.assign(this.extrema);\n }\n getName() {\n return this.name;\n }\n getCache() {\n return this.cache;\n }\n getColour() {\n return this.colour;\n }\n cachedBetween(start, stop) {\n if (this.cache.length <= 0) {\n return new Int32Array();\n }\n else {\n return this.cache.slice(this.findIndexInCache(start) - 1, this.findIndexInCache(stop));\n }\n }\n append(value, index) {\n if (this.cache.length < this.currentEndPointer + 2) {\n const newCache = new Int32Array(this.cache.length * 2);\n newCache.set(this.cache, 0);\n newCache.set([value, index], this.currentEndPointer);\n this.cache = newCache;\n }\n }\n async updateFromWindow(start, stop) {\n if (!this.fetching) {\n try {\n if (this.cache.length === 0) {\n this.fetching = true;\n await this.fullFetch(start, stop);\n }\n if (this.cache[1] > start + this.tolerance) {\n this.fetching = true;\n await this.fetchPrior(this.cache[1], start);\n }\n if (this.cache[this.currentEndPointer - 1] < stop - this.tolerance) {\n this.fetching = true;\n await this.fetchAfter(this.cache[this.currentEndPointer - 1], stop);\n }\n }\n catch (e) {\n throw new Error(`Error fetching timeseries data: ${e}`);\n }\n }\n this.fetching = false;\n }\n async getLatest() {\n this.fetching = true;\n try {\n await this.fetchAfter(this.cache[this.currentEndPointer - 1]);\n }\n catch (e) {\n throw new Error(`Error fetching timeseries data: ${e}`);\n }\n this.fetching = false;\n }\n async fullFetch(start, stop) {\n try {\n this.cache = await this.loader(start, stop);\n this.currentEndPointer = this.cache.length;\n this.updateExtremaFrom(this.cache);\n }\n catch (e) {\n throw new Error(`Error fully fetching data: ${e}`);\n }\n }\n async fetchAfter(after, atLeastUntil) {\n try {\n let forwardDist = 2 * (this.cache[this.currentEndPointer - 1] - this.cache[1]);\n if (atLeastUntil && (atLeastUntil > after + forwardDist)) {\n forwardDist = atLeastUntil - after;\n }\n const result = await this.loader(after, after + forwardDist);\n const newCache = new Int32Array(this.cache.length + result.length);\n newCache.set(this.cache, 0);\n newCache.set(result, this.currentEndPointer);\n this.cache = newCache;\n this.currentEndPointer += result.length;\n this.updateExtremaFrom(result);\n }\n catch (e) {\n throw new Error(`Error fetching anterior data: ${e}`);\n }\n }\n async fetchPrior(priorTo, atLeastUntil) {\n try {\n let backDist = 2 * (this.cache[this.currentEndPointer - 1] - this.cache[1]);\n if (atLeastUntil < priorTo - backDist) {\n backDist = priorTo - atLeastUntil;\n }\n const result = await this.loader(priorTo - backDist, priorTo);\n const newCache = new Int32Array(this.cache.length + result.length);\n newCache.set(result, 0);\n newCache.set(this.cache, result.length);\n this.cache = newCache;\n this.currentEndPointer += result.length;\n this.updateExtremaFrom(result);\n }\n catch (e) {\n throw new Error(`Error fetching anterior data: ${e}`);\n }\n }\n updateExtremaFrom(data) {\n for (let i = 0; i < data.length; i += 2) {\n if (data[i] < this.extrema.minVal) {\n this.extrema.minVal = data[i];\n }\n if (data[i] > this.extrema.maxVal) {\n this.extrema.maxVal = data[i];\n }\n }\n for (let i = 1; i < this.cache.length; i += 2) {\n if (data[i] < this.extrema.minIndex) {\n this.extrema.minIndex = data[i];\n }\n if (data[i] > this.extrema.maxIndex) {\n this.extrema.maxIndex = data[i];\n }\n }\n }\n findIndexInCache(soughtIndex) {\n return this.findIndexInCacheBinary(soughtIndex);\n }\n findIndexInCacheLinear(soughtIndex) {\n for (let i = 1; i < this.cache.length; i += 2) {\n if (soughtIndex < this.cache[i]) {\n return i > 3 ? i - 3 : i - 1;\n }\n }\n return this.cache.length - 2;\n }\n findIndexInCacheBinary(soughtIndex, listStart = 0, listStop = (this.currentEndPointer / 2)) {\n if (listStop - listStart === 1) {\n return listStart * 2 + 1;\n }\n else {\n const middle = Math.floor((listStop + listStart) / 2);\n const val = this.cache[middle * 2 + 1];\n if (val > soughtIndex) {\n return this.findIndexInCacheBinary(soughtIndex, listStart, middle);\n }\n else if (val < soughtIndex) {\n return this.findIndexInCacheBinary(soughtIndex, middle, listStop);\n }\n else {\n return middle * 2 + 1;\n }\n }\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Timeseries);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/Timeseries.ts?"); - -/***/ }), - -/***/ "./src/errors.ts": -/*!***********************!*\ - !*** ./src/errors.ts ***! - \***********************/ -/*! namespace exports */ -/*! export ClayPIDashboardError [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"ClayPIDashboardError\": () => /* binding */ ClayPIDashboardError\n/* harmony export */ });\nclass ClayPIDashboardError extends Error {\n constructor(message, displayMessage) {\n super(message);\n this.name = \"ClayPIError\";\n this.displayMessage = displayMessage ?? message;\n }\n}\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/errors.ts?"); - -/***/ }), - -/***/ "./src/main.ts": -/*!*********************!*\ - !*** ./src/main.ts ***! - \*********************/ -/*! namespace exports */ -/*! export config [provided] [no usage info] [missing usage info prevents renaming] -> ./src/config.json .default */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.d, __webpack_require__.r, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"config\": () => /* reexport default export from named module */ _config_json__WEBPACK_IMPORTED_MODULE_0__\n/* harmony export */ });\n/* harmony import */ var _config_json__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./config.json */ \"./src/config.json\");\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _ui_components_AppUI__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ui-components/AppUI */ \"./src/ui-components/AppUI.ts\");\n/* harmony import */ var _Timeseries__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Timeseries */ \"./src/Timeseries.ts\");\n/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./errors */ \"./src/errors.ts\");\n/* harmony import */ var _ClimateChart__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ClimateChart */ \"./src/ClimateChart.ts\");\n;\n\n\n\n\n\n\nfunction getDisplayedMinutes() {\n let minutesDisplayed = _config_json__WEBPACK_IMPORTED_MODULE_0__.defaultMinuteSpan;\n const argsStart = window.location.search.search(/\\?minute-span=/);\n if (argsStart !== -1) {\n const parsedMins = Number(window.location.search.substring(13));\n if (!isNaN(parsedMins) && parsedMins > 0) {\n minutesDisplayed = parsedMins;\n }\n }\n return minutesDisplayed;\n}\nfunction getUtcOffset() {\n return -(new Date().getTimezoneOffset() / 60);\n}\nasync function init() {\n const now = new Date().getTime() / 1000;\n await (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.initStore)({\n overlayText: \"\",\n lastUpdateTime: now,\n minutesDisplayed: getDisplayedMinutes(),\n utcOffset: getUtcOffset(),\n dataEndpointBase: _config_json__WEBPACK_IMPORTED_MODULE_0__.dataEndpoint,\n isLoading: false,\n updateIntervalSeconds: _config_json__WEBPACK_IMPORTED_MODULE_0__.reloadIntervalSec,\n displayMode: \"pastMins\",\n fatalError: null,\n displayWindow: { start: now - getDisplayedMinutes() * 60, stop: now },\n documentReady: false,\n leftTimeseries: [],\n rightTimeseries: [],\n });\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().addTimeseries(new _Timeseries__WEBPACK_IMPORTED_MODULE_3__.default(\"temp\", (start, stop) => loadClimateTimeseriesData(\"temp\", start, stop), (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().updateIntervalSeconds), _ClimateChart__WEBPACK_IMPORTED_MODULE_5__.ScaleId.Left);\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().addTimeseries(new _Timeseries__WEBPACK_IMPORTED_MODULE_3__.default(\"humidity\", (start, stop) => loadClimateTimeseriesData(\"humidity\", start, stop), (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().updateIntervalSeconds), _ClimateChart__WEBPACK_IMPORTED_MODULE_5__.ScaleId.Left);\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().addTimeseries(new _Timeseries__WEBPACK_IMPORTED_MODULE_3__.default(\"co2\", (start, stop) => loadClimateTimeseriesData(\"co2\", start, stop), (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().updateIntervalSeconds), _ClimateChart__WEBPACK_IMPORTED_MODULE_5__.ScaleId.Right);\n const ui = new _ui_components_AppUI__WEBPACK_IMPORTED_MODULE_2__.default();\n ui.bootstrap(\"root\");\n}\nasync function loadClimateTimeseriesData(dataType, start, stop) {\n const endpoint = `${(0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().dataEndpointBase}/timeseries/${dataType}${start && `?from=${start * 1000}`}${stop && `&to=${stop * 1000}`}`;\n try {\n const response = await fetch(endpoint, { headers: {\n \"Content-Type\": \"application/octet-stream\",\n } });\n const reader = await response.body.getReader();\n let receivedLength = 0;\n const chunks = [];\n let finishedReading = false;\n while (!finishedReading) {\n const chunk = await reader.read();\n finishedReading = chunk.done;\n if (!finishedReading) {\n const chunkBuffer = new Int32Array(chunk.value.buffer);\n chunks.push(chunkBuffer);\n receivedLength += chunkBuffer.length;\n }\n }\n const data = new Int32Array(receivedLength);\n let position = 0;\n for (const chunk of chunks) {\n data.set(chunk, position);\n position += chunk.length;\n }\n return data;\n }\n catch (e) {\n const message = \"Error fetching timerseries data from the server\";\n throw new _errors__WEBPACK_IMPORTED_MODULE_4__.ClayPIDashboardError(`${message}: ${e}`, message);\n }\n}\ndocument.onreadystatechange = async () => {\n await init();\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDocumentReady(true);\n // @ts-ignore\n window.store = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)();\n document.onreadystatechange = null;\n};\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/main.ts?"); - -/***/ }), - -/***/ "./src/ui-components/AppUI.ts": -/*!************************************!*\ - !*** ./src/ui-components/AppUI.ts ***! - \************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _TimezoneWidget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./TimezoneWidget */ \"./src/ui-components/TimezoneWidget.tsx\");\n/* harmony import */ var _DisplayModeWidget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./DisplayModeWidget */ \"./src/ui-components/DisplayModeWidget.tsx\");\n/* harmony import */ var _TimerWidget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./TimerWidget */ \"./src/ui-components/TimerWidget.tsx\");\n/* harmony import */ var _ClimateChartWidget__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ClimateChartWidget */ \"./src/ui-components/ClimateChartWidget.ts\");\n/* harmony import */ var _MessageOverlay__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./MessageOverlay */ \"./src/ui-components/MessageOverlay.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n/* harmony import */ var _SelectDisplayModeWidget__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./SelectDisplayModeWidget */ \"./src/ui-components/SelectDisplayModeWidget.tsx\");\n;\n\n\n\n\n\n\nclass AppUI extends _UIComponent__WEBPACK_IMPORTED_MODULE_5__.default {\n constructor() {\n super();\n this.element = document.createElement(\"div\");\n this.grid = document.createElement(\"div\");\n this.messageOverlay = new _MessageOverlay__WEBPACK_IMPORTED_MODULE_4__.default();\n this.setupGrid({ width: 5, height: 10 });\n this.element.append(Object.assign(document.createElement(\"h1\"), { innerText: \"Ledda's Room Climate\" }), this.grid, this.messageOverlay.current());\n this.element.className = \"center\";\n }\n setupGrid(size) {\n this.setupWidgets();\n this.grid.append(this.chartWidget.current(), this.displayModeSettingsWidget.current(), this.selectModeWidget.current(), this.timerWidget.current(), this.timezoneWidget.current());\n this.grid.className = \"main-content-grid\";\n this.grid.style.gridTemplateRows = `repeat(${size.height}, 1fr)`;\n this.grid.style.gridTemplateColumns = `repeat(${size.width}, 1fr)`;\n }\n setupWidgets() {\n this.displayModeSettingsWidget = new _DisplayModeWidget__WEBPACK_IMPORTED_MODULE_1__.default({\n row: \"auto\", col: 5, width: 1, height: 3,\n });\n this.selectModeWidget = new _SelectDisplayModeWidget__WEBPACK_IMPORTED_MODULE_6__.default({\n row: \"auto\", col: 5, width: 1, height: 2,\n });\n this.timezoneWidget = new _TimezoneWidget__WEBPACK_IMPORTED_MODULE_0__.default({\n row: \"auto\", col: 5, width: 1, height: 2,\n });\n this.timerWidget = new _TimerWidget__WEBPACK_IMPORTED_MODULE_2__.default({\n row: \"auto\", col: 5, width: 1, height: 3,\n });\n this.chartWidget = new _ClimateChartWidget__WEBPACK_IMPORTED_MODULE_3__.default({\n row: 1, col: 1, width: 4, height: 10,\n });\n }\n bootstrap(rootNode) {\n document.getElementById(rootNode).append(this.element);\n this.chartWidget.updateDimensions();\n }\n current() {\n return this.element;\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (AppUI);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/AppUI.ts?"); - -/***/ }), - -/***/ "./src/ui-components/ClimateChartWidget.ts": -/*!*************************************************!*\ - !*** ./src/ui-components/ClimateChartWidget.ts ***! - \*************************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _GridWidget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./GridWidget */ \"./src/ui-components/GridWidget.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n/* harmony import */ var _ClimateChart__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../ClimateChart */ \"./src/ClimateChart.ts\");\n;\n\n\n\nclass ClimateChartWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_2__.default {\n constructor(gridProps) {\n super();\n this.chart = null;\n this.displayMode = \"pastMins\";\n this.canvasElement = document.createElement(\"canvas\");\n this.initialised = false;\n this.canvasElement.className = \"chart-canvas\";\n this.skeleton = new _GridWidget__WEBPACK_IMPORTED_MODULE_1__.default({\n ...gridProps,\n body: this.canvasElement,\n });\n const now = new Date().getTime() / 1000;\n this.latestSnapshotInChartTime = now - (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().minutesDisplayed * 60;\n this.setupListeners();\n }\n updateDimensions() {\n const skelStyle = getComputedStyle(this.skeleton.current());\n this.canvasElement.height = this.skeleton.current().clientHeight\n - Number(skelStyle.paddingTop.slice(0, -2))\n - Number(skelStyle.paddingBottom.slice(0, -2));\n this.canvasElement.width = this.skeleton.current().clientWidth\n - Number(skelStyle.paddingLeft.slice(0, -2))\n - Number(skelStyle.paddingRight.slice(0, -2));\n }\n setupListeners() {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"displayMode\", () => this.updateDisplayMode());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"minutesDisplayed\", () => this.rerender());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"displayWindow\", () => this.rerender());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().on(\"timeseriesUpdated\", () => this.rerender());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().on(\"newTimeseries\", (timeseries) => this.chart.addTimeseries(timeseries));\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"documentReady\", () => this.initChart());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"utcOffset\", () => this.updateTimezone());\n }\n updateTimezone() {\n const offset = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().utcOffset * 60 * 60 * 1000;\n this.chart.setTimestampFormatter((timestamp) => new Date(timestamp * 1000 + offset).toLocaleTimeString());\n }\n async initChart() {\n try {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().addLoad();\n const ctx = this.canvasElement.getContext(\"2d\", { alpha: false });\n this.chart = new _ClimateChart__WEBPACK_IMPORTED_MODULE_3__.default(ctx);\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().leftTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, _ClimateChart__WEBPACK_IMPORTED_MODULE_3__.ScaleId.Left));\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().rightTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, _ClimateChart__WEBPACK_IMPORTED_MODULE_3__.ScaleId.Right));\n await this.rerender();\n this.initialised = true;\n }\n catch (e) {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().fatalError(e);\n }\n finally {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().finishLoad();\n }\n }\n async updateDisplayMode() {\n this.displayMode = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().displayMode;\n await this.rerender();\n }\n async rerender() {\n if (!this.initialised) {\n return;\n }\n let start;\n let stop;\n if (this.displayMode === \"window\") {\n start = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().displayWindow.start;\n stop = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().displayWindow.stop;\n }\n else if (this.displayMode === \"pastMins\") {\n const mins = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().minutesDisplayed;\n start = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().lastUpdateTime - mins * 60;\n stop = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().lastUpdateTime;\n }\n this.chart.setRange({ start, stop });\n this.chart.render();\n }\n current() {\n return this.skeleton.current();\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ClimateChartWidget);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/ClimateChartWidget.ts?"); - -/***/ }), - -/***/ "./src/ui-components/DisplayModeWidget.tsx": -/*!*************************************************!*\ - !*** ./src/ui-components/DisplayModeWidget.tsx ***! - \*************************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _GridWidget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./GridWidget */ \"./src/ui-components/GridWidget.ts\");\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _JSXFactory__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../JSXFactory */ \"./src/JSXFactory.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n;\n\n\n\nclass DisplayModeWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_3__.default {\n constructor(gridProps) {\n super();\n this.mainDisplay = this.MainDisplay({ ctx: this });\n this.skeleton = new _GridWidget__WEBPACK_IMPORTED_MODULE_0__.default({\n ...gridProps,\n title: \"Displaying:\",\n body: this.mainDisplay,\n });\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().subscribeStoreVal(\"minutesDisplayed\", () => this.updateDisplay());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().subscribeStoreVal(\"displayMode\", () => this.updateDisplay());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().subscribeStoreVal(\"displayWindow\", () => this.updateDisplay());\n }\n WindowStartTime({ ctx }) {\n ctx.windowStartTimeRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"display-mode-widget-date\" }, new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayWindow.start).toLocaleString()));\n return ctx.fromRef(ctx.windowStartTimeRef);\n }\n WindowStopTime({ ctx }) {\n ctx.windowStopTimeRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"display-mode-widget-date\" }, new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayWindow.stop).toLocaleString()));\n return ctx.fromRef(ctx.windowStopTimeRef);\n }\n MinutesCounter({ ctx, onclick }) {\n ctx.minsInputRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"input\", { value: (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().minutesDisplayed.toString(), onblur: (e) => ctx.onMinutesCounterInputBlur(e) }));\n ctx.minsCounterRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"min-count\", onclick: onclick }, (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().minutesDisplayed.toString()));\n return ctx.fromRef(ctx.minsCounterRef);\n }\n onMinutesCounterInputBlur(e) {\n const input = Number(e.target.value);\n if (!isNaN(input)) {\n if (input >= 1) {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setMinutesDisplayed(input);\n }\n }\n else {\n e.target.value = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().minutesDisplayed.toString();\n }\n this.fromRef(this.minsInputRef).replaceWith(this.fromRef(this.minsCounterRef));\n }\n MinutesDisplay({ ctx }) {\n return (_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"display-mode-widget-mins\" },\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", null, \"Last\"),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.MinusButton, { onclick: () => {\n const mins = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().minutesDisplayed;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setMinutesDisplayed(mins - 1);\n } }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.MinutesCounter, { ctx: ctx, onclick: () => ctx.onMinutesCounterClick() }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.PlusButton, { onclick: () => {\n const mins = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().minutesDisplayed;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setMinutesDisplayed(mins + 1);\n } }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", null, \"minutes\")));\n }\n onMinutesCounterClick() {\n const input = this.fromRef(this.minsInputRef);\n this.fromRef(this.minsCounterRef).replaceWith(input);\n input.focus();\n input.selectionStart = 0;\n input.selectionEnd = input.value.length;\n }\n MinusButton(props) {\n return _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"minus-button\", onclick: props.onclick });\n }\n PlusButton(props) {\n return _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"plus-button\", onclick: props.onclick });\n }\n WindowedDisplay({ ctx }) {\n return (_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", null,\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", null, \"From\"),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.MinusButton, { onclick: () => {\n const displayWindow = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().displayWindow;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDisplayWindow({ start: displayWindow.start - 60, stop: displayWindow.stop });\n } }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.WindowStartTime, { ctx: ctx }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.PlusButton, { onclick: () => {\n const displayWindow = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().displayWindow;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDisplayWindow({ start: displayWindow.start + 60, stop: displayWindow.stop });\n } }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", null, \"to\"),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.MinusButton, { onclick: () => {\n const displayWindow = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().displayWindow;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDisplayWindow({ start: displayWindow.start, stop: displayWindow.stop - 60 });\n } }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.WindowStopTime, { ctx: ctx }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.PlusButton, { onclick: () => {\n const displayWindow = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().displayWindow;\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDisplayWindow({ start: displayWindow.start, stop: displayWindow.stop + 60 });\n } })));\n }\n MainDisplay({ ctx }) {\n const windowMode = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayMode === \"window\";\n ctx.windowedDisplayRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.WindowedDisplay, { ctx: ctx }));\n ctx.minsDisplayRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(ctx.MinutesDisplay, { ctx: ctx }));\n return _JSXFactory__WEBPACK_IMPORTED_MODULE_2__.createElement(\"div\", { className: \"display-mode-widget\" }, windowMode\n ? ctx.fromRef(ctx.windowedDisplayRef)\n : ctx.fromRef(ctx.minsDisplayRef));\n }\n onSelectMode(mode) {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setDisplayMode(mode);\n }\n updateDisplay() {\n if ((0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayMode === \"window\") {\n this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.windowedDisplayRef));\n this.fromRef(this.windowStartTimeRef).innerText = new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayWindow.start * 1000).toLocaleString();\n this.fromRef(this.windowStopTimeRef).innerText = new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().displayWindow.stop * 1000).toLocaleString();\n }\n else {\n this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.minsDisplayRef));\n this.fromRef(this.minsCounterRef).innerText = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().minutesDisplayed.toString();\n this.fromRef(this.minsInputRef).value = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.getAppState)().minutesDisplayed.toString();\n }\n }\n current() {\n return this.skeleton.current();\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (DisplayModeWidget);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/DisplayModeWidget.tsx?"); - -/***/ }), - -/***/ "./src/ui-components/GridWidget.ts": -/*!*****************************************!*\ - !*** ./src/ui-components/GridWidget.ts ***! - \*****************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n;\nclass GridWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_0__.default {\n constructor(props) {\n super();\n this.container = document.createElement(\"div\");\n this.title = document.createElement(\"h2\");\n this.body = document.createElement(\"div\");\n this.container.className = `widget${props.className ? ` ${props.className}` : \"\"}`;\n this.title.className = \"widget-title\";\n this.body.className = \"widget-body\";\n this.setTitle(props.title);\n this.setPosition({ row: props.row, col: props.col });\n this.setSize({ width: props.width, height: props.height });\n if (props.title) {\n this.container.append(this.title);\n }\n if (props.body) {\n this.body.append(props.body);\n }\n this.container.append(this.body);\n }\n setPosition(pos) {\n this.container.style.gridRowStart = `${pos.row}`;\n this.container.style.gridColumnStart = `${pos.col}`;\n }\n setSize(size) {\n this.container.style.gridRowEnd = `span ${size.height}`;\n this.container.style.gridColumnEnd = `span ${size.width}`;\n }\n setTitle(newTitle) {\n this.title.innerText = newTitle;\n }\n replaceBody(newEl) {\n this.body.replaceWith(newEl);\n }\n current() {\n return this.container;\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (GridWidget);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/GridWidget.ts?"); - -/***/ }), - -/***/ "./src/ui-components/MessageOverlay.ts": -/*!*********************************************!*\ - !*** ./src/ui-components/MessageOverlay.ts ***! - \*********************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n;\n\nclass MessageOverlay extends _UIComponent__WEBPACK_IMPORTED_MODULE_1__.default {\n constructor() {\n super();\n this.showingError = false;\n this.build();\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"overlayText\", () => this.update());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"isLoading\", () => this.update());\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"fatalError\", () => this.showError());\n this.update();\n }\n build() {\n this.element = document.createElement(\"div\");\n this.element.classList.add(\"overlay\", \"center\");\n this.textElement = document.createElement(\"span\");\n this.textElement.innerText = \"\";\n this.element.appendChild(this.textElement);\n }\n show() {\n this.element.classList.remove(\"hidden\");\n }\n hide() {\n this.element.classList.add(\"hidden\");\n }\n showError() {\n const err = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().fatalError;\n this.showingError = true;\n this.element.innerText = `${err.name}: ${err.message}!`;\n this.show();\n }\n update() {\n if (!this.showingError) {\n let text;\n if ((0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().isLoading) {\n text = \"Loading...\";\n }\n else if ((0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().overlayText) {\n text = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().overlayText;\n }\n if (text) {\n this.textElement.innerText = text;\n this.show();\n }\n else {\n this.hide();\n }\n }\n }\n current() {\n return this.element;\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (MessageOverlay);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/MessageOverlay.ts?"); - -/***/ }), - -/***/ "./src/ui-components/SelectDisplayModeWidget.tsx": -/*!*******************************************************!*\ - !*** ./src/ui-components/SelectDisplayModeWidget.tsx ***! - \*******************************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => /* binding */ SelectDisplayModeWidget\n/* harmony export */ });\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n/* harmony import */ var _JSXFactory__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../JSXFactory */ \"./src/JSXFactory.ts\");\n/* harmony import */ var _GridWidget__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./GridWidget */ \"./src/ui-components/GridWidget.ts\");\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n;\n\n\n\nclass SelectDisplayModeWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_0__.default {\n constructor(gridProps) {\n super();\n this.mainBody = this.MainBody({ ctx: this });\n this.gridWidgetSkeleton = new _GridWidget__WEBPACK_IMPORTED_MODULE_2__.default({\n ...gridProps,\n title: \"Display Mode:\",\n body: this.mainBody,\n });\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_3__.AppStore)().subscribeStoreVal(\"displayMode\", () => this.update());\n }\n selectMode(mode) {\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_3__.AppStore)().setDisplayMode(mode);\n }\n update() {\n const windowedMode = (0,_StateStore__WEBPACK_IMPORTED_MODULE_3__.getAppState)().displayMode === \"window\";\n this.fromRef(this.windowInputRef).checked = windowedMode;\n this.fromRef(this.minSpanInputRef).checked = !windowedMode;\n }\n MainBody({ ctx }) {\n const isInWindowMode = (0,_StateStore__WEBPACK_IMPORTED_MODULE_3__.getAppState)().displayMode === \"window\";\n ctx.windowInputRef = this.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"input\", { type: \"radio\", id: \"window\", name: \"display-mode\", checked: isInWindowMode, onclick: () => ctx.selectMode(\"window\") }));\n ctx.minSpanInputRef = this.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"input\", { type: \"radio\", id: \"min-span\", name: \"display-mode\", checked: !isInWindowMode, onclick: () => ctx.selectMode(\"pastMins\") }));\n return (_JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"div\", null,\n _JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"div\", null,\n this.fromRef(ctx.windowInputRef),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"label\", { htmlFor: \"window\" }, \"Time Window\")),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"div\", null,\n this.fromRef(ctx.minSpanInputRef),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_1__.createElement(\"label\", { htmlFor: \"minSpan\" }, \"Rolling Minute Span\"))));\n }\n current() {\n return this.gridWidgetSkeleton.current();\n }\n}\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/SelectDisplayModeWidget.tsx?"); - -/***/ }), - -/***/ "./src/ui-components/TimerWidget.tsx": -/*!*******************************************!*\ - !*** ./src/ui-components/TimerWidget.tsx ***! - \*******************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _GridWidget__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./GridWidget */ \"./src/ui-components/GridWidget.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n/* harmony import */ var _JSXFactory__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../JSXFactory */ \"./src/JSXFactory.ts\");\n;\n\n\n\nclass TimerWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_2__.default {\n constructor(gridProps) {\n super();\n this.display = _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(this.MainDisplay, { ctx: this });\n this.skeleton = new _GridWidget__WEBPACK_IMPORTED_MODULE_1__.default({\n ...gridProps,\n className: \"timer-widget\",\n title: \"Next update in:\",\n body: this.display,\n });\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.AppStore)().subscribeStoreVal(\"lastUpdateTime\", () => this.resetTimer());\n setInterval(() => this.refreshTimer(), 10);\n this.resetTimer();\n }\n resetTimer() {\n this.nextUpdateTime = (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().lastUpdateTime + (0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().updateIntervalSeconds;\n this.fromRef(this.lastUpdateRef).innerText = new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().lastUpdateTime * 1000).toLocaleString();\n this.refreshTimer();\n }\n MainDisplay({ ctx }) {\n ctx.timerRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", { className: \"countdown\" }));\n ctx.lastUpdateRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"span\", { className: \"last-update\" }, new Date((0,_StateStore__WEBPACK_IMPORTED_MODULE_0__.getAppState)().lastUpdateTime).toLocaleString()));\n return (_JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", null,\n ctx.fromRef(ctx.timerRef),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", null,\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", { className: \"last-update\" }, \"Last update was at:\"),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", null, ctx.fromRef(ctx.lastUpdateRef)))));\n }\n refreshTimer() {\n const now = new Date().getTime() / 1000;\n if (now <= this.nextUpdateTime) {\n this.fromRef(this.timerRef).innerText = `${(this.nextUpdateTime - now).toFixed(2)}s`;\n }\n else {\n this.fromRef(this.timerRef).innerText = \"0.00s\";\n }\n }\n current() {\n return this.skeleton.current();\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (TimerWidget);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/TimerWidget.tsx?"); - -/***/ }), - -/***/ "./src/ui-components/TimezoneWidget.tsx": -/*!**********************************************!*\ - !*** ./src/ui-components/TimezoneWidget.tsx ***! - \**********************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var _GridWidget__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./GridWidget */ \"./src/ui-components/GridWidget.ts\");\n/* harmony import */ var _StateStore__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../StateStore */ \"./src/StateStore.ts\");\n/* harmony import */ var _UIComponent__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./UIComponent */ \"./src/ui-components/UIComponent.ts\");\n/* harmony import */ var _JSXFactory__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../JSXFactory */ \"./src/JSXFactory.ts\");\n;\n\n\n\nclass TimezoneWidget extends _UIComponent__WEBPACK_IMPORTED_MODULE_2__.default {\n constructor(gridProps) {\n super();\n this.display = document.createElement(\"span\");\n this.display = _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(this.MainBody, { ctx: this });\n this.skeleton = new _GridWidget__WEBPACK_IMPORTED_MODULE_0__.default({\n ...gridProps,\n title: \"Displayed Timezone:\",\n body: this.display,\n });\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().subscribeStoreVal(\"utcOffset\", () => this.updateDisplay());\n this.updateDisplay();\n }\n updateDisplay() {\n const offset = (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().getState().utcOffset;\n this.fromRef(this.timezoneDisplayRef).innerText = `${offset > 0 ? \"+\" : \"−\"} ${Math.abs(offset)}`;\n this.fromRef(this.timezoneInputRef).value = `${offset > 0 ? \"\" : \"-\"}${Math.abs(offset)}`;\n }\n MainBody({ ctx }) {\n return _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"div\", { className: \"timezone-widget\", onclick: () => ctx.onTimezoneClick() },\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"span\", null, \"UTC \"),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(ctx.TimezoneDisplay, { ctx: ctx }),\n _JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"span\", null, \":00\"));\n }\n TimezoneDisplay({ ctx }) {\n ctx.timezoneDisplayRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"span\", null));\n ctx.timezoneInputRef = ctx.makeRef(_JSXFactory__WEBPACK_IMPORTED_MODULE_3__.createElement(\"input\", { type: \"text\", onblur: () => ctx.onTimezoneInputBlur() }));\n return ctx.fromRef(ctx.timezoneDisplayRef);\n }\n onTimezoneInputBlur() {\n const input = this.fromRef(this.timezoneInputRef);\n const display = this.fromRef(this.timezoneDisplayRef);\n (0,_StateStore__WEBPACK_IMPORTED_MODULE_1__.AppStore)().setUtcOffset(Number(input.value));\n input.replaceWith(display);\n this.updateDisplay();\n }\n onTimezoneClick() {\n const input = this.fromRef(this.timezoneInputRef);\n this.fromRef(this.timezoneDisplayRef).replaceWith(input);\n input.focus();\n input.selectionStart = 0;\n input.selectionEnd = input.value.length;\n }\n current() {\n return this.skeleton.current();\n }\n}\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (TimezoneWidget);\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/TimezoneWidget.tsx?"); - -/***/ }), - -/***/ "./src/ui-components/UIComponent.ts": -/*!******************************************!*\ - !*** ./src/ui-components/UIComponent.ts ***! - \******************************************/ -/*! namespace exports */ -/*! export default [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ -/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { - -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => /* binding */ UIComponent\n/* harmony export */ });\nclass UIComponent {\n constructor() {\n this.id = UIComponent.componentCount;\n UIComponent.componentCount++;\n }\n makeRef(el) {\n UIComponent.reffedComponents.push(el);\n return UIComponent.reffedComponentCount++;\n }\n fromRef(ref) {\n return UIComponent.reffedComponents[ref] ?? null;\n }\n}\nUIComponent.componentCount = 0;\nUIComponent.reffedComponentCount = 0;\nUIComponent.reffedComponents = [];\n\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/ui-components/UIComponent.ts?"); - -/***/ }), - -/***/ "./src/config.json": -/*!*************************!*\ - !*** ./src/config.json ***! - \*************************/ -/*! default exports */ -/*! export dataEndpoint [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export default [not provided] [no usage info] [missing usage info prevents renaming] */ -/*! export defaultMinuteSpan [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export development [provided] [no usage info] [missing usage info prevents renaming] */ -/*! export reloadIntervalSec [provided] [no usage info] [missing usage info prevents renaming] */ -/*! other exports [not provided] [no usage info] */ -/*! runtime requirements: module */ -/***/ ((module) => { - -eval("module.exports = JSON.parse(\"{\\\"development\\\":true,\\\"defaultMinuteSpan\\\":60,\\\"reloadIntervalSec\\\":30,\\\"dataEndpoint\\\":\\\"http://tortedda.local/climate/api\\\"}\");\n\n//# sourceURL=webpack://climate-ranger-frontend/./src/config.json?"); - -/***/ }) - -/******/ }); -/************************************************************************/ -/******/ // The module cache -/******/ var __webpack_module_cache__ = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ // Check if module is in cache -/******/ if(__webpack_module_cache__[moduleId]) { -/******/ return __webpack_module_cache__[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = __webpack_module_cache__[moduleId] = { -/******/ // no module.id needed -/******/ // no module.loaded needed -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ }; -/******/ __webpack_require__.i.forEach(function(handler) { handler(execOptions); }); -/******/ module = execOptions.module; -/******/ execOptions.factory.call(module.exports, module, module.exports, execOptions.require); -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = __webpack_modules__; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = __webpack_module_cache__; -/******/ -/******/ // expose the module execution interceptor -/******/ __webpack_require__.i = []; -/******/ -/************************************************************************/ -/******/ /* webpack/runtime/define property getters */ -/******/ (() => { -/******/ // define getter functions for harmony exports -/******/ __webpack_require__.d = (exports, definition) => { -/******/ for(var key in definition) { -/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { -/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); -/******/ } -/******/ } -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/get javascript update chunk filename */ -/******/ (() => { -/******/ // This function allow to reference all chunks -/******/ __webpack_require__.hu = (chunkId) => { -/******/ // return url for filenames based on template -/******/ return "" + chunkId + "." + __webpack_require__.h() + ".hot-update.js"; -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/get update manifest filename */ -/******/ (() => { -/******/ __webpack_require__.hmrF = () => "" + __webpack_require__.h() + ".hot-update.json"; -/******/ })(); -/******/ -/******/ /* webpack/runtime/getFullHash */ -/******/ (() => { -/******/ __webpack_require__.h = () => "747f0d1189db30276f63" -/******/ })(); -/******/ -/******/ /* webpack/runtime/global */ -/******/ (() => { -/******/ __webpack_require__.g = (function() { -/******/ if (typeof globalThis === 'object') return globalThis; -/******/ try { -/******/ return this || new Function('return this')(); -/******/ } catch (e) { -/******/ if (typeof window === 'object') return window; -/******/ } -/******/ })(); -/******/ })(); -/******/ -/******/ /* webpack/runtime/hasOwnProperty shorthand */ -/******/ (() => { -/******/ __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop) -/******/ })(); -/******/ -/******/ /* webpack/runtime/load script */ -/******/ (() => { -/******/ var inProgress = {}; -/******/ var dataWebpackPrefix = "climate-ranger-frontend:"; -/******/ // loadScript function to load a script via script tag -/******/ __webpack_require__.l = (url, done, key) => { -/******/ if(inProgress[url]) { inProgress[url].push(done); return; } -/******/ var script, needAttach; -/******/ if(key !== undefined) { -/******/ var scripts = document.getElementsByTagName("script"); -/******/ for(var i = 0; i < scripts.length; i++) { -/******/ var s = scripts[i]; -/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } -/******/ } -/******/ } -/******/ if(!script) { -/******/ needAttach = true; -/******/ script = document.createElement('script'); -/******/ -/******/ script.charset = 'utf-8'; -/******/ script.timeout = 120; -/******/ if (__webpack_require__.nc) { -/******/ script.setAttribute("nonce", __webpack_require__.nc); -/******/ } -/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key); -/******/ script.src = url; -/******/ } -/******/ inProgress[url] = [done]; -/******/ var onScriptComplete = (prev, event) => { -/******/ // avoid mem leaks in IE. -/******/ script.onerror = script.onload = null; -/******/ clearTimeout(timeout); -/******/ var doneFns = inProgress[url]; -/******/ delete inProgress[url]; -/******/ script.parentNode && script.parentNode.removeChild(script); -/******/ doneFns && doneFns.forEach((fn) => fn(event)); -/******/ if(prev) return prev(event); -/******/ } -/******/ ; -/******/ var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); -/******/ script.onerror = onScriptComplete.bind(null, script.onerror); -/******/ script.onload = onScriptComplete.bind(null, script.onload); -/******/ needAttach && document.head.appendChild(script); -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/make namespace object */ -/******/ (() => { -/******/ // define __esModule on exports -/******/ __webpack_require__.r = (exports) => { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ })(); -/******/ -/******/ /* webpack/runtime/hot module replacement */ -/******/ (() => { -/******/ var currentModuleData = {}; -/******/ var installedModules = __webpack_require__.c; -/******/ -/******/ // module and require creation -/******/ var currentChildModule; -/******/ var currentParents = []; -/******/ -/******/ // status -/******/ var registeredStatusHandlers = []; -/******/ var currentStatus = "idle"; -/******/ -/******/ // while downloading -/******/ var blockingPromises; -/******/ -/******/ // The update info -/******/ var currentUpdateApplyHandlers; -/******/ var queuedInvalidatedModules; -/******/ -/******/ __webpack_require__.hmrD = currentModuleData; -/******/ -/******/ __webpack_require__.i.push(function (options) { -/******/ var module = options.module; -/******/ var require = createRequire(options.require, options.id); -/******/ module.hot = createModuleHotObject(options.id, module); -/******/ module.parents = currentParents; -/******/ module.children = []; -/******/ currentParents = []; -/******/ options.require = require; -/******/ }); -/******/ -/******/ __webpack_require__.hmrC = {}; -/******/ __webpack_require__.hmrI = {}; -/******/ -/******/ function createRequire(require, moduleId) { -/******/ var me = installedModules[moduleId]; -/******/ if (!me) return require; -/******/ var fn = function (request) { -/******/ if (me.hot.active) { -/******/ if (installedModules[request]) { -/******/ var parents = installedModules[request].parents; -/******/ if (parents.indexOf(moduleId) === -1) { -/******/ parents.push(moduleId); -/******/ } -/******/ } else { -/******/ currentParents = [moduleId]; -/******/ currentChildModule = request; -/******/ } -/******/ if (me.children.indexOf(request) === -1) { -/******/ me.children.push(request); -/******/ } -/******/ } else { -/******/ console.warn( -/******/ "[HMR] unexpected require(" + -/******/ request + -/******/ ") from disposed module " + -/******/ moduleId -/******/ ); -/******/ currentParents = []; -/******/ } -/******/ return require(request); -/******/ }; -/******/ var createPropertyDescriptor = function (name) { -/******/ return { -/******/ configurable: true, -/******/ enumerable: true, -/******/ get: function () { -/******/ return require[name]; -/******/ }, -/******/ set: function (value) { -/******/ require[name] = value; -/******/ } -/******/ }; -/******/ }; -/******/ for (var name in require) { -/******/ if (Object.prototype.hasOwnProperty.call(require, name) && name !== "e") { -/******/ Object.defineProperty(fn, name, createPropertyDescriptor(name)); -/******/ } -/******/ } -/******/ fn.e = function (chunkId) { -/******/ return trackBlockingPromise(require.e(chunkId)); -/******/ }; -/******/ return fn; -/******/ } -/******/ -/******/ function createModuleHotObject(moduleId, me) { -/******/ var hot = { -/******/ // private stuff -/******/ _acceptedDependencies: {}, -/******/ _declinedDependencies: {}, -/******/ _selfAccepted: false, -/******/ _selfDeclined: false, -/******/ _selfInvalidated: false, -/******/ _disposeHandlers: [], -/******/ _main: currentChildModule !== moduleId, -/******/ _requireSelf: function () { -/******/ currentParents = me.parents.slice(); -/******/ currentChildModule = moduleId; -/******/ __webpack_require__(moduleId); -/******/ }, -/******/ -/******/ // Module API -/******/ active: true, -/******/ accept: function (dep, callback) { -/******/ if (dep === undefined) hot._selfAccepted = true; -/******/ else if (typeof dep === "function") hot._selfAccepted = dep; -/******/ else if (typeof dep === "object" && dep !== null) -/******/ for (var i = 0; i < dep.length; i++) -/******/ hot._acceptedDependencies[dep[i]] = callback || function () {}; -/******/ else hot._acceptedDependencies[dep] = callback || function () {}; -/******/ }, -/******/ decline: function (dep) { -/******/ if (dep === undefined) hot._selfDeclined = true; -/******/ else if (typeof dep === "object" && dep !== null) -/******/ for (var i = 0; i < dep.length; i++) -/******/ hot._declinedDependencies[dep[i]] = true; -/******/ else hot._declinedDependencies[dep] = true; -/******/ }, -/******/ dispose: function (callback) { -/******/ hot._disposeHandlers.push(callback); -/******/ }, -/******/ addDisposeHandler: function (callback) { -/******/ hot._disposeHandlers.push(callback); -/******/ }, -/******/ removeDisposeHandler: function (callback) { -/******/ var idx = hot._disposeHandlers.indexOf(callback); -/******/ if (idx >= 0) hot._disposeHandlers.splice(idx, 1); -/******/ }, -/******/ invalidate: function () { -/******/ this._selfInvalidated = true; -/******/ switch (currentStatus) { -/******/ case "idle": -/******/ currentUpdateApplyHandlers = []; -/******/ Object.keys(__webpack_require__.hmrI).forEach(function (key) { -/******/ __webpack_require__.hmrI[key]( -/******/ moduleId, -/******/ currentUpdateApplyHandlers -/******/ ); -/******/ }); -/******/ setStatus("ready"); -/******/ break; -/******/ case "ready": -/******/ Object.keys(__webpack_require__.hmrI).forEach(function (key) { -/******/ __webpack_require__.hmrI[key]( -/******/ moduleId, -/******/ currentUpdateApplyHandlers -/******/ ); -/******/ }); -/******/ break; -/******/ case "prepare": -/******/ case "check": -/******/ case "dispose": -/******/ case "apply": -/******/ (queuedInvalidatedModules = queuedInvalidatedModules || []).push( -/******/ moduleId -/******/ ); -/******/ break; -/******/ default: -/******/ // ignore requests in error states -/******/ break; -/******/ } -/******/ }, -/******/ -/******/ // Management API -/******/ check: hotCheck, -/******/ apply: hotApply, -/******/ status: function (l) { -/******/ if (!l) return currentStatus; -/******/ registeredStatusHandlers.push(l); -/******/ }, -/******/ addStatusHandler: function (l) { -/******/ registeredStatusHandlers.push(l); -/******/ }, -/******/ removeStatusHandler: function (l) { -/******/ var idx = registeredStatusHandlers.indexOf(l); -/******/ if (idx >= 0) registeredStatusHandlers.splice(idx, 1); -/******/ }, -/******/ -/******/ //inherit from previous dispose call -/******/ data: currentModuleData[moduleId] -/******/ }; -/******/ currentChildModule = undefined; -/******/ return hot; -/******/ } -/******/ -/******/ function setStatus(newStatus) { -/******/ currentStatus = newStatus; -/******/ for (var i = 0; i < registeredStatusHandlers.length; i++) -/******/ registeredStatusHandlers[i].call(null, newStatus); -/******/ } -/******/ -/******/ function trackBlockingPromise(promise) { -/******/ switch (currentStatus) { -/******/ case "ready": -/******/ setStatus("prepare"); -/******/ blockingPromises.push(promise); -/******/ waitForBlockingPromises(function () { -/******/ setStatus("ready"); -/******/ }); -/******/ return promise; -/******/ case "prepare": -/******/ blockingPromises.push(promise); -/******/ return promise; -/******/ default: -/******/ return promise; -/******/ } -/******/ } -/******/ -/******/ function waitForBlockingPromises(fn) { -/******/ if (blockingPromises.length === 0) return fn(); -/******/ var blocker = blockingPromises; -/******/ blockingPromises = []; -/******/ return Promise.all(blocker).then(function () { -/******/ return waitForBlockingPromises(fn); -/******/ }); -/******/ } -/******/ -/******/ function hotCheck(applyOnUpdate) { -/******/ if (currentStatus !== "idle") { -/******/ throw new Error("check() is only allowed in idle status"); -/******/ } -/******/ setStatus("check"); -/******/ return __webpack_require__.hmrM().then(function (update) { -/******/ if (!update) { -/******/ setStatus(applyInvalidatedModules() ? "ready" : "idle"); -/******/ return null; -/******/ } -/******/ -/******/ setStatus("prepare"); -/******/ -/******/ var updatedModules = []; -/******/ blockingPromises = []; -/******/ currentUpdateApplyHandlers = []; -/******/ -/******/ return Promise.all( -/******/ Object.keys(__webpack_require__.hmrC).reduce(function ( -/******/ promises, -/******/ key -/******/ ) { -/******/ __webpack_require__.hmrC[key]( -/******/ update.c, -/******/ update.r, -/******/ update.m, -/******/ promises, -/******/ currentUpdateApplyHandlers, -/******/ updatedModules -/******/ ); -/******/ return promises; -/******/ }, -/******/ []) -/******/ ).then(function () { -/******/ return waitForBlockingPromises(function () { -/******/ if (applyOnUpdate) { -/******/ return internalApply(applyOnUpdate); -/******/ } else { -/******/ setStatus("ready"); -/******/ -/******/ return updatedModules; -/******/ } -/******/ }); -/******/ }); -/******/ }); -/******/ } -/******/ -/******/ function hotApply(options) { -/******/ if (currentStatus !== "ready") { -/******/ return Promise.resolve().then(function () { -/******/ throw new Error("apply() is only allowed in ready status"); -/******/ }); -/******/ } -/******/ return internalApply(options); -/******/ } -/******/ -/******/ function internalApply(options) { -/******/ options = options || {}; -/******/ -/******/ applyInvalidatedModules(); -/******/ -/******/ var results = currentUpdateApplyHandlers.map(function (handler) { -/******/ return handler(options); -/******/ }); -/******/ currentUpdateApplyHandlers = undefined; -/******/ -/******/ var errors = results -/******/ .map(function (r) { -/******/ return r.error; -/******/ }) -/******/ .filter(Boolean); -/******/ -/******/ if (errors.length > 0) { -/******/ setStatus("abort"); -/******/ return Promise.resolve().then(function () { -/******/ throw errors[0]; -/******/ }); -/******/ } -/******/ -/******/ // Now in "dispose" phase -/******/ setStatus("dispose"); -/******/ -/******/ results.forEach(function (result) { -/******/ if (result.dispose) result.dispose(); -/******/ }); -/******/ -/******/ // Now in "apply" phase -/******/ setStatus("apply"); -/******/ -/******/ var error; -/******/ var reportError = function (err) { -/******/ if (!error) error = err; -/******/ }; -/******/ -/******/ var outdatedModules = []; -/******/ results.forEach(function (result) { -/******/ if (result.apply) { -/******/ var modules = result.apply(reportError); -/******/ if (modules) { -/******/ for (var i = 0; i < modules.length; i++) { -/******/ outdatedModules.push(modules[i]); -/******/ } -/******/ } -/******/ } -/******/ }); -/******/ -/******/ // handle errors in accept handlers and self accepted module load -/******/ if (error) { -/******/ setStatus("fail"); -/******/ return Promise.resolve().then(function () { -/******/ throw error; -/******/ }); -/******/ } -/******/ -/******/ if (queuedInvalidatedModules) { -/******/ return internalApply(options).then(function (list) { -/******/ outdatedModules.forEach(function (moduleId) { -/******/ if (list.indexOf(moduleId) < 0) list.push(moduleId); -/******/ }); -/******/ return list; -/******/ }); -/******/ } -/******/ -/******/ setStatus("idle"); -/******/ return Promise.resolve(outdatedModules); -/******/ } -/******/ -/******/ function applyInvalidatedModules() { -/******/ if (queuedInvalidatedModules) { -/******/ if (!currentUpdateApplyHandlers) currentUpdateApplyHandlers = []; -/******/ Object.keys(__webpack_require__.hmrI).forEach(function (key) { -/******/ queuedInvalidatedModules.forEach(function (moduleId) { -/******/ __webpack_require__.hmrI[key]( -/******/ moduleId, -/******/ currentUpdateApplyHandlers -/******/ ); -/******/ }); -/******/ }); -/******/ queuedInvalidatedModules = undefined; -/******/ return true; -/******/ } -/******/ } -/******/ })(); -/******/ -/******/ /* webpack/runtime/publicPath */ -/******/ (() => { -/******/ var scriptUrl; -/******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + ""; -/******/ var document = __webpack_require__.g.document; -/******/ if (!scriptUrl && document) { -/******/ if (document.currentScript) -/******/ scriptUrl = document.currentScript.src -/******/ if (!scriptUrl) { -/******/ var scripts = document.getElementsByTagName("script"); -/******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src -/******/ } -/******/ } -/******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration -/******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic. -/******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser"); -/******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/"); -/******/ __webpack_require__.p = scriptUrl; -/******/ })(); -/******/ -/******/ /* webpack/runtime/jsonp chunk loading */ -/******/ (() => { -/******/ // no baseURI -/******/ -/******/ // object to store loaded and loading chunks -/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched -/******/ // Promise = chunk loading, 0 = chunk loaded -/******/ var installedChunks = { -/******/ "main": 0 -/******/ }; -/******/ -/******/ -/******/ // no chunk on demand loading -/******/ -/******/ // no prefetching -/******/ -/******/ // no preloaded -/******/ -/******/ var currentUpdatedModulesList; -/******/ var waitingUpdateResolves = {}; -/******/ function loadUpdateChunk(chunkId) { -/******/ return new Promise((resolve, reject) => { -/******/ waitingUpdateResolves[chunkId] = resolve; -/******/ // start update chunk loading -/******/ var url = __webpack_require__.p + __webpack_require__.hu(chunkId); -/******/ // create error before stack unwound to get useful stacktrace later -/******/ var error = new Error(); -/******/ var loadingEnded = (event) => { -/******/ if(waitingUpdateResolves[chunkId]) { -/******/ waitingUpdateResolves[chunkId] = undefined -/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type); -/******/ var realSrc = event && event.target && event.target.src; -/******/ error.message = 'Loading hot update chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; -/******/ error.name = 'ChunkLoadError'; -/******/ error.type = errorType; -/******/ error.request = realSrc; -/******/ reject(error); -/******/ } -/******/ }; -/******/ __webpack_require__.l(url, loadingEnded); -/******/ }); -/******/ } -/******/ -/******/ self["webpackHotUpdateclimate_ranger_frontend"] = (chunkId, moreModules, runtime) => { -/******/ for(var moduleId in moreModules) { -/******/ if(__webpack_require__.o(moreModules, moduleId)) { -/******/ currentUpdate[moduleId] = moreModules[moduleId]; -/******/ if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId); -/******/ } -/******/ } -/******/ if(runtime) currentUpdateRuntime.push(runtime); -/******/ if(waitingUpdateResolves[chunkId]) { -/******/ waitingUpdateResolves[chunkId](); -/******/ waitingUpdateResolves[chunkId] = undefined; -/******/ } -/******/ }; -/******/ -/******/ var currentUpdateChunks; -/******/ var currentUpdate; -/******/ var currentUpdateRemovedChunks; -/******/ var currentUpdateRuntime; -/******/ function applyHandler(options) { -/******/ if (__webpack_require__.f) delete __webpack_require__.f.jsonpHmr; -/******/ currentUpdateChunks = undefined; -/******/ function getAffectedModuleEffects(updateModuleId) { -/******/ var outdatedModules = [updateModuleId]; -/******/ var outdatedDependencies = {}; -/******/ -/******/ var queue = outdatedModules.map(function (id) { -/******/ return { -/******/ chain: [id], -/******/ id: id -/******/ }; -/******/ }); -/******/ while (queue.length > 0) { -/******/ var queueItem = queue.pop(); -/******/ var moduleId = queueItem.id; -/******/ var chain = queueItem.chain; -/******/ var module = __webpack_require__.c[moduleId]; -/******/ if ( -/******/ !module || -/******/ (module.hot._selfAccepted && !module.hot._selfInvalidated) -/******/ ) -/******/ continue; -/******/ if (module.hot._selfDeclined) { -/******/ return { -/******/ type: "self-declined", -/******/ chain: chain, -/******/ moduleId: moduleId -/******/ }; -/******/ } -/******/ if (module.hot._main) { -/******/ return { -/******/ type: "unaccepted", -/******/ chain: chain, -/******/ moduleId: moduleId -/******/ }; -/******/ } -/******/ for (var i = 0; i < module.parents.length; i++) { -/******/ var parentId = module.parents[i]; -/******/ var parent = __webpack_require__.c[parentId]; -/******/ if (!parent) continue; -/******/ if (parent.hot._declinedDependencies[moduleId]) { -/******/ return { -/******/ type: "declined", -/******/ chain: chain.concat([parentId]), -/******/ moduleId: moduleId, -/******/ parentId: parentId -/******/ }; -/******/ } -/******/ if (outdatedModules.indexOf(parentId) !== -1) continue; -/******/ if (parent.hot._acceptedDependencies[moduleId]) { -/******/ if (!outdatedDependencies[parentId]) -/******/ outdatedDependencies[parentId] = []; -/******/ addAllToSet(outdatedDependencies[parentId], [moduleId]); -/******/ continue; -/******/ } -/******/ delete outdatedDependencies[parentId]; -/******/ outdatedModules.push(parentId); -/******/ queue.push({ -/******/ chain: chain.concat([parentId]), -/******/ id: parentId -/******/ }); -/******/ } -/******/ } -/******/ -/******/ return { -/******/ type: "accepted", -/******/ moduleId: updateModuleId, -/******/ outdatedModules: outdatedModules, -/******/ outdatedDependencies: outdatedDependencies -/******/ }; -/******/ } -/******/ -/******/ function addAllToSet(a, b) { -/******/ for (var i = 0; i < b.length; i++) { -/******/ var item = b[i]; -/******/ if (a.indexOf(item) === -1) a.push(item); -/******/ } -/******/ } -/******/ -/******/ // at begin all updates modules are outdated -/******/ // the "outdated" status can propagate to parents if they don't accept the children -/******/ var outdatedDependencies = {}; -/******/ var outdatedModules = []; -/******/ var appliedUpdate = {}; -/******/ -/******/ var warnUnexpectedRequire = function warnUnexpectedRequire(module) { -/******/ console.warn( -/******/ "[HMR] unexpected require(" + module.id + ") to disposed module" -/******/ ); -/******/ }; -/******/ -/******/ for (var moduleId in currentUpdate) { -/******/ if (__webpack_require__.o(currentUpdate, moduleId)) { -/******/ var newModuleFactory = currentUpdate[moduleId]; -/******/ /** @type {TODO} */ -/******/ var result; -/******/ if (newModuleFactory) { -/******/ result = getAffectedModuleEffects(moduleId); -/******/ } else { -/******/ result = { -/******/ type: "disposed", -/******/ moduleId: moduleId -/******/ }; -/******/ } -/******/ /** @type {Error|false} */ -/******/ var abortError = false; -/******/ var doApply = false; -/******/ var doDispose = false; -/******/ var chainInfo = ""; -/******/ if (result.chain) { -/******/ chainInfo = "\nUpdate propagation: " + result.chain.join(" -> "); -/******/ } -/******/ switch (result.type) { -/******/ case "self-declined": -/******/ if (options.onDeclined) options.onDeclined(result); -/******/ if (!options.ignoreDeclined) -/******/ abortError = new Error( -/******/ "Aborted because of self decline: " + -/******/ result.moduleId + -/******/ chainInfo -/******/ ); -/******/ break; -/******/ case "declined": -/******/ if (options.onDeclined) options.onDeclined(result); -/******/ if (!options.ignoreDeclined) -/******/ abortError = new Error( -/******/ "Aborted because of declined dependency: " + -/******/ result.moduleId + -/******/ " in " + -/******/ result.parentId + -/******/ chainInfo -/******/ ); -/******/ break; -/******/ case "unaccepted": -/******/ if (options.onUnaccepted) options.onUnaccepted(result); -/******/ if (!options.ignoreUnaccepted) -/******/ abortError = new Error( -/******/ "Aborted because " + moduleId + " is not accepted" + chainInfo -/******/ ); -/******/ break; -/******/ case "accepted": -/******/ if (options.onAccepted) options.onAccepted(result); -/******/ doApply = true; -/******/ break; -/******/ case "disposed": -/******/ if (options.onDisposed) options.onDisposed(result); -/******/ doDispose = true; -/******/ break; -/******/ default: -/******/ throw new Error("Unexception type " + result.type); -/******/ } -/******/ if (abortError) { -/******/ return { -/******/ error: abortError -/******/ }; -/******/ } -/******/ if (doApply) { -/******/ appliedUpdate[moduleId] = newModuleFactory; -/******/ addAllToSet(outdatedModules, result.outdatedModules); -/******/ for (moduleId in result.outdatedDependencies) { -/******/ if (__webpack_require__.o(result.outdatedDependencies, moduleId)) { -/******/ if (!outdatedDependencies[moduleId]) -/******/ outdatedDependencies[moduleId] = []; -/******/ addAllToSet( -/******/ outdatedDependencies[moduleId], -/******/ result.outdatedDependencies[moduleId] -/******/ ); -/******/ } -/******/ } -/******/ } -/******/ if (doDispose) { -/******/ addAllToSet(outdatedModules, [result.moduleId]); -/******/ appliedUpdate[moduleId] = warnUnexpectedRequire; -/******/ } -/******/ } -/******/ } -/******/ currentUpdate = undefined; -/******/ -/******/ // Store self accepted outdated modules to require them later by the module system -/******/ var outdatedSelfAcceptedModules = []; -/******/ for (var j = 0; j < outdatedModules.length; j++) { -/******/ var outdatedModuleId = outdatedModules[j]; -/******/ if ( -/******/ __webpack_require__.c[outdatedModuleId] && -/******/ __webpack_require__.c[outdatedModuleId].hot._selfAccepted && -/******/ // removed self-accepted modules should not be required -/******/ appliedUpdate[outdatedModuleId] !== warnUnexpectedRequire && -/******/ // when called invalidate self-accepting is not possible -/******/ !__webpack_require__.c[outdatedModuleId].hot._selfInvalidated -/******/ ) { -/******/ outdatedSelfAcceptedModules.push({ -/******/ module: outdatedModuleId, -/******/ require: __webpack_require__.c[outdatedModuleId].hot._requireSelf, -/******/ errorHandler: __webpack_require__.c[outdatedModuleId].hot._selfAccepted -/******/ }); -/******/ } -/******/ } -/******/ -/******/ var moduleOutdatedDependencies; -/******/ -/******/ return { -/******/ dispose: function () { -/******/ currentUpdateRemovedChunks.forEach(function (chunkId) { -/******/ delete installedChunks[chunkId]; -/******/ }); -/******/ currentUpdateRemovedChunks = undefined; -/******/ -/******/ var idx; -/******/ var queue = outdatedModules.slice(); -/******/ while (queue.length > 0) { -/******/ var moduleId = queue.pop(); -/******/ var module = __webpack_require__.c[moduleId]; -/******/ if (!module) continue; -/******/ -/******/ var data = {}; -/******/ -/******/ // Call dispose handlers -/******/ var disposeHandlers = module.hot._disposeHandlers; -/******/ for (j = 0; j < disposeHandlers.length; j++) { -/******/ disposeHandlers[j].call(null, data); -/******/ } -/******/ __webpack_require__.hmrD[moduleId] = data; -/******/ -/******/ // disable module (this disables requires from this module) -/******/ module.hot.active = false; -/******/ -/******/ // remove module from cache -/******/ delete __webpack_require__.c[moduleId]; -/******/ -/******/ // when disposing there is no need to call dispose handler -/******/ delete outdatedDependencies[moduleId]; -/******/ -/******/ // remove "parents" references from all children -/******/ for (j = 0; j < module.children.length; j++) { -/******/ var child = __webpack_require__.c[module.children[j]]; -/******/ if (!child) continue; -/******/ idx = child.parents.indexOf(moduleId); -/******/ if (idx >= 0) { -/******/ child.parents.splice(idx, 1); -/******/ } -/******/ } -/******/ } -/******/ -/******/ // remove outdated dependency from module children -/******/ var dependency; -/******/ for (var outdatedModuleId in outdatedDependencies) { -/******/ if (__webpack_require__.o(outdatedDependencies, outdatedModuleId)) { -/******/ module = __webpack_require__.c[outdatedModuleId]; -/******/ if (module) { -/******/ moduleOutdatedDependencies = -/******/ outdatedDependencies[outdatedModuleId]; -/******/ for (j = 0; j < moduleOutdatedDependencies.length; j++) { -/******/ dependency = moduleOutdatedDependencies[j]; -/******/ idx = module.children.indexOf(dependency); -/******/ if (idx >= 0) module.children.splice(idx, 1); -/******/ } -/******/ } -/******/ } -/******/ } -/******/ }, -/******/ apply: function (reportError) { -/******/ // insert new code -/******/ for (var updateModuleId in appliedUpdate) { -/******/ if (__webpack_require__.o(appliedUpdate, updateModuleId)) { -/******/ __webpack_require__.m[updateModuleId] = appliedUpdate[updateModuleId]; -/******/ } -/******/ } -/******/ -/******/ // run new runtime modules -/******/ for (var i = 0; i < currentUpdateRuntime.length; i++) { -/******/ currentUpdateRuntime[i](__webpack_require__); -/******/ } -/******/ -/******/ // call accept handlers -/******/ for (var outdatedModuleId in outdatedDependencies) { -/******/ if (__webpack_require__.o(outdatedDependencies, outdatedModuleId)) { -/******/ var module = __webpack_require__.c[outdatedModuleId]; -/******/ if (module) { -/******/ moduleOutdatedDependencies = -/******/ outdatedDependencies[outdatedModuleId]; -/******/ var callbacks = []; -/******/ var dependenciesForCallbacks = []; -/******/ for (var j = 0; j < moduleOutdatedDependencies.length; j++) { -/******/ var dependency = moduleOutdatedDependencies[j]; -/******/ var acceptCallback = -/******/ module.hot._acceptedDependencies[dependency]; -/******/ if (acceptCallback) { -/******/ if (callbacks.indexOf(acceptCallback) !== -1) continue; -/******/ callbacks.push(acceptCallback); -/******/ dependenciesForCallbacks.push(dependency); -/******/ } -/******/ } -/******/ for (var k = 0; k < callbacks.length; k++) { -/******/ try { -/******/ callbacks[k].call(null, moduleOutdatedDependencies); -/******/ } catch (err) { -/******/ if (options.onErrored) { -/******/ options.onErrored({ -/******/ type: "accept-errored", -/******/ moduleId: outdatedModuleId, -/******/ dependencyId: dependenciesForCallbacks[k], -/******/ error: err -/******/ }); -/******/ } -/******/ if (!options.ignoreErrored) { -/******/ reportError(err); -/******/ } -/******/ } -/******/ } -/******/ } -/******/ } -/******/ } -/******/ -/******/ // Load self accepted modules -/******/ for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) { -/******/ var item = outdatedSelfAcceptedModules[o]; -/******/ var moduleId = item.module; -/******/ try { -/******/ item.require(moduleId); -/******/ } catch (err) { -/******/ if (typeof item.errorHandler === "function") { -/******/ try { -/******/ item.errorHandler(err); -/******/ } catch (err2) { -/******/ if (options.onErrored) { -/******/ options.onErrored({ -/******/ type: "self-accept-error-handler-errored", -/******/ moduleId: moduleId, -/******/ error: err2, -/******/ originalError: err -/******/ }); -/******/ } -/******/ if (!options.ignoreErrored) { -/******/ reportError(err2); -/******/ } -/******/ reportError(err); -/******/ } -/******/ } else { -/******/ if (options.onErrored) { -/******/ options.onErrored({ -/******/ type: "self-accept-errored", -/******/ moduleId: moduleId, -/******/ error: err -/******/ }); -/******/ } -/******/ if (!options.ignoreErrored) { -/******/ reportError(err); -/******/ } -/******/ } -/******/ } -/******/ } -/******/ -/******/ return outdatedModules; -/******/ } -/******/ }; -/******/ } -/******/ __webpack_require__.hmrI.jsonp = function (moduleId, applyHandlers) { -/******/ if (!currentUpdate) { -/******/ currentUpdate = {}; -/******/ currentUpdateRuntime = []; -/******/ currentUpdateRemovedChunks = []; -/******/ applyHandlers.push(applyHandler); -/******/ } -/******/ if (!__webpack_require__.o(currentUpdate, moduleId)) { -/******/ currentUpdate[moduleId] = __webpack_require__.m[moduleId]; -/******/ } -/******/ }; -/******/ __webpack_require__.hmrC.jsonp = function ( -/******/ chunkIds, -/******/ removedChunks, -/******/ removedModules, -/******/ promises, -/******/ applyHandlers, -/******/ updatedModulesList -/******/ ) { -/******/ applyHandlers.push(applyHandler); -/******/ currentUpdateChunks = {}; -/******/ currentUpdateRemovedChunks = removedChunks; -/******/ currentUpdate = removedModules.reduce(function (obj, key) { -/******/ obj[key] = false; -/******/ return obj; -/******/ }, {}); -/******/ currentUpdateRuntime = []; -/******/ chunkIds.forEach(function (chunkId) { -/******/ if ( -/******/ __webpack_require__.o(installedChunks, chunkId) && -/******/ installedChunks[chunkId] !== undefined -/******/ ) { -/******/ promises.push(loadUpdateChunk(chunkId, updatedModulesList)); -/******/ currentUpdateChunks[chunkId] = true; -/******/ } -/******/ }); -/******/ if (__webpack_require__.f) { -/******/ __webpack_require__.f.jsonpHmr = function (chunkId, promises) { -/******/ if ( -/******/ currentUpdateChunks && -/******/ !__webpack_require__.o(currentUpdateChunks, chunkId) && -/******/ __webpack_require__.o(installedChunks, chunkId) && -/******/ installedChunks[chunkId] !== undefined -/******/ ) { -/******/ promises.push(loadUpdateChunk(chunkId)); -/******/ currentUpdateChunks[chunkId] = true; -/******/ } -/******/ }; -/******/ } -/******/ }; -/******/ -/******/ __webpack_require__.hmrM = () => { -/******/ if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API"); -/******/ return fetch(__webpack_require__.p + __webpack_require__.hmrF()).then((response) => { -/******/ if(response.status === 404) return; // no update available -/******/ if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText); -/******/ return response.json(); -/******/ }); -/******/ }; -/******/ -/******/ // no deferred startup -/******/ -/******/ // no jsonp function -/******/ })(); -/******/ -/************************************************************************/ -/******/ // module cache are used so entry inlining is disabled -/******/ // startup -/******/ // Load entry module -/******/ __webpack_require__("./src/main.ts"); -/******/ })() -; \ No newline at end of file +(()=>{"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}})(); \ No newline at end of file diff --git a/server/src/.env b/server/src/.env index 2c60955..87f7f62 100644 --- a/server/src/.env +++ b/server/src/.env @@ -2,6 +2,6 @@ PORT=4040 SERVER_ROOT=/climate MYSQL_ADDRESS=192.168.0.198 MYSQL_USERNAME=admin -MYSQL_PW=sekna123jk +MYSQL_PW= SENSOR_PING_INTERVAL=30 DEV=true \ No newline at end of file