diff --git a/.dockerignore b/.dockerignore index 2832f5cb3..ab823cb09 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ +.cache +.storybook .vscode *.conf *.Dockerfile @@ -5,7 +7,6 @@ *.sh *.yaml Dockerfile -.cache dist node_modules !entrypoint.sh diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 000000000..c8b46ca1f --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,4 @@ +module.exports = { + stories: ["../assets/js/**/*.stories.mdx", "../assets/js/**/*.stories.@(js|jsx|ts|tsx)"], + addons: ["@storybook/addon-links", "@storybook/addon-essentials"], +}; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 000000000..b7f801aed --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,2 @@ + + diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 000000000..5d00c0212 --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,4 @@ + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 1ec710644..49c227147 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2,40 +2,11 @@ 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 { faBatteryThreeQuarters } from "@fortawesome/free-solid-svg-icons/faBatteryThreeQuarters"; -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, - faBatteryThreeQuarters, - faTemperatureLow, - faTemperatureHigh, - faThermometerHalf, - faLeaf, - faChevronUp, - faChevronDown, - faExclamationTriangle -); - -Vue.component("fa-icon", FontAwesomeIcon); - const loc = window.location; axios.defaults.baseURL = loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "") + loc.pathname + "api"; diff --git a/assets/js/components/Loadpoint.stories.js b/assets/js/components/Loadpoint.stories.js new file mode 100644 index 000000000..a7cabc4e2 --- /dev/null +++ b/assets/js/components/Loadpoint.stories.js @@ -0,0 +1,29 @@ +import Loadpoint from "./Loadpoint.vue"; + +export default { + title: "Main/Loadpoint", + component: Loadpoint, + argTypes: { + mode: { control: { type: "inline-radio", options: ["off", "now", "minpv", "pv"] } }, + climater: { control: { type: "inline-radio", options: ["on", "heating", "cooling"] } }, + }, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { Loadpoint }, + template: '', +}); + +export const Base = Template.bind({}); +Base.args = { + id: 0, + pvConfigured: true, +}; + +export const WithLevels = Template.bind({}); +WithLevels.args = { + id: 0, + pvConfigured: true, + socLevels: [20, 50, 80, 100], +}; diff --git a/assets/js/components/Loadpoint.vue b/assets/js/components/Loadpoint.vue index 760ef9a29..537f84ace 100644 --- a/assets/js/components/Loadpoint.vue +++ b/assets/js/components/Loadpoint.vue @@ -2,11 +2,11 @@
- {{ state.title || "Ladepunkt" }} + {{ title || "Ladepunkt" }}
- +
@@ -14,17 +14,17 @@
@@ -32,23 +32,20 @@
-
+
@@ -57,35 +54,32 @@
- {{ state.title || "Ladepunkt" }} + {{ title || "Ladepunkt" }}
-
+
- - {{ state.remoteDisabledSource }}: Adaptives PV-Laden deaktiviert + + {{ remoteDisabledSource }}: Adaptives PV-Laden deaktiviert - - {{ state.remoteDisabledSource }}: Deaktiviert + + {{ remoteDisabledSource }}: Deaktiviert
@@ -95,34 +89,41 @@
- +
Modus
Ladeziel
- +
- +
@@ -138,28 +139,72 @@ import formatter from "../mixins/formatter"; export default { name: "Loadpoint", - props: ["state", "id", "pv", "multi"], + props: { + id: Number, + multi: Boolean, + pvConfigured: Boolean, + + // main + title: String, + mode: String, + targetSoC: Number, + socLevels: Array, + remoteDisabled: Boolean, + remoteDisabledSource: String, + chargeDuration: Number, + charging: Boolean, + + // vehicle + connected: Boolean, + // charging: Boolean, + socTitle: String, + socCharge: Number, + minSoC: Number, + + // details + chargePower: Number, + chargedEnergy: Number, + // chargeDuration: Number, + soc: Boolean, + climater: String, + range: Number, + chargeEstimate: Number, + }, components: { LoadpointDetails, Soc, Mode, Vehicle }, mixins: [formatter], data: function () { return { tickerHandle: null, + chargeDurationDisplayed: null, }; }, computed: { + details: function () { + return { + connected: this.connected, + minSoC: this.minSoC, + socCharge: this.socCharge, + climater: this.climater, + chargePower: this.chargePower, + chargedEnergy: this.chargedEnergy, + range: this.range, + chargeDuration: this.chargeDurationDisplayed, + chargeEstimate: this.chargeEstimate, + }; + }, hasTargetSoC: function () { - return this.state.socLevels != null && this.state.socLevels.length > 0; + return this.socLevels != null && this.socLevels.length > 0; }, }, watch: { - "state.chargeDuration": function () { + chargeDuration: function () { window.clearInterval(this.tickerHandle); // only ticker if actually charging - if (this.state.charging && this.state.chargeDuration >= 0) { + if (this.charging && this.chargeDuration >= 0) { + this.chargeDurationDisplayed = this.chargeDuration; this.tickerHandle = window.setInterval( function () { - // eslint-disable-next-line vue/no-mutating-props - this.state.chargeDuration += 1; + this.chargeDurationDisplayed += 1; }.bind(this), 1000 ); @@ -170,24 +215,24 @@ export default { api: function (func) { return "loadpoints/" + this.id + "/" + func; }, - targetMode: function (mode) { + setTargetMode: function (mode) { axios .post(this.api("mode") + "/" + mode) .then( function (response) { // eslint-disable-next-line vue/no-mutating-props - this.state.mode = response.data.mode; + this.mode = response.data.mode; }.bind(this) ) .catch(window.toasts.error); }, - targetSoC: function (soc) { + setTargetSoC: function (soc) { axios .post(this.api("targetsoc") + "/" + soc) .then( function (response) { // eslint-disable-next-line vue/no-mutating-props - this.state.targetSoC = response.data.targetSoC; + this.targetSoC = response.data.targetSoC; }.bind(this) ) .catch(window.toasts.error); diff --git a/assets/js/components/LoadpointDetails.stories.js b/assets/js/components/LoadpointDetails.stories.js new file mode 100644 index 000000000..f5c8b9ef2 --- /dev/null +++ b/assets/js/components/LoadpointDetails.stories.js @@ -0,0 +1,55 @@ +import LoadpointDetails from "./LoadpointDetails.vue"; + +export default { + title: "Main/LoadpointDetails", + component: LoadpointDetails, + argTypes: { + climater: { control: { type: "inline-radio", options: ["on", "heating", "cooling"] } }, + }, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { LoadpointDetails }, + template: '', +}); + +export const Base = Template.bind({}); +Base.args = {}; + +export const Charging = Template.bind({}); +Charging.args = { + chargePower: 2800, + chargedEnergy: 11e3, + chargeDuration: 95 * 60, +}; + +export const Vehicle = Template.bind({}); +Vehicle.args = { + chargePower: 2800, + chargedEnergy: 11e3, + chargeDuration: 95 * 60, + soc: true, + chargeEstimate: 5 * 3600, +}; + +export const VehicleRange = Template.bind({}); +VehicleRange.args = { + chargePower: 2800, + chargedEnergy: 11e3, + chargeDuration: 95 * 60, + soc: true, + range: 240.123, + chargeEstimate: 5 * 3600, +}; + +export const VehicleClimater = Template.bind({}); +VehicleClimater.args = { + chargePower: 2800, + chargedEnergy: 11e3, + chargeDuration: 95 * 60, + soc: true, + range: 240.123, + chargeEstimate: 5 * 3600, + climater: "on", +}; diff --git a/assets/js/components/LoadpointDetails.vue b/assets/js/components/LoadpointDetails.vue index d464ec172..3a8dca889 100644 --- a/assets/js/components/LoadpointDetails.vue +++ b/assets/js/components/LoadpointDetails.vue @@ -6,36 +6,36 @@

- {{ fmt(state.chargePower) }} - {{ fmtUnit(state.chargePower) }}W + {{ fmt(chargePower) }} + {{ fmtUnit(chargePower) }}W

Geladen

- {{ fmt(state.chargedEnergy) }} - {{ fmtUnit(state.chargedEnergy) }}Wh + {{ fmt(chargedEnergy) }} + {{ fmtUnit(chargedEnergy) }}Wh

-
+
Reichweite

- {{ state.range }} + {{ Math.round(range) }} km

@@ -43,36 +43,36 @@
Dauer

- {{ fmtShortDuration(state.chargeDuration) }} - {{ fmtShortDurationUnit(state.chargeDuration) }} + {{ fmtShortDuration(chargeDuration) }} + {{ fmtShortDurationUnit(chargeDuration) }}

-
+
Restzeit

- {{ fmtShortDuration(state.chargeEstimate) }} - {{ fmtShortDurationUnit(state.chargeEstimate) }} + {{ fmtShortDuration(chargeEstimate) }} + {{ fmtShortDurationUnit(chargeEstimate) }}

diff --git a/assets/js/components/Mode.stories.js b/assets/js/components/Mode.stories.js new file mode 100644 index 000000000..570343f5d --- /dev/null +++ b/assets/js/components/Mode.stories.js @@ -0,0 +1,25 @@ +import Mode from "./Mode.vue"; + +export default { + title: "Main/Mode", + component: Mode, + argTypes: { + mode: { control: { type: "inline-radio", options: ["off", "now", "minpv", "pv"] } }, + }, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { Mode }, + template: '', +}); + +export const Base = Template.bind({}); +Base.args = {}; + +export const CaptionAndPV = Template.bind({}); +CaptionAndPV.args = { + caption: true, + pvConfigured: true, + mode: "pv", +}; diff --git a/assets/js/components/Mode.vue b/assets/js/components/Mode.vue index 5ce6a9820..33cdabee1 100644 --- a/assets/js/components/Mode.vue +++ b/assets/js/components/Mode.vue @@ -6,22 +6,27 @@ > Modus + + - - -
@@ -19,7 +19,11 @@ diff --git a/assets/js/components/Vehicle.stories.js b/assets/js/components/Vehicle.stories.js new file mode 100644 index 000000000..d4f510b17 --- /dev/null +++ b/assets/js/components/Vehicle.stories.js @@ -0,0 +1,37 @@ +import Vehicle from "./Vehicle.vue"; + +export default { + title: "Main/Vehicle", + component: Vehicle, + argTypes: {}, +}; + +const Template = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { Vehicle }, + template: '', +}); + +export const Base = Template.bind({}); +Base.args = { + soc: false, + socTitle: "Mein Auto", + connected: true, +}; + +export const Car = Template.bind({}); +Car.args = { + soc: true, + socTitle: "Mein Auto", + connected: true, + socCharge: 15, +}; + +export const CarWithMin = Template.bind({}); +CarWithMin.args = { + soc: true, + socTitle: "Mein Auto", + connected: true, + socCharge: 15, + minSoC: 30, +}; diff --git a/assets/js/components/Vehicle.vue b/assets/js/components/Vehicle.vue index 96f09bae9..108d506f4 100644 --- a/assets/js/components/Vehicle.vue +++ b/assets/js/components/Vehicle.vue @@ -1,31 +1,31 @@