diff --git a/.clang-format b/.clang-format index 2f5e3b42b..e84ce1c3c 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Language: Cpp BasedOnStyle: LLVM UseTab: Never IndentWidth: 4 -ColumnLimit: 220 +ColumnLimit: 160 TabWidth: 4 #BreakBeforeBraces: Custom BraceWrapping: diff --git a/.github/workflows/pre_release.yml b/.github/workflows/pre_release.yml index f99027c89..d8308d513 100644 --- a/.github/workflows/pre_release.yml +++ b/.github/workflows/pre_release.yml @@ -1,6 +1,7 @@ name: "pre-release" on: + workflow_dispatch: push: branches: - "dev" @@ -12,37 +13,37 @@ jobs: runs-on: ubuntu-latest steps: + + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: actions/setup-node@v2 + with: + node-version: '16' - - name: Checkout source code - uses: actions/checkout@v2 - - - name: Get build variables + - name: Get EMS-ESP source code and version id: build_info run: | version=`grep -E '^#define EMSESP_APP_VERSION' ./src/version.h | awk -F'"' '{print $2}'` echo "::set-output name=version::$version" - - name: Setup Python - uses: actions/setup-python@v2 - - - name: Install pio + - name: Install PlatformIO run: | python -m pip install --upgrade pip pip install -U platformio platformio upgrade platformio update - - name: Build web + - name: Build WebUI run: | cd interface - npm install + npm ci npm run build - name: Build firmware run: | platformio run -e ci - - name: Release + - name: Create a GH Release id: "automatic_releases" uses: "marvinpinto/action-automatic-releases@latest" with: @@ -53,4 +54,4 @@ jobs: files: | CHANGELOG_LATEST.md ./build/firmware/*.* - + diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index ea1320609..1d0b1fb9c 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -2,8 +2,22 @@ ## Added +- new command called `commands` which lists all available commands. `ems-esp/api/{device}/commands` +- More Home Assistant icons to match the UOMs +- new API. Using secure access tokens and OpenAPI standard. See `doc/EMS-ESP32 API.md` and [#50](https://github.com/emsesp/EMS-ESP32/issues/50) +- show log messages in Web UI [#71](https://github.com/emsesp/EMS-ESP32/issues/71) + ## Fixed +- HA thermostat mode was not in sync with actual mode [#66](https://github.com/emsesp/EMS-ESP32/issues/66) +- Don't publish rssi if Wifi is disabled and ethernet is being used +- Booleans are shown as true/false in API GETs + ## Changed +- `info` command always shows full names in API. For short names query the device or name directly, e.g. `http://ems-esp/api/boiler` +- free memory is shown in kilobytes +- boiler's warm water entities have ww added to the Home Assistant entity name [#67](https://github.com/emsesp/EMS-ESP32/issues/67) +- improved layout and rendering of device values in the WebUI, also the edit value screen + ## Removed diff --git a/README.md b/README.md index 8d8f8ab59..ae7419747 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,15 @@ **EMS-ESP** is an open-source firmware for the Espressif ESP8266 and ESP32 microcontroller that communicates with **EMS** (Energy Management System) based equipment from manufacturers like Bosch, Buderus, Nefit, Junkers, Worcester and Sieger. -This is the firmware for the ESP32. Compared to version 2 for the ESP8266, this release has +This project is the specifically for the ESP32. Compared with the previous ESP8266 (version 2) release it has the following enhancements: - Ethernet Support -- Pre-configured board layouts -- Writing values directly from the Web UI -- Mock API server for faster offline development -- Expose to more commands, via MQTT -- Improvements to Dallas sensors, Shower service - -This version is the latest development track, where as v2 is in maintenance mode. +- Pre-configured circuit board layouts +- Supports writing EMS values directly from within Web UI +- Mock API server for faster offline development and testing +- Improved API and MQTT commands +- Improvements to Dallas temperature sensors +- Embedded log tracing in the Web UI [![version](https://img.shields.io/github/release/emsesp/EMS-ESP32.svg?label=Latest%20Release)](https://github.com/emsesp/EMS-ESP32/blob/main/CHANGELOG.md) [![release-date](https://img.shields.io/github/release-date/emsesp/EMS-ESP32.svg?label=Released)](https://github.com/emsesp/EMS-ESP32/commits/main) @@ -26,7 +25,7 @@ If you like **EMS-ESP**, please give it a star, or fork it and contribute! [![GitHub forks](https://img.shields.io/github/forks/emsesp/EMS-ESP32.svg?style=social&label=Fork)](https://github.com/emsesp/EMS-ES32P/network) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/paypalme/prderbyshire/2) -Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be ordered at . +Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus data to be read by the microcontroller. These can be ordered at or contact the contributors that can provide the schematic and designs. @@ -34,17 +33,16 @@ Note, EMS-ESP requires a small hardware circuit that can convert the EMS bus dat # **Features** -- Compatible with both ESP8266 and ESP32 - A multi-user secure web interface to change settings and monitor the data - A console, accessible via Serial and Telnet for more monitoring - Native support for Home Assistant via [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) - Can run standalone as an independent WiFi Access Point or join an existing WiFi network - Easy first-time configuration via a web Captive Portal -- Support for more than [70 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways) +- Support for more than [80 EMS devices](https://emsesp.github.io/docs/#/Supported-EMS-Devices) (boilers, thermostats, solar modules, mixer modules, heat pumps, gateways) ## **Demo** -See a live demo on [here](https://ems-esp.derbyshire.nl). The data you see is static and any username/password is accepted. +See a live demo [here](https://ems-esp.derbyshire.nl) using fake data. Log in with any username/password. # **Screenshots** @@ -54,6 +52,7 @@ See a live demo on [here](https://ems-esp.derbyshire.nl). The data you see is st | ---------------------------------- | -------------------------------- | | | | | | | +| | | ## Telnet Console @@ -94,20 +93,12 @@ If you're looking for support on **EMS-ESP** there are some options available: # **Contributors ✨** -EMS-ESP is a project originally created by [proddy](https://github.com/proddy) with the main contributors and owners: +EMS-ESP is a project originally created and owned by [proddy](https://github.com/proddy). Key contributors are: - -
- -
proddy
-
- v2 - v3 -

MichaelDvP

v2 v3 @@ -126,11 +117,11 @@ You can also contribute to EMS-ESP by # **Libraries used** -- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the Web UI -- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging is based on these libraries -- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) -- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for MQTT, with modifications from @bertmelis -- ESPAsyncWebServer and AsyncTCP for the Web and TCP backends, with custom modifications for performance +- [esp8266-react](https://github.com/rjwats/esp8266-react) by @rjwats for the framework that provides the core of the Web UI +- [uuid-\*](https://github.com/nomis/mcu-uuid-console) from @nomis. The console, syslog, telnet and logging are based off these open source libraries +- [ArduinoJson](https://github.com/bblanchon/ArduinoJson) for JSON +- [AsyncMqttClient](https://github.com/marvinroger/async-mqtt-client) for the MQTT client, with custom modifications from @bertmelis and @proddy +- ESPAsyncWebServer and AsyncTCP for the Web server and TCP backends, with custom modifications for performance # **License** diff --git a/interface/.env.hosted b/interface/.env.hosted new file mode 100644 index 000000000..bb892d82d --- /dev/null +++ b/interface/.env.hosted @@ -0,0 +1,3 @@ +GENERATE_SOURCEMAP=false + +REACT_APP_HOSTED=true diff --git a/interface/.eslintignore b/interface/.eslintignore new file mode 100644 index 000000000..965d55e2c --- /dev/null +++ b/interface/.eslintignore @@ -0,0 +1,2 @@ +# don't ever lint node_modules +node_modules diff --git a/interface/.eslintrc b/interface/.eslintrc new file mode 100644 index 000000000..3e31639f6 --- /dev/null +++ b/interface/.eslintrc @@ -0,0 +1,27 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "prettier" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "rules": { + // 0 = ignore, 1 = warning, 2 = error + "no-console": 0, + "prettier/prettier": ["error", { endOfLine: "auto" }], + "explicit-function-return-type": 0, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/ban-types": 0, + "@typescript-eslint/no-non-null-asserted-optional-chain": 0, + "@typescript-eslint/no-non-null-assertion": 0, + "@typescript-eslint/no-explicit-any": 0 + } + } + \ No newline at end of file diff --git a/interface/.prettierrc b/interface/.prettierrc new file mode 100644 index 000000000..3fb844c3c --- /dev/null +++ b/interface/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "semi": true, + "trailingComma": "none", + "printWidth": 80 +} diff --git a/interface/config-overrides.js b/interface/config-overrides.js index 41c5c6e6e..96e481183 100644 --- a/interface/config-overrides.js +++ b/interface/config-overrides.js @@ -4,34 +4,49 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const ProgmemGenerator = require('./progmem-generator.js'); -const path = require('path'); -const fs = require('fs'); - module.exports = function override(config, env) { - if (env === "production") { + const hosted = process.env.REACT_APP_HOSTED; + + if (env === 'production' && !hosted) { + console.log('Custom webpack...'); + // rename the output file, we need it's path to be short for LittleFS config.output.filename = 'js/[id].[chunkhash:4].js'; config.output.chunkFilename = 'js/[id].[chunkhash:4].js'; // take out the manifest and service worker plugins - config.plugins = config.plugins.filter(plugin => !(plugin instanceof ManifestPlugin)); - config.plugins = config.plugins.filter(plugin => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW)); + config.plugins = config.plugins.filter( + (plugin) => !(plugin instanceof ManifestPlugin) + ); + config.plugins = config.plugins.filter( + (plugin) => !(plugin instanceof WorkboxWebpackPlugin.GenerateSW) + ); // shorten css filenames - const miniCssExtractPlugin = config.plugins.find((plugin) => plugin instanceof MiniCssExtractPlugin); - miniCssExtractPlugin.options.filename = "css/[id].[contenthash:4].css"; - miniCssExtractPlugin.options.chunkFilename = "css/[id].[contenthash:4].c.css"; + const miniCssExtractPlugin = config.plugins.find( + (plugin) => plugin instanceof MiniCssExtractPlugin + ); + miniCssExtractPlugin.options.filename = 'css/[id].[contenthash:4].css'; + miniCssExtractPlugin.options.chunkFilename = + 'css/[id].[contenthash:4].c.css'; // build progmem data files - config.plugins.push(new ProgmemGenerator({ outputPath: "../lib/framework/WWWData.h", bytesPerLine: 20 })); + config.plugins.push( + new ProgmemGenerator({ + outputPath: '../lib/framework/WWWData.h', + bytesPerLine: 20 + }) + ); // add compression plugin, compress javascript - config.plugins.push(new CompressionPlugin({ - filename: "[path].gz[query]", - algorithm: "gzip", - test: /\.(js)$/, - deleteOriginalAssets: true - })); + config.plugins.push( + new CompressionPlugin({ + filename: '[path].gz[query]', + algorithm: 'gzip', + test: /\.(js)$/, + deleteOriginalAssets: true + }) + ); } return config; -} +}; diff --git a/interface/package-lock.json b/interface/package-lock.json index b0b4052d2..a9ca3a7a6 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -1,15 +1,16 @@ { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "dependencies": { "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", + "@msgpack/msgpack": "^2.7.0", "@types/lodash": "^4.14.168", "@types/node": "^15.0.1", "@types/react": "^17.0.4", @@ -18,6 +19,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", "compression-webpack-plugin": "^5.0.2", + "env-cmd": "^10.1.0", "express": "^4.17.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", @@ -38,9 +40,12 @@ }, "devDependencies": { "concurrently": "^6.0.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0", "http-proxy-middleware": "^1.1.1", "nodemon": "^2.0.7", "npm-run-all": "^4.1.5", + "prettier": "^2.0.5", "react-app-rewired": "^2.1.8" } }, @@ -2171,6 +2176,14 @@ "node": ">=8.0.0" } }, + "node_modules/@msgpack/msgpack": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.7.0.tgz", + "integrity": "sha512-mlRYq9FSsOd4m+3wZWatemn3hGFZPWNJ4JQOdrir4rrMK2PyIk26idKBoUWrqF3HJJHl+5GpRU+M0wEruJwecg==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -4574,21 +4587,25 @@ } }, "node_modules/browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dependencies": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.71" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" } }, "node_modules/bser": { @@ -4831,9 +4848,13 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==" + "version": "1.0.30001235", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz", + "integrity": "sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } }, "node_modules/capture-exit": { "version": "2.0.0", @@ -6609,9 +6630,9 @@ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" }, "node_modules/dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "dependencies": { "ip": "^1.1.0", "safe-buffer": "^5.0.1" @@ -6833,9 +6854,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.3.712", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.712.tgz", - "integrity": "sha512-3kRVibBeCM4vsgoHHGKHmPocLqtFAGTrebXxxtgKs87hNUzXrX2NuS3jnBys7IozCnw7viQlozxKkmty2KNfrw==" + "version": "1.3.749", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz", + "integrity": "sha512-F+v2zxZgw/fMwPz/VUGIggG4ZndDsYy0vlpthi3tjmDZlcfbhN5mYW0evXUsBr2sUtuDANFtle410A9u/sd/4A==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -6971,6 +6992,75 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, + "node_modules/env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "dependencies": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "bin": { + "env-cmd": "bin/env-cmd.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/env-cmd/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/env-cmd/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/env-cmd/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/env-cmd/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/env-cmd/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -7231,6 +7321,18 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, "node_modules/eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -7424,6 +7526,27 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.23.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", @@ -8198,6 +8321,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", @@ -8731,18 +8860,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -13136,9 +13253,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "node_modules/nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==", + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -14748,11 +14865,10 @@ } }, "node_modules/postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", + "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", "dependencies": { - "lodash.template": "^4.5.0", "postcss": "^7.0.2" } }, @@ -15454,24 +15570,20 @@ } }, "node_modules/postcss-safe-parser/node_modules/postcss": { - "version": "8.2.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.9.tgz", - "integrity": "sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.0.tgz", + "integrity": "sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==", "dependencies": { "colorette": "^1.2.2", - "nanoid": "^3.1.22", - "source-map": "^0.6.1" + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" }, "engines": { "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-safe-parser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-selector-matches": { @@ -15590,6 +15702,30 @@ "node": ">=0.10.0" } }, + "node_modules/prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -16812,9 +16948,9 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "node_modules/resolve-url-loader": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", - "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.3.tgz", + "integrity": "sha512-WbDSNFiKPPLem1ln+EVTE+bFUBdTTytfQZWbmghroaFNFaAVmGq0Saqw6F/306CwgPXsGwXVxbODE+3xAo/YbA==", "dependencies": { "adjust-sourcemap-loader": "3.0.0", "camelcase": "5.3.1", @@ -17830,6 +17966,14 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -19641,284 +19785,6 @@ "watchpack-chokidar2": "^2.0.1" } }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "node_modules/watchpack-chokidar2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack-chokidar2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "node_modules/watchpack-chokidar2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -20220,19 +20086,6 @@ "node": ">=6" } }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 4.0" - } - }, "node_modules/webpack-dev-server/node_modules/glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -20574,9 +20427,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dependencies": { "async-limiter": "~1.0.0" } @@ -21353,11 +21206,23 @@ } }, "node_modules/ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "engines": { "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/xdg-basedir": { @@ -23361,6 +23226,11 @@ "react-is": "^16.8.0 || ^17.0.0" } }, + "@msgpack/msgpack": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.7.0.tgz", + "integrity": "sha512-mlRYq9FSsOd4m+3wZWatemn3hGFZPWNJ4JQOdrir4rrMK2PyIk26idKBoUWrqF3HJJHl+5GpRU+M0wEruJwecg==" + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -25462,15 +25332,15 @@ } }, "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.71" } }, "bser": { @@ -25676,9 +25546,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==" + "version": "1.0.30001235", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz", + "integrity": "sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A==" }, "capture-exit": { "version": "2.0.0", @@ -27132,9 +27002,9 @@ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=" }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "requires": { "ip": "^1.1.0", "safe-buffer": "^5.0.1" @@ -27341,9 +27211,9 @@ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==" }, "electron-to-chromium": { - "version": "1.3.712", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.712.tgz", - "integrity": "sha512-3kRVibBeCM4vsgoHHGKHmPocLqtFAGTrebXxxtgKs87hNUzXrX2NuS3jnBys7IozCnw7viQlozxKkmty2KNfrw==" + "version": "1.3.749", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.749.tgz", + "integrity": "sha512-F+v2zxZgw/fMwPz/VUGIggG4ZndDsYy0vlpthi3tjmDZlcfbhN5mYW0evXUsBr2sUtuDANFtle410A9u/sd/4A==" }, "elliptic": { "version": "6.5.4", @@ -27465,6 +27335,53 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" }, + "env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "requires": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -27796,6 +27713,13 @@ } } }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true, + "requires": {} + }, "eslint-config-react-app": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", @@ -27953,6 +27877,15 @@ } } }, + "eslint-plugin-prettier": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", + "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-plugin-react": { "version": "7.23.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", @@ -28448,6 +28381,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-glob": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", @@ -28904,12 +28843,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -32451,9 +32384,9 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "nanoid": { - "version": "3.1.22", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz", - "integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==" + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==" }, "nanomatch": { "version": "1.2.13", @@ -33758,11 +33691,10 @@ } }, "postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.4.tgz", + "integrity": "sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg==", "requires": { - "lodash.template": "^4.5.0", "postcss": "^7.0.2" } }, @@ -34364,19 +34296,14 @@ }, "dependencies": { "postcss": { - "version": "8.2.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.9.tgz", - "integrity": "sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.0.tgz", + "integrity": "sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==", "requires": { "colorette": "^1.2.2", - "nanoid": "^3.1.22", - "source-map": "^0.6.1" + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -34461,6 +34388,21 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -35479,9 +35421,9 @@ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "resolve-url-loader": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", - "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.3.tgz", + "integrity": "sha512-WbDSNFiKPPLem1ln+EVTE+bFUBdTTytfQZWbmghroaFNFaAVmGq0Saqw6F/306CwgPXsGwXVxbODE+3xAo/YbA==", "requires": { "adjust-sourcemap-loader": "3.0.0", "camelcase": "5.3.1", @@ -36353,6 +36295,11 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==" + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -37874,250 +37821,6 @@ "watchpack-chokidar2": "^2.0.1" } }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "optional": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, "wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", @@ -38596,12 +38299,6 @@ "locate-path": "^3.0.0" } }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -38870,9 +38567,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "requires": { "async-limiter": "~1.0.0" } @@ -39323,9 +39020,10 @@ } }, "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "requires": {} }, "xdg-basedir": { "version": "4.0.0", diff --git a/interface/package.json b/interface/package.json index 0726d973c..fd3d99c94 100644 --- a/interface/package.json +++ b/interface/package.json @@ -1,10 +1,11 @@ { - "name": "esp8266-react", + "name": "emsesp-react", "version": "0.1.0", "private": true, "dependencies": { "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.11.2", + "@msgpack/msgpack": "^2.7.0", "@types/lodash": "^4.14.168", "@types/node": "^15.0.1", "@types/react": "^17.0.4", @@ -13,6 +14,7 @@ "@types/react-router": "^5.1.13", "@types/react-router-dom": "^5.1.7", "compression-webpack-plugin": "^5.0.2", + "env-cmd": "^10.1.0", "express": "^4.17.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", @@ -34,9 +36,12 @@ "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", - "eject": "react-scripts eject", + "format": "prettier --write '**/*.{ts,tsx,js,css,json,md}'", + "build-hosted": "env-cmd -f .env.hosted npm run build", + "build-localhost": "PUBLIC_URL=/ react-app-rewired build", "mock-api": "nodemon --watch ../mock-api ../mock-api/server.js", - "dev": "run-p start mock-api" + "standalone": "npm-run-all -p start mock-api", + "lint": "eslint . --ext .ts,.tsx" }, "eslintConfig": { "extends": "react-app" @@ -55,9 +60,12 @@ }, "devDependencies": { "concurrently": "^6.0.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^3.4.0", "http-proxy-middleware": "^1.1.1", "nodemon": "^2.0.7", "npm-run-all": "^4.1.5", + "prettier": "^2.0.5", "react-app-rewired": "^2.1.8" } } diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js index 674adaebe..ef8855962 100644 --- a/interface/progmem-generator.js +++ b/interface/progmem-generator.js @@ -1,19 +1,25 @@ const { resolve, relative, sep } = require('path'); -const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs'); +const { + readdirSync, + existsSync, + unlinkSync, + readFileSync, + createWriteStream +} = require('fs'); var zlib = require('zlib'); var mime = require('mime-types'); -const ARDUINO_INCLUDES = "#include \n\n"; +const ARDUINO_INCLUDES = '#include \n\n'; function getFilesSync(dir, files = []) { - readdirSync(dir, { withFileTypes: true }).forEach(entry => { + readdirSync(dir, { withFileTypes: true }).forEach((entry) => { const entryPath = resolve(dir, entry.name); if (entry.isDirectory()) { getFilesSync(entryPath, files); } else { files.push(entryPath); } - }) + }); return files; } @@ -25,13 +31,17 @@ function cleanAndOpen(path) { if (existsSync(path)) { unlinkSync(path); } - return createWriteStream(path, { flags: "w+" }); + return createWriteStream(path, { flags: 'w+' }); } class ProgmemGenerator { - constructor(options = {}) { - const { outputPath, bytesPerLine = 20, indent = " ", includes = ARDUINO_INCLUDES } = options; + const { + outputPath, + bytesPerLine = 20, + indent = ' ', + includes = ARDUINO_INCLUDES + } = options; this.options = { outputPath, bytesPerLine, indent, includes }; } @@ -41,30 +51,34 @@ class ProgmemGenerator { (compilation, callback) => { const { outputPath, bytesPerLine, indent, includes } = this.options; const fileInfo = []; - const writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath)); + const writeStream = cleanAndOpen( + resolve(compilation.options.context, outputPath) + ); try { const writeIncludes = () => { writeStream.write(includes); - } + }; const writeFile = (relativeFilePath, buffer) => { - const variable = "ESP_REACT_DATA_" + fileInfo.length; + const variable = 'ESP_REACT_DATA_' + fileInfo.length; const mimeType = mime.lookup(relativeFilePath); var size = 0; - writeStream.write("const uint8_t " + variable + "[] PROGMEM = {"); + writeStream.write('const uint8_t ' + variable + '[] PROGMEM = {'); const zipBuffer = zlib.gzipSync(buffer); zipBuffer.forEach((b) => { if (!(size % bytesPerLine)) { - writeStream.write("\n"); + writeStream.write('\n'); writeStream.write(indent); } - writeStream.write("0x" + ("00" + b.toString(16).toUpperCase()).substr(-2) + ","); + writeStream.write( + '0x' + ('00' + b.toString(16).toUpperCase()).substr(-2) + ',' + ); size++; }); if (size % bytesPerLine) { - writeStream.write("\n"); + writeStream.write('\n'); } - writeStream.write("};\n\n"); + writeStream.write('};\n\n'); fileInfo.push({ uri: '/' + relativeFilePath.replace(sep, '/'), mimeType, @@ -84,25 +98,37 @@ class ProgmemGenerator { // process assets const { assets } = compilation; Object.keys(assets).forEach((relativeFilePath) => { - writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source())); + writeFile( + relativeFilePath, + coherseToBuffer(assets[relativeFilePath].source()) + ); }); - } + }; const generateWWWClass = () => { return `typedef std::function RouteRegistrationHandler; class WWWData { ${indent}public: -${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { -${fileInfo.map(file => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`).join('\n')} +${indent.repeat( + 2 +)}static void registerRoutes(RouteRegistrationHandler handler) { +${fileInfo + .map( + (file) => + `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${ + file.variable + }, ${file.size});` + ) + .join('\n')} ${indent.repeat(2)}} }; `; - } + }; const writeWWWClass = () => { writeStream.write(generateWWWClass()); - } + }; writeIncludes(); writeFiles(); diff --git a/interface/public/app/manifest.json b/interface/public/app/manifest.json index ad69575f0..519c1d141 100644 --- a/interface/public/app/manifest.json +++ b/interface/public/app/manifest.json @@ -1,12 +1,12 @@ { - "name":"EMS-ESP", - "icons":[ + "name": "EMS-ESP", + "icons": [ { - "src":"/app/icon.png", - "sizes":"48x48 72x72 96x96 128x128 256x256" + "src": "/app/icon.png", + "sizes": "48x48 72x72 96x96 128x128 256x256" } ], - "start_url":"/", - "display":"fullscreen", - "orientation":"any" + "start_url": "/", + "display": "fullscreen", + "orientation": "any" } diff --git a/interface/public/css/roboto.css b/interface/public/css/roboto.css index ac21f0f55..3214d4c6e 100644 --- a/interface/public/css/roboto.css +++ b/interface/public/css/roboto.css @@ -3,20 +3,26 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/li.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; + src: local('Roboto Light'), local('Roboto-Light'), + url(../fonts/li.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; } @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; + src: local('Roboto'), local('Roboto-Regular'), + url(../fonts/re.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; } @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/me.woff2) format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; -} \ No newline at end of file + src: local('Roboto Medium'), local('Roboto-Medium'), + url(../fonts/me.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; +} diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 32d3fb303..4a9d277f7 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -14,7 +14,6 @@ import FeaturesWrapper from './features/FeaturesWrapper'; const unauthorizedRedirect = () => ; class App extends Component { - notistackRef: RefObject = React.createRef(); componentDidMount() { @@ -23,21 +22,29 @@ class App extends Component { onClickDismiss = (key: string | number | undefined) => () => { this.notistackRef.current.closeSnackbar(key); - } + }; render() { return ( - ( - )}> + )} + > - + @@ -47,4 +54,4 @@ class App extends Component { } } -export default App +export default App; diff --git a/interface/src/AppRouting.tsx b/interface/src/AppRouting.tsx index 12c280bf3..c10881ab8 100644 --- a/interface/src/AppRouting.tsx +++ b/interface/src/AppRouting.tsx @@ -19,9 +19,9 @@ import Mqtt from './mqtt/Mqtt'; import { withFeatures, WithFeaturesProps } from './features/FeaturesContext'; import { Features } from './features/types'; -export const getDefaultRoute = (features: Features) => features.project ? `/${PROJECT_PATH}/` : "/network/"; +export const getDefaultRoute = (features: Features) => + features.project ? `/${PROJECT_PATH}/` : '/network/'; class AppRouting extends Component { - componentDidMount() { Authentication.clearLoginRedirect(); } @@ -35,9 +35,17 @@ class AppRouting extends Component { )} {features.project && ( - + )} - + {features.ntp && ( @@ -52,7 +60,7 @@ class AppRouting extends Component { - ) + ); } } diff --git a/interface/src/CustomMuiTheme.tsx b/interface/src/CustomMuiTheme.tsx index 98caf1315..d62965149 100644 --- a/interface/src/CustomMuiTheme.tsx +++ b/interface/src/CustomMuiTheme.tsx @@ -1,17 +1,21 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { CssBaseline } from '@material-ui/core'; -import { MuiThemeProvider, createMuiTheme, StylesProvider } from '@material-ui/core/styles'; +import { + MuiThemeProvider, + createMuiTheme, + StylesProvider +} from '@material-ui/core/styles'; import { blueGrey, orange, red, green } from '@material-ui/core/colors'; const theme = createMuiTheme({ palette: { - type: "dark", + type: 'dark', primary: { - main: '#33bfff', + main: '#33bfff' }, secondary: { - main: '#3d5afe', + main: '#3d5afe' }, info: { main: blueGrey[500] @@ -29,7 +33,6 @@ const theme = createMuiTheme({ }); export default class CustomMuiTheme extends Component { - render() { return ( @@ -40,5 +43,4 @@ export default class CustomMuiTheme extends Component { ); } - } diff --git a/interface/src/SignIn.tsx b/interface/src/SignIn.tsx index e4df446fb..90cdb4a52 100644 --- a/interface/src/SignIn.tsx +++ b/interface/src/SignIn.tsx @@ -2,53 +2,63 @@ import React, { Component } from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { withStyles, createStyles, Theme, WithStyles } from '@material-ui/core/styles'; +import { + withStyles, + createStyles, + Theme, + WithStyles +} from '@material-ui/core/styles'; import { Paper, Typography, Fab } from '@material-ui/core'; import ForwardIcon from '@material-ui/icons/Forward'; -import { withAuthenticationContext, AuthenticationContextProps } from './authentication/AuthenticationContext'; -import {PasswordValidator} from './components'; +import { + withAuthenticationContext, + AuthenticationContextProps +} from './authentication/AuthenticationContext'; +import { PasswordValidator } from './components'; import { PROJECT_NAME, SIGN_IN_ENDPOINT } from './api'; -const styles = (theme: Theme) => createStyles({ - signInPage: { - display: "flex", - height: "100vh", - margin: "auto", - padding: theme.spacing(2), - justifyContent: "center", - flexDirection: "column", - maxWidth: theme.breakpoints.values.sm - }, - signInPanel: { - textAlign: "center", - padding: theme.spacing(2), - paddingTop: "200px", - backgroundImage: 'url("/app/icon.png")', - backgroundRepeat: "no-repeat", - backgroundPosition: "50% " + theme.spacing(2) + "px", - backgroundSize: "auto 150px", - width: "100%" - }, - extendedIcon: { - marginRight: theme.spacing(0.5), - }, - button: { - marginRight: theme.spacing(2), - marginTop: theme.spacing(2), - } -}); +const styles = (theme: Theme) => + createStyles({ + signInPage: { + display: 'flex', + height: '100vh', + margin: 'auto', + padding: theme.spacing(2), + justifyContent: 'center', + flexDirection: 'column', + maxWidth: theme.breakpoints.values.sm + }, + signInPanel: { + textAlign: 'center', + padding: theme.spacing(2), + paddingTop: '200px', + backgroundImage: 'url("/app/icon.png")', + backgroundRepeat: 'no-repeat', + backgroundPosition: '50% ' + theme.spacing(2) + 'px', + backgroundSize: 'auto 150px', + width: '100%' + }, + extendedIcon: { + marginRight: theme.spacing(0.5) + }, + button: { + marginRight: theme.spacing(2), + marginTop: theme.spacing(2) + } + }); -type SignInProps = WithSnackbarProps & WithStyles & AuthenticationContextProps; +type SignInProps = WithSnackbarProps & + WithStyles & + AuthenticationContextProps; interface SignInState { - username: string, - password: string, - processing: boolean + username: string; + password: string; + processing: boolean; } class SignIn extends Component { - constructor(props: SignInProps) { super(props); this.state = { @@ -60,10 +70,10 @@ class SignIn extends Component { updateInputElement = (event: React.ChangeEvent): void => { const { name, value } = event.currentTarget; - this.setState(prevState => ({ + this.setState((prevState) => ({ ...prevState, - [name]: value, - })) + [name]: value + })); }; onSubmit = () => { @@ -77,20 +87,21 @@ class SignIn extends Component { 'Content-Type': 'application/json' }) }) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } else if (response.status === 401) { - throw Error("Invalid credentials."); + throw Error('Invalid credentials.'); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } - }).then(json => { + }) + .then((json) => { authenticationContext.signIn(json.access_token); }) - .catch(error => { + .catch((error) => { this.props.enqueueSnackbar(error.message, { - variant: 'warning', + variant: 'warning' }); this.setState({ processing: false }); }); @@ -116,8 +127,8 @@ class SignIn extends Component { onChange={this.updateInputElement} margin="normal" inputProps={{ - autoCapitalize: "none", - autoCorrect: "off", + autoCapitalize: 'none', + autoCorrect: 'off' }} /> { onChange={this.updateInputElement} margin="normal" /> - + Sign In @@ -141,7 +158,8 @@ class SignIn extends Component { ); } - } -export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn))); +export default withAuthenticationContext( + withSnackbar(withStyles(styles)(SignIn)) +); diff --git a/interface/src/ap/APModes.ts b/interface/src/ap/APModes.ts index 6a705db39..e89818668 100644 --- a/interface/src/ap/APModes.ts +++ b/interface/src/ap/APModes.ts @@ -1,5 +1,8 @@ -import { APSettings, APProvisionMode } from "./types"; +import { APSettings, APProvisionMode } from './types'; export const isAPEnabled = ({ provision_mode }: APSettings) => { - return provision_mode === APProvisionMode.AP_MODE_ALWAYS || provision_mode === APProvisionMode.AP_MODE_DISCONNECTED; -} + return ( + provision_mode === APProvisionMode.AP_MODE_ALWAYS || + provision_mode === APProvisionMode.AP_MODE_DISCONNECTED + ); +}; diff --git a/interface/src/ap/APSettingsController.tsx b/interface/src/ap/APSettingsController.tsx index 3284b4ab6..1a93050ba 100644 --- a/interface/src/ap/APSettingsController.tsx +++ b/interface/src/ap/APSettingsController.tsx @@ -1,7 +1,12 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { AP_SETTINGS_ENDPOINT } from '../api'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import APSettingsForm from './APSettingsForm'; import { APSettings } from './types'; @@ -9,7 +14,6 @@ import { APSettings } from './types'; type APSettingsControllerProps = RestControllerProps; class APSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class APSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(AP_SETTINGS_ENDPOINT, APSettingsController); diff --git a/interface/src/ap/APSettingsForm.tsx b/interface/src/ap/APSettingsForm.tsx index 0dd34560e..df871a291 100644 --- a/interface/src/ap/APSettingsForm.tsx +++ b/interface/src/ap/APSettingsForm.tsx @@ -1,10 +1,19 @@ import React, { Fragment } from 'react'; -import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; import MenuItem from '@material-ui/core/MenuItem'; import SaveIcon from '@material-ui/icons/Save'; -import { PasswordValidator, RestFormProps, FormActions, FormButton } from '../components'; +import { + PasswordValidator, + RestFormProps, + FormActions, + FormButton +} from '../components'; import { isAPEnabled } from './APModes'; import { APSettings, APProvisionMode } from './types'; @@ -13,7 +22,6 @@ import { isIP } from '../validators'; type APSettingsFormProps = RestFormProps; class APSettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIP', isIP); } @@ -22,23 +30,29 @@ class APSettingsForm extends React.Component { const { data, handleValueChange, saveData } = this.props; return ( - + margin="normal" + > Always - When Network Disconnected + + When Network Disconnected + Never - { - isAPEnabled(data) && + {isAPEnabled(data) && ( { /> { /> { /> { margin="normal" /> - } + )} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/ap/APStatus.ts b/interface/src/ap/APStatus.ts index c35cadc5f..5a0a13eec 100644 --- a/interface/src/ap/APStatus.ts +++ b/interface/src/ap/APStatus.ts @@ -1,5 +1,5 @@ -import { Theme } from "@material-ui/core"; -import { APStatus, APNetworkStatus } from "./types"; +import { Theme } from '@material-ui/core'; +import { APStatus, APNetworkStatus } from './types'; export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { switch (status) { @@ -12,17 +12,17 @@ export const apStatusHighlight = ({ status }: APStatus, theme: Theme) => { default: return theme.palette.warning.main; } -} +}; export const apStatus = ({ status }: APStatus) => { switch (status) { case APNetworkStatus.ACTIVE: - return "Active"; + return 'Active'; case APNetworkStatus.INACTIVE: - return "Inactive"; + return 'Inactive'; case APNetworkStatus.LINGERING: - return "Lingering until idle"; + return 'Lingering until idle'; default: - return "Unknown"; + return 'Unknown'; } }; diff --git a/interface/src/ap/APStatusController.tsx b/interface/src/ap/APStatusController.tsx index e406bca25..5b817d4de 100644 --- a/interface/src/ap/APStatusController.tsx +++ b/interface/src/ap/APStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { AP_STATUS_ENDPOINT } from '../api'; import APStatusForm from './APStatusForm'; @@ -9,7 +14,6 @@ import { APStatus } from './types'; type APStatusControllerProps = RestControllerProps; class APStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,10 +23,10 @@ class APStatusController extends Component { } + render={(formProps) => } /> - ) + ); } } diff --git a/interface/src/ap/APStatusForm.tsx b/interface/src/ap/APStatusForm.tsx index 88c2135e2..39e6c914a 100644 --- a/interface/src/ap/APStatusForm.tsx +++ b/interface/src/ap/APStatusForm.tsx @@ -1,23 +1,34 @@ import React, { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText +} from '@material-ui/core'; import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna'; import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import ComputerIcon from '@material-ui/icons/Computer'; import RefreshIcon from '@material-ui/icons/Refresh'; -import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components'; +import { + RestFormProps, + FormActions, + FormButton, + HighlightAvatar +} from '../components'; import { apStatusHighlight, apStatus } from './APStatus'; import { APStatus } from './types'; type APStatusFormProps = RestFormProps & WithTheme; class APStatusForm extends Component { - createListItems() { - const { data, theme } = this.props + const { data, theme } = this.props; return ( @@ -61,18 +72,20 @@ class APStatusForm extends Component { render() { return ( - - {this.createListItems()} - + {this.createListItems()} - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh ); } - } export default withTheme(APStatusForm); diff --git a/interface/src/ap/AccessPoint.tsx b/interface/src/ap/AccessPoint.tsx index eba011e20..f1b3b0ab0 100644 --- a/interface/src/ap/AccessPoint.tsx +++ b/interface/src/ap/AccessPoint.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, withAuthenticatedContext, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + withAuthenticatedContext, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import APSettingsController from './APSettingsController'; @@ -12,8 +16,7 @@ import APStatusController from './APStatusController'; type AccessPointProps = AuthenticatedContextProps & RouteComponentProps; class AccessPoint extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -21,17 +24,33 @@ class AccessPoint extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } } diff --git a/interface/src/api/Endpoints.ts b/interface/src/api/Endpoints.ts index a30f42e16..4264000d5 100644 --- a/interface/src/api/Endpoints.ts +++ b/interface/src/api/Endpoints.ts @@ -1,23 +1,24 @@ import { ENDPOINT_ROOT } from './Env'; -export const FEATURES_ENDPOINT = ENDPOINT_ROOT + "features"; -export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + "ntpStatus"; -export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "ntpSettings"; -export const TIME_ENDPOINT = ENDPOINT_ROOT + "time"; -export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "apSettings"; -export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + "apStatus"; -export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "scanNetworks"; -export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + "listNetworks"; -export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "networkSettings"; -export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + "networkStatus"; -export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "otaSettings"; -export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + "uploadFirmware"; -export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "mqttSettings"; -export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + "mqttStatus"; -export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus"; -export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn"; -export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization"; -export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings"; -export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken"; -export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart"; -export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset"; +export const FEATURES_ENDPOINT = ENDPOINT_ROOT + 'features'; +export const NTP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'ntpStatus'; +export const NTP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'ntpSettings'; +export const TIME_ENDPOINT = ENDPOINT_ROOT + 'time'; +export const AP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'apSettings'; +export const AP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'apStatus'; +export const SCAN_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'scanNetworks'; +export const LIST_NETWORKS_ENDPOINT = ENDPOINT_ROOT + 'listNetworks'; +export const NETWORK_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'networkSettings'; +export const NETWORK_STATUS_ENDPOINT = ENDPOINT_ROOT + 'networkStatus'; +export const OTA_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'otaSettings'; +export const UPLOAD_FIRMWARE_ENDPOINT = ENDPOINT_ROOT + 'uploadFirmware'; +export const MQTT_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'mqttSettings'; +export const MQTT_STATUS_ENDPOINT = ENDPOINT_ROOT + 'mqttStatus'; +export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + 'systemStatus'; +export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + 'signIn'; +export const VERIFY_AUTHORIZATION_ENDPOINT = + ENDPOINT_ROOT + 'verifyAuthorization'; +export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'securitySettings'; +export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + 'generateToken'; +export const RESTART_ENDPOINT = ENDPOINT_ROOT + 'restart'; +export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + 'factoryReset'; diff --git a/interface/src/api/Env.ts b/interface/src/api/Env.ts index 9992e6811..3bfef1d74 100644 --- a/interface/src/api/Env.ts +++ b/interface/src/api/Env.ts @@ -1,24 +1,25 @@ export const PROJECT_NAME = process.env.REACT_APP_PROJECT_NAME!; export const PROJECT_PATH = process.env.REACT_APP_PROJECT_PATH!; -export const ENDPOINT_ROOT = calculateEndpointRoot("/rest/"); -export const WEB_SOCKET_ROOT = calculateWebSocketRoot("/ws/"); +export const ENDPOINT_ROOT = calculateEndpointRoot('/rest/'); +export const WEB_SOCKET_ROOT = calculateWebSocketRoot('/ws/'); +export const EVENT_SOURCE_ROOT = calculateEndpointRoot('/es/'); function calculateEndpointRoot(endpointPath: string) { - const httpRoot = process.env.REACT_APP_HTTP_ROOT; - if (httpRoot) { - return httpRoot + endpointPath; - } - const location = window.location; - return location.protocol + "//" + location.host + endpointPath; + const httpRoot = process.env.REACT_APP_HTTP_ROOT; + if (httpRoot) { + return httpRoot + endpointPath; + } + const location = window.location; + return location.protocol + '//' + location.host + endpointPath; } function calculateWebSocketRoot(webSocketPath: string) { - const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT; - if (webSocketRoot) { - return webSocketRoot + webSocketPath; - } - const location = window.location; - const webProtocol = location.protocol === "https:" ? "wss:" : "ws:"; - return webProtocol + "//" + location.host + webSocketPath; + const webSocketRoot = process.env.REACT_APP_WEB_SOCKET_ROOT; + if (webSocketRoot) { + return webSocketRoot + webSocketPath; + } + const location = window.location; + const webProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; + return webProtocol + '//' + location.host + webSocketPath; } diff --git a/interface/src/api/index.ts b/interface/src/api/index.ts index 8ec579747..1e5b5ff82 100644 --- a/interface/src/api/index.ts +++ b/interface/src/api/index.ts @@ -1,2 +1,2 @@ -export * from './Env' -export * from './Endpoints' +export * from './Env'; +export * from './Endpoints'; diff --git a/interface/src/authentication/AuthenticatedRoute.tsx b/interface/src/authentication/AuthenticatedRoute.tsx index 3c71234a9..ff28427d0 100644 --- a/interface/src/authentication/AuthenticatedRoute.tsx +++ b/interface/src/authentication/AuthenticatedRoute.tsx @@ -1,40 +1,56 @@ import * as React from 'react'; -import { Redirect, Route, RouteProps, RouteComponentProps } from "react-router-dom"; +import { + Redirect, + Route, + RouteProps, + RouteComponentProps +} from 'react-router-dom'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import * as Authentication from './Authentication'; -import { withAuthenticationContext, AuthenticationContextProps, AuthenticatedContext, AuthenticatedContextValue } from './AuthenticationContext'; +import { + withAuthenticationContext, + AuthenticationContextProps, + AuthenticatedContext, + AuthenticatedContextValue +} from './AuthenticationContext'; -interface AuthenticatedRouteProps extends RouteProps, WithSnackbarProps, AuthenticationContextProps { - component: React.ComponentType> | React.ComponentType; +interface AuthenticatedRouteProps + extends RouteProps, + WithSnackbarProps, + AuthenticationContextProps { + component: + | React.ComponentType> + | React.ComponentType; } type RenderComponent = (props: RouteComponentProps) => React.ReactNode; export class AuthenticatedRoute extends React.Component { - render() { - const { enqueueSnackbar, authenticationContext, component: Component, ...rest } = this.props; + const { + enqueueSnackbar, + authenticationContext, + component: Component, + ...rest + } = this.props; const { location } = this.props; const renderComponent: RenderComponent = (props) => { if (authenticationContext.me) { return ( - + ); } Authentication.storeLoginRedirect(location); - enqueueSnackbar("Please sign in to continue", { variant: 'info' }); - return ( - - ); - } - return ( - - ); + enqueueSnackbar('Please sign in to continue', { variant: 'info' }); + return ; + }; + return ; } - } export default withSnackbar(withAuthenticationContext(AuthenticatedRoute)); diff --git a/interface/src/authentication/Authentication.ts b/interface/src/authentication/Authentication.ts index 4c8749348..26bb65ded 100644 --- a/interface/src/authentication/Authentication.ts +++ b/interface/src/authentication/Authentication.ts @@ -27,7 +27,9 @@ export function clearLoginRedirect() { getStorage().removeItem(SIGN_IN_SEARCH); } -export function fetchLoginRedirect(features: Features): H.LocationDescriptorObject { +export function fetchLoginRedirect( + features: Features +): H.LocationDescriptorObject { const signInPathname = getStorage().getItem(SIGN_IN_PATHNAME); const signInSearch = getStorage().getItem(SIGN_IN_SEARCH); clearLoginRedirect(); @@ -38,43 +40,51 @@ export function fetchLoginRedirect(features: Features): H.LocationDescriptorObje } /** - * Wraps the normal fetch routene with one with provides the access token if present. + * Wraps the normal fetch routine with one with provides the access token if present. */ -export function authorizedFetch(url: RequestInfo, params?: RequestInit): Promise { +export function authorizedFetch( + url: RequestInfo, + params?: RequestInit +): Promise { const accessToken = getStorage().getItem(ACCESS_TOKEN); if (accessToken) { params = params || {}; params.credentials = 'include'; params.headers = { ...params.headers, - "Authorization": 'Bearer ' + accessToken + Authorization: 'Bearer ' + accessToken }; } return fetch(url, params); } /** - * fetch() does not yet support upload progress, this wrapper allows us to configure the xhr request - * for a single file upload and takes care of adding the Authroization header and redirecting on - * authroization errors as we do for normal fetch operations. + * fetch() does not yet support upload progress, this wrapper allows us to configure the xhr request + * for a single file upload and takes care of adding the Authorization header and redirecting on + * authorization errors as we do for normal fetch operations. */ -export function redirectingAuthorizedUpload(xhr: XMLHttpRequest, url: string, file: File, onProgress: (event: ProgressEvent) => void): Promise { +export function redirectingAuthorizedUpload( + xhr: XMLHttpRequest, + url: string, + file: File, + onProgress: (event: ProgressEvent) => void +): Promise { return new Promise((resolve, reject) => { - xhr.open("POST", url, true); + xhr.open('POST', url, true); const accessToken = getStorage().getItem(ACCESS_TOKEN); if (accessToken) { xhr.withCredentials = true; - xhr.setRequestHeader("Authorization", 'Bearer ' + accessToken); + xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken); } xhr.upload.onprogress = onProgress; xhr.onload = function () { if (xhr.status === 401 || xhr.status === 403) { - history.push("/unauthorized"); + history.push('/unauthorized'); } else { resolve(); } }; - xhr.onerror = function (event: ProgressEvent) { + xhr.onerror = function () { reject(new DOMException('Error', 'UploadError')); }; xhr.onabort = function () { @@ -87,19 +97,24 @@ export function redirectingAuthorizedUpload(xhr: XMLHttpRequest, url: string, fi } /** - * Wraps the normal fetch routene which redirects on 401 response. + * Wraps the normal fetch routine which redirects on 401 response. */ -export function redirectingAuthorizedFetch(url: RequestInfo, params?: RequestInit): Promise { +export function redirectingAuthorizedFetch( + url: RequestInfo, + params?: RequestInit +): Promise { return new Promise((resolve, reject) => { - authorizedFetch(url, params).then(response => { - if (response.status === 401 || response.status === 403) { - history.push("/unauthorized"); - } else { - resolve(response); - } - }).catch(error => { - reject(error); - }); + authorizedFetch(url, params) + .then((response) => { + if (response.status === 401 || response.status === 403) { + history.push('/unauthorized'); + } else { + resolve(response); + } + }) + .catch((error) => { + reject(error); + }); }); } diff --git a/interface/src/authentication/AuthenticationContext.tsx b/interface/src/authentication/AuthenticationContext.tsx index 40b0ce177..7350c0d03 100644 --- a/interface/src/authentication/AuthenticationContext.tsx +++ b/interface/src/authentication/AuthenticationContext.tsx @@ -1,9 +1,8 @@ -import * as React from "react"; +import * as React from 'react'; export interface Me { username: string; admin: boolean; - version: string; // proddy added } export interface AuthenticationContextValue { @@ -13,7 +12,7 @@ export interface AuthenticationContextValue { me?: Me; } -const AuthenticationContextDefaultValue = {} as AuthenticationContextValue +const AuthenticationContextDefaultValue = {} as AuthenticationContextValue; export const AuthenticationContext = React.createContext( AuthenticationContextDefaultValue ); @@ -22,12 +21,21 @@ export interface AuthenticationContextProps { authenticationContext: AuthenticationContextValue; } -export function withAuthenticationContext(Component: React.ComponentType) { - return class extends React.Component> { +export function withAuthenticationContext( + Component: React.ComponentType +) { + return class extends React.Component< + Omit + > { render() { return ( - {authenticationContext => } + {(authenticationContext) => ( + + )} ); } @@ -38,7 +46,7 @@ export interface AuthenticatedContextValue extends AuthenticationContextValue { me: Me; } -const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue +const AuthenticatedContextDefaultValue = {} as AuthenticatedContextValue; export const AuthenticatedContext = React.createContext( AuthenticatedContextDefaultValue ); @@ -47,14 +55,23 @@ export interface AuthenticatedContextProps { authenticatedContext: AuthenticatedContextValue; } -export function withAuthenticatedContext(Component: React.ComponentType) { - return class extends React.Component> { +export function withAuthenticatedContext( + Component: React.ComponentType +) { + return class extends React.Component< + Omit + > { render() { return ( - {authenticatedContext => } + {(authenticatedContext) => ( + + )} ); } }; -} \ No newline at end of file +} diff --git a/interface/src/authentication/AuthenticationWrapper.tsx b/interface/src/authentication/AuthenticationWrapper.tsx index c91522c8b..3437ff719 100644 --- a/interface/src/authentication/AuthenticationWrapper.tsx +++ b/interface/src/authentication/AuthenticationWrapper.tsx @@ -2,14 +2,19 @@ import * as React from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; import jwtDecode from 'jwt-decode'; -import history from '../history' +import history from '../history'; import { VERIFY_AUTHORIZATION_ENDPOINT } from '../api'; import { ACCESS_TOKEN, authorizedFetch, getStorage } from './Authentication'; -import { AuthenticationContext, AuthenticationContextValue, Me } from './AuthenticationContext'; +import { + AuthenticationContext, + AuthenticationContextValue, + Me +} from './AuthenticationContext'; import FullScreenLoading from '../components/FullScreenLoading'; import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; -export const decodeMeJWT = (accessToken: string): Me => jwtDecode(accessToken) as Me; +export const decodeMeJWT = (accessToken: string): Me => + jwtDecode(accessToken) as Me; interface AuthenticationWrapperState { context: AuthenticationContextValue; @@ -18,15 +23,17 @@ interface AuthenticationWrapperState { type AuthenticationWrapperProps = WithSnackbarProps & WithFeaturesProps; -class AuthenticationWrapper extends React.Component { - +class AuthenticationWrapper extends React.Component< + AuthenticationWrapperProps, + AuthenticationWrapperState +> { constructor(props: AuthenticationWrapperProps) { super(props); this.state = { context: { refresh: this.refresh, signIn: this.signIn, - signOut: this.signOut, + signOut: this.signOut }, initialized: false }; @@ -39,7 +46,9 @@ class AuthenticationWrapper extends React.Component - {this.state.initialized ? this.renderContent() : this.renderContentLoading()} + {this.state.initialized + ? this.renderContent() + : this.renderContentLoading()} ); } @@ -53,9 +62,7 @@ class AuthenticationWrapper extends React.Component - ); + return ; } refresh = () => { @@ -64,34 +71,53 @@ class AuthenticationWrapper extends React.Component { - const me = response.status === 200 ? decodeMeJWT(accessToken) : undefined; - this.setState({ initialized: true, context: { ...this.state.context, me } }); - }).catch(error => { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); - this.props.enqueueSnackbar("Error verifying authorization: " + error.message, { - variant: 'error', + .then((response) => { + const me = + response.status === 200 ? decodeMeJWT(accessToken) : undefined; + this.setState({ + initialized: true, + context: { ...this.state.context, me } }); + }) + .catch((error) => { + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); + this.props.enqueueSnackbar( + 'Error verifying authorization: ' + error.message, + { + variant: 'error' + } + ); }); } else { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); } - } + }; signIn = (accessToken: string) => { try { getStorage().setItem(ACCESS_TOKEN, accessToken); const me: Me = decodeMeJWT(accessToken); this.setState({ context: { ...this.state.context, me } }); - this.props.enqueueSnackbar(`Logged in as ${me.username}`, { variant: 'success' }); + this.props.enqueueSnackbar(`Logged in as ${me.username}`, { + variant: 'success' + }); } catch (err) { - this.setState({ initialized: true, context: { ...this.state.context, me: undefined } }); - throw new Error("Failed to parse JWT " + err.message); + this.setState({ + initialized: true, + context: { ...this.state.context, me: undefined } + }); + throw new Error('Failed to parse JWT ' + err.message); } - } + }; signOut = () => { getStorage().removeItem(ACCESS_TOKEN); @@ -101,10 +127,9 @@ class AuthenticationWrapper extends React.Component> | React.ComponentType; +interface UnauthenticatedRouteProps + extends RouteProps, + AuthenticationContextProps, + WithFeaturesProps { + component: + | React.ComponentType> + | React.ComponentType; } type RenderComponent = (props: RouteComponentProps) => React.ReactNode; class UnauthenticatedRoute extends Route { - public render() { - const { authenticationContext, component: Component, features, ...rest } = this.props; + const { + authenticationContext, + component: Component, + features, + ...rest + } = this.props; const renderComponent: RenderComponent = (props) => { if (authenticationContext.me) { - return (); + return ; } if (Component) { - return (); + return ; } - } - return ( - - ); + }; + return ; } } diff --git a/interface/src/authentication/index.ts b/interface/src/authentication/index.ts index fa528ff5b..d93294c89 100644 --- a/interface/src/authentication/index.ts +++ b/interface/src/authentication/index.ts @@ -3,4 +3,4 @@ export { default as AuthenticationWrapper } from './AuthenticationWrapper'; export { default as UnauthenticatedRoute } from './UnauthenticatedRoute'; export * from './Authentication'; -export * from './AuthenticationContext'; \ No newline at end of file +export * from './AuthenticationContext'; diff --git a/interface/src/components/ApplicationError.tsx b/interface/src/components/ApplicationError.tsx index 7fbe0fc4c..46cbae129 100644 --- a/interface/src/components/ApplicationError.tsx +++ b/interface/src/components/ApplicationError.tsx @@ -1,27 +1,25 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/styles'; -import { Paper, Typography, Box, CssBaseline } from "@material-ui/core"; -import WarningIcon from "@material-ui/icons/Warning" +import { Paper, Typography, Box, CssBaseline } from '@material-ui/core'; +import WarningIcon from '@material-ui/icons/Warning'; -const styles = makeStyles( - { - siteErrorPage: { - display: "flex", - height: "100vh", - justifyContent: "center", - flexDirection: "column" - }, - siteErrorPagePanel: { - textAlign: "center", - padding: "280px 0 40px 0", - backgroundImage: 'url("/app/icon.png")', - backgroundRepeat: "no-repeat", - backgroundPosition: "50% 40px", - backgroundSize: "200px auto", - width: "100%", - } +const styles = makeStyles({ + siteErrorPage: { + display: 'flex', + height: '100vh', + justifyContent: 'center', + flexDirection: 'column' + }, + siteErrorPagePanel: { + textAlign: 'center', + padding: '280px 0 40px 0', + backgroundImage: 'url("/app/icon.png")', + backgroundRepeat: 'no-repeat', + backgroundPosition: '50% 40px', + backgroundSize: '200px auto', + width: '100%' } -); +}); interface ApplicationErrorProps { error?: string; @@ -33,27 +31,29 @@ const ApplicationError: FC = ({ error }) => {
- + - - Application error - + Application error Failed to configure the application, please refresh to try again. - {error && - ( - - Error: {error} - - ) - } + {error && ( + + Error: {error} + + )}
); -} +}; export default ApplicationError; diff --git a/interface/src/components/BlockFormControlLabel.tsx b/interface/src/components/BlockFormControlLabel.tsx index 2ea2ebab2..3067c961f 100644 --- a/interface/src/components/BlockFormControlLabel.tsx +++ b/interface/src/components/BlockFormControlLabel.tsx @@ -1,10 +1,10 @@ -import React, { FC } from "react"; -import { FormControlLabel, FormControlLabelProps } from "@material-ui/core"; +import { FC } from 'react'; +import { FormControlLabel, FormControlLabelProps } from '@material-ui/core'; const BlockFormControlLabel: FC = (props) => (
-) +); export default BlockFormControlLabel; diff --git a/interface/src/components/ErrorButton.tsx b/interface/src/components/ErrorButton.tsx index c93cddd66..667b6eb6c 100644 --- a/interface/src/components/ErrorButton.tsx +++ b/interface/src/components/ErrorButton.tsx @@ -1,10 +1,10 @@ -import { Button, styled } from "@material-ui/core"; +import { Button, styled } from '@material-ui/core'; const ErrorButton = styled(Button)(({ theme }) => ({ color: theme.palette.getContrastText(theme.palette.error.main), backgroundColor: theme.palette.error.main, '&:hover': { - backgroundColor: theme.palette.error.dark, + backgroundColor: theme.palette.error.dark } })); diff --git a/interface/src/components/FormActions.tsx b/interface/src/components/FormActions.tsx index 8c6eb73fe..8e47e9ad9 100644 --- a/interface/src/components/FormActions.tsx +++ b/interface/src/components/FormActions.tsx @@ -1,4 +1,4 @@ -import { styled, Box } from "@material-ui/core"; +import { styled, Box } from '@material-ui/core'; const FormActions = styled(Box)(({ theme }) => ({ marginTop: theme.spacing(1) diff --git a/interface/src/components/FormButton.tsx b/interface/src/components/FormButton.tsx index f14ef0653..3254c29f4 100644 --- a/interface/src/components/FormButton.tsx +++ b/interface/src/components/FormButton.tsx @@ -1,12 +1,12 @@ -import { Button, styled } from "@material-ui/core"; +import { Button, styled } from '@material-ui/core'; const FormButton = styled(Button)(({ theme }) => ({ margin: theme.spacing(0, 1), '&:last-child': { - marginRight: 0, + marginRight: 0 }, '&:first-child': { - marginLeft: 0, + marginLeft: 0 } })); diff --git a/interface/src/components/FullScreenLoading.tsx b/interface/src/components/FullScreenLoading.tsx index d08d90466..4e6c02086 100644 --- a/interface/src/components/FullScreenLoading.tsx +++ b/interface/src/components/FullScreenLoading.tsx @@ -3,30 +3,30 @@ import CircularProgress from '@material-ui/core/CircularProgress'; import { Typography, Theme } from '@material-ui/core'; import { makeStyles, createStyles } from '@material-ui/styles'; -const useStyles = makeStyles((theme: Theme) => createStyles({ - fullScreenLoading: { - padding: theme.spacing(2), - display: "flex", - alignItems: "center", - justifyContent: "center", - height: "100vh", - flexDirection: "column" - }, - progress: { - margin: theme.spacing(4), - } -})); +const useStyles = makeStyles((theme: Theme) => + createStyles({ + fullScreenLoading: { + padding: theme.spacing(2), + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + flexDirection: 'column' + }, + progress: { + margin: theme.spacing(4) + } + }) +); const FullScreenLoading = () => { const classes = useStyles(); return (
- - Loading… - + Loading…
- ) -} + ); +}; export default FullScreenLoading; diff --git a/interface/src/components/HighlightAvatar.tsx b/interface/src/components/HighlightAvatar.tsx index f7ce8c752..cfc640948 100644 --- a/interface/src/components/HighlightAvatar.tsx +++ b/interface/src/components/HighlightAvatar.tsx @@ -1,5 +1,5 @@ -import { Avatar, makeStyles } from "@material-ui/core"; -import React, { FC } from "react"; +import { Avatar, makeStyles } from '@material-ui/core'; +import { FC } from 'react'; interface HighlightAvatarProps { color: string; @@ -13,11 +13,7 @@ const useStyles = makeStyles({ const HighlightAvatar: FC = (props) => { const classes = useStyles(props); - return ( - - {props.children} - - ); -} + return {props.children}; +}; export default HighlightAvatar; diff --git a/interface/src/components/MenuAppBar.tsx b/interface/src/components/MenuAppBar.tsx index 9c8e9040b..f33b6cb66 100644 --- a/interface/src/components/MenuAppBar.tsx +++ b/interface/src/components/MenuAppBar.tsx @@ -1,12 +1,39 @@ import React, { RefObject, Fragment } from 'react'; import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import { Drawer, AppBar, Toolbar, Avatar, Divider, Button, Box, IconButton } from '@material-ui/core'; -import { ClickAwayListener, Popper, Hidden, Typography } from '@material-ui/core'; -import { List, ListItem, ListItemIcon, ListItemText, ListItemAvatar } from '@material-ui/core'; +import { + Drawer, + AppBar, + Toolbar, + Avatar, + Divider, + Button, + Box, + IconButton +} from '@material-ui/core'; +import { + ClickAwayListener, + Popper, + Hidden, + Typography +} from '@material-ui/core'; +import { + List, + ListItem, + ListItemIcon, + ListItemText, + ListItemAvatar +} from '@material-ui/core'; import { Card, CardContent, CardActions } from '@material-ui/core'; -import { withStyles, createStyles, Theme, WithTheme, WithStyles, withTheme } from '@material-ui/core/styles'; +import { + withStyles, + createStyles, + Theme, + WithTheme, + WithStyles, + withTheme +} from '@material-ui/core/styles'; import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet'; import SettingsIcon from '@material-ui/icons/Settings'; @@ -19,76 +46,84 @@ import MenuIcon from '@material-ui/icons/Menu'; import ProjectMenu from '../project/ProjectMenu'; import { PROJECT_NAME } from '../api'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; import { withFeatures, WithFeaturesProps } from '../features/FeaturesContext'; const drawerWidth = 290; -const styles = (theme: Theme) => createStyles({ - root: { - display: 'flex', - }, - drawer: { - [theme.breakpoints.up('md')]: { - width: drawerWidth, - flexShrink: 0, +const styles = (theme: Theme) => + createStyles({ + root: { + display: 'flex' }, - }, - title: { - flexGrow: 1 - }, - appBar: { - marginLeft: drawerWidth, - [theme.breakpoints.up('md')]: { - width: `calc(100% - ${drawerWidth}px)`, + drawer: { + [theme.breakpoints.up('md')]: { + width: drawerWidth, + flexShrink: 0 + } }, - }, - toolbarImage: { - [theme.breakpoints.up('xs')]: { - height: 24, - marginRight: theme.spacing(2) + title: { + flexGrow: 1 }, - [theme.breakpoints.up('sm')]: { - height: 36, - marginRight: theme.spacing(3) + appBar: { + marginLeft: drawerWidth, + [theme.breakpoints.up('md')]: { + width: `calc(100% - ${drawerWidth}px)` + } }, - }, - menuButton: { - marginRight: theme.spacing(2), - [theme.breakpoints.up('md')]: { - display: 'none', + toolbarImage: { + [theme.breakpoints.up('xs')]: { + height: 24, + marginRight: theme.spacing(2) + }, + [theme.breakpoints.up('sm')]: { + height: 36, + marginRight: theme.spacing(3) + } }, - }, - toolbar: theme.mixins.toolbar, - drawerPaper: { - width: drawerWidth, - }, - content: { - flexGrow: 1 - }, - authMenu: { - zIndex: theme.zIndex.tooltip, - maxWidth: 400, - }, - authMenuActions: { - padding: theme.spacing(2), - "& > * + *": { - marginLeft: theme.spacing(2), + menuButton: { + marginRight: theme.spacing(2), + [theme.breakpoints.up('md')]: { + display: 'none' + } + }, + toolbar: theme.mixins.toolbar, + drawerPaper: { + width: drawerWidth + }, + content: { + flexGrow: 1 + }, + authMenu: { + zIndex: theme.zIndex.tooltip, + maxWidth: 400 + }, + authMenuActions: { + padding: theme.spacing(2), + '& > * + *': { + marginLeft: theme.spacing(2) + } } - }, -}); + }); interface MenuAppBarState { mobileOpen: boolean; authMenuOpen: boolean; } -interface MenuAppBarProps extends WithFeaturesProps, AuthenticatedContextProps, WithTheme, WithStyles, RouteComponentProps { +interface MenuAppBarProps + extends WithFeaturesProps, + AuthenticatedContextProps, + WithTheme, + WithStyles, + RouteComponentProps { sectionTitle: string; } class MenuAppBar extends React.Component { - constructor(props: MenuAppBarProps) { super(props); this.state = { @@ -101,38 +136,48 @@ class MenuAppBar extends React.Component { handleToggle = () => { this.setState({ authMenuOpen: !this.state.authMenuOpen }); - } + }; handleClose = (event: React.MouseEvent) => { - if (this.anchorRef.current && this.anchorRef.current.contains(event.currentTarget)) { + if ( + this.anchorRef.current && + this.anchorRef.current.contains(event.currentTarget) + ) { return; } this.setState({ authMenuOpen: false }); - } + }; handleDrawerToggle = () => { this.setState({ mobileOpen: !this.state.mobileOpen }); }; render() { - const { classes, theme, children, sectionTitle, authenticatedContext, features } = this.props; + const { + classes, + theme, + children, + sectionTitle, + authenticatedContext, + features + } = this.props; const { mobileOpen, authMenuOpen } = this.state; const path = this.props.match.url; const drawer = (
- {PROJECT_NAME} + {PROJECT_NAME} {PROJECT_NAME} - -   v{authenticatedContext.me.version} - - @@ -142,22 +187,37 @@ class MenuAppBar extends React.Component { )} - + - + - + {features.ntp && ( - + @@ -165,7 +225,12 @@ class MenuAppBar extends React.Component { )} {features.mqtt && ( - + @@ -173,14 +238,25 @@ class MenuAppBar extends React.Component { )} {features.security && ( - + )} - + @@ -201,7 +277,12 @@ class MenuAppBar extends React.Component { > - + @@ -212,13 +293,27 @@ class MenuAppBar extends React.Component { - + - + @@ -239,7 +334,12 @@ class MenuAppBar extends React.Component { > - + {sectionTitle} {features.security && userMenu} @@ -253,10 +353,10 @@ class MenuAppBar extends React.Component { open={mobileOpen} onClose={this.handleDrawerToggle} classes={{ - paper: classes.drawerPaper, + paper: classes.drawerPaper }} ModalProps={{ - keepMounted: true, + keepMounted: true }} > {drawer} @@ -265,7 +365,7 @@ class MenuAppBar extends React.Component { { export default withRouter( withTheme( - withFeatures( - withAuthenticatedContext( - withStyles(styles)(MenuAppBar) - ) - ) + withFeatures(withAuthenticatedContext(withStyles(styles)(MenuAppBar))) ) ); diff --git a/interface/src/components/PasswordValidator.tsx b/interface/src/components/PasswordValidator.tsx index 8ee027622..6cdc99a20 100644 --- a/interface/src/components/PasswordValidator.tsx +++ b/interface/src/components/PasswordValidator.tsx @@ -1,26 +1,32 @@ import React from 'react'; -import { TextValidator, ValidatorComponentProps } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorComponentProps +} from 'react-material-ui-form-validator'; import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; import { InputAdornment, IconButton } from '@material-ui/core'; -import {Visibility,VisibilityOff } from '@material-ui/icons'; +import { Visibility, VisibilityOff } from '@material-ui/icons'; const styles = createStyles({ input: { - "&::-ms-reveal": { - display: "none" + '&::-ms-reveal': { + display: 'none' } } }); -type PasswordValidatorProps = WithStyles & Exclude; +type PasswordValidatorProps = WithStyles & + Exclude; interface PasswordValidatorState { showPassword: boolean; } -class PasswordValidator extends React.Component { - +class PasswordValidator extends React.Component< + PasswordValidatorProps, + PasswordValidatorState +> { state = { showPassword: false }; @@ -29,7 +35,7 @@ class PasswordValidator extends React.Component : } + ) }} /> ); } - } export default withStyles(styles)(PasswordValidator); diff --git a/interface/src/components/RestController.tsx b/interface/src/components/RestController.tsx index c9751c6a1..0e3019aa4 100644 --- a/interface/src/components/RestController.tsx +++ b/interface/src/components/RestController.tsx @@ -4,7 +4,9 @@ import { withSnackbar, WithSnackbarProps } from 'notistack'; import { redirectingAuthorizedFetch } from '../authentication'; export interface RestControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof D + ) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -15,16 +17,18 @@ export interface RestControllerProps extends WithSnackbarProps { errorMessage?: string; } -export const extractEventValue = (event: React.ChangeEvent) => { +export const extractEventValue = ( + event: React.ChangeEvent +) => { switch (event.target.type) { - case "number": + case 'number': return event.target.valueAsNumber; - case "checkbox": + case 'checkbox': return event.target.checked; default: - return event.target.value + return event.target.value; } -} +}; interface RestControllerState { data?: D; @@ -32,10 +36,15 @@ interface RestControllerState { errorMessage?: string; } -export function restController>(endpointUrl: string, RestController: React.ComponentType

>) { +export function restController>( + endpointUrl: string, + RestController: React.ComponentType

> +) { return withSnackbar( - class extends React.Component> & WithSnackbarProps, RestControllerState> { - + class extends React.Component< + Omit> & WithSnackbarProps, + RestControllerState + > { state: RestControllerState = { data: undefined, loading: false, @@ -43,12 +52,15 @@ export function restController>(endpointUrl: }; setData = (data: D, callback?: () => void) => { - this.setState({ - data, - loading: false, - errorMessage: undefined - }, callback); - } + this.setState( + { + data, + loading: false, + errorMessage: undefined + }, + callback + ); + }; loadData = () => { this.setState({ @@ -56,19 +68,24 @@ export function restController>(endpointUrl: loading: true, errorMessage: undefined }); - redirectingAuthorizedFetch(endpointUrl).then(response => { - if (response.status === 200) { - return response.json(); - } - throw Error("Invalid status code: " + response.status); - }).then(json => { - this.setState({ data: json, loading: false }) - }).catch(error => { - const errorMessage = error.message || "Unknown error"; - this.props.enqueueSnackbar("Problem fetching: " + errorMessage, { variant: 'error' }); - this.setState({ data: undefined, loading: false, errorMessage }); - }); - } + redirectingAuthorizedFetch(endpointUrl) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Invalid status code: ' + response.status); + }) + .then((json) => { + this.setState({ data: json, loading: false }); + }) + .catch((error) => { + const errorMessage = error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem fetching: ' + errorMessage, { + variant: 'error' + }); + this.setState({ data: undefined, loading: false, errorMessage }); + }); + }; saveData = () => { this.setState({ loading: true }); @@ -78,36 +95,47 @@ export function restController>(endpointUrl: headers: { 'Content-Type': 'application/json' } - }).then(response => { - if (response.status === 200) { - return response.json(); - } - throw Error("Invalid status code: " + response.status); - }).then(json => { - this.props.enqueueSnackbar("Update successful.", { variant: 'success' }); - this.setState({ data: json, loading: false }); - }).catch(error => { - const errorMessage = error.message || "Unknown error"; - this.props.enqueueSnackbar("Problem updating: " + errorMessage, { variant: 'error' }); - this.setState({ data: undefined, loading: false, errorMessage }); - }); - } + }) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Invalid status code: ' + response.status); + }) + .then((json) => { + this.props.enqueueSnackbar('Update successful.', { + variant: 'success' + }); + this.setState({ data: json, loading: false }); + }) + .catch((error) => { + const errorMessage = error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem updating: ' + errorMessage, { + variant: 'error' + }); + this.setState({ data: undefined, loading: false, errorMessage }); + }); + }; - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D) => ( + event: React.ChangeEvent + ) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; this.setState({ data }); - } + }; render() { - return ; + return ( + + ); } - - }); + } + ); } diff --git a/interface/src/components/RestFormLoader.tsx b/interface/src/components/RestFormLoader.tsx index 29fb304af..ca65805fd 100644 --- a/interface/src/components/RestFormLoader.tsx +++ b/interface/src/components/RestFormLoader.tsx @@ -8,20 +8,23 @@ import { RestControllerProps } from '.'; const useStyles = makeStyles((theme: Theme) => createStyles({ loadingSettings: { - margin: theme.spacing(0.5), + margin: theme.spacing(0.5) }, loadingSettingsDetails: { margin: theme.spacing(4), - textAlign: "center" + textAlign: 'center' }, button: { marginRight: theme.spacing(2), - marginTop: theme.spacing(2), + marginTop: theme.spacing(2) } }) ); -export type RestFormProps = Omit, "loading" | "errorMessage"> & { data: D }; +export type RestFormProps = Omit< + RestControllerProps, + 'loading' | 'errorMessage' +> & { data: D }; interface RestFormLoaderProps extends RestControllerProps { render: (props: RestFormProps) => JSX.Element; @@ -46,7 +49,12 @@ export default function RestFormLoader(props: RestFormLoaderProps) { {errorMessage} -

diff --git a/interface/src/components/SectionContent.tsx b/interface/src/components/SectionContent.tsx index 457014f69..fdd0ede8a 100644 --- a/interface/src/components/SectionContent.tsx +++ b/interface/src/components/SectionContent.tsx @@ -7,7 +7,7 @@ const useStyles = makeStyles((theme: Theme) => createStyles({ content: { padding: theme.spacing(2), - margin: theme.spacing(3), + margin: theme.spacing(3) } }) ); @@ -15,13 +15,14 @@ const useStyles = makeStyles((theme: Theme) => interface SectionContentProps { title: string; titleGutter?: boolean; + id?: string; } const SectionContent: React.FC = (props) => { - const { children, title, titleGutter } = props; + const { children, title, titleGutter, id } = props; const classes = useStyles(); return ( - + {title} diff --git a/interface/src/components/SingleUpload.tsx b/interface/src/components/SingleUpload.tsx index 003c28653..fe422354a 100644 --- a/interface/src/components/SingleUpload.tsx +++ b/interface/src/components/SingleUpload.tsx @@ -4,13 +4,20 @@ import { useDropzone, DropzoneState } from 'react-dropzone'; import { makeStyles, createStyles } from '@material-ui/styles'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import CancelIcon from '@material-ui/icons/Cancel'; -import { Theme, Box, Typography, LinearProgress, Button } from '@material-ui/core'; +import { + Theme, + Box, + Typography, + LinearProgress, + Button +} from '@material-ui/core'; interface SingleUploadStyleProps extends DropzoneState { uploading: boolean; } -const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total); +const progressPercentage = (progress: ProgressEvent) => + Math.round((progress.loaded * 100) / progress.total); const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => { if (props.isDragAccept) { @@ -23,21 +30,25 @@ const getBorderColor = (theme: Theme, props: SingleUploadStyleProps) => { return theme.palette.info.main; } return theme.palette.grey[700]; -} +}; -const useStyles = makeStyles((theme: Theme) => createStyles({ - dropzone: { - padding: theme.spacing(8, 2), - borderWidth: 2, - borderRadius: 2, - borderStyle: 'dashed', - color: theme.palette.grey[700], - transition: 'border .24s ease-in-out', - cursor: (props: SingleUploadStyleProps) => props.uploading ? 'default' : 'pointer', - width: '100%', - borderColor: (props: SingleUploadStyleProps) => getBorderColor(theme, props) - } -})); +const useStyles = makeStyles((theme: Theme) => + createStyles({ + dropzone: { + padding: theme.spacing(8, 2), + borderWidth: 2, + borderRadius: 2, + borderStyle: 'dashed', + color: theme.palette.grey[700], + transition: 'border .24s ease-in-out', + cursor: (props: SingleUploadStyleProps) => + props.uploading ? 'default' : 'pointer', + width: '100%', + borderColor: (props: SingleUploadStyleProps) => + getBorderColor(theme, props) + } + }) +); export interface SingleUploadProps { onDrop: (acceptedFiles: File[]) => void; @@ -47,26 +58,44 @@ export interface SingleUploadProps { progress?: ProgressEvent; } -const SingleUpload: FC = ({ onDrop, onCancel, accept, uploading, progress }) => { - const dropzoneState = useDropzone({ onDrop, accept, disabled: uploading, multiple: false }); +const SingleUpload: FC = ({ + onDrop, + onCancel, + accept, + uploading, + progress +}) => { + const dropzoneState = useDropzone({ + onDrop, + accept, + disabled: uploading, + multiple: false + }); const { getRootProps, getInputProps } = dropzoneState; const classes = useStyles({ ...dropzoneState, uploading }); - const renderProgressText = () => { if (uploading) { if (progress?.lengthComputable) { return `Uploading: ${progressPercentage(progress)}%`; } - return "Uploading\u2026"; + return 'Uploading\u2026'; } - return "Drop file or click here"; - } + return 'Drop file or click here'; + }; const renderProgress = (progress?: ProgressEvent) => ( ); @@ -74,16 +103,19 @@ const SingleUpload: FC = ({ onDrop, onCancel, accept, uploadi
- - - {renderProgressText()} - + + {renderProgressText()} {uploading && ( {renderProgress(progress)} - @@ -91,6 +123,6 @@ const SingleUpload: FC = ({ onDrop, onCancel, accept, uploadi
); -} +}; export default SingleUpload; diff --git a/interface/src/components/WebSocketController.tsx b/interface/src/components/WebSocketController.tsx index 5fe9fa33c..fda9bf591 100644 --- a/interface/src/components/WebSocketController.tsx +++ b/interface/src/components/WebSocketController.tsx @@ -7,7 +7,9 @@ import { addAccessTokenParameter } from '../authentication'; import { extractEventValue } from '.'; export interface WebSocketControllerProps extends WithSnackbarProps { - handleValueChange: (name: keyof D) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof D + ) => (event: React.ChangeEvent) => void; setData: (data: D, callback?: () => void) => void; saveData: () => void; @@ -25,8 +27,8 @@ interface WebSocketControllerState { } enum WebSocketMessageType { - ID = "id", - PAYLOAD = "payload" + ID = 'id', + PAYLOAD = 'payload' } interface WebSocketIdMessage { @@ -40,21 +42,32 @@ interface WebSocketPayloadMessage { payload: D; } -export type WebSocketMessage = WebSocketIdMessage | WebSocketPayloadMessage; +export type WebSocketMessage = + | WebSocketIdMessage + | WebSocketPayloadMessage; -export function webSocketController>(wsUrl: string, wsThrottle: number, WebSocketController: React.ComponentType

>) { +export function webSocketController>( + wsUrl: string, + wsThrottle: number, + WebSocketController: React.ComponentType

> +) { return withSnackbar( - class extends React.Component> & WithSnackbarProps, WebSocketControllerState> { - constructor(props: Omit> & WithSnackbarProps) { + class extends React.Component< + Omit> & WithSnackbarProps, + WebSocketControllerState + > { + constructor( + props: Omit> & WithSnackbarProps + ) { super(props); this.state = { ws: new Sockette(addAccessTokenParameter(wsUrl), { onmessage: this.onMessage, onopen: this.onOpen, - onclose: this.onClose, + onclose: this.onClose }), connected: false - } + }; } componentWillUnmount() { @@ -64,37 +77,42 @@ export function webSocketController>(ws onMessage = (event: MessageEvent) => { const rawData = event.data; if (typeof rawData === 'string' || rawData instanceof String) { - this.handleMessage(JSON.parse(rawData as string) as WebSocketMessage); + this.handleMessage( + JSON.parse(rawData as string) as WebSocketMessage + ); } - } + }; handleMessage = (message: WebSocketMessage) => { + const { clientId, data } = this.state; + switch (message.type) { case WebSocketMessageType.ID: this.setState({ clientId: message.id }); break; case WebSocketMessageType.PAYLOAD: - const { clientId, data } = this.state; if (clientId && (!data || clientId !== message.origin_id)) { - this.setState( - { data: message.payload } - ); + this.setState({ data: message.payload }); } break; } - } + }; onOpen = () => { this.setState({ connected: true }); - } + }; onClose = () => { - this.setState({ connected: false, clientId: undefined, data: undefined }); - } + this.setState({ + connected: false, + clientId: undefined, + data: undefined + }); + }; setData = (data: D, callback?: () => void) => { this.setState({ data }, callback); - } + }; saveData = throttle(() => { const { ws, connected, data } = this.state; @@ -106,28 +124,35 @@ export function webSocketController>(ws saveDataAndClear = throttle(() => { const { ws, connected, data } = this.state; if (connected) { - this.setState({ - data: undefined - }, () => ws.json(data)); + this.setState( + { + data: undefined + }, + () => ws.json(data) + ); } }, wsThrottle); - handleValueChange = (name: keyof D) => (event: React.ChangeEvent) => { + handleValueChange = (name: keyof D) => ( + event: React.ChangeEvent + ) => { const data = { ...this.state.data!, [name]: extractEventValue(event) }; this.setState({ data }); - } + }; render() { - return ; + return ( + + ); } - - }); + } + ); } diff --git a/interface/src/components/WebSocketFormLoader.tsx b/interface/src/components/WebSocketFormLoader.tsx index ee5f335a0..eb4c2e582 100644 --- a/interface/src/components/WebSocketFormLoader.tsx +++ b/interface/src/components/WebSocketFormLoader.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; import { LinearProgress, Typography } from '@material-ui/core'; @@ -8,22 +6,27 @@ import { WebSocketControllerProps } from '.'; const useStyles = makeStyles((theme: Theme) => createStyles({ loadingSettings: { - margin: theme.spacing(0.5), + margin: theme.spacing(0.5) }, loadingSettingsDetails: { margin: theme.spacing(4), - textAlign: "center" + textAlign: 'center' } }) ); -export type WebSocketFormProps = Omit, "connected"> & { data: D }; +export type WebSocketFormProps = Omit< + WebSocketControllerProps, + 'connected' +> & { data: D }; interface WebSocketFormLoaderProps extends WebSocketControllerProps { render: (props: WebSocketFormProps) => JSX.Element; } -export default function WebSocketFormLoader(props: WebSocketFormLoaderProps) { +export default function WebSocketFormLoader( + props: WebSocketFormLoaderProps +) { const { connected, render, data, ...rest } = props; const classes = useStyles(); if (!connected || !data) { diff --git a/interface/src/components/WindowSize.tsx b/interface/src/components/WindowSize.tsx new file mode 100644 index 000000000..d69405ae8 --- /dev/null +++ b/interface/src/components/WindowSize.tsx @@ -0,0 +1,14 @@ +import { useLayoutEffect, useState } from 'react'; + +export function useWindowSize() { + const [size, setSize] = useState([0, 0]); + useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener('resize', updateSize); + updateSize(); + return () => window.removeEventListener('resize', updateSize); + }, []); + return size; +} diff --git a/interface/src/components/index.ts b/interface/src/components/index.ts index 4a6cc6a7b..47348a94d 100644 --- a/interface/src/components/index.ts +++ b/interface/src/components/index.ts @@ -15,3 +15,5 @@ export * from './RestController'; export * from './WebSocketFormLoader'; export * from './WebSocketController'; + +export * from './WindowSize'; diff --git a/interface/src/features/FeaturesContext.tsx b/interface/src/features/FeaturesContext.tsx index 7986072c6..15296e085 100644 --- a/interface/src/features/FeaturesContext.tsx +++ b/interface/src/features/FeaturesContext.tsx @@ -5,21 +5,26 @@ export interface FeaturesContextValue { features: Features; } -const FeaturesContextDefaultValue = {} as FeaturesContextValue -export const FeaturesContext = React.createContext( - FeaturesContextDefaultValue -); +const FeaturesContextDefaultValue = {} as FeaturesContextValue; +export const FeaturesContext = React.createContext(FeaturesContextDefaultValue); export interface WithFeaturesProps { features: Features; } -export function withFeatures(Component: React.ComponentType) { +export function withFeatures( + Component: React.ComponentType +) { return class extends React.Component> { render() { return ( - {featuresContext => } + {(featuresContext) => ( + + )} ); } diff --git a/interface/src/features/FeaturesWrapper.tsx b/interface/src/features/FeaturesWrapper.tsx index aac353328..4742a4bcc 100644 --- a/interface/src/features/FeaturesWrapper.tsx +++ b/interface/src/features/FeaturesWrapper.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { Features } from './types'; import { FeaturesContext } from './FeaturesContext'; @@ -9,10 +9,9 @@ import { FEATURES_ENDPOINT } from '../api'; interface FeaturesWrapperState { features?: Features; error?: string; -}; +} class FeaturesWrapper extends Component<{}, FeaturesWrapperState> { - state: FeaturesWrapperState = {}; componentDidMount() { @@ -21,41 +20,39 @@ class FeaturesWrapper extends Component<{}, FeaturesWrapperState> { fetchFeaturesDetails = () => { fetch(FEATURES_ENDPOINT) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } else { - throw Error("Unexpected status code: " + response.status); + throw Error('Unexpected status code: ' + response.status); } - }).then(features => { + }) + .then((features) => { this.setState({ features }); }) - .catch(error => { + .catch((error) => { this.setState({ error: error.message }); }); - } + }; render() { const { features, error } = this.state; if (features) { return ( - + {this.props.children} ); } if (error) { - return ( - - ); + return ; } - return ( - - ); + return ; } - } export default FeaturesWrapper; diff --git a/interface/src/history.ts b/interface/src/history.ts index eb70d7bea..2d6284a38 100644 --- a/interface/src/history.ts +++ b/interface/src/history.ts @@ -2,4 +2,4 @@ import { createBrowserHistory } from 'history'; export default createBrowserHistory({ /* pass a configuration object here if needed */ -}) +}); diff --git a/interface/src/index.tsx b/interface/src/index.tsx index a0801fcd1..2abd63e9f 100644 --- a/interface/src/index.tsx +++ b/interface/src/index.tsx @@ -6,8 +6,9 @@ import { Router } from 'react-router'; import App from './App'; -render(( +render( - - -), document.getElementById("root")) + + , + document.getElementById('root') +); diff --git a/interface/src/mqtt/Mqtt.tsx b/interface/src/mqtt/Mqtt.tsx index 8daca772a..6db8dd5e0 100644 --- a/interface/src/mqtt/Mqtt.tsx +++ b/interface/src/mqtt/Mqtt.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, withAuthenticatedContext, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + withAuthenticatedContext, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import MqttStatusController from './MqttStatusController'; import MqttSettingsController from './MqttSettingsController'; @@ -11,8 +15,7 @@ import MqttSettingsController from './MqttSettingsController'; type MqttProps = AuthenticatedContextProps & RouteComponentProps; class Mqtt extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -20,17 +23,33 @@ class Mqtt extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } } diff --git a/interface/src/mqtt/MqttSettingsController.tsx b/interface/src/mqtt/MqttSettingsController.tsx index 8cc9d160d..eeea08d7c 100644 --- a/interface/src/mqtt/MqttSettingsController.tsx +++ b/interface/src/mqtt/MqttSettingsController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { MQTT_SETTINGS_ENDPOINT } from '../api'; import MqttSettingsForm from './MqttSettingsForm'; @@ -9,7 +14,6 @@ import { MqttSettings } from './types'; type MqttSettingsControllerProps = RestControllerProps; class MqttSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class MqttSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(MQTT_SETTINGS_ENDPOINT, MqttSettingsController); diff --git a/interface/src/mqtt/MqttSettingsForm.tsx b/interface/src/mqtt/MqttSettingsForm.tsx index 492183abf..d4ecc3b0f 100644 --- a/interface/src/mqtt/MqttSettingsForm.tsx +++ b/interface/src/mqtt/MqttSettingsForm.tsx @@ -1,31 +1,31 @@ -import React from "react"; +import React from 'react'; import { TextValidator, ValidatorForm, - SelectValidator, -} from "react-material-ui-form-validator"; + SelectValidator +} from 'react-material-ui-form-validator'; -import { Checkbox, TextField, Typography } from "@material-ui/core"; -import SaveIcon from "@material-ui/icons/Save"; -import MenuItem from "@material-ui/core/MenuItem"; +import { Checkbox, TextField, Typography } from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import MenuItem from '@material-ui/core/MenuItem'; import { RestFormProps, FormActions, FormButton, BlockFormControlLabel, - PasswordValidator, -} from "../components"; -import { isIP, isHostname, or, isPath } from "../validators"; + PasswordValidator +} from '../components'; +import { isIP, isHostname, or, isPath } from '../validators'; -import { MqttSettings } from "./types"; +import { MqttSettings } from './types'; type MqttSettingsFormProps = RestFormProps; class MqttSettingsForm extends React.Component { componentDidMount() { - ValidatorForm.addValidationRule("isIPOrHostname", or(isIP, isHostname)); - ValidatorForm.addValidationRule("isPath", isPath); + ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); + ValidatorForm.addValidationRule('isPath', isPath); } render() { @@ -36,38 +36,38 @@ class MqttSettingsForm extends React.Component { control={ } label="Enable MQTT" /> { variant="outlined" value={data.port} type="number" - onChange={handleValueChange("port")} + onChange={handleValueChange('port')} margin="normal" /> { fullWidth variant="outlined" value={data.username} - onChange={handleValueChange("username")} + onChange={handleValueChange('username')} margin="normal" /> { fullWidth variant="outlined" value={data.password} - onChange={handleValueChange("password")} + onChange={handleValueChange('password')} margin="normal" /> { fullWidth variant="outlined" value={data.client_id} - onChange={handleValueChange("client_id")} + onChange={handleValueChange('client_id')} margin="normal" /> { variant="outlined" value={data.keep_alive} type="number" - onChange={handleValueChange("keep_alive")} + onChange={handleValueChange('keep_alive')} margin="normal" /> { value={data.mqtt_qos} fullWidth variant="outlined" - onChange={handleValueChange("mqtt_qos")} + onChange={handleValueChange('mqtt_qos')} margin="normal" > 0 (default) @@ -155,7 +155,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -165,7 +165,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -181,7 +181,7 @@ class MqttSettingsForm extends React.Component { value={data.nested_format} fullWidth variant="outlined" - onChange={handleValueChange("nested_format")} + onChange={handleValueChange('nested_format')} margin="normal" > nested on a single topic @@ -193,7 +193,7 @@ class MqttSettingsForm extends React.Component { value={data.dallas_format} fullWidth variant="outlined" - onChange={handleValueChange("dallas_format")} + onChange={handleValueChange('dallas_format')} margin="normal" > by Sensor ID @@ -205,7 +205,7 @@ class MqttSettingsForm extends React.Component { value={data.bool_format} fullWidth variant="outlined" - onChange={handleValueChange("bool_format")} + onChange={handleValueChange('bool_format')} margin="normal" > "on"/"off" @@ -219,7 +219,7 @@ class MqttSettingsForm extends React.Component { value={data.subscribe_format} fullWidth variant="outlined" - onChange={handleValueChange("subscribe_format")} + onChange={handleValueChange('subscribe_format')} margin="normal" > general device topic @@ -230,7 +230,7 @@ class MqttSettingsForm extends React.Component { control={ } @@ -243,7 +243,7 @@ class MqttSettingsForm extends React.Component { value={data.ha_climate_format} fullWidth variant="outlined" - onChange={handleValueChange("ha_climate_format")} + onChange={handleValueChange('ha_climate_format')} margin="normal" > use Current temperature (default) @@ -257,16 +257,16 @@ class MqttSettingsForm extends React.Component { { variant="outlined" value={data.publish_time_boiler} type="number" - onChange={handleValueChange("publish_time_boiler")} + onChange={handleValueChange('publish_time_boiler')} margin="normal" /> { variant="outlined" value={data.publish_time_thermostat} type="number" - onChange={handleValueChange("publish_time_thermostat")} + onChange={handleValueChange('publish_time_thermostat')} margin="normal" /> { variant="outlined" value={data.publish_time_solar} type="number" - onChange={handleValueChange("publish_time_solar")} + onChange={handleValueChange('publish_time_solar')} margin="normal" /> { variant="outlined" value={data.publish_time_mixer} type="number" - onChange={handleValueChange("publish_time_mixer")} + onChange={handleValueChange('publish_time_mixer')} margin="normal" /> { variant="outlined" value={data.publish_time_sensor} type="number" - onChange={handleValueChange("publish_time_sensor")} + onChange={handleValueChange('publish_time_sensor')} margin="normal" /> { variant="outlined" value={data.publish_time_other} type="number" - onChange={handleValueChange("publish_time_other")} + onChange={handleValueChange('publish_time_other')} margin="normal" /> diff --git a/interface/src/mqtt/MqttStatus.ts b/interface/src/mqtt/MqttStatus.ts index 4263aafb9..c898fddbd 100644 --- a/interface/src/mqtt/MqttStatus.ts +++ b/interface/src/mqtt/MqttStatus.ts @@ -1,7 +1,10 @@ -import { Theme } from "@material-ui/core"; -import { MqttStatus, MqttDisconnectReason } from "./types"; +import { Theme } from '@material-ui/core'; +import { MqttStatus, MqttDisconnectReason } from './types'; -export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: Theme) => { +export const mqttStatusHighlight = ( + { enabled, connected }: MqttStatus, + theme: Theme +) => { if (!enabled) { return theme.palette.info.main; } @@ -9,48 +12,48 @@ export const mqttStatusHighlight = ({ enabled, connected }: MqttStatus, theme: T return theme.palette.success.main; } return theme.palette.error.main; -} +}; export const mqttStatus = ({ enabled, connected }: MqttStatus) => { if (!enabled) { - return "Not enabled"; + return 'Not enabled'; } if (connected) { - return "Connected"; + return 'Connected'; } - return "Disconnected"; -} + return 'Disconnected'; +}; export const disconnectReason = ({ disconnect_reason }: MqttStatus) => { switch (disconnect_reason) { case MqttDisconnectReason.TCP_DISCONNECTED: - return "TCP disconnected"; + return 'TCP disconnected'; case MqttDisconnectReason.MQTT_UNACCEPTABLE_PROTOCOL_VERSION: - return "Unacceptable protocol version"; + return 'Unacceptable protocol version'; case MqttDisconnectReason.MQTT_IDENTIFIER_REJECTED: - return "Client ID rejected"; + return 'Client ID rejected'; case MqttDisconnectReason.MQTT_SERVER_UNAVAILABLE: - return "Server unavailable"; + return 'Server unavailable'; case MqttDisconnectReason.MQTT_MALFORMED_CREDENTIALS: - return "Malformed credentials"; + return 'Malformed credentials'; case MqttDisconnectReason.MQTT_NOT_AUTHORIZED: - return "Not authorized"; + return 'Not authorized'; case MqttDisconnectReason.ESP8266_NOT_ENOUGH_SPACE: - return "Device out of memory"; + return 'Device out of memory'; case MqttDisconnectReason.TLS_BAD_FINGERPRINT: - return "Server fingerprint invalid"; + return 'Server fingerprint invalid'; default: - return "Unknown" + return 'Unknown'; } -} +}; -export const mqttPublishHighlight = ({ mqtt_fails }: MqttStatus, theme: Theme) => { +export const mqttPublishHighlight = ( + { mqtt_fails }: MqttStatus, + theme: Theme +) => { + if (mqtt_fails === 0) return theme.palette.success.main; - if (mqtt_fails === 0) - return theme.palette.success.main; - - if (mqtt_fails < 10) - return theme.palette.warning.main; + if (mqtt_fails < 10) return theme.palette.warning.main; return theme.palette.error.main; -} \ No newline at end of file +}; diff --git a/interface/src/mqtt/MqttStatusController.tsx b/interface/src/mqtt/MqttStatusController.tsx index 4dd540976..80eb646c3 100644 --- a/interface/src/mqtt/MqttStatusController.tsx +++ b/interface/src/mqtt/MqttStatusController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { MQTT_STATUS_ENDPOINT } from '../api'; import MqttStatusForm from './MqttStatusForm'; @@ -9,7 +14,6 @@ import { MqttStatus } from './types'; type MqttStatusControllerProps = RestControllerProps; class MqttStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,10 +23,10 @@ class MqttStatusController extends Component { } + render={(formProps) => } /> - ) + ); } } diff --git a/interface/src/mqtt/MqttStatusForm.tsx b/interface/src/mqtt/MqttStatusForm.tsx index e17a63999..5d3eefd2e 100644 --- a/interface/src/mqtt/MqttStatusForm.tsx +++ b/interface/src/mqtt/MqttStatusForm.tsx @@ -1,23 +1,39 @@ -import React, { Component, Fragment } from 'react'; +import { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText +} from '@material-ui/core'; import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import RefreshIcon from '@material-ui/icons/Refresh'; import ReportIcon from '@material-ui/icons/Report'; -import SpeakerNotesOffIcon from "@material-ui/icons/SpeakerNotesOff"; +import SpeakerNotesOffIcon from '@material-ui/icons/SpeakerNotesOff'; -import { RestFormProps, FormActions, FormButton, HighlightAvatar } from '../components'; -import { mqttStatusHighlight, mqttStatus, mqttPublishHighlight, disconnectReason } from './MqttStatus'; +import { + RestFormProps, + FormActions, + FormButton, + HighlightAvatar +} from '../components'; +import { + mqttStatusHighlight, + mqttStatus, + mqttPublishHighlight, + disconnectReason +} from './MqttStatus'; import { MqttStatus } from './types'; type MqttStatusFormProps = RestFormProps & WithTheme; class MqttStatusForm extends Component { - renderConnectionStatus() { - const { data, theme } = this.props + const { data, theme } = this.props; if (data.connected) { return ( @@ -29,16 +45,16 @@ class MqttStatusForm extends Component { - - - - - - - + + + + + + + ); } @@ -50,7 +66,10 @@ class MqttStatusForm extends Component { - + @@ -58,7 +77,7 @@ class MqttStatusForm extends Component { } createListItems() { - const { data, theme } = this.props + const { data, theme } = this.props; return ( @@ -78,18 +97,20 @@ class MqttStatusForm extends Component { render() { return ( - - {this.createListItems()} - + {this.createListItems()} - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh ); } - } export default withTheme(MqttStatusForm); diff --git a/interface/src/network/NetworkConnection.tsx b/interface/src/network/NetworkConnection.tsx index 86c289249..e8433e2af 100644 --- a/interface/src/network/NetworkConnection.tsx +++ b/interface/src/network/NetworkConnection.tsx @@ -1,22 +1,31 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import NetworkStatusController from './NetworkStatusController'; import NetworkSettingsController from './NetworkSettingsController'; import WiFiNetworkScanner from './WiFiNetworkScanner'; -import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext'; +import { + NetworkConnectionContext, + NetworkConnectionContextValue +} from './NetworkConnectionContext'; import { WiFiNetwork } from './types'; type NetworkConnectionProps = AuthenticatedContextProps & RouteComponentProps; -class NetworkConnection extends Component { - +class NetworkConnection extends Component< + NetworkConnectionProps, + NetworkConnectionContextValue +> { constructor(props: NetworkConnectionProps) { super(props); this.state = { @@ -28,13 +37,13 @@ class NetworkConnection extends Component { this.setState({ selectedNetwork: network }); this.props.history.push('/network/settings'); - } + }; deselectNetwork = () => { this.setState({ selectedNetwork: undefined }); - } + }; - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -43,20 +52,44 @@ class NetworkConnection extends Component - + this.handleTabChange(path)} + variant="fullWidth" + > - - + + - - - + + + - ) + ); } } diff --git a/interface/src/network/NetworkConnectionContext.tsx b/interface/src/network/NetworkConnectionContext.tsx index e2077ab30..600331199 100644 --- a/interface/src/network/NetworkConnectionContext.tsx +++ b/interface/src/network/NetworkConnectionContext.tsx @@ -7,7 +7,7 @@ export interface NetworkConnectionContextValue { deselectNetwork: () => void; } -const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContextValue +const NetworkConnectionContextDefaultValue = {} as NetworkConnectionContextValue; export const NetworkConnectionContext = React.createContext( NetworkConnectionContextDefaultValue ); diff --git a/interface/src/network/NetworkSettingsController.tsx b/interface/src/network/NetworkSettingsController.tsx index 109e18d8a..5a96bc25b 100644 --- a/interface/src/network/NetworkSettingsController.tsx +++ b/interface/src/network/NetworkSettingsController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import NetworkSettingsForm from './NetworkSettingsForm'; import { NETWORK_SETTINGS_ENDPOINT } from '../api'; import { NetworkSettings } from './types'; @@ -8,7 +13,6 @@ import { NetworkSettings } from './types'; type NetworkSettingsControllerProps = RestControllerProps; class NetworkSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -18,12 +22,14 @@ class NetworkSettingsController extends Component } + render={(formProps) => } /> ); } - } -export default restController(NETWORK_SETTINGS_ENDPOINT, NetworkSettingsController); \ No newline at end of file +export default restController( + NETWORK_SETTINGS_ENDPOINT, + NetworkSettingsController +); diff --git a/interface/src/network/NetworkSettingsForm.tsx b/interface/src/network/NetworkSettingsForm.tsx index 1347d1d0e..5928688a5 100644 --- a/interface/src/network/NetworkSettingsForm.tsx +++ b/interface/src/network/NetworkSettingsForm.tsx @@ -1,7 +1,14 @@ import React, { Fragment } from 'react'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Checkbox, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction } from '@material-ui/core'; +import { + Checkbox, + List, + ListItem, + ListItemText, + ListItemAvatar, + ListItemSecondaryAction +} from '@material-ui/core'; import Avatar from '@material-ui/core/Avatar'; import IconButton from '@material-ui/core/IconButton'; @@ -10,31 +17,42 @@ import LockOpenIcon from '@material-ui/icons/LockOpen'; import DeleteIcon from '@material-ui/icons/Delete'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, PasswordValidator, BlockFormControlLabel, FormActions, FormButton } from '../components'; +import { + RestFormProps, + PasswordValidator, + BlockFormControlLabel, + FormActions, + FormButton +} from '../components'; import { isIP, isHostname, optional } from '../validators'; -import { NetworkConnectionContext, NetworkConnectionContextValue } from './NetworkConnectionContext'; +import { + NetworkConnectionContext, + NetworkConnectionContextValue +} from './NetworkConnectionContext'; import { isNetworkOpen, networkSecurityMode } from './WiFiSecurityModes'; import { NetworkSettings } from './types'; type NetworkStatusFormProps = RestFormProps; class NetworkSettingsForm extends React.Component { - static contextType = NetworkConnectionContext; context!: React.ContextType; - constructor(props: NetworkStatusFormProps, context: NetworkConnectionContextValue) { + constructor( + props: NetworkStatusFormProps, + context: NetworkConnectionContextValue + ) { super(props); const { selectedNetwork } = context; if (selectedNetwork) { const networkSettings: NetworkSettings = { ssid: selectedNetwork.ssid, - password: "", + password: '', hostname: props.data.hostname, - static_ip_config: false, - } + static_ip_config: false + }; props.setData(networkSettings); } } @@ -48,7 +66,7 @@ class NetworkSettingsForm extends React.Component { deselectNetworkAndLoadData = () => { this.context.deselectNetwork(); this.props.loadData(); - } + }; componentWillUnmount() { this.context.deselectNetwork(); @@ -59,41 +77,51 @@ class NetworkSettingsForm extends React.Component { const { data, handleValueChange, saveData } = this.props; return ( - { - selectedNetwork ? - - - - - {isNetworkOpen(selectedNetwork) ? : } - - - - - - - - - - - : - - } - { - (!selectedNetwork || !isNetworkOpen(selectedNetwork)) && + {selectedNetwork ? ( + + + + + {isNetworkOpen(selectedNetwork) ? ( + + ) : ( + + )} + + + + + + + + + + + ) : ( + + )} + {(!selectedNetwork || !isNetworkOpen(selectedNetwork)) && ( { onChange={handleValueChange('password')} margin="normal" /> - } + )} { } label="Static IP Config" /> - { - data.static_ip_config && + {data.static_ip_config && ( { /> { margin="normal" /> - } + )} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save @@ -197,4 +232,4 @@ class NetworkSettingsForm extends React.Component { } } -export default NetworkSettingsForm; \ No newline at end of file +export default NetworkSettingsForm; diff --git a/interface/src/network/NetworkStatus.ts b/interface/src/network/NetworkStatus.ts index f555bc574..c75b14531 100644 --- a/interface/src/network/NetworkStatus.ts +++ b/interface/src/network/NetworkStatus.ts @@ -1,5 +1,5 @@ -import { Theme } from "@material-ui/core"; -import { NetworkStatus, NetworkConnectionStatus } from "./types"; +import { Theme } from '@material-ui/core'; +import { NetworkStatus, NetworkConnectionStatus } from './types'; export const isConnected = ({ status }: NetworkStatus) => { return ( @@ -36,22 +36,22 @@ export const networkStatusHighlight = ( export const networkStatus = ({ status }: NetworkStatus) => { switch (status) { case NetworkConnectionStatus.WIFI_STATUS_NO_SHIELD: - return "Inactive"; + return 'Inactive'; case NetworkConnectionStatus.WIFI_STATUS_IDLE: - return "Idle"; + return 'Idle'; case NetworkConnectionStatus.WIFI_STATUS_NO_SSID_AVAIL: - return "No SSID Available"; + return 'No SSID Available'; case NetworkConnectionStatus.WIFI_STATUS_CONNECTED: - return "Connected (WiFi)"; + return 'Connected (WiFi)'; case NetworkConnectionStatus.ETHERNET_STATUS_CONNECTED: - return "Connected (Ethernet)"; + return 'Connected (Ethernet)'; case NetworkConnectionStatus.WIFI_STATUS_CONNECT_FAILED: - return "Connection Failed"; + return 'Connection Failed'; case NetworkConnectionStatus.WIFI_STATUS_CONNECTION_LOST: - return "Connection Lost"; + return 'Connection Lost'; case NetworkConnectionStatus.WIFI_STATUS_DISCONNECTED: - return "Disconnected"; + return 'Disconnected'; default: - return "Unknown"; + return 'Unknown'; } }; diff --git a/interface/src/network/NetworkStatusController.tsx b/interface/src/network/NetworkStatusController.tsx index 46b286a64..0c813fcff 100644 --- a/interface/src/network/NetworkStatusController.tsx +++ b/interface/src/network/NetworkStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import NetworkStatusForm from './NetworkStatusForm'; import { NETWORK_STATUS_ENDPOINT } from '../api'; import { NetworkStatus } from './types'; @@ -8,7 +13,6 @@ import { NetworkStatus } from './types'; type NetworkStatusControllerProps = RestControllerProps; class NetworkStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -18,12 +22,11 @@ class NetworkStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(NETWORK_STATUS_ENDPOINT, NetworkStatusController); diff --git a/interface/src/network/NetworkStatusForm.tsx b/interface/src/network/NetworkStatusForm.tsx index 889ddf3cb..ed4c3ca8e 100644 --- a/interface/src/network/NetworkStatusForm.tsx +++ b/interface/src/network/NetworkStatusForm.tsx @@ -1,46 +1,46 @@ -import React, { Component, Fragment } from "react"; +import { Component, Fragment } from 'react'; -import { WithTheme, withTheme } from "@material-ui/core/styles"; +import { WithTheme, withTheme } from '@material-ui/core/styles'; import { Avatar, Divider, List, ListItem, ListItemAvatar, - ListItemText, -} from "@material-ui/core"; + ListItemText +} from '@material-ui/core'; -import DNSIcon from "@material-ui/icons/Dns"; -import WifiIcon from "@material-ui/icons/Wifi"; -import RouterIcon from "@material-ui/icons/Router"; -import SettingsInputComponentIcon from "@material-ui/icons/SettingsInputComponent"; -import SettingsInputAntennaIcon from "@material-ui/icons/SettingsInputAntenna"; -import DeviceHubIcon from "@material-ui/icons/DeviceHub"; -import RefreshIcon from "@material-ui/icons/Refresh"; +import DNSIcon from '@material-ui/icons/Dns'; +import WifiIcon from '@material-ui/icons/Wifi'; +import RouterIcon from '@material-ui/icons/Router'; +import SettingsInputComponentIcon from '@material-ui/icons/SettingsInputComponent'; +import SettingsInputAntennaIcon from '@material-ui/icons/SettingsInputAntenna'; +import DeviceHubIcon from '@material-ui/icons/DeviceHub'; +import RefreshIcon from '@material-ui/icons/Refresh'; import { RestFormProps, FormActions, FormButton, - HighlightAvatar, -} from "../components"; + HighlightAvatar +} from '../components'; import { networkStatus, networkStatusHighlight, isConnected, isWiFi, - isEthernet, -} from "./NetworkStatus"; -import { NetworkStatus } from "./types"; + isEthernet +} from './NetworkStatus'; +import { NetworkStatus } from './types'; type NetworkStatusFormProps = RestFormProps & WithTheme; class NetworkStatusForm extends Component { dnsServers(status: NetworkStatus) { if (!status.dns_ip_1) { - return "none"; + return 'none'; } - return status.dns_ip_1 + (status.dns_ip_2 ? "," + status.dns_ip_2 : ""); + return status.dns_ip_1 + (status.dns_ip_2 ? ',' + status.dns_ip_2 : ''); } createListItems() { @@ -110,7 +110,7 @@ class NetworkStatusForm extends Component { diff --git a/interface/src/network/WiFiNetworkScanner.tsx b/interface/src/network/WiFiNetworkScanner.tsx index 744f5154f..ecd470701 100644 --- a/interface/src/network/WiFiNetworkScanner.tsx +++ b/interface/src/network/WiFiNetworkScanner.tsx @@ -1,7 +1,14 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { withSnackbar, WithSnackbarProps } from 'notistack'; -import { createStyles, WithStyles, Theme, withStyles, Typography, LinearProgress } from '@material-ui/core'; +import { + createStyles, + WithStyles, + Theme, + withStyles, + Typography, + LinearProgress +} from '@material-ui/core'; import PermScanWifiIcon from '@material-ui/icons/PermScanWifi'; import { FormActions, FormButton, SectionContent } from '../components'; @@ -11,9 +18,9 @@ import { SCAN_NETWORKS_ENDPOINT, LIST_NETWORKS_ENDPOINT } from '../api'; import WiFiNetworkSelector from './WiFiNetworkSelector'; import { WiFiNetworkList, WiFiNetwork } from './types'; -const NUM_POLLS = 10 -const POLLING_FREQUENCY = 500 -const RETRY_EXCEPTION_TYPE = "retry" +const NUM_POLLS = 10; +const POLLING_FREQUENCY = 500; +const RETRY_EXCEPTION_TYPE = 'retry'; interface WiFiNetworkScannerState { scanningForNetworks: boolean; @@ -21,28 +28,31 @@ interface WiFiNetworkScannerState { networkList?: WiFiNetworkList; } -const styles = (theme: Theme) => createStyles({ - scanningSettings: { - margin: theme.spacing(0.5), - }, - scanningSettingsDetails: { - margin: theme.spacing(4), - textAlign: "center" - }, - scanningProgress: { - margin: theme.spacing(4), - textAlign: "center" - } -}); +const styles = (theme: Theme) => + createStyles({ + scanningSettings: { + margin: theme.spacing(0.5) + }, + scanningSettingsDetails: { + margin: theme.spacing(4), + textAlign: 'center' + }, + scanningProgress: { + margin: theme.spacing(4), + textAlign: 'center' + } + }); type WiFiNetworkScannerProps = WithSnackbarProps & WithStyles; -class WiFiNetworkScanner extends Component { - - pollCount: number = 0; +class WiFiNetworkScanner extends Component< + WiFiNetworkScannerProps, + WiFiNetworkScannerState +> { + pollCount = 0; state: WiFiNetworkScannerState = { - scanningForNetworks: false, + scanningForNetworks: false }; componentDidMount() { @@ -54,23 +64,36 @@ class WiFiNetworkScanner extends Component { - if (response.status === 202) { - this.schedulePollTimeout(); - return; - } - throw Error("Scanning for networks returned unexpected response code: " + response.status); - }).catch(error => { - this.props.enqueueSnackbar("Problem scanning: " + error.message, { - variant: 'error', - }); - this.setState({ scanningForNetworks: false, networkList: undefined, errorMessage: error.message }); + this.setState({ + scanningForNetworks: true, + networkList: undefined, + errorMessage: undefined }); + redirectingAuthorizedFetch(SCAN_NETWORKS_ENDPOINT) + .then((response) => { + if (response.status === 202) { + this.schedulePollTimeout(); + return; + } + throw Error( + 'Scanning for networks returned unexpected response code: ' + + response.status + ); + }) + .catch((error) => { + this.props.enqueueSnackbar('Problem scanning: ' + error.message, { + variant: 'error' + }); + this.setState({ + scanningForNetworks: false, + networkList: undefined, + errorMessage: error.message + }); + }); } schedulePollTimeout() { @@ -80,21 +103,20 @@ class WiFiNetworkScanner extends Component network2.rssi) - return -1; + if (network1.rssi < network2.rssi) return 1; + if (network1.rssi > network2.rssi) return -1; return 0; } pollNetworkList = () => { redirectingAuthorizedFetch(LIST_NETWORKS_ENDPOINT) - .then(response => { + .then((response) => { if (response.status === 200) { return response.json(); } @@ -103,24 +125,34 @@ class WiFiNetworkScanner extends Component { - json.networks.sort(this.compareNetworks) - this.setState({ scanningForNetworks: false, networkList: json, errorMessage: undefined }) + .then((json) => { + json.networks.sort(this.compareNetworks); + this.setState({ + scanningForNetworks: false, + networkList: json, + errorMessage: undefined + }); }) - .catch(error => { + .catch((error) => { if (error.name !== RETRY_EXCEPTION_TYPE) { - this.props.enqueueSnackbar("Problem scanning: " + error.message, { - variant: 'error', + this.props.enqueueSnackbar('Problem scanning: ' + error.message, { + variant: 'error' + }); + this.setState({ + scanningForNetworks: false, + networkList: undefined, + errorMessage: error.message }); - this.setState({ scanningForNetworks: false, networkList: undefined, errorMessage: error.message }); } }); - } + }; renderNetworkScanner() { const { classes } = this.props; @@ -144,9 +176,7 @@ class WiFiNetworkScanner extends Component ); } - return ( - - ); + return ; } render() { @@ -155,14 +185,19 @@ class WiFiNetworkScanner extends Component {this.renderNetworkScanner()} - } variant="contained" color="secondary" onClick={this.requestNetworkScan} disabled={scanningForNetworks}> + } + variant="contained" + color="secondary" + onClick={this.requestNetworkScan} + disabled={scanningForNetworks} + > Scan again… ); } - } export default withSnackbar(withStyles(styles)(WiFiNetworkScanner)); diff --git a/interface/src/network/WiFiNetworkSelector.tsx b/interface/src/network/WiFiNetworkSelector.tsx index 04aa7d660..f63347162 100644 --- a/interface/src/network/WiFiNetworkSelector.tsx +++ b/interface/src/network/WiFiNetworkSelector.tsx @@ -1,7 +1,13 @@ import React, { Component } from 'react'; import { Avatar, Badge } from '@material-ui/core'; -import { List, ListItem, ListItemIcon, ListItemText, ListItemAvatar } from '@material-ui/core'; +import { + List, + ListItem, + ListItemIcon, + ListItemText, + ListItemAvatar +} from '@material-ui/core'; import WifiIcon from '@material-ui/icons/Wifi'; import LockIcon from '@material-ui/icons/Lock'; @@ -16,13 +22,16 @@ interface WiFiNetworkSelectorProps { } class WiFiNetworkSelector extends Component { - static contextType = NetworkConnectionContext; context!: React.ContextType; renderNetwork = (network: WiFiNetwork) => { return ( - this.context.selectNetwork(network)}> + this.context.selectNetwork(network)} + > {isNetworkOpen(network) ? : } @@ -30,25 +39,27 @@ class WiFiNetworkSelector extends Component { - + ); - } + }; render() { return ( - - {this.props.networkList.networks.map(this.renderNetwork)} - + {this.props.networkList.networks.map(this.renderNetwork)} ); } - } export default WiFiNetworkSelector; diff --git a/interface/src/network/WiFiSecurityModes.ts b/interface/src/network/WiFiSecurityModes.ts index 0aea4b317..40a055f9d 100644 --- a/interface/src/network/WiFiSecurityModes.ts +++ b/interface/src/network/WiFiSecurityModes.ts @@ -1,22 +1,23 @@ -import { WiFiNetwork, WiFiEncryptionType } from "./types"; +import { WiFiNetwork, WiFiEncryptionType } from './types'; -export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN; +export const isNetworkOpen = ({ encryption_type }: WiFiNetwork) => + encryption_type === WiFiEncryptionType.WIFI_AUTH_OPEN; export const networkSecurityMode = ({ encryption_type }: WiFiNetwork) => { switch (encryption_type) { case WiFiEncryptionType.WIFI_AUTH_WEP: - return "WEP"; + return 'WEP'; case WiFiEncryptionType.WIFI_AUTH_WPA_PSK: - return "WPA"; + return 'WPA'; case WiFiEncryptionType.WIFI_AUTH_WPA2_PSK: - return "WPA2"; + return 'WPA2'; case WiFiEncryptionType.WIFI_AUTH_WPA_WPA2_PSK: - return "WPA/WPA2"; + return 'WPA/WPA2'; case WiFiEncryptionType.WIFI_AUTH_WPA2_ENTERPRISE: - return "WPA2 Enterprise"; + return 'WPA2 Enterprise'; case WiFiEncryptionType.WIFI_AUTH_OPEN: - return "None"; + return 'None'; default: - return "Unknown"; + return 'Unknown'; } -} +}; diff --git a/interface/src/ntp/NTPSettingsController.tsx b/interface/src/ntp/NTPSettingsController.tsx index 78f5f631d..fa7e3cb2f 100644 --- a/interface/src/ntp/NTPSettingsController.tsx +++ b/interface/src/ntp/NTPSettingsController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { NTP_SETTINGS_ENDPOINT } from '../api'; import NTPSettingsForm from './NTPSettingsForm'; @@ -9,7 +14,6 @@ import { NTPSettings } from './types'; type NTPSettingsControllerProps = RestControllerProps; class NTPSettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class NTPSettingsController extends Component { } + render={(formProps) => } /> - ) + ); } - } export default restController(NTP_SETTINGS_ENDPOINT, NTPSettingsController); diff --git a/interface/src/ntp/NTPSettingsForm.tsx b/interface/src/ntp/NTPSettingsForm.tsx index d43700380..aff27a87c 100644 --- a/interface/src/ntp/NTPSettingsForm.tsx +++ b/interface/src/ntp/NTPSettingsForm.tsx @@ -1,10 +1,19 @@ import React from 'react'; -import { TextValidator, ValidatorForm, SelectValidator } from 'react-material-ui-form-validator'; +import { + TextValidator, + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; import { Checkbox, MenuItem } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, FormActions, FormButton, BlockFormControlLabel } from '../components'; +import { + RestFormProps, + FormActions, + FormButton, + BlockFormControlLabel +} from '../components'; import { isIP, isHostname, or } from '../validators'; import { TIME_ZONES, timeZoneSelectItems, selectedTimeZone } from './TZ'; @@ -13,7 +22,6 @@ import { NTPSettings } from './types'; type NTPSettingsFormProps = RestFormProps; class NTPSettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); } @@ -25,7 +33,7 @@ class NTPSettingsForm extends React.Component { tz_label: event.target.value, tz_format: TIME_ZONES[event.target.value] }); - } + }; render() { const { data, handleValueChange, saveData } = this.props; @@ -43,7 +51,10 @@ class NTPSettingsForm extends React.Component { /> { {timeZoneSelectItems()} - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/ntp/NTPStatus.ts b/interface/src/ntp/NTPStatus.ts index 744b56d34..4e490bbe4 100644 --- a/interface/src/ntp/NTPStatus.ts +++ b/interface/src/ntp/NTPStatus.ts @@ -1,7 +1,8 @@ -import { Theme } from "@material-ui/core"; -import { NTPStatus, NTPSyncStatus } from "./types"; +import { Theme } from '@material-ui/core'; +import { NTPStatus, NTPSyncStatus } from './types'; -export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE; +export const isNtpActive = ({ status }: NTPStatus) => + status === NTPSyncStatus.NTP_ACTIVE; export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { switch (status) { @@ -12,15 +13,15 @@ export const ntpStatusHighlight = ({ status }: NTPStatus, theme: Theme) => { default: return theme.palette.error.main; } -} +}; export const ntpStatus = ({ status }: NTPStatus) => { switch (status) { case NTPSyncStatus.NTP_INACTIVE: - return "Inactive"; + return 'Inactive'; case NTPSyncStatus.NTP_ACTIVE: - return "Active"; + return 'Active'; default: - return "Unknown"; + return 'Unknown'; } -} +}; diff --git a/interface/src/ntp/NTPStatusController.tsx b/interface/src/ntp/NTPStatusController.tsx index 25ea4dee0..56c8e970e 100644 --- a/interface/src/ntp/NTPStatusController.tsx +++ b/interface/src/ntp/NTPStatusController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { NTP_STATUS_ENDPOINT } from '../api'; import NTPStatusForm from './NTPStatusForm'; @@ -9,7 +14,6 @@ import { NTPStatus } from './types'; type NTPStatusControllerProps = RestControllerProps; class NTPStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class NTPStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(NTP_STATUS_ENDPOINT, NTPStatusController); diff --git a/interface/src/ntp/NTPStatusForm.tsx b/interface/src/ntp/NTPStatusForm.tsx index cc0996783..bda5ce295 100644 --- a/interface/src/ntp/NTPStatusForm.tsx +++ b/interface/src/ntp/NTPStatusForm.tsx @@ -1,8 +1,23 @@ import React, { Component, Fragment } from 'react'; import { WithTheme, withTheme } from '@material-ui/core/styles'; -import { Avatar, Divider, List, ListItem, ListItemAvatar, ListItemText, Button } from '@material-ui/core'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, TextField } from '@material-ui/core'; +import { + Avatar, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText, + Button +} from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + TextField +} from '@material-ui/core'; import SwapVerticalCircleIcon from '@material-ui/icons/SwapVerticalCircle'; import AccessTimeIcon from '@material-ui/icons/AccessTime'; @@ -13,12 +28,22 @@ import RefreshIcon from '@material-ui/icons/Refresh'; import { RestFormProps, FormButton, HighlightAvatar } from '../components'; import { isNtpActive, ntpStatusHighlight, ntpStatus } from './NTPStatus'; -import { formatDuration, formatDateTime, formatLocalDateTime } from './TimeFormat'; +import { + formatDuration, + formatDateTime, + formatLocalDateTime +} from './TimeFormat'; import { NTPStatus, Time } from './types'; -import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; +import { + redirectingAuthorizedFetch, + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; import { TIME_ENDPOINT } from '../api'; -type NTPStatusFormProps = RestFormProps & WithTheme & AuthenticatedContextProps; +type NTPStatusFormProps = RestFormProps & + WithTheme & + AuthenticatedContextProps; interface NTPStatusFormState { settingTime: boolean; @@ -27,7 +52,6 @@ interface NTPStatusFormState { } class NTPStatusForm extends Component { - constructor(props: NTPStatusFormProps) { super(props); this.state = { @@ -41,20 +65,20 @@ class NTPStatusForm extends Component { this.setState({ localTime: event.target.value }); - } + }; openSetTime = () => { this.setState({ localTime: formatLocalDateTime(new Date()), settingTime: true }); - } + }; closeSetTime = () => { this.setState({ settingTime: false }); - } + }; createTime = (): Time => ({ local_time: formatLocalDateTime(new Date(this.state.localTime)) @@ -62,27 +86,34 @@ class NTPStatusForm extends Component { configureTime = () => { this.setState({ processing: true }); - redirectingAuthorizedFetch(TIME_ENDPOINT, - { - method: 'POST', - body: JSON.stringify(this.createTime()), - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => { + redirectingAuthorizedFetch(TIME_ENDPOINT, { + method: 'POST', + body: JSON.stringify(this.createTime()), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Time set successfully", { variant: 'success' }); - this.setState({ processing: false, settingTime: false }, this.props.loadData); + this.props.enqueueSnackbar('Time set successfully', { + variant: 'success' + }); + this.setState( + { processing: false, settingTime: false }, + this.props.loadData + ); } else { - throw Error("Error setting time, status code: " + response.status); + throw Error('Error setting time, status code: ' + response.status); } }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem setting the time", { variant: 'error' }); + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem setting the time', + { variant: 'error' } + ); this.setState({ processing: false, settingTime: false }); }); - } + }; renderSetTimeDialog() { return ( @@ -94,7 +125,9 @@ class NTPStatusForm extends Component { > Set Time - Enter local date and time below to set the device's time. + + Enter local date and time below to set the device's time. + { variant="outlined" fullWidth InputLabelProps={{ - shrink: true, + shrink: true }} /> - - - ) + ); } render() { - const { data, theme } = this.props + const { data, theme } = this.props; const me = this.props.authenticatedContext.me; return ( @@ -154,7 +198,10 @@ class NTPStatusForm extends Component { - + @@ -163,7 +210,10 @@ class NTPStatusForm extends Component { - + @@ -172,19 +222,32 @@ class NTPStatusForm extends Component { - + - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh {me.admin && !isNtpActive(data) && ( - diff --git a/interface/src/ntp/NetworkTime.tsx b/interface/src/ntp/NetworkTime.tsx index ebefb6e40..aa7f3af88 100644 --- a/interface/src/ntp/NetworkTime.tsx +++ b/interface/src/ntp/NetworkTime.tsx @@ -1,9 +1,13 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import NTPStatusController from './NTPStatusController'; @@ -12,8 +16,7 @@ import NTPSettingsController from './NTPSettingsController'; type NetworkTimeProps = AuthenticatedContextProps & RouteComponentProps; class NetworkTime extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -21,19 +24,34 @@ class NetworkTime extends Component { const { authenticatedContext } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - + + - ) + ); } - } -export default withAuthenticatedContext(NetworkTime) +export default withAuthenticatedContext(NetworkTime); diff --git a/interface/src/ntp/TZ.tsx b/interface/src/ntp/TZ.tsx index 0c7ebf809..099300d66 100644 --- a/interface/src/ntp/TZ.tsx +++ b/interface/src/ntp/TZ.tsx @@ -1,479 +1,480 @@ -import React from 'react'; import MenuItem from '@material-ui/core/MenuItem'; type TimeZones = { - [name: string]: string + [name: string]: string; }; export const TIME_ZONES: TimeZones = { - "Africa/Abidjan": "GMT0", - "Africa/Accra": "GMT0", - "Africa/Addis_Ababa": "EAT-3", - "Africa/Algiers": "CET-1", - "Africa/Asmara": "EAT-3", - "Africa/Bamako": "GMT0", - "Africa/Bangui": "WAT-1", - "Africa/Banjul": "GMT0", - "Africa/Bissau": "GMT0", - "Africa/Blantyre": "CAT-2", - "Africa/Brazzaville": "WAT-1", - "Africa/Bujumbura": "CAT-2", - "Africa/Cairo": "EET-2", - "Africa/Casablanca": "UNK-1", - "Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3", - "Africa/Conakry": "GMT0", - "Africa/Dakar": "GMT0", - "Africa/Dar_es_Salaam": "EAT-3", - "Africa/Djibouti": "EAT-3", - "Africa/Douala": "WAT-1", - "Africa/El_Aaiun": "UNK-1", - "Africa/Freetown": "GMT0", - "Africa/Gaborone": "CAT-2", - "Africa/Harare": "CAT-2", - "Africa/Johannesburg": "SAST-2", - "Africa/Juba": "EAT-3", - "Africa/Kampala": "EAT-3", - "Africa/Khartoum": "CAT-2", - "Africa/Kigali": "CAT-2", - "Africa/Kinshasa": "WAT-1", - "Africa/Lagos": "WAT-1", - "Africa/Libreville": "WAT-1", - "Africa/Lome": "GMT0", - "Africa/Luanda": "WAT-1", - "Africa/Lubumbashi": "CAT-2", - "Africa/Lusaka": "CAT-2", - "Africa/Malabo": "WAT-1", - "Africa/Maputo": "CAT-2", - "Africa/Maseru": "SAST-2", - "Africa/Mbabane": "SAST-2", - "Africa/Mogadishu": "EAT-3", - "Africa/Monrovia": "GMT0", - "Africa/Nairobi": "EAT-3", - "Africa/Ndjamena": "WAT-1", - "Africa/Niamey": "WAT-1", - "Africa/Nouakchott": "GMT0", - "Africa/Ouagadougou": "GMT0", - "Africa/Porto-Novo": "WAT-1", - "Africa/Sao_Tome": "GMT0", - "Africa/Tripoli": "EET-2", - "Africa/Tunis": "CET-1", - "Africa/Windhoek": "CAT-2", - "America/Adak": "HST10HDT,M3.2.0,M11.1.0", - "America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Anguilla": "AST4", - "America/Antigua": "AST4", - "America/Araguaina": "UNK3", - "America/Argentina/Buenos_Aires": "UNK3", - "America/Argentina/Catamarca": "UNK3", - "America/Argentina/Cordoba": "UNK3", - "America/Argentina/Jujuy": "UNK3", - "America/Argentina/La_Rioja": "UNK3", - "America/Argentina/Mendoza": "UNK3", - "America/Argentina/Rio_Gallegos": "UNK3", - "America/Argentina/Salta": "UNK3", - "America/Argentina/San_Juan": "UNK3", - "America/Argentina/San_Luis": "UNK3", - "America/Argentina/Tucuman": "UNK3", - "America/Argentina/Ushuaia": "UNK3", - "America/Aruba": "AST4", - "America/Asuncion": "UNK4UNK,M10.1.0/0,M3.4.0/0", - "America/Atikokan": "EST5", - "America/Bahia": "UNK3", - "America/Bahia_Banderas": "CST6CDT,M4.1.0,M10.5.0", - "America/Barbados": "AST4", - "America/Belem": "UNK3", - "America/Belize": "CST6", - "America/Blanc-Sablon": "AST4", - "America/Boa_Vista": "UNK4", - "America/Bogota": "UNK5", - "America/Boise": "MST7MDT,M3.2.0,M11.1.0", - "America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0", - "America/Campo_Grande": "UNK4", - "America/Cancun": "EST5", - "America/Caracas": "UNK4", - "America/Cayenne": "UNK3", - "America/Cayman": "EST5", - "America/Chicago": "CST6CDT,M3.2.0,M11.1.0", - "America/Chihuahua": "MST7MDT,M4.1.0,M10.5.0", - "America/Costa_Rica": "CST6", - "America/Creston": "MST7", - "America/Cuiaba": "UNK4", - "America/Curacao": "AST4", - "America/Danmarkshavn": "GMT0", - "America/Dawson": "MST7", - "America/Dawson_Creek": "MST7", - "America/Denver": "MST7MDT,M3.2.0,M11.1.0", - "America/Detroit": "EST5EDT,M3.2.0,M11.1.0", - "America/Dominica": "AST4", - "America/Edmonton": "MST7MDT,M3.2.0,M11.1.0", - "America/Eirunepe": "UNK5", - "America/El_Salvador": "CST6", - "America/Fort_Nelson": "MST7", - "America/Fortaleza": "UNK3", - "America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0", - "America/Godthab": "UNK3UNK,M3.5.0/-2,M10.5.0/-1", - "America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0", - "America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0", - "America/Grenada": "AST4", - "America/Guadeloupe": "AST4", - "America/Guatemala": "CST6", - "America/Guayaquil": "UNK5", - "America/Guyana": "UNK4", - "America/Halifax": "AST4ADT,M3.2.0,M11.1.0", - "America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1", - "America/Hermosillo": "MST7", - "America/Indiana/Indianapolis": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Knox": "CST6CDT,M3.2.0,M11.1.0", - "America/Indiana/Marengo": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Petersburg": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Tell_City": "CST6CDT,M3.2.0,M11.1.0", - "America/Indiana/Vevay": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Vincennes": "EST5EDT,M3.2.0,M11.1.0", - "America/Indiana/Winamac": "EST5EDT,M3.2.0,M11.1.0", - "America/Inuvik": "MST7MDT,M3.2.0,M11.1.0", - "America/Iqaluit": "EST5EDT,M3.2.0,M11.1.0", - "America/Jamaica": "EST5", - "America/Juneau": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0", - "America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0", - "America/Kralendijk": "AST4", - "America/La_Paz": "UNK4", - "America/Lima": "UNK5", - "America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0", - "America/Lower_Princes": "AST4", - "America/Maceio": "UNK3", - "America/Managua": "CST6", - "America/Manaus": "UNK4", - "America/Marigot": "AST4", - "America/Martinique": "AST4", - "America/Matamoros": "CST6CDT,M3.2.0,M11.1.0", - "America/Mazatlan": "MST7MDT,M4.1.0,M10.5.0", - "America/Menominee": "CST6CDT,M3.2.0,M11.1.0", - "America/Merida": "CST6CDT,M4.1.0,M10.5.0", - "America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Mexico_City": "CST6CDT,M4.1.0,M10.5.0", - "America/Miquelon": "UNK3UNK,M3.2.0,M11.1.0", - "America/Moncton": "AST4ADT,M3.2.0,M11.1.0", - "America/Monterrey": "CST6CDT,M4.1.0,M10.5.0", - "America/Montevideo": "UNK3", - "America/Montreal": "EST5EDT,M3.2.0,M11.1.0", - "America/Montserrat": "AST4", - "America/Nassau": "EST5EDT,M3.2.0,M11.1.0", - "America/New_York": "EST5EDT,M3.2.0,M11.1.0", - "America/Nipigon": "EST5EDT,M3.2.0,M11.1.0", - "America/Nome": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Noronha": "UNK2", - "America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0", - "America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0", - "America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0", - "America/Ojinaga": "MST7MDT,M3.2.0,M11.1.0", - "America/Panama": "EST5", - "America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0", - "America/Paramaribo": "UNK3", - "America/Phoenix": "MST7", - "America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0", - "America/Port_of_Spain": "AST4", - "America/Porto_Velho": "UNK4", - "America/Puerto_Rico": "AST4", - "America/Punta_Arenas": "UNK3", - "America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0", - "America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0", - "America/Recife": "UNK3", - "America/Regina": "CST6", - "America/Resolute": "CST6CDT,M3.2.0,M11.1.0", - "America/Rio_Branco": "UNK5", - "America/Santarem": "UNK3", - "America/Santiago": "UNK4UNK,M9.1.6/24,M4.1.6/24", - "America/Santo_Domingo": "AST4", - "America/Sao_Paulo": "UNK3", - "America/Scoresbysund": "UNK1UNK,M3.5.0/0,M10.5.0/1", - "America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0", - "America/St_Barthelemy": "AST4", - "America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0", - "America/St_Kitts": "AST4", - "America/St_Lucia": "AST4", - "America/St_Thomas": "AST4", - "America/St_Vincent": "AST4", - "America/Swift_Current": "CST6", - "America/Tegucigalpa": "CST6", - "America/Thule": "AST4ADT,M3.2.0,M11.1.0", - "America/Thunder_Bay": "EST5EDT,M3.2.0,M11.1.0", - "America/Tijuana": "PST8PDT,M3.2.0,M11.1.0", - "America/Toronto": "EST5EDT,M3.2.0,M11.1.0", - "America/Tortola": "AST4", - "America/Vancouver": "PST8PDT,M3.2.0,M11.1.0", - "America/Whitehorse": "MST7", - "America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0", - "America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0", - "America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0", - "Antarctica/Casey": "UNK-8", - "Antarctica/Davis": "UNK-7", - "Antarctica/DumontDUrville": "UNK-10", - "Antarctica/Macquarie": "UNK-11", - "Antarctica/Mawson": "UNK-5", - "Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3", - "Antarctica/Palmer": "UNK3", - "Antarctica/Rothera": "UNK3", - "Antarctica/Syowa": "UNK-3", - "Antarctica/Troll": "UNK0UNK-2,M3.5.0/1,M10.5.0/3", - "Antarctica/Vostok": "UNK-6", - "Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Asia/Aden": "UNK-3", - "Asia/Almaty": "UNK-6", - "Asia/Amman": "EET-2EEST,M3.5.4/24,M10.5.5/1", - "Asia/Anadyr": "UNK-12", - "Asia/Aqtau": "UNK-5", - "Asia/Aqtobe": "UNK-5", - "Asia/Ashgabat": "UNK-5", - "Asia/Atyrau": "UNK-5", - "Asia/Baghdad": "UNK-3", - "Asia/Bahrain": "UNK-3", - "Asia/Baku": "UNK-4", - "Asia/Bangkok": "UNK-7", - "Asia/Barnaul": "UNK-7", - "Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0", - "Asia/Bishkek": "UNK-6", - "Asia/Brunei": "UNK-8", - "Asia/Chita": "UNK-9", - "Asia/Choibalsan": "UNK-8", - "Asia/Colombo": "UNK-5:30", - "Asia/Damascus": "EET-2EEST,M3.5.5/0,M10.5.5/0", - "Asia/Dhaka": "UNK-6", - "Asia/Dili": "UNK-9", - "Asia/Dubai": "UNK-4", - "Asia/Dushanbe": "UNK-5", - "Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Asia/Gaza": "EET-2EEST,M3.5.5/0,M10.5.6/1", - "Asia/Hebron": "EET-2EEST,M3.5.5/0,M10.5.6/1", - "Asia/Ho_Chi_Minh": "UNK-7", - "Asia/Hong_Kong": "HKT-8", - "Asia/Hovd": "UNK-7", - "Asia/Irkutsk": "UNK-8", - "Asia/Jakarta": "WIB-7", - "Asia/Jayapura": "WIT-9", - "Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0", - "Asia/Kabul": "UNK-4:30", - "Asia/Kamchatka": "UNK-12", - "Asia/Karachi": "PKT-5", - "Asia/Kathmandu": "UNK-5:45", - "Asia/Khandyga": "UNK-9", - "Asia/Kolkata": "IST-5:30", - "Asia/Krasnoyarsk": "UNK-7", - "Asia/Kuala_Lumpur": "UNK-8", - "Asia/Kuching": "UNK-8", - "Asia/Kuwait": "UNK-3", - "Asia/Macau": "CST-8", - "Asia/Magadan": "UNK-11", - "Asia/Makassar": "WITA-8", - "Asia/Manila": "PST-8", - "Asia/Muscat": "UNK-4", - "Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Asia/Novokuznetsk": "UNK-7", - "Asia/Novosibirsk": "UNK-7", - "Asia/Omsk": "UNK-6", - "Asia/Oral": "UNK-5", - "Asia/Phnom_Penh": "UNK-7", - "Asia/Pontianak": "WIB-7", - "Asia/Pyongyang": "KST-9", - "Asia/Qatar": "UNK-3", - "Asia/Qyzylorda": "UNK-5", - "Asia/Riyadh": "UNK-3", - "Asia/Sakhalin": "UNK-11", - "Asia/Samarkand": "UNK-5", - "Asia/Seoul": "KST-9", - "Asia/Shanghai": "CST-8", - "Asia/Singapore": "UNK-8", - "Asia/Srednekolymsk": "UNK-11", - "Asia/Taipei": "CST-8", - "Asia/Tashkent": "UNK-5", - "Asia/Tbilisi": "UNK-4", - "Asia/Tehran": "UNK-3:30UNK,J79/24,J263/24", - "Asia/Thimphu": "UNK-6", - "Asia/Tokyo": "JST-9", - "Asia/Tomsk": "UNK-7", - "Asia/Ulaanbaatar": "UNK-8", - "Asia/Urumqi": "UNK-6", - "Asia/Ust-Nera": "UNK-10", - "Asia/Vientiane": "UNK-7", - "Asia/Vladivostok": "UNK-10", - "Asia/Yakutsk": "UNK-9", - "Asia/Yangon": "UNK-6:30", - "Asia/Yekaterinburg": "UNK-5", - "Asia/Yerevan": "UNK-4", - "Atlantic/Azores": "UNK1UNK,M3.5.0/0,M10.5.0/1", - "Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0", - "Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Cape_Verde": "UNK1", - "Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0", - "Atlantic/Reykjavik": "GMT0", - "Atlantic/South_Georgia": "UNK2", - "Atlantic/St_Helena": "GMT0", - "Atlantic/Stanley": "UNK3", - "Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", - "Australia/Brisbane": "AEST-10", - "Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", - "Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Darwin": "ACST-9:30", - "Australia/Eucla": "UNK-8:45", - "Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Lindeman": "AEST-10", - "Australia/Lord_Howe": "UNK-10:30UNK-11,M10.1.0,M4.1.0", - "Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Australia/Perth": "AWST-8", - "Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3", - "Etc/GMT": "GMT0", - "Etc/GMT+0": "GMT0", - "Etc/GMT+1": "UNK1", - "Etc/GMT+10": "UNK10", - "Etc/GMT+11": "UNK11", - "Etc/GMT+12": "UNK12", - "Etc/GMT+2": "UNK2", - "Etc/GMT+3": "UNK3", - "Etc/GMT+4": "UNK4", - "Etc/GMT+5": "UNK5", - "Etc/GMT+6": "UNK6", - "Etc/GMT+7": "UNK7", - "Etc/GMT+8": "UNK8", - "Etc/GMT+9": "UNK9", - "Etc/GMT-0": "GMT0", - "Etc/GMT-1": "UNK-1", - "Etc/GMT-10": "UNK-10", - "Etc/GMT-11": "UNK-11", - "Etc/GMT-12": "UNK-12", - "Etc/GMT-13": "UNK-13", - "Etc/GMT-14": "UNK-14", - "Etc/GMT-2": "UNK-2", - "Etc/GMT-3": "UNK-3", - "Etc/GMT-4": "UNK-4", - "Etc/GMT-5": "UNK-5", - "Etc/GMT-6": "UNK-6", - "Etc/GMT-7": "UNK-7", - "Etc/GMT-8": "UNK-8", - "Etc/GMT-9": "UNK-9", - "Etc/GMT0": "GMT0", - "Etc/Greenwich": "GMT0", - "Etc/UCT": "UTC0", - "Etc/UTC": "UTC0", - "Etc/Universal": "UTC0", - "Etc/Zulu": "UTC0", - "Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Astrakhan": "UNK-4", - "Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Bratislava": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Brussels": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Bucharest": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Budapest": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Busingen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Chisinau": "EET-2EEST,M3.5.0,M10.5.0/3", - "Europe/Copenhagen": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Dublin": "IST-1GMT0,M10.5.0,M3.5.0/1", - "Europe/Gibraltar": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Istanbul": "UNK-3", - "Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Kaliningrad": "EET-2", - "Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Kirov": "UNK-3", - "Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0", - "Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/London": "GMT0BST,M3.5.0/1,M10.5.0", - "Europe/Luxembourg": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Minsk": "UNK-3", - "Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Moscow": "MSK-3", - "Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Paris": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Podgorica": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Samara": "UNK-4", - "Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Saratov": "UNK-4", - "Europe/Simferopol": "MSK-3", - "Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Ulyanovsk": "UNK-4", - "Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Volgograd": "UNK-4", - "Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3", - "Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4", - "Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3", - "Indian/Antananarivo": "EAT-3", - "Indian/Chagos": "UNK-6", - "Indian/Christmas": "UNK-7", - "Indian/Cocos": "UNK-6:30", - "Indian/Comoro": "EAT-3", - "Indian/Kerguelen": "UNK-5", - "Indian/Mahe": "UNK-4", - "Indian/Maldives": "UNK-5", - "Indian/Mauritius": "UNK-4", - "Indian/Mayotte": "EAT-3", - "Indian/Reunion": "UNK-4", - "Pacific/Apia": "UNK-13UNK,M9.5.0/3,M4.1.0/4", - "Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3", - "Pacific/Bougainville": "UNK-11", - "Pacific/Chatham": "UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45", - "Pacific/Chuuk": "UNK-10", - "Pacific/Easter": "UNK6UNK,M9.1.6/22,M4.1.6/22", - "Pacific/Efate": "UNK-11", - "Pacific/Enderbury": "UNK-13", - "Pacific/Fakaofo": "UNK-13", - "Pacific/Fiji": "UNK-12UNK,M11.2.0,M1.2.3/99", - "Pacific/Funafuti": "UNK-12", - "Pacific/Galapagos": "UNK6", - "Pacific/Gambier": "UNK9", - "Pacific/Guadalcanal": "UNK-11", - "Pacific/Guam": "ChST-10", - "Pacific/Honolulu": "HST10", - "Pacific/Kiritimati": "UNK-14", - "Pacific/Kosrae": "UNK-11", - "Pacific/Kwajalein": "UNK-12", - "Pacific/Majuro": "UNK-12", - "Pacific/Marquesas": "UNK9:30", - "Pacific/Midway": "SST11", - "Pacific/Nauru": "UNK-12", - "Pacific/Niue": "UNK11", - "Pacific/Norfolk": "UNK-11UNK,M10.1.0,M4.1.0/3", - "Pacific/Noumea": "UNK-11", - "Pacific/Pago_Pago": "SST11", - "Pacific/Palau": "UNK-9", - "Pacific/Pitcairn": "UNK8", - "Pacific/Pohnpei": "UNK-11", - "Pacific/Port_Moresby": "UNK-10", - "Pacific/Rarotonga": "UNK10", - "Pacific/Saipan": "ChST-10", - "Pacific/Tahiti": "UNK10", - "Pacific/Tarawa": "UNK-12", - "Pacific/Tongatapu": "UNK-13", - "Pacific/Wake": "UNK-12", - "Pacific/Wallis": "UNK-12" -} + 'Africa/Abidjan': 'GMT0', + 'Africa/Accra': 'GMT0', + 'Africa/Addis_Ababa': 'EAT-3', + 'Africa/Algiers': 'CET-1', + 'Africa/Asmara': 'EAT-3', + 'Africa/Bamako': 'GMT0', + 'Africa/Bangui': 'WAT-1', + 'Africa/Banjul': 'GMT0', + 'Africa/Bissau': 'GMT0', + 'Africa/Blantyre': 'CAT-2', + 'Africa/Brazzaville': 'WAT-1', + 'Africa/Bujumbura': 'CAT-2', + 'Africa/Cairo': 'EET-2', + 'Africa/Casablanca': 'UNK-1', + 'Africa/Ceuta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Africa/Conakry': 'GMT0', + 'Africa/Dakar': 'GMT0', + 'Africa/Dar_es_Salaam': 'EAT-3', + 'Africa/Djibouti': 'EAT-3', + 'Africa/Douala': 'WAT-1', + 'Africa/El_Aaiun': 'UNK-1', + 'Africa/Freetown': 'GMT0', + 'Africa/Gaborone': 'CAT-2', + 'Africa/Harare': 'CAT-2', + 'Africa/Johannesburg': 'SAST-2', + 'Africa/Juba': 'EAT-3', + 'Africa/Kampala': 'EAT-3', + 'Africa/Khartoum': 'CAT-2', + 'Africa/Kigali': 'CAT-2', + 'Africa/Kinshasa': 'WAT-1', + 'Africa/Lagos': 'WAT-1', + 'Africa/Libreville': 'WAT-1', + 'Africa/Lome': 'GMT0', + 'Africa/Luanda': 'WAT-1', + 'Africa/Lubumbashi': 'CAT-2', + 'Africa/Lusaka': 'CAT-2', + 'Africa/Malabo': 'WAT-1', + 'Africa/Maputo': 'CAT-2', + 'Africa/Maseru': 'SAST-2', + 'Africa/Mbabane': 'SAST-2', + 'Africa/Mogadishu': 'EAT-3', + 'Africa/Monrovia': 'GMT0', + 'Africa/Nairobi': 'EAT-3', + 'Africa/Ndjamena': 'WAT-1', + 'Africa/Niamey': 'WAT-1', + 'Africa/Nouakchott': 'GMT0', + 'Africa/Ouagadougou': 'GMT0', + 'Africa/Porto-Novo': 'WAT-1', + 'Africa/Sao_Tome': 'GMT0', + 'Africa/Tripoli': 'EET-2', + 'Africa/Tunis': 'CET-1', + 'Africa/Windhoek': 'CAT-2', + 'America/Adak': 'HST10HDT,M3.2.0,M11.1.0', + 'America/Anchorage': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Anguilla': 'AST4', + 'America/Antigua': 'AST4', + 'America/Araguaina': 'UNK3', + 'America/Argentina/Buenos_Aires': 'UNK3', + 'America/Argentina/Catamarca': 'UNK3', + 'America/Argentina/Cordoba': 'UNK3', + 'America/Argentina/Jujuy': 'UNK3', + 'America/Argentina/La_Rioja': 'UNK3', + 'America/Argentina/Mendoza': 'UNK3', + 'America/Argentina/Rio_Gallegos': 'UNK3', + 'America/Argentina/Salta': 'UNK3', + 'America/Argentina/San_Juan': 'UNK3', + 'America/Argentina/San_Luis': 'UNK3', + 'America/Argentina/Tucuman': 'UNK3', + 'America/Argentina/Ushuaia': 'UNK3', + 'America/Aruba': 'AST4', + 'America/Asuncion': 'UNK4UNK,M10.1.0/0,M3.4.0/0', + 'America/Atikokan': 'EST5', + 'America/Bahia': 'UNK3', + 'America/Bahia_Banderas': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Barbados': 'AST4', + 'America/Belem': 'UNK3', + 'America/Belize': 'CST6', + 'America/Blanc-Sablon': 'AST4', + 'America/Boa_Vista': 'UNK4', + 'America/Bogota': 'UNK5', + 'America/Boise': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Cambridge_Bay': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Campo_Grande': 'UNK4', + 'America/Cancun': 'EST5', + 'America/Caracas': 'UNK4', + 'America/Cayenne': 'UNK3', + 'America/Cayman': 'EST5', + 'America/Chicago': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Chihuahua': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Costa_Rica': 'CST6', + 'America/Creston': 'MST7', + 'America/Cuiaba': 'UNK4', + 'America/Curacao': 'AST4', + 'America/Danmarkshavn': 'GMT0', + 'America/Dawson': 'MST7', + 'America/Dawson_Creek': 'MST7', + 'America/Denver': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Detroit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Dominica': 'AST4', + 'America/Edmonton': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Eirunepe': 'UNK5', + 'America/El_Salvador': 'CST6', + 'America/Fort_Nelson': 'MST7', + 'America/Fortaleza': 'UNK3', + 'America/Glace_Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Godthab': 'UNK3UNK,M3.5.0/-2,M10.5.0/-1', + 'America/Goose_Bay': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Grand_Turk': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Grenada': 'AST4', + 'America/Guadeloupe': 'AST4', + 'America/Guatemala': 'CST6', + 'America/Guayaquil': 'UNK5', + 'America/Guyana': 'UNK4', + 'America/Halifax': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Havana': 'CST5CDT,M3.2.0/0,M11.1.0/1', + 'America/Hermosillo': 'MST7', + 'America/Indiana/Indianapolis': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Knox': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Marengo': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Petersburg': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Tell_City': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Indiana/Vevay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Vincennes': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Indiana/Winamac': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Inuvik': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Iqaluit': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Jamaica': 'EST5', + 'America/Juneau': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Kentucky/Louisville': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kentucky/Monticello': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Kralendijk': 'AST4', + 'America/La_Paz': 'UNK4', + 'America/Lima': 'UNK5', + 'America/Los_Angeles': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Lower_Princes': 'AST4', + 'America/Maceio': 'UNK3', + 'America/Managua': 'CST6', + 'America/Manaus': 'UNK4', + 'America/Marigot': 'AST4', + 'America/Martinique': 'AST4', + 'America/Matamoros': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Mazatlan': 'MST7MDT,M4.1.0,M10.5.0', + 'America/Menominee': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Merida': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Metlakatla': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Mexico_City': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Miquelon': 'UNK3UNK,M3.2.0,M11.1.0', + 'America/Moncton': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Monterrey': 'CST6CDT,M4.1.0,M10.5.0', + 'America/Montevideo': 'UNK3', + 'America/Montreal': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Montserrat': 'AST4', + 'America/Nassau': 'EST5EDT,M3.2.0,M11.1.0', + 'America/New_York': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nipigon': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Nome': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Noronha': 'UNK2', + 'America/North_Dakota/Beulah': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North_Dakota/Center': 'CST6CDT,M3.2.0,M11.1.0', + 'America/North_Dakota/New_Salem': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Ojinaga': 'MST7MDT,M3.2.0,M11.1.0', + 'America/Panama': 'EST5', + 'America/Pangnirtung': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Paramaribo': 'UNK3', + 'America/Phoenix': 'MST7', + 'America/Port-au-Prince': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Port_of_Spain': 'AST4', + 'America/Porto_Velho': 'UNK4', + 'America/Puerto_Rico': 'AST4', + 'America/Punta_Arenas': 'UNK3', + 'America/Rainy_River': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rankin_Inlet': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Recife': 'UNK3', + 'America/Regina': 'CST6', + 'America/Resolute': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Rio_Branco': 'UNK5', + 'America/Santarem': 'UNK3', + 'America/Santiago': 'UNK4UNK,M9.1.6/24,M4.1.6/24', + 'America/Santo_Domingo': 'AST4', + 'America/Sao_Paulo': 'UNK3', + 'America/Scoresbysund': 'UNK1UNK,M3.5.0/0,M10.5.0/1', + 'America/Sitka': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/St_Barthelemy': 'AST4', + 'America/St_Johns': 'NST3:30NDT,M3.2.0,M11.1.0', + 'America/St_Kitts': 'AST4', + 'America/St_Lucia': 'AST4', + 'America/St_Thomas': 'AST4', + 'America/St_Vincent': 'AST4', + 'America/Swift_Current': 'CST6', + 'America/Tegucigalpa': 'CST6', + 'America/Thule': 'AST4ADT,M3.2.0,M11.1.0', + 'America/Thunder_Bay': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tijuana': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Toronto': 'EST5EDT,M3.2.0,M11.1.0', + 'America/Tortola': 'AST4', + 'America/Vancouver': 'PST8PDT,M3.2.0,M11.1.0', + 'America/Whitehorse': 'MST7', + 'America/Winnipeg': 'CST6CDT,M3.2.0,M11.1.0', + 'America/Yakutat': 'AKST9AKDT,M3.2.0,M11.1.0', + 'America/Yellowknife': 'MST7MDT,M3.2.0,M11.1.0', + 'Antarctica/Casey': 'UNK-8', + 'Antarctica/Davis': 'UNK-7', + 'Antarctica/DumontDUrville': 'UNK-10', + 'Antarctica/Macquarie': 'UNK-11', + 'Antarctica/Mawson': 'UNK-5', + 'Antarctica/McMurdo': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Antarctica/Palmer': 'UNK3', + 'Antarctica/Rothera': 'UNK3', + 'Antarctica/Syowa': 'UNK-3', + 'Antarctica/Troll': 'UNK0UNK-2,M3.5.0/1,M10.5.0/3', + 'Antarctica/Vostok': 'UNK-6', + 'Arctic/Longyearbyen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Asia/Aden': 'UNK-3', + 'Asia/Almaty': 'UNK-6', + 'Asia/Amman': 'EET-2EEST,M3.5.4/24,M10.5.5/1', + 'Asia/Anadyr': 'UNK-12', + 'Asia/Aqtau': 'UNK-5', + 'Asia/Aqtobe': 'UNK-5', + 'Asia/Ashgabat': 'UNK-5', + 'Asia/Atyrau': 'UNK-5', + 'Asia/Baghdad': 'UNK-3', + 'Asia/Bahrain': 'UNK-3', + 'Asia/Baku': 'UNK-4', + 'Asia/Bangkok': 'UNK-7', + 'Asia/Barnaul': 'UNK-7', + 'Asia/Beirut': 'EET-2EEST,M3.5.0/0,M10.5.0/0', + 'Asia/Bishkek': 'UNK-6', + 'Asia/Brunei': 'UNK-8', + 'Asia/Chita': 'UNK-9', + 'Asia/Choibalsan': 'UNK-8', + 'Asia/Colombo': 'UNK-5:30', + 'Asia/Damascus': 'EET-2EEST,M3.5.5/0,M10.5.5/0', + 'Asia/Dhaka': 'UNK-6', + 'Asia/Dili': 'UNK-9', + 'Asia/Dubai': 'UNK-4', + 'Asia/Dushanbe': 'UNK-5', + 'Asia/Famagusta': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Gaza': 'EET-2EEST,M3.5.5/0,M10.5.6/1', + 'Asia/Hebron': 'EET-2EEST,M3.5.5/0,M10.5.6/1', + 'Asia/Ho_Chi_Minh': 'UNK-7', + 'Asia/Hong_Kong': 'HKT-8', + 'Asia/Hovd': 'UNK-7', + 'Asia/Irkutsk': 'UNK-8', + 'Asia/Jakarta': 'WIB-7', + 'Asia/Jayapura': 'WIT-9', + 'Asia/Jerusalem': 'IST-2IDT,M3.4.4/26,M10.5.0', + 'Asia/Kabul': 'UNK-4:30', + 'Asia/Kamchatka': 'UNK-12', + 'Asia/Karachi': 'PKT-5', + 'Asia/Kathmandu': 'UNK-5:45', + 'Asia/Khandyga': 'UNK-9', + 'Asia/Kolkata': 'IST-5:30', + 'Asia/Krasnoyarsk': 'UNK-7', + 'Asia/Kuala_Lumpur': 'UNK-8', + 'Asia/Kuching': 'UNK-8', + 'Asia/Kuwait': 'UNK-3', + 'Asia/Macau': 'CST-8', + 'Asia/Magadan': 'UNK-11', + 'Asia/Makassar': 'WITA-8', + 'Asia/Manila': 'PST-8', + 'Asia/Muscat': 'UNK-4', + 'Asia/Nicosia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Asia/Novokuznetsk': 'UNK-7', + 'Asia/Novosibirsk': 'UNK-7', + 'Asia/Omsk': 'UNK-6', + 'Asia/Oral': 'UNK-5', + 'Asia/Phnom_Penh': 'UNK-7', + 'Asia/Pontianak': 'WIB-7', + 'Asia/Pyongyang': 'KST-9', + 'Asia/Qatar': 'UNK-3', + 'Asia/Qyzylorda': 'UNK-5', + 'Asia/Riyadh': 'UNK-3', + 'Asia/Sakhalin': 'UNK-11', + 'Asia/Samarkand': 'UNK-5', + 'Asia/Seoul': 'KST-9', + 'Asia/Shanghai': 'CST-8', + 'Asia/Singapore': 'UNK-8', + 'Asia/Srednekolymsk': 'UNK-11', + 'Asia/Taipei': 'CST-8', + 'Asia/Tashkent': 'UNK-5', + 'Asia/Tbilisi': 'UNK-4', + 'Asia/Tehran': 'UNK-3:30UNK,J79/24,J263/24', + 'Asia/Thimphu': 'UNK-6', + 'Asia/Tokyo': 'JST-9', + 'Asia/Tomsk': 'UNK-7', + 'Asia/Ulaanbaatar': 'UNK-8', + 'Asia/Urumqi': 'UNK-6', + 'Asia/Ust-Nera': 'UNK-10', + 'Asia/Vientiane': 'UNK-7', + 'Asia/Vladivostok': 'UNK-10', + 'Asia/Yakutsk': 'UNK-9', + 'Asia/Yangon': 'UNK-6:30', + 'Asia/Yekaterinburg': 'UNK-5', + 'Asia/Yerevan': 'UNK-4', + 'Atlantic/Azores': 'UNK1UNK,M3.5.0/0,M10.5.0/1', + 'Atlantic/Bermuda': 'AST4ADT,M3.2.0,M11.1.0', + 'Atlantic/Canary': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Cape_Verde': 'UNK1', + 'Atlantic/Faroe': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Madeira': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Atlantic/Reykjavik': 'GMT0', + 'Atlantic/South_Georgia': 'UNK2', + 'Atlantic/St_Helena': 'GMT0', + 'Atlantic/Stanley': 'UNK3', + 'Australia/Adelaide': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Brisbane': 'AEST-10', + 'Australia/Broken_Hill': 'ACST-9:30ACDT,M10.1.0,M4.1.0/3', + 'Australia/Currie': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Darwin': 'ACST-9:30', + 'Australia/Eucla': 'UNK-8:45', + 'Australia/Hobart': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Lindeman': 'AEST-10', + 'Australia/Lord_Howe': 'UNK-10:30UNK-11,M10.1.0,M4.1.0', + 'Australia/Melbourne': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Australia/Perth': 'AWST-8', + 'Australia/Sydney': 'AEST-10AEDT,M10.1.0,M4.1.0/3', + 'Etc/GMT': 'GMT0', + 'Etc/GMT+0': 'GMT0', + 'Etc/GMT+1': 'UNK1', + 'Etc/GMT+10': 'UNK10', + 'Etc/GMT+11': 'UNK11', + 'Etc/GMT+12': 'UNK12', + 'Etc/GMT+2': 'UNK2', + 'Etc/GMT+3': 'UNK3', + 'Etc/GMT+4': 'UNK4', + 'Etc/GMT+5': 'UNK5', + 'Etc/GMT+6': 'UNK6', + 'Etc/GMT+7': 'UNK7', + 'Etc/GMT+8': 'UNK8', + 'Etc/GMT+9': 'UNK9', + 'Etc/GMT-0': 'GMT0', + 'Etc/GMT-1': 'UNK-1', + 'Etc/GMT-10': 'UNK-10', + 'Etc/GMT-11': 'UNK-11', + 'Etc/GMT-12': 'UNK-12', + 'Etc/GMT-13': 'UNK-13', + 'Etc/GMT-14': 'UNK-14', + 'Etc/GMT-2': 'UNK-2', + 'Etc/GMT-3': 'UNK-3', + 'Etc/GMT-4': 'UNK-4', + 'Etc/GMT-5': 'UNK-5', + 'Etc/GMT-6': 'UNK-6', + 'Etc/GMT-7': 'UNK-7', + 'Etc/GMT-8': 'UNK-8', + 'Etc/GMT-9': 'UNK-9', + 'Etc/GMT0': 'GMT0', + 'Etc/Greenwich': 'GMT0', + 'Etc/UCT': 'UTC0', + 'Etc/UTC': 'UTC0', + 'Etc/Universal': 'UTC0', + 'Etc/Zulu': 'UTC0', + 'Europe/Amsterdam': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Andorra': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Astrakhan': 'UNK-4', + 'Europe/Athens': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Belgrade': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Berlin': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bratislava': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Brussels': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Bucharest': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Budapest': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Busingen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Chisinau': 'EET-2EEST,M3.5.0,M10.5.0/3', + 'Europe/Copenhagen': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Dublin': 'IST-1GMT0,M10.5.0,M3.5.0/1', + 'Europe/Gibraltar': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Guernsey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Helsinki': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Isle_of_Man': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Istanbul': 'UNK-3', + 'Europe/Jersey': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Kaliningrad': 'EET-2', + 'Europe/Kiev': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Kirov': 'UNK-3', + 'Europe/Lisbon': 'WET0WEST,M3.5.0/1,M10.5.0', + 'Europe/Ljubljana': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/London': 'GMT0BST,M3.5.0/1,M10.5.0', + 'Europe/Luxembourg': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Madrid': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Malta': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Mariehamn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Minsk': 'UNK-3', + 'Europe/Monaco': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Moscow': 'MSK-3', + 'Europe/Oslo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Paris': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Podgorica': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Prague': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Riga': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Rome': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Samara': 'UNK-4', + 'Europe/San_Marino': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sarajevo': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Saratov': 'UNK-4', + 'Europe/Simferopol': 'MSK-3', + 'Europe/Skopje': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Sofia': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Stockholm': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Tallinn': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Tirane': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Ulyanovsk': 'UNK-4', + 'Europe/Uzhgorod': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Vaduz': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vatican': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vienna': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Vilnius': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Volgograd': 'UNK-4', + 'Europe/Warsaw': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zagreb': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Europe/Zaporozhye': 'EET-2EEST,M3.5.0/3,M10.5.0/4', + 'Europe/Zurich': 'CET-1CEST,M3.5.0,M10.5.0/3', + 'Indian/Antananarivo': 'EAT-3', + 'Indian/Chagos': 'UNK-6', + 'Indian/Christmas': 'UNK-7', + 'Indian/Cocos': 'UNK-6:30', + 'Indian/Comoro': 'EAT-3', + 'Indian/Kerguelen': 'UNK-5', + 'Indian/Mahe': 'UNK-4', + 'Indian/Maldives': 'UNK-5', + 'Indian/Mauritius': 'UNK-4', + 'Indian/Mayotte': 'EAT-3', + 'Indian/Reunion': 'UNK-4', + 'Pacific/Apia': 'UNK-13UNK,M9.5.0/3,M4.1.0/4', + 'Pacific/Auckland': 'NZST-12NZDT,M9.5.0,M4.1.0/3', + 'Pacific/Bougainville': 'UNK-11', + 'Pacific/Chatham': 'UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45', + 'Pacific/Chuuk': 'UNK-10', + 'Pacific/Easter': 'UNK6UNK,M9.1.6/22,M4.1.6/22', + 'Pacific/Efate': 'UNK-11', + 'Pacific/Enderbury': 'UNK-13', + 'Pacific/Fakaofo': 'UNK-13', + 'Pacific/Fiji': 'UNK-12UNK,M11.2.0,M1.2.3/99', + 'Pacific/Funafuti': 'UNK-12', + 'Pacific/Galapagos': 'UNK6', + 'Pacific/Gambier': 'UNK9', + 'Pacific/Guadalcanal': 'UNK-11', + 'Pacific/Guam': 'ChST-10', + 'Pacific/Honolulu': 'HST10', + 'Pacific/Kiritimati': 'UNK-14', + 'Pacific/Kosrae': 'UNK-11', + 'Pacific/Kwajalein': 'UNK-12', + 'Pacific/Majuro': 'UNK-12', + 'Pacific/Marquesas': 'UNK9:30', + 'Pacific/Midway': 'SST11', + 'Pacific/Nauru': 'UNK-12', + 'Pacific/Niue': 'UNK11', + 'Pacific/Norfolk': 'UNK-11UNK,M10.1.0,M4.1.0/3', + 'Pacific/Noumea': 'UNK-11', + 'Pacific/Pago_Pago': 'SST11', + 'Pacific/Palau': 'UNK-9', + 'Pacific/Pitcairn': 'UNK8', + 'Pacific/Pohnpei': 'UNK-11', + 'Pacific/Port_Moresby': 'UNK-10', + 'Pacific/Rarotonga': 'UNK10', + 'Pacific/Saipan': 'ChST-10', + 'Pacific/Tahiti': 'UNK10', + 'Pacific/Tarawa': 'UNK-12', + 'Pacific/Tongatapu': 'UNK-13', + 'Pacific/Wake': 'UNK-12', + 'Pacific/Wallis': 'UNK-12' +}; export function selectedTimeZone(label: string, format: string) { return TIME_ZONES[label] === format ? label : undefined; } export function timeZoneSelectItems() { - return Object.keys(TIME_ZONES).map(label => ( - {label} + return Object.keys(TIME_ZONES).map((label) => ( + + {label} + )); -} \ No newline at end of file +} diff --git a/interface/src/ntp/TimeFormat.ts b/interface/src/ntp/TimeFormat.ts index 863771112..273c5df14 100644 --- a/interface/src/ntp/TimeFormat.ts +++ b/interface/src/ntp/TimeFormat.ts @@ -1,32 +1,29 @@ import parseMilliseconds from 'parse-ms'; -const LOCALE_FORMAT = new Intl.DateTimeFormat( - [...window.navigator.languages], - { - day: 'numeric', - month: 'short', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - hour12: false - } -); +const LOCALE_FORMAT = new Intl.DateTimeFormat([...window.navigator.languages], { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + hour12: false +}); export const formatDateTime = (dateTime: string) => { return LOCALE_FORMAT.format(new Date(dateTime.substr(0, 19))); -} +}; export const formatLocalDateTime = (date: Date) => { return new Date(date.getTime() - date.getTimezoneOffset() * 60000) .toISOString() .slice(0, -1) .substr(0, 19); -} +}; export const formatDuration = (duration: number) => { const { days, hours, minutes, seconds } = parseMilliseconds(duration * 1000); - var formatted = ''; + let formatted = ''; if (days) { formatted += pluralize(days, 'day'); } @@ -40,6 +37,7 @@ export const formatDuration = (duration: number) => { formatted += pluralize(seconds, 'second'); } return formatted; -} +}; -const pluralize = (count: number, noun: string, suffix: string = 's') => ` ${count} ${noun}${count !== 1 ? suffix : ''} `; +const pluralize = (count: number, noun: string, suffix = 's') => + ` ${count} ${noun}${count !== 1 ? suffix : ''} `; diff --git a/interface/src/ntp/types.ts b/interface/src/ntp/types.ts index 4a19da6e7..06c87ef06 100644 --- a/interface/src/ntp/types.ts +++ b/interface/src/ntp/types.ts @@ -20,4 +20,4 @@ export interface NTPSettings { export interface Time { local_time: string; -} \ No newline at end of file +} diff --git a/interface/src/project/EMSESPBoardProfiles.tsx b/interface/src/project/EMSESPBoardProfiles.tsx index b13eced75..05336ae70 100644 --- a/interface/src/project/EMSESPBoardProfiles.tsx +++ b/interface/src/project/EMSESPBoardProfiles.tsx @@ -1,23 +1,24 @@ -import React from 'react'; import MenuItem from '@material-ui/core/MenuItem'; type BoardProfiles = { - [name: string]: string + [name: string]: string; }; export const BOARD_PROFILES: BoardProfiles = { - "S32": "BBQKees Gateway S32", - "E32": "BBQKees Gateway E32", - "NODEMCU": "NodeMCU 32S", - "MH-ET": "MH-ET Live D1 Mini", - "LOLIN": "Lolin D32", - "OLIMEX": "Olimex ESP32-EVB", - "TLK110": "Generic Ethernet (TLK110)", - "LAN8720": "Generic Ethernet (LAN8720)" -} + S32: 'BBQKees Gateway S32', + E32: 'BBQKees Gateway E32', + NODEMCU: 'NodeMCU 32S', + 'MH-ET': 'MH-ET Live D1 Mini', + LOLIN: 'Lolin D32', + OLIMEX: 'Olimex ESP32-EVB', + TLK110: 'Generic Ethernet (TLK110)', + LAN8720: 'Generic Ethernet (LAN8720)' +}; export function boardProfileSelectItems() { - return Object.keys(BOARD_PROFILES).map(code => ( - {BOARD_PROFILES[code]} - )); + return Object.keys(BOARD_PROFILES).map((code) => ( + + {BOARD_PROFILES[code]} + + )); } diff --git a/interface/src/project/EMSESPDashboard.tsx b/interface/src/project/EMSESPDashboard.tsx index 626821f7c..b567aa79f 100644 --- a/interface/src/project/EMSESPDashboard.tsx +++ b/interface/src/project/EMSESPDashboard.tsx @@ -1,5 +1,5 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; @@ -12,30 +12,46 @@ import EMSESPDevicesController from './EMSESPDevicesController'; import EMSESPHelp from './EMSESPHelp'; class EMSESP extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - - + this.handleTabChange(path)} + variant="fullWidth" + > + - - - + + + - - ) + ); } - } export default EMSESP; diff --git a/interface/src/project/EMSESPDevicesController.tsx b/interface/src/project/EMSESPDevicesController.tsx index bd035f933..7300a5a48 100644 --- a/interface/src/project/EMSESPDevicesController.tsx +++ b/interface/src/project/EMSESPDevicesController.tsx @@ -1,30 +1,35 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; + import { ENDPOINT_ROOT } from '../api'; import EMSESPDevicesForm from './EMSESPDevicesForm'; import { EMSESPDevices } from './EMSESPtypes'; -export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + "allDevices"; +export const EMSESP_DEVICES_ENDPOINT = ENDPOINT_ROOT + 'allDevices'; type EMSESPDevicesControllerProps = RestControllerProps; class EMSESPDevicesController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - } - /> - - ) - } + render() { + return ( + + } + /> + + ); + } } export default restController(EMSESP_DEVICES_ENDPOINT, EMSESPDevicesController); diff --git a/interface/src/project/EMSESPDevicesForm.tsx b/interface/src/project/EMSESPDevicesForm.tsx index e6f422c3b..98630dae2 100644 --- a/interface/src/project/EMSESPDevicesForm.tsx +++ b/interface/src/project/EMSESPDevicesForm.tsx @@ -1,38 +1,70 @@ -import React, { Component, Fragment } from "react"; -import { withStyles, Theme, createStyles } from "@material-ui/core/styles"; +import React, { Component, Fragment } from 'react'; +import { withStyles, Theme, createStyles } from '@material-ui/core/styles'; + +import parseMilliseconds from 'parse-ms'; + +import { Decoder } from '@msgpack/msgpack'; +const decoder = new Decoder(); import { - Table, TableBody, TableCell, TableHead, TableRow, TableContainer, withWidth, WithWidthProps, isWidthDown, - Button, Tooltip, DialogTitle, DialogContent, DialogActions, Box, Dialog, Typography -} from "@material-ui/core"; + Table, + TableBody, + TableCell, + TableHead, + TableRow, + TableContainer, + withWidth, + WithWidthProps, + isWidthDown, + Button, + Tooltip, + DialogTitle, + DialogContent, + DialogActions, + Box, + Dialog, + Typography +} from '@material-ui/core'; -import RefreshIcon from "@material-ui/icons/Refresh"; -import ListIcon from "@material-ui/icons/List"; +import RefreshIcon from '@material-ui/icons/Refresh'; +import ListIcon from '@material-ui/icons/List'; import IconButton from '@material-ui/core/IconButton'; import EditIcon from '@material-ui/icons/Edit'; -import { redirectingAuthorizedFetch, withAuthenticatedContext, AuthenticatedContextProps } from "../authentication"; -import { RestFormProps, FormButton, extractEventValue } from "../components"; +import { + redirectingAuthorizedFetch, + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; -import { EMSESPDevices, EMSESPDeviceData, Device, DeviceValue } from "./EMSESPtypes"; +import { RestFormProps, FormButton, extractEventValue } from '../components'; + +import { + EMSESPDevices, + EMSESPDeviceData, + Device, + DeviceValue, + DeviceValueUOM, + DeviceValueUOM_s +} from './EMSESPtypes'; import ValueForm from './ValueForm'; -import { ENDPOINT_ROOT } from "../api"; +import { ENDPOINT_ROOT } from '../api'; -export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + "scanDevices"; -export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + "deviceData"; -export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + "writeValue"; +export const SCANDEVICES_ENDPOINT = ENDPOINT_ROOT + 'scanDevices'; +export const DEVICE_DATA_ENDPOINT = ENDPOINT_ROOT + 'deviceData'; +export const WRITE_VALUE_ENDPOINT = ENDPOINT_ROOT + 'writeValue'; const StyledTableCell = withStyles((theme: Theme) => createStyles({ head: { backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, + color: theme.palette.common.white }, body: { - fontSize: 14, - }, + fontSize: 14 + } }) )(TableCell); @@ -42,8 +74,8 @@ const CustomTooltip = withStyles((theme: Theme) => ({ color: 'white', boxShadow: theme.shadows[1], fontSize: 11, - border: '1px solid #dadde9', - }, + border: '1px solid #dadde9' + } }))(Tooltip); function compareDevices(a: Device, b: Device) { @@ -61,87 +93,118 @@ interface EMSESPDevicesFormState { processing: boolean; deviceData?: EMSESPDeviceData; selectedDevice?: number; - devicevalue?: DeviceValue; + edit_devicevalue?: DeviceValue; } -type EMSESPDevicesFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; +type EMSESPDevicesFormProps = RestFormProps & + AuthenticatedContextProps & + WithWidthProps; -function formatTemp(t: string) { - if (t == null) { - return "n/a"; +export const formatDuration = (duration_min: number) => { + const { days, hours, minutes } = parseMilliseconds(duration_min * 60000); + let formatted = ''; + if (days) { + formatted += pluralize(days, 'day'); } - return t + " °C"; -} - -function formatUnit(u: string) { - if (u == null) { - return u; + if (hours) { + formatted += pluralize(hours, 'hour'); + } + if (minutes) { + formatted += pluralize(minutes, 'minute'); + } + return formatted; +}; + +const pluralize = (count: number, noun: string, suffix = 's') => + ` ${count} ${noun}${count !== 1 ? suffix : ''} `; + +function formatValue(value: any, uom: number) { + switch (uom) { + case DeviceValueUOM.HOURS: + return value ? formatDuration(value * 60) : '0 hours'; + case DeviceValueUOM.MINUTES: + return value ? formatDuration(value) : '0 minutes'; + case DeviceValueUOM.NONE: + return value; + case DeviceValueUOM.NUM: + return new Intl.NumberFormat().format(value); + case DeviceValueUOM.BOOLEAN: + return value ? 'on' : 'off'; + default: + return ( + new Intl.NumberFormat().format(value) + ' ' + DeviceValueUOM_s[uom] + ); } - return " " + u; } -class EMSESPDevicesForm extends Component { +class EMSESPDevicesForm extends Component< + EMSESPDevicesFormProps, + EMSESPDevicesFormState +> { state: EMSESPDevicesFormState = { confirmScanDevices: false, processing: false }; - handleValueChange = (name: keyof DeviceValue) => (event: React.ChangeEvent) => { - this.setState({ devicevalue: { ...this.state.devicevalue!, [name]: extractEventValue(event) } }); + handleValueChange = (name: keyof DeviceValue) => ( + event: React.ChangeEvent + ) => { + this.setState({ + edit_devicevalue: { + ...this.state.edit_devicevalue!, + [name]: extractEventValue(event) + } + }); }; cancelEditingValue = () => { - this.setState({ - devicevalue: undefined - }); - } + this.setState({ edit_devicevalue: undefined }); + }; doneEditingValue = () => { - const { devicevalue } = this.state; + const { edit_devicevalue, selectedDevice } = this.state; redirectingAuthorizedFetch(WRITE_VALUE_ENDPOINT, { - method: "POST", - body: JSON.stringify({ devicevalue: devicevalue }), + method: 'POST', + body: JSON.stringify({ + id: selectedDevice, + devicevalue: edit_devicevalue + }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Write command sent to device", { variant: "success" }); + this.props.enqueueSnackbar('Write command sent to device', { + variant: 'success' + }); } else if (response.status === 204) { - this.props.enqueueSnackbar("Write command failed", { variant: "error" }); + this.props.enqueueSnackbar('Write command failed', { + variant: 'error' + }); } else if (response.status === 403) { - this.props.enqueueSnackbar("Write access denied", { variant: "error" }); + this.props.enqueueSnackbar('Write access denied', { + variant: 'error' + }); } else { - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); } }) .catch((error) => { - this.props.enqueueSnackbar( - error.message || "Problem writing value", { variant: "error" } - ); + this.props.enqueueSnackbar(error.message || 'Problem writing value', { + variant: 'error' + }); }); - if (devicevalue) { - this.setState({ - devicevalue: undefined - }); + if (edit_devicevalue) { + this.setState({ edit_devicevalue: undefined }); } - }; - sendCommand = (i: any) => { - this.setState({ - devicevalue: { - id: this.state.selectedDevice!, - data: this.state.deviceData?.data[i]!, - uom: this.state.deviceData?.data[i + 1]!, - name: this.state.deviceData?.data[i + 2]!, - cmd: this.state.deviceData?.data[i + 3]!, - } - }); - } + sendCommand = (dv: DeviceValue) => { + this.setState({ edit_devicevalue: dv }); + }; noDevices = () => { return this.props.data.devices.length === 0; @@ -166,22 +229,41 @@ class EMSESPDevicesForm extends Component {data.devices.sort(compareDevices).map((device) => ( - this.handleRowClick(device)}> + this.handleRowClick(device)} + > - - {device.brand + " " + device.name} + + {device.brand + ' ' + device.name}{' '} + ))} @@ -191,10 +273,13 @@ class EMSESPDevicesForm extends Component - No EMS devices found. Check the connections and for possible Tx errors. + No EMS devices found. Check the connections and for possible Tx + errors. )} @@ -227,7 +312,7 @@ class EMSESPDevicesForm extends Component {sensorData.id} - {formatTemp(sensorData.temp)} + {formatValue(sensorData.temp, DeviceValueUOM.DEGREES)} ))} @@ -255,14 +340,25 @@ class EMSESPDevicesForm extends Component Confirm Scan Devices - Are you sure you want to initiate a scan on the EMS bus for all new devices? + Are you sure you want to start a scan on the EMS bus for all new + devices? - @@ -283,17 +379,17 @@ class EMSESPDevicesForm extends Component { if (response.status === 200) { - this.props.enqueueSnackbar("Device scan is starting...", { - variant: "info", + this.props.enqueueSnackbar('Device scan is starting...', { + variant: 'info' }); this.setState({ processing: false, confirmScanDevices: false }); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } }) .catch((error) => { - this.props.enqueueSnackbar(error.message || "Problem with scan", { - variant: "error", + this.props.enqueueSnackbar(error.message || 'Problem with scan', { + variant: 'error' }); this.setState({ processing: false, confirmScanDevices: false }); }); @@ -302,25 +398,26 @@ class EMSESPDevicesForm extends Component { this.setState({ selectedDevice: device.id, deviceData: undefined }); redirectingAuthorizedFetch(DEVICE_DATA_ENDPOINT, { - method: "POST", + method: 'POST', body: JSON.stringify({ id: device.id }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { - return response.json(); + return response.arrayBuffer(); } - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); }) - .then((json) => { + .then((arrayBuffer) => { + const json: any = decoder.decode(arrayBuffer); this.setState({ deviceData: json }); }) .catch((error) => { this.props.enqueueSnackbar( - error.message || "Problem getting device data", - { variant: "error" } + error.message || 'Problem getting device data', + { variant: 'error' } ); this.setState({ deviceData: undefined }); }); @@ -351,34 +448,37 @@ class EMSESPDevicesForm extends Component - - + - {deviceData.data.map((item, i) => { - if (i % 4) { - return null; - } else { - return ( - - - {deviceData.data[i + 3] && me.admin && ( - - this.sendCommand(i)}> - - - - )} - - {deviceData.data[i + 2]} - {deviceData.data[i]}{formatUnit(deviceData.data[i + 1])} - - ); - } - })} + {deviceData.data.map((item, i) => ( + + + {item.c && me.admin && ( + + this.sendCommand(item)} + > + + + + )} + + + {item.n} + + + {formatValue(item.v, item.u)} + + + ))}
@@ -390,12 +490,12 @@ class EMSESPDevicesForm extends Component )} -
+ ); } render() { - const { devicevalue } = this.state; + const { edit_devicevalue } = this.state; return (

@@ -405,26 +505,34 @@ class EMSESPDevicesForm extends Component
- } variant="contained" color="secondary" onClick={this.props.loadData} > + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh - } variant="contained" onClick={this.onScanDevices} > + } + variant="contained" + onClick={this.onScanDevices} + > Scan Devices {this.renderScanDevicesDialog()} - { - devicevalue && + {edit_devicevalue && ( - } + )}
); } diff --git a/interface/src/project/EMSESPHelp.tsx b/interface/src/project/EMSESPHelp.tsx index 52f890122..46905d542 100644 --- a/interface/src/project/EMSESPHelp.tsx +++ b/interface/src/project/EMSESPHelp.tsx @@ -1,85 +1,110 @@ import React, { Component } from 'react'; -import { Typography, Box, List, ListItem, ListItemText, Link, ListItemAvatar } from '@material-ui/core'; +import { + Typography, + Box, + List, + ListItem, + ListItemText, + Link, + ListItemAvatar +} from '@material-ui/core'; import { SectionContent } from '../components'; -import CommentIcon from "@material-ui/icons/CommentTwoTone"; -import MenuBookIcon from "@material-ui/icons/MenuBookTwoTone"; -import GitHubIcon from "@material-ui/icons/GitHub"; -import StarIcon from "@material-ui/icons/Star"; -import ImportExportIcon from "@material-ui/icons/ImportExport"; -import BugReportIcon from "@material-ui/icons/BugReportTwoTone"; +import CommentIcon from '@material-ui/icons/CommentTwoTone'; +import MenuBookIcon from '@material-ui/icons/MenuBookTwoTone'; +import GitHubIcon from '@material-ui/icons/GitHub'; +import StarIcon from '@material-ui/icons/Star'; +import ImportExportIcon from '@material-ui/icons/ImportExport'; +import BugReportIcon from '@material-ui/icons/BugReportTwoTone'; -export const WebAPISystemSettings = window.location.origin + "/api?device=system&cmd=settings"; -export const WebAPISystemInfo = window.location.origin + "/api?device=system&cmd=info"; +export const WebAPISystemSettings = + window.location.origin + '/api/system/settings'; +export const WebAPISystemInfo = window.location.origin + '/api/system/info'; class EMSESPHelp extends Component { + render() { + return ( + + + + + + + + For the latest news and updates go to the{' '} + + {'official documentation'} website + + + - render() { - return ( - + + + + + + For live community chat join our{' '} + + {'Discord'} server + + + - + + + + + + To report an issue or feature request go to{' '} + + {'click here'} + + + - - - - - - For the latest news and updates go to the {'official documentation'} website - - + + + + + + To export your system settings{' '} + + {'click here'} + + + - - - - - - For live community chat join our {'Discord'} server - - - - - - - - - To report an issue or feature request go to {'click here'} - - - - - - - - - To list your system settings {'click here'} - - - - - - - - - - To create a report of the current EMS-ESP status {'click here'} - - - - - - - - EMS-ESP is free and open-source. -

Please consider supporting this project by giving it a on our {'GitHub page'}. -
-
-

- -
- ) - } + + + + + + To export the current status of EMS-ESP{' '} + + {'click here'} + + + +
+ + + EMS-ESP is free and open-source. +

Please consider supporting this project by giving it a{' '} + on our{' '} + + {'GitHub page'} + + . +
+
+

+
+ ); + } } export default EMSESPHelp; diff --git a/interface/src/project/EMSESPSettings.tsx b/interface/src/project/EMSESPSettings.tsx index f61fa9b71..826130a2e 100644 --- a/interface/src/project/EMSESPSettings.tsx +++ b/interface/src/project/EMSESPSettings.tsx @@ -1,5 +1,5 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; @@ -10,26 +10,31 @@ import { AuthenticatedRoute } from '../authentication'; import EMSESPSettingsController from './EMSESPSettingsController'; class EMSESP extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - + - - ) + ); } - } export default EMSESP; diff --git a/interface/src/project/EMSESPSettingsController.tsx b/interface/src/project/EMSESPSettingsController.tsx index 286afd576..08a0eba45 100644 --- a/interface/src/project/EMSESPSettingsController.tsx +++ b/interface/src/project/EMSESPSettingsController.tsx @@ -1,38 +1,39 @@ -import React, { Component } from 'react'; -// import { Container } from '@material-ui/core'; +import { Component } from 'react'; import { ENDPOINT_ROOT } from '../api'; import EMSESPSettingsForm from './EMSESPSettingsForm'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { EMSESPSettings } from './EMSESPtypes'; -export const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "emsespSettings"; +export const EMSESP_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'emsespSettings'; type EMSESPSettingsControllerProps = RestControllerProps; class EMSESPSettingsController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - // - - ( - - )} - /> - - // - ) - } - + render() { + return ( + + } + /> + + ); + } } -export default restController(EMSESP_SETTINGS_ENDPOINT, EMSESPSettingsController); +export default restController( + EMSESP_SETTINGS_ENDPOINT, + EMSESPSettingsController +); diff --git a/interface/src/project/EMSESPSettingsForm.tsx b/interface/src/project/EMSESPSettingsForm.tsx index 03dace618..6045d5556 100644 --- a/interface/src/project/EMSESPSettingsForm.tsx +++ b/interface/src/project/EMSESPSettingsForm.tsx @@ -1,9 +1,10 @@ -import React from "react"; +import { Component } from 'react'; + import { ValidatorForm, TextValidator, - SelectValidator, -} from "react-material-ui-form-validator"; + SelectValidator +} from 'react-material-ui-form-validator'; import { Checkbox, @@ -12,33 +13,33 @@ import { Link, withWidth, WithWidthProps, -} from "@material-ui/core"; -import SaveIcon from "@material-ui/icons/Save"; -import MenuItem from "@material-ui/core/MenuItem"; + Grid +} from '@material-ui/core'; -import Grid from "@material-ui/core/Grid"; +import SaveIcon from '@material-ui/icons/Save'; +import MenuItem from '@material-ui/core/MenuItem'; import { redirectingAuthorizedFetch, withAuthenticatedContext, - AuthenticatedContextProps, -} from "../authentication"; + AuthenticatedContextProps +} from '../authentication'; import { RestFormProps, FormActions, FormButton, - BlockFormControlLabel, -} from "../components"; + BlockFormControlLabel +} from '../components'; -import { isIP, optional } from "../validators"; +import { isIP, optional } from '../validators'; -import { EMSESPSettings } from "./EMSESPtypes"; +import { EMSESPSettings } from './EMSESPtypes'; -import { boardProfileSelectItems } from "./EMSESPBoardProfiles"; +import { boardProfileSelectItems } from './EMSESPBoardProfiles'; -import { ENDPOINT_ROOT } from "../api"; -export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + "boardProfile"; +import { ENDPOINT_ROOT } from '../api'; +export const BOARD_PROFILE_ENDPOINT = ENDPOINT_ROOT + 'boardProfile'; type EMSESPSettingsFormProps = RestFormProps & AuthenticatedContextProps & @@ -48,40 +49,40 @@ interface EMSESPSettingsFormState { processing: boolean; } -class EMSESPSettingsForm extends React.Component { +class EMSESPSettingsForm extends Component { state: EMSESPSettingsFormState = { - processing: false, + processing: false }; componentDidMount() { - ValidatorForm.addValidationRule("isOptionalIP", optional(isIP)); + ValidatorForm.addValidationRule('isOptionalIP', optional(isIP)); } changeBoardProfile = (event: React.ChangeEvent) => { const { data, setData } = this.props; setData({ ...data, - board_profile: event.target.value, + board_profile: event.target.value }); - if (event.target.value === "CUSTOM") return; + if (event.target.value === 'CUSTOM') return; this.setState({ processing: true }); redirectingAuthorizedFetch(BOARD_PROFILE_ENDPOINT, { - method: "POST", + method: 'POST', body: JSON.stringify({ code: event.target.value }), headers: { - "Content-Type": "application/json", - }, + 'Content-Type': 'application/json' + } }) .then((response) => { if (response.status === 200) { return response.json(); } - throw Error("Unexpected response code: " + response.status); + throw Error('Unexpected response code: ' + response.status); }) .then((json) => { - this.props.enqueueSnackbar("Profile loaded", { variant: "success" }); + this.props.enqueueSnackbar('Profile loaded', { variant: 'success' }); setData({ ...data, led_gpio: json.led_gpio, @@ -89,14 +90,14 @@ class EMSESPSettingsForm extends React.Component { rx_gpio: json.rx_gpio, tx_gpio: json.tx_gpio, pbutton_gpio: json.pbutton_gpio, - board_profile: event.target.value, + board_profile: event.target.value }); this.setState({ processing: false }); }) .catch((error) => { this.props.enqueueSnackbar( - error.message || "Problem fetching board profile", - { variant: "warning" } + error.message || 'Problem fetching board profile', + { variant: 'warning' } ); this.setState({ processing: false }); }); @@ -108,13 +109,13 @@ class EMSESPSettingsForm extends React.Component { - Adjust any of the EMS-ESP settings here. For help refer to the{" "} + Adjust any of the EMS-ESP settings here. For help refer to the{' '} - {"online documentation"} + {'online documentation'} . @@ -139,7 +140,7 @@ class EMSESPSettingsForm extends React.Component { value={data.tx_mode} fullWidth variant="outlined" - onChange={handleValueChange("tx_mode")} + onChange={handleValueChange('tx_mode')} margin="normal" > Off @@ -156,7 +157,7 @@ class EMSESPSettingsForm extends React.Component { value={data.ems_bus_id} fullWidth variant="outlined" - onChange={handleValueChange("ems_bus_id")} + onChange={handleValueChange('ems_bus_id')} margin="normal" > Service Key (0x0B) @@ -169,16 +170,16 @@ class EMSESPSettingsForm extends React.Component { { variant="outlined" value={data.tx_delay} type="number" - onChange={handleValueChange("tx_delay")} + onChange={handleValueChange('tx_delay')} margin="normal" /> @@ -216,12 +217,12 @@ class EMSESPSettingsForm extends React.Component { margin="normal" > {boardProfileSelectItems()} - + Custom... - {data.board_profile === "CUSTOM" && ( + {data.board_profile === 'CUSTOM' && ( { { variant="outlined" value={data.rx_gpio} type="number" - onChange={handleValueChange("rx_gpio")} + onChange={handleValueChange('rx_gpio')} margin="normal" /> { variant="outlined" value={data.tx_gpio} type="number" - onChange={handleValueChange("tx_gpio")} + onChange={handleValueChange('tx_gpio')} margin="normal" /> { variant="outlined" value={data.pbutton_gpio} type="number" - onChange={handleValueChange("pbutton_gpio")} + onChange={handleValueChange('pbutton_gpio')} margin="normal" /> { variant="outlined" value={data.dallas_gpio} type="number" - onChange={handleValueChange("dallas_gpio")} + onChange={handleValueChange('dallas_gpio')} margin="normal" /> { variant="outlined" value={data.led_gpio} type="number" - onChange={handleValueChange("led_gpio")} + onChange={handleValueChange('led_gpio')} margin="normal" /> @@ -372,7 +373,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -385,7 +386,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -396,18 +397,18 @@ class EMSESPSettingsForm extends React.Component { } - label="Enable API write commands" + label="Bypass Access Token authorization on API calls" /> } @@ -424,7 +425,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -434,7 +435,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -451,7 +452,7 @@ class EMSESPSettingsForm extends React.Component { control={ } @@ -468,30 +469,30 @@ class EMSESPSettingsForm extends React.Component { > { variant="outlined" value={data.syslog_port} type="number" - onChange={handleValueChange("syslog_port")} + onChange={handleValueChange('syslog_port')} margin="normal" /> @@ -510,7 +511,7 @@ class EMSESPSettingsForm extends React.Component { value={data.syslog_level} fullWidth variant="outlined" - onChange={handleValueChange("syslog_level")} + onChange={handleValueChange('syslog_level')} margin="normal" > OFF @@ -524,16 +525,16 @@ class EMSESPSettingsForm extends React.Component { { variant="outlined" value={data.syslog_mark_interval} type="number" - onChange={handleValueChange("syslog_mark_interval")} + onChange={handleValueChange('syslog_mark_interval')} margin="normal" /> @@ -549,7 +550,7 @@ class EMSESPSettingsForm extends React.Component { control={ } diff --git a/interface/src/project/EMSESPStatus.ts b/interface/src/project/EMSESPStatus.ts index b60d9887f..b018ff6cc 100644 --- a/interface/src/project/EMSESPStatus.ts +++ b/interface/src/project/EMSESPStatus.ts @@ -1,10 +1,10 @@ import { Theme } from '@material-ui/core'; import { EMSESPStatus, busConnectionStatus } from './EMSESPtypes'; -export const isConnected = ({ status }: EMSESPStatus) => status !== busConnectionStatus.BUS_STATUS_OFFLINE; +export const isConnected = ({ status }: EMSESPStatus) => + status !== busConnectionStatus.BUS_STATUS_OFFLINE; export const busStatusHighlight = ({ status }: EMSESPStatus, theme: Theme) => { - switch (status) { case busConnectionStatus.BUS_STATUS_TX_ERRORS: return theme.palette.warning.main; @@ -15,26 +15,25 @@ export const busStatusHighlight = ({ status }: EMSESPStatus, theme: Theme) => { default: return theme.palette.warning.main; } -} +}; export const busStatus = ({ status }: EMSESPStatus) => { switch (status) { case busConnectionStatus.BUS_STATUS_CONNECTED: - return "Connected"; + return 'Connected'; case busConnectionStatus.BUS_STATUS_TX_ERRORS: - return "Tx Errors"; + return 'Tx Errors'; case busConnectionStatus.BUS_STATUS_OFFLINE: - return "Disconnected"; + return 'Disconnected'; default: - return "Unknown"; + return 'Unknown'; } -} +}; -export const qualityHighlight = ( value: number, theme: Theme) => { +export const qualityHighlight = (value: number, theme: Theme) => { if (value >= 95) { return theme.palette.success.main; } return theme.palette.error.main; -} - +}; diff --git a/interface/src/project/EMSESPStatusController.tsx b/interface/src/project/EMSESPStatusController.tsx index 0b8293b6f..837b8db3b 100644 --- a/interface/src/project/EMSESPStatusController.tsx +++ b/interface/src/project/EMSESPStatusController.tsx @@ -1,30 +1,34 @@ import React, { Component } from 'react'; -import { restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { ENDPOINT_ROOT } from '../api'; import EMSESPStatusForm from './EMSESPStatusForm'; import { EMSESPStatus } from './EMSESPtypes'; -export const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + "emsespStatus"; +export const EMSESP_STATUS_ENDPOINT = ENDPOINT_ROOT + 'emsespStatus'; type EMSESPStatusControllerProps = RestControllerProps; class EMSESPStatusController extends Component { + componentDidMount() { + this.props.loadData(); + } - componentDidMount() { - this.props.loadData(); - } - - render() { - return ( - - } - /> - - ) - } + render() { + return ( + + } + /> + + ); + } } export default restController(EMSESP_STATUS_ENDPOINT, EMSESPStatusController); diff --git a/interface/src/project/EMSESPStatusForm.tsx b/interface/src/project/EMSESPStatusForm.tsx index a080e3e63..909024bcb 100644 --- a/interface/src/project/EMSESPStatusForm.tsx +++ b/interface/src/project/EMSESPStatusForm.tsx @@ -1,6 +1,6 @@ -import React, { Component, Fragment } from "react"; +import React, { Component, Fragment } from 'react'; -import { WithTheme, withTheme } from "@material-ui/core/styles"; +import { WithTheme, withTheme } from '@material-ui/core/styles'; import { TableContainer, Table, @@ -13,35 +13,32 @@ import { ListItemText, withWidth, WithWidthProps, - isWidthDown, -} from "@material-ui/core"; + isWidthDown +} from '@material-ui/core'; -import RefreshIcon from "@material-ui/icons/Refresh"; -import DeviceHubIcon from "@material-ui/icons/DeviceHub"; +import RefreshIcon from '@material-ui/icons/Refresh'; +import DeviceHubIcon from '@material-ui/icons/DeviceHub'; import { RestFormProps, FormActions, FormButton, - HighlightAvatar, -} from "../components"; + HighlightAvatar +} from '../components'; -import { - busStatus, - busStatusHighlight, - isConnected, -} from "./EMSESPStatus"; +import { busStatus, busStatusHighlight, isConnected } from './EMSESPStatus'; -import { EMSESPStatus } from "./EMSESPtypes"; +import { EMSESPStatus } from './EMSESPtypes'; function formatNumber(num: number) { return new Intl.NumberFormat().format(num); } -type EMSESPStatusFormProps = RestFormProps & WithTheme & WithWidthProps; +type EMSESPStatusFormProps = RestFormProps & + WithTheme & + WithWidthProps; class EMSESPStatusForm extends Component { - createListItems() { const { data, theme, width } = this.props; return ( @@ -52,24 +49,30 @@ class EMSESPStatusForm extends Component { - + {isConnected(data) && ( - +
- - # Telegrams Received - - {formatNumber(data.rx_received)} (quality {data.rx_quality}%) + # Telegrams Received + + {formatNumber(data.rx_received)} (quality{' '} + {data.rx_quality}%) - - # Telegrams Sent - - {formatNumber(data.tx_sent)} (quality {data.tx_quality}%) + # Telegrams Sent + + {formatNumber(data.tx_sent)} (quality {data.tx_quality} + %) @@ -86,7 +89,11 @@ class EMSESPStatusForm extends Component { {this.createListItems()} } variant="contained" color="secondary" onClick={this.props.loadData}> + startIcon={} + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh diff --git a/interface/src/project/EMSESPtypes.ts b/interface/src/project/EMSESPtypes.ts index ef0a47567..ffd37c132 100644 --- a/interface/src/project/EMSESPtypes.ts +++ b/interface/src/project/EMSESPtypes.ts @@ -16,7 +16,7 @@ export interface EMSESPSettings { dallas_parasite: boolean; led_gpio: number; hide_led: boolean; - api_enabled: boolean; + notoken_api: boolean; analog_enabled: boolean; pbutton_gpio: number; trace_raw: boolean; @@ -58,15 +58,54 @@ export interface EMSESPDevices { sensors: Sensor[]; } -export interface EMSESPDeviceData { - name: string; - data: string[]; +export interface DeviceValue { + v: any; + u: number; + n: string; + c: string; } -export interface DeviceValue { - id: number; - data: string, - uom: string, - name: string, - cmd: string +export interface EMSESPDeviceData { + name: string; + data: DeviceValue[]; } + +export enum DeviceValueUOM { + NONE = 0, + DEGREES, + PERCENT, + LMIN, + KWH, + WH, + HOURS, + MINUTES, + UA, + BAR, + KW, + W, + KB, + SECONDS, + DBM, + NUM, + BOOLEAN +} + +export const DeviceValueUOM_s = [ + '', + '°C', + '%', + 'l/min', + 'kWh', + 'Wh', + 'hours', + 'minutes', + 'uA', + 'bar', + 'kW', + 'W', + 'KB', + 'seconds', + 'dBm', + 'number', + 'on/off' +]; diff --git a/interface/src/project/ProjectMenu.tsx b/interface/src/project/ProjectMenu.tsx index 7adc934bd..844c60664 100644 --- a/interface/src/project/ProjectMenu.tsx +++ b/interface/src/project/ProjectMenu.tsx @@ -1,12 +1,15 @@ -import React, { Component } from "react"; -import { Link, withRouter, RouteComponentProps } from "react-router-dom"; +import { Component } from 'react'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -import { List, ListItem, ListItemIcon, ListItemText } from "@material-ui/core"; +import { List, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'; import TuneIcon from '@material-ui/icons/Tune'; -import DashboardIcon from "@material-ui/icons/Dashboard"; +import DashboardIcon from '@material-ui/icons/Dashboard'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; type ProjectProps = AuthenticatedContextProps & RouteComponentProps; @@ -16,17 +19,32 @@ class ProjectMenu extends Component { const path = this.props.match.url; return ( - + - + - + ); diff --git a/interface/src/project/ProjectRouting.tsx b/interface/src/project/ProjectRouting.tsx index 489642dad..4ab78ad2c 100644 --- a/interface/src/project/ProjectRouting.tsx +++ b/interface/src/project/ProjectRouting.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { Redirect, Switch } from 'react-router'; import { AuthenticatedRoute } from '../authentication'; @@ -7,24 +7,32 @@ import EMSESPDashboard from './EMSESPDashboard'; import EMSESPSettings from './EMSESPSettings'; class ProjectRouting extends Component { - render() { return ( - - - - { - /* - * The redirect below caters for the default project route and redirecting invalid paths. - * The "to" property must match one of the routes above for this to work correctly. - */ - } + + + + {/* + * The redirect below caters for the default project route and redirecting invalid paths. + * The "to" property must match one of the routes above for this to work correctly. + */} - ) + ); } - } export default ProjectRouting; diff --git a/interface/src/project/ValueForm.tsx b/interface/src/project/ValueForm.tsx index 2da7e5d94..3200a543b 100644 --- a/interface/src/project/ValueForm.tsx +++ b/interface/src/project/ValueForm.tsx @@ -1,64 +1,121 @@ import React, { RefObject } from 'react'; -import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, Typography } from '@material-ui/core'; +import { ValidatorForm } from 'react-material-ui-form-validator'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + Typography, + FormHelperText, + OutlinedInput, + InputAdornment, + TextField, + MenuItem +} from '@material-ui/core'; + import { FormButton } from '../components'; -import { DeviceValue } from './EMSESPtypes'; +import { DeviceValue, DeviceValueUOM, DeviceValueUOM_s } from './EMSESPtypes'; interface ValueFormProps { - devicevalue: DeviceValue; - onDoneEditing: () => void; - onCancelEditing: () => void; - handleValueChange: (data: keyof DeviceValue) => (event: React.ChangeEvent) => void; + devicevalue: DeviceValue; + onDoneEditing: () => void; + onCancelEditing: () => void; + handleValueChange: ( + data: keyof DeviceValue + ) => (event: React.ChangeEvent) => void; } class ValueForm extends React.Component { + formRef: RefObject = React.createRef(); - formRef: RefObject = React.createRef(); + submit = () => { + this.formRef.current.submit(); + }; - submit = () => { - this.formRef.current.submit(); - } + render() { + const { + devicevalue, + handleValueChange, + onDoneEditing, + onCancelEditing + } = this.props; - buildLabel = (devicevalue: DeviceValue) => { - if ((devicevalue.uom === "") || (!devicevalue.uom)) { - return "New value"; - } - return "New value (" + devicevalue.uom + ")"; - } - - render() { - const { devicevalue, handleValueChange, onDoneEditing, onCancelEditing } = this.props; - return ( - - - Change the {devicevalue.name} - - - - - Note: it may take a few seconds before the change is visible. If nothing happens check the logs. - - - - - - Cancel - Done - - - - ); - } + return ( + + + Change Value + + {devicevalue.u !== DeviceValueUOM.BOOLEAN && ( + + {DeviceValueUOM_s[devicevalue.u]} + + } + aria-describedby="outlined-value-helper-text" + inputProps={{ + 'aria-label': 'value' + }} + /> + )} + {devicevalue.u === DeviceValueUOM.BOOLEAN && ( + + on + off + + )} + + {devicevalue.n} + + + + + Note: it may take a few seconds before the change is + registered with the EMS device. + + + + + + + Cancel + + + Done + + + + + ); + } } export default ValueForm; diff --git a/interface/src/security/GenerateToken.tsx b/interface/src/security/GenerateToken.tsx index f2f483e13..5904c25b8 100644 --- a/interface/src/security/GenerateToken.tsx +++ b/interface/src/security/GenerateToken.tsx @@ -1,5 +1,14 @@ import React, { Fragment } from 'react'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Box, LinearProgress, Typography, TextField } from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + LinearProgress, + Typography, + TextField +} from '@material-ui/core'; import { FormButton } from '../components'; import { redirectingAuthorizedFetch } from '../authentication'; @@ -7,71 +16,105 @@ import { GENERATE_TOKEN_ENDPOINT } from '../api'; import { withSnackbar, WithSnackbarProps } from 'notistack'; interface GenerateTokenProps extends WithSnackbarProps { - username: string; - onClose: () => void; + username: string; + onClose: () => void; } interface GenerateTokenState { - token?: string; + token?: string; } -class GenerateToken extends React.Component { +class GenerateToken extends React.Component< + GenerateTokenProps, + GenerateTokenState +> { + state: GenerateTokenState = {}; - state: GenerateTokenState = {}; - - componentDidMount() { - const { username } = this.props; - redirectingAuthorizedFetch(GENERATE_TOKEN_ENDPOINT + "?" + new URLSearchParams({ username }), { method: 'GET' }) - .then(response => { - if (response.status === 200) { - return response.json(); - } else { - throw Error("Error generating token: " + response.status); - } - }).then(generatedToken => { - console.log(generatedToken); - this.setState({ token: generatedToken.token }); - }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem generating token", { variant: 'error' }); - }); - } - - render() { - const { onClose, username } = this.props; - const { token } = this.state; - return ( - - Token for: {username} - - {token ? - - - - The token below may be used to access the secured APIs. This may be used for bearer authentication with the "Authorization" header or using the "access_token" query paramater. - - - - - - - : - - - - Generating token… - - - } - - - - Close - - - + componentDidMount() { + const { username } = this.props; + redirectingAuthorizedFetch( + GENERATE_TOKEN_ENDPOINT + '?' + new URLSearchParams({ username }), + { method: 'GET' } + ) + .then((response) => { + if (response.status === 200) { + return response.json(); + } else { + throw Error('Error generating token: ' + response.status); + } + }) + .then((generatedToken) => { + // console.log(generatedToken); + this.setState({ token: generatedToken.token }); + }) + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem generating token', + { variant: 'error' } ); - } + }); + } + + render() { + const { onClose, username } = this.props; + const { token } = this.state; + return ( + + + Token for: {username} + + + {token ? ( + + + + The token below may be used to access the secured APIs, either + as a Bearer authentication in the "Authorization" header or + using the "access_token" query parameter. + + + + + + + ) : ( + + + Generating token… + + )} + + + + Close + + + + ); + } } export default withSnackbar(GenerateToken); diff --git a/interface/src/security/ManageUsersController.tsx b/interface/src/security/ManageUsersController.tsx index ccd3cde90..02ed43da6 100644 --- a/interface/src/security/ManageUsersController.tsx +++ b/interface/src/security/ManageUsersController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SECURITY_SETTINGS_ENDPOINT } from '../api'; import ManageUsersForm from './ManageUsersForm'; @@ -9,7 +14,6 @@ import { SecuritySettings } from './types'; type ManageUsersControllerProps = RestControllerProps; class ManageUsersController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,14 @@ class ManageUsersController extends Component { } + render={(formProps) => } /> - ) + ); } - } -export default restController(SECURITY_SETTINGS_ENDPOINT, ManageUsersController); +export default restController( + SECURITY_SETTINGS_ENDPOINT, + ManageUsersController +); diff --git a/interface/src/security/ManageUsersForm.tsx b/interface/src/security/ManageUsersForm.tsx index e861f7136..4f0ff084b 100644 --- a/interface/src/security/ManageUsersForm.tsx +++ b/interface/src/security/ManageUsersForm.tsx @@ -1,8 +1,18 @@ import React, { Fragment } from 'react'; import { ValidatorForm } from 'react-material-ui-form-validator'; -import { Table, TableBody, TableCell, TableHead, TableFooter, TableRow, withWidth, WithWidthProps, isWidthDown } from '@material-ui/core'; -import { Box, Button, Typography, } from '@material-ui/core'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableFooter, + TableRow, + withWidth, + WithWidthProps, + isWidthDown +} from '@material-ui/core'; +import { Box, Button, Typography } from '@material-ui/core'; import EditIcon from '@material-ui/icons/Edit'; import DeleteIcon from '@material-ui/icons/Delete'; @@ -13,8 +23,16 @@ import SaveIcon from '@material-ui/icons/Save'; import PersonAddIcon from '@material-ui/icons/PersonAdd'; import VpnKeyIcon from '@material-ui/icons/VpnKey'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; -import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; +import { + RestFormProps, + FormActions, + FormButton, + extractEventValue +} from '../components'; import UserForm from './UserForm'; import { SecuritySettings, User } from './types'; @@ -30,16 +48,20 @@ function compareUsers(a: User, b: User) { return 0; } -type ManageUsersFormProps = RestFormProps & AuthenticatedContextProps & WithWidthProps; +type ManageUsersFormProps = RestFormProps & + AuthenticatedContextProps & + WithWidthProps; type ManageUsersFormState = { creating: boolean; user?: User; generateTokenFor?: string; -} - -class ManageUsersForm extends React.Component { +}; +class ManageUsersForm extends React.Component< + ManageUsersFormProps, + ManageUsersFormState +> { state: ManageUsersFormState = { creating: false }; @@ -48,38 +70,38 @@ class ManageUsersForm extends React.Component { - return !this.props.data.users.find(u => u.username === username); - } + return !this.props.data.users.find((u) => u.username === username); + }; noAdminConfigured = () => { - return !this.props.data.users.find(u => u.admin); - } + return !this.props.data.users.find((u) => u.admin); + }; removeUser = (user: User) => { const { data } = this.props; - const users = data.users.filter(u => u.username !== user.username); + const users = data.users.filter((u) => u.username !== user.username); this.props.setData({ ...data, users }); - } + }; closeGenerateToken = () => { this.setState({ generateTokenFor: undefined }); - } + }; generateToken = (user: User) => { this.setState({ generateTokenFor: user.username }); - } + }; startEditingUser = (user: User) => { this.setState({ @@ -92,13 +114,13 @@ class ManageUsersForm extends React.Component { const { user } = this.state; if (user) { const { data } = this.props; - const users = data.users.filter(u => u.username !== user.username); + const users = data.users.filter((u) => u.username !== user.username); users.push(user); this.props.setData({ ...data, users }); this.setState({ @@ -107,14 +129,18 @@ class ManageUsersForm extends React.Component (event: React.ChangeEvent) => { - this.setState({ user: { ...this.state.user!, [name]: extractEventValue(event) } }); + handleUserValueChange = (name: keyof User) => ( + event: React.ChangeEvent + ) => { + this.setState({ + user: { ...this.state.user!, [name]: extractEventValue(event) } + }); }; onSubmit = () => { this.props.saveData(); this.props.authenticatedContext.refresh(); - } + }; render() { const { width, data } = this.props; @@ -122,7 +148,10 @@ class ManageUsersForm extends React.Component -
+
Username @@ -131,7 +160,7 @@ class ManageUsersForm extends React.Component - {data.users.sort(compareUsers).map(user => ( + {data.users.sort(compareUsers).map((user) => ( {user.username} @@ -140,51 +169,79 @@ class ManageUsersForm extends React.Component : } - this.generateToken(user)}> + this.generateToken(user)} + > - this.removeUser(user)}> + this.removeUser(user)} + > - this.startEditingUser(user)}> + this.startEditingUser(user)} + > ))} - + -
- { - this.noAdminConfigured() && - ( - - - You must have at least one admin user configured. - - - ) - } + {this.noAdminConfigured() && ( + + + You must have at least one admin user configured. + + + )} - } variant="contained" color="primary" type="submit" disabled={this.noAdminConfigured()}> + } + variant="contained" + color="primary" + type="submit" + disabled={this.noAdminConfigured()} + > Save
- { - generateTokenFor && - } - { - user && + {generateTokenFor && ( + + )} + {user && ( - } + )} ); } - } export default withAuthenticatedContext(withWidth()(ManageUsersForm)); diff --git a/interface/src/security/Security.tsx b/interface/src/security/Security.tsx index 4e99769b6..cab47a709 100644 --- a/interface/src/security/Security.tsx +++ b/interface/src/security/Security.tsx @@ -1,9 +1,12 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; -import { AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import ManageUsersController from './ManageUsersController'; @@ -12,25 +15,36 @@ import SecuritySettingsController from './SecuritySettingsController'; type SecurityProps = AuthenticatedContextProps & RouteComponentProps; class Security extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; render() { return ( - + this.handleTabChange(path)} + variant="fullWidth" + > - - + + - ) + ); } } diff --git a/interface/src/security/SecuritySettingsController.tsx b/interface/src/security/SecuritySettingsController.tsx index d42328a7d..e98885011 100644 --- a/interface/src/security/SecuritySettingsController.tsx +++ b/interface/src/security/SecuritySettingsController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SECURITY_SETTINGS_ENDPOINT } from '../api'; import SecuritySettingsForm from './SecuritySettingsForm'; @@ -9,7 +14,6 @@ import { SecuritySettings } from './types'; type SecuritySettingsControllerProps = RestControllerProps; class SecuritySettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,14 @@ class SecuritySettingsController extends Component } + render={(formProps) => } /> ); } - } -export default restController(SECURITY_SETTINGS_ENDPOINT, SecuritySettingsController); +export default restController( + SECURITY_SETTINGS_ENDPOINT, + SecuritySettingsController +); diff --git a/interface/src/security/SecuritySettingsForm.tsx b/interface/src/security/SecuritySettingsForm.tsx index 2c9d12779..fa770f8ce 100644 --- a/interface/src/security/SecuritySettingsForm.tsx +++ b/interface/src/security/SecuritySettingsForm.tsx @@ -4,19 +4,27 @@ import { ValidatorForm } from 'react-material-ui-form-validator'; import { Box, Typography } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; -import { RestFormProps, PasswordValidator, FormActions, FormButton } from '../components'; +import { + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; +import { + RestFormProps, + PasswordValidator, + FormActions, + FormButton +} from '../components'; import { SecuritySettings } from './types'; -type SecuritySettingsFormProps = RestFormProps & AuthenticatedContextProps; +type SecuritySettingsFormProps = RestFormProps & + AuthenticatedContextProps; class SecuritySettingsForm extends React.Component { - onSubmit = () => { this.props.saveData(); this.props.authenticatedContext.refresh(); - } + }; render() { const { data, handleValueChange } = this.props; @@ -24,7 +32,10 @@ class SecuritySettingsForm extends React.Component { { onChange={handleValueChange('jwt_secret')} margin="normal" /> - + - The Super User password is used to sign authentication tokens and is also the Console's `su` password. If you modify this all users will be signed out. + The Super User password is used to sign authentication tokens and is + also the Console's `su` password. If you modify this all users will + be signed out. - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save ); } - } export default withAuthenticatedContext(SecuritySettingsForm); diff --git a/interface/src/security/UserForm.tsx b/interface/src/security/UserForm.tsx index dd16674de..db7d140a5 100644 --- a/interface/src/security/UserForm.tsx +++ b/interface/src/security/UserForm.tsx @@ -1,9 +1,19 @@ import React, { RefObject } from 'react'; import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; -import { Dialog, DialogTitle, DialogContent, DialogActions, Checkbox } from '@material-ui/core'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Checkbox +} from '@material-ui/core'; -import { PasswordValidator, BlockFormControlLabel, FormButton } from '../components'; +import { + PasswordValidator, + BlockFormControlLabel, + FormButton +} from '../components'; import { User } from './types'; @@ -11,33 +21,67 @@ interface UserFormProps { creating: boolean; user: User; uniqueUsername: (value: any) => boolean; - handleValueChange: (name: keyof User) => (event: React.ChangeEvent) => void; + handleValueChange: ( + name: keyof User + ) => (event: React.ChangeEvent) => void; onDoneEditing: () => void; onCancelEditing: () => void; } class UserForm extends React.Component { - formRef: RefObject = React.createRef(); componentDidMount() { - ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername); + ValidatorForm.addValidationRule( + 'uniqueUsername', + this.props.uniqueUsername + ); } submit = () => { this.formRef.current.submit(); - } + }; render() { - const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props; + const { + user, + creating, + handleValueChange, + onDoneEditing, + onCancelEditing + } = this.props; return ( -

- {creating ? 'Add' : 'Modify'} User + + + {creating ? 'Add' : 'Modify'} User + { /> { /> - + Cancel - + Done diff --git a/interface/src/serviceWorker.ts b/interface/src/serviceWorker.ts index d5f0275a7..d9941b314 100644 --- a/interface/src/serviceWorker.ts +++ b/interface/src/serviceWorker.ts @@ -28,10 +28,7 @@ type Config = { export function register(config?: Config) { if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL( - process.env.PUBLIC_URL, - window.location.href - ); + const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); if (publicUrl.origin !== window.location.origin) { // Our service worker won't work if PUBLIC_URL is on a different origin // from what our page is served on. This might happen if a CDN is used to @@ -65,7 +62,7 @@ export function register(config?: Config) { function registerValidSW(swUrl: string, config?: Config) { navigator.serviceWorker .register(swUrl) - .then(registration => { + .then((registration) => { registration.onupdatefound = () => { const installingWorker = registration.installing; if (installingWorker == null) { @@ -101,7 +98,7 @@ function registerValidSW(swUrl: string, config?: Config) { }; }; }) - .catch(error => { + .catch((error) => { console.error('Error during service worker registration:', error); }); } @@ -111,7 +108,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { fetch(swUrl, { headers: { 'Service-Worker': 'script' } }) - .then(response => { + .then((response) => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); if ( @@ -119,7 +116,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { (contentType != null && contentType.indexOf('javascript') === -1) ) { // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister().then(() => { window.location.reload(); }); @@ -138,7 +135,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) { export function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { + navigator.serviceWorker.ready.then((registration) => { registration.unregister(); }); } diff --git a/interface/src/setupProxy.js b/interface/src/setupProxy.js index af9f42516..5a8e4ac3d 100644 --- a/interface/src/setupProxy.js +++ b/interface/src/setupProxy.js @@ -1,12 +1,21 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); module.exports = function (app) { - app.use( - '/rest/*', - createProxyMiddleware({ - target: 'http://localhost:3080', - secure: false, - changeOrigin: true - }) - ); + app.use( + '/rest/*', + createProxyMiddleware({ + target: 'http://localhost:3080', + secure: false, + changeOrigin: true + }) + ); + + app.use( + '/es/*', + createProxyMiddleware({ + target: 'http://localhost:3090', + secure: false, + changeOrigin: true + }) + ); }; diff --git a/interface/src/system/LogEventConsole.tsx b/interface/src/system/LogEventConsole.tsx new file mode 100644 index 000000000..19a96bb77 --- /dev/null +++ b/interface/src/system/LogEventConsole.tsx @@ -0,0 +1,130 @@ +import { FC } from 'react'; + +import { LogEvent, LogLevel } from './types'; +import { Theme, makeStyles, Box } from '@material-ui/core'; +import { useWindowSize } from '../components'; + +interface LogEventConsoleProps { + events: LogEvent[]; +} + +interface Offsets { + topOffset: () => number; + leftOffset: () => number; +} + +const topOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().bottom || 0; + +const leftOffset = () => + document.getElementById('log-window')?.getBoundingClientRect().left || 0; + +const useStyles = makeStyles((theme: Theme) => ({ + console: { + padding: theme.spacing(1), + position: 'absolute', + left: (offsets: Offsets) => offsets.leftOffset(), + right: 24, + top: (offsets: Offsets) => offsets.topOffset(), + bottom: 24, + backgroundColor: 'black', + overflow: 'auto' + }, + entry: { + color: '#bbbbbb', + fontFamily: 'Courier New, monospace', + fontSize: '13px', + letterSpacing: 'normal', + whiteSpace: 'nowrap' + }, + debug: { + color: '#00FFFF' + }, + trace: { + color: '#00FFFF' + }, + info: { + color: '#ffff00' + }, + notice: { + color: '#ffff00' + }, + error: { + color: '#ff0000' + }, + warning: { + color: '#ff0000' + }, + default: { + color: '#ffffff' + } +})); + +const LogEventConsole: FC = (props) => { + useWindowSize(); + const classes = useStyles({ topOffset, leftOffset }); + const { events } = props; + + const styleLevel = (level: LogLevel) => { + switch (level) { + case LogLevel.DEBUG: + return classes.debug; + case LogLevel.TRACE: + return classes.trace; + case LogLevel.INFO: + return classes.info; + case LogLevel.NOTICE: + return classes.notice; + case LogLevel.WARNING: + return classes.warning; + case LogLevel.ERROR: + return classes.error; + default: + return classes.default; + } + }; + + const levelLabel = (level: LogLevel) => { + switch (level) { + case LogLevel.ERROR: + return 'ERROR'; + case LogLevel.WARNING: + return 'WARNING'; + case LogLevel.NOTICE: + return 'NOTICE'; + case LogLevel.INFO: + return 'INFO'; + case LogLevel.DEBUG: + return 'DEBUG'; + case LogLevel.TRACE: + return 'TRACE'; + default: + return ''; + } + }; + + const paddedLevelLabel = (level: LogLevel) => { + const label = levelLabel(level); + return label.padStart(8, '\xa0'); + }; + + const paddedNameLabel = (name: string) => { + const label = '[' + name + ']'; + return label.padStart(8, '\xa0'); + }; + + return ( + + {events.map((e) => ( +
+ {e.t} + {paddedLevelLabel(e.l)} + {paddedNameLabel(e.n)} + {e.m} +
+ ))} +
+ ); +}; + +export default LogEventConsole; diff --git a/interface/src/system/LogEventController.tsx b/interface/src/system/LogEventController.tsx new file mode 100644 index 000000000..cdeffafb1 --- /dev/null +++ b/interface/src/system/LogEventController.tsx @@ -0,0 +1,118 @@ +import { Component } from 'react'; + +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; + +import { addAccessTokenParameter } from '../authentication'; + +import { ENDPOINT_ROOT, EVENT_SOURCE_ROOT } from '../api'; +export const FETCH_LOG_ENDPOINT = ENDPOINT_ROOT + 'fetchLog'; +export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings'; + +export const LOG_EVENT_EVENT_SOURCE_URL = EVENT_SOURCE_ROOT + 'log'; + +import LogEventForm from './LogEventForm'; +import LogEventConsole from './LogEventConsole'; + +import { LogEvent, LogSettings } from './types'; + +import { Decoder } from '@msgpack/msgpack'; +const decoder = new Decoder(); + +interface LogEventControllerState { + eventSource?: EventSource; + events: LogEvent[]; +} + +type LogEventControllerProps = RestControllerProps; + +class LogEventController extends Component< + LogEventControllerProps, + LogEventControllerState +> { + eventSource?: EventSource; + reconnectTimeout?: NodeJS.Timeout; + + constructor(props: LogEventControllerProps) { + super(props); + this.state = { + events: [] + }; + } + + componentDidMount() { + this.props.loadData(); + this.fetchLog(); + this.configureEventSource(); + } + + componentWillUnmount() { + if (this.eventSource) { + this.eventSource.close(); + } + if (this.reconnectTimeout) { + clearTimeout(this.reconnectTimeout); + } + } + + fetchLog = () => { + fetch(FETCH_LOG_ENDPOINT) + .then((response) => { + if (response.status === 200) { + return response.arrayBuffer(); + } else { + throw Error('Unexpected status code: ' + response.status); + } + }) + .then((arrayBuffer) => { + const json: any = decoder.decode(arrayBuffer); + this.setState({ events: json.events }); + }) + .catch((error) => { + this.setState({ events: [] }); + throw Error('Unexpected error: ' + error); + }); + }; + + configureEventSource = () => { + this.eventSource = new EventSource( + addAccessTokenParameter(LOG_EVENT_EVENT_SOURCE_URL) + ); + this.eventSource.onmessage = this.onMessage; + this.eventSource.onerror = this.onError; + }; + + onError = () => { + if (this.eventSource && this.reconnectTimeout) { + this.eventSource.close(); + this.eventSource = undefined; + this.reconnectTimeout = setTimeout(this.configureEventSource, 1000); + } + }; + + onMessage = (event: MessageEvent) => { + const rawData = event.data; + if (typeof rawData === 'string' || rawData instanceof String) { + const event = JSON.parse(rawData as string) as LogEvent; + this.setState((state) => ({ events: [...state.events, event] })); + } + }; + + render() { + return ( + + } + /> + + + ); + } +} + +export default restController(LOG_SETTINGS_ENDPOINT, LogEventController); diff --git a/interface/src/system/LogEventForm.tsx b/interface/src/system/LogEventForm.tsx new file mode 100644 index 000000000..16de6ba0e --- /dev/null +++ b/interface/src/system/LogEventForm.tsx @@ -0,0 +1,107 @@ +import { Component } from 'react'; + +import { + ValidatorForm, + SelectValidator +} from 'react-material-ui-form-validator'; + +import { Typography, Grid } from '@material-ui/core'; + +import MenuItem from '@material-ui/core/MenuItem'; + +import { + redirectingAuthorizedFetch, + withAuthenticatedContext, + AuthenticatedContextProps +} from '../authentication'; + +import { RestFormProps } from '../components'; +import { LogSettings } from './types'; + +import { ENDPOINT_ROOT } from '../api'; +export const LOG_SETTINGS_ENDPOINT = ENDPOINT_ROOT + 'logSettings'; + +type LogEventFormProps = AuthenticatedContextProps & RestFormProps; + +class LogEventForm extends Component { + changeLevel = (event: React.ChangeEvent) => { + const { data, setData } = this.props; + setData({ + ...data, + level: parseInt(event.target.value) + }); + + redirectingAuthorizedFetch(LOG_SETTINGS_ENDPOINT, { + method: 'POST', + body: JSON.stringify({ level: event.target.value }), + headers: { + 'Content-Type': 'application/json' + } + }) + .then((response) => { + if (response.status === 200) { + return response.json(); + } + throw Error('Unexpected response code: ' + response.status); + }) + .then((json) => { + this.props.enqueueSnackbar('Log settings changed', { + variant: 'success' + }); + setData({ + ...data, + level: json.level + }); + }) + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem changing log settings', + { variant: 'warning' } + ); + }); + }; + + render() { + const { data, saveData } = this.props; + return ( + + + + + OFF + ERROR + WARNING + NOTICE + INFO + DEBUG + ALL + + + + + + (the last {data.max_messages} messages are buffered and new log + events are shown in real time) + + + + + + ); + } +} + +export default withAuthenticatedContext(LogEventForm); diff --git a/interface/src/system/OTASettingsController.tsx b/interface/src/system/OTASettingsController.tsx index 2f683b304..b18f5ce5a 100644 --- a/interface/src/system/OTASettingsController.tsx +++ b/interface/src/system/OTASettingsController.tsx @@ -1,6 +1,11 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { OTA_SETTINGS_ENDPOINT } from '../api'; import OTASettingsForm from './OTASettingsForm'; @@ -9,7 +14,6 @@ import { OTASettings } from './types'; type OTASettingsControllerProps = RestControllerProps; class OTASettingsController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class OTASettingsController extends Component { } + render={(formProps) => } /> ); } - } export default restController(OTA_SETTINGS_ENDPOINT, OTASettingsController); diff --git a/interface/src/system/OTASettingsForm.tsx b/interface/src/system/OTASettingsForm.tsx index 1e88ad6df..394eba4a8 100644 --- a/interface/src/system/OTASettingsForm.tsx +++ b/interface/src/system/OTASettingsForm.tsx @@ -4,7 +4,14 @@ import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator'; import { Checkbox } from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; -import { RestFormProps, BlockFormControlLabel, PasswordValidator, FormButton, FormActions } from '../components'; +import { + RestFormProps, + BlockFormControlLabel, + PasswordValidator, + FormButton, + FormActions +} from '../components'; + import { isIP, isHostname, or } from '../validators'; import { OTASettings } from './types'; @@ -12,7 +19,6 @@ import { OTASettings } from './types'; type OTASettingsFormProps = RestFormProps; class OTASettingsForm extends React.Component { - componentDidMount() { ValidatorForm.addValidationRule('isIPOrHostname', or(isIP, isHostname)); } @@ -25,14 +31,24 @@ class OTASettingsForm extends React.Component { control={ } label="Enable OTA Updates" /> { /> { margin="normal" /> - } variant="contained" color="primary" type="submit"> + } + variant="contained" + color="primary" + type="submit" + > Save diff --git a/interface/src/system/System.tsx b/interface/src/system/System.tsx index 671d5e073..e9bdf0394 100644 --- a/interface/src/system/System.tsx +++ b/interface/src/system/System.tsx @@ -1,22 +1,28 @@ -import React, { Component } from 'react'; -import { Redirect, Switch, RouteComponentProps } from 'react-router-dom' +import { Component } from 'react'; +import { Redirect, Switch, RouteComponentProps } from 'react-router-dom'; import { Tabs, Tab } from '@material-ui/core'; import { WithFeaturesProps, withFeatures } from '../features/FeaturesContext'; -import { withAuthenticatedContext, AuthenticatedContextProps, AuthenticatedRoute } from '../authentication'; +import { + withAuthenticatedContext, + AuthenticatedContextProps, + AuthenticatedRoute +} from '../authentication'; import { MenuAppBar } from '../components'; import SystemStatusController from './SystemStatusController'; import OTASettingsController from './OTASettingsController'; import UploadFirmwareController from './UploadFirmwareController'; +import LogEventController from './LogEventController'; -type SystemProps = AuthenticatedContextProps & RouteComponentProps & WithFeaturesProps; +type SystemProps = AuthenticatedContextProps & + RouteComponentProps & + WithFeaturesProps; class System extends Component { - - handleTabChange = (event: React.ChangeEvent<{}>, path: string) => { + handleTabChange = (path: string) => { this.props.history.push(path); }; @@ -24,27 +30,57 @@ class System extends Component { const { authenticatedContext, features } = this.props; return ( - + this.handleTabChange(path)} + variant="fullWidth" + > + {features.ota && ( - + )} {features.upload_firmware && ( - + )} - + + {features.ota && ( - + )} {features.upload_firmware && ( - + )} - ) + ); } } diff --git a/interface/src/system/SystemStatusController.tsx b/interface/src/system/SystemStatusController.tsx index abee0b571..4d77ef1ab 100644 --- a/interface/src/system/SystemStatusController.tsx +++ b/interface/src/system/SystemStatusController.tsx @@ -1,6 +1,11 @@ import React, { Component } from 'react'; -import {restController, RestControllerProps, RestFormLoader, SectionContent } from '../components'; +import { + restController, + RestControllerProps, + RestFormLoader, + SectionContent +} from '../components'; import { SYSTEM_STATUS_ENDPOINT } from '../api'; import SystemStatusForm from './SystemStatusForm'; @@ -9,7 +14,6 @@ import { SystemStatus } from './types'; type SystemStatusControllerProps = RestControllerProps; class SystemStatusController extends Component { - componentDidMount() { this.props.loadData(); } @@ -19,12 +23,11 @@ class SystemStatusController extends Component { } + render={(formProps) => } /> ); } - } export default restController(SYSTEM_STATUS_ENDPOINT, SystemStatusController); diff --git a/interface/src/system/SystemStatusForm.tsx b/interface/src/system/SystemStatusForm.tsx index ad59fabf3..1fa18647b 100644 --- a/interface/src/system/SystemStatusForm.tsx +++ b/interface/src/system/SystemStatusForm.tsx @@ -1,7 +1,21 @@ import React, { Component, Fragment } from 'react'; -import { Avatar, Button, Divider, Dialog, DialogTitle, DialogContent, DialogActions, Box } from '@material-ui/core'; -import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'; +import { + Avatar, + Button, + Divider, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box +} from '@material-ui/core'; +import { + List, + ListItem, + ListItemAvatar, + ListItemText +} from '@material-ui/core'; import DevicesIcon from '@material-ui/icons/Devices'; import MemoryIcon from '@material-ui/icons/Memory'; @@ -11,9 +25,14 @@ import AppsIcon from '@material-ui/icons/Apps'; import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'; import RefreshIcon from '@material-ui/icons/Refresh'; import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore'; -import TimerIcon from "@material-ui/icons/Timer"; +import TimerIcon from '@material-ui/icons/Timer'; +import BuildIcon from '@material-ui/icons/Build'; -import { redirectingAuthorizedFetch, AuthenticatedContextProps, withAuthenticatedContext } from '../authentication'; +import { + redirectingAuthorizedFetch, + AuthenticatedContextProps, + withAuthenticatedContext +} from '../authentication'; import { RestFormProps, FormButton, ErrorButton } from '../components'; import { FACTORY_RESET_ENDPOINT, RESTART_ENDPOINT } from '../api'; @@ -25,31 +44,48 @@ interface SystemStatusFormState { processing: boolean; } -type SystemStatusFormProps = AuthenticatedContextProps & RestFormProps; +type SystemStatusFormProps = AuthenticatedContextProps & + RestFormProps; function formatNumber(num: number) { return new Intl.NumberFormat().format(num); } -class SystemStatusForm extends Component { - +class SystemStatusForm extends Component< + SystemStatusFormProps, + SystemStatusFormState +> { state: SystemStatusFormState = { confirmRestart: false, confirmFactoryReset: false, processing: false - } + }; createListItems() { - const { data } = this.props + const { data } = this.props; return ( - + + + + + + + + + - + @@ -61,45 +97,76 @@ class SystemStatusForm extends Component - + - + - + - + - { - (data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0) && ( - - - - - - - - - - - ) - } + {data.esp_platform === EspPlatform.ESP32 && data.psram_size > 0 && ( + + + + + + + + + + + + )} - + - + @@ -119,41 +186,57 @@ class SystemStatusForm extends Component - -
- ) + ); } onRestart = () => { this.setState({ confirmRestart: true }); - } + }; onRestartRejected = () => { this.setState({ confirmRestart: false }); - } + }; onRestartConfirmed = () => { this.setState({ processing: true }); redirectingAuthorizedFetch(RESTART_ENDPOINT, { method: 'POST' }) - .then(response => { + .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Device is restarting", { variant: 'info' }); + this.props.enqueueSnackbar('Device is restarting', { + variant: 'info' + }); this.setState({ processing: false, confirmRestart: false }); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem restarting device", { variant: 'error' }); + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem restarting device', + { variant: 'error' } + ); this.setState({ processing: false, confirmRestart: false }); }); - } + }; renderFactoryResetDialog() { return ( @@ -168,72 +251,98 @@ class SystemStatusForm extends Component - - } variant="contained" onClick={this.onFactoryResetConfirmed} disabled={this.state.processing} autoFocus> + } + variant="contained" + onClick={this.onFactoryResetConfirmed} + disabled={this.state.processing} + autoFocus + > Factory Reset
- ) + ); } onFactoryReset = () => { this.setState({ confirmFactoryReset: true }); - } + }; onFactoryResetRejected = () => { this.setState({ confirmFactoryReset: false }); - } + }; onFactoryResetConfirmed = () => { this.setState({ processing: true }); redirectingAuthorizedFetch(FACTORY_RESET_ENDPOINT, { method: 'POST' }) - .then(response => { + .then((response) => { if (response.status === 200) { - this.props.enqueueSnackbar("Factory reset in progress.", { variant: 'error' }); + this.props.enqueueSnackbar('Factory reset in progress.', { + variant: 'error' + }); this.setState({ processing: false, confirmFactoryReset: false }); } else { - throw Error("Invalid status code: " + response.status); + throw Error('Invalid status code: ' + response.status); } }) - .catch(error => { - this.props.enqueueSnackbar(error.message || "Problem factory resetting device", { variant: 'error' }); + .catch((error) => { + this.props.enqueueSnackbar( + error.message || 'Problem factory resetting device', + { variant: 'error' } + ); this.setState({ processing: false, confirmRestart: false }); }); - } + }; render() { const me = this.props.authenticatedContext.me; return ( - - {this.createListItems()} - + {this.createListItems()} - } variant="contained" color="secondary" onClick={this.props.loadData}> + } + variant="contained" + color="secondary" + onClick={this.props.loadData} + > Refresh - {me.admin && + {me.admin && ( - } variant="contained" color="primary" onClick={this.onRestart}> + } + variant="contained" + color="primary" + onClick={this.onRestart} + > Restart - } variant="contained" onClick={this.onFactoryReset}> + } + variant="contained" + onClick={this.onFactoryReset} + > Factory reset - } + )} {this.renderRestartDialog()} {this.renderFactoryResetDialog()} ); } - } export default withAuthenticatedContext(SystemStatusForm); diff --git a/interface/src/system/UploadFirmwareController.tsx b/interface/src/system/UploadFirmwareController.tsx index 16d21ffb4..931ae9eaf 100644 --- a/interface/src/system/UploadFirmwareController.tsx +++ b/interface/src/system/UploadFirmwareController.tsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import { Component } from 'react'; import { SectionContent } from '../components'; import { UPLOAD_FIRMWARE_ENDPOINT } from '../api'; @@ -12,8 +12,10 @@ interface UploadFirmwareControllerState { progress?: ProgressEvent; } -class UploadFirmwareController extends Component { - +class UploadFirmwareController extends Component< + WithSnackbarProps, + UploadFirmwareControllerState +> { state: UploadFirmwareControllerState = { xhr: undefined, progress: undefined @@ -25,47 +27,67 @@ class UploadFirmwareController extends Component { this.setState({ progress }); - } + }; uploadFile = (file: File) => { if (this.state.xhr) { return; } - var xhr = new XMLHttpRequest(); + const xhr = new XMLHttpRequest(); this.setState({ xhr }); - redirectingAuthorizedUpload(xhr, UPLOAD_FIRMWARE_ENDPOINT, file, this.updateProgress).then(() => { - if (xhr.status !== 200) { - throw Error("Invalid status code: " + xhr.status); - } - this.props.enqueueSnackbar("Activating new firmware", { variant: 'success' }); - this.setState({ xhr: undefined, progress: undefined }); - }).catch((error: Error) => { - if (error.name === 'AbortError') { - this.props.enqueueSnackbar("Upload cancelled by user", { variant: 'warning' }); - } else { - const errorMessage = error.name === 'UploadError' ? "Error during upload" : (error.message || "Unknown error"); - this.props.enqueueSnackbar("Problem uploading: " + errorMessage, { variant: 'error' }); + redirectingAuthorizedUpload( + xhr, + UPLOAD_FIRMWARE_ENDPOINT, + file, + this.updateProgress + ) + .then(() => { + if (xhr.status !== 200) { + throw Error('Invalid status code: ' + xhr.status); + } + this.props.enqueueSnackbar('Activating new firmware', { + variant: 'success' + }); this.setState({ xhr: undefined, progress: undefined }); - } - }); - } + }) + .catch((error: Error) => { + if (error.name === 'AbortError') { + this.props.enqueueSnackbar('Upload cancelled by user', { + variant: 'warning' + }); + } else { + const errorMessage = + error.name === 'UploadError' + ? 'Error during upload' + : error.message || 'Unknown error'; + this.props.enqueueSnackbar('Problem uploading: ' + errorMessage, { + variant: 'error' + }); + this.setState({ xhr: undefined, progress: undefined }); + } + }); + }; cancelUpload = () => { if (this.state.xhr) { this.state.xhr.abort(); this.setState({ xhr: undefined, progress: undefined }); } - } + }; render() { const { xhr, progress } = this.state; return ( - + ); } - } export default withSnackbar(UploadFirmwareController); diff --git a/interface/src/system/UploadFirmwareForm.tsx b/interface/src/system/UploadFirmwareForm.tsx index 0b9fade75..cb3e16073 100644 --- a/interface/src/system/UploadFirmwareForm.tsx +++ b/interface/src/system/UploadFirmwareForm.tsx @@ -1,6 +1,6 @@ -import React, { Fragment } from "react"; -import { SingleUpload } from "../components"; -import { Box } from "@material-ui/core"; +import React, { Fragment } from 'react'; +import { SingleUpload } from '../components'; +import { Box } from '@material-ui/core'; interface UploadFirmwareFormProps { uploading: boolean; @@ -22,8 +22,10 @@ class UploadFirmwareForm extends React.Component { return ( - Upload a new firmware file (.bin or .bin.gz) below to replace the existing firmware. -

This can take up to a minute. Wait until you see "Activating new Firmware" and EMS-ESP will then automatically restart. + Upload a new firmware file (.bin or .bin.gz) below to replace the + existing firmware. +

This can take up to a minute. Wait until you see "Activating + new firmware" and EMS-ESP will then automatically restart.
boolean) => (value: any) => !value || validator(value); +const OPTIONAL = (validator: (value: any) => boolean) => (value: any) => + !value || validator(value); export default OPTIONAL; diff --git a/interface/src/validators/or.ts b/interface/src/validators/or.ts index a57d2cd75..eb189e65b 100644 --- a/interface/src/validators/or.ts +++ b/interface/src/validators/or.ts @@ -1,6 +1,8 @@ -const OR = (validator1: (value: any) => boolean, validator2: (value: any) => boolean) => { - return (value: any) => validator1(value) || validator2(value); +const OR = ( + validator1: (value: any) => boolean, + validator2: (value: any) => boolean +) => { + return (value: any) => validator1(value) || validator2(value); }; export default OR; - diff --git a/interface/tsconfig.json b/interface/tsconfig.json index e18c413eb..cc47c36d4 100644 --- a/interface/tsconfig.json +++ b/interface/tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,7 +16,5 @@ "jsx": "react-jsx", "noFallthroughCasesInSwitch": true }, - "include": [ - "src" - ] + "include": ["src"] } diff --git a/lib/ArduinoJson/ArduinoJson.h b/lib/ArduinoJson/ArduinoJson.h index b5d887ab9..ba79dc8e3 100644 --- a/lib/ArduinoJson/ArduinoJson.h +++ b/lib/ArduinoJson/ArduinoJson.h @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/CHANGELOG.md b/lib/ArduinoJson/CHANGELOG.md index 53c8f010e..f3866b3c8 100644 --- a/lib/ArduinoJson/CHANGELOG.md +++ b/lib/ArduinoJson/CHANGELOG.md @@ -1,9 +1,11 @@ ArduinoJson: change log ======================= -HEAD ----- +v6.18.0 (2021-05-05) +------- +* Added support for custom converters (issue #687) +* Added support for `Printable` (issue #1444) * Removed support for `char` values, see below (issue #1498) * `deserializeJson()` leaves `\uXXXX` unchanged instead of returning `NotSupported` * `deserializeMsgPack()` inserts `null` instead of returning `NotSupported` @@ -11,9 +13,17 @@ HEAD * Added `JsonVariant::is()` (issue #1412) * Added `JsonVariant::is()` (issue #1412) * Changed `JsonVariantConst::is()` to return `false` (issue #1412) +* Simplified `JsonVariant::as()` to always return `T` (see below) +* Updated folders list in `.mbedignore` (PR #1515 by @AGlass0fMilk) +* Fixed member-call-on-null-pointer in `getMember()` when array is empty +* `serializeMsgPack(doc, buffer, size)` doesn't add null-terminator anymore (issue #1545) +* `serializeJson(doc, buffer, size)` adds null-terminator only if there is enough room +* PlatformIO: set `build.libArchive` to `false` (PR #1550 by @askreet) > ### BREAKING CHANGES > +> #### Support for `char` removed +> > We cannot cast a `JsonVariant` to a `char` anymore, so the following will break: > ```c++ > char age = doc["age"]; // error: no matching function for call to 'variantAs(VariantData*&)' @@ -33,6 +43,31 @@ HEAD > int8_t age; > doc["age"] = age; // OK > ``` +> A deprecation warning with the message "Support for `char` is deprecated, use `int8_t` or `uint8_t` instead" was added to allow a smooth transition. +> +> #### `as()` always returns `T` +> +> Previously, `JsonVariant::as()` could return a type different from `T`. +> The most common example is `as()` that returned a `const char*`. +> While this feature simplified a few use cases, it was confusing and complicated the +> implementation of custom converters. +> +> Starting from this version, `as` doesn't try to auto-correct the return type and always return `T`, +> which means that you cannot write this anymore: +> +> ```c++ +> Serial.println(doc["sensor"].as()); // error: invalid conversion from 'const char*' to 'char*' [-fpermissive] +> ``` +> +> Instead, you must write: +> +> ```c++ +> Serial.println(doc["sensor"].as()); // OK +> ``` +> +> A deprecation warning with the message "Replace `as()` with `as()`" was added to allow a smooth transition. +> +> #### `DeserializationError::NotSupported` removed > > On a different topic, `DeserializationError::NotSupported` has been removed. > Instead of returning this error: @@ -40,7 +75,9 @@ HEAD > * `deserializeJson()` leaves `\uXXXX` unchanged (only when `ARDUINOJSON_DECODE_UNICODE` is `0`) > * `deserializeMsgPack()` replaces unsupported values with `null`s > -> Lastly, a very minor change conserns `JsonVariantConst::is()`. +> #### Const-aware `is()` +> +> Lastly, a very minor change concerns `JsonVariantConst::is()`. > It used to return `true` for `JsonArray` and `JsonOject`, but now it returns `false`. > Instead, you must use `JsonArrayConst` and `JsonObjectConst`. diff --git a/lib/ArduinoJson/README.md b/lib/ArduinoJson/README.md index 5b5206c7c..c30028435 100644 --- a/lib/ArduinoJson/README.md +++ b/lib/ArduinoJson/README.md @@ -2,7 +2,7 @@ --- -[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.17.3)](https://www.ardu-badge.com/ArduinoJson/6.17.3) +[![arduino-library-badge](https://www.ardu-badge.com/badge/ArduinoJson.svg?version=6.18.0)](https://www.ardu-badge.com/ArduinoJson/6.18.0) [![Continuous Integration](https://github.com/bblanchon/ArduinoJson/workflows/Continuous%20Integration/badge.svg?branch=6.x)](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A6.x) [![Continuous Integration](https://ci.appveyor.com/api/projects/status/m7s53wav1l0abssg/branch/6.x?svg=true)](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/6.x) [![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/arduinojson.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) @@ -34,10 +34,11 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things). * Deduplicates strings * Versatile * [Supports custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme) - * Supports [Arduino's `String`](https://arduinojson.org/v6/api/config/enable_arduino_string/) and [STL's `std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme) - * Supports Arduino's `Stream` and [STL's `std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/?utm_source=github&utm_medium=readme) + * Supports [Arduino's `String`](https://arduinojson.org/v6/api/config/enable_arduino_string/?utm_source=github&utm_medium=readme) and [STL's `std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme) + * Supports [Arduino's `Stream`](https://arduinojson.org/v6/api/config/enable_arduino_stream/?utm_source=github&utm_medium=readme) and [STL's `std::istream`/`std::ostream`](https://arduinojson.org/v6/api/config/enable_std_stream/?utm_source=github&utm_medium=readme) * [Supports Flash strings](https://arduinojson.org/v6/api/config/enable_progmem/?utm_source=github&utm_medium=readme) * Supports [custom readers](https://arduinojson.org/v6/api/json/deserializejson/?utm_source=github&utm_medium=readme#custom-reader) and [custom writers](https://arduinojson.org/v6/api/json/serializejson/?utm_source=github&utm_medium=readme#custom-writer) + * Supports custom converters * Portable * Usable on any C++ project (not limited to Arduino) * Compatible with C++98 @@ -86,7 +87,7 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things). * [How-tos](https://arduinojson.org/v6/example/?utm_source=github&utm_medium=readme) * [FAQ](https://arduinojson.org/v6/faq/?utm_source=github&utm_medium=readme) * [Book](https://arduinojson.org/book/?utm_source=github&utm_medium=readme) - * [Changelog](changelog.md) + * [Changelog](CHANGELOG.md) * Vibrant user community * Most popular of all Arduino libraries on [GitHub](https://github.com/search?o=desc&q=arduino+library&s=stars&type=Repositories) and [PlatformIO](https://platformio.org/lib/search) * [Used in hundreds of projects](https://www.hackster.io/search?i=projects&q=arduinojson) diff --git a/lib/ArduinoJson/src/ArduinoJson.h b/lib/ArduinoJson/src/ArduinoJson.h index e6e2b2425..2984c2379 100644 --- a/lib/ArduinoJson/src/ArduinoJson.h +++ b/lib/ArduinoJson/src/ArduinoJson.h @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson.hpp b/lib/ArduinoJson/src/ArduinoJson.hpp index 52b4f3be9..28d42eef5 100644 --- a/lib/ArduinoJson/src/ArduinoJson.hpp +++ b/lib/ArduinoJson/src/ArduinoJson.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -27,7 +27,7 @@ #include "ArduinoJson/Collection/CollectionImpl.hpp" #include "ArduinoJson/Object/MemberProxy.hpp" #include "ArduinoJson/Object/ObjectImpl.hpp" -#include "ArduinoJson/Variant/VariantAsImpl.hpp" +#include "ArduinoJson/Variant/ConverterImpl.hpp" #include "ArduinoJson/Variant/VariantCompare.hpp" #include "ArduinoJson/Variant/VariantImpl.hpp" diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp index 219be6a74..e7cdc4c5c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayFunctions.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp index e4e41978c..ae06b2046 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayImpl.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp index b465aa489..fcacc6b6a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp index b92107c18..a991db0d7 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -164,4 +164,42 @@ class ArrayRef : public ArrayRefBase, private: MemoryPool* _pool; }; + +template <> +struct Converter { + static bool toJson(VariantConstRef src, VariantRef dst) { + return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ArrayConstRef fromJson(VariantConstRef src) { + return ArrayConstRef(variantAsArray(getData(src))); + } + + static bool checkJson(VariantConstRef src) { + const VariantData* data = getData(src); + return data && data->isArray(); + } +}; + +template <> +struct Converter { + static bool toJson(VariantConstRef src, VariantRef dst) { + return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ArrayRef fromJson(VariantRef src) { + VariantData* data = getData(src); + MemoryPool* pool = getPool(src); + return ArrayRef(pool, data != 0 ? data->asArray() : 0); + } + + static bool checkJson(VariantConstRef) { + return false; + } + + static bool checkJson(VariantRef src) { + VariantData* data = getData(src); + return data && data->isArray(); + } +}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp index e39043aa1..fd26d04ae 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ArrayShortcuts.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -9,6 +9,8 @@ namespace ARDUINOJSON_NAMESPACE { // Forward declarations. +class ArrayRef; +class ObjectRef; template class ElementProxy; diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp index 6e19d01c2..c6062e492 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -65,10 +65,18 @@ class ElementProxy : public VariantOperators >, } template - FORCE_INLINE typename VariantAs::type as() const { + FORCE_INLINE typename enable_if::value, T>::type as() + const { return getUpstreamElement().template as(); } + template + FORCE_INLINE typename enable_if::value, const char*>::type + ARDUINOJSON_DEPRECATED("Replace as() with as()") + as() const { + return as(); + } + template FORCE_INLINE operator T() const { return getUpstreamElement(); @@ -170,6 +178,10 @@ class ElementProxy : public VariantOperators >, return _array.getOrAddElement(_index); } + friend bool convertToJson(const this_type& src, VariantRef dst) { + return dst.set(src.getUpstreamElement()); + } + TArray _array; const size_t _index; }; diff --git a/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp b/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp index 6deba8fe3..619b91d8d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Array/Utilities.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -76,7 +76,8 @@ class ArrayCopier1D : public Visitor { VariantSlot* slot = array.head(); while (slot != 0 && size < _capacity) { - _destination[size++] = variantAs(slot->data()); + _destination[size++] = + Converter::fromJson(VariantConstRef(slot->data())); slot = slot->next(); } return size; diff --git a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp index 630c2d48e..d2bca45f0 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionData.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp index 51d167790..49a24beed 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -115,6 +115,8 @@ inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const { } inline VariantSlot* CollectionData::getSlot(size_t index) const { + if (!_head) + return 0; return _head->next(index); } diff --git a/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp b/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp index 3c517369b..0c0d4c489 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Configuration.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp index de6b7dedf..7b617111a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/DeserializationError.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp index 9f50f6e12..7ea3078dc 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Filter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp index 88223efdd..06964b43e 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/NestingLimit.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp index 77ca27430..e965c8256 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Reader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp index f72ad5fa2..724638f92 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStreamReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp index b76ce8c71..71571d416 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/ArduinoStringReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp index 0d41f7dd0..7eca134d7 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/FlashReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp index 47cfb9250..37c3c317d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/IteratorReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp index 8f577a000..67cf6825b 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/RamReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp index 44ccdddc8..eebaa2cc0 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/StdStreamReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp index f9c8fb19b..e56e26264 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/Readers/VariantReader.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp b/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp index 045fbcbd8..23295421c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Deserialization/deserialize.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp b/lib/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp index 4ab738973..5c85d8a78 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Document/BasicJsonDocument.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp b/lib/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp index d6c328fd3..de6f41131 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Document/DynamicJsonDocument.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp b/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp index 6ac1ea2d3..d67d93496 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Document/JsonDocument.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -21,12 +21,12 @@ class JsonDocument : public Visitable { } template - typename VariantAs::type as() { + T as() { return getVariant().template as(); } template - typename VariantConstAs::type as() const { + T as() const { return getVariant().template as(); } @@ -70,7 +70,7 @@ class JsonDocument : public Visitable { } bool set(const JsonDocument& src) { - return to().set(src.as()); + return to().set(src.as()); } template @@ -337,4 +337,8 @@ class JsonDocument : public Visitable { JsonDocument& operator=(const JsonDocument&); }; +inline bool convertToJson(const JsonDocument& src, VariantRef dst) { + return dst.set(src.as()); +} + } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp b/lib/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp index be204b550..fbbadd43a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Document/StaticJsonDocument.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp index 7e35bd11b..811e82522 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/EscapeSequence.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp index c20cf4ca8..1a07be866 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/JsonDeserializer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp index a3fe4e24e..5cb9aa270 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/JsonSerializer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -14,6 +14,8 @@ namespace ARDUINOJSON_NAMESPACE { template class JsonSerializer : public Visitor { public: + static const bool producesText = true; + JsonSerializer(TWriter writer) : _formatter(writer) {} FORCE_INLINE size_t visitArray(const CollectionData &array) { @@ -71,13 +73,13 @@ class JsonSerializer : public Visitor { return bytesWritten(); } - size_t visitNegativeInteger(UInt value) { - _formatter.writeNegativeInteger(value); + size_t visitSignedInteger(Integer value) { + _formatter.writeInteger(value); return bytesWritten(); } - size_t visitPositiveInteger(UInt value) { - _formatter.writePositiveInteger(value); + size_t visitUnsignedInteger(UInt value) { + _formatter.writeInteger(value); return bytesWritten(); } diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp index a5d042766..aef1fe368 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/Latch.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp index 3b1b919dc..dbb0c1723 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/PrettyJsonSerializer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -16,7 +16,7 @@ class PrettyJsonSerializer : public JsonSerializer { typedef JsonSerializer base; public: - PrettyJsonSerializer(TWriter &writer) : base(writer), _nesting(0) {} + PrettyJsonSerializer(TWriter writer) : base(writer), _nesting(0) {} size_t visitArray(const CollectionData &array) { VariantSlot *slot = array.head(); diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp index 114e45563..18694f14e 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/TextFormatter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace ARDUINOJSON_NAMESPACE { @@ -75,28 +76,31 @@ class TextFormatter { FloatParts parts(value); - writePositiveInteger(parts.integral); + writeInteger(parts.integral); if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces); - if (parts.exponent < 0) { - writeRaw("e-"); - writePositiveInteger(-parts.exponent); - } - - if (parts.exponent > 0) { + if (parts.exponent) { writeRaw('e'); - writePositiveInteger(parts.exponent); + writeInteger(parts.exponent); } } - void writeNegativeInteger(UInt value) { - writeRaw('-'); - writePositiveInteger(value); - } - template - void writePositiveInteger(T value) { + typename enable_if::value>::type writeInteger(T value) { + typedef typename make_unsigned::type unsigned_type; + unsigned_type unsigned_value; + if (value < 0) { + writeRaw('-'); + unsigned_value = unsigned_type(unsigned_type(~value) + 1); + } else { + unsigned_value = unsigned_type(value); + } + writeInteger(unsigned_value); + } + + template + typename enable_if::value>::type writeInteger(T value) { char buffer[22]; char *end = buffer + sizeof(buffer); char *begin = end; diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp index ae10d4d23..4e2750f3b 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/Utf16.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp b/lib/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp index 4f4bc63e6..a30f77a82 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Json/Utf8.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp b/lib/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp index 9d91e83e9..bf1679879 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Memory/Alignment.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp b/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp index 60459eb48..49debf856 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Memory/MemoryPool.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp b/lib/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp index 97408ee0a..30173bf86 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Misc/SerializedValue.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Misc/Visitable.hpp b/lib/ArduinoJson/src/ArduinoJson/Misc/Visitable.hpp index 8dba09a66..f25d12f2b 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Misc/Visitable.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Misc/Visitable.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index ec011846f..8988173e3 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -15,478 +15,468 @@ namespace ARDUINOJSON_NAMESPACE { template class MsgPackDeserializer { - public: - MsgPackDeserializer(MemoryPool &pool, TReader reader, - TStringStorage stringStorage) - : _pool(&pool), - _reader(reader), - _stringStorage(stringStorage), - _error(DeserializationError::Ok), - _foundSomething(false) {} + public: + MsgPackDeserializer(MemoryPool & pool, TReader reader, TStringStorage stringStorage) + : _pool(&pool) + , _reader(reader) + , _stringStorage(stringStorage) + , _error(DeserializationError::Ok) + , _foundSomething(false) { + } - template - DeserializationError parse(VariantData &variant, TFilter filter, - NestingLimit nestingLimit) { - parseVariant(variant, filter, nestingLimit); - return _foundSomething ? _error : DeserializationError::EmptyInput; - } + template + DeserializationError parse(VariantData & variant, TFilter filter, NestingLimit nestingLimit) { + parseVariant(&variant, filter, nestingLimit); + return _foundSomething ? _error : DeserializationError::EmptyInput; + } - private: - // Prevent VS warning "assignment operator could not be generated" - MsgPackDeserializer &operator=(const MsgPackDeserializer &); + private: + // Prevent VS warning "assignment operator could not be generated" + MsgPackDeserializer & operator=(const MsgPackDeserializer &); - bool invalidInput() { - _error = DeserializationError::InvalidInput; - return false; - } + bool invalidInput() { + _error = DeserializationError::InvalidInput; + return false; + } - template - bool parseVariant(VariantData &variant, TFilter filter, - NestingLimit nestingLimit) { - uint8_t code = 0; - if (!readByte(code)) - return false; + template + bool parseVariant(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { + uint8_t code = 0; + if (!readByte(code)) + return false; - _foundSomething = true; + _foundSomething = true; - bool allowValue = filter.allowValue(); + bool allowValue = filter.allowValue(); - switch (code) { - case 0xc0: - // already null - return true; + switch (code) { + case 0xc0: + // already null + return true; - case 0xc1: - return invalidInput(); + case 0xc1: + return invalidInput(); - case 0xc2: - if (allowValue) - variant.setBoolean(false); - return true; + case 0xc2: + if (allowValue) + variant->setBoolean(false); + return true; - case 0xc3: - if (allowValue) - variant.setBoolean(true); - return true; + case 0xc3: + if (allowValue) + variant->setBoolean(true); + return true; - case 0xc4: // bin 8 (not supported) - return skipString(); + case 0xc4: // bin 8 (not supported) + return skipString(); - case 0xc5: // bin 16 (not supported) - return skipString(); + case 0xc5: // bin 16 (not supported) + return skipString(); - case 0xc6: // bin 32 (not supported) - return skipString(); + case 0xc6: // bin 32 (not supported) + return skipString(); - case 0xc7: // ext 8 (not supported) - return skipExt(); + case 0xc7: // ext 8 (not supported) + return skipExt(); - case 0xc8: // ext 16 (not supported) - return skipExt(); + case 0xc8: // ext 16 (not supported) + return skipExt(); - case 0xc9: // ext 32 (not supported) - return skipExt(); + case 0xc9: // ext 32 (not supported) + return skipExt(); - case 0xca: - if (allowValue) - return readFloat(variant); - else - return skipBytes(4); + case 0xca: + if (allowValue) + return readFloat(variant); + else + return skipBytes(4); - case 0xcb: - if (allowValue) - return readDouble(variant); - else - return skipBytes(8); + case 0xcb: + if (allowValue) + return readDouble(variant); + else + return skipBytes(8); - case 0xcc: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); + case 0xcc: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); - case 0xcd: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); + case 0xcd: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); - case 0xce: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); + case 0xce: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); - case 0xcf: + case 0xcf: #if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); #else - return skipBytes(8); // not supported + return skipBytes(8); // not supported #endif - case 0xd0: - if (allowValue) - return readInteger(variant); - else - return skipBytes(1); + case 0xd0: + if (allowValue) + return readInteger(variant); + else + return skipBytes(1); - case 0xd1: - if (allowValue) - return readInteger(variant); - else - return skipBytes(2); + case 0xd1: + if (allowValue) + return readInteger(variant); + else + return skipBytes(2); - case 0xd2: - if (allowValue) - return readInteger(variant); - else - return skipBytes(4); + case 0xd2: + if (allowValue) + return readInteger(variant); + else + return skipBytes(4); - case 0xd3: + case 0xd3: #if ARDUINOJSON_USE_LONG_LONG - if (allowValue) - return readInteger(variant); - else - return skipBytes(8); // not supported + if (allowValue) + return readInteger(variant); + else + return skipBytes(8); // not supported #else - return skipBytes(8); + return skipBytes(8); #endif - case 0xd4: // fixext 1 (not supported) - return skipBytes(2); + case 0xd4: // fixext 1 (not supported) + return skipBytes(2); - case 0xd5: // fixext 2 (not supported) - return skipBytes(3); + case 0xd5: // fixext 2 (not supported) + return skipBytes(3); - case 0xd6: // fixext 4 (not supported) - return skipBytes(5); + case 0xd6: // fixext 4 (not supported) + return skipBytes(5); - case 0xd7: // fixext 8 (not supported) - return skipBytes(9); + case 0xd7: // fixext 8 (not supported) + return skipBytes(9); - case 0xd8: // fixext 16 (not supported) - return skipBytes(17); + case 0xd8: // fixext 16 (not supported) + return skipBytes(17); + + case 0xd9: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xda: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdb: + if (allowValue) + return readString(variant); + else + return skipString(); + + case 0xdc: + return readArray(variant, filter, nestingLimit); + + case 0xdd: + return readArray(variant, filter, nestingLimit); + + case 0xde: + return readObject(variant, filter, nestingLimit); + + case 0xdf: + return readObject(variant, filter, nestingLimit); + } + + switch (code & 0xf0) { + case 0x80: + return readObject(variant, code & 0x0F, filter, nestingLimit); + + case 0x90: + return readArray(variant, code & 0x0F, filter, nestingLimit); + } + + if ((code & 0xe0) == 0xa0) { + if (allowValue) + return readString(variant, code & 0x1f); + else + return skipBytes(code & 0x1f); + } - case 0xd9: if (allowValue) - return readString(variant); - else - return skipString(); + variant->setInteger(static_cast(code)); - case 0xda: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdb: - if (allowValue) - return readString(variant); - else - return skipString(); - - case 0xdc: - return readArray(variant, filter, nestingLimit); - - case 0xdd: - return readArray(variant, filter, nestingLimit); - - case 0xde: - return readObject(variant, filter, nestingLimit); - - case 0xdf: - return readObject(variant, filter, nestingLimit); + return true; } - switch (code & 0xf0) { - case 0x80: - return readObject(variant, code & 0x0F, filter, nestingLimit); - - case 0x90: - return readArray(variant, code & 0x0F, filter, nestingLimit); + bool readByte(uint8_t & value) { + int c = _reader.read(); + if (c < 0) { + _error = DeserializationError::IncompleteInput; + return false; + } + value = static_cast(c); + return true; } - if ((code & 0xe0) == 0xa0) { - if (allowValue) - return readString(variant, code & 0x1f); - else - return skipBytes(code & 0x1f); - } - - if (allowValue) - variant.setInteger(static_cast(code)); - - return true; - } - - bool readByte(uint8_t &value) { - int c = _reader.read(); - if (c < 0) { - _error = DeserializationError::IncompleteInput; - return false; - } - value = static_cast(c); - return true; - } - - bool readBytes(uint8_t *p, size_t n) { - if (_reader.readBytes(reinterpret_cast(p), n) == n) - return true; - _error = DeserializationError::IncompleteInput; - return false; - } - - template - bool readBytes(T &value) { - return readBytes(reinterpret_cast(&value), sizeof(value)); - } - - bool skipBytes(size_t n) { - for (; n; --n) { - if (_reader.read() < 0) { + bool readBytes(uint8_t * p, size_t n) { + if (_reader.readBytes(reinterpret_cast(p), n) == n) + return true; _error = DeserializationError::IncompleteInput; return false; - } - } - return true; - } - - template - bool readInteger(T &value) { - if (!readBytes(value)) - return false; - fixEndianess(value); - return true; - } - - template - bool readInteger(VariantData &variant) { - T value; - if (!readInteger(value)) - return false; - variant.setInteger(value); - return true; - } - - template - typename enable_if::type readFloat( - VariantData &variant) { - T value; - if (!readBytes(value)) - return false; - fixEndianess(value); - variant.setFloat(value); - return true; - } - - template - typename enable_if::type readDouble( - VariantData &variant) { - T value; - if (!readBytes(value)) - return false; - fixEndianess(value); - variant.setFloat(value); - return true; - } - - template - typename enable_if::type readDouble( - VariantData &variant) { - uint8_t i[8]; // input is 8 bytes - T value; // output is 4 bytes - uint8_t *o = reinterpret_cast(&value); - if (!readBytes(i, 8)) - return false; - doubleToFloat(i, o); - fixEndianess(value); - variant.setFloat(value); - return true; - } - - template - bool readString(VariantData &variant) { - T size; - if (!readInteger(size)) - return false; - return readString(variant, size); - } - - template - bool readString() { - T size; - if (!readInteger(size)) - return false; - return readString(size); - } - - template - bool skipString() { - T size; - if (!readInteger(size)) - return false; - return skipBytes(size); - } - - bool readString(VariantData &variant, size_t n) { - if (!readString(n)) - return false; - variant.setStringPointer(_stringStorage.save(), - typename TStringStorage::storage_policy()); - return true; - } - - bool readString(size_t n) { - _stringStorage.startString(); - for (; n; --n) { - uint8_t c; - if (!readBytes(c)) - return false; - _stringStorage.append(static_cast(c)); - } - _stringStorage.append('\0'); - if (!_stringStorage.isValid()) { - _error = DeserializationError::NoMemory; - return false; } - return true; - } - - template - bool readArray(VariantData &variant, TFilter filter, - NestingLimit nestingLimit) { - TSize size; - if (!readInteger(size)) - return false; - return readArray(variant, size, filter, nestingLimit); - } - - template - bool readArray(VariantData &variant, size_t n, TFilter filter, - NestingLimit nestingLimit) { - if (nestingLimit.reached()) { - _error = DeserializationError::TooDeep; - return false; + template + bool readBytes(T & value) { + return readBytes(reinterpret_cast(&value), sizeof(value)); } - bool allowArray = filter.allowArray(); - - CollectionData *array = allowArray ? &variant.toArray() : 0; - - TFilter memberFilter = filter[0U]; - - for (; n; --n) { - VariantData *value; - - if (memberFilter.allow()) { - value = array->addElement(_pool); - if (!value) { - _error = DeserializationError::NoMemory; - return false; + bool skipBytes(size_t n) { + for (; n; --n) { + if (_reader.read() < 0) { + _error = DeserializationError::IncompleteInput; + return false; + } } - } else { - value = 0; - } - - if (!parseVariant(*value, memberFilter, nestingLimit.decrement())) - return false; + return true; } - return true; - } - - template - bool readObject(VariantData &variant, TFilter filter, - NestingLimit nestingLimit) { - TSize size; - if (!readInteger(size)) - return false; - return readObject(variant, size, filter, nestingLimit); - } - - template - bool readObject(VariantData &variant, size_t n, TFilter filter, - NestingLimit nestingLimit) { - if (nestingLimit.reached()) { - _error = DeserializationError::TooDeep; - return false; + template + bool readInteger(T & value) { + if (!readBytes(value)) + return false; + fixEndianess(value); + return true; } - CollectionData *object = filter.allowObject() ? &variant.toObject() : 0; + template + bool readInteger(VariantData * variant) { + T value; + if (!readInteger(value)) + return false; + variant->setInteger(value); + return true; + } - for (; n; --n) { - if (!readKey()) - return false; + template + typename enable_if::type readFloat(VariantData * variant) { + T value; + if (!readBytes(value)) + return false; + fixEndianess(value); + variant->setFloat(value); + return true; + } - const char *key = _stringStorage.c_str(); - TFilter memberFilter = filter[key]; - VariantData *member; + template + typename enable_if::type readDouble(VariantData * variant) { + T value; + if (!readBytes(value)) + return false; + fixEndianess(value); + variant->setFloat(value); + return true; + } - if (memberFilter.allow()) { - // Save key in memory pool. - // This MUST be done before adding the slot. - key = _stringStorage.save(); + template + typename enable_if::type readDouble(VariantData * variant) { + uint8_t i[8]; // input is 8 bytes + T value; // output is 4 bytes + uint8_t * o = reinterpret_cast(&value); + if (!readBytes(i, 8)) + return false; + doubleToFloat(i, o); + fixEndianess(value); + variant->setFloat(value); + return true; + } - VariantSlot *slot = object->addSlot(_pool); - if (!slot) { - _error = DeserializationError::NoMemory; - return false; + template + bool readString(VariantData * variant) { + T size; + if (!readInteger(size)) + return false; + return readString(variant, size); + } + + template + bool readString() { + T size; + if (!readInteger(size)) + return false; + return readString(size); + } + + template + bool skipString() { + T size; + if (!readInteger(size)) + return false; + return skipBytes(size); + } + + bool readString(VariantData * variant, size_t n) { + if (!readString(n)) + return false; + variant->setStringPointer(_stringStorage.save(), typename TStringStorage::storage_policy()); + return true; + } + + bool readString(size_t n) { + _stringStorage.startString(); + for (; n; --n) { + uint8_t c; + if (!readBytes(c)) + return false; + _stringStorage.append(static_cast(c)); + } + _stringStorage.append('\0'); + if (!_stringStorage.isValid()) { + _error = DeserializationError::NoMemory; + return false; } - slot->setKey(key, typename TStringStorage::storage_policy()); - - member = slot->data(); - } else { - member = 0; - } - - if (!parseVariant(*member, memberFilter, nestingLimit.decrement())) - return false; + return true; } - return true; - } - - bool readKey() { - uint8_t code; - if (!readByte(code)) - return false; - - if ((code & 0xe0) == 0xa0) - return readString(code & 0x1f); - - switch (code) { - case 0xd9: - return readString(); - - case 0xda: - return readString(); - - case 0xdb: - return readString(); - - default: - return invalidInput(); + template + bool readArray(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { + TSize size; + if (!readInteger(size)) + return false; + return readArray(variant, size, filter, nestingLimit); } - } - template - bool skipExt() { - T size; - if (!readInteger(size)) - return false; - return skipBytes(size + 1); - } + template + bool readArray(VariantData * variant, size_t n, TFilter filter, NestingLimit nestingLimit) { + if (nestingLimit.reached()) { + _error = DeserializationError::TooDeep; + return false; + } - MemoryPool *_pool; - TReader _reader; - TStringStorage _stringStorage; - DeserializationError _error; - bool _foundSomething; + bool allowArray = filter.allowArray(); + + CollectionData * array = allowArray ? &variant->toArray() : 0; + + TFilter memberFilter = filter[0U]; + + for (; n; --n) { + VariantData * value; + + if (memberFilter.allow()) { + value = array->addElement(_pool); + if (!value) { + _error = DeserializationError::NoMemory; + return false; + } + } else { + value = 0; + } + + if (!parseVariant(value, memberFilter, nestingLimit.decrement())) + return false; + } + + return true; + } + + template + bool readObject(VariantData * variant, TFilter filter, NestingLimit nestingLimit) { + TSize size; + if (!readInteger(size)) + return false; + return readObject(variant, size, filter, nestingLimit); + } + + template + bool readObject(VariantData * variant, size_t n, TFilter filter, NestingLimit nestingLimit) { + if (nestingLimit.reached()) { + _error = DeserializationError::TooDeep; + return false; + } + + CollectionData * object = filter.allowObject() ? &variant->toObject() : 0; + + for (; n; --n) { + if (!readKey()) + return false; + + const char * key = _stringStorage.c_str(); + TFilter memberFilter = filter[key]; + VariantData * member; + + if (memberFilter.allow()) { + // Save key in memory pool. + // This MUST be done before adding the slot. + key = _stringStorage.save(); + + VariantSlot * slot = object->addSlot(_pool); + if (!slot) { + _error = DeserializationError::NoMemory; + return false; + } + + slot->setKey(key, typename TStringStorage::storage_policy()); + + member = slot->data(); + } else { + member = 0; + } + + if (!parseVariant(member, memberFilter, nestingLimit.decrement())) + return false; + } + + return true; + } + + bool readKey() { + uint8_t code; + if (!readByte(code)) + return false; + + if ((code & 0xe0) == 0xa0) + return readString(code & 0x1f); + + switch (code) { + case 0xd9: + return readString(); + + case 0xda: + return readString(); + + case 0xdb: + return readString(); + + default: + return invalidInput(); + } + } + + template + bool skipExt() { + T size; + if (!readInteger(size)) + return false; + return skipBytes(size + 1); + } + + MemoryPool * _pool; + TReader _reader; + TStringStorage _stringStorage; + DeserializationError _error; + bool _foundSomething; }; // @@ -494,25 +484,18 @@ class MsgPackDeserializer { // // ... = NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, const TString &input, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, - AllowAllFilter()); +DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, const TString &input, Filter filter, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument &doc, const TString &input, - NestingLimit nestingLimit, - Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, const TString & input, NestingLimit nestingLimit, Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -520,25 +503,18 @@ DeserializationError deserializeMsgPack(JsonDocument &doc, const TString &input, // // ... = NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TStream &input, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, - AllowAllFilter()); +DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TStream &input, Filter filter, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument &doc, TStream &input, - NestingLimit nestingLimit, - Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TStream & input, NestingLimit nestingLimit, Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -546,25 +522,18 @@ DeserializationError deserializeMsgPack(JsonDocument &doc, TStream &input, // // ... = NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TChar *input, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, - AllowAllFilter()); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TChar *input, Filter filter, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, Filter filter, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument &doc, TChar *input, - NestingLimit nestingLimit, - Filter filter) { - return deserialize(doc, input, nestingLimit, filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, NestingLimit nestingLimit, Filter filter) { + return deserialize(doc, input, nestingLimit, filter); } // @@ -572,28 +541,18 @@ DeserializationError deserializeMsgPack(JsonDocument &doc, TChar *input, // // ... = NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TChar *input, size_t inputSize, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, inputSize, nestingLimit, - AllowAllFilter()); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, inputSize, nestingLimit, AllowAllFilter()); } // ... = Filter, NestingLimit template -DeserializationError deserializeMsgPack( - JsonDocument &doc, TChar *input, size_t inputSize, Filter filter, - NestingLimit nestingLimit = NestingLimit()) { - return deserialize(doc, input, inputSize, nestingLimit, - filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, Filter filter, NestingLimit nestingLimit = NestingLimit()) { + return deserialize(doc, input, inputSize, nestingLimit, filter); } // ... = NestingLimit, Filter template -DeserializationError deserializeMsgPack(JsonDocument &doc, TChar *input, - size_t inputSize, - NestingLimit nestingLimit, - Filter filter) { - return deserialize(doc, input, inputSize, nestingLimit, - filter); +DeserializationError deserializeMsgPack(JsonDocument & doc, TChar * input, size_t inputSize, NestingLimit nestingLimit, Filter filter) { + return deserialize(doc, input, inputSize, nestingLimit, filter); } -} // namespace ARDUINOJSON_NAMESPACE +} // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp index bb9d05bb2..b1cd8727c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -17,6 +17,8 @@ namespace ARDUINOJSON_NAMESPACE { template class MsgPackSerializer : public Visitor { public: + static const bool producesText = false; + MsgPackSerializer(TWriter writer) : _writer(writer) {} template @@ -101,30 +103,37 @@ class MsgPackSerializer : public Visitor { return bytesWritten(); } - size_t visitNegativeInteger(UInt value) { - UInt negated = UInt(~value + 1); - if (value <= 0x20) { - writeInteger(int8_t(negated)); - } else if (value <= 0x80) { + size_t visitSignedInteger(Integer value) { + if (value > 0) { + visitUnsignedInteger(static_cast(value)); + } else if (value >= -0x20) { + writeInteger(int8_t(value)); + } else if (value >= -0x80) { writeByte(0xD0); - writeInteger(int8_t(negated)); - } else if (value <= 0x8000) { + writeInteger(int8_t(value)); + } else if (value >= -0x8000) { writeByte(0xD1); - writeInteger(int16_t(negated)); - } else if (value <= 0x80000000) { + writeInteger(int16_t(value)); + } +#if ARDUINOJSON_USE_LONG_LONG + else if (value >= -0x80000000LL) +#else + else +#endif + { writeByte(0xD2); - writeInteger(int32_t(negated)); + writeInteger(int32_t(value)); } #if ARDUINOJSON_USE_LONG_LONG else { writeByte(0xD3); - writeInteger(int64_t(negated)); + writeInteger(int64_t(value)); } #endif return bytesWritten(); } - size_t visitPositiveInteger(UInt value) { + size_t visitUnsignedInteger(UInt value) { if (value <= 0x7F) { writeInteger(uint8_t(value)); } else if (value <= 0xFF) { diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp index ff1bf8ed9..74f7e9dae 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/endianess.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp b/lib/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp index 19ead87e4..016d5334d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/MsgPack/ieee754.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp b/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp index 88b67dd42..2d85440aa 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Namespace.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/Float.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/Float.hpp index de5884ef3..bbaca0454 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/Float.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/Float.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp index 7bdfe9f37..4e53add39 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatParts.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp index 4286af016..60b1dfd1a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/FloatTraits.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp index d5e3a15f4..fb656a7d9 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/Integer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp index caf6e3361..bfd41d548 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/arithmeticCompare.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp index fad7c6d71..02bbefa50 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/convertNumber.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -15,84 +15,86 @@ #endif #include -#include -#include #include +#include namespace ARDUINOJSON_NAMESPACE { +// uint32 -> int32 +// uint64 -> int32 template -typename enable_if::value && sizeof(TOut) <= sizeof(TIn), +typename enable_if::value && is_unsigned::value && + is_integral::value && sizeof(TOut) <= sizeof(TIn), bool>::type -canStorePositiveInteger(TIn value) { +canConvertNumber(TIn value) { return value <= TIn(numeric_limits::highest()); } +// uint32 -> int64 template -typename enable_if::value && sizeof(TIn) < sizeof(TOut), +typename enable_if::value && is_unsigned::value && + is_integral::value && sizeof(TIn) < sizeof(TOut), bool>::type -canStorePositiveInteger(TIn) { +canConvertNumber(TIn) { return true; } +// uint32 -> float +// int32 -> float template -typename enable_if::value, bool>::type -canStorePositiveInteger(TIn) { +typename enable_if::value && is_floating_point::value, + bool>::type +canConvertNumber(TIn) { return true; } +// int64 -> int32 template -typename enable_if::value, bool>::type -canStoreNegativeInteger(TIn) { +typename enable_if::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TOut) < sizeof(TIn), + bool>::type +canConvertNumber(TIn value) { + return value >= TIn(numeric_limits::lowest()) && + value <= TIn(numeric_limits::highest()); +} + +// int32 -> int32 +// int32 -> int64 +template +typename enable_if::value && is_signed::value && + is_integral::value && is_signed::value && + sizeof(TIn) <= sizeof(TOut), + bool>::type +canConvertNumber(TIn) { return true; } +// int32 -> uint32 template -typename enable_if::value && is_signed::value && - sizeof(TOut) <= sizeof(TIn), +typename enable_if::value && is_signed::value && + is_integral::value && is_unsigned::value, bool>::type -canStoreNegativeInteger(TIn value) { - return value <= TIn(numeric_limits::highest()) + 1; +canConvertNumber(TIn value) { + if (value < 0) + return false; + return value <= TIn(numeric_limits::highest()); } +// float -> int32 +// float -> int64 template -typename enable_if::value && is_signed::value && - sizeof(TIn) < sizeof(TOut), +typename enable_if::value && + !is_floating_point::value, bool>::type -canStoreNegativeInteger(TIn) { - return true; -} - -template -typename enable_if::value && is_unsigned::value, - bool>::type -canStoreNegativeInteger(TIn) { - return false; -} - -template -TOut convertPositiveInteger(TIn value) { - return canStorePositiveInteger(value) ? TOut(value) : 0; -} - -template -TOut convertNegativeInteger(TIn value) { - return canStoreNegativeInteger(value) ? TOut(~value + 1) : 0; -} - -template -typename enable_if::value, TOut>::type convertFloat( - TIn value) { - return TOut(value); -} - -template -typename enable_if::value, TOut>::type convertFloat( - TIn value) { +canConvertNumber(TIn value) { return value >= numeric_limits::lowest() && - value <= numeric_limits::highest() - ? TOut(value) - : 0; + value <= numeric_limits::highest(); +} + +template +TOut convertNumber(TIn value) { + return canConvertNumber(value) ? TOut(value) : 0; } } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp b/lib/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp index eb3ce579c..cf050c78d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Numbers/parseNumber.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include namespace ARDUINOJSON_NAMESPACE { @@ -69,11 +69,17 @@ inline bool parseNumber(const char* s, VariantData& result) { } if (*s == '\0') { - if (is_negative) - result.setNegativeInteger(UInt(mantissa)); - else - result.setPositiveInteger(UInt(mantissa)); - return true; + if (is_negative) { + const mantissa_t sintMantissaMax = mantissa_t(1) + << (sizeof(Integer) * 8 - 1); + if (mantissa <= sintMantissaMax) { + result.setInteger(Integer(~mantissa + 1)); + return true; + } + } else { + result.setInteger(UInt(mantissa)); + return true; + } } // avoid mantissa overflow @@ -142,6 +148,6 @@ inline T parseNumber(const char* s) { VariantData value; value.init(); // VariantData is a POD, so it has no constructor parseNumber(s, value); - return variantAs(&value); + return Converter::fromJson(VariantConstRef(&value)); } } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp index c3cd9d514..0bee84bf2 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -46,20 +46,6 @@ class MemberProxy : public VariantOperators >, template FORCE_INLINE typename enable_if::value, this_type &>::type operator=(const TValue &src) { - /******************************************************************** - ** THIS IS NOT A BUG IN THE LIBRARY ** - ** -------------------------------- ** - ** Get a compilation error pointing here? ** - ** It doesn't mean the error *is* here. ** - ** Often, it's because you try to assign the wrong value type. ** - ** ** - ** For example: ** - ** char age = 42 ** - ** doc["age"] = age; ** - ** Instead, use: ** - ** int8_t age = 42; ** - ** doc["age"] = age; ** - ********************************************************************/ getOrAddUpstreamMember().set(src); return *this; } @@ -81,9 +67,17 @@ class MemberProxy : public VariantOperators >, return getUpstreamMember().isNull(); } - template - FORCE_INLINE typename VariantAs::type as() const { - return getUpstreamMember().template as(); + template + FORCE_INLINE typename enable_if::value, T>::type as() + const { + return getUpstreamMember().template as(); + } + + template + FORCE_INLINE typename enable_if::value, const char *>::type + ARDUINOJSON_DEPRECATED("Replace as() with as()") + as() const { + return as(); } template @@ -193,6 +187,10 @@ class MemberProxy : public VariantOperators >, return _object.getOrAddMember(_key); } + friend bool convertToJson(const this_type &src, VariantRef dst) { + return dst.set(src.getUpstreamMember()); + } + TObject _object; TStringRef _key; }; diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectFunctions.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectFunctions.hpp index b784c86e6..1b46e8426 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectFunctions.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectFunctions.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp index 97c6e8ce0..d66b61cad 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectIterator.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectIterator.hpp index 6f70c5d9f..d72945990 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectIterator.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectIterator.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp index 618f1ecdb..c945fb6ca 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectRef.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -236,4 +236,42 @@ class ObjectRef : public ObjectRefBase, private: MemoryPool* _pool; }; + +template <> +struct Converter { + static bool toJson(VariantConstRef src, VariantRef dst) { + return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ObjectConstRef fromJson(VariantConstRef src) { + return ObjectConstRef(variantAsObject(getData(src))); + } + + static bool checkJson(VariantConstRef src) { + const VariantData* data = getData(src); + return data && data->isObject(); + } +}; + +template <> +struct Converter { + static bool toJson(VariantConstRef src, VariantRef dst) { + return variantCopyFrom(getData(dst), getData(src), getPool(dst)); + } + + static ObjectRef fromJson(VariantRef src) { + VariantData* data = getData(src); + MemoryPool* pool = getPool(src); + return ObjectRef(pool, data != 0 ? data->asObject() : 0); + } + + static bool checkJson(VariantConstRef) { + return false; + } + + static bool checkJson(VariantRef src) { + VariantData* data = getData(src); + return data && data->isObject(); + } +}; } // namespace ARDUINOJSON_NAMESPACE diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectShortcuts.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectShortcuts.hpp index 579adc746..0f3409f5f 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/ObjectShortcuts.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/ObjectShortcuts.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Object/Pair.hpp b/lib/ArduinoJson/src/ArduinoJson/Object/Pair.hpp index 213426c6a..44fce7518 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Object/Pair.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Object/Pair.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp index b7a040ee1..8f8e2770c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/alias_cast.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp index 5fa2b4598..1030ec60e 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/assert.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp index 66fd688f2..f04c9acce 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/attributes.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -8,23 +8,32 @@ #define FORCE_INLINE // __forceinline causes C4714 when returning std::string #define NO_INLINE __declspec(noinline) -#define DEPRECATED(msg) __declspec(deprecated(msg)) + +#ifndef ARDUINOJSON_DEPRECATED +#define ARDUINOJSON_DEPRECATED(msg) __declspec(deprecated(msg)) +#endif #elif defined(__GNUC__) // GCC or Clang #define FORCE_INLINE __attribute__((always_inline)) #define NO_INLINE __attribute__((noinline)) + +#ifndef ARDUINOJSON_DEPRECATED #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) -#define DEPRECATED(msg) __attribute__((deprecated(msg))) +#define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated(msg))) #else -#define DEPRECATED(msg) __attribute__((deprecated)) +#define ARDUINOJSON_DEPRECATED(msg) __attribute__((deprecated)) +#endif #endif #else // Other compilers #define FORCE_INLINE #define NO_INLINE -#define DEPRECATED(msg) + +#ifndef ARDUINOJSON_DEPRECATED +#define ARDUINOJSON_DEPRECATED(msg) +#endif #endif diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp index fd3683d6a..bd7a8cc65 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/ctype.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp index 2d9985ded..066c7b800 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/integer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp index 65cd79ffc..80048284b 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp index 635200c32..eff272add 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/math.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp index 21484cd51..9ac47a53d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/mpl/max.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp index d0333aaa0..f253818e0 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp index 39c3755c0..f5bbd85e5 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/pgmspace_generic.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp index a3370c14c..488654b21 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/preprocessor.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/safe_strcmp.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/safe_strcmp.hpp index dcb175ba6..e017b5dda 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/safe_strcmp.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/safe_strcmp.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp index eac4d9472..a877b4caa 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/static_array.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp index 6d6ffb675..4a8ff4b94 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp index 3ae1d2838..e42d1bb99 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/conditional.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp index 541cae46e..87081123a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/declval.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp index 4e1f0a7c4..cc29b3375 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/enable_if.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp index 98a8a44b3..b53d7111c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/integral_constant.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp index eb6148a80..ee739a70a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_array.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp index 2af9a6282..32b41cd95 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_base_of.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp index 05bceeebd..a3808f3a3 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_class.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp index a49b0b61a..32e758c98 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_const.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp index 7f2724320..847525a93 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp index 973d937b2..26aec1de0 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_enum.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp index d2a26c8ad..b7e9d3d24 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_floating_point.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp index 61ef33120..26e895c8a 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_integral.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp index 39d286978..a24953964 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_pointer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp index 374f9e56c..db5da9bdf 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_same.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp index 08b27e654..fbb701cf7 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_signed.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp index 8a24e592b..be2649829 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_unsigned.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp index 97c7bcc5f..4cf2d08b8 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/make_unsigned.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp index 9adae4a3f..5a19eb18d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_const.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp index 7098d322d..181285087 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/remove_reference.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp index d1b6052c1..c464a47c3 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/type_identity.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp b/lib/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp index 363e1ae45..c99bc9919 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Polyfills/utility.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp index bd4432c18..3d89fb1d6 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/CountingDecorator.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp index cbbc5ca73..52bd1175d 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writer.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp index ba0adfb5d..5efa6e492 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/ArduinoStringWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp index d8561de96..a26a1f1cf 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/DummyWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp index d702bd894..13a64912c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/PrintWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp index e36779335..1a4213b83 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StaticStringWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -8,18 +8,14 @@ namespace ARDUINOJSON_NAMESPACE { -// A Print implementation that allows to write in a char[] class StaticStringWriter { public: - StaticStringWriter(char *buf, size_t size) : end(buf + size - 1), p(buf) { - *p = '\0'; - } + StaticStringWriter(char *buf, size_t size) : end(buf + size), p(buf) {} size_t write(uint8_t c) { if (p >= end) return 0; *p++ = static_cast(c); - *p = '\0'; return 1; } @@ -29,7 +25,6 @@ class StaticStringWriter { *p++ = static_cast(*s++); n--; } - *p = '\0'; return size_t(p - begin); } diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp index d0d534289..e08ba4d60 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStreamWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp index eb56b2b32..c1f7cc00f 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/Writers/StdStringWriter.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp index 7b656f650..6d199944c 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/measure.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License diff --git a/lib/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp b/lib/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp index 73a720e2e..16d2e4ef1 100644 --- a/lib/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp +++ b/lib/ArduinoJson/src/ArduinoJson/Serialization/serialize.hpp @@ -1,4 +1,4 @@ -// ArduinoJson - arduinojson.org +// ArduinoJson - https://arduinojson.org // Copyright Benoit Blanchon 2014-2021 // MIT License @@ -23,11 +23,23 @@ size_t serialize(const TSource &source, TDestination &destination) { } template