From 41a5dd73558398c05c1fc53630fb9bb22cd1d3b5 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Sat, 6 Jan 2024 13:21:21 +0100 Subject: [PATCH] Add setting modals to top navigation (#11405) --- assets/css/app.css | 1 + .../js/components/Energyflow/Energyflow.vue | 40 +++---- .../components/Energyflow/EnergyflowEntry.vue | 1 + assets/js/components/TopNavigation.vue | 45 ++++++-- assets/js/mixins/collector.js | 7 +- assets/js/utils/gridModalAvailable.js | 5 + assets/js/views/App.vue | 30 +++++ i18n/de.toml | 3 +- i18n/en.toml | 9 +- tariff/fixed.go | 2 +- tests/limits.spec.js | 2 +- tests/modals.spec.js | 103 ++++++++++++++++++ tests/simulator.evcc.yaml | 5 +- 13 files changed, 207 insertions(+), 46 deletions(-) create mode 100644 assets/js/utils/gridModalAvailable.js create mode 100644 tests/modals.spec.js diff --git a/assets/css/app.css b/assets/css/app.css index 4bf4818f7..3511d755b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -78,6 +78,7 @@ --evcc-accent2: var(--evcc-dark-green); --evcc-accent3: var(--evcc-darker-green); --bs-primary: var(--evcc-dark-green); + --bs-border-color-translucent: rgba(255, 255, 255, 0.175); } html { diff --git a/assets/js/components/Energyflow/Energyflow.vue b/assets/js/components/Energyflow/Energyflow.vue index b5317d08e..c4bac2732 100644 --- a/assets/js/components/Energyflow/Energyflow.vue +++ b/assets/js/components/Energyflow/Energyflow.vue @@ -2,6 +2,7 @@
@@ -78,6 +79,7 @@ :details="batterySoc" :detailsFmt="batteryFmt" detailsClickable + data-testid="energyflow-entry-batterydischarge" @details-clicked="openBatterySettingsModal" />
@@ -157,8 +160,6 @@
- - @@ -167,13 +168,12 @@ import "@h2d2/shopicons/es/filled/square"; import Modal from "bootstrap/js/dist/modal"; import Visualization from "./Visualization.vue"; import EnergyflowEntry from "./EnergyflowEntry.vue"; -import GridSettingsModal from "../GridSettingsModal.vue"; import formatter from "../../mixins/formatter"; import AnimatedNumber from "../AnimatedNumber.vue"; import settings from "../../settings"; -import { CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE } from "../../units"; +import { CO2_TYPE } from "../../units"; import collector from "../../mixins/collector"; -import BatterySettingsModal from "../BatterySettingsModal.vue"; +import gridModalAvailable from "../../utils/gridModalAvailable"; export default { name: "Energyflow", @@ -181,8 +181,6 @@ export default { Visualization, EnergyflowEntry, AnimatedNumber, - GridSettingsModal, - BatterySettingsModal, }, mixins: [formatter, collector], props: { @@ -217,8 +215,8 @@ export default { return { detailsOpen: false, detailsCompleteHeight: null, gridSettingsModal: null }; }, computed: { - smartCostAvailable: function () { - return [CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE].includes(this.smartCostType); + gridModalAvailable: function () { + return gridModalAvailable(this.smartCostType); }, gridImport: function () { return Math.max(0, this.gridPower); @@ -288,27 +286,15 @@ export default { batteryFmt() { return (soc) => `${Math.round(soc)}%`; }, - gridSettings() { - return this.collectProps(GridSettingsModal); - }, - batterySettings() { - return this.collectProps(BatterySettingsModal); - }, co2Available() { return this.smartCostType === CO2_TYPE; }, }, mounted() { - this.gridSettingsModal = Modal.getOrCreateInstance( - document.querySelector("#gridSettingsModal") - ); - this.batterySettingsModal = Modal.getOrCreateInstance( - document.querySelector("#batterySettingsModal") - ); window.addEventListener("resize", this.updateHeight); // height must be calculated in case of initially open details if (settings.energyflowDetails) { - setTimeout(this.toggleDetails, 50); + setTimeout(this.toggleDetails, 100); } }, unmounted() { @@ -349,10 +335,14 @@ export default { this.detailsCompleteHeight = this.$refs.detailsInner.offsetHeight; }, openGridSettingsModal() { - this.gridSettingsModal.show(); + const modal = Modal.getOrCreateInstance(document.getElementById("gridSettingsModal")); + modal.show(); }, openBatterySettingsModal() { - this.batterySettingsModal.show(); + const modal = Modal.getOrCreateInstance( + document.getElementById("batterySettingsModal") + ); + modal.show(); }, }, }; diff --git a/assets/js/components/Energyflow/EnergyflowEntry.vue b/assets/js/components/Energyflow/EnergyflowEntry.vue index 33b3bff3d..0a055aef4 100644 --- a/assets/js/components/Energyflow/EnergyflowEntry.vue +++ b/assets/js/components/Energyflow/EnergyflowEntry.vue @@ -13,6 +13,7 @@ ref="details" class="fw-normal" :class="{ 'text-decoration-underline': detailsClickable }" + data-testid="energyflow-entry-details" data-bs-toggle="tooltip" :tabindex="detailsClickable ? 0 : undefined" @click="detailsClicked" diff --git a/assets/js/components/TopNavigation.vue b/assets/js/components/TopNavigation.vue index 4783087bf..9ba99d7be 100644 --- a/assets/js/components/TopNavigation.vue +++ b/assets/js/components/TopNavigation.vue @@ -26,18 +26,32 @@ {{ $t("header.sessions") }} +
  • +
  • +
  • + +
  • +
  • +
  • - Configuration 🧪 + + Device Configuration 🧪 +
  • +
  • @@ -85,15 +97,13 @@ import "@h2d2/shopicons/es/regular/gift"; import "@h2d2/shopicons/es/regular/moonstars"; import "@h2d2/shopicons/es/regular/menu"; import "@h2d2/shopicons/es/regular/newtab"; -import GlobalSettingsModal from "./GlobalSettingsModal.vue"; -import HelpModal from "./HelpModal.vue"; import collector from "../mixins/collector"; +import gridModalAvailable from "../utils/gridModalAvailable"; import baseAPI from "../baseapi"; export default { name: "TopNavigation", - components: { GlobalSettingsModal, HelpModal }, mixins: [collector], props: { vehicleLogins: { @@ -104,11 +114,10 @@ export default { }, sponsor: String, sponsorTokenExpires: Number, + batteryConfigured: Boolean, + smartCostType: String, }, computed: { - globalSettingsModalProps: function () { - return this.collectProps(GlobalSettingsModal); - }, logoutCount() { return this.providerLogins.filter((login) => !login.loggedIn).length; }, @@ -126,6 +135,12 @@ export default { showBadge() { return this.loginRequired || this.sponsorTokenExpires; }, + batteryModalAvailable() { + return this.batteryConfigured; + }, + gridModalAvailable: function () { + return gridModalAvailable(this.smartCostType); + }, }, mounted() { const $el = document.getElementById("topNavigatonDropdown"); @@ -155,6 +170,16 @@ export default { const modal = Modal.getOrCreateInstance(document.getElementById("helpModal")); modal.show(); }, + openBatterySettingsModal() { + const modal = Modal.getOrCreateInstance( + document.getElementById("batterySettingsModal") + ); + modal.show(); + }, + openGridSettingsModal() { + const modal = Modal.getOrCreateInstance(document.getElementById("gridSettingsModal")); + modal.show(); + }, }, }; diff --git a/assets/js/mixins/collector.js b/assets/js/mixins/collector.js index b84b893d0..1a3b1bef5 100644 --- a/assets/js/mixins/collector.js +++ b/assets/js/mixins/collector.js @@ -1,9 +1,14 @@ export default { methods: { // collect all target component properties from current instance - collectProps: function (component) { + collectProps: function (component, state) { let data = {}; for (var p in component.props) { + // check in optional state + if (state && p in state) { + data[p] = state[p]; + } + // check in current instance if (p in this) { data[p] = this[p]; } diff --git a/assets/js/utils/gridModalAvailable.js b/assets/js/utils/gridModalAvailable.js new file mode 100644 index 000000000..a3d02435e --- /dev/null +++ b/assets/js/utils/gridModalAvailable.js @@ -0,0 +1,5 @@ +import { CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE } from "../units"; + +export default function (smartCostType) { + return [CO2_TYPE, PRICE_DYNAMIC_TYPE, PRICE_FORECAST_TYPE].includes(smartCostType); +} diff --git a/assets/js/views/App.vue b/assets/js/views/App.vue index b99c7bc55..1074f6b0f 100644 --- a/assets/js/views/App.vue +++ b/assets/js/views/App.vue @@ -1,14 +1,27 @@