kW/W units; tighter icon threshold; ignore batterypower below 50W; revert navbar link; i18n number formats (#1352)
This commit is contained in:
parent
51824bb8a0
commit
1e223014cd
14 changed files with 88 additions and 40 deletions
|
|
@ -2,6 +2,14 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import Energyflow from "./Energyflow.vue";
|
||||
|
||||
const mocks = {
|
||||
$t: (x) => x,
|
||||
$n: function (value, options) {
|
||||
const n = new Intl.NumberFormat("en-EN", options);
|
||||
return n.format(value);
|
||||
},
|
||||
};
|
||||
|
||||
describe("Energyflow.vue", () => {
|
||||
const defaultProps = {
|
||||
gridConfigured: true,
|
||||
|
|
@ -15,7 +23,7 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
it("using pv and grid power", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: { ...defaultProps, gridPower: 1000, pvPower: 4000 },
|
||||
});
|
||||
await wrapper.find(".energyflow").trigger("click");
|
||||
|
|
@ -31,7 +39,7 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
it("exporting all pv power, no usage", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: { ...defaultProps, gridPower: -4000, pvPower: 4000 },
|
||||
});
|
||||
|
||||
|
|
@ -48,7 +56,7 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
it("more grid export than pv, grid value wins (invalid state)", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: { ...defaultProps, gridPower: -4000, pvPower: 3000 },
|
||||
});
|
||||
|
||||
|
|
@ -65,7 +73,7 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
it("only grid usage, no pv, idleBattery", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
gridPower: 360,
|
||||
|
|
@ -77,23 +85,23 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
await wrapper.find(".energyflow").trigger("click");
|
||||
|
||||
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("0.4 kW");
|
||||
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0.0 kW");
|
||||
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0.0 kW");
|
||||
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("360 W");
|
||||
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0 W");
|
||||
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0 W");
|
||||
|
||||
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("0.4 kW");
|
||||
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0.0 kW");
|
||||
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("360 W");
|
||||
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0 W");
|
||||
expect(wrapper.find("[data-test-battery]").text()).toMatch("main.energyflow.battery");
|
||||
});
|
||||
|
||||
it("grid and battery usage, no pv", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
gridPower: 300,
|
||||
batteryConfigured: true,
|
||||
batteryPower: 200,
|
||||
batteryPower: 234,
|
||||
batterySoC: 77,
|
||||
pvPower: 0,
|
||||
},
|
||||
|
|
@ -101,20 +109,20 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
await wrapper.find(".energyflow").trigger("click");
|
||||
|
||||
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("0.3 kW");
|
||||
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("0.2 kW");
|
||||
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0.0 kW");
|
||||
expect(wrapper.find("[data-test-grid-import]").text()).toMatch("300 W");
|
||||
expect(wrapper.find("[data-test-self-consumption]").text()).toMatch("234 W");
|
||||
expect(wrapper.find("[data-test-pv-export]").text()).toMatch("0 W");
|
||||
|
||||
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("0.5 kW");
|
||||
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0.0 kW");
|
||||
expect(wrapper.find("[data-test-battery]").text()).toMatch("0.2 kW");
|
||||
expect(wrapper.find("[data-test-house-consumption]").text()).toMatch("534 W");
|
||||
expect(wrapper.find("[data-test-pv-production]").text()).toMatch("0 W");
|
||||
expect(wrapper.find("[data-test-battery]").text()).toMatch("234 W");
|
||||
expect(wrapper.find("[data-test-battery]").text()).toMatch("77%");
|
||||
expect(wrapper.find("[data-test-battery]").text()).toMatch("main.energyflow.batteryDischarge");
|
||||
});
|
||||
|
||||
it("battery charge, pv export", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
gridPower: -2500,
|
||||
|
|
@ -138,7 +146,7 @@ describe("Energyflow.vue", () => {
|
|||
|
||||
it("thresholds", async () => {
|
||||
const wrapper = shallowMount(Energyflow, {
|
||||
mocks: { $t: (x) => x },
|
||||
mocks,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
gridPower: 5555,
|
||||
|
|
|
|||
|
|
@ -100,3 +100,15 @@ GridOnly.args = {
|
|||
batterySoC: 0,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
export const LowEnergy = Template.bind({});
|
||||
LowEnergy.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
batteryConfigured: true,
|
||||
gridPower: -352,
|
||||
pvPower: 710,
|
||||
batteryPower: 86,
|
||||
batterySoC: 55,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
:pvProduction="pvProduction"
|
||||
:houseConsumption="houseConsumption"
|
||||
:batterySoC="batterySoC"
|
||||
:valuesInKw="valuesInKw"
|
||||
/>
|
||||
<div
|
||||
class="col-12 col-sm-6 col-md-5 col-lg-3 col-xl-3 order-md-1 mt-2 mt-md-0"
|
||||
|
|
@ -153,25 +154,32 @@ export default {
|
|||
this.pvProduction + this.gridPower - this.batteryCharge
|
||||
);
|
||||
},
|
||||
batteryPowerAdjusted: function () {
|
||||
const batteryPowerThreshold = 50;
|
||||
return Math.abs(this.batteryPower) < batteryPowerThreshold ? 0 : this.batteryPower;
|
||||
},
|
||||
batteryDischarge: function () {
|
||||
return Math.max(0, this.batteryPower);
|
||||
return Math.max(0, this.batteryPowerAdjusted);
|
||||
},
|
||||
batteryCharge: function () {
|
||||
return Math.min(0, this.batteryPower) * -1;
|
||||
return Math.min(0, this.batteryPowerAdjusted) * -1;
|
||||
},
|
||||
houseConsumption: function () {
|
||||
return this.gridImport + this.pvConsumption + this.batteryDischarge;
|
||||
return Math.max(0, this.gridImport + this.pvConsumption + this.batteryDischarge);
|
||||
},
|
||||
selfConsumption: function () {
|
||||
return this.batteryDischarge + this.pvConsumption + this.batteryCharge;
|
||||
return Math.max(0, this.batteryDischarge + this.pvConsumption + this.batteryCharge);
|
||||
},
|
||||
pvExport: function () {
|
||||
return Math.min(0, this.gridPower) * -1;
|
||||
return Math.max(0, this.gridPower * -1);
|
||||
},
|
||||
valuesInKw: function () {
|
||||
return this.gridImport + this.selfConsumption + this.pvExport > 1000;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
kw: function (watt) {
|
||||
return Math.max(0, watt / 1000).toFixed(1) + " kW";
|
||||
return this.fmtKw(watt, this.valuesInKw);
|
||||
},
|
||||
toggleDetails() {
|
||||
this.showDetails = !this.showDetails;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<div
|
||||
class="label-bar label-bar--down"
|
||||
:class="{
|
||||
'label-bar--hide-icon': hideLabelIcon(batteryDischarge),
|
||||
'label-bar--hide-icon': hideLabelIcon(batteryDischarge, 44),
|
||||
'label-bar--hidden': !selfConsumptionAdjusted,
|
||||
}"
|
||||
:style="{ width: widthTotal(batteryDischarge) }"
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
<div
|
||||
class="label-bar label-bar--up"
|
||||
:class="{
|
||||
'label-bar--hide-icon': hideLabelIcon(batteryCharge),
|
||||
'label-bar--hide-icon': hideLabelIcon(batteryCharge, 44),
|
||||
'label-bar--hidden': !selfConsumptionAdjusted,
|
||||
}"
|
||||
:style="{ width: widthTotal(batteryCharge) }"
|
||||
|
|
@ -119,6 +119,7 @@ export default {
|
|||
pvProduction: { type: Number, default: 0 },
|
||||
houseConsumption: { type: Number, default: 0 },
|
||||
batterySoC: { type: Number, default: 0 },
|
||||
valuesInKw: { type: Boolean, default: false },
|
||||
},
|
||||
data: function () {
|
||||
return { width: 0 };
|
||||
|
|
@ -161,7 +162,7 @@ export default {
|
|||
return (100 / this.totalAdjusted) * power + "%";
|
||||
},
|
||||
kw: function (watt) {
|
||||
return Math.max(0, watt / 1000).toFixed(1) + " kW";
|
||||
return this.fmtKw(watt, this.valuesInKw);
|
||||
},
|
||||
hidePowerLabel(power) {
|
||||
if (this.totalAdjusted === 0) return true;
|
||||
|
|
@ -169,9 +170,8 @@ export default {
|
|||
const percent = (100 / this.totalAdjusted) * power;
|
||||
return (this.width / 100) * percent < minWidth;
|
||||
},
|
||||
hideLabelIcon(power) {
|
||||
hideLabelIcon(power, minWidth = 32) {
|
||||
if (this.totalAdjusted === 0) return true;
|
||||
const minWidth = 60;
|
||||
const percent = (100 / this.totalAdjusted) * power;
|
||||
return (this.width / 100) * percent < minWidth;
|
||||
},
|
||||
|
|
@ -258,7 +258,7 @@ export default {
|
|||
.label-bar-icon {
|
||||
background-color: white;
|
||||
color: var(--bs-gray);
|
||||
padding: 0 0.75rem;
|
||||
padding: 0 0.3rem;
|
||||
opacity: 1;
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ export default {
|
|||
val = Math.abs(val);
|
||||
return val >= this.fmtLimit ? this.round(val / 1e3, this.fmtDigits) : this.round(val, 0);
|
||||
},
|
||||
fmtKw: function (watt, kw = true) {
|
||||
const digits = kw ? 1 : 0;
|
||||
const value = kw ? watt / 1000 : watt;
|
||||
const unit = kw ? " kW" : " W";
|
||||
return (
|
||||
this.$n(value, { minimumFractionDigits: digits, maximumFractionDigits: digits }) + unit
|
||||
);
|
||||
},
|
||||
fmtUnit: function (val) {
|
||||
return Math.abs(val) >= this.fmtLimit ? "k" : "";
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<div class="flex-grow-1 d-flex flex-column justify-content-between">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/" @click.prevent="reload">
|
||||
<Logo class="logo me-3"></Logo>
|
||||
<a class="navbar-brand" href="https://github.com/andig/evcc">
|
||||
<Logo class="logo"></Logo>
|
||||
</a>
|
||||
<div class="d-flex">
|
||||
<div class="d-flex">
|
||||
|
|
|
|||
1
dist/css/index.637a2552.css
vendored
Normal file
1
dist/css/index.637a2552.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/css/index.d2c9ca1b.css
vendored
1
dist/css/index.d2c9ca1b.css
vendored
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.d2c9ca1b.css" rel="preload" as="style"><link href="js/chunk-vendors.1a7ce110.js" rel="preload" as="script"><link href="js/index.6e9352a0.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.d2c9ca1b.css" rel="stylesheet"></head><body><script>window.evcc = {
|
||||
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.882a611f.css" rel="preload" as="style"><link href="css/index.637a2552.css" rel="preload" as="style"><link href="js/chunk-vendors.1a7ce110.js" rel="preload" as="script"><link href="js/index.95321b25.js" rel="preload" as="script"><link href="css/chunk-vendors.882a611f.css" rel="stylesheet"><link href="css/index.637a2552.css" rel="stylesheet"></head><body><script>window.evcc = {
|
||||
version: "[[.Version]]",
|
||||
configured: "[[.Configured]]",
|
||||
};</script><div id="app"></div><script src="js/chunk-vendors.1a7ce110.js"></script><script src="js/index.6e9352a0.js"></script></body></html>
|
||||
};</script><div id="app"></div><script src="js/chunk-vendors.1a7ce110.js"></script><script src="js/index.95321b25.js"></script></body></html>
|
||||
2
dist/js/index.6e9352a0.js
vendored
2
dist/js/index.6e9352a0.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/index.6e9352a0.js.map
vendored
1
dist/js/index.6e9352a0.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/js/index.95321b25.js
vendored
Normal file
2
dist/js/index.95321b25.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/js/index.95321b25.js.map
vendored
Normal file
1
dist/js/index.95321b25.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -18434,6 +18434,16 @@
|
|||
"minipass": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
|
|
@ -23084,12 +23094,14 @@
|
|||
},
|
||||
"tar": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^3.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue