From 3d3d8b42e2833aba12c4948afaceee8a38258de4 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Wed, 2 Dec 2020 08:29:54 +0100 Subject: [PATCH] Refactor UI into single file components (#491) Authored-by: @naltatis --- .eslintrc.js | 13 + .prettierrc.js | 3 + Makefile | 1 + assets/css/app.css | 31 +- assets/ico/favicon.svg | 83 +- assets/ico/safari-pinned-tab.svg | 25 +- assets/ico/site.webmanifest | 10 +- assets/index.html | 540 +- assets/js/app.js | 443 +- assets/js/components/Loadpoint.vue | 200 + assets/js/components/LoadpointDetails.vue | 70 + assets/js/components/MessageToast.vue | 37 + assets/js/components/Mode.vue | 41 + assets/js/components/Site.vue | 50 + assets/js/components/SiteDetails.vue | 79 + assets/js/components/Soc.vue | 37 + assets/js/components/Toasts.vue | 22 + assets/js/components/Vehicle.vue | 73 + assets/js/components/Version.vue | 86 + assets/js/mixins/formatter.js | 62 + assets/js/router.js | 16 + assets/js/store.js | 36 + assets/js/views/App.vue | 89 + assets/js/views/Config.vue | 95 + assets/js/views/Main.vue | 107 + package-lock.json | 3819 ++- package.json | 31 +- server/assets.go | 26011 +++++++------------- 28 files changed, 13755 insertions(+), 18355 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .prettierrc.js create mode 100644 assets/js/components/Loadpoint.vue create mode 100644 assets/js/components/LoadpointDetails.vue create mode 100644 assets/js/components/MessageToast.vue create mode 100644 assets/js/components/Mode.vue create mode 100644 assets/js/components/Site.vue create mode 100644 assets/js/components/SiteDetails.vue create mode 100644 assets/js/components/Soc.vue create mode 100644 assets/js/components/Toasts.vue create mode 100644 assets/js/components/Vehicle.vue create mode 100644 assets/js/components/Version.vue create mode 100644 assets/js/mixins/formatter.js create mode 100644 assets/js/router.js create mode 100644 assets/js/store.js create mode 100644 assets/js/views/App.vue create mode 100644 assets/js/views/Config.vue create mode 100644 assets/js/views/Main.vue diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..145a9383e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + env: { + browser: true, + node: true, + es6: true, + }, + extends: [ + "eslint:recommended", + "plugin:vue/essential", + "plugin:prettier/recommended", + "prettier", + ], +}; diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..5f9a0815f --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + printWidth: 100, +}; diff --git a/Makefile b/Makefile index 6a24dd112..bad095b5d 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ install: lint: golangci-lint run + npm run lint test: @echo "Running testsuite" diff --git a/assets/css/app.css b/assets/css/app.css index d121d6367..6fdde5d8e 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,26 +1,29 @@ /* reverse loading animation */ .progress-bar-animated { - animation-direction: reverse; + animation-direction: reverse; } .btn.caption { - opacity: 1; + opacity: 1; } .btn.first { - border-top-left-radius: .2rem; - border-bottom-left-radius: .2rem; + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; } code { - font-size: 87.5%; - color: #e83e8c; - word-wrap: break-word; + font-size: 87.5%; + color: #e83e8c; + word-wrap: break-word; } -.value, input[type=radio], label.btn { - white-space: nowrap !important; +.value, +input[type="radio"], +label.btn { + white-space: nowrap !important; } -.text-muted a, .text-muted a:hover { - color: #6c757d!important; - text-decoration: none; +.text-muted a, +.text-muted a:hover { + color: #6c757d !important; + text-decoration: none; } .bg-muted { - opacity: 0.25; -} \ No newline at end of file + opacity: 0.25; +} diff --git a/assets/ico/favicon.svg b/assets/ico/favicon.svg index 04a56b43e..73b7efaa7 100644 --- a/assets/ico/favicon.svg +++ b/assets/ico/favicon.svg @@ -1,58 +1,27 @@ - - - - - image/svg+xml - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/assets/ico/safari-pinned-tab.svg b/assets/ico/safari-pinned-tab.svg index 977605fa5..4d91df165 100644 --- a/assets/ico/safari-pinned-tab.svg +++ b/assets/ico/safari-pinned-tab.svg @@ -1,15 +1,12 @@ - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - + + + Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + 11 51 3 14 8 36 10 50 2 14 6 36 9 50 6 34 16 97 21 140 3 19 7 55 10 79 16 121 19 198 19 446 -1 238 -4 308 -20 430 -2 17 -5 49 -8 72 -2 23 -7 55 -10 70 -3 15 -8 48 -12 73 -3 24 -7 48 -10 51 -2 4 -6 24 -10 45 -19 130 -124 484 --181 614 -57 130 -81 162 -130 181 -34 13 -82 11 -119 -5z"/> - - +-181 614 -57 130 -81 162 -130 181 -34 13 -82 11 -119 -5z" /> + + \ No newline at end of file diff --git a/assets/ico/site.webmanifest b/assets/ico/site.webmanifest index 1f1869a28..c9a28f674 100644 --- a/assets/ico/site.webmanifest +++ b/assets/ico/site.webmanifest @@ -1,8 +1,8 @@ { "start_url": "../#", - "name": "EV Charge Controller", - "short_name": "EV Charge Controller", - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + "name": "EV Charge Controller", + "short_name": "EV Charge Controller", + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" } diff --git a/assets/index.html b/assets/index.html index ba0983482..43da3a3db 100644 --- a/assets/index.html +++ b/assets/index.html @@ -1,507 +1,55 @@ - + - - - - - - - + + + + + + + - - - - - - - - - + + + + + + + + + - evcc + evcc - - - - - + + + + + + -
- +
+
- - - - -
- - - -
-
- - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/assets/js/app.js b/assets/js/app.js index 594782ef3..b0ed5225b 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,227 +1,54 @@ -import $ from "jquery"; -import "popper.js" -import "bootstrap" -import Vue from "vue" -import VueRouter from "vue-router" -import axios from "axios" -import { library } from '@fortawesome/fontawesome-svg-core' -import { faSun, faArrowUp, faArrowDown, faTemperatureLow, faTemperatureHigh, faThermometerHalf, faLeaf, faChevronUp, faChevronDown, faExclamationTriangle }from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import "popper.js"; +import "bootstrap"; +import Vue from "vue"; +import axios from "axios"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faSun } from "@fortawesome/free-solid-svg-icons/faSun"; +import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp"; +import { faArrowDown } from "@fortawesome/free-solid-svg-icons/faArrowDown"; +import { faTemperatureLow } from "@fortawesome/free-solid-svg-icons/faTemperatureLow"; +import { faTemperatureHigh } from "@fortawesome/free-solid-svg-icons/faTemperatureHigh"; +import { faThermometerHalf } from "@fortawesome/free-solid-svg-icons/faThermometerHalf"; +import { faLeaf } from "@fortawesome/free-solid-svg-icons/faLeaf"; +import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp"; +import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; +import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons/faExclamationTriangle"; +import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; +import App from "./views/App"; +import Toasts from "./components/Toasts"; +import router from "./router"; +import store from "./store"; -library.add(faSun, faArrowUp, faArrowDown, faTemperatureLow, faTemperatureHigh, faThermometerHalf, faLeaf, faChevronUp, faChevronDown, faExclamationTriangle) +library.add( + faSun, + faArrowUp, + faArrowDown, + faTemperatureLow, + faTemperatureHigh, + faThermometerHalf, + faLeaf, + faChevronUp, + faChevronDown, + faExclamationTriangle +); -Vue.component('fa-icon', FontAwesomeIcon) - -Vue.use(VueRouter) +Vue.component("font-awesome-icon", FontAwesomeIcon); const loc = window.location; -axios.defaults.baseURL = loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api"; -axios.defaults.headers.post['Content-Type'] = 'application/json'; +axios.defaults.baseURL = + loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api"; +axios.defaults.headers.post["Content-Type"] = "application/json"; -// -// Mixins -// - -let formatter = { - data: function () { - return { - fmtLimit: 100, - fmtDigits: 1, - } - }, - methods: { - round: function(num, precision) { - var base = 10 ** precision; - return (Math.round(num * base) / base).toFixed(precision); - }, - fmt: function (val) { - if (val === undefined || val === null) { - return 0; - } - val = Math.abs(val); - return val >= this.fmtLimit ? this.round(val / 1e3, this.fmtDigits) : this.round(val, 0); - }, - fmtUnit: function (val) { - return Math.abs(val) >= this.fmtLimit ? "k" : ""; - }, - fmtDuration: function (d) { - if (d <= 0 || d == null) { - return '—'; - } - var seconds = "0" + (d % 60); - var minutes = "0" + (Math.floor(d / 60) % 60); - var hours = "" + Math.floor(d / 3600); - if (hours.length < 2) { - hours = "0" + hours; - } - return hours + ":" + minutes.substr(-2) + ":" + seconds.substr(-2); - }, - fmtShortDuration: function (d) { - if (d <= 0 || d == null) { - return '—'; - } - var minutes = (Math.floor(d / 60) % 60); - var hours = Math.floor(d / 3600); - var tm; - if (hours >= 1) { - minutes = "0" + minutes; - tm = hours + ":" + minutes.substr(-2); - } else { - var seconds = "0" + (d % 60); - tm = minutes + ":" + seconds.substr(-2); - } - return tm; - }, - fmtShortDurationUnit: function (d) { - if (d <= 0 || d == null) { - return ''; - } - var hours = Math.floor(d / 3600); - if (hours >= 1) { - return "h"; - } - return "m"; - }, - } -} - -// -// State -// - -function setProperty(obj, props, value) { - const prop = props.shift() - if (!obj[prop]) { - Vue.set(obj, prop, {}) - } - - if (!props.length) { - if (value && typeof value === 'object' && !Array.isArray(value)) { - obj[prop] = { ...obj[prop], ...value } - } else { - obj[prop] = value - } - return - } - - setProperty(obj[prop], props, value) -} - -let store = { - state: { - loadpoints: [], // ensure array type - }, - update: function(msg) { - Object.keys(msg).forEach(function (k) { - if (typeof toasts[k] === "function") { - toasts[k]({message: msg[k]}) - } else { - setProperty(store.state, k.split('.'), msg[k]) - } - }); - } -}; - -// -// Heartbeat -// - -window.setInterval(function() { - axios.get("health").catch(function(res) { - res.message = "Server unavailable"; - toasts.error(res) - }); -}, 5000); - -// -// App & Routing -// - -const main = Vue.component('Main', { - template: '#main-template', - data: function() { - return { - state: store.state // global state - } - }, - methods: { - configured: function (val) { - // for development purposes - if (val == '[[.Configured]]') { - return true; - } - if (!isNaN(parseInt(val)) && parseInt(val) > 0) { - return true; - } - return false; - } - } -}); - -const config = Vue.component("config", { - template: "#config-template", - data: function() { - return { - state: store.state // global state - }; - }, -}); - -const routes = [ - { path: "/", component: main }, - { path: "/config", component: config }, -]; - -const router = new VueRouter({ - routes, // short for `routes: routes` - linkExactActiveClass: "active", // Bootstrap