diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cdbcc5dd2..1e1704d13b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [1.13.0] - 2019-11-25 + +### New Features + +- Display link "Dashboards" inside the navigation bar (it becomes visible after clicking an address in one of the Grafana dashboards. Alternatively the Grafana URL can be added manually on the settings page) +- Enable or disable the sleep mode depending on the location. For example, the car can be allowed to sleep at home or work, but nowhere else. +- Extend Charge Stats Dashboard with discharge stats, a charge delta graph and a charge heatmap ([#270](https://github.com/adriankumpf/teslamate/pull/270) by [@marcogabriel](https://github.com/marcogabriel)) + +### Enhancements + +- Make sleep mode separately configurable for each car +- Reduce default "Time to try sleeping" to 12 minutes for newer vehicles +- The "States" dashboard now includes software updates +- Automatically repair trips and charges with missing addresses (e.g. because OpenStreetMap was temporarily unavailable) +- Update thresholds of the battery level gauge ([#256](https://github.com/adriankumpf/teslamate/pull/256) by [@marcogabriel](https://github.com/marcogabriel)) + +### Bug Fixes + +- Fix issue where consumption values were displayed as 0 +- Fix issue where installing a software update when charging would produce an incomplete charge record + +--- + +```text +TeslaMate is open source and completely free for everyone to use. + +If you like this project and want to support further development, please consider making a donation. +``` + ## [1.12.2] - 2019-11-06 ### Bug Fixes @@ -544,7 +573,8 @@ New users need to sign in via the web interface. ## [1.0.0] - 2019-07-25 -[unreleased]: https://github.com/adriankumpf/teslamate/compare/v1.12.2...HEAD +[unreleased]: https://github.com/adriankumpf/teslamate/compare/v1.13.0...HEAD +[1.13.0]: https://github.com/adriankumpf/teslamate/compare/v1.12.2...v1.13.0 [1.12.2]: https://github.com/adriankumpf/teslamate/compare/v1.12.1...v1.12.2 [1.12.1]: https://github.com/adriankumpf/teslamate/compare/v1.12.0...v1.12.1 [1.12.0]: https://github.com/adriankumpf/teslamate/compare/v1.11.1...v1.12.0 diff --git a/assets/css/app.scss b/assets/css/app.scss index 178a2c989a..9dde254a71 100644 --- a/assets/css/app.scss +++ b/assets/css/app.scss @@ -30,6 +30,7 @@ $fullhd-enabled: false; @import "~bulma/sass/grid/_all"; @import '~bulma-tooltip'; +@import '~bulma-switch'; // @import "~bulma/bulma"; @@ -112,6 +113,10 @@ $fullhd-enabled: false; padding: 0.75rem 1.5rem; } } + + button[phx-click=suspend_logging]:disabled { + pointer-events: none; + } } .navbar-brand a.navbar-item:hover { diff --git a/assets/js/app.js b/assets/js/app.js index 30303551ac..1f28149b56 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -12,7 +12,10 @@ import * as hooks from "./hooks"; const liveSocket = new LiveSocket("/live", Socket, { hooks, - params: { baseUrl: window.location.origin } + params: { + baseUrl: window.location.origin, + referrer: document.referrer + } }); liveSocket.connect(); diff --git a/assets/package.json b/assets/package.json index 3c3122444b..c509fccdf0 100644 --- a/assets/package.json +++ b/assets/package.json @@ -6,7 +6,8 @@ "watch": "webpack --mode development --watch" }, "dependencies": { - "@mdi/font": "^4.5.95", + "@mdi/font": "^4.6.95", + "bulma-switch": "^2.0.0", "bulma-tooltip": "^3.0.2", "leaflet": "^1.6.0", "leaflet-control-geocoder": "^1.10.0", @@ -15,19 +16,19 @@ "phoenix_live_view": "file:../deps/phoenix_live_view" }, "devDependencies": { - "@babel/core": "^7.7.2", - "@babel/preset-env": "^7.7.1", + "@babel/core": "^7.7.4", + "@babel/preset-env": "^7.7.4", "babel-loader": "^8.0.6", "bulma": "^0.8.0", "copy-webpack-plugin": "^5.0.5", "css-loader": "^3.2.0", - "file-loader": "^4.2.0", + "file-loader": "^4.3.0", "mini-css-extract-plugin": "^0.8.0", "optimize-css-assets-webpack-plugin": "^5.0.3", - "sass": "^1.23.6", + "sass": "^1.23.7", "sass-loader": "^8.0.0", "uglifyjs-webpack-plugin": "^2.2.0", - "url-loader": "^2.2.0", + "url-loader": "^2.3.0", "webpack": "4.41.2", "webpack-cli": "^3.3.10" } diff --git a/assets/yarn.lock b/assets/yarn.lock index 186ccdcfe7..c63fc7878a 100644 --- a/assets/yarn.lock +++ b/assets/yarn.lock @@ -9,18 +9,18 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.2.tgz#ea5b99693bcfc058116f42fa1dd54da412b29d91" - integrity sha512-eeD7VEZKfhK1KUXGiyPFettgF3m513f8FoBSWiQ1xTvl1RAopLs42Wp9+Ze911I6H0N9lNqJMDgoZT7gHsipeQ== +"@babel/core@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.4.tgz#37e864532200cb6b50ee9a4045f5f817840166ab" + integrity sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.7.2" - "@babel/helpers" "^7.7.0" - "@babel/parser" "^7.7.2" - "@babel/template" "^7.7.0" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.7.2" + "@babel/generator" "^7.7.4" + "@babel/helpers" "^7.7.4" + "@babel/parser" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" convert-source-map "^1.7.0" debug "^4.1.0" json5 "^2.1.0" @@ -29,204 +29,120 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56" - integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA== +"@babel/generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369" + integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg== dependencies: - "@babel/types" "^7.6.0" + "@babel/types" "^7.7.4" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/generator@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.0.tgz#c6d4d1f7a0d6e139cbd01aca73170b0bff5425b4" - integrity sha512-1wdJ6UxHyL1XoJQ119JmvuRX27LRih7iYStMPZOWAjQqeAabFg3dYXKMpgihma+to+0ADsTVVt6oRyUxWZw6Mw== +"@babel/helper-annotate-as-pure@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz#bb3faf1e74b74bd547e867e48f551fa6b098b6ce" + integrity sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og== dependencies: - "@babel/types" "^7.7.0" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.2.tgz#2f4852d04131a5e17ea4f6645488b5da66ebf3af" - integrity sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ== - dependencies: - "@babel/types" "^7.7.2" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/helper-annotate-as-pure@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" - integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== - dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.7.4" -"@babel/helper-annotate-as-pure@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.0.tgz#efc54032d43891fe267679e63f6860aa7dbf4a5e" - integrity sha512-k50CQxMlYTYo+GGyUGFwpxKVtxVJi9yh61sXZji3zYHccK9RYliZGSTOgci85T+r+0VFN2nWbGM04PIqwfrpMg== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz#5f73f2b28580e224b5b9bd03146a4015d6217f5f" + integrity sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ== dependencies: - "@babel/types" "^7.7.0" + "@babel/helper-explode-assignable-expression" "^7.7.4" + "@babel/types" "^7.7.4" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" - integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== +"@babel/helper-call-delegate@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz#621b83e596722b50c0066f9dc37d3232e461b801" + integrity sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA== dependencies: - "@babel/helper-explode-assignable-expression" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-hoist-variables" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" -"@babel/helper-call-delegate@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43" - integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ== - dependencies: - "@babel/helper-hoist-variables" "^7.4.4" - "@babel/traverse" "^7.4.4" - "@babel/types" "^7.4.4" - -"@babel/helper-create-regexp-features-plugin@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.0.tgz#2e8badfe201cfafb5d930f46cf1e0b6f1cdcab23" - integrity sha512-ZhagAAVGD3L6MPM9/zZi7RRteonfBFLVUz3kjsnYsMAtr9hOJCKI9BAKIMpqn3NyWicPieoX779UL+7/3BEAOA== +"@babel/helper-create-regexp-features-plugin@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz#6d5762359fd34f4da1500e4cff9955b5299aaf59" + integrity sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A== dependencies: "@babel/helper-regex" "^7.4.4" regexpu-core "^4.6.0" -"@babel/helper-define-map@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.0.tgz#60b0e9fd60def9de5054c38afde8c8ee409c7529" - integrity sha512-kPKWPb0dMpZi+ov1hJiwse9dWweZsz3V9rP4KdytnX1E7z3cTNmFGglwklzFPuqIcHLIY3bgKSs4vkwXXdflQA== +"@babel/helper-define-map@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz#2841bf92eb8bd9c906851546fe6b9d45e162f176" + integrity sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg== dependencies: - "@babel/helper-function-name" "^7.7.0" - "@babel/types" "^7.7.0" + "@babel/helper-function-name" "^7.7.4" + "@babel/types" "^7.7.4" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" - integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== - dependencies: - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" - -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-explode-assignable-expression@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz#fa700878e008d85dc51ba43e9fb835cddfe05c84" + integrity sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" -"@babel/helper-function-name@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz#44a5ad151cfff8ed2599c91682dda2ec2c8430a3" - integrity sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q== +"@babel/helper-function-name@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e" + integrity sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ== dependencies: - "@babel/helper-get-function-arity" "^7.7.0" - "@babel/template" "^7.7.0" - "@babel/types" "^7.7.0" + "@babel/helper-get-function-arity" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz#cb46348d2f8808e632f0ab048172130e636005f0" + integrity sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.7.4" -"@babel/helper-get-function-arity@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz#c604886bc97287a1d1398092bc666bc3d7d7aa2d" - integrity sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw== +"@babel/helper-hoist-variables@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz#612384e3d823fdfaaf9fce31550fe5d4db0f3d12" + integrity sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ== dependencies: - "@babel/types" "^7.7.0" + "@babel/types" "^7.7.4" -"@babel/helper-hoist-variables@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" - integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w== +"@babel/helper-member-expression-to-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz#356438e2569df7321a8326644d4b790d2122cb74" + integrity sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw== dependencies: - "@babel/types" "^7.4.4" + "@babel/types" "^7.7.4" -"@babel/helper-hoist-variables@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.0.tgz#b4552e4cfe5577d7de7b183e193e84e4ec538c81" - integrity sha512-LUe/92NqsDAkJjjCEWkNe+/PcpnisvnqdlRe19FahVapa4jndeuJ+FBiTX1rcAKWKcJGE+C3Q3tuEuxkSmCEiQ== +"@babel/helper-module-imports@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz#e5a92529f8888bf319a6376abfbd1cebc491ad91" + integrity sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ== dependencies: - "@babel/types" "^7.7.0" + "@babel/types" "^7.7.4" -"@babel/helper-member-expression-to-functions@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" - integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA== +"@babel/helper-module-transforms@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz#8d7cdb1e1f8ea3d8c38b067345924ac4f8e0879a" + integrity sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA== dependencies: - "@babel/types" "^7.5.5" - -"@babel/helper-member-expression-to-functions@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.0.tgz#472b93003a57071f95a541ea6c2b098398bcad8a" - integrity sha512-QaCZLO2RtBcmvO/ekOLp8p7R5X2JriKRizeDpm5ChATAFWrrYDcDxPuCIBXKyBjY+i1vYSdcUTMIb8psfxHDPA== - dependencies: - "@babel/types" "^7.7.0" - -"@babel/helper-module-imports@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" - integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helper-module-imports@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.7.0.tgz#99c095889466e5f7b6d66d98dffc58baaf42654d" - integrity sha512-Dv3hLKIC1jyfTkClvyEkYP2OlkzNvWs5+Q8WgPbxM5LMeorons7iPP91JM+DU7tRbhqA1ZeooPaMFvQrn23RHw== - dependencies: - "@babel/types" "^7.7.0" - -"@babel/helper-module-transforms@^7.1.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a" - integrity sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/template" "^7.4.4" - "@babel/types" "^7.5.5" + "@babel/helper-module-imports" "^7.7.4" + "@babel/helper-simple-access" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" lodash "^4.17.13" -"@babel/helper-module-transforms@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.7.0.tgz#154a69f0c5b8fd4d39e49750ff7ac4faa3f36786" - integrity sha512-rXEefBuheUYQyX4WjV19tuknrJFwyKw0HgzRwbkyTbB+Dshlq7eqkWbyjzToLrMZk/5wKVKdWFluiAsVkHXvuQ== +"@babel/helper-optimise-call-expression@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz#034af31370d2995242aa4df402c3b7794b2dcdf2" + integrity sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg== dependencies: - "@babel/helper-module-imports" "^7.7.0" - "@babel/helper-simple-access" "^7.7.0" - "@babel/helper-split-export-declaration" "^7.7.0" - "@babel/template" "^7.7.0" - "@babel/types" "^7.7.0" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" - integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helper-optimise-call-expression@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.0.tgz#4f66a216116a66164135dc618c5d8b7a959f9365" - integrity sha512-48TeqmbazjNU/65niiiJIJRc5JozB8acui1OS7bSd6PgxfuovWsvjfWSzlgx+gPFdVveNzUdpdIg5l56Pl5jqg== - dependencies: - "@babel/types" "^7.7.0" + "@babel/types" "^7.7.4" "@babel/helper-plugin-utils@^7.0.0": version "7.0.0" @@ -240,85 +156,60 @@ dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.0.tgz#4d69ec653e8bff5bce62f5d33fc1508f223c75a7" - integrity sha512-pHx7RN8X0UNHPB/fnuDnRXVZ316ZigkO8y8D835JlZ2SSdFKb6yH9MIYRU4fy/KPe5sPHDFOPvf8QLdbAGGiyw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.7.0" - "@babel/helper-wrap-function" "^7.7.0" - "@babel/template" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - -"@babel/helper-replace-supers@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" - integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.5.5" - "@babel/types" "^7.5.5" - -"@babel/helper-replace-supers@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.0.tgz#d5365c8667fe7cbd13b8ddddceb9bd7f2b387512" - integrity sha512-5ALYEul5V8xNdxEeWvRsBzLMxQksT7MaStpxjJf9KsnLxpAKBtfw5NeMKZJSYDa0lKdOcy0g+JT/f5mPSulUgg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.7.0" - "@babel/helper-optimise-call-expression" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - -"@babel/helper-simple-access@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" - integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== - dependencies: - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" - -"@babel/helper-simple-access@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.0.tgz#97a8b6c52105d76031b86237dc1852b44837243d" - integrity sha512-AJ7IZD7Eem3zZRuj5JtzFAptBw7pMlS3y8Qv09vaBWoFsle0d1kAn5Wq6Q9MyBXITPOKnxwkZKoAm4bopmv26g== - dependencies: - "@babel/template" "^7.7.0" - "@babel/types" "^7.7.0" - -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== - dependencies: - "@babel/types" "^7.4.4" - -"@babel/helper-split-export-declaration@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz#1365e74ea6c614deeb56ebffabd71006a0eb2300" - integrity sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA== - dependencies: - "@babel/types" "^7.7.0" - -"@babel/helper-wrap-function@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.0.tgz#15af3d3e98f8417a60554acbb6c14e75e0b33b74" - integrity sha512-sd4QjeMgQqzshSjecZjOp8uKfUtnpmCyQhKQrVJBBgeHAB/0FPi33h3AbVlVp07qQtMD4QgYSzaMI7VwncNK/w== - dependencies: - "@babel/helper-function-name" "^7.7.0" - "@babel/template" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" - -"@babel/helpers@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.0.tgz#359bb5ac3b4726f7c1fde0ec75f64b3f4275d60b" - integrity sha512-VnNwL4YOhbejHb7x/b5F39Zdg5vIQpUUNzJwx0ww1EcVRt41bbGRZWhAURrfY32T5zTT3qwNOQFWpn+P0i0a2g== - dependencies: - "@babel/template" "^7.7.0" - "@babel/traverse" "^7.7.0" - "@babel/types" "^7.7.0" +"@babel/helper-remap-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz#c68c2407350d9af0e061ed6726afb4fff16d0234" + integrity sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-wrap-function" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-replace-supers@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz#3c881a6a6a7571275a72d82e6107126ec9e2cdd2" + integrity sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.7.4" + "@babel/helper-optimise-call-expression" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-simple-access@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz#a169a0adb1b5f418cfc19f22586b2ebf58a9a294" + integrity sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A== + dependencies: + "@babel/template" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helper-split-export-declaration@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8" + integrity sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug== + dependencies: + "@babel/types" "^7.7.4" + +"@babel/helper-wrap-function@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz#37ab7fed5150e22d9d7266e830072c0cdd8baace" + integrity sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg== + dependencies: + "@babel/helper-function-name" "^7.7.4" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" + +"@babel/helpers@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.7.4.tgz#62c215b9e6c712dadc15a9a0dcab76c92a940302" + integrity sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg== + dependencies: + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@babel/types" "^7.7.4" "@babel/highlight@^7.0.0": version "7.5.0" @@ -329,507 +220,440 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b" - integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ== - -"@babel/parser@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.0.tgz#232618f6e8947bc54b407fa1f1c91a22758e7159" - integrity sha512-GqL+Z0d7B7ADlQBMXlJgvXEbtt5qlqd1YQ5fr12hTSfh7O/vgrEIvJxU2e7aSVrEUn75zTZ6Nd0s8tthrlZnrQ== +"@babel/parser@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.4.tgz#75ab2d7110c2cf2fa949959afb05fa346d2231bb" + integrity sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g== -"@babel/parser@^7.7.2": - version "7.7.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.3.tgz#5fad457c2529de476a248f75b0f090b3060af043" - integrity sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A== - -"@babel/plugin-proposal-async-generator-functions@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.0.tgz#83ef2d6044496b4c15d8b4904e2219e6dccc6971" - integrity sha512-ot/EZVvf3mXtZq0Pd0+tSOfGWMizqmOohXmNZg6LNFjHOV+wOPv7BvVYh8oPR8LhpIP3ye8nNooKL50YRWxpYA== +"@babel/plugin-proposal-async-generator-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz#0351c5ac0a9e927845fffd5b82af476947b7ce6d" + integrity sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.7.0" - "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" -"@babel/plugin-proposal-dynamic-import@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.0.tgz#dc02a8bad8d653fb59daf085516fa416edd2aa7f" - integrity sha512-7poL3Xi+QFPC7sGAzEIbXUyYzGJwbc2+gSD0AkiC5k52kH2cqHdqxm5hNFfLW3cRSTcx9bN0Fl7/6zWcLLnKAQ== +"@babel/plugin-proposal-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz#dde64a7f127691758cbfed6cf70de0fa5879d52d" + integrity sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" -"@babel/plugin-proposal-json-strings@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" - integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== +"@babel/plugin-proposal-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz#7700a6bfda771d8dc81973249eac416c6b4c697d" + integrity sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.7.4" -"@babel/plugin-proposal-object-rest-spread@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz#8ffccc8f3a6545e9f78988b6bf4fe881b88e8096" - integrity sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw== +"@babel/plugin-proposal-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz#cc57849894a5c774214178c8ab64f6334ec8af71" + integrity sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" -"@babel/plugin-proposal-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" - integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== +"@babel/plugin-proposal-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz#ec21e8aeb09ec6711bc0a39ca49520abee1de379" + integrity sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" -"@babel/plugin-proposal-unicode-property-regex@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.0.tgz#549fe1717a1bd0a2a7e63163841cb37e78179d5d" - integrity sha512-mk34H+hp7kRBWJOOAR0ZMGCydgKMD4iN9TpDRp3IIcbunltxEY89XSimc6WbtSLCDrwcdy/EEw7h5CFCzxTchw== +"@babel/plugin-proposal-unicode-property-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz#7c239ccaf09470dbe1d453d50057460e84517ebb" + integrity sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.7.0" + "@babel/helper-create-regexp-features-plugin" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-async-generators@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" - integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== +"@babel/plugin-syntax-async-generators@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz#331aaf310a10c80c44a66b238b6e49132bd3c889" + integrity sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-dynamic-import@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612" - integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w== +"@babel/plugin-syntax-dynamic-import@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz#29ca3b4415abfe4a5ec381e903862ad1a54c3aec" + integrity sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-json-strings@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" - integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== +"@babel/plugin-syntax-json-strings@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz#86e63f7d2e22f9e27129ac4e83ea989a382e86cc" + integrity sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-object-rest-spread@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" - integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== +"@babel/plugin-syntax-object-rest-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz#47cf220d19d6d0d7b154304701f468fc1cc6ff46" + integrity sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" - integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== +"@babel/plugin-syntax-optional-catch-binding@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz#a3e38f59f4b6233867b4a92dcb0ee05b2c334aa6" + integrity sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-syntax-top-level-await@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.0.tgz#f5699549f50bbe8d12b1843a4e82f0a37bb65f4d" - integrity sha512-hi8FUNiFIY1fnUI2n1ViB1DR0R4QeK4iHcTlW6aJkrPoTdb8Rf1EMQ6GT3f67DDkYyWgew9DFoOZ6gOoEsdzTA== +"@babel/plugin-syntax-top-level-await@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz#bd7d8fa7b9fee793a36e4027fd6dd1aa32f946da" + integrity sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-arrow-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" - integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== +"@babel/plugin-transform-arrow-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz#76309bd578addd8aee3b379d809c802305a98a12" + integrity sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-async-to-generator@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.0.tgz#e2b84f11952cf5913fe3438b7d2585042772f492" - integrity sha512-vLI2EFLVvRBL3d8roAMqtVY0Bm9C1QzLkdS57hiKrjUBSqsQYrBsMCeOg/0KK7B0eK9V71J5mWcha9yyoI2tZw== +"@babel/plugin-transform-async-to-generator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz#694cbeae6d613a34ef0292713fa42fb45c4470ba" + integrity sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg== dependencies: - "@babel/helper-module-imports" "^7.7.0" + "@babel/helper-module-imports" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.7.0" + "@babel/helper-remap-async-to-generator" "^7.7.4" -"@babel/plugin-transform-block-scoped-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" - integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== +"@babel/plugin-transform-block-scoped-functions@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz#d0d9d5c269c78eaea76227ace214b8d01e4d837b" + integrity sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-block-scoping@^7.6.3": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.3.tgz#6e854e51fbbaa84351b15d4ddafe342f3a5d542a" - integrity sha512-7hvrg75dubcO3ZI2rjYTzUrEuh1E9IyDEhhB6qfcooxhDA33xx2MasuLVgdxzcP6R/lipAC6n9ub9maNW6RKdw== +"@babel/plugin-transform-block-scoping@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz#200aad0dcd6bb80372f94d9e628ea062c58bf224" + integrity sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" lodash "^4.17.13" -"@babel/plugin-transform-classes@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.0.tgz#b411ecc1b8822d24b81e5d184f24149136eddd4a" - integrity sha512-/b3cKIZwGeUesZheU9jNYcwrEA7f/Bo4IdPmvp7oHgvks2majB5BoT5byAql44fiNQYOPzhk2w8DbgfuafkMoA== +"@babel/plugin-transform-classes@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz#c92c14be0a1399e15df72667067a8f510c9400ec" + integrity sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg== dependencies: - "@babel/helper-annotate-as-pure" "^7.7.0" - "@babel/helper-define-map" "^7.7.0" - "@babel/helper-function-name" "^7.7.0" - "@babel/helper-optimise-call-expression" "^7.7.0" + "@babel/helper-annotate-as-pure" "^7.7.4" + "@babel/helper-define-map" "^7.7.4" + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-optimise-call-expression" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.7.0" - "@babel/helper-split-export-declaration" "^7.7.0" + "@babel/helper-replace-supers" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" - integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== +"@babel/plugin-transform-computed-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz#e856c1628d3238ffe12d668eb42559f79a81910d" + integrity sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-destructuring@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6" - integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ== +"@babel/plugin-transform-destructuring@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz#2b713729e5054a1135097b6a67da1b6fe8789267" + integrity sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-dotall-regex@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.0.tgz#c5c9ecacab3a5e0c11db6981610f0c32fd698b3b" - integrity sha512-3QQlF7hSBnSuM1hQ0pS3pmAbWLax/uGNCbPBND9y+oJ4Y776jsyujG2k0Sn2Aj2a0QwVOiOFL5QVPA7spjvzSA== +"@babel/plugin-transform-dotall-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz#f7ccda61118c5b7a2599a72d5e3210884a021e96" + integrity sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.7.0" + "@babel/helper-create-regexp-features-plugin" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-duplicate-keys@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" - integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ== +"@babel/plugin-transform-duplicate-keys@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz#3d21731a42e3f598a73835299dd0169c3b90ac91" + integrity sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-exponentiation-operator@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" - integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== +"@babel/plugin-transform-exponentiation-operator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz#dd30c0191e3a1ba19bcc7e389bdfddc0729d5db9" + integrity sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-for-of@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" - integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ== +"@babel/plugin-transform-for-of@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz#248800e3a5e507b1f103d8b4ca998e77c63932bc" + integrity sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-function-name@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.0.tgz#0fa786f1eef52e3b7d4fc02e54b2129de8a04c2a" - integrity sha512-P5HKu0d9+CzZxP5jcrWdpe7ZlFDe24bmqP6a6X8BHEBl/eizAsY8K6LX8LASZL0Jxdjm5eEfzp+FIrxCm/p8bA== +"@babel/plugin-transform-function-name@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz#75a6d3303d50db638ff8b5385d12451c865025b1" + integrity sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g== dependencies: - "@babel/helper-function-name" "^7.7.0" + "@babel/helper-function-name" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" - integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== +"@babel/plugin-transform-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz#27fe87d2b5017a2a5a34d1c41a6b9f6a6262643e" + integrity sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-member-expression-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" - integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA== +"@babel/plugin-transform-member-expression-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz#aee127f2f3339fc34ce5e3055d7ffbf7aa26f19a" + integrity sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-modules-amd@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91" - integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg== +"@babel/plugin-transform-modules-amd@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz#276b3845ca2b228f2995e453adc2e6f54d72fb71" + integrity sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ== dependencies: - "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-module-transforms" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.0.tgz#3e5ffb4fd8c947feede69cbe24c9554ab4113fe3" - integrity sha512-KEMyWNNWnjOom8vR/1+d+Ocz/mILZG/eyHHO06OuBQ2aNhxT62fr4y6fGOplRx+CxCSp3IFwesL8WdINfY/3kg== +"@babel/plugin-transform-modules-commonjs@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz#bee4386e550446343dd52a571eda47851ff857a3" + integrity sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA== dependencies: - "@babel/helper-module-transforms" "^7.7.0" + "@babel/helper-module-transforms" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.7.0" + "@babel/helper-simple-access" "^7.7.4" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-systemjs@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.0.tgz#9baf471213af9761c1617bb12fd278e629041417" - integrity sha512-ZAuFgYjJzDNv77AjXRqzQGlQl4HdUM6j296ee4fwKVZfhDR9LAGxfvXjBkb06gNETPnN0sLqRm9Gxg4wZH6dXg== +"@babel/plugin-transform-modules-systemjs@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz#cd98152339d3e763dfe838b7d4273edaf520bb30" + integrity sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw== dependencies: - "@babel/helper-hoist-variables" "^7.7.0" + "@babel/helper-hoist-variables" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-umd@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.0.tgz#d62c7da16670908e1d8c68ca0b5d4c0097b69966" - integrity sha512-u7eBA03zmUswQ9LQ7Qw0/ieC1pcAkbp5OQatbWUzY1PaBccvuJXUkYzoN1g7cqp7dbTu6Dp9bXyalBvD04AANA== +"@babel/plugin-transform-modules-umd@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz#1027c355a118de0aae9fee00ad7813c584d9061f" + integrity sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw== dependencies: - "@babel/helper-module-transforms" "^7.7.0" + "@babel/helper-module-transforms" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-named-capturing-groups-regex@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.0.tgz#358e6fd869b9a4d8f5cbc79e4ed4fc340e60dcaf" - integrity sha512-+SicSJoKouPctL+j1pqktRVCgy+xAch1hWWTMy13j0IflnyNjaoskj+DwRQFimHbLqO3sq2oN2CXMvXq3Bgapg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz#fb3bcc4ee4198e7385805007373d6b6f42c98220" + integrity sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.7.0" + "@babel/helper-create-regexp-features-plugin" "^7.7.4" -"@babel/plugin-transform-new-target@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" - integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA== +"@babel/plugin-transform-new-target@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz#4a0753d2d60639437be07b592a9e58ee00720167" + integrity sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-object-super@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9" - integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ== +"@babel/plugin-transform-object-super@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz#48488937a2d586c0148451bf51af9d7dda567262" + integrity sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" + "@babel/helper-replace-supers" "^7.7.4" -"@babel/plugin-transform-parameters@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" - integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw== +"@babel/plugin-transform-parameters@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz#da4555c97f39b51ac089d31c7380f03bca4075ce" + integrity sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw== dependencies: - "@babel/helper-call-delegate" "^7.4.4" - "@babel/helper-get-function-arity" "^7.0.0" + "@babel/helper-call-delegate" "^7.7.4" + "@babel/helper-get-function-arity" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-property-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" - integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ== +"@babel/plugin-transform-property-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz#2388d6505ef89b266103f450f9167e6bd73f98c2" + integrity sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-regenerator@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.0.tgz#f1b20b535e7716b622c99e989259d7dd942dd9cc" - integrity sha512-AXmvnC+0wuj/cFkkS/HFHIojxH3ffSXE+ttulrqWjZZRaUOonfJc60e1wSNT4rV8tIunvu/R3wCp71/tLAa9xg== +"@babel/plugin-transform-regenerator@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz#d18eac0312a70152d7d914cbed2dc3999601cfc0" + integrity sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw== dependencies: regenerator-transform "^0.14.0" -"@babel/plugin-transform-reserved-words@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" - integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw== +"@babel/plugin-transform-reserved-words@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz#6a7cf123ad175bb5c69aec8f6f0770387ed3f1eb" + integrity sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-shorthand-properties@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" - integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== +"@babel/plugin-transform-shorthand-properties@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz#74a0a9b2f6d67a684c6fbfd5f0458eb7ba99891e" + integrity sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-spread@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz#fc77cf798b24b10c46e1b51b1b88c2bf661bb8dd" - integrity sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg== +"@babel/plugin-transform-spread@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz#aa673b356fe6b7e70d69b6e33a17fef641008578" + integrity sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-sticky-regex@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" - integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== +"@babel/plugin-transform-sticky-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz#ffb68c05090c30732076b1285dc1401b404a123c" + integrity sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-regex" "^7.0.0" -"@babel/plugin-transform-template-literals@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" - integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g== +"@babel/plugin-transform-template-literals@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz#1eb6411736dd3fe87dbd20cc6668e5121c17d604" + integrity sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-typeof-symbol@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" - integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== +"@babel/plugin-transform-typeof-symbol@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz#3174626214f2d6de322882e498a38e8371b2140e" + integrity sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-transform-unicode-regex@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.0.tgz#743d9bcc44080e3cc7d49259a066efa30f9187a3" - integrity sha512-RrThb0gdrNwFAqEAAx9OWgtx6ICK69x7i9tCnMdVrxQwSDp/Abu9DXFU5Hh16VP33Rmxh04+NGW28NsIkFvFKA== +"@babel/plugin-transform-unicode-regex@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz#a3c0f65b117c4c81c5b6484f2a5e7b95346b83ae" + integrity sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.7.0" + "@babel/helper-create-regexp-features-plugin" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" -"@babel/preset-env@^7.7.1": - version "7.7.1" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.1.tgz#04a2ff53552c5885cf1083e291c8dd5490f744bb" - integrity sha512-/93SWhi3PxcVTDpSqC+Dp4YxUu3qZ4m7I76k0w73wYfn7bGVuRIO4QUz95aJksbS+AD1/mT1Ie7rbkT0wSplaA== +"@babel/preset-env@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.7.4.tgz#ccaf309ae8d1ee2409c85a4e2b5e280ceee830f8" + integrity sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g== dependencies: - "@babel/helper-module-imports" "^7.7.0" + "@babel/helper-module-imports" "^7.7.4" "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-async-generator-functions" "^7.7.0" - "@babel/plugin-proposal-dynamic-import" "^7.7.0" - "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.6.2" - "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.7.0" - "@babel/plugin-syntax-async-generators" "^7.2.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" - "@babel/plugin-syntax-top-level-await" "^7.7.0" - "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.7.0" - "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.3" - "@babel/plugin-transform-classes" "^7.7.0" - "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.6.0" - "@babel/plugin-transform-dotall-regex" "^7.7.0" - "@babel/plugin-transform-duplicate-keys" "^7.5.0" - "@babel/plugin-transform-exponentiation-operator" "^7.2.0" - "@babel/plugin-transform-for-of" "^7.4.4" - "@babel/plugin-transform-function-name" "^7.7.0" - "@babel/plugin-transform-literals" "^7.2.0" - "@babel/plugin-transform-member-expression-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.5.0" - "@babel/plugin-transform-modules-commonjs" "^7.7.0" - "@babel/plugin-transform-modules-systemjs" "^7.7.0" - "@babel/plugin-transform-modules-umd" "^7.7.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.0" - "@babel/plugin-transform-new-target" "^7.4.4" - "@babel/plugin-transform-object-super" "^7.5.5" - "@babel/plugin-transform-parameters" "^7.4.4" - "@babel/plugin-transform-property-literals" "^7.2.0" - "@babel/plugin-transform-regenerator" "^7.7.0" - "@babel/plugin-transform-reserved-words" "^7.2.0" - "@babel/plugin-transform-shorthand-properties" "^7.2.0" - "@babel/plugin-transform-spread" "^7.6.2" - "@babel/plugin-transform-sticky-regex" "^7.2.0" - "@babel/plugin-transform-template-literals" "^7.4.4" - "@babel/plugin-transform-typeof-symbol" "^7.2.0" - "@babel/plugin-transform-unicode-regex" "^7.7.0" - "@babel/types" "^7.7.1" + "@babel/plugin-proposal-async-generator-functions" "^7.7.4" + "@babel/plugin-proposal-dynamic-import" "^7.7.4" + "@babel/plugin-proposal-json-strings" "^7.7.4" + "@babel/plugin-proposal-object-rest-spread" "^7.7.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.7.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.7.4" + "@babel/plugin-syntax-async-generators" "^7.7.4" + "@babel/plugin-syntax-dynamic-import" "^7.7.4" + "@babel/plugin-syntax-json-strings" "^7.7.4" + "@babel/plugin-syntax-object-rest-spread" "^7.7.4" + "@babel/plugin-syntax-optional-catch-binding" "^7.7.4" + "@babel/plugin-syntax-top-level-await" "^7.7.4" + "@babel/plugin-transform-arrow-functions" "^7.7.4" + "@babel/plugin-transform-async-to-generator" "^7.7.4" + "@babel/plugin-transform-block-scoped-functions" "^7.7.4" + "@babel/plugin-transform-block-scoping" "^7.7.4" + "@babel/plugin-transform-classes" "^7.7.4" + "@babel/plugin-transform-computed-properties" "^7.7.4" + "@babel/plugin-transform-destructuring" "^7.7.4" + "@babel/plugin-transform-dotall-regex" "^7.7.4" + "@babel/plugin-transform-duplicate-keys" "^7.7.4" + "@babel/plugin-transform-exponentiation-operator" "^7.7.4" + "@babel/plugin-transform-for-of" "^7.7.4" + "@babel/plugin-transform-function-name" "^7.7.4" + "@babel/plugin-transform-literals" "^7.7.4" + "@babel/plugin-transform-member-expression-literals" "^7.7.4" + "@babel/plugin-transform-modules-amd" "^7.7.4" + "@babel/plugin-transform-modules-commonjs" "^7.7.4" + "@babel/plugin-transform-modules-systemjs" "^7.7.4" + "@babel/plugin-transform-modules-umd" "^7.7.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.7.4" + "@babel/plugin-transform-new-target" "^7.7.4" + "@babel/plugin-transform-object-super" "^7.7.4" + "@babel/plugin-transform-parameters" "^7.7.4" + "@babel/plugin-transform-property-literals" "^7.7.4" + "@babel/plugin-transform-regenerator" "^7.7.4" + "@babel/plugin-transform-reserved-words" "^7.7.4" + "@babel/plugin-transform-shorthand-properties" "^7.7.4" + "@babel/plugin-transform-spread" "^7.7.4" + "@babel/plugin-transform-sticky-regex" "^7.7.4" + "@babel/plugin-transform-template-literals" "^7.7.4" + "@babel/plugin-transform-typeof-symbol" "^7.7.4" + "@babel/plugin-transform-unicode-regex" "^7.7.4" + "@babel/types" "^7.7.4" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/template@^7.1.0", "@babel/template@^7.4.4": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" - integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" - -"@babel/template@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.0.tgz#4fadc1b8e734d97f56de39c77de76f2562e597d0" - integrity sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ== +"@babel/template@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" + integrity sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.7.0" - "@babel/types" "^7.7.0" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516" - integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.0.tgz#9f5744346b8d10097fd2ec2eeffcaf19813cbfaf" - integrity sha512-ea/3wRZc//e/uwCpuBX2itrhI0U9l7+FsrKWyKGNyvWbuMcCG7ATKY2VI4wlg2b2TA39HHwIxnvmXvtiKsyn7w== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.7.0" - "@babel/helper-function-name" "^7.7.0" - "@babel/helper-split-export-declaration" "^7.7.0" - "@babel/parser" "^7.7.0" - "@babel/types" "^7.7.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" + "@babel/parser" "^7.7.4" + "@babel/types" "^7.7.4" -"@babel/traverse@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.2.tgz#ef0a65e07a2f3c550967366b3d9b62a2dcbeae09" - integrity sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw== +"@babel/traverse@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.7.4.tgz#9c1e7c60fb679fe4fcfaa42500833333c2058558" + integrity sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.7.2" - "@babel/helper-function-name" "^7.7.0" - "@babel/helper-split-export-declaration" "^7.7.0" - "@babel/parser" "^7.7.2" - "@babel/types" "^7.7.2" + "@babel/generator" "^7.7.4" + "@babel/helper-function-name" "^7.7.4" + "@babel/helper-split-export-declaration" "^7.7.4" + "@babel/parser" "^7.7.4" + "@babel/types" "^7.7.4" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" - integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.7.0", "@babel/types@^7.7.1": - version "7.7.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.1.tgz#8b08ea368f2baff236613512cf67109e76285827" - integrity sha512-kN/XdANDab9x1z5gcjDc9ePpxexkt+1EQ2MQUiM4XnMvQfvp87/+6kY4Ko2maLXH+tei/DgJ/ybFITeqqRwDiA== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" - -"@babel/types@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.2.tgz#550b82e5571dcd174af576e23f0adba7ffc683f7" - integrity sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA== +"@babel/types@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.7.4.tgz#516570d539e44ddf308c07569c258ff94fde9193" + integrity sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA== dependencies: esutils "^2.0.2" lodash "^4.17.13" to-fast-properties "^2.0.0" -"@mdi/font@^4.5.95": - version "4.5.95" - resolved "https://registry.yarnpkg.com/@mdi/font/-/font-4.5.95.tgz#43c0d2e7b08d4221a778f58d899455d0c45916ed" - integrity sha512-AjR2Zgu1feBXWlTfEjD6JQqLAMCqYn2Gzia5PWqFnysvz5F6JmPHtQFldIHXqyv2s/FwME7ZDBc5N86NEHbyvQ== +"@mdi/font@^4.6.95": + version "4.6.95" + resolved "https://registry.yarnpkg.com/@mdi/font/-/font-4.6.95.tgz#f97ecbdfd59c183d19f3a24a6fe4f84f579cf287" + integrity sha512-m5AjVnVS9Xi4xbQQt2rh+ClVcWT9oFqSmo8mZs7teOGbAmUrZ9km1RuNRNKIr7LV3vy/zCeEfMc154Z9l+ab0g== "@types/q@^1.5.1": version "1.5.2" @@ -1352,6 +1176,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +bulma-switch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bulma-switch/-/bulma-switch-2.0.0.tgz#7a187cd1bb8073e9a542478d936b89315f1ffcd8" + integrity sha512-myD38zeUfjmdduq+pXabhJEe3x2hQP48l/OI+Y0fO3HdDynZUY/VJygucvEAJKRjr4HxD5DnEm4yx+oDOBXpAA== + bulma-tooltip@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/bulma-tooltip/-/bulma-tooltip-3.0.2.tgz#2cf0abab1de2eba07f9d84eb7f07a8a88819ea92" @@ -2286,13 +2115,13 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== -file-loader@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.2.0.tgz#5fb124d2369d7075d70a9a5abecd12e60a95215e" - integrity sha512-+xZnaK5R8kBJrHK0/6HRlrKNamvVS5rjyuju+rnyxRGuwUJwpAMsVzUl5dz6rK8brkzjV6JpcFNjp6NqV0g1OQ== +file-loader@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.3.0.tgz#780f040f729b3d18019f20605f723e844b8a58af" + integrity sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA== dependencies: loader-utils "^1.2.3" - schema-utils "^2.0.0" + schema-utils "^2.5.0" fill-range@^4.0.0: version "4.0.0" @@ -4463,10 +4292,10 @@ sass-loader@^8.0.0: schema-utils "^2.1.0" semver "^6.3.0" -sass@^1.23.6: - version "1.23.6" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.23.6.tgz#7cce6dbeac1b8caeb33301a817642ee2708f7d7e" - integrity sha512-awBvj9xrAuiS2TOCcYSUGCmaBV3UW6fVSK4oJ2LHS8IRfnRLc5EJihw90C7ZJ/skcEwFGSf9/XO5NlMiKupBCg== +sass@^1.23.7: + version "1.23.7" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.23.7.tgz#090254e006af1219d442f1bff31e139d5e085dff" + integrity sha512-cYgc0fanwIpi0rXisGxl+/wadVQ/HX3RhpdRcjLdj2o2ye/sxUTpAxIhbmJy3PLQgRFbf6Pn8Jsrta2vdXcoOQ== dependencies: chokidar ">=2.0.0 <4.0.0" @@ -4492,10 +4321,10 @@ schema-utils@^2.0.0, schema-utils@^2.1.0: ajv "^6.10.2" ajv-keywords "^3.4.1" -schema-utils@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56" - integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w== +schema-utils@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f" + integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ== dependencies: ajv "^6.10.2" ajv-keywords "^3.4.1" @@ -4962,11 +4791,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -5092,14 +4916,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.2.0.tgz#af321aece1fd0d683adc8aaeb27829f29c75b46e" - integrity sha512-G8nk3np8ZAnwhHXas1JxJEwJyQdqFXAKJehfgZ/XrC48volFBRtO+FIKtF2u0Ma3bw+4vnDVjHPAQYlF9p2vsw== +url-loader@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b" + integrity sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog== dependencies: loader-utils "^1.2.3" mime "^2.4.4" - schema-utils "^2.4.1" + schema-utils "^2.5.0" url@^0.11.0: version "0.11.0" diff --git a/grafana/Dockerfile b/grafana/Dockerfile index cb741e70db..2d0020c95e 100644 --- a/grafana/Dockerfile +++ b/grafana/Dockerfile @@ -1,4 +1,4 @@ -FROM grafana/grafana:6.5.0-beta1 +FROM grafana/grafana:6.3.7 ENV GF_ANALYTICS_REPORTING_ENABLED=FALSE \ GF_AUTH_BASIC_ENABLED=false \ diff --git a/grafana/dashboards/charging-stats.json b/grafana/dashboards/charging-stats.json index 7d30c81f87..190e271028 100644 --- a/grafana/dashboards/charging-stats.json +++ b/grafana/dashboards/charging-stats.json @@ -15,7 +15,7 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "iteration": 1569440284783, + "iteration": 1574711546516, "links": [], "panels": [ { @@ -31,8 +31,7 @@ "repeat": "car_id", "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -94,8 +93,7 @@ ], "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -208,8 +206,7 @@ ], "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -267,15 +264,250 @@ ], "valueName": "avg" }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "linear", + "colorScheme": "interpolateGreens", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "timeseries", + "datasource": "TeslaMate", + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 4 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 15, + "legend": { + "show": false + }, + "options": {}, + "reverseYBuckets": false, + "scopedVars": { + "car_id": { + "selected": false, + "value": "1" + } + }, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n $__time(start_date),\n start_battery_level,\n end_battery_level\nFROM\n charging_processes\nWHERE\n $__timeFilter(start_date)\n\tAND duration_min > 3\n\tAND car_id = $car_id\nORDER BY \n start_date;", + "refId": "A", + "select": [ + [ + { + "params": [ + "charge_energy_added" + ], + "type": "column" + } + ] + ], + "table": "charges", + "timeColumn": "date", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Charge Heatmap", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "TeslaMate", + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "scopedVars": { + "car_id": { + "selected": false, + "value": "1" + } + }, + "seriesOverrides": [ + { + "alias": "end_battery_level", + "color": "#73BF69", + "fillBelowTo": "start_battery_level", + "lines": false + }, + { + "alias": "start_battery_level", + "lines": false + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n $__time(start_date),\n start_battery_level,\n end_battery_level\nFROM\n charging_processes\nWHERE\n $__timeFilter(start_date)\n\tAND duration_min > 3\n\tAND car_id = $car_id\nORDER BY \n start_date;", + "refId": "A", + "select": [ + [ + { + "params": [ + "charge_energy_added" + ], + "type": "column" + } + ] + ], + "table": "charges", + "timeColumn": "date", + "timeColumnType": "timestamp", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [ + { + "colorMode": "ok", + "fill": false, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "colorMode": "ok", + "fill": false, + "line": true, + "op": "lt", + "value": 20, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Charge Delta", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "short", + "label": null, + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "columns": [], "datasource": "TeslaMate", "fontSize": "100%", "gridPos": { "h": 14, - "w": 7, + "w": 3, "x": 0, - "y": 4 + "y": 10 }, "id": 2, "links": [], @@ -283,8 +515,7 @@ "pageSize": null, "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -373,7 +604,116 @@ ] } ], - "title": "SOC Stats", + "title": "Charge Stats", + "transform": "table", + "type": "table" + }, + { + "columns": [], + "datasource": "TeslaMate", + "fontSize": "100%", + "gridPos": { + "h": 14, + "w": 3, + "x": 3, + "y": 10 + }, + "id": 13, + "links": [], + "options": {}, + "pageSize": null, + "scopedVars": { + "car_id": { + "selected": false, + "value": "1" + } + }, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "SOC", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 0, + "mappingType": 1, + "pattern": "soc", + "thresholds": [ + "10", + "20" + ], + "type": "number", + "unit": "percent" + }, + { + "alias": "# Charges", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 0, + "mappingType": 1, + "pattern": "n", + "thresholds": [], + "type": "number", + "unit": "short" + }, + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 0, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "locale" + } + ], + "targets": [ + { + "format": "table", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT\n\tROUND(start_battery_level / 5, 0) * 5 AS SOC,\n\tcount(*) AS n\nFROM\n\tcharging_processes\nWHERE\n\tduration_min > 3\n\tAND car_id = $car_id\nGROUP BY\n 1\nORDER BY\n\tSOC DESC", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "title": "Discharge Stats", "transform": "table", "type": "table" }, @@ -384,16 +724,15 @@ "gridPos": { "h": 14, "w": 9, - "x": 7, - "y": 4 + "x": 6, + "y": 10 }, "id": 4, "options": {}, "pageSize": null, "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -484,17 +823,16 @@ "fontSize": "100%", "gridPos": { "h": 14, - "w": 8, - "x": 16, - "y": 4 + "w": 9, + "x": 15, + "y": 10 }, "id": 6, "options": {}, "pageSize": null, "scopedVars": { "car_id": { - "selected": true, - "text": "1", + "selected": false, "value": "1" } }, @@ -595,6 +933,7 @@ "type": "table" } ], + "refresh": false, "schemaVersion": 19, "style": "dark", "tags": [], @@ -632,7 +971,7 @@ "to": "now" }, "timepicker": { - "hidden": true, + "hidden": false, "refresh_intervals": [ "5s", "10s", diff --git a/grafana/dashboards/overview-dash.json b/grafana/dashboards/overview-dash.json index f600e8e549..c6fe5d16f5 100644 --- a/grafana/dashboards/overview-dash.json +++ b/grafana/dashboards/overview-dash.json @@ -41,6 +41,7 @@ "type": "row" }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -77,7 +78,7 @@ { "color": "light-red", "value": 90 - }, + } ], "title": "", "unit": "percent" @@ -127,6 +128,7 @@ "type": "gauge" }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -204,6 +206,7 @@ "type": "gauge" }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -284,6 +287,7 @@ "bars": false, "dashLength": 10, "dashes": false, + "datasource": "TeslaMate", "fill": 1, "fillGradient": 0, "gridPos": { @@ -415,6 +419,7 @@ "rgba(237, 129, 40, 0.89)", "#d44a3a" ], + "datasource": "TeslaMate", "decimals": 0, "format": "none", "gauge": { @@ -527,6 +532,7 @@ "rgba(237, 129, 40, 0.89)", "#d44a3a" ], + "datasource": "TeslaMate", "decimals": 0, "format": "none", "gauge": { @@ -645,6 +651,7 @@ "rgba(237, 129, 40, 0.89)", "#d44a3a" ], + "datasource": "TeslaMate", "format": "none", "gauge": { "maxValue": 100, @@ -760,6 +767,7 @@ "bars": false, "dashLength": 10, "dashes": false, + "datasource": "TeslaMate", "fill": 1, "fillGradient": 0, "gridPos": { @@ -921,6 +929,7 @@ } }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -997,6 +1006,7 @@ "type": "gauge" }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -1073,6 +1083,7 @@ "type": "gauge" }, { + "datasource": "TeslaMate", "gridPos": { "h": 4, "w": 3, @@ -1158,8 +1169,9 @@ { "allValue": null, "current": { - "text": "1", - "value": "1" + "selected": false, + "text": "All", + "value": "$__all" }, "datasource": "TeslaMate", "definition": "SELECT name AS __text, id AS __value FROM cars;", diff --git a/lib/teslamate/application.ex b/lib/teslamate/application.ex index 6c2403c807..a721485ce0 100644 --- a/lib/teslamate/application.ex +++ b/lib/teslamate/application.ex @@ -7,8 +7,9 @@ defmodule TeslaMate.Application do [ TeslaMate.Repo, TeslaMate.Api, + TeslaMate.Locations, TeslaMateWeb.Endpoint, - TeslaMate.Mapping, + TeslaMate.Terrain, TeslaMate.Vehicles, if(mqtt_enabled?(), do: TeslaMate.Mqtt), TeslaMate.Repair diff --git a/lib/teslamate/locations.ex b/lib/teslamate/locations.ex index 6f92b15c2f..f6eeb3c85b 100644 --- a/lib/teslamate/locations.ex +++ b/lib/teslamate/locations.ex @@ -3,13 +3,19 @@ defmodule TeslaMate.Locations do The Locations context. """ + require Logger + import Ecto.Query, warn: false import TeslaMate.CustomExpressions - alias __MODULE__.{Address, Geocoder, GeoFence} + alias __MODULE__.{Address, Geocoder, GeoFence, Cache} alias TeslaMate.Log.{Drive, Position, ChargingProcess} alias TeslaMate.{Repo, Log} + def child_spec(_arg) do + %{id: __MODULE__, start: {Cachex, :start_link, [Cache, [limit: 100]]}} + end + ## Address def create_address(attrs \\ %{}) do @@ -116,7 +122,9 @@ defmodule TeslaMate.Locations do end def get_geofence!(id) do - Repo.get!(GeoFence, id) + GeoFence + |> Repo.get!(id) + |> Repo.preload([:sleep_mode_blacklist, :sleep_mode_whitelist]) end def find_geofence(%{latitude: _, longitude: _} = point) do @@ -128,6 +136,49 @@ defmodule TeslaMate.Locations do |> Repo.one() end + alias TeslaMate.Settings.CarSettings + alias TeslaMate.Log.Car + + def may_fall_asleep_at?( + %Car{id: id, settings: %CarSettings{sleep_mode_enabled: enabled}}, + %{latitude: lat, longitude: lng} = position + ) do + key = {id, {lat, lng}, enabled} + + result = + Cachex.fetch(Cache, key, fn -> + assoc = if enabled, do: :sleep_mode_blacklist, else: :sleep_mode_whitelist + + query = + GeoFence + |> select([:id]) + |> join(:left, [geofence], c in assoc(geofence, ^assoc)) + |> where([geofence, c], c.id == ^id and within_geofence?(position, geofence, :left)) + |> limit(1) + + may_fall_asleep? = + if enabled do + Repo.one(query) == nil + else + Repo.one(query) != nil + end + + {:commit, may_fall_asleep?} + end) + + with {:commit, value} <- result do + Logger.debug("Inserted #{value} into #{Cache} for #{inspect(key)}") + {:ok, value} + end + end + + def clear_cache do + with {:ok, n} <- Cachex.clear(Cache) do + Logger.debug("Removed #{n} entrie(s) from #{Cache}") + :ok + end + end + def create_geofence(attrs) do Repo.transaction(fn -> with {:ok, geofence} <- %GeoFence{} |> GeoFence.changeset(attrs) |> Repo.insert(), @@ -143,7 +194,8 @@ defmodule TeslaMate.Locations do Repo.transaction(fn -> with {:ok, geofence} <- geofence |> GeoFence.changeset(attrs) |> Repo.update(), :ok <- remove_geofence(geofence), - :ok <- apply_geofence(geofence) do + :ok <- apply_geofence(geofence), + :ok <- clear_cache() do geofence else {:error, reason} -> Repo.rollback(reason) @@ -152,10 +204,15 @@ defmodule TeslaMate.Locations do end def delete_geofence(%GeoFence{} = geofence) do - with :ok <- remove_geofence(%GeoFence{geofence | apply_phase_correction: true}), - {:ok, geofence} <- Repo.delete(geofence) do - {:ok, geofence} - end + Repo.transaction(fn -> + with :ok <- remove_geofence(%GeoFence{geofence | apply_phase_correction: true}), + {:ok, geofence} <- Repo.delete(geofence), + :ok <- clear_cache() do + geofence + else + {:error, reason} -> Repo.rollback(reason) + end + end) end def change_geofence(%GeoFence{} = geofence, attrs \\ %{}) do diff --git a/lib/teslamate/locations/geo_fence.ex b/lib/teslamate/locations/geo_fence.ex index a8d5c26c57..6890d16dff 100644 --- a/lib/teslamate/locations/geo_fence.ex +++ b/lib/teslamate/locations/geo_fence.ex @@ -5,7 +5,7 @@ defmodule TeslaMate.Locations.GeoFence do import Ecto.Query import TeslaMate.CustomExpressions, only: [within_geofence?: 3] - alias TeslaMate.Log.{ChargingProcess, Drive} + alias TeslaMate.Log.{ChargingProcess, Drive, Car} schema "geofences" do field :name, :string @@ -23,6 +23,18 @@ defmodule TeslaMate.Locations.GeoFence do has_many :drives_start, Drive, foreign_key: :start_geofence_id, on_delete: :nilify_all has_many :drives_end, Drive, foreign_key: :end_geofence_id, on_delete: :nilify_all + many_to_many :sleep_mode_whitelist, Car, + join_through: "geofence_sleep_mode_whitelist", + join_keys: [geofence_id: :id, car_id: :id], + on_replace: :delete, + unique: true + + many_to_many :sleep_mode_blacklist, Car, + join_through: "geofence_sleep_mode_blacklist", + join_keys: [geofence_id: :id, car_id: :id], + on_replace: :delete, + unique: true + timestamps() end @@ -37,6 +49,8 @@ defmodule TeslaMate.Locations.GeoFence do :phase_correction, :apply_phase_correction ]) + |> put_assoc_if(attrs, :sleep_mode_blacklist) + |> put_assoc_if(attrs, :sleep_mode_whitelist) |> validate_required([:name, :latitude, :longitude, :radius]) |> validate_number(:radius, greater_than: 0, less_than: 1000) |> validate_number(:phase_correction, greater_than: 0, less_than_or_equal_to: 3) @@ -58,6 +72,13 @@ defmodule TeslaMate.Locations.GeoFence do end) end + defp put_assoc_if(changeset, attrs, key) do + case attrs[key] || attrs["#{key}"] do + nil -> changeset + val -> put_assoc(changeset, key, val) + end + end + defp where_exclude(query, %__MODULE__{id: nil}), do: query defp where_exclude(query, %__MODULE__{id: id}), do: where(query, [g], g.id != ^id) end diff --git a/lib/teslamate/log.ex b/lib/teslamate/log.ex index 798a5c3a4c..8765298e37 100644 --- a/lib/teslamate/log.ex +++ b/lib/teslamate/log.ex @@ -9,7 +9,7 @@ defmodule TeslaMate.Log do import Ecto.Query, warn: false alias __MODULE__.{Car, Drive, Update, ChargingProcess, Charge, Position, State} - alias TeslaMate.{Repo, Locations, Mapping, Settings} + alias TeslaMate.{Repo, Locations, Terrain, Settings} alias TeslaMate.Locations.GeoFence alias TeslaMate.Settings.{CarSettings, GlobalSettings} @@ -90,13 +90,13 @@ defmodule TeslaMate.Log do ## Position - @mapping (case Mix.env() do - :test -> MappingMock - _____ -> Mapping + @terrain (case Mix.env() do + :test -> TerrainMock + _____ -> Terrain end) def insert_position(%Drive{id: id, car_id: car_id}, attrs) do - elevation = @mapping.get_elevation({attrs.latitude, attrs.longitude}) + elevation = @terrain.get_elevation({attrs.latitude, attrs.longitude}) attrs = Map.put(attrs, :elevation, elevation) %Position{car_id: car_id, drive_id: id} @@ -105,6 +105,9 @@ defmodule TeslaMate.Log do end def insert_position(%Car{id: id}, attrs) do + elevation = @terrain.get_elevation({attrs.latitude, attrs.longitude}) + attrs = Map.put(attrs, :elevation, elevation) + %Position{car_id: id} |> Position.changeset(attrs) |> Repo.insert() diff --git a/lib/teslamate/settings.ex b/lib/teslamate/settings.ex index 06989e4dc1..9cc135836b 100644 --- a/lib/teslamate/settings.ex +++ b/lib/teslamate/settings.ex @@ -7,8 +7,8 @@ defmodule TeslaMate.Settings do alias TeslaMate.Repo alias __MODULE__.{GlobalSettings, CarSettings} + alias TeslaMate.{Log, Locations} alias TeslaMate.Log.Car - alias TeslaMate.Log def get_global_settings! do case Repo.all(GlobalSettings) do @@ -22,6 +22,12 @@ defmodule TeslaMate.Settings do |> Repo.all() end + def get_car_settings!(%Car{settings_id: id}) do + CarSettings + |> Repo.get!(id) + |> Repo.preload(:car) + end + def update_global_settings(%GlobalSettings{} = pre, attrs) do Repo.transaction(fn -> with {:ok, post} <- pre |> GlobalSettings.changeset(attrs) |> Repo.update(), @@ -33,10 +39,11 @@ defmodule TeslaMate.Settings do end) end - def update_car_settings(%CarSettings{car: %Car{}} = settings, attrs) do + def update_car_settings(%CarSettings{car: %Car{}} = pre, attrs) do Repo.transaction(fn -> - with {:ok, post} <- settings |> CarSettings.changeset(attrs) |> Repo.update(), - :ok <- broadcast(settings.car, post) do + with {:ok, post} <- pre |> CarSettings.changeset(attrs) |> Repo.update(), + :ok <- on_sleep_mode_change(pre, post), + :ok <- broadcast(pre.car, post) do post else {:error, reason} -> Repo.rollback(reason) @@ -58,11 +65,34 @@ defmodule TeslaMate.Settings do Phoenix.PubSub.subscribe(TeslaMate.PubSub, topic(car)) end - defp on_range_change(%GlobalSettings{preferred_range: pf}, %GlobalSettings{preferred_range: pf}), - do: :ok + defp on_range_change(%GlobalSettings{preferred_range: pf}, %GlobalSettings{preferred_range: pf}) do + :ok + end + + defp on_range_change(%GlobalSettings{}, %GlobalSettings{} = new) do + Log.recalculate_efficiencies(new) + end + + defp on_sleep_mode_change(%CarSettings{sleep_mode_enabled: m}, %CarSettings{ + sleep_mode_enabled: m + }) do + :ok + end + + defp on_sleep_mode_change(%CarSettings{}, %CarSettings{id: id}) do + %Car{id: car_id} = + Repo.one(from c in Car, select: [:id], where: c.settings_id == ^id, limit: 1) + + {:ok, %Postgrex.Result{num_rows: _rows}} = + Repo.query("DELETE FROM geofence_sleep_mode_whitelist WHERE car_id = $1", [car_id]) - defp on_range_change(%GlobalSettings{}, %GlobalSettings{} = new), - do: Log.recalculate_efficiencies(new) + {:ok, %Postgrex.Result{num_rows: _rows}} = + Repo.query("DELETE FROM geofence_sleep_mode_blacklist WHERE car_id = $1", [car_id]) + + :ok = Locations.clear_cache() + + :ok + end defp broadcast(car, settings) do Phoenix.PubSub.broadcast(TeslaMate.PubSub, topic(car), settings) diff --git a/lib/teslamate/settings/car_settings.ex b/lib/teslamate/settings/car_settings.ex index 2ff72b0d6a..753a939299 100644 --- a/lib/teslamate/settings/car_settings.ex +++ b/lib/teslamate/settings/car_settings.ex @@ -5,6 +5,8 @@ defmodule TeslaMate.Settings.CarSettings do alias TeslaMate.Log.Car schema "car_settings" do + field :sleep_mode_enabled, :boolean, default: true + field :suspend_min, :integer, default: 21 field :suspend_after_idle_min, :integer, default: 15 @@ -16,6 +18,7 @@ defmodule TeslaMate.Settings.CarSettings do end @all_fields [ + :sleep_mode_enabled, :suspend_min, :suspend_after_idle_min, :req_no_shift_state_reading, diff --git a/lib/teslamate/mapping.ex b/lib/teslamate/terrain.ex similarity index 99% rename from lib/teslamate/mapping.ex rename to lib/teslamate/terrain.ex index c9d86012b5..515eac5103 100644 --- a/lib/teslamate/mapping.ex +++ b/lib/teslamate/terrain.ex @@ -1,4 +1,4 @@ -defmodule TeslaMate.Mapping do +defmodule TeslaMate.Terrain do use GenStateMachine require Logger diff --git a/lib/teslamate/vehicles/vehicle.ex b/lib/teslamate/vehicles/vehicle.ex index 3c357f54f7..74faf297ec 100644 --- a/lib/teslamate/vehicles/vehicle.ex +++ b/lib/teslamate/vehicles/vehicle.ex @@ -16,7 +16,6 @@ defmodule TeslaMate.Vehicles.Vehicle do last_used: nil, last_response: nil, last_state_change: nil, - settings: nil, deps: %{}, task: nil @@ -98,12 +97,13 @@ defmodule TeslaMate.Vehicles.Vehicle do @impl true def init(opts) do - %Log.Car{settings: %CarSettings{} = settings} = car = Keyword.fetch!(opts, :car) + %Log.Car{settings: %CarSettings{}} = car = Keyword.fetch!(opts, :car) deps = %{ log: Keyword.get(opts, :deps_log, Log), api: Keyword.get(opts, :deps_api, Api), settings: Keyword.get(opts, :deps_settings, Settings), + locations: Keyword.get(opts, :deps_locations, Locations), vehicles: Keyword.get(opts, :deps_vehicles, Vehicles), pubsub: Keyword.get(opts, :deps_pubsub, Phoenix.PubSub) } @@ -117,7 +117,6 @@ defmodule TeslaMate.Vehicles.Vehicle do car: car, last_used: DateTime.utc_now(), last_state_change: last_state_change, - settings: settings, deps: deps } @@ -202,7 +201,7 @@ defmodule TeslaMate.Vehicles.Vehicle do def handle_event({:call, from}, :suspend_logging, _online, data) do with {:ok, %Vehicle{} = vehicle} <- fetch(data, expected_state: :online), - :ok <- can_fall_asleep(vehicle, data.settings) do + :ok <- can_fall_asleep(vehicle, data) do Logger.info("Suspending logging [Triggered manually]", car_id: data.car.id) {:next_state, {:suspended, :online}, @@ -210,7 +209,7 @@ defmodule TeslaMate.Vehicles.Vehicle do [ {:reply, from, :ok}, notify_subscribers(), - schedule_fetch(data.settings.suspend_min, :minutes) + schedule_fetch(data.car.settings.suspend_min, :minutes) ]} else {:error, reason} -> @@ -309,7 +308,8 @@ defmodule TeslaMate.Vehicles.Vehicle do end def handle_event(:info, %CarSettings{} = settings, _state, data) do - {:keep_state, %Data{data | settings: settings}} + Logger.debug("Received settings: #{inspect(settings, pretty: true)}") + {:keep_state, %Data{data | car: Map.put(data.car, :settings, settings)}} end def handle_event(:info, message, _state, _data) do @@ -793,11 +793,14 @@ defmodule TeslaMate.Vehicles.Vehicle do DateTime.from_unix!(ts, :millisecond) end - defp try_to_suspend(vehicle, current_state, %Data{car: car, settings: settings} = data) do + defp try_to_suspend(vehicle, current_state, %Data{car: %{settings: settings} = car} = data) do idle_min = diff_seconds(DateTime.utc_now(), data.last_used) / 60 suspend = idle_min >= settings.suspend_after_idle_min - case can_fall_asleep(vehicle, settings) do + case can_fall_asleep(vehicle, data) do + {:error, reason} when reason in [:sleep_mode_disabled, :sleep_mode_disabled_at_location] -> + {:keep_state_and_data, [notify_subscribers(), schedule_fetch(30)]} + {:error, :sentry_mode} -> {:keep_state, %Data{data | last_used: DateTime.utc_now()}, [notify_subscribers(), schedule_fetch(30)]} @@ -843,32 +846,41 @@ defmodule TeslaMate.Vehicles.Vehicle do end end - defp can_fall_asleep(vehicle, settings) do - case {vehicle, settings} do - {%Vehicle{vehicle_state: %VehicleState{is_user_present: true}}, _} -> + defp can_fall_asleep(vehicle, %Data{car: car, deps: deps}) do + {:ok, may_fall_asleep} = + call(deps.locations, :may_fall_asleep_at?, [car, vehicle.drive_state]) + + case {vehicle, car.settings, may_fall_asleep} do + {%Vehicle{}, %CarSettings{sleep_mode_enabled: false}, false} -> + {:error, :sleep_mode_disabled} + + {%Vehicle{}, %CarSettings{sleep_mode_enabled: true}, false} -> + {:error, :sleep_mode_disabled_at_location} + + {%Vehicle{vehicle_state: %VehicleState{is_user_present: true}}, _, true} -> {:error, :user_present} - {%Vehicle{climate_state: %Climate{is_preconditioning: true}}, _} -> + {%Vehicle{climate_state: %Climate{is_preconditioning: true}}, _, true} -> {:error, :preconditioning} - {%Vehicle{vehicle_state: %VehicleState{sentry_mode: true}}, _} -> + {%Vehicle{vehicle_state: %VehicleState{sentry_mode: true}}, _, true} -> {:error, :sentry_mode} {%Vehicle{vehicle_state: %VehicleState{locked: false}}, - %CarSettings{req_not_unlocked: true}} -> + %CarSettings{req_not_unlocked: true}, true} -> {:error, :unlocked} {%Vehicle{drive_state: %Drive{shift_state: shift_state}}, - %CarSettings{req_no_shift_state_reading: true}} + %CarSettings{req_no_shift_state_reading: true}, true} when not is_nil(shift_state) -> {:error, :shift_state} {%Vehicle{climate_state: %Climate{outside_temp: out_t, inside_temp: in_t}}, - %CarSettings{req_no_temp_reading: true}} + %CarSettings{req_no_temp_reading: true}, true} when not is_nil(out_t) or not is_nil(in_t) -> {:error, :temp_reading} - {%Vehicle{}, %CarSettings{}} -> + {%Vehicle{}, %CarSettings{}, true} -> :ok end end diff --git a/lib/teslamate_web.ex b/lib/teslamate_web.ex index b586b1f1e9..e426aba6bc 100644 --- a/lib/teslamate_web.ex +++ b/lib/teslamate_web.ex @@ -37,7 +37,16 @@ defmodule TeslaMateWeb do # Import convenience functions from controllers import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] - import Phoenix.LiveView, only: [live_render: 2, live_render: 3, live_link: 1, live_link: 2] + import Phoenix.LiveView, + only: [ + live_render: 2, + live_render: 3, + live_link: 1, + live_link: 2, + live_component: 2, + live_component: 3, + live_component: 4 + ] # Use all HTML functionality (forms, tags, etc) use Phoenix.HTML diff --git a/lib/teslamate_web/channels/liveview_socket.ex b/lib/teslamate_web/channels/liveview_socket.ex index 51137431f3..545e48ab46 100644 --- a/lib/teslamate_web/channels/liveview_socket.ex +++ b/lib/teslamate_web/channels/liveview_socket.ex @@ -8,21 +8,25 @@ defmodule TeslaMateWeb.LiveViewSocket do """ use Phoenix.Socket, log: :debug + if Version.match?(System.version(), ">= 1.8.0") do + @derive {Inspect, only: [:id, :endpoint, :view, :parent_pid, :root_id, :assigns, :changed]} + end + defstruct id: nil, endpoint: nil, view: nil, - router: nil, parent_pid: nil, root_pid: nil, assigns: %{}, changed: %{}, - temporary: %{}, - fingerprints: {nil, %{}}, private: %{}, + fingerprints: Phoenix.LiveView.Diff.new_fingerprints(), redirected: nil, connected?: false @type t :: %__MODULE__{} + @type unsigned_params :: map + @type assigns :: map channel "lv:*", Phoenix.LiveView.Channel diff --git a/lib/teslamate_web/live/car_live/summary.ex b/lib/teslamate_web/live/car_live/summary.ex index bec9d61a61..03bdec2b15 100644 --- a/lib/teslamate_web/live/car_live/summary.ex +++ b/lib/teslamate_web/live/car_live/summary.ex @@ -100,6 +100,9 @@ defmodule TeslaMateWeb.CarLive.Summary do defp translate_error(:user_present), do: gettext("User present") defp translate_error(:update_in_progress), do: gettext("Update in progress") + defp translate_error(:sleep_mode_disabled_at_location), + do: gettext("Sleep Mode is disabled at current location") + defp cancel_timer(nil), do: :ok defp cancel_timer(ref) when is_reference(ref), do: Process.cancel_timer(ref) diff --git a/lib/teslamate_web/live/geofence_live/edit.ex b/lib/teslamate_web/live/geofence_live/edit.ex deleted file mode 100644 index dc7ae919b9..0000000000 --- a/lib/teslamate_web/live/geofence_live/edit.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule TeslaMateWeb.GeoFenceLive.Edit do - use Phoenix.LiveView - - alias TeslaMateWeb.Router.Helpers, as: Routes - alias TeslaMateWeb.GeoFenceLive - alias TeslaMateWeb.GeoFenceView - - alias TeslaMate.Settings.GlobalSettings - alias TeslaMate.{Locations, Settings, Convert} - alias TeslaMate.Locations.GeoFence - - import TeslaMateWeb.Gettext - - @impl true - def render(assigns), do: GeoFenceView.render("edit.html", assigns) - - @impl true - def mount(_session, socket) do - {:ok, assign(socket, show_errors: false)} - end - - @impl true - def handle_params(%{"id" => id}, _uri, socket) do - %GeoFence{radius: radius} = geofence = Locations.get_geofence!(id) - - {unit_of_length, radius} = - case Settings.get_global_settings!() do - %GlobalSettings{unit_of_length: :km} -> {:m, radius} - %GlobalSettings{unit_of_length: :mi} -> {:ft, Convert.m_to_ft(radius)} - end - - assigns = %{ - geofence: geofence, - changeset: Locations.change_geofence(geofence, %{radius: round(radius)}), - unit_of_length: unit_of_length - } - - {:noreply, assign(socket, assigns)} - end - - @impl true - def handle_event("move", %{"lat" => lat, "lng" => lng}, socket) do - changeset = - socket.assigns.changeset - |> Ecto.Changeset.change(%{latitude: lat, longitude: lng}) - - {:noreply, assign(socket, changeset: changeset, show_errors: false)} - end - - def handle_event("validate", %{"geo_fence" => params}, socket) do - changeset = - socket.assigns.geofence - |> Locations.change_geofence(params) - |> Map.put(:action, :update) - - {:noreply, assign(socket, changeset: changeset, show_errors: false)} - end - - def handle_event("save", %{"geo_fence" => geofence_params}, socket) do - geofence_params = - Map.update(geofence_params, "radius", nil, fn radius -> - case socket.assigns.unit_of_length do - :ft -> with {radius, _} <- Float.parse(radius), do: Convert.ft_to_m(radius) - :m -> radius - end - end) - - case Locations.update_geofence(socket.assigns.geofence, geofence_params) do - {:ok, %GeoFence{name: name}} -> - {:stop, - socket - |> put_flash(:success, gettext("Geo-fence \"%{name}\" updated successfully", name: name)) - |> redirect(to: Routes.live_path(socket, GeoFenceLive.Index))} - - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, assign(socket, changeset: changeset, show_errors: true)} - end - end -end diff --git a/lib/teslamate_web/live/geofence_live/form.ex b/lib/teslamate_web/live/geofence_live/form.ex new file mode 100644 index 0000000000..6babed9c7e --- /dev/null +++ b/lib/teslamate_web/live/geofence_live/form.ex @@ -0,0 +1,188 @@ +defmodule TeslaMateWeb.GeoFenceLive.Form do + use Phoenix.LiveView + + require Logger + + alias TeslaMateWeb.{GeoFenceLive, GeoFenceView} + alias TeslaMateWeb.Router.Helpers, as: Routes + + alias TeslaMate.{Log, Locations, Settings, Convert} + alias TeslaMate.Settings.{GlobalSettings, CarSettings} + alias TeslaMate.Log.{Car, Position} + alias TeslaMate.Locations.GeoFence + + import TeslaMateWeb.Gettext + + @impl true + def render(assigns), do: GeoFenceView.render("form.html", assigns) + + @impl true + def mount(%{action: action}, socket) do + settings = Settings.get_global_settings!() + + {unit_of_length, radius} = + case settings do + %GlobalSettings{unit_of_length: :km} -> {:m, 20} + %GlobalSettings{unit_of_length: :mi} -> {:ft, 65} + end + + geofence = %GeoFence{radius: radius} + + assigns = %{ + action: action, + settings: settings, + geofence: geofence, + changeset: Locations.change_geofence(geofence), + unit_of_length: unit_of_length, + car_settings: Settings.get_car_settings(), + sleep_mode_whitelist: [], + sleep_mode_blacklist: [], + show_errors: false + } + + {:ok, assign(socket, assigns)} + end + + @impl true + def handle_params(%{"id" => id}, _uri, %{assigns: %{action: :edit}} = socket) do + %GeoFence{radius: radius} = geofence = Locations.get_geofence!(id) + + radius = + case socket.assigns.unit_of_length do + :m -> radius + :ft -> Convert.m_to_ft(radius) + end + + assigns = %{ + geofence: geofence, + changeset: Locations.change_geofence(geofence, %{radius: round(radius)}), + sleep_mode_whitelist: geofence.sleep_mode_whitelist, + sleep_mode_blacklist: geofence.sleep_mode_blacklist + } + + {:noreply, assign(socket, assigns)} + end + + def handle_params(%{"lat" => lat, "lng" => lng}, _uri, %{assigns: %{action: :new}} = socket) do + if connected?(socket), do: :ok = set_grafana_url(socket) + + changeset = + socket.assigns.geofence + |> Locations.change_geofence(%{latitude: lat, longitude: lng}) + + {:noreply, assign(socket, changeset: changeset)} + end + + def handle_params(_params, _uri, %{assigns: %{action: :new}} = socket) do + attrs = + case Log.get_latest_position() do + %Position{latitude: lat, longitude: lng} -> %{latitude: lat, longitude: lng} + nil -> %{} + end + + changeset = + socket.assigns.geofence + |> Locations.change_geofence(attrs) + + {:noreply, assign(socket, changeset: changeset)} + end + + @impl true + def handle_event("move", %{"lat" => lat, "lng" => lng}, socket) do + changeset = + socket.assigns.changeset + |> Ecto.Changeset.change(%{latitude: lat, longitude: lng}) + + {:noreply, assign(socket, changeset: changeset, show_errors: false)} + end + + def handle_event("validate", %{"geo_fence" => params}, socket) do + changeset = + socket.assigns.geofence + |> Locations.change_geofence(params) + |> Map.put(:action, :update) + + {:noreply, assign(socket, changeset: changeset, show_errors: false)} + end + + def handle_event("toggle", %{"checked" => value, "car" => id}, socket) do + %{ + car_settings: car_settings, + sleep_mode_blacklist: blacklist, + sleep_mode_whitelist: whitelist + } = socket.assigns + + car_id = String.to_integer(id) + + %CarSettings{sleep_mode_enabled: sleep_mode_enabled, car: car} = + Enum.find(car_settings, fn s -> s.car.id == car_id end) + + assigns = + if sleep_mode_enabled do + blacklist = + case value do + "false" -> Enum.uniq([car | blacklist]) + "true" -> Enum.reject(blacklist, &match?(%Car{id: ^car_id}, &1)) + end + + %{sleep_mode_blacklist: blacklist} + else + whitelist = + case value do + "false" -> Enum.reject(whitelist, &match?(%Car{id: ^car_id}, &1)) + "true" -> Enum.uniq([car | whitelist]) + end + + %{sleep_mode_whitelist: whitelist} + end + + {:noreply, assign(socket, assigns)} + end + + def handle_event("save", %{"geo_fence" => params}, socket) do + geofence_params = + params + |> Map.update("radius", nil, fn radius -> + case socket.assigns.unit_of_length do + :ft -> with {radius, _} <- Float.parse(radius), do: Convert.ft_to_m(radius) + :m -> radius + end + end) + |> Map.put("sleep_mode_blacklist", socket.assigns.sleep_mode_blacklist) + |> Map.put("sleep_mode_whitelist", socket.assigns.sleep_mode_whitelist) + + case socket.assigns.action do + :new -> Locations.create_geofence(geofence_params) + :edit -> Locations.update_geofence(socket.assigns.geofence, geofence_params) + end + |> case do + {:ok, %GeoFence{name: name}} -> + {:stop, + socket + |> put_flash(:success, flash_msg(socket.assigns.action, name)) + |> redirect(to: Routes.live_path(socket, GeoFenceLive.Index))} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, changeset: changeset, show_errors: true)} + end + end + + # Private + + defp set_grafana_url(%{assigns: %{settings: settings}} = socket) do + with nil <- settings.grafana_url, + %{"referrer" => referrer} when is_binary(referrer) <- get_connect_params(socket), + url = URI.parse(referrer), + [_, _, _ | path] <- url.path |> String.split("/") |> Enum.reverse(), + url = %URI{url | path: Enum.join([nil | path], "/"), query: nil} |> URI.to_string(), + {:ok, _settings} <- Settings.update_global_settings(settings, %{grafana_url: url}) do + :ok + else + {:error, reason} -> Logger.warn("Updating settings failed: #{inspect(reason)}") + _ -> :ok + end + end + + defp flash_msg(:new, name), do: gettext("Geo-fence \"%{name}\" created", name: name) + defp flash_msg(:edit, name), do: gettext("Geo-fence \"%{name}\" updated", name: name) +end diff --git a/lib/teslamate_web/live/geofence_live/new.ex b/lib/teslamate_web/live/geofence_live/new.ex deleted file mode 100644 index 4670522f4b..0000000000 --- a/lib/teslamate_web/live/geofence_live/new.ex +++ /dev/null @@ -1,93 +0,0 @@ -defmodule TeslaMateWeb.GeoFenceLive.New do - use Phoenix.LiveView - - alias TeslaMateWeb.Router.Helpers, as: Routes - alias TeslaMateWeb.GeoFenceLive - alias TeslaMateWeb.GeoFenceView - - alias TeslaMate.{Locations, Settings, Convert} - alias TeslaMate.Locations.GeoFence - alias TeslaMate.Log.Position - alias TeslaMate.Log - - import TeslaMateWeb.Gettext - - @impl true - def render(assigns), do: GeoFenceView.render("new.html", assigns) - - @impl true - def mount(_session, socket) do - alias Settings.GlobalSettings - - {unit_of_length, radius} = - case Settings.get_global_settings!() do - %GlobalSettings{unit_of_length: :km} -> {:m, 20} - %GlobalSettings{unit_of_length: :mi} -> {:ft, 65} - end - - geo_fence = %GeoFence{radius: radius} - - assigns = %{ - geo_fence: geo_fence, - changeset: Locations.change_geofence(geo_fence), - unit_of_length: unit_of_length, - show_errors: false - } - - {:ok, assign(socket, assigns)} - end - - @impl true - def handle_params(%{"lat" => lat, "lng" => lng}, _uri, socket) do - changeset = - socket.assigns.geo_fence - |> Locations.change_geofence(%{latitude: lat, longitude: lng}) - - {:noreply, assign(socket, changeset: changeset)} - end - - def handle_params(_params, _uri, socket) do - attrs = - case Log.get_latest_position() do - %Position{latitude: lat, longitude: lng} -> %{latitude: lat, longitude: lng} - nil -> %{} - end - - changeset = - socket.assigns.geo_fence - |> Locations.change_geofence(attrs) - - {:noreply, assign(socket, changeset: changeset)} - end - - @impl true - def handle_event("validate", %{"geo_fence" => params}, socket) do - changeset = - %GeoFence{} - |> Locations.change_geofence(params) - |> Map.put(:action, :insert) - - {:noreply, assign(socket, changeset: changeset, show_errors: false)} - end - - def handle_event("save", %{"geo_fence" => geofence_params}, socket) do - geofence_params = - Map.update(geofence_params, "radius", nil, fn radius -> - case socket.assigns.unit_of_length do - :ft -> with {radius, _} <- Float.parse(radius), do: Convert.ft_to_m(radius) - :m -> radius - end - end) - - case Locations.create_geofence(geofence_params) do - {:ok, %GeoFence{name: name}} -> - {:stop, - socket - |> put_flash(:success, gettext("Geo-fence \"%{name}\" created", name: name)) - |> redirect(to: Routes.live_path(socket, GeoFenceLive.Index))} - - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, assign(socket, changeset: changeset, show_errors: true)} - end - end -end diff --git a/lib/teslamate_web/live/settings_live/index.ex b/lib/teslamate_web/live/settings_live/index.ex index ad61d791f6..5c9da1ea6c 100644 --- a/lib/teslamate_web/live/settings_live/index.ex +++ b/lib/teslamate_web/live/settings_live/index.ex @@ -1,6 +1,8 @@ defmodule TeslaMateWeb.SettingsLive.Index do use Phoenix.LiveView + require Logger + alias TeslaMateWeb.Router.Helpers, as: Routes alias TeslaMateWeb.SettingsView alias TeslaMate.Settings.{GlobalSettings, CarSettings} @@ -51,15 +53,27 @@ defmodule TeslaMateWeb.SettingsLive.Index do {:noreply, assign(socket, global_settings: settings)} end - def handle_event("change", %{"car_settings" => params}, socket) do - %{assigns: %{car_settings: settings, car: id}} = socket + def handle_event("change", params, %{assigns: %{car_settings: settings, car: id}} = socket) do + orig = get_in(settings, [id, :original]) + + # workaround #1: switching between cars caused leex to not be re-evaluated. + # Solution: custom ":as" attribute on form_for/4 for each CarSetting changeset + params = params["car_settings_#{id}"] + + # workaround #2: enableding sleep mode caused previously disabled checkbox to be disabled + params = + if params["sleep_mode_enabled"] == "true" and not orig.sleep_mode_enabled do + %{"sleep_mode_enabled" => "true"} + else + params + end settings = - settings - |> get_in([id, :original]) + orig |> Settings.update_car_settings(params) |> case do {:error, changeset} -> + Logger.warn(inspect(changeset)) put_in(settings, [id, :changeset], changeset) {:ok, car_settings} -> diff --git a/lib/teslamate_web/router.ex b/lib/teslamate_web/router.ex index d1d2e2bc8b..b90d6e7259 100644 --- a/lib/teslamate_web/router.ex +++ b/lib/teslamate_web/router.ex @@ -24,8 +24,8 @@ defmodule TeslaMateWeb.Router do live "/sign_in", SignInLive.Index live "/settings", SettingsLive.Index live "/geo-fences", GeoFenceLive.Index - live "/geo-fences/new", GeoFenceLive.New - live "/geo-fences/:id/edit", GeoFenceLive.Edit + live "/geo-fences/new", GeoFenceLive.Form, session: [action: :new] + live "/geo-fences/:id/edit", GeoFenceLive.Form, session: [action: :edit] end scope "/api", TeslaMateWeb do diff --git a/lib/teslamate_web/templates/car/summary.html.leex b/lib/teslamate_web/templates/car/summary.html.leex index 26f704600b..fc2d3aae7a 100644 --- a/lib/teslamate_web/templates/car/summary.html.leex +++ b/lib/teslamate_web/templates/car/summary.html.leex @@ -212,9 +212,21 @@ not is_nil(@error) -> link @error, to: "#", class: "button is-danger is-small is-outlined is-fullwidth", disabled: true - @summary.state == :online and !@summary.sentry_mode and !@summary.is_user_present and !@summary.is_preconditioning and @summary.locked-> + @car.settings.sleep_mode_enabled and + @summary.state == :online and + !@summary.sentry_mode and + !@summary.is_user_present and + !@summary.is_preconditioning and + @summary.locked-> + disabled = + case Locations.may_fall_asleep_at?(@car, @summary) do + {:ok, may_fall_asleep?} -> not may_fall_asleep? + {:error, _reason} -> true + end + link gettext("try to sleep"), to: "#", phx_click: "suspend_logging", - class: "button is-info is-small is-outlined is-fullwidth" <> (if @loading, do: " is-loading", else: "") + class: "button is-info is-small is-outlined is-fullwidth" <> (if @loading, do: " is-loading", else: ""), + disabled: disabled @summary.state == :suspended -> link gettext("cancel sleep attempt"), to: "#", phx_click: "resume_logging", diff --git a/lib/teslamate_web/templates/geo_fence/edit.html.leex b/lib/teslamate_web/templates/geo_fence/edit.html.leex deleted file mode 100644 index 66f1e530fa..0000000000 --- a/lib/teslamate_web/templates/geo_fence/edit.html.leex +++ /dev/null @@ -1,9 +0,0 @@ -<nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li><%= link gettext("Home"), to: "/" %></li> - <li><%= link gettext("Geo-fences"), to: "/geo-fences" %></li> - <li class="is-active"><a href="#"><%= @geofence.name %></a></li> - </ul> -</nav> - -<%= render "form.html", assigns %> diff --git a/lib/teslamate_web/templates/geo_fence/form.html.leex b/lib/teslamate_web/templates/geo_fence/form.html.leex index 7601c4ec84..51397ae6ad 100644 --- a/lib/teslamate_web/templates/geo_fence/form.html.leex +++ b/lib/teslamate_web/templates/geo_fence/form.html.leex @@ -1,4 +1,17 @@ -<%= f = form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, class: "geofence"] %> +<nav class="breadcrumb" aria-label="breadcrumbs"> + <ul> + <li><%= link gettext("Home"), to: "/" %></li> + <li><%= link gettext("Geo-fences"), to: "/geo-fences" %></li> + <li class="is-active"><a href="#"><%= + case @action do + :new -> Ecto.Changeset.get_field(@changeset, :name) || "…" + :edit -> @geofence.name + end + %></a></li> + </ul> +</nav> + +<%= f = form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, class: "geofence", id: @geofence.id] %> <div class="field is-horizontal"> <div class="field-label is-normal"> <%= label f, :name, gettext("Name"), class: "label" %> @@ -94,6 +107,44 @@ </div> </div> + <div class="field is-horizontal mt-20"> + <div class="field-label is-normal"> + <%= label class: "label" do gettext("Sleep Mode Enabled") end %> + </div> + <div class="field-body"> + <table class="table is-fullwidth"> + <tbody> + <%= for settings <- @car_settings do %> + <tr> + <td class="has-text-weight-medium"><%= settings.car.name %></td> + <td align="right"> + <% + car_id = settings.car.id + + value = + if settings.sleep_mode_enabled do + Enum.find(@sleep_mode_blacklist, &match?(%Car{id: ^car_id}, &1)) == nil + else + Enum.find(@sleep_mode_whitelist, &match?(%Car{id: ^car_id}, &1)) != nil + end + %> + + <%= checkbox :sleep_mode, :"#{settings.car.id}", class: "switch is-rounded is-success ", + phx_click: "toggle", + phx_value_car: settings.car.id, + phx_value_checked: to_string(not value), + value: value + %> + <%= label :sleep_mode, :"#{settings.car.id}", nil %> + </td> + </tr> + <% end %> + + </tbody> + </table> + </div> + </div> + <div class="field is-horizontal mt-25"> <div class="field-label"></div> <div class="field-body"> diff --git a/lib/teslamate_web/templates/geo_fence/index.html.leex b/lib/teslamate_web/templates/geo_fence/index.html.leex index a0a7f6c956..e6d8f68844 100644 --- a/lib/teslamate_web/templates/geo_fence/index.html.leex +++ b/lib/teslamate_web/templates/geo_fence/index.html.leex @@ -11,7 +11,7 @@ <span class="mdi mdi-plus"></span> </span> """) , - to: Routes.live_path(@socket, GeoFenceLive.New), + to: Routes.live_path(@socket, GeoFenceLive.Form, session: [action: :new]), class: "button is-info is-small is-rounded is-pulled-right mb-20" %> @@ -39,7 +39,7 @@ <div class="field is-grouped is-pulled-right"> <p class="control"> <%= link raw("<span class='icon'><span class='mdi mdi-square-edit-outline'></span></span>"), - to: Routes.live_path(@socket, GeoFenceLive.Edit, geofence), + to: Routes.live_path(@socket, GeoFenceLive.Form, geofence, session: [action: :edit]), class: "button is-info is-small is-inverted is-light" %> </p> diff --git a/lib/teslamate_web/templates/geo_fence/new.html.leex b/lib/teslamate_web/templates/geo_fence/new.html.leex deleted file mode 100644 index ff5f2dbd98..0000000000 --- a/lib/teslamate_web/templates/geo_fence/new.html.leex +++ /dev/null @@ -1,9 +0,0 @@ -<nav class="breadcrumb" aria-label="breadcrumbs"> - <ul> - <li><%= link gettext("Home"), to: "/" %></li> - <li><%= link gettext("Geo-fences"), to: "/geo-fences" %></li> - <li class="is-active"><a href="#"><%= Ecto.Changeset.get_field(@changeset, :name) || "…" %></a></li> - </ul> -</nav> - -<%= render "form.html", assigns %> diff --git a/lib/teslamate_web/templates/settings/index.html.leex b/lib/teslamate_web/templates/settings/index.html.leex index d46b385fdb..da46e76638 100644 --- a/lib/teslamate_web/templates/settings/index.html.leex +++ b/lib/teslamate_web/templates/settings/index.html.leex @@ -8,7 +8,7 @@ <%= if Enum.count(@car_settings) > 0 do %> <div class="title-with-addon"> <h2 class="title is-4 is-marginless"> - <%= gettext("Sleep") %> + <%= gettext("Sleep Mode") %> </h2> <div class="dropdown is-right" phx-hook="Dropdown"> @@ -34,7 +34,23 @@ <div class="columns is-tablet is-centered"> <div class="column"> - <%= f = form_for get_in(@car_settings, [@car, :changeset]), "#", [phx_change: :change] %> + <%= f = form_for get_in(@car_settings, [@car, :changeset]), "#", [phx_change: :change, as: "car_settings_#{@car}"] %> + <% disabled = not Ecto.Changeset.get_field(f.source, :sleep_mode_enabled) %> + + <div class="field is-horizontal center-vertically"> + <div class="field-label is-normal is-paddingless"> + <%= label f, :sleep_mode_enabled, gettext("Enabled"), class: "label" %> + </div> + <div class="field-body"> + <div class="field"> + <div class="control"> + <%= checkbox(f, :sleep_mode_enabled, class: "switch is-rounded is-success") %> + <%= label f, :sleep_mode_enabled, nil %> + </div> + </div> + </div> + </div> + <div class="field is-horizontal center-vertically"> <div class="field-label is-normal"> <%= label f, :suspend_min, gettext("Time to Try Sleeping"), class: "label" %> @@ -45,7 +61,7 @@ <div class="select is-fullwidth"> <%= select(f, :suspend_min, (Enum.filter(12..30, & rem(&1, 3) == 0) ++ Enum.filter(35..90, & rem(&1, 5) == 0)) - |> Enum.map(&{"#{&1} #{gettext("min")}", &1})) + |> Enum.map(&{"#{&1} #{gettext("min")}", &1}), disabled: disabled) %> </div> </div> @@ -64,7 +80,7 @@ <%= select(f, :suspend_after_idle_min, 5..60 |> Enum.filter(& rem(&1, 5) == 0) - |> Enum.map(&{"#{&1} #{gettext("min")}", &1})) + |> Enum.map(&{"#{&1} #{gettext("min")}", &1}), disabled: disabled) %> </div> </div> @@ -73,7 +89,7 @@ </div> </div> - <div class="field is-horizontal center-vertically mb-0"> + <div class="field is-horizontal mb-0"> <div class="field-label is-normal is-paddingless"> <label class="label"><%= gettext("Requirements") %></label> </div> @@ -83,19 +99,19 @@ <div class="columns is-multiline is-mobile is-gapless"> <div class="column is-full"> <label class="checkbox"> - <%= checkbox(f, :req_no_shift_state_reading) %> + <%= checkbox(f, :req_no_shift_state_reading, disabled: disabled) %> <%= gettext("No shift state reading") %> </label> </div> <div class="column is-full"> <label class="checkbox"> - <%= checkbox(f, :req_no_temp_reading) %> + <%= checkbox(f, :req_no_temp_reading, disabled: disabled) %> <%= gettext("No temperature readings") %> </label> </div> <div class="column is-full"> <label class="checkbox"> - <%= checkbox(f, :req_not_unlocked) %> + <%= checkbox(f, :req_not_unlocked, disabled: disabled) %> <%= gettext("Vehicle must be locked") %> </label> </div> diff --git a/lib/teslamate_web/views/car_view.ex b/lib/teslamate_web/views/car_view.ex index b57e901504..bf5e30ffae 100644 --- a/lib/teslamate_web/views/car_view.ex +++ b/lib/teslamate_web/views/car_view.ex @@ -1,6 +1,8 @@ defmodule TeslaMateWeb.CarView do use TeslaMateWeb, :view + alias TeslaMate.Locations + def render("command_failed.json", %{reason: reason}) do %{error: reason} end diff --git a/lib/teslamate_web/views/geo_fence_view.ex b/lib/teslamate_web/views/geo_fence_view.ex index 3f1ac27338..c80f0a07f5 100644 --- a/lib/teslamate_web/views/geo_fence_view.ex +++ b/lib/teslamate_web/views/geo_fence_view.ex @@ -2,6 +2,7 @@ defmodule TeslaMateWeb.GeoFenceView do use TeslaMateWeb, :view alias TeslaMateWeb.GeoFenceLive + alias TeslaMate.Log.Car import TeslaMate.Convert, only: [m_to_ft: 1] end diff --git a/mix.exs b/mix.exs index 239820a062..fb4680262f 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule TeslaMate.MixProject do def project do [ app: :teslamate, - version: "1.13.0-dev", + version: "1.13.0", elixir: "~> 1.9", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), @@ -59,7 +59,8 @@ defmodule TeslaMate.MixProject do {:srtm, "~> 0.2"}, {:fuse, "~> 2.4"}, {:mock, "~> 0.3", only: :test}, - {:castore, "~> 0.1"} + {:castore, "~> 0.1"}, + {:cachex, "~> 3.2"} ] end diff --git a/mix.lock b/mix.lock index eb6076e631..9a1228c782 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,5 @@ %{ + "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm"}, "castore": {:hex, :castore, "0.1.4", "e7fd082c0e755716826a20b95c479f5dd42f536f9d12b8da8f47c92f1d4aed58", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, @@ -8,8 +9,9 @@ "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.2.1", "4eed4100cbb2abcff10c46660d6613693807bf64f1b865f414daccf762d3758d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.12.0", "50e17a1b116fdb7facc2fe127a94db246169f38d7627b391376a0bc418413ce1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, + "ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.12.1", "a553c59f6850d0aff3770e4729515762ba7c8e41eedde03208182a8dc9d0ce07", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"}, "floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"}, "fuse": {:hex, :fuse, "2.4.2", "9106b08db8793a34cc156177d7e24c41bd638ee1b28463cb76562fde213e8ced", [:rebar3], [], "hexpm"}, @@ -19,6 +21,7 @@ "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, @@ -39,6 +42,7 @@ "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, + "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm"}, "srtm": {:hex, :srtm, "0.4.0", "b253857ac22e54e689f64f4fc949ad468ec09189aef48ce6b823819a4e2dfcc9", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, @@ -46,4 +50,5 @@ "tesla_api": {:git, "https://github.com/adriankumpf/tesla_api.git", "da8941cd4b93530d1a059116cd047a30a9132b81", [branch: "v10"]}, "tortoise": {:hex, :tortoise, "0.9.4", "3bca5f4475f80a5bd45aab644ddb3364dd0956b67f4202dcef6ed20e045ea3a6", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, + "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"}, } diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index df21894289..6beb894f4e 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -12,22 +12,22 @@ msgstr "" "Plural-Forms: nplurals=2\n" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:80 +#: lib/teslamate_web/templates/car/summary.html.leex:73 msgid "Status" msgstr "Status" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:173 +#: lib/teslamate_web/templates/car/summary.html.leex:166 msgid "Speed" msgstr "Geschwindigkeit" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:167 +#: lib/teslamate_web/templates/car/summary.html.leex:160 msgid "State of Charge" msgstr "Ladezustand" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:136 +#: lib/teslamate_web/templates/car/summary.html.leex:129 msgid "Charged" msgstr "Geladen" @@ -62,19 +62,18 @@ msgid "updating" msgstr "installiert Update" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Locked" msgstr "Verschlossen" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:49 +#: lib/teslamate_web/templates/car/summary.html.leex:42 msgid "Sentry Mode" msgstr "Wächtermodus" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:3 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/geo_fence/new.html.leex:3 -#: lib/teslamate_web/templates/settings/index.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/settings/index.html.leex:3 msgid "Home" msgstr "Home" @@ -85,17 +84,17 @@ msgid "Settings" msgstr "Einstellungen" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:151 +#: lib/teslamate_web/templates/car/summary.html.leex:144 msgid "Scheduled charging" msgstr "Ladezeitpunkt" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:39 +#: lib/teslamate_web/templates/car/summary.html.leex:32 msgid "Plugged in" msgstr "Eingesteckt" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:160 +#: lib/teslamate_web/templates/car/summary.html.leex:153 msgid "Charge limit" msgstr "Ladelimit" @@ -110,22 +109,22 @@ msgid "unavailable" msgstr "nicht verfügbar" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:16 +#: lib/teslamate_web/templates/settings/index.html.leex:168 msgid "Length" msgstr "Länge" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:31 +#: lib/teslamate_web/templates/settings/index.html.leex:183 msgid "Temperature" msgstr "Temperatur" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:11 +#: lib/teslamate_web/templates/settings/index.html.leex:163 msgid "Units" msgstr "Einheiten" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:102 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:153 msgid "Back" msgstr "Zurück" @@ -135,68 +134,58 @@ msgid "Geo-Fences" msgstr "" #, elixir-format -#: lib/teslamate_web/live/geofence_live/new.ex:84 +#: lib/teslamate_web/live/geofence_live/form.ex:186 msgid "Geo-fence \"%{name}\" created" msgstr "Geo-Fence \"%{name}\" erstellt" #, elixir-format -#: lib/teslamate_web/live/geofence_live/edit.ex:71 -msgid "Geo-fence \"%{name}\" updated successfully" -msgstr "Geo-Fence \"%{name}\" erfolgreich aktualisiert" - -#, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 lib/teslamate_web/templates/geo_fence/new.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 msgid "Geo-fences" msgstr "Geo-Fences" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:104 +#: lib/teslamate_web/templates/settings/index.html.leex:74 msgid "Idle Time Before Trying to Sleep" msgstr "Leerlaufzeit vor dem Einschlafversuch" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:9 lib/teslamate_web/templates/geo_fence/index.html.leex:21 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:17 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:22 lib/teslamate_web/templates/geo_fence/index.html.leex:21 msgid "Name" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:20 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:33 #: lib/teslamate_web/templates/geo_fence/index.html.leex:22 msgid "Position" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:46 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:59 #: lib/teslamate_web/templates/geo_fence/index.html.leex:23 msgid "Radius" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Save" msgstr "Speichern" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Saving..." msgstr "Speichere..." #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:81 -msgid "Sleep" -msgstr "Schlaf" - -#, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:86 +#: lib/teslamate_web/templates/settings/index.html.leex:56 msgid "Time to Try Sleeping" msgstr "Dauer des Schlafversuchs" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:94 -#: lib/teslamate_web/templates/settings/index.html.leex:94 lib/teslamate_web/templates/settings/index.html.leex:113 -#: lib/teslamate_web/templates/settings/index.html.leex:113 +#: lib/teslamate_web/templates/settings/index.html.leex:64 +#: lib/teslamate_web/templates/settings/index.html.leex:64 lib/teslamate_web/templates/settings/index.html.leex:83 +#: lib/teslamate_web/templates/settings/index.html.leex:83 msgid "min" msgstr "" @@ -218,9 +207,9 @@ msgstr "Erstellen" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:99 -#: lib/teslamate_web/templates/car/summary.html.leex:29 +#: lib/teslamate_web/templates/car/summary.html.leex:22 msgid "Preconditioning" -msgstr "" +msgstr "Vorkonditionierung" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:96 @@ -229,43 +218,43 @@ msgstr "Wächtermodus aktiviert" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:100 -#: lib/teslamate_web/templates/car/summary.html.leex:34 +#: lib/teslamate_web/templates/car/summary.html.leex:27 msgid "User present" msgstr "Benutzer anwesend" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:227 +#: lib/teslamate_web/templates/car/summary.html.leex:233 msgid "cancel sleep attempt" msgstr "Schlafversuch abbrechen" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:223 +#: lib/teslamate_web/templates/car/summary.html.leex:227 msgid "try to sleep" msgstr "versuchen zu schlafen" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:123 +#: lib/teslamate_web/templates/car/summary.html.leex:116 msgid "Range (est.)" msgstr "Reichweite (gesch.)" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:83 -#: lib/teslamate_web/templates/car/summary.html.leex:83 +#: lib/teslamate_web/templates/car/summary.html.leex:76 +#: lib/teslamate_web/templates/car/summary.html.leex:76 msgid "for" msgstr "seit" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:133 +#: lib/teslamate_web/templates/settings/index.html.leex:103 msgid "No shift state reading" msgstr "Kein Schaltzustand" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:139 +#: lib/teslamate_web/templates/settings/index.html.leex:109 msgid "No temperature readings" msgstr "Keine Temperaturmesswerte" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:124 +#: lib/teslamate_web/templates/settings/index.html.leex:94 msgid "Requirements" msgstr "Voraussetzungen" @@ -275,12 +264,12 @@ msgid "Shift state present" msgstr "Schaltzustand vorhanden" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:152 +#: lib/teslamate_web/templates/settings/index.html.leex:122 msgid "Some firmware versions stop reporting temperatures or the shift state before the car is ready to sleep. Enabling these options will wait for the readings to stop." msgstr "Einige Firmware-Versionen hören auf, Temperaturen oder den Schaltzustand zu melden, bevor das Fahrzeug bereit ist einzuschlafen. Wenn diese Optionen aktiviert sind, wird gewartet, bis die Messwerte nicht mehr gemeldet werden." #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:145 +#: lib/teslamate_web/templates/settings/index.html.leex:115 msgid "Vehicle must be locked" msgstr "Fahrzeug muss verschlossen sein" @@ -290,42 +279,42 @@ msgid "Temperature readings" msgstr "Temperaturmesswerte" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:104 +#: lib/teslamate_web/templates/car/summary.html.leex:97 msgid "Range (rated)" msgstr "Reichweite (rated)" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:142 +#: lib/teslamate_web/templates/car/summary.html.leex:135 msgid "Charger Power" msgstr "Ladeleistung" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:58 +#: lib/teslamate_web/templates/settings/index.html.leex:140 msgid "Preferred range" msgstr "Bevorzugte Reichweite" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:52 +#: lib/teslamate_web/templates/settings/index.html.leex:134 msgid "Range" msgstr "Reichweite" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "ideal" msgstr "ideal" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "rated" msgstr "rated" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:103 +#: lib/teslamate_web/templates/car/summary.html.leex:96 msgid "Range (ideal)" msgstr "Reichweite (ideal)" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:57 +#: lib/teslamate_web/templates/settings/index.html.leex:139 msgid "The car's estimate of remaining range is based on a fixed energy consumption in Wh/km. The Wh/km factor is determined by Tesla and is not country specific whereas the rated range is based on regulatory tests in the different markets for that vehicle." msgstr "Die Schätzung der verbleibenden Reichweite basiert auf einem fixen Energieverbrauch in Wh/km. Der Wh/km-Faktor wird von Tesla bestimmt und ist nicht länderspezifisch, wohingegen die 'rated' Reichweite auf regulatorischen Tests in den verschiedenen Märkten für dieses Fahrzeug basiert." @@ -335,7 +324,7 @@ msgid "Update in progress" msgstr "Update läuft" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:44 +#: lib/teslamate_web/templates/car/summary.html.leex:37 msgid "Windows open" msgstr "Fenster geöffnet" @@ -345,80 +334,105 @@ msgid "Delete '%{geo_fence}'?" msgstr "'%{geo_fence}' löschen?" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:198 +#: lib/teslamate_web/templates/car/summary.html.leex:191 msgid "Inside temperature" msgstr "Innentemperatur" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:186 +#: lib/teslamate_web/templates/car/summary.html.leex:179 msgid "Outside temperature" msgstr "Außentemperatur" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:211 +#: lib/teslamate_web/templates/car/summary.html.leex:204 msgid "Version" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:59 +#: lib/teslamate_web/templates/car/summary.html.leex:52 msgid "Software Update available" msgstr "Software Update verfügbar" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:64 +#: lib/teslamate_web/templates/car/summary.html.leex:57 msgid "Health check failed" msgstr "Health Check fehlgeschlagen" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Unlocked" msgstr "Verschlossen" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "Phase" msgid_plural "Phases" msgstr[0] "Phase" msgstr[1] "Phasen" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:66 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:79 msgid "Phase correction" msgstr "Phasenkorrektur" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "disabled" msgstr "deaktiviert" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:101 msgid "Apply phase correction to existing charges" msgstr "Phasenkorrektur auf bestehende Ladevorgänge anwenden" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:65 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:78 msgid "Some firmware versions incorrectly report 2 phases when charging at some chargers. With phase correction, a fixed value can be set when charging at this location, which overwrites the reported phases." msgstr "Einige Firmware-Versionen melden beim Laden an manchen Ladestationnen fälschlicherweise 2 Phasen. Mit der Phasenkorrektur kann beim Laden an diesem Ort ein fixer Wert eingestellt werden, der die gemeldeten Phasen überschreibt." #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:95 +#: lib/teslamate_web/templates/car/summary.html.leex:88 msgid "Remaining Time" msgstr "Restlaufzeit" #, elixir-format #: lib/teslamate_web/templates/layout/app.html.eex:42 -#: lib/teslamate_web/templates/settings/index.html.leex:182 +#: lib/teslamate_web/templates/settings/index.html.leex:222 msgid "Dashboards" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:163 +#: lib/teslamate_web/templates/settings/index.html.leex:203 msgid "URLs" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:168 +#: lib/teslamate_web/templates/settings/index.html.leex:208 msgid "Web App" msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:42 +msgid "Enabled" +msgstr "Aktiviert" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:11 +msgid "Sleep Mode" +msgstr "Schlafmodus" + +#, elixir-format +#: lib/teslamate_web/live/geofence_live/form.ex:187 +msgid "Geo-fence \"%{name}\" updated" +msgstr "Geo-Fence \"%{name}\" aktualisiert" + +#, elixir-format +#: lib/teslamate_web/templates/geo_fence/form.html.leex:112 +msgid "Sleep Mode Enabled" +msgstr "Schlafmodus aktiviert" + +#, elixir-format +#: lib/teslamate_web/live/car_live/summary.ex:104 +msgid "Sleep Mode is disabled at current location" +msgstr "Schlafmodus ist am aktuellen Ort deaktiviert" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index acbbf91a16..8a96dae8d2 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -11,22 +11,22 @@ msgid "" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:80 +#: lib/teslamate_web/templates/car/summary.html.leex:73 msgid "Status" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:173 +#: lib/teslamate_web/templates/car/summary.html.leex:166 msgid "Speed" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:167 +#: lib/teslamate_web/templates/car/summary.html.leex:160 msgid "State of Charge" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:136 +#: lib/teslamate_web/templates/car/summary.html.leex:129 msgid "Charged" msgstr "" @@ -61,19 +61,18 @@ msgid "updating" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Locked" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:49 +#: lib/teslamate_web/templates/car/summary.html.leex:42 msgid "Sentry Mode" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:3 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/geo_fence/new.html.leex:3 -#: lib/teslamate_web/templates/settings/index.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/settings/index.html.leex:3 msgid "Home" msgstr "" @@ -84,17 +83,17 @@ msgid "Settings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:151 +#: lib/teslamate_web/templates/car/summary.html.leex:144 msgid "Scheduled charging" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:39 +#: lib/teslamate_web/templates/car/summary.html.leex:32 msgid "Plugged in" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:160 +#: lib/teslamate_web/templates/car/summary.html.leex:153 msgid "Charge limit" msgstr "" @@ -109,22 +108,22 @@ msgid "unavailable" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:16 +#: lib/teslamate_web/templates/settings/index.html.leex:168 msgid "Length" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:31 +#: lib/teslamate_web/templates/settings/index.html.leex:183 msgid "Temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:11 +#: lib/teslamate_web/templates/settings/index.html.leex:163 msgid "Units" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:102 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:153 msgid "Back" msgstr "" @@ -134,68 +133,58 @@ msgid "Geo-Fences" msgstr "" #, elixir-format -#: lib/teslamate_web/live/geofence_live/new.ex:84 +#: lib/teslamate_web/live/geofence_live/form.ex:186 msgid "Geo-fence \"%{name}\" created" msgstr "" #, elixir-format -#: lib/teslamate_web/live/geofence_live/edit.ex:71 -msgid "Geo-fence \"%{name}\" updated successfully" -msgstr "" - -#, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 lib/teslamate_web/templates/geo_fence/new.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 msgid "Geo-fences" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:104 +#: lib/teslamate_web/templates/settings/index.html.leex:74 msgid "Idle Time Before Trying to Sleep" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:9 lib/teslamate_web/templates/geo_fence/index.html.leex:21 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:17 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:22 lib/teslamate_web/templates/geo_fence/index.html.leex:21 msgid "Name" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:20 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:33 #: lib/teslamate_web/templates/geo_fence/index.html.leex:22 msgid "Position" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:46 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:59 #: lib/teslamate_web/templates/geo_fence/index.html.leex:23 msgid "Radius" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Save" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Saving..." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:81 -msgid "Sleep" -msgstr "" - -#, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:86 +#: lib/teslamate_web/templates/settings/index.html.leex:56 msgid "Time to Try Sleeping" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:94 -#: lib/teslamate_web/templates/settings/index.html.leex:94 lib/teslamate_web/templates/settings/index.html.leex:113 -#: lib/teslamate_web/templates/settings/index.html.leex:113 +#: lib/teslamate_web/templates/settings/index.html.leex:64 +#: lib/teslamate_web/templates/settings/index.html.leex:64 lib/teslamate_web/templates/settings/index.html.leex:83 +#: lib/teslamate_web/templates/settings/index.html.leex:83 msgid "min" msgstr "" @@ -217,7 +206,7 @@ msgstr "" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:99 -#: lib/teslamate_web/templates/car/summary.html.leex:29 +#: lib/teslamate_web/templates/car/summary.html.leex:22 msgid "Preconditioning" msgstr "" @@ -228,43 +217,43 @@ msgstr "" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:100 -#: lib/teslamate_web/templates/car/summary.html.leex:34 +#: lib/teslamate_web/templates/car/summary.html.leex:27 msgid "User present" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:227 +#: lib/teslamate_web/templates/car/summary.html.leex:233 msgid "cancel sleep attempt" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:223 +#: lib/teslamate_web/templates/car/summary.html.leex:227 msgid "try to sleep" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:123 +#: lib/teslamate_web/templates/car/summary.html.leex:116 msgid "Range (est.)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:83 -#: lib/teslamate_web/templates/car/summary.html.leex:83 +#: lib/teslamate_web/templates/car/summary.html.leex:76 +#: lib/teslamate_web/templates/car/summary.html.leex:76 msgid "for" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:133 +#: lib/teslamate_web/templates/settings/index.html.leex:103 msgid "No shift state reading" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:139 +#: lib/teslamate_web/templates/settings/index.html.leex:109 msgid "No temperature readings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:124 +#: lib/teslamate_web/templates/settings/index.html.leex:94 msgid "Requirements" msgstr "" @@ -274,12 +263,12 @@ msgid "Shift state present" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:152 +#: lib/teslamate_web/templates/settings/index.html.leex:122 msgid "Some firmware versions stop reporting temperatures or the shift state before the car is ready to sleep. Enabling these options will wait for the readings to stop." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:145 +#: lib/teslamate_web/templates/settings/index.html.leex:115 msgid "Vehicle must be locked" msgstr "" @@ -289,42 +278,42 @@ msgid "Temperature readings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:104 +#: lib/teslamate_web/templates/car/summary.html.leex:97 msgid "Range (rated)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:142 +#: lib/teslamate_web/templates/car/summary.html.leex:135 msgid "Charger Power" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:58 +#: lib/teslamate_web/templates/settings/index.html.leex:140 msgid "Preferred range" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:52 +#: lib/teslamate_web/templates/settings/index.html.leex:134 msgid "Range" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "ideal" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "rated" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:103 +#: lib/teslamate_web/templates/car/summary.html.leex:96 msgid "Range (ideal)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:57 +#: lib/teslamate_web/templates/settings/index.html.leex:139 msgid "The car's estimate of remaining range is based on a fixed energy consumption in Wh/km. The Wh/km factor is determined by Tesla and is not country specific whereas the rated range is based on regulatory tests in the different markets for that vehicle." msgstr "" @@ -334,7 +323,7 @@ msgid "Update in progress" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:44 +#: lib/teslamate_web/templates/car/summary.html.leex:37 msgid "Windows open" msgstr "" @@ -344,80 +333,105 @@ msgid "Delete '%{geo_fence}'?" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:198 +#: lib/teslamate_web/templates/car/summary.html.leex:191 msgid "Inside temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:186 +#: lib/teslamate_web/templates/car/summary.html.leex:179 msgid "Outside temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:211 +#: lib/teslamate_web/templates/car/summary.html.leex:204 msgid "Version" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:59 +#: lib/teslamate_web/templates/car/summary.html.leex:52 msgid "Software Update available" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:64 +#: lib/teslamate_web/templates/car/summary.html.leex:57 msgid "Health check failed" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Unlocked" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "Phase" msgid_plural "Phases" msgstr[0] "" msgstr[1] "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:66 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:79 msgid "Phase correction" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "disabled" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:101 msgid "Apply phase correction to existing charges" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:65 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:78 msgid "Some firmware versions incorrectly report 2 phases when charging at some chargers. With phase correction, a fixed value can be set when charging at this location, which overwrites the reported phases." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:95 +#: lib/teslamate_web/templates/car/summary.html.leex:88 msgid "Remaining Time" msgstr "" #, elixir-format #: lib/teslamate_web/templates/layout/app.html.eex:42 -#: lib/teslamate_web/templates/settings/index.html.leex:182 +#: lib/teslamate_web/templates/settings/index.html.leex:222 msgid "Dashboards" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:163 +#: lib/teslamate_web/templates/settings/index.html.leex:203 msgid "URLs" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:168 +#: lib/teslamate_web/templates/settings/index.html.leex:208 msgid "Web App" msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:42 +msgid "Enabled" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:11 +msgid "Sleep Mode" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/live/geofence_live/form.ex:187 +msgid "Geo-fence \"%{name}\" updated" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/geo_fence/form.html.leex:112 +msgid "Sleep Mode Enabled" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/live/car_live/summary.ex:104 +msgid "Sleep Mode is disabled at current location" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index c82755d345..37df46ac06 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -12,22 +12,22 @@ msgstr "" "Plural-Forms: nplurals=2\n" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:80 +#: lib/teslamate_web/templates/car/summary.html.leex:73 msgid "Status" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:173 +#: lib/teslamate_web/templates/car/summary.html.leex:166 msgid "Speed" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:167 +#: lib/teslamate_web/templates/car/summary.html.leex:160 msgid "State of Charge" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:136 +#: lib/teslamate_web/templates/car/summary.html.leex:129 msgid "Charged" msgstr "" @@ -62,19 +62,18 @@ msgid "updating" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Locked" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:49 +#: lib/teslamate_web/templates/car/summary.html.leex:42 msgid "Sentry Mode" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:3 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/geo_fence/new.html.leex:3 -#: lib/teslamate_web/templates/settings/index.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:3 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:3 lib/teslamate_web/templates/settings/index.html.leex:3 msgid "Home" msgstr "" @@ -85,17 +84,17 @@ msgid "Settings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:151 +#: lib/teslamate_web/templates/car/summary.html.leex:144 msgid "Scheduled charging" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:39 +#: lib/teslamate_web/templates/car/summary.html.leex:32 msgid "Plugged in" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:160 +#: lib/teslamate_web/templates/car/summary.html.leex:153 msgid "Charge limit" msgstr "" @@ -110,22 +109,22 @@ msgid "unavailable" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:16 +#: lib/teslamate_web/templates/settings/index.html.leex:168 msgid "Length" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:31 +#: lib/teslamate_web/templates/settings/index.html.leex:183 msgid "Temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:11 +#: lib/teslamate_web/templates/settings/index.html.leex:163 msgid "Units" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:102 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:153 msgid "Back" msgstr "" @@ -135,68 +134,58 @@ msgid "Geo-Fences" msgstr "" #, elixir-format -#: lib/teslamate_web/live/geofence_live/new.ex:84 +#: lib/teslamate_web/live/geofence_live/form.ex:186 msgid "Geo-fence \"%{name}\" created" msgstr "" #, elixir-format -#: lib/teslamate_web/live/geofence_live/edit.ex:71 -msgid "Geo-fence \"%{name}\" updated successfully" -msgstr "" - -#, elixir-format -#: lib/teslamate_web/templates/geo_fence/edit.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 lib/teslamate_web/templates/geo_fence/new.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 +#: lib/teslamate_web/templates/geo_fence/index.html.leex:4 msgid "Geo-fences" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:104 +#: lib/teslamate_web/templates/settings/index.html.leex:74 msgid "Idle Time Before Trying to Sleep" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:4 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:9 lib/teslamate_web/templates/geo_fence/index.html.leex:21 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:17 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:22 lib/teslamate_web/templates/geo_fence/index.html.leex:21 msgid "Name" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:20 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:33 #: lib/teslamate_web/templates/geo_fence/index.html.leex:22 msgid "Position" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:46 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:59 #: lib/teslamate_web/templates/geo_fence/index.html.leex:23 msgid "Radius" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Save" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:105 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:156 msgid "Saving..." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:81 -msgid "Sleep" -msgstr "" - -#, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:86 +#: lib/teslamate_web/templates/settings/index.html.leex:56 msgid "Time to Try Sleeping" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:94 -#: lib/teslamate_web/templates/settings/index.html.leex:94 lib/teslamate_web/templates/settings/index.html.leex:113 -#: lib/teslamate_web/templates/settings/index.html.leex:113 +#: lib/teslamate_web/templates/settings/index.html.leex:64 +#: lib/teslamate_web/templates/settings/index.html.leex:64 lib/teslamate_web/templates/settings/index.html.leex:83 +#: lib/teslamate_web/templates/settings/index.html.leex:83 msgid "min" msgstr "" @@ -218,7 +207,7 @@ msgstr "" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:99 -#: lib/teslamate_web/templates/car/summary.html.leex:29 +#: lib/teslamate_web/templates/car/summary.html.leex:22 msgid "Preconditioning" msgstr "" @@ -229,43 +218,43 @@ msgstr "" #, elixir-format #: lib/teslamate_web/live/car_live/summary.ex:100 -#: lib/teslamate_web/templates/car/summary.html.leex:34 +#: lib/teslamate_web/templates/car/summary.html.leex:27 msgid "User present" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:227 +#: lib/teslamate_web/templates/car/summary.html.leex:233 msgid "cancel sleep attempt" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:223 +#: lib/teslamate_web/templates/car/summary.html.leex:227 msgid "try to sleep" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:123 +#: lib/teslamate_web/templates/car/summary.html.leex:116 msgid "Range (est.)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:83 -#: lib/teslamate_web/templates/car/summary.html.leex:83 +#: lib/teslamate_web/templates/car/summary.html.leex:76 +#: lib/teslamate_web/templates/car/summary.html.leex:76 msgid "for" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:133 +#: lib/teslamate_web/templates/settings/index.html.leex:103 msgid "No shift state reading" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:139 +#: lib/teslamate_web/templates/settings/index.html.leex:109 msgid "No temperature readings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:124 +#: lib/teslamate_web/templates/settings/index.html.leex:94 msgid "Requirements" msgstr "" @@ -275,12 +264,12 @@ msgid "Shift state present" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:152 +#: lib/teslamate_web/templates/settings/index.html.leex:122 msgid "Some firmware versions stop reporting temperatures or the shift state before the car is ready to sleep. Enabling these options will wait for the readings to stop." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:145 +#: lib/teslamate_web/templates/settings/index.html.leex:115 msgid "Vehicle must be locked" msgstr "" @@ -290,42 +279,42 @@ msgid "Temperature readings" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:104 +#: lib/teslamate_web/templates/car/summary.html.leex:97 msgid "Range (rated)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:142 +#: lib/teslamate_web/templates/car/summary.html.leex:135 msgid "Charger Power" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:58 +#: lib/teslamate_web/templates/settings/index.html.leex:140 msgid "Preferred range" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:52 +#: lib/teslamate_web/templates/settings/index.html.leex:134 msgid "Range" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "ideal" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:67 +#: lib/teslamate_web/templates/settings/index.html.leex:149 msgid "rated" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:103 +#: lib/teslamate_web/templates/car/summary.html.leex:96 msgid "Range (ideal)" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:57 +#: lib/teslamate_web/templates/settings/index.html.leex:139 msgid "The car's estimate of remaining range is based on a fixed energy consumption in Wh/km. The Wh/km factor is determined by Tesla and is not country specific whereas the rated range is based on regulatory tests in the different markets for that vehicle." msgstr "" @@ -335,7 +324,7 @@ msgid "Update in progress" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:44 +#: lib/teslamate_web/templates/car/summary.html.leex:37 msgid "Windows open" msgstr "" @@ -345,80 +334,105 @@ msgid "Delete '%{geo_fence}'?" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:198 +#: lib/teslamate_web/templates/car/summary.html.leex:191 msgid "Inside temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:186 +#: lib/teslamate_web/templates/car/summary.html.leex:179 msgid "Outside temperature" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:211 +#: lib/teslamate_web/templates/car/summary.html.leex:204 msgid "Version" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:59 +#: lib/teslamate_web/templates/car/summary.html.leex:52 msgid "Software Update available" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:64 +#: lib/teslamate_web/templates/car/summary.html.leex:57 msgid "Health check failed" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:54 +#: lib/teslamate_web/templates/car/summary.html.leex:47 msgid "Unlocked" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "Phase" msgid_plural "Phases" msgstr[0] "" msgstr[1] "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:66 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:79 msgid "Phase correction" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:75 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 msgid "disabled" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:88 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:101 msgid "Apply phase correction to existing charges" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/geo_fence/form.html.leex:65 +#: lib/teslamate_web/templates/geo_fence/form.html.leex:78 msgid "Some firmware versions incorrectly report 2 phases when charging at some chargers. With phase correction, a fixed value can be set when charging at this location, which overwrites the reported phases." msgstr "" #, elixir-format -#: lib/teslamate_web/templates/car/summary.html.leex:95 +#: lib/teslamate_web/templates/car/summary.html.leex:88 msgid "Remaining Time" msgstr "" #, elixir-format #: lib/teslamate_web/templates/layout/app.html.eex:42 -#: lib/teslamate_web/templates/settings/index.html.leex:182 +#: lib/teslamate_web/templates/settings/index.html.leex:222 msgid "Dashboards" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:163 +#: lib/teslamate_web/templates/settings/index.html.leex:203 msgid "URLs" msgstr "" #, elixir-format -#: lib/teslamate_web/templates/settings/index.html.leex:168 +#: lib/teslamate_web/templates/settings/index.html.leex:208 msgid "Web App" msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:42 +msgid "Enabled" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/settings/index.html.leex:11 +msgid "Sleep Mode" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/live/geofence_live/form.ex:187 +msgid "Geo-fence \"%{name}\" updated" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/templates/geo_fence/form.html.leex:112 +msgid "Sleep Mode Enabled" +msgstr "" + +#, elixir-format +#: lib/teslamate_web/live/car_live/summary.ex:104 +msgid "Sleep Mode is disabled at current location" +msgstr "" diff --git a/priv/repo/migrations/20191117042320_add_cost_field_to_charges.exs b/priv/repo/migrations/20191117042320_add_cost_field_to_charges.exs new file mode 100644 index 0000000000..56ced757bd --- /dev/null +++ b/priv/repo/migrations/20191117042320_add_cost_field_to_charges.exs @@ -0,0 +1,9 @@ +defmodule TeslaMate.Repo.Migrations.AddCostFieldToCharges do + use Ecto.Migration + + def change do + alter table(:charging_processes) do + add(:cost, :decimal, precision: 6, scale: 2) + end + end +end diff --git a/priv/repo/migrations/20191117171307_car_settings.exs b/priv/repo/migrations/20191117171307_car_settings.exs index e4111b338e..c0b0e2ef3c 100644 --- a/priv/repo/migrations/20191117171307_car_settings.exs +++ b/priv/repo/migrations/20191117171307_car_settings.exs @@ -47,8 +47,6 @@ defmodule TeslaMate.Repo.Migrations.CarSettings do use Ecto.Schema import Ecto.Changeset - alias TeslaMate.Settings.CarSettings - schema "cars" do belongs_to(:settings, CarSettings) end diff --git a/priv/repo/migrations/20191119162847_geofence_sleep.exs b/priv/repo/migrations/20191119162847_geofence_sleep.exs new file mode 100644 index 0000000000..a1153a14a9 --- /dev/null +++ b/priv/repo/migrations/20191119162847_geofence_sleep.exs @@ -0,0 +1,19 @@ +defmodule TeslaMate.Repo.Migrations.GeofenceSleep do + use Ecto.Migration + + def change do + alter table(:car_settings) do + add(:sleep_mode_enabled, :boolean, null: false, default: true) + end + + create table(:geofence_sleep_mode_whitelist, primary_key: false) do + add(:car_id, references(:cars, on_delete: :delete_all), primary_key: true) + add(:geofence_id, references(:geofences, on_delete: :delete_all), primary_key: true) + end + + create table(:geofence_sleep_mode_blacklist, primary_key: false) do + add(:car_id, references(:cars, on_delete: :delete_all), primary_key: true) + add(:geofence_id, references(:geofences, on_delete: :delete_all), primary_key: true) + end + end +end diff --git a/test/support/mocks/locations.ex b/test/support/mocks/locations.ex new file mode 100644 index 0000000000..6b815c5d89 --- /dev/null +++ b/test/support/mocks/locations.ex @@ -0,0 +1,48 @@ +defmodule LocationsMock do + use GenServer + + defstruct [:pid, :blacklist, :whitelist] + alias __MODULE__, as: State + + alias TeslaMate.Settings.CarSettings + alias TeslaMate.Log.Car + + # API + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: Keyword.fetch!(opts, :name)) + end + + def may_fall_asleep_at?(name, car, position) do + GenServer.call(name, {:may_fall_asleep_at?, car, position}) + end + + # Callbacks + + @impl true + def init(opts) do + {:ok, + %State{ + pid: Keyword.fetch!(opts, :pid), + blacklist: Keyword.fetch!(opts, :blacklist), + whitelist: Keyword.fetch!(opts, :whitelist) + }} + end + + @impl true + + def handle_call({:may_fall_asleep_at?, %Car{settings: settings}, position}, _from, state) do + %{latitude: lat, longitude: lng} = position + + response = + case settings do + %CarSettings{sleep_mode_enabled: true} -> + Enum.find(state.blacklist, &match?({^lat, ^lng}, &1)) == nil + + %CarSettings{sleep_mode_enabled: false} -> + Enum.find(state.whitelist, &match?({^lat, ^lng}, &1)) != nil + end + + {:reply, {:ok, response}, state} + end +end diff --git a/test/support/mocks/mapping.ex b/test/support/mocks/terrain.ex similarity index 68% rename from test/support/mocks/mapping.ex rename to test/support/mocks/terrain.ex index bd4ded37c7..9b7c375ecf 100644 --- a/test/support/mocks/mapping.ex +++ b/test/support/mocks/terrain.ex @@ -1,4 +1,4 @@ -defmodule MappingMock do +defmodule TerrainMock do def get_elevation({_lat, _lng}) do nil end diff --git a/test/support/vehicle_case.ex b/test/support/vehicle_case.ex index 144b26f320..31090b25b8 100644 --- a/test/support/vehicle_case.ex +++ b/test/support/vehicle_case.ex @@ -17,6 +17,7 @@ defmodule TeslaMate.VehicleCase do log_name = :"log_#{name}" api_name = :"api_#{name}" settings_name = :"settings_#{name}" + locations_name = :"locations_#{name}" vehicles_name = :"vehicles_#{name}" pubsub_name = :"pubsub_#{name}" @@ -26,6 +27,15 @@ defmodule TeslaMate.VehicleCase do {:ok, _pid} = start_supervised({VehiclesMock, name: vehicles_name, pid: self()}) {:ok, _pid} = start_supervised({PubSubMock, name: pubsub_name, pid: self()}) + {:ok, _pid} = + start_supervised( + {LocationsMock, + name: locations_name, + pid: self(), + blacklist: Keyword.get(opts, :blacklist, []), + whitelist: Keyword.get(opts, :whitelist, [])} + ) + opts = Keyword.put_new_lazy(opts, :car, fn -> settings = @@ -51,6 +61,7 @@ defmodule TeslaMate.VehicleCase do deps_log: {LogMock, log_name}, deps_api: {ApiMock, api_name}, deps_settings: {SettingsMock, settings_name}, + deps_locations: {LocationsMock, locations_name}, deps_vehicles: {VehiclesMock, vehicles_name}, deps_pubsub: {PubSubMock, pubsub_name} )} diff --git a/test/teslamate/locations/geofences_test.exs b/test/teslamate/locations/geofences_test.exs index 62ee1b68be..92e4ae4995 100644 --- a/test/teslamate/locations/geofences_test.exs +++ b/test/teslamate/locations/geofences_test.exs @@ -6,8 +6,22 @@ defmodule TeslaMate.LocationsGeofencesTest do describe "geofences" do @valid_attrs %{name: "foo", latitude: 52.514521, longitude: 13.350144, radius: 42} - @update_attrs %{name: "bar", latitude: 53.514521, longitude: 14.350144, radius: 43} - @invalid_attrs %{name: nil, latitude: nil, longitude: nil, radius: nil} + @update_attrs %{ + name: "bar", + latitude: 53.514521, + longitude: 14.350144, + radius: 43, + phase_correction: 3 + } + @invalid_attrs %{ + name: nil, + latitude: nil, + longitude: nil, + radius: nil, + phase_correction: nil, + sleep_mode_whitelist: nil, + sleep_mode_blacklist: nil + } def geofence_fixture(attrs \\ %{}) do {:ok, geofence} = @@ -15,7 +29,7 @@ defmodule TeslaMate.LocationsGeofencesTest do |> Enum.into(@valid_attrs) |> Locations.create_geofence() - geofence + Repo.preload(geofence, [:sleep_mode_whitelist, :sleep_mode_blacklist]) end defp car_fixture(attrs \\ %{}) do @@ -120,7 +134,12 @@ defmodule TeslaMate.LocationsGeofencesTest do test "list_geofences/0 returns all geofences" do geofence = geofence_fixture() - assert Locations.list_geofences() == [geofence] + + geofences = + Locations.list_geofences() + |> Enum.map(&Repo.preload(&1, [:sleep_mode_blacklist, :sleep_mode_whitelist])) + + assert geofences == [geofence] end test "get_geofence!/1 returns the geofence with given id" do @@ -134,6 +153,10 @@ defmodule TeslaMate.LocationsGeofencesTest do assert geofence.latitude == 52.514521 assert geofence.longitude == 13.350144 assert geofence.radius == 42 + assert geofence.phase_correction == nil + geofence = Repo.preload(geofence, [:sleep_mode_whitelist, :sleep_mode_blacklist]) + assert geofence.sleep_mode_blacklist == [] + assert geofence.sleep_mode_whitelist == [] end test "create_geofence/1 with invalid data returns error changeset" do @@ -147,9 +170,17 @@ defmodule TeslaMate.LocationsGeofencesTest do } assert {:error, %Ecto.Changeset{} = changeset} = - Locations.create_geofence(%{latitude: "wat", longitude: "wat"}) + Locations.create_geofence(%{ + latitude: "wat", + longitude: "wat", + phase_correction: "wat" + }) - assert %{latitude: ["is invalid"], longitude: ["is invalid"]} = errors_on(changeset) + assert %{ + latitude: ["is invalid"], + longitude: ["is invalid"], + phase_correction: ["is invalid"] + } = errors_on(changeset) end test "create_geofence/1 fails if there is already a geo-fence nearby" do @@ -221,12 +252,26 @@ defmodule TeslaMate.LocationsGeofencesTest do end test "update_geofence/2 with valid data updates the geofence" do + car = car_fixture() geofence = geofence_fixture() - assert {:ok, %GeoFence{} = geofence} = Locations.update_geofence(geofence, @update_attrs) + + attrs = + Enum.into(%{sleep_mode_whitelist: [car], sleep_mode_blacklist: [car]}, @update_attrs) + + assert {:ok, %GeoFence{} = geofence} = Locations.update_geofence(geofence, attrs) assert geofence.name == "bar" assert geofence.latitude == 53.514521 assert geofence.longitude == 14.350144 assert geofence.radius == 43 + assert geofence.phase_correction == 3 + assert geofence.sleep_mode_blacklist == [car] + assert geofence.sleep_mode_whitelist == [car] + + assert {:ok, %GeoFence{} = geofence} = + Locations.update_geofence(geofence, %{sleep_mode_whitelist: []}) + + assert geofence.sleep_mode_blacklist == [car] + assert geofence.sleep_mode_whitelist == [] end test "update_geofence/2 with invalid data returns error changeset" do diff --git a/test/teslamate/settings_test.exs b/test/teslamate/settings_test.exs index 065b253c7e..32b215ce4f 100644 --- a/test/teslamate/settings_test.exs +++ b/test/teslamate/settings_test.exs @@ -117,14 +117,16 @@ defmodule TeslaMate.SettingsTest do suspend_after_idle_min: 60, req_no_shift_state_reading: false, req_no_temp_reading: false, - req_not_unlocked: true + req_not_unlocked: true, + sleep_mode_enabled: false } @invalid_attrs %{ suspend_min: nil, suspend_after_idle_min: nil, req_no_shift_state_reading: nil, req_no_temp_reading: nil, - req_not_unlocked: nil + req_not_unlocked: nil, + sleep_mode_enabled: nil } test "get_car_settings/0 returns the settings" do @@ -137,6 +139,7 @@ defmodule TeslaMate.SettingsTest do assert settings.req_no_shift_state_reading == false assert settings.req_no_temp_reading == false assert settings.req_not_unlocked == true + assert settings.sleep_mode_enabled == true end test "update_car_settings/2 with valid data updates the settings" do @@ -154,6 +157,7 @@ defmodule TeslaMate.SettingsTest do assert settings.req_no_shift_state_reading == false assert settings.req_no_temp_reading == false assert settings.req_not_unlocked == true + assert settings.sleep_mode_enabled == false end test "update_car_settings/2 publishes the settings" do @@ -182,11 +186,59 @@ defmodule TeslaMate.SettingsTest do req_no_temp_reading: ["can't be blank"], req_not_unlocked: ["can't be blank"], suspend_after_idle_min: ["can't be blank"], - suspend_min: ["can't be blank"] + suspend_min: ["can't be blank"], + sleep_mode_enabled: ["can't be blank"] } assert [^settings] = Settings.get_car_settings() end + + test "toggling sleep mode status clears the corresponding geo-fence black- & whitelists" do + alias TeslaMate.Locations.GeoFence + alias TeslaMate.Locations + alias TeslaMate.Log.Car + + {:ok, _pid} = start_supervised({Phoenix.PubSub.PG2, name: TeslaMate.PubSub}) + + car = car_fixture() + another_car = car_fixture(eid: 43, vid: 43, vin: "43") + + car_id = car.id + another_car_id = another_car.id + + {:ok, geofence} = + Locations.create_geofence(%{ + name: "foo", + latitude: -50.606262, + longitude: 165.972475, + radius: 250, + sleep_mode_blacklist: [car], + sleep_mode_whitelist: [car] + }) + + {:ok, another_geofence} = + Locations.create_geofence(%{ + name: "bar", + latitude: 37.457631, + longitude: -92.105263, + radius: 250, + sleep_mode_blacklist: [another_car], + sleep_mode_whitelist: [another_car] + }) + + [%CarSettings{car: %Car{id: ^car_id}} = settings, _] = Settings.get_car_settings() + + assert {:ok, %CarSettings{} = settings} = + Settings.update_car_settings(settings, %{sleep_mode_enabled: false}) + + assert %GeoFence{sleep_mode_whitelist: [], sleep_mode_blacklist: []} = + Locations.get_geofence!(geofence.id) + + assert %GeoFence{ + sleep_mode_whitelist: [%Car{id: ^another_car_id}], + sleep_mode_blacklist: [%Car{id: ^another_car_id}] + } = Locations.get_geofence!(another_geofence.id) + end end describe "efficiencies" do diff --git a/test/teslamate/mapping/update_positions_test.exs b/test/teslamate/terrain/update_positions_test.exs similarity index 91% rename from test/teslamate/mapping/update_positions_test.exs rename to test/teslamate/terrain/update_positions_test.exs index 5ca927b9b5..c07316a6d2 100644 --- a/test/teslamate/mapping/update_positions_test.exs +++ b/test/teslamate/terrain/update_positions_test.exs @@ -1,7 +1,7 @@ -defmodule TeslaMate.Mapping.UpdatePositionsTest do +defmodule TeslaMate.Terrain.UpdatePositionsTest do use TeslaMate.DataCase - alias TeslaMate.{Log, Mapping} + alias TeslaMate.{Log, Terrain} alias TeslaMate.Log.Position defp start_mapping(name, responses) do @@ -10,7 +10,7 @@ defmodule TeslaMate.Mapping.UpdatePositionsTest do {:ok, _pid} = start_supervised({SRTMMock, name: srtm_name, pid: self(), responses: responses}) {:ok, _} = - start_supervised({Mapping, name: name, timeout: 100, deps_srtm: {SRTMMock, srtm_name}}) + start_supervised({Terrain, name: name, timeout: 100, deps_srtm: {SRTMMock, srtm_name}}) :ok end @@ -38,9 +38,9 @@ defmodule TeslaMate.Mapping.UpdatePositionsTest do }) # blocked - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil for {_, i} <- Enum.with_index(positions) do if i in [50, 150] do diff --git a/test/teslamate/mapping_test.exs b/test/teslamate/terrain_test.exs similarity index 72% rename from test/teslamate/mapping_test.exs rename to test/teslamate/terrain_test.exs index e6fbacb5db..e657ebb3f1 100644 --- a/test/teslamate/mapping_test.exs +++ b/test/teslamate/terrain_test.exs @@ -1,7 +1,7 @@ -defmodule TeslaMate.MappingTest do +defmodule TeslaMate.TerrainTest do use TeslaMate.DataCase, async: true - alias TeslaMate.Mapping + alias TeslaMate.Terrain def start_mapping(name, responses \\ %{}) do log_name = :"log_#{name}" @@ -12,12 +12,12 @@ defmodule TeslaMate.MappingTest do opts = [ name: name, - timeout: 150, + timeout: 500, deps_log: {LogMock, log_name}, deps_srtm: {SRTMMock, srtm_name} ] - {:ok, _} = start_supervised({Mapping, opts}) + {:ok, _} = start_supervised({Terrain, opts}) assert_receive {:get_positions_without_elevation, 0} :ok @@ -27,7 +27,7 @@ defmodule TeslaMate.MappingTest do test "return the elevation", %{test: name} do :ok = start_mapping(name, %{{0, 0} => fn -> {:ok, 42} end}) - assert 42 == Mapping.get_elevation(name, {0, 0}) + assert 42 == Terrain.get_elevation(name, {0, 0}) assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} refute_receive _ @@ -37,7 +37,7 @@ defmodule TeslaMate.MappingTest do test "return nil if an error occured", %{test: name} do :ok = start_mapping(name, %{{0, 0} => fn -> {:error, :kaputt} end}) - assert Mapping.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil TestHelper.eventually(fn -> assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} @@ -55,12 +55,14 @@ defmodule TeslaMate.MappingTest do end }) - assert Mapping.get_elevation(name, {0, 0}) == nil - Process.sleep(100) - assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} + assert Terrain.get_elevation(name, {0, 0}) == nil + + TestHelper.eventually(fn -> + assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} + end) # still blocked - assert Mapping.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil refute_receive _, 300 end @@ -75,9 +77,11 @@ defmodule TeslaMate.MappingTest do end }) - assert Mapping.get_elevation(name, {1, 1}) == nil - Process.sleep(100) - assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 1, 1}} + assert Terrain.get_elevation(name, {1, 1}) == nil + + TestHelper.eventually(fn -> + assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 1, 1}} + end) refute_receive _ end @@ -89,11 +93,11 @@ defmodule TeslaMate.MappingTest do {0, 0} => fn -> {:error, :kaputt} end }) - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil - assert Mapping.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil + assert Terrain.get_elevation(name, {0, 0}) == nil # circuit broke after 3 attempts TestHelper.eventually( @@ -102,7 +106,7 @@ defmodule TeslaMate.MappingTest do assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} assert_received {SRTM, {:get_elevation, %SRTM.Client{}, 0, 0}} end, - attempts: 15 + attempts: 25 ) refute_receive _ diff --git a/test/teslamate/vehicles/vehicle/driving_test.exs b/test/teslamate/vehicles/vehicle/driving_test.exs index 4d408bf04e..4a4c3d3964 100644 --- a/test/teslamate/vehicles/vehicle/driving_test.exs +++ b/test/teslamate/vehicles/vehicle/driving_test.exs @@ -303,7 +303,7 @@ defmodule TeslaMate.Vehicles.Vehicle.DrivingTest do refute_receive _, 100 # Logs previous drive - assert_receive {:close_drive, ^drive}, 200 + assert_receive {:close_drive, ^drive}, 250 assert_receive {:start_state, ^car, :online} assert_receive {:insert_position, ^car, %{}} diff --git a/test/teslamate/vehicles/vehicle/suspend_logging_test.exs b/test/teslamate/vehicles/vehicle/suspend_logging_test.exs index 4ac9faebe2..57fab0194c 100644 --- a/test/teslamate/vehicles/vehicle/suspend_logging_test.exs +++ b/test/teslamate/vehicles/vehicle/suspend_logging_test.exs @@ -48,6 +48,47 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendLoggingTest do refute_receive _ end + test "cannot be suspended if sleep mode is disabled", %{test: name} do + events = [ + {:ok, online_event()} + ] + + :ok = start_vehicle(name, events, settings: %{sleep_mode_enabled: false}) + assert_receive {:start_state, _, :online} + + assert {:error, :sleep_mode_disabled} = Vehicle.suspend_logging(name) + end + + @tag :capture_log + test "is suspended if sleep mode is disabled but enabled for current location", %{test: name} do + events = [ + {:ok, + online_event(drive_state: %{timestamp: 0, latitude: -50.606993, longitude: 165.972471})} + ] + + :ok = + start_vehicle(name, events, + settings: %{sleep_mode_enabled: false}, + whitelist: [{-50.606993, 165.972471}] + ) + + assert_receive {:start_state, _, :online} + + assert :ok = Vehicle.suspend_logging(name) + end + + test "cannot be suspended if sleep mode is disabled for the current location", %{test: name} do + events = [ + {:ok, + online_event(drive_state: %{timestamp: 0, latitude: -50.606993, longitude: 165.972471})} + ] + + :ok = start_vehicle(name, events, blacklist: [{-50.606993, 165.972471}]) + assert_receive {:start_state, _, :online} + + assert {:error, :sleep_mode_disabled_at_location} = Vehicle.suspend_logging(name) + end + test "cannot be suspended if vehicle is preconditioning", %{test: name} do not_supendable = online_event( diff --git a/test/teslamate/vehicles/vehicle/suspend_test.exs b/test/teslamate/vehicles/vehicle/suspend_test.exs index 1a38250ef9..7a3f54ee07 100644 --- a/test/teslamate/vehicles/vehicle/suspend_test.exs +++ b/test/teslamate/vehicles/vehicle/suspend_test.exs @@ -3,7 +3,7 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do alias TeslaMate.Vehicles.Vehicle - test "suspends when idling.", %{test: name} do + test "suspends when idling", %{test: name} do suspendable = online_event( drive_state: %{timestamp: 0, latitude: 0.0, longitude: 0.0}, @@ -42,6 +42,104 @@ defmodule TeslaMate.Vehicles.Vehicle.SuspendTest do refute_receive _ end + test "does not suspend if sleep mode is disabled", %{test: name} do + suspendable = + online_event( + drive_state: %{timestamp: 0, latitude: 0.0, longitude: 0.0}, + climate_state: %{is_preconditioning: false} + ) + + events = [ + {:ok, suspendable} + ] + + sudpend_after_idle_ms = 1 + suspend_ms = 200 + + :ok = + start_vehicle(name, events, + settings: %{ + sleep_mode_enabled: false, + suspend_after_idle_min: round(sudpend_after_idle_ms / 60), + suspend_min: suspend_ms + } + ) + + assert_receive {:start_state, car_id, :online} + assert_receive {:insert_position, ^car_id, %{}} + + assert_receive {:pubsub, {:broadcast, _, _, %Summary{state: :online, since: s0}}} + + refute_receive _ + end + + test "suspends if sleep mode is disabled but enabled for location", %{test: name} do + suspendable = + online_event(drive_state: %{timestamp: 0, latitude: -50.606993, longitude: 165.972471}) + + events = [ + {:ok, online_event()}, + {:ok, online_event()}, + {:ok, suspendable}, + {:ok, %TeslaApi.Vehicle{state: "asleep"}} + ] + + sudpend_after_idle_ms = 1 + suspend_ms = 200 + + :ok = + start_vehicle(name, events, + settings: %{ + sleep_mode_enabled: false, + suspend_after_idle_min: round(sudpend_after_idle_ms / 60), + suspend_min: suspend_ms + }, + whitelist: [{-50.606993, 165.972471}] + ) + + assert_receive {:start_state, car_id, :online} + assert_receive {:insert_position, ^car_id, %{}} + assert_receive {:pubsub, {:broadcast, _, _, %Summary{state: :online, since: s0}}} + + assert_receive {:pubsub, {:broadcast, _, _, %Summary{state: :suspended, since: s1}}} + assert DateTime.diff(s0, s1, :nanosecond) < 0 + + assert_receive {:start_state, ^car_id, :asleep}, round(suspend_ms * 1.1) + assert_receive {:pubsub, {:broadcast, _, _, %Summary{state: :asleep, since: s2}}} + assert DateTime.diff(s1, s2, :nanosecond) < 0 + + refute_receive _ + end + + test "does not suspend if sleep mode is disabled for the current location", %{test: name} do + suspendable = + online_event(drive_state: %{timestamp: 0, latitude: -50.606993, longitude: 165.972471}) + + events = [ + {:ok, suspendable} + ] + + sudpend_after_idle_ms = 1 + suspend_ms = 200 + + :ok = + start_vehicle(name, events, + settings: %{ + sleep_mode_enabled: true, + suspend_after_idle_min: round(sudpend_after_idle_ms / 60), + suspend_min: suspend_ms + }, + blacklist: [{-50.606993, 165.972471}] + ) + + assert_receive {:start_state, car_id, :online} + assert_receive {:insert_position, ^car_id, %{}} + + assert_receive {:pubsub, {:broadcast, _, _, %Summary{state: :online, since: s0}}} + + refute_receive _ + end + @tag :capture_log test "does not suspend if preconditioning", %{test: name} do not_supendable = diff --git a/test/teslamate_web/live/car_summary_live_test.exs b/test/teslamate_web/live/car_summary_live_test.exs index 5614081684..a0ebc99849 100644 --- a/test/teslamate_web/live/car_summary_live_test.exs +++ b/test/teslamate_web/live/car_summary_live_test.exs @@ -3,7 +3,7 @@ defmodule TeslaMateWeb.CarLive.SummaryTest do use TeslaMate.VehicleCase alias TeslaApi.Vehicle.State.VehicleState.SoftwareUpdate - alias TeslaMate.{Settings, Log, Repo} + alias TeslaMate.{Locations, Settings, Log, Repo} defp table_row(key, value) do ~r/<tr>\n?\s*<td class=\"has-text-weight-medium\">#{key}<\/td>\n?\s*<td.*?>\n?\s*#{value}\n?\s*<\/td>\n?\s*<\/tr>/ @@ -54,8 +54,7 @@ defmodule TeslaMateWeb.CarLive.SummaryTest do assert html = render(view) assert html =~ table_row("Status", "online") - assert html =~ - ~r/a class="button is-info .*?" .*? phx-click="suspend_logging">try to sleep<\/a>/ + assert "try to sleep" == html |> Floki.find("a[phx-click=suspend_logging]") |> Floki.text() render_click(view, :suspend_logging) @@ -120,6 +119,59 @@ defmodule TeslaMateWeb.CarLive.SummaryTest do Floki.find(html, ".button.is-danger") end end + + @tag :signed_in + test "hides suspend button if sleep mode is disabled", %{conn: conn} do + _car = + car_fixture(%{sleep_mode_enabled: false, suspend_min: 60, suspend_after_idle_min: 60}) + + events = [ + {:ok, + online_event( + display_name: "FooCar", + drive_state: %{timestamp: 0, latitude: 0.0, longitude: 0.0}, + vehicle_state: %{sentry_mode: false, locked: true} + )} + ] + + :ok = start_vehicles(events) + + assert {:ok, _view, html} = + live(conn, "/", connect_params: %{"baseUrl" => "http://localhost"}) + + assert [] = Floki.find(html, "a[phx-click=suspend_logging]") + end + + @tag :signed_in + test "disables suspend button if sleep mode is disabled for locations", %{conn: conn} do + car = car_fixture(%{sleep_mode_enabled: true, suspend_min: 60, suspend_after_idle_min: 60}) + + assert {:ok, _geofence} = + Locations.create_geofence(%{ + name: "foo", + latitude: -50.606, + longitude: 165.972, + radius: 500, + sleep_mode_blacklist: [car] + }) + + events = [ + {:ok, + online_event( + display_name: "FooCar", + drive_state: %{timestamp: 0, latitude: -50.606262, longitude: 165.972475}, + vehicle_state: %{sentry_mode: false, locked: true} + )} + ] + + :ok = start_vehicles(events) + + assert {:ok, _view, html} = + live(conn, "/", connect_params: %{"baseUrl" => "http://localhost"}) + + assert ["disabled"] = + html |> Floki.find("a[phx-click=suspend_logging]") |> Floki.attribute("disabled") + end end describe "resume" do @@ -148,8 +200,8 @@ defmodule TeslaMateWeb.CarLive.SummaryTest do assert html = render(view) assert html =~ table_row("Status", "falling asleep") - assert html =~ - ~r/a class="button is-info .*?" .*? phx-click="resume_logging">cancel sleep attempt<\/a>/ + assert "cancel sleep attempt" == + html |> Floki.find("a[phx-click=resume_logging]") |> Floki.text() render_click(view, :resume_logging) diff --git a/test/teslamate_web/live/geofence_live_test.exs b/test/teslamate_web/live/geofence_live_test.exs index 89ea8fe5ae..c620a0912a 100644 --- a/test/teslamate_web/live/geofence_live_test.exs +++ b/test/teslamate_web/live/geofence_live_test.exs @@ -1,8 +1,9 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do use TeslaMateWeb.ConnCase - alias TeslaMate.{Locations, Settings, Log} + alias TeslaMate.{Locations, Settings, Log, Repo} alias TeslaMate.Locations.GeoFence + alias TeslaMate.Log.Car def geofence_fixture(attrs \\ %{}) do {:ok, address} = @@ -13,6 +14,23 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do address end + defp car_fixture(attrs \\ %{}) do + {:ok, car} = + attrs + |> Enum.into(%{ + efficiency: 0.153, + eid: 42, + model: "S", + vid: 42, + name: "foo", + trim_badging: "P100D", + vin: "12345F" + }) + |> Log.create_car() + + car + end + describe "Index" do test "renders all geo-fences", %{conn: conn} do _gf1 = @@ -193,7 +211,8 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do _field_position, _field_radius, field_phase_correction, - _ + _, + _sleep_mode ] = Floki.find(html, ".field.is-horizontal") assert field_phase_correction @@ -204,8 +223,7 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do describe "New" do test "pre-fills the coordinates with the most recent position", %{conn: conn} do - assert {:ok, car} = - Log.create_car(%{efficiency: 0.153, eid: 42, model: "3", vid: 42, vin: "xxxxx"}) + car = car_fixture() assert {:ok, _} = Log.insert_position(car, %{ @@ -224,6 +242,8 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do end test "validates cahnges when creating a new geo-fence", %{conn: conn} do + %Car{id: car_id} = car_fixture() + assert {:ok, view, html} = live(conn, "/geo-fences/new") html = @@ -241,8 +261,14 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do |> Floki.find("#geo_fence_phase_correction option[selected]") |> Floki.attribute("value") - assert [field_name, field_position, field_radius, field_phase_correction, _] = - Floki.find(html, ".field.is-horizontal") + assert [ + field_name, + field_position, + field_radius, + field_phase_correction, + field_sleep_mode, + _ + ] = Floki.find(html, ".field.is-horizontal") assert field_name |> Floki.find("span") |> Floki.text() == "can't be blank" @@ -252,6 +278,11 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do assert field_radius |> Floki.find("span") |> Floki.text() == "can't be blank" assert field_phase_correction |> Floki.find("span") |> Floki.text() == "" + assert ["checked"] = + field_sleep_mode + |> Floki.find("#sleep_mode_#{car_id}") + |> Floki.attribute("checked") + html = render_submit(view, :save, %{ geo_fence: %{ @@ -279,6 +310,7 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do field_position, field_radius, field_phase_correction, + _field_sleep_mod, _ ] = Floki.find(html, ".field.is-horizontal") @@ -334,7 +366,7 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do } }) - assert [_field_name, field_position, _field_radius, _field_phase_corr, _] = + assert [_field_name, field_position, _field_radius, _field_phase_corr, _field_sleep_mode, _] = Floki.find(html, ".field.is-horizontal") assert ["is overlapping with other geo-fence"] = @@ -371,4 +403,134 @@ defmodule TeslaMateWeb.GeoFenceLiveTest do html |> Floki.find("td") |> Enum.map(&Floki.text/1) end end + + test "toggles sleep mode status", %{conn: conn} do + %Car{id: car_id} = car = car_fixture() + %Car{id: another_car_id} = another_car = car_fixture(vid: 43, eid: 43, vin: "43") + + {:ok, _settings} = + Settings.get_car_settings!(another_car) + |> Settings.update_car_settings(%{sleep_mode_enabled: false}) + + assert {:ok, view, html} = live(conn, "/geo-fences/new") + + assert ["checked"] = + html + |> Floki.find("#sleep_mode_#{car.id}") + |> Floki.attribute("checked") + + assert [] = + html + |> Floki.find("#sleep_mode_#{another_car.id}") + |> Floki.attribute("checked") + + assert [] = + render_click(view, :toggle, %{checked: "false", car: to_string(car.id)}) + |> Floki.find("#sleep_mode_#{car.id}") + |> Floki.attribute("checked") + + assert ["checked"] = + render_click(view, :toggle, %{checked: "true", car: to_string(another_car.id)}) + |> Floki.find("#sleep_mode_#{another_car.id}") + |> Floki.attribute("checked") + + assert {:error, {:redirect, %{to: "/geo-fences"}}} = + render_submit(view, :save, %{ + geo_fence: %{ + name: "post office", + latitude: -25.066188, + longitude: -130.100502, + radius: 25 + } + }) + + assert [ + %GeoFence{ + id: id, + sleep_mode_blacklist: [%Car{id: ^car_id}], + sleep_mode_whitelist: [%Car{id: ^another_car_id}] + } + ] = + Locations.list_geofences() + |> Enum.map(&Repo.preload(&1, [:sleep_mode_blacklist, :sleep_mode_whitelist])) + + # enable sleep mode(s) + + assert {:ok, view, html} = live(conn, "/geo-fences/#{id}/edit") + + assert [] = + html + |> Floki.find("#sleep_mode_#{car.id}") + |> Floki.attribute("checked") + + assert ["checked"] = + html + |> Floki.find("#sleep_mode_#{another_car.id}") + |> Floki.attribute("checked") + + assert ["checked"] = + render_click(view, :toggle, %{checked: "true", car: to_string(car.id)}) + |> Floki.find("#sleep_mode_#{car.id}") + |> Floki.attribute("checked") + + assert [] = + render_click(view, :toggle, %{checked: "false", car: to_string(another_car.id)}) + |> Floki.find("#sleep_mode_#{another_car.id}") + |> Floki.attribute("checked") + + assert {:error, {:redirect, %{to: "/geo-fences"}}} = + render_submit(view, :save, %{geo_fence: %{name: "post_office", radius: 20}}) + + assert [ + %GeoFence{ + id: ^id, + sleep_mode_blacklist: [], + sleep_mode_whitelist: [] + } + ] = + Locations.list_geofences() + |> Enum.map(&Repo.preload(&1, [:sleep_mode_blacklist, :sleep_mode_whitelist])) + end + + describe "grafana URL" do + alias TeslaMate.Settings.GlobalSettings + + test "initiall sets the base URL", %{conn: conn} do + assert %GlobalSettings{grafana_url: nil} = Settings.get_global_settings!() + + assert {:ok, _parent_view, _html} = + live(conn, "/geo-fences/new?lat=0.0&lng=0.0", + connect_params: %{"referrer" => "http://grafana.example.com/d/xyz/12"} + ) + + assert %GlobalSettings{grafana_url: "http://grafana.example.com"} = + Settings.get_global_settings!() + end + + test "keeps the path", %{conn: conn} do + assert %GlobalSettings{grafana_url: nil} = Settings.get_global_settings!() + + assert {:ok, _parent_view, _html} = + live(conn, "/geo-fences/new?lat=0.0&lng=0.0", + connect_params: %{"referrer" => "http://example.com:9090/grafana/d/xyz/12"} + ) + + assert %GlobalSettings{grafana_url: "http://example.com:9090/grafana"} = + Settings.get_global_settings!() + end + + test "does not update the base URL if exists already", %{conn: conn} do + assert {:ok, _settings} = + Settings.get_global_settings!() + |> Settings.update_global_settings(%{grafana_url: "https://grafana.example.com"}) + + assert {:ok, _parent_view, _html} = + live(conn, "/geo-fences/new?lat=0.0&lng=0.0", + connect_params: %{"referrer" => "http://grafana.foo.com/d/xyz/12"} + ) + + assert %GlobalSettings{grafana_url: "https://grafana.example.com"} = + Settings.get_global_settings!() + end + end end diff --git a/test/teslamate_web/live/settings_test.exs b/test/teslamate_web/live/settings_test.exs index 8bfbd70a09..b737b2cdfc 100644 --- a/test/teslamate_web/live/settings_test.exs +++ b/test/teslamate_web/live/settings_test.exs @@ -123,6 +123,49 @@ defmodule TeslaMateWeb.SettingsLiveTest do car end + test "Greys out input fields if sleep mode is disabled", %{conn: conn} do + car = car_fixture() + + ids = [ + "#car_settings_#{car.id}_suspend_min", + "#car_settings_#{car.id}_suspend_after_idle_min", + "#car_settings_#{car.id}_req_no_shift_state_reading", + "#car_settings_#{car.id}_req_no_temp_reading", + "#car_settings_#{car.id}_req_not_unlocked" + ] + + assert {:ok, view, html} = live(conn, "/settings") + + assert ["checked"] == + html + |> Floki.find("#car_settings_#{car.id}_sleep_mode_enabled") + |> Floki.attribute("checked") + + html = + render_change(view, :change, %{"car_settings_#{car.id}" => %{sleep_mode_enabled: false}}) + + assert [] = + html + |> Floki.find("#car_settings_#{car.id}_sleep_mode_enabled") + |> Floki.attribute("checked") + + for id <- ids do + assert ["disabled"] = html |> Floki.find(id) |> Floki.attribute("disabled") + end + + html = + render_change(view, :change, %{"car_settings_#{car.id}" => %{sleep_mode_enabled: true}}) + + assert ["checked"] = + html + |> Floki.find("#car_settings_#{car.id}_sleep_mode_enabled") + |> Floki.attribute("checked") + + for id <- ids do + assert [] = html |> Floki.find(id) |> Floki.attribute("disabled") + end + end + test "shows 21 and 15 minutes by default", %{conn: conn} do car = car_fixture() @@ -153,7 +196,7 @@ defmodule TeslaMateWeb.SettingsLiveTest do {"option", [{"value", "85"}], ["85 min"]}, {"option", [{"value", "90"}], ["90 min"]} ]} - ] = Floki.find(html, "#car_settings_suspend_min") + ] = Floki.find(html, "#car_settings_#{car.id}_suspend_min") assert [ {"select", _, @@ -171,7 +214,7 @@ defmodule TeslaMateWeb.SettingsLiveTest do {"option", [{"value", "55"}], ["55 min"]}, {"option", [{"value", "60"}], ["60 min"]} ]} - ] = Floki.find(html, "#car_settings_suspend_after_idle_min") + ] = Floki.find(html, "#car_settings_#{car.id}_suspend_after_idle_min") end test "shows false, false, true by default", %{conn: conn} do @@ -190,17 +233,17 @@ defmodule TeslaMateWeb.SettingsLiveTest do assert [] = html - |> Floki.find("#car_settings_req_no_shift_state_reading") + |> Floki.find("#car_settings_#{car.id}_req_no_shift_state_reading") |> Floki.attribute("checked") assert [] = html - |> Floki.find("#car_settings_req_no_temp_reading") + |> Floki.find("#car_settings_#{car.id}_req_no_temp_reading") |> Floki.attribute("checked") assert ["checked"] = html - |> Floki.find("#car_settings_req_not_unlocked") + |> Floki.find("#car_settings_#{car.id}_req_not_unlocked") |> Floki.attribute("checked") end @@ -221,40 +264,48 @@ defmodule TeslaMateWeb.SettingsLiveTest do assert car.name == html |> Floki.find(".dropdown-item.is-active") |> Floki.text() assert [{"option", [{"value", "90"}, {"selected", "selected"}], ["90 min"]}] = - render_change(view, :change, %{car_settings: %{suspend_min: 90}}) - |> Floki.find("#car_settings_suspend_min option") + render_change(view, :change, %{"car_settings_#{car.id}" => %{suspend_min: 90}}) + |> Floki.find("#car_settings_#{car.id}_suspend_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) assert [settings] = Settings.get_car_settings() assert settings.suspend_min == 90 assert [{"option", [{"value", "30"}, {"selected", "selected"}], ["30 min"]}] = - render_change(view, :change, %{car_settings: %{suspend_after_idle_min: 30}}) - |> Floki.find("#car_settings_suspend_after_idle_min option") + render_change(view, :change, %{ + "car_settings_#{car.id}" => %{suspend_after_idle_min: 30} + }) + |> Floki.find("#car_settings_#{car.id}_suspend_after_idle_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) assert [settings] = Settings.get_car_settings() assert settings.suspend_after_idle_min == 30 assert ["checked"] = - render_change(view, :change, %{car_settings: %{req_no_shift_state_reading: true}}) - |> Floki.find("#car_settings_req_no_shift_state_reading") + render_change(view, :change, %{ + "car_settings_#{car.id}" => %{req_no_shift_state_reading: true} + }) + |> Floki.find("#car_settings_#{car.id}_req_no_shift_state_reading") |> Floki.attribute("checked") assert [settings] = Settings.get_car_settings() assert settings.req_no_shift_state_reading == true assert ["checked"] = - render_change(view, :change, %{car_settings: %{req_no_temp_reading: true}}) - |> Floki.find("#car_settings_req_no_temp_reading") + render_change(view, :change, %{ + "car_settings_#{car.id}" => %{req_no_temp_reading: true} + }) + |> Floki.find("#car_settings_#{car.id}_req_no_temp_reading") |> Floki.attribute("checked") assert [settings] = Settings.get_car_settings() assert settings.req_no_temp_reading == true assert [] = - render_change(view, :change, %{car_settings: %{req_not_unlocked: false}}) - |> Floki.find("#car_settings_req_not_unlocked") + render_change(view, :change, %{ + "car_settings_#{car.id}" => %{req_not_unlocked: false} + }) + |> Floki.find("#car_settings_#{car.id}_req_not_unlocked") |> Floki.attribute("checked") assert [settings] = Settings.get_car_settings() @@ -272,8 +323,8 @@ defmodule TeslaMateWeb.SettingsLiveTest do # change settings of car "one" assert [{"option", [{"value", "90"}, {"selected", "selected"}], ["90 min"]}] = - render_change(view, :change, %{car_settings: %{suspend_min: 90}}) - |> Floki.find("#car_settings_suspend_min option") + render_change(view, :change, %{"car_settings_#{one.id}" => %{suspend_min: 90}}) + |> Floki.find("#car_settings_#{one.id}_suspend_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) assert [settings, _] = Settings.get_car_settings() @@ -290,14 +341,14 @@ defmodule TeslaMateWeb.SettingsLiveTest do assert [{"option", [{"value", "21"}, {"selected", "selected"}], ["21 min"]}] = html - |> Floki.find("#car_settings_suspend_min option") + |> Floki.find("#car_settings_#{two.id}_suspend_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) # change settings of car "two" assert [{"option", [{"value", "60"}, {"selected", "selected"}], ["60 min"]}] = - render_click(view, :change, %{car_settings: %{suspend_min: 60}}) - |> Floki.find("#car_settings_suspend_min option") + render_click(view, :change, %{"car_settings_#{two.id}" => %{suspend_min: 60}}) + |> Floki.find("#car_settings_#{two.id}_suspend_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) # change back @@ -311,7 +362,7 @@ defmodule TeslaMateWeb.SettingsLiveTest do assert [{"option", [{"value", "90"}, {"selected", "selected"}], ["90 min"]}] = html - |> Floki.find("#car_settings_suspend_min option") + |> Floki.find("#car_settings_#{one.id}_suspend_min option") |> Enum.filter(&match?({_, [_, {"selected", "selected"}], _}, &1)) end end diff --git a/test/test_helper.exs b/test/test_helper.exs index c3657fe6f8..65126208fc 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -6,4 +6,7 @@ end TeslaMate.Repo.start_link() -ExUnit.start(assert_receive_timeout: 200) +%{start: {m, f, [name, _opts]}} = TeslaMate.Locations.child_spec([]) +apply(m, f, [name, [limit: 1]]) + +ExUnit.start(assert_receive_timeout: 300)