Energyflow UI: expand solar/battery (#20389)
This commit is contained in:
parent
667276b450
commit
dc57999c95
5 changed files with 161 additions and 64 deletions
|
|
@ -82,7 +82,6 @@
|
|||
:name="$t('main.energyflow.pvProduction')"
|
||||
icon="sun"
|
||||
:power="pvProduction"
|
||||
:powerTooltip="pvTooltip"
|
||||
:details="solarForecastRemainingToday"
|
||||
:detailsFmt="forecastFmt"
|
||||
:detailsTooltip="solarForecastTooltip"
|
||||
|
|
@ -90,15 +89,27 @@
|
|||
:detailsIcon="solarForecastIcon"
|
||||
:detailsClickable="solarForecastExists"
|
||||
:powerUnit="powerUnit"
|
||||
:expanded="pvExpanded"
|
||||
data-testid="energyflow-entry-production"
|
||||
@details-clicked="openForecastModal"
|
||||
/>
|
||||
@toggle="togglePv"
|
||||
>
|
||||
<template v-if="pv.length > 1" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(p, index) in pv"
|
||||
:key="index"
|
||||
:name="p.title || genericPvTitle(index)"
|
||||
:power="p.power"
|
||||
:powerUnit="powerUnit"
|
||||
:data-testid="`energyflow-entry-production-${index}`"
|
||||
/>
|
||||
</template>
|
||||
</EnergyflowEntry>
|
||||
<EnergyflowEntry
|
||||
v-if="batteryConfigured"
|
||||
:name="batteryDischargeLabel"
|
||||
icon="battery"
|
||||
:power="batteryDischarge"
|
||||
:powerTooltip="batteryDischargeTooltip"
|
||||
:powerUnit="powerUnit"
|
||||
:iconProps="{
|
||||
hold: batteryHold,
|
||||
|
|
@ -107,13 +118,26 @@
|
|||
}"
|
||||
:details="batterySoc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:expanded="batteryExpanded"
|
||||
detailsClickable
|
||||
data-testid="energyflow-entry-batterydischarge"
|
||||
@details-clicked="openBatterySettingsModal"
|
||||
@toggle="toggleBattery"
|
||||
>
|
||||
<template v-if="batteryGridChargeLimitSet" #subline>
|
||||
<div class="d-none d-md-block"> </div>
|
||||
</template>
|
||||
<template v-if="battery.length > 1" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(b, index) in battery"
|
||||
:key="index"
|
||||
:name="b.title || genericBatteryTitle(index)"
|
||||
:details="b.soc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:power="dischargePower(b.power)"
|
||||
:powerUnit="powerUnit"
|
||||
/>
|
||||
</template>
|
||||
</EnergyflowEntry>
|
||||
<EnergyflowEntry
|
||||
:name="$t('main.energyflow.gridImport')"
|
||||
|
|
@ -174,7 +198,6 @@
|
|||
:name="batteryChargeLabel"
|
||||
icon="battery"
|
||||
:power="batteryCharge"
|
||||
:powerTooltip="batteryChargeTooltip"
|
||||
:powerUnit="powerUnit"
|
||||
:iconProps="{
|
||||
hold: batteryHold,
|
||||
|
|
@ -183,8 +206,10 @@
|
|||
}"
|
||||
:details="batterySoc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:expanded="batteryExpanded"
|
||||
detailsClickable
|
||||
@details-clicked="openBatterySettingsModal"
|
||||
@toggle="toggleBattery"
|
||||
>
|
||||
<template v-if="batteryGridChargeLimitSet" #subline>
|
||||
<button
|
||||
|
|
@ -207,6 +232,17 @@
|
|||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-if="battery.length > 1" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(b, index) in battery"
|
||||
:key="index"
|
||||
:name="b.title || genericBatteryTitle(index)"
|
||||
:details="b.soc"
|
||||
:detailsFmt="batteryFmt"
|
||||
:power="chargePower(b.power)"
|
||||
:powerUnit="powerUnit"
|
||||
/>
|
||||
</template>
|
||||
</EnergyflowEntry>
|
||||
<EnergyflowEntry
|
||||
v-if="pvPossible"
|
||||
|
|
@ -348,21 +384,6 @@ export default {
|
|||
detailsHeight() {
|
||||
return this.detailsOpen ? this.detailsCompleteHeight + "px" : 0;
|
||||
},
|
||||
pvTooltip() {
|
||||
if (!Array.isArray(this.pv) || this.pv.length <= 1) {
|
||||
return;
|
||||
}
|
||||
return this.pv.map(
|
||||
({ power, title }) =>
|
||||
`${title ? `${title}: ` : ""}${this.fmtW(power, this.powerUnit)}`
|
||||
);
|
||||
},
|
||||
batteryDischargeTooltip() {
|
||||
return this.batteryTooltip(true);
|
||||
},
|
||||
batteryChargeTooltip() {
|
||||
return this.batteryTooltip(false);
|
||||
},
|
||||
batteryFmt() {
|
||||
return (soc) => this.fmtPercentage(soc, 0);
|
||||
},
|
||||
|
|
@ -412,6 +433,12 @@ export default {
|
|||
}
|
||||
return [];
|
||||
},
|
||||
pvExpanded() {
|
||||
return settings.energyflowPv;
|
||||
},
|
||||
batteryExpanded() {
|
||||
return settings.energyflowBattery;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
pvConfigured() {
|
||||
|
|
@ -494,17 +521,19 @@ export default {
|
|||
chargePower(power) {
|
||||
return Math.abs(Math.min(0, power) * -1);
|
||||
},
|
||||
batteryTooltip(discharge = false) {
|
||||
if (!Array.isArray(this.battery) || this.battery.length <= 1) {
|
||||
return;
|
||||
}
|
||||
return this.battery.map(({ power, soc, title }) => {
|
||||
const value = discharge ? this.dischargePower(power) : this.chargePower(power);
|
||||
|
||||
const powerFmt = this.fmtW(value, this.powerUnit);
|
||||
const socFmt = this.fmtPercentage(soc, 0);
|
||||
return `${title ? `${title}: ` : ""}${powerFmt} (${socFmt})`;
|
||||
});
|
||||
toggleBattery() {
|
||||
settings.energyflowBattery = !settings.energyflowBattery;
|
||||
this.$nextTick(this.updateHeight);
|
||||
},
|
||||
togglePv() {
|
||||
settings.energyflowPv = !settings.energyflowPv;
|
||||
this.$nextTick(this.updateHeight);
|
||||
},
|
||||
genericBatteryTitle(index) {
|
||||
return `${this.$t("config.devices.batteryStorage")} #${index + 1}`;
|
||||
},
|
||||
genericPvTitle(index) {
|
||||
return `${this.$t("config.devices.solarSystem")} #${index + 1}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,39 +1,63 @@
|
|||
<template>
|
||||
<div class="mb-2 entry" :class="{ 'evcc-gray': !active }">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="d-flex flex-nowrap">
|
||||
<BatteryIcon v-if="isBattery" v-bind="iconProps" />
|
||||
<VehicleIcon v-else-if="isVehicle" v-bind="iconProps" />
|
||||
<component :is="`shopicon-regular-${icon}`" v-else></component>
|
||||
</span>
|
||||
<div class="d-block text-nowrap flex-grow-1 ms-3 text-truncate">
|
||||
{{ name }}
|
||||
<div>
|
||||
<div class="mb-2 entry" :class="{ 'evcc-gray': !active }">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="d-flex flex-nowrap">
|
||||
<BatteryIcon v-if="isBattery" v-bind="iconProps" />
|
||||
<VehicleIcon v-else-if="isVehicle" v-bind="iconProps" />
|
||||
<div v-else-if="!icon" class="icon-placeholder"></div>
|
||||
<component :is="`shopicon-regular-${icon}`" v-else></component>
|
||||
</span>
|
||||
<div class="d-block flex-grow-1 ms-3 text-truncate">
|
||||
<span v-if="!$slots.expanded">{{ name }}</span>
|
||||
<button
|
||||
v-else
|
||||
class="btn-neutral d-flex align-items-baseline flex-shrink-1 flex-grow-1"
|
||||
style="max-width: 100%"
|
||||
@click="toggle"
|
||||
>
|
||||
<div class="text-truncate flex-shrink-1 flex-grow-0">
|
||||
{{ name }}
|
||||
</div>
|
||||
<shopicon-regular-arrowdropdown
|
||||
class="expand-icon flex-shrink-0 flex-grow-0"
|
||||
:class="{ 'expand-icon--expanded': expanded }"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-end text-nowrap ps-1 fw-bold d-flex">
|
||||
<div
|
||||
ref="details"
|
||||
class="fw-normal"
|
||||
:class="{
|
||||
'text-decoration-underline': detailsClickable,
|
||||
'evcc-gray': detailsInactive,
|
||||
}"
|
||||
data-testid="energyflow-entry-details"
|
||||
data-bs-toggle="tooltip"
|
||||
:tabindex="detailsClickable ? 0 : undefined"
|
||||
@click="detailsClicked"
|
||||
>
|
||||
<ForecastIcon
|
||||
v-if="detailsIcon === 'forecast'"
|
||||
class="ms-2 me-1 d-inline-block"
|
||||
/>
|
||||
<AnimatedNumber v-if="!isNaN(details)" :to="details" :format="detailsFmt" />
|
||||
</div>
|
||||
<div ref="power" class="power" data-bs-toggle="tooltip" @click="powerClicked">
|
||||
<AnimatedNumber ref="powerNumber" :to="power" :format="kw" />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-end text-nowrap ps-1 fw-bold d-flex">
|
||||
<div
|
||||
ref="details"
|
||||
class="fw-normal"
|
||||
:class="{
|
||||
'text-decoration-underline': detailsClickable,
|
||||
'evcc-gray': detailsInactive,
|
||||
}"
|
||||
data-testid="energyflow-entry-details"
|
||||
data-bs-toggle="tooltip"
|
||||
:tabindex="detailsClickable ? 0 : undefined"
|
||||
@click="detailsClicked"
|
||||
>
|
||||
<ForecastIcon
|
||||
v-if="detailsIcon === 'forecast'"
|
||||
class="ms-2 me-1 d-inline-block"
|
||||
/>
|
||||
<AnimatedNumber v-if="!isNaN(details)" :to="details" :format="detailsFmt" />
|
||||
</div>
|
||||
<div ref="power" class="power" data-bs-toggle="tooltip" @click="powerClicked">
|
||||
<AnimatedNumber ref="powerNumber" :to="power" :format="kw" />
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="$slots.subline" class="ms-4 ps-3">
|
||||
<div
|
||||
v-if="$slots.expanded"
|
||||
class="expandable ms-2"
|
||||
:class="{ 'expandable--open': expanded }"
|
||||
>
|
||||
<slot name="expanded" />
|
||||
</div>
|
||||
<div v-if="$slots.subline" class="ms-4 ps-3 mb-2">
|
||||
<slot name="subline" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -43,6 +67,7 @@
|
|||
import "@h2d2/shopicons/es/regular/powersupply";
|
||||
import "@h2d2/shopicons/es/regular/sun";
|
||||
import "@h2d2/shopicons/es/regular/home";
|
||||
import "@h2d2/shopicons/es/regular/arrowdropdown";
|
||||
import Tooltip from "bootstrap/js/dist/tooltip";
|
||||
import BatteryIcon from "./BatteryIcon.vue";
|
||||
import formatter from "../../mixins/formatter";
|
||||
|
|
@ -67,8 +92,9 @@ export default {
|
|||
detailsTooltip: { type: Array },
|
||||
detailsClickable: { type: Boolean },
|
||||
detailsInactive: { type: Boolean },
|
||||
expanded: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ["details-clicked"],
|
||||
emits: ["details-clicked", "toggle"],
|
||||
data() {
|
||||
return { powerTooltipInstance: null, detailsTooltipInstance: null };
|
||||
},
|
||||
|
|
@ -153,6 +179,10 @@ export default {
|
|||
// hide tooltip, chrome needs a timeout
|
||||
setTimeout(() => this.detailsTooltipInstance?.hide(), 10);
|
||||
},
|
||||
toggle($event) {
|
||||
$event.stopPropagation();
|
||||
this.$emit("toggle");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -163,4 +193,25 @@ export default {
|
|||
.power {
|
||||
min-width: 75px;
|
||||
}
|
||||
.icon-placeholder {
|
||||
width: 24px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
.expand-icon {
|
||||
transition: transform var(--evcc-transition-medium) ease;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.expand-icon--expanded {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
.expandable {
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
transition: opacity var(--evcc-transition-medium) ease-in;
|
||||
}
|
||||
.expandable--open {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ const SETTINGS_THEME = "settings_theme";
|
|||
const SETTINGS_UNIT = "settings_unit";
|
||||
const SETTINGS_HIDDEN_FEATURES = "settings_hidden_features";
|
||||
const SETTINGS_ENERGYFLOW_DETAILS = "settings_energyflow_details";
|
||||
const SETTINGS_ENERGYFLOW_PV = "settings_energyflow_pv";
|
||||
const SETTINGS_ENERGYFLOW_BATTERY = "settings_energyflow_battery";
|
||||
const SESSION_INFO = "session_info";
|
||||
const SESSION_COLUMNS = "session_columns";
|
||||
const SAVINGS_PERIOD = "savings_period";
|
||||
|
|
@ -58,6 +60,8 @@ const settings = reactive({
|
|||
unit: read(SETTINGS_UNIT),
|
||||
hiddenFeatures: readBool(SETTINGS_HIDDEN_FEATURES),
|
||||
energyflowDetails: readBool(SETTINGS_ENERGYFLOW_DETAILS),
|
||||
energyflowPv: readBool(SETTINGS_ENERGYFLOW_PV),
|
||||
energyflowBattery: readBool(SETTINGS_ENERGYFLOW_BATTERY),
|
||||
sessionInfo: readArray(SESSION_INFO),
|
||||
sessionColumns: readArray(SESSION_COLUMNS),
|
||||
savingsPeriod: read(SAVINGS_PERIOD),
|
||||
|
|
@ -72,6 +76,8 @@ watch(() => settings.theme, save(SETTINGS_THEME));
|
|||
watch(() => settings.unit, save(SETTINGS_UNIT));
|
||||
watch(() => settings.hiddenFeatures, saveBool(SETTINGS_HIDDEN_FEATURES));
|
||||
watch(() => settings.energyflowDetails, saveBool(SETTINGS_ENERGYFLOW_DETAILS));
|
||||
watch(() => settings.energyflowPv, saveBool(SETTINGS_ENERGYFLOW_PV));
|
||||
watch(() => settings.energyflowBattery, saveBool(SETTINGS_ENERGYFLOW_BATTERY));
|
||||
watch(() => settings.sessionInfo, saveArray(SESSION_INFO));
|
||||
watch(() => settings.sessionColumns, saveArray(SESSION_COLUMNS));
|
||||
watch(() => settings.savingsPeriod, save(SAVINGS_PERIOD));
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ A = "A (nicht verbunden)"
|
|||
B = "B (verbunden)"
|
||||
C = "C (lädt)"
|
||||
|
||||
[config.devices]
|
||||
auxMeter = "Smarter Verbraucher"
|
||||
batteryStorage = "Batterie"
|
||||
solarSystem = "PV-Anlage"
|
||||
|
||||
[config.eebus]
|
||||
description = "Grundkonfiguration für die Kommunikation mit anderen EEBus-Geräten."
|
||||
title = "EEBus"
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ A = "A (not connected)"
|
|||
B = "B (connected)"
|
||||
C = "C (charging)"
|
||||
|
||||
[config.devices]
|
||||
auxMeter = "Smart consumer"
|
||||
batteryStorage = "Battery storage"
|
||||
solarSystem = "Solar system"
|
||||
|
||||
[config.eebus]
|
||||
description = "Configuration that enables evcc to communicate with other EEBus devices."
|
||||
title = "EEBus"
|
||||
|
|
@ -591,6 +596,7 @@ gridImport = "Grid use"
|
|||
homePower = "Consumption"
|
||||
loadpoints = "Charger| Charger | {count} chargers"
|
||||
noEnergy = "No meter data"
|
||||
pv = "Solar system"
|
||||
pvExport = "Grid export"
|
||||
pvProduction = "Production"
|
||||
selfConsumption = "Self-consumption"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue