From 4b0c5bc9aba4447ba3506f83215a5724cb72633c Mon Sep 17 00:00:00 2001 From: Daniel Ledda Date: Wed, 18 Nov 2020 22:51:30 +0100 Subject: [PATCH] oops --- webapp/dist/styles.css | 8 ++++ webapp/package-lock.json | 10 +++++ webapp/src/ClimateChart.ts | 60 +++++++++++++++---------- webapp/src/climateChartConfig.ts | 13 ++++-- webapp/src/config.json | 3 ++ webapp/src/main.ts | 8 ++-- webapp/tsconfig.json | 1 + webapp/webpack.config.js | 75 ++++++++++++-------------------- 8 files changed, 100 insertions(+), 78 deletions(-) diff --git a/webapp/dist/styles.css b/webapp/dist/styles.css index 9703a0e..5c68eaf 100644 --- a/webapp/dist/styles.css +++ b/webapp/dist/styles.css @@ -1,6 +1,7 @@ html, body { margin: 0; height: 100%; + background-color: #fff1de; } .overlay { @@ -34,4 +35,11 @@ html, body { padding: 10vw; width: calc(100% - 20vw); height: calc(100% - 20vw); +} + +#myChart { + background-color: white; + padding: 2vw; + border-radius: 1vw; + border: 3px #c7ab82 solid; } \ No newline at end of file diff --git a/webapp/package-lock.json b/webapp/package-lock.json index b28a17b..ce95aa6 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -451,6 +451,11 @@ "moment": "^2.10.2" } }, + "@types/chartist": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@types/chartist/-/chartist-0.11.0.tgz", + "integrity": "sha512-YDJuUm0TkKj2WW6GlYmhOuBkaYzZBGJMvZz1X+Qp0Oj8oY3aozQ/YeWw4aNhfpyk5df0DKf6psjMftJI+GThtA==" + }, "@types/eslint": { "version": "7.2.4", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.4.tgz", @@ -1467,6 +1472,11 @@ "moment": "^2.10.2" } }, + "chartist": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/chartist/-/chartist-0.11.4.tgz", + "integrity": "sha512-H4AimxaUD738/u9Mq8t27J4lh6STsLi4BQHt65nOtpLk3xyrBPaLiLMrHw7/WV9CmsjGA02WihjuL5qpSagLYw==" + }, "chartjs-color": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", diff --git a/webapp/src/ClimateChart.ts b/webapp/src/ClimateChart.ts index ba8ec69..ca7863b 100644 --- a/webapp/src/ClimateChart.ts +++ b/webapp/src/ClimateChart.ts @@ -1,6 +1,7 @@ import Chart from "chart.js/dist/Chart.bundle.min"; import type {ChartPoint} from "chart.js"; import {generateClimateChartConfig} from "./climateChartConfig"; +import {config} from "./main"; interface Snapshot { id: number, @@ -25,11 +26,17 @@ class ClimateChart { private onLoadedCallback: () => void = () => {}; private onErrorCallback: (e: Error) => void = () => {}; private errorLog: string = ""; - private readonly rootUrl: string; - private readonly canvasId: string; + private readonly dataEndpointBase: string; + private readonly domId: string; private readonly minutesDisplayed: number = 60; - constructor(rootUrl: string, canvasId: string, minutesDisplayed: number) { + constructor(domId: string, minutesDisplayed: number) { + this.domId = domId; + if (config.development) { + this.dataEndpointBase = "http://tortedda.local/climate/data"; + } else { + this.dataEndpointBase = "data"; + } this.minutesDisplayed = Math.floor(minutesDisplayed); if (minutesDisplayed < 0 || Math.floor(minutesDisplayed) !== minutesDisplayed) { console.warn(`Minutes passed were ${ minutesDisplayed }, which is invalid. ${ this.minutesDisplayed } minutes are being shown instead.`); @@ -37,17 +44,8 @@ class ClimateChart { this.initChart().catch((e) => {this.logError(e);}); } - private async getInitialDataBlob(): Promise { - const data = await fetch("data?since=" + new Date((new Date().getTime() - this.minutesDisplayed * 60000)).toISOString()); - const payload = await data.json(); - if (payload.snapshots.length < 0) { - throw new Error("Bad response - no snapshots found!"); - } - return payload; - } - private async initChart() { - const canvasElement = document.getElementById(this.canvasId); + const canvasElement = document.getElementById(this.domId); let ctx: CanvasRenderingContext2D; if (ClimateChart.isCanvas(canvasElement)) { ctx = canvasElement.getContext('2d'); @@ -59,8 +57,8 @@ class ClimateChart { const payload = await this.getInitialDataBlob(); this.latestSnapshot = payload.snapshots[0]; this.insertSnapshots(...payload.snapshots); + this.rerender(); setInterval(async () => this.updateFromServer().catch(e => this.logError(e)), 30 * 1000); - this.chart.update(); this.onLoadedCallback(); } catch (e) { @@ -68,18 +66,29 @@ class ClimateChart { } } + private async getInitialDataBlob(): Promise { + const minutesAsDate = (new Date().getTime() - this.minutesDisplayed * 60000); + const dataEndpoint = `${ this.dataEndpointBase }?since=${ new Date(minutesAsDate).toISOString() }`; + const payload = await (await fetch(dataEndpoint)).json(); + if (payload.snapshots.length < 0) { + throw new Error("Bad response - no snapshots found!"); + } + return payload; + } + private async updateFromServer() { - const lastTimeInChart = (new Date(this.latestSnapshot.time)).toISOString(); - const url = "data?since=" + lastTimeInChart; + const lastTimeInChart = this.latestSnapshot.time; + const url = `${ this.dataEndpointBase }?since=${ new Date(this.latestSnapshot.time + "+00:00").toISOString() }`; try { const payload: SnapshotRecords = await (await fetch(url)).json(); if (payload.snapshots.length > 0) { - const latestSnapshotIsNew = new Date(payload.snapshots[0].time).getTime() > new Date(lastTimeInChart).getTime(); - if (latestSnapshotIsNew) { - this.removeExpiredPointsAfter(lastTimeInChart); + const newLatestTime = new Date(payload.snapshots[0].time).getTime(); + if (newLatestTime > new Date(lastTimeInChart).getTime()) { + console.log(payload); + this.removePointsOlderThan(newLatestTime - this.minutesDisplayed * 60000); this.latestSnapshot = payload.snapshots[0]; this.insertSnapshots(...payload.snapshots); - this.chart.update(); + this.rerender(); } } } @@ -88,6 +97,10 @@ class ClimateChart { } } + private rerender() { + this.chart.update(); + } + private insertSnapshots(...snapshots: Snapshot[]) { for (const snapshot of snapshots.reverse()) { this.humidityPointList().push({x: snapshot.time, y: snapshot.humidity}); @@ -96,11 +109,10 @@ class ClimateChart { } } - private removeExpiredPointsAfter(referenceTime: string) { + private removePointsOlderThan(referenceTime: number) { for (let i = 0; i < this.humidityPointList().length; i++) { const timeOnPoint = this.humidityPointList()[i].x; - const timeElapsedSinceReference = Date.parse(referenceTime) - Date.parse(timeOnPoint); - if (timeElapsedSinceReference > this.minutesDisplayed * 60000) { + if (new Date(timeOnPoint).getTime() < referenceTime) { this.humidityPointList().splice(i, 1); this.tempPointList().splice(i, 1); this.co2PointList().splice(i, 1); @@ -119,7 +131,7 @@ class ClimateChart { } private co2PointList(): ClimatePoint[] { - return this.chart.data.datasets[1].data as ClimatePoint[]; + return this.chart.data.datasets[2].data as ClimatePoint[]; } onLoaded(callback: () => void) { diff --git a/webapp/src/climateChartConfig.ts b/webapp/src/climateChartConfig.ts index 8a8ce79..b0a13b1 100644 --- a/webapp/src/climateChartConfig.ts +++ b/webapp/src/climateChartConfig.ts @@ -11,9 +11,9 @@ interface ClimateChartSettings { } } -const defaultHumidityColor = 'rgb(45,141,45)'; -const defaultTempColor = 'rgb(0,134,222)'; -const defaultCo2Color = 'rgb(194,30,30)'; +const defaultHumidityColor = 'rgb(196,107,107)'; +const defaultTempColor = 'rgb(173,136,68)'; +const defaultCo2Color = 'rgb(52,133,141)'; export function generateClimateChartConfig(settings: ClimateChartSettings): ChartConfiguration { return { @@ -41,8 +41,13 @@ export function generateClimateChartConfig(settings: ClimateChartSettings): Char }, options: { title: { - display: true, + display: true, text: 'Ledda\'s Room Climate', + fontSize: 50, + }, + legend: { + position: "top", + align: "end", }, scales: { xAxes: [{ diff --git a/webapp/src/config.json b/webapp/src/config.json index e69de29..8031bcb 100644 --- a/webapp/src/config.json +++ b/webapp/src/config.json @@ -0,0 +1,3 @@ +{ + "development": false +} \ No newline at end of file diff --git a/webapp/src/main.ts b/webapp/src/main.ts index ceb10a3..115c097 100644 --- a/webapp/src/main.ts +++ b/webapp/src/main.ts @@ -1,4 +1,6 @@ import ClimateChart from "./ClimateChart"; +import config from "./config.json"; +export {config}; const CHART_DOM_ID: string = "myChart"; let rootUrl: string = ""; @@ -16,7 +18,7 @@ function createClimateChart() { minutesDisplayed = parsedMins; } } - return new ClimateChart(rootUrl, CHART_DOM_ID, minutesDisplayed); + return new ClimateChart(CHART_DOM_ID, minutesDisplayed); } const overlay = document.createElement('div'); @@ -33,7 +35,7 @@ document.onreadystatechange = (e) => { }); climateChart.onErrored((e) => { overlay.classList.remove('hidden'); - textContainer.innerText = `An error occurred: ${e}\nTry restarting the page.`; + textContainer.innerText = `An error occurred: ${e}\nTry reloading the page.`; }); document.onreadystatechange = () => {}; -}; \ No newline at end of file +}; diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index 7c8d2cc..bfa7101 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -6,5 +6,6 @@ "target": "es5", "allowJs": true, "moduleResolution": "Node", + "resolveJsonModule": true } } diff --git a/webapp/webpack.config.js b/webapp/webpack.config.js index 3cbb30e..b4511a0 100644 --- a/webapp/webpack.config.js +++ b/webapp/webpack.config.js @@ -1,34 +1,11 @@ const path = require('path'); const webpack = require('webpack'); - - - - -/* - * SplitChunksPlugin is enabled by default and replaced - * deprecated CommonsChunkPlugin. It automatically identifies modules which - * should be splitted of chunk by heuristics using module duplication count and - * module category (i. e. node_modules). And splits the chunks… - * - * It is safe to remove "splitChunks" from the generated configuration - * and was added as an educational example. - * - * https://webpack.js.org/plugins/split-chunks-plugin/ - * - */ - -/* - * We've enabled TerserPlugin for you! This minifies your app - * in order to load faster and run less javascript. - * - * https://github.com/webpack-contrib/terser-webpack-plugin - * - */ +const config = require('./src/config.json'); const TerserPlugin = require('terser-webpack-plugin'); -module.exports = { - mode: 'production', +const webpackConfig = { + mode: 'development', entry: './src/main.ts', plugins: [new webpack.ProgressPlugin()], @@ -45,7 +22,6 @@ module.exports = { loader: "style-loader" }, { loader: "css-loader", - options: { sourceMap: true } @@ -57,29 +33,34 @@ module.exports = { extensions: ['.tsx', '.ts', '.js'] }, - optimization: { - minimizer: [new TerserPlugin()], - - splitChunks: { - cacheGroups: { - vendors: { - priority: -10, - test: /[\\/]node_modules[\\/]/ - } - }, - - chunks: 'async', - minChunks: 1, - minSize: 30000, - name: false - } - }, - devServer: { contentBase: path.join(__dirname, "dist/"), - contentBasePublicPath: "/static/", + contentBasePublicPath: "/", port: 3000, publicPath: "http://localhost:3000/", hotOnly: true }, -} \ No newline at end of file +}; + +if (!config.development) { + webpackConfig.optimization = { + minimizer: [new TerserPlugin()], + + splitChunks: { + cacheGroups: { + vendors: { + priority: -10, + test: /[\\/]node_modules[\\/]/ + } + }, + + chunks: 'async', + minChunks: 1, + minSize: 30000, + name: false + } + }; + webpackConfig.mode = 'production'; +} + +module.exports = {...webpackConfig}; \ No newline at end of file