UI: move vehicle settings from charging plan modal to own modal (#31775)
This commit is contained in:
parent
f673c6d0c8
commit
2a86cc44b1
65 changed files with 690 additions and 796 deletions
|
|
@ -745,6 +745,14 @@ html.app .modal-dialog {
|
|||
outline: revert;
|
||||
}
|
||||
|
||||
/* selects match :focus-visible on click, CustomSelect sets .focus-ring-visible instead */
|
||||
.keyboard-focus-ring:focus-visible,
|
||||
.keyboard-focus-ring.focus-ring-visible {
|
||||
outline: var(--bs-focus-ring-width) solid var(--bs-focus-ring-color);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--bs-border-radius);
|
||||
}
|
||||
|
||||
.round-box {
|
||||
border-radius: 1rem;
|
||||
color: var(--evcc-default-text);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
<MoreItem
|
||||
:active="moreActive"
|
||||
:vehicles="vehicles"
|
||||
:auth-providers="authProviders"
|
||||
:sponsor="sponsor"
|
||||
:fatal="fatal"
|
||||
|
|
@ -50,7 +51,15 @@ import BatteryIcon from "../Energyflow/BatteryIcon.vue";
|
|||
import Item from "./Item.vue";
|
||||
import MoreItem from "./MoreItem.vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { FatalError, Forecast, Sponsor, EvOpt, AuthProviders, Battery } from "@/types/evcc";
|
||||
import type {
|
||||
FatalError,
|
||||
Forecast,
|
||||
Sponsor,
|
||||
EvOpt,
|
||||
AuthProviders,
|
||||
Battery,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BottomTabBar",
|
||||
|
|
@ -66,6 +75,7 @@ export default defineComponent({
|
|||
batteryGridChargeActive: Boolean,
|
||||
batteryMode: { type: String as PropType<string> },
|
||||
forecast: { type: Object as PropType<Forecast> },
|
||||
vehicles: { type: Object as PropType<Record<string, Vehicle>>, default: () => ({}) },
|
||||
authProviders: { type: Object as PropType<AuthProviders>, default: () => ({}) },
|
||||
sponsor: { type: Object as PropType<Sponsor>, default: () => ({}) },
|
||||
fatal: { type: Array as PropType<FatalError[]>, default: () => [] },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<template #menu>
|
||||
<MoreMenu
|
||||
:open="open"
|
||||
:vehicles="vehicles"
|
||||
:auth-providers="authProviders"
|
||||
:sponsor="sponsor"
|
||||
:fatal="fatal"
|
||||
|
|
@ -34,13 +35,14 @@ import MoreMenu from "./MoreMenu.vue";
|
|||
import { isUserConfigError } from "@/utils/fatal";
|
||||
import { isNewVersionAvailable, isNewVersionUnacknowledged } from "@/utils/version";
|
||||
import settings from "@/settings";
|
||||
import type { FatalError, Sponsor, EvOpt, AuthProviders } from "@/types/evcc";
|
||||
import type { FatalError, Sponsor, EvOpt, AuthProviders, Vehicle } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MoreItem",
|
||||
components: { Item, MoreIcon, MoreMenu },
|
||||
props: {
|
||||
active: Boolean,
|
||||
vehicles: { type: Object as PropType<Record<string, Vehicle>>, default: () => ({}) },
|
||||
authProviders: { type: Object as PropType<AuthProviders>, default: () => ({}) },
|
||||
sponsor: { type: Object as PropType<Sponsor>, default: () => ({}) },
|
||||
fatal: { type: Array as PropType<FatalError[]>, default: () => [] },
|
||||
|
|
|
|||
|
|
@ -57,6 +57,14 @@
|
|||
<button type="button" class="dropdown-item" @click="openSettingsModal">
|
||||
{{ $t("settings.title") }}
|
||||
</button>
|
||||
<button
|
||||
v-if="hasVehicles"
|
||||
type="button"
|
||||
class="dropdown-item"
|
||||
@click="openVehicleSettingsModal"
|
||||
>
|
||||
{{ $t("main.vehicleSettings.menu") }}
|
||||
</button>
|
||||
<router-link class="dropdown-item" to="/config" active-class="active">
|
||||
<span v-if="showConfigBadge" class="circle-badge me-1" :class="badgeClass"></span>
|
||||
{{ $t("config.main.title") }}
|
||||
|
|
@ -88,12 +96,13 @@ import {
|
|||
import settings from "@/settings";
|
||||
import { isUserConfigError } from "@/utils/fatal";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { FatalError, Sponsor, EvOpt, AuthProviders } from "@/types/evcc";
|
||||
import type { FatalError, Sponsor, EvOpt, AuthProviders, Vehicle } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MoreMenu",
|
||||
props: {
|
||||
open: { type: Boolean, default: false },
|
||||
vehicles: { type: Object as PropType<Record<string, Vehicle>>, default: () => ({}) },
|
||||
authProviders: { type: Object as PropType<AuthProviders>, default: () => ({}) },
|
||||
sponsor: { type: Object as PropType<Sponsor>, default: () => ({}) },
|
||||
fatal: { type: Array as PropType<FatalError[]>, default: () => [] },
|
||||
|
|
@ -155,6 +164,9 @@ export default defineComponent({
|
|||
showLogout() {
|
||||
return !this.authDisabled && isLoggedIn();
|
||||
},
|
||||
hasVehicles() {
|
||||
return Object.keys(this.vehicles).length > 0;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onClickOutside = (e: MouseEvent) => {
|
||||
|
|
@ -191,6 +203,12 @@ export default defineComponent({
|
|||
);
|
||||
modal.show();
|
||||
},
|
||||
openVehicleSettingsModal() {
|
||||
const modal = Modal.getOrCreateInstance(
|
||||
document.getElementById("vehicleSettingsModal") as HTMLElement
|
||||
);
|
||||
modal.show();
|
||||
},
|
||||
openNativeSettings() {
|
||||
sendToApp({ type: "settings" });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
<template>
|
||||
<div class="mt-4 container">
|
||||
<div class="row mb-4">
|
||||
<label :for="formId('mode')" class="col-12 col-md-4 col-lg-3 col-form-label">
|
||||
{{ $t("config.loadpoint.defaultModeLabel") }}
|
||||
</label>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<select
|
||||
:id="formId('mode')"
|
||||
v-model="selectedMode"
|
||||
class="form-select"
|
||||
@change="changeMode"
|
||||
>
|
||||
<option v-for="opt in modeOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">{{ modeHelp }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="socSettingsVisible">
|
||||
<div class="row mb-4">
|
||||
<label :for="formId('minsoc')" class="col-12 col-md-4 col-lg-3 col-form-label">
|
||||
{{ $t("main.loadpointSettings.minSoc.label") }}
|
||||
</label>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<select
|
||||
:id="formId('minsoc')"
|
||||
v-model.number="selectedMinSoc"
|
||||
class="form-select"
|
||||
@change="changeMinSoc"
|
||||
>
|
||||
<option v-for="soc in minSocOptions" :key="soc.value" :value="soc.value">
|
||||
{{ soc.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">
|
||||
{{
|
||||
$t("main.loadpointSettings.minSoc.description", [
|
||||
selectedMinSoc ? fmtPercentage(selectedMinSoc) : "x",
|
||||
])
|
||||
}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label :for="formId('limitsoc')" class="col-12 col-md-4 col-lg-3 col-form-label">
|
||||
{{ $t("main.loadpointSettings.limitSoc.label") }}
|
||||
</label>
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<select
|
||||
:id="formId('limitsoc')"
|
||||
v-model.number="selectedLimitSoc"
|
||||
class="form-select"
|
||||
@change="changeLimitSoc"
|
||||
>
|
||||
<option v-for="soc in limitSocOptions" :key="soc.value" :value="soc.value">
|
||||
{{ soc.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t("main.loadpointSettings.limitSoc.description") }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { distanceUnit } from "@/units";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { CHARGE_MODE, type SelectOption } from "@/types/evcc";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
const { OFF, PV, MINPV, NOW } = CHARGE_MODE;
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanArrival",
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
id: [String, Number],
|
||||
minSoc: { type: Number, default: 0 },
|
||||
limitSoc: { type: Number, default: 0 },
|
||||
mode: { type: String, default: "" },
|
||||
vehicleName: String,
|
||||
vehicleNotReachable: Boolean,
|
||||
socBasedCharging: Boolean,
|
||||
rangePerSoc: Number,
|
||||
},
|
||||
emits: ["minsoc-updated", "limitsoc-updated", "mode-updated"],
|
||||
data() {
|
||||
return {
|
||||
selectedMinSoc: this.minSoc,
|
||||
selectedLimitSoc: this.limitSoc,
|
||||
selectedMode: this.mode,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
modeOptions(): SelectOption<string>[] {
|
||||
return [
|
||||
{ value: "", name: this.$t("config.loadpoint.defaultModeKeep") },
|
||||
...[OFF, PV, MINPV, NOW].map((mode) => ({
|
||||
value: mode,
|
||||
name: this.$t(`main.mode.${mode}`),
|
||||
})),
|
||||
];
|
||||
},
|
||||
modeHelp(): string {
|
||||
return this.selectedMode === ""
|
||||
? this.$t("config.loadpoint.defaultModeHelpKeep")
|
||||
: this.$t("config.loadpoint.defaultModeHelp.charging");
|
||||
},
|
||||
minSocOptions(): SelectOption<number>[] {
|
||||
// a list of entries from 0 to 95 with a step of 5
|
||||
return Array.from(Array(20).keys())
|
||||
.map((i) => i * 5)
|
||||
.map(this.socOption);
|
||||
},
|
||||
limitSocOptions(): SelectOption<number>[] {
|
||||
// a list of entries from 0 to 100 with a step of 5
|
||||
return Array.from(Array(21).keys())
|
||||
.map((i) => i * 5)
|
||||
.map(this.socOption);
|
||||
},
|
||||
socSettingsVisible(): boolean {
|
||||
return this.socBasedCharging || this.vehicleNotReachable;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
minSoc(value: number): void {
|
||||
this.selectedMinSoc = value;
|
||||
},
|
||||
limitSoc(value: number): void {
|
||||
this.selectedLimitSoc = value;
|
||||
},
|
||||
mode(value: string): void {
|
||||
this.selectedMode = value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
socOption(soc: number): SelectOption<number> {
|
||||
return {
|
||||
value: soc,
|
||||
name: soc === 0 ? "---" : this.fmtSocOption(soc, this.rangePerSoc, distanceUnit()),
|
||||
};
|
||||
},
|
||||
formId(name: string): string {
|
||||
return `chargingplan_${this.id}_${name}`;
|
||||
},
|
||||
changeMinSoc(): void {
|
||||
this.$emit("minsoc-updated", this.selectedMinSoc);
|
||||
},
|
||||
changeLimitSoc(): void {
|
||||
this.$emit("limitsoc-updated", this.selectedLimitSoc);
|
||||
},
|
||||
changeMode(): void {
|
||||
this.$emit("mode-updated", this.selectedMode);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
>
|
||||
<div class="value m-0 d-block align-items-baseline justify-content-center">
|
||||
<button
|
||||
class="value-button p-0"
|
||||
class="value-button p-0 keyboard-focus-ring"
|
||||
:class="buttonColor"
|
||||
data-testid="charging-plan-button"
|
||||
@click="openModal"
|
||||
|
|
|
|||
|
|
@ -9,72 +9,33 @@
|
|||
@open="modalVisible"
|
||||
@closed="modalInvisible"
|
||||
>
|
||||
<div class="pt-2">
|
||||
<ul v-if="vehicle" class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: departureTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showDepartureTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.departureTab") }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="vehicle" class="nav-item">
|
||||
<a
|
||||
class="nav-link"
|
||||
:class="{ active: arrivalTabActive }"
|
||||
href="#"
|
||||
@click.prevent="showArrivalTab"
|
||||
>
|
||||
{{ $t("main.chargingPlan.arrivalTab") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="isModalVisible">
|
||||
<PlansSettings
|
||||
v-if="departureTabActive"
|
||||
:id="id"
|
||||
:staticPlan="staticPlan"
|
||||
:repeatingPlans="repeatingPlans"
|
||||
:effectiveLimitSoc="loadpoint?.effectiveLimitSoc"
|
||||
:effectivePlanTime="loadpoint?.effectivePlanTime ?? undefined"
|
||||
:effectivePlanSoc="loadpoint?.effectivePlanSoc"
|
||||
:effectivePlanStrategy="loadpoint?.effectivePlanStrategy"
|
||||
:planEnergy="loadpoint?.planEnergy"
|
||||
:limitEnergy="loadpoint?.limitEnergy"
|
||||
:socBasedPlanning="!!loadpoint?.socBasedPlanning"
|
||||
:socPerKwh="loadpoint?.socPerKwh"
|
||||
:rangePerSoc="loadpoint?.rangePerSoc"
|
||||
:smartCostType="smartCostType"
|
||||
:currency="currency"
|
||||
:mode="loadpoint?.mode"
|
||||
:capacity="vehicle?.capacity"
|
||||
:vehicle="vehicle"
|
||||
:vehicleLimitSoc="loadpoint?.vehicleLimitSoc"
|
||||
:planOverrun="loadpoint?.planOverrun"
|
||||
:forecast="forecast"
|
||||
@static-plan-updated="updateStaticPlan"
|
||||
@static-plan-removed="removeStaticPlan"
|
||||
@repeating-plans-updated="updateRepeatingPlans"
|
||||
@plan-strategy-updated="updatePlanStrategy"
|
||||
/>
|
||||
<Arrival
|
||||
v-if="arrivalTabActive"
|
||||
:id="id"
|
||||
:minSoc="vehicle?.minSoc"
|
||||
:limitSoc="vehicle?.limitSoc"
|
||||
:mode="vehicle?.mode"
|
||||
:vehicleName="vehicle?.name"
|
||||
:vehicleNotReachable="loadpoint?.vehicleNotReachable"
|
||||
:socBasedCharging="loadpoint?.socBasedCharging"
|
||||
:rangePerSoc="loadpoint?.rangePerSoc"
|
||||
@minsoc-updated="setMinSoc"
|
||||
@limitsoc-updated="setLimitSoc"
|
||||
@mode-updated="setMode"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="isModalVisible">
|
||||
<PlansSettings
|
||||
:id="id"
|
||||
:staticPlan="staticPlan"
|
||||
:repeatingPlans="repeatingPlans"
|
||||
:effectiveLimitSoc="loadpoint?.effectiveLimitSoc"
|
||||
:effectivePlanTime="loadpoint?.effectivePlanTime ?? undefined"
|
||||
:effectivePlanSoc="loadpoint?.effectivePlanSoc"
|
||||
:effectivePlanStrategy="loadpoint?.effectivePlanStrategy"
|
||||
:planEnergy="loadpoint?.planEnergy"
|
||||
:limitEnergy="loadpoint?.limitEnergy"
|
||||
:socBasedPlanning="!!loadpoint?.socBasedPlanning"
|
||||
:socPerKwh="loadpoint?.socPerKwh"
|
||||
:rangePerSoc="loadpoint?.rangePerSoc"
|
||||
:smartCostType="smartCostType"
|
||||
:currency="currency"
|
||||
:mode="loadpoint?.mode"
|
||||
:capacity="vehicle?.capacity"
|
||||
:vehicle="vehicle"
|
||||
:vehicleLimitSoc="loadpoint?.vehicleLimitSoc"
|
||||
:planOverrun="loadpoint?.planOverrun"
|
||||
:forecast="forecast"
|
||||
@static-plan-updated="updateStaticPlan"
|
||||
@static-plan-removed="removeStaticPlan"
|
||||
@repeating-plans-updated="updateRepeatingPlans"
|
||||
@plan-strategy-updated="updatePlanStrategy"
|
||||
/>
|
||||
</div>
|
||||
</GenericModal>
|
||||
</Teleport>
|
||||
|
|
@ -84,7 +45,6 @@
|
|||
import { defineComponent, type PropType } from "vue";
|
||||
import GenericModal from "../Helper/GenericModal.vue";
|
||||
import PlansSettings from "./PlansSettings.vue";
|
||||
import Arrival from "./Arrival.vue";
|
||||
import api from "@/api";
|
||||
import type {
|
||||
PlanStrategy,
|
||||
|
|
@ -93,21 +53,13 @@ import type {
|
|||
StaticPlan,
|
||||
StaticSocPlan,
|
||||
} from "./types";
|
||||
import type {
|
||||
CHARGE_MODE,
|
||||
CURRENCY,
|
||||
Forecast,
|
||||
SMART_COST_TYPE,
|
||||
UiLoadpoint,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
import type { CURRENCY, Forecast, SMART_COST_TYPE, UiLoadpoint, Vehicle } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanModal",
|
||||
components: {
|
||||
GenericModal,
|
||||
PlansSettings,
|
||||
Arrival,
|
||||
},
|
||||
props: {
|
||||
loadpoints: { type: Array as PropType<UiLoadpoint[]>, default: () => [] },
|
||||
|
|
@ -119,7 +71,6 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
isModalVisible: false,
|
||||
activeTab: "departure",
|
||||
id: undefined as string | number | undefined,
|
||||
};
|
||||
},
|
||||
|
|
@ -151,18 +102,11 @@ export default defineComponent({
|
|||
},
|
||||
modalTitle(): string {
|
||||
const baseTitle = this.$t("main.chargingPlan.modalTitle");
|
||||
const withVehicle = this.arrivalTabActive || this.loadpoint?.socBasedPlanning;
|
||||
if (withVehicle && this.vehicle) {
|
||||
if (this.loadpoint?.socBasedPlanning && this.vehicle) {
|
||||
return `${baseTitle}: ${this.vehicle.title}`;
|
||||
}
|
||||
return baseTitle;
|
||||
},
|
||||
departureTabActive(): boolean {
|
||||
return this.activeTab === "departure" || !this.vehicle;
|
||||
},
|
||||
arrivalTabActive(): boolean {
|
||||
return this.activeTab === "arrival" && !!this.vehicle;
|
||||
},
|
||||
apiVehicle(): string {
|
||||
return `vehicles/${this.vehicle?.name}/`;
|
||||
},
|
||||
|
|
@ -192,19 +136,6 @@ export default defineComponent({
|
|||
modalInvisible(): void {
|
||||
this.isModalVisible = false;
|
||||
},
|
||||
setMinSoc(soc: number): void {
|
||||
api.post(`${this.apiVehicle}minsoc/${soc}`);
|
||||
},
|
||||
setLimitSoc(soc: number): void {
|
||||
api.post(`${this.apiVehicle}limitsoc/${soc}`);
|
||||
},
|
||||
setMode(mode: CHARGE_MODE | ""): void {
|
||||
if (mode === "") {
|
||||
api.delete(`${this.apiVehicle}mode`);
|
||||
} else {
|
||||
api.post(`${this.apiVehicle}mode/${mode}`);
|
||||
}
|
||||
},
|
||||
updateStaticPlan(plan: StaticPlan): void {
|
||||
const timeISO = plan.time.toISOString();
|
||||
if (this.loadpoint?.socBasedPlanning) {
|
||||
|
|
@ -232,12 +163,6 @@ export default defineComponent({
|
|||
api.post(`${this.apiLoadpoint}plan/strategy`, strategy);
|
||||
}
|
||||
},
|
||||
showDepartureTab(): void {
|
||||
this.activeTab = "departure";
|
||||
},
|
||||
showArrivalTab(): void {
|
||||
this.activeTab = "arrival";
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="mt-4">
|
||||
<div>
|
||||
<div class="form-group d-lg-flex align-items-baseline justify-content-between">
|
||||
<div class="container px-0">
|
||||
<ChargingPlanStaticSettings
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
<small v-if="expiryLabel(client)" class="text-muted ms-2 me-2 text-end">
|
||||
{{ expiryLabel(client) }}
|
||||
</small>
|
||||
<span v-if="isActive(client)" class="badge bg-success ms-2 me-2">
|
||||
<Badge v-if="isActive(client)" class="ms-2 me-2">
|
||||
{{ $t("config.remote.active") }}
|
||||
</span>
|
||||
</Badge>
|
||||
<small
|
||||
v-else-if="activityLabel(client)"
|
||||
class="text-muted ms-2 me-2 text-end d-block"
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import "@h2d2/shopicons/es/regular/trash";
|
||||
import Badge from "../../Helper/Badge.vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import minuteTicker from "@/mixins/minuteTicker";
|
||||
import type { RemoteClient } from "@/types/evcc";
|
||||
|
|
@ -59,6 +60,7 @@ import { isRemoteClientActive } from "@/utils/remote";
|
|||
|
||||
export default defineComponent({
|
||||
name: "RemoteClientList",
|
||||
components: { Badge },
|
||||
mixins: [formatter, minuteTicker],
|
||||
props: {
|
||||
clients: { type: Array as PropType<RemoteClient[]>, required: true },
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
data-testid="energyflow"
|
||||
@click="toggleDetails"
|
||||
>
|
||||
<div class="row">
|
||||
<div
|
||||
class="row keyboard-focus-ring"
|
||||
:role="detailsAlwaysOpen ? undefined : 'button'"
|
||||
:tabindex="detailsAlwaysOpen ? undefined : 0"
|
||||
:aria-expanded="detailsAlwaysOpen ? undefined : detailsOpen"
|
||||
:aria-label="detailsAlwaysOpen ? undefined : $t('main.energyflow.toggleDetails')"
|
||||
@keydown.enter.prevent="toggleDetails"
|
||||
@keydown.space.prevent="toggleDetails"
|
||||
>
|
||||
<Visualization
|
||||
class="col-12 mb-3 mb-md-4"
|
||||
:gridImport="gridImport"
|
||||
|
|
@ -33,6 +41,7 @@
|
|||
class="details"
|
||||
:style="{ height: detailsHeight }"
|
||||
:class="{ 'details--ready': ready }"
|
||||
:inert="!detailsOpen && !detailsAlwaysOpen"
|
||||
>
|
||||
<div ref="detailsInner" class="details-inner row">
|
||||
<div class="col-12 d-flex justify-content-between pt-2 mb-4">
|
||||
|
|
|
|||
26
assets/js/components/Helper/Badge.stories.ts
Normal file
26
assets/js/components/Helper/Badge.stories.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Badge from "./Badge.vue";
|
||||
import type { Meta, StoryFn } from "@storybook/vue3";
|
||||
|
||||
const variants = ["success", "muted"] as const;
|
||||
|
||||
export default {
|
||||
title: "Helper/Badge",
|
||||
component: Badge,
|
||||
argTypes: {
|
||||
variant: { control: "select", options: variants },
|
||||
},
|
||||
} as Meta<typeof Badge>;
|
||||
|
||||
const Template: StoryFn<typeof Badge> = (args) => ({
|
||||
components: { Badge },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<Badge v-bind="args">connected to Garage</Badge>',
|
||||
});
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = { variant: "success" };
|
||||
|
||||
export const Muted = Template.bind({});
|
||||
Muted.args = { variant: "muted" };
|
||||
34
assets/js/components/Helper/Badge.vue
Normal file
34
assets/js/components/Helper/Badge.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<span class="badge rounded-pill" :class="`badge--${variant}`">
|
||||
<slot />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
|
||||
type Variant = "success" | "muted";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Badge",
|
||||
props: {
|
||||
variant: { type: String as PropType<Variant>, default: "success" },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.badge {
|
||||
--bs-badge-font-size: 0.75rem;
|
||||
--bs-badge-padding-y: 0.25em;
|
||||
font-weight: 700;
|
||||
}
|
||||
.badge--success {
|
||||
background: var(--evcc-green);
|
||||
color: var(--evcc-darkest-green);
|
||||
}
|
||||
.badge--muted {
|
||||
background: var(--evcc-gray-10);
|
||||
color: var(--evcc-gray);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
<template>
|
||||
<label
|
||||
class="root position-relative"
|
||||
:class="inline ? 'd-inline-block align-baseline' : 'd-block'"
|
||||
class="root position-relative keyboard-focus-ring"
|
||||
:class="[
|
||||
inline ? 'd-inline-block align-baseline' : 'd-block',
|
||||
{ 'focus-ring-visible': showFocusRing },
|
||||
]"
|
||||
:for="id"
|
||||
role="button"
|
||||
@mousedown="mouseDown = true"
|
||||
>
|
||||
<select :id="id" :value="selected" class="custom-select" tabindex="0" @change="change">
|
||||
<select
|
||||
:id="id"
|
||||
:value="selected"
|
||||
:aria-label="ariaLabel"
|
||||
class="custom-select"
|
||||
tabindex="0"
|
||||
@change="change"
|
||||
@focus="handleFocus"
|
||||
@keydown="showFocusRing = true"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<option
|
||||
v-for="{ name, value, count, disabled } in options"
|
||||
:key="value"
|
||||
|
|
@ -30,8 +44,16 @@ export default defineComponent({
|
|||
selected: { type: [String, Number] },
|
||||
id: { type: String },
|
||||
inline: Boolean,
|
||||
ariaLabel: { type: String },
|
||||
},
|
||||
emits: ["change"],
|
||||
data() {
|
||||
return {
|
||||
// selects match :focus-visible even on mouse interaction, track modality manually
|
||||
mouseDown: false,
|
||||
showFocusRing: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
text(name: string, count?: number) {
|
||||
if (count === undefined) {
|
||||
|
|
@ -42,6 +64,14 @@ export default defineComponent({
|
|||
change(event: Event) {
|
||||
this.$emit("change", event);
|
||||
},
|
||||
handleFocus() {
|
||||
this.showFocusRing = !this.mouseDown;
|
||||
this.mouseDown = false;
|
||||
},
|
||||
handleBlur() {
|
||||
this.showFocusRing = false;
|
||||
this.mouseDown = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -55,13 +85,7 @@ export default defineComponent({
|
|||
position: absolute;
|
||||
opacity: 0;
|
||||
-webkit-appearance: menulist-button;
|
||||
}
|
||||
.root {
|
||||
border-radius: var(--bs-border-radius);
|
||||
}
|
||||
.root:focus-within {
|
||||
outline: var(--bs-focus-ring-width) solid var(--bs-focus-ring-color);
|
||||
outline-width: var(--bs-focus-ring-width);
|
||||
outline-offset: var(--bs-focus-ring-width);
|
||||
/* WebKit propagates the select's intrinsic option width as overflow, causing page scroll */
|
||||
contain: paint;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export default defineComponent({
|
|||
methods: {
|
||||
handleShow() {
|
||||
this.$emit("open");
|
||||
this.isModalVisible = true;
|
||||
},
|
||||
handleShown() {
|
||||
this.$emit("opened");
|
||||
|
|
@ -107,14 +108,19 @@ export default defineComponent({
|
|||
}
|
||||
});
|
||||
}
|
||||
this.isModalVisible = true;
|
||||
// bootstrap removes the attribute on show, Vue only rewrites it on state change
|
||||
(this.$refs["modal"] as HTMLElement)?.setAttribute("aria-hidden", "false");
|
||||
},
|
||||
handleHide() {
|
||||
this.$emit("close");
|
||||
this.isModalVisible = false;
|
||||
},
|
||||
handleHidden() {
|
||||
// stale event from a previous close, modal was reopened while still fading out
|
||||
if (this.isModalVisible) {
|
||||
return;
|
||||
}
|
||||
this.$emit("closed");
|
||||
this.isModalVisible = false;
|
||||
if (this.configModalName) {
|
||||
if (onModalHidden(this.configModalName)) {
|
||||
this.$emit("dismiss");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="root">
|
||||
<div class="mb-2 label text-truncate" :class="labelClass" :style="{ color }">
|
||||
<div class="label text-truncate" :class="labelClass" :style="{ color }">
|
||||
<slot name="label">{{ label }}</slot>
|
||||
</div>
|
||||
<slot>
|
||||
|
|
@ -54,6 +54,9 @@ export default defineComponent({
|
|||
text-transform: uppercase;
|
||||
color: var(--evcc-gray);
|
||||
font-size: 14px;
|
||||
/* focus ring clearance, keeps mb-2 spacing */
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem -0.5rem 0;
|
||||
}
|
||||
.value {
|
||||
font-size: 18px;
|
||||
|
|
|
|||
30
assets/js/components/Helper/SettingsFormRow.vue
Normal file
30
assets/js/components/Helper/SettingsFormRow.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div class="mb-3 row">
|
||||
<label :for="id" class="col-sm-4 col-form-label pt-0 pt-sm-2">
|
||||
{{ label }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<small v-if="description" class="d-block text-muted mt-1">
|
||||
{{ description }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SettingsFormRow",
|
||||
props: {
|
||||
id: String,
|
||||
label: String,
|
||||
description: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
@remove-vehicle="removeVehicle"
|
||||
@open-loadpoint-settings="openSettingsModal"
|
||||
@batteryboost-updated="setBatteryBoost"
|
||||
@open-modal="(openArrivalTab) => $emit('open-charging-plan-modal', openArrivalTab)"
|
||||
@open-modal="$emit('open-charging-plan-modal')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@
|
|||
class="h-100"
|
||||
:class="{ 'loadpoint-unselected': !selected(loadpoint.id) }"
|
||||
@click="goTo(loadpoint.id)"
|
||||
@open-charging-plan-modal="
|
||||
(openArrivalTab) => openChargingPlanModal(loadpoint.id, openArrivalTab)
|
||||
"
|
||||
@open-charging-plan-modal="openChargingPlanModal(loadpoint.id)"
|
||||
@open-settings-modal="openSettingsModal(loadpoint.id)"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -224,17 +222,10 @@ export default defineComponent({
|
|||
}
|
||||
}, 1000);
|
||||
},
|
||||
openChargingPlanModal(loadpointId: string, openArrivalTab = false) {
|
||||
openChargingPlanModal(loadpointId: string) {
|
||||
const modal = this.$refs["chargingPlanModal"] as
|
||||
| InstanceType<typeof ChargingPlanModal>
|
||||
| undefined;
|
||||
|
||||
if (openArrivalTab) {
|
||||
modal?.showArrivalTab();
|
||||
} else {
|
||||
modal?.showDepartureTab();
|
||||
}
|
||||
|
||||
modal?.open(loadpointId);
|
||||
},
|
||||
openSettingsModal(loadpointId: string) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<LabelAndValue class="d-block" align="end">
|
||||
<template #label>
|
||||
<CustomSelect
|
||||
inline
|
||||
:selected="selectedKey"
|
||||
:options="selectOptions"
|
||||
data-testid="sessionInfoSelect"
|
||||
|
|
@ -197,8 +198,10 @@ export default defineComponent({
|
|||
|
||||
<style scoped>
|
||||
.sessionInfo * {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
.sessionInfo .value {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary position-relative border-0 p-2 evcc-gray"
|
||||
class="btn btn-sm btn-outline-secondary position-relative border-0 p-2 evcc-gray keyboard-focus-ring"
|
||||
data-testid="loadpoint-settings-button"
|
||||
>
|
||||
<shopicon-regular-adjust size="s"></shopicon-regular-adjust>
|
||||
|
|
|
|||
|
|
@ -89,7 +89,9 @@ import type {
|
|||
FatalError,
|
||||
EvOpt,
|
||||
BATTERY_MODE,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
import vehicleList from "@/utils/vehicleList";
|
||||
import store from "@/store";
|
||||
import type { Grid } from "./types";
|
||||
|
||||
|
|
@ -130,7 +132,7 @@ export default defineComponent({
|
|||
bufferSoc: Number,
|
||||
bufferStartSoc: Number,
|
||||
siteTitle: String,
|
||||
vehicles: Object,
|
||||
vehicles: Object as PropType<Record<string, Vehicle>>,
|
||||
authProviders: { type: Object as PropType<AuthProviders>, default: () => ({}) },
|
||||
currency: { type: String as PropType<CURRENCY> },
|
||||
tariffFeedIn: Number,
|
||||
|
|
@ -178,8 +180,7 @@ export default defineComponent({
|
|||
return this.collectProps(Energyflow);
|
||||
},
|
||||
vehicleList() {
|
||||
const vehicles = this.vehicles || {};
|
||||
return Object.entries(vehicles).map(([name, vehicle]) => ({ name, ...vehicle }));
|
||||
return vehicleList(this.vehicles);
|
||||
},
|
||||
showParkingLot() {
|
||||
// work in progess
|
||||
|
|
|
|||
|
|
@ -6,17 +6,7 @@
|
|||
data-testid="limit-energy"
|
||||
>
|
||||
<h3 class="value m-0">
|
||||
<label class="position-relative" role="button">
|
||||
<select :value="limitEnergy" class="custom-select" @change="change">
|
||||
<option
|
||||
v-for="{ energy, text, disabled } in options"
|
||||
:key="energy"
|
||||
:value="energy"
|
||||
:disabled="disabled"
|
||||
>
|
||||
{{ text }}
|
||||
</option>
|
||||
</select>
|
||||
<CustomSelect inline :options="selectOptions" :selected="limitEnergy" @change="change">
|
||||
<span
|
||||
class="text-decoration-underline"
|
||||
:class="{ 'text-gray fw-normal': !limitEnergy }"
|
||||
|
|
@ -24,7 +14,7 @@
|
|||
>
|
||||
<AnimatedNumber :to="limitEnergy" :format="fmtEnergy" />
|
||||
</span>
|
||||
</label>
|
||||
</CustomSelect>
|
||||
|
||||
<div v-if="estimated" class="extraValue text-nowrap">
|
||||
<AnimatedNumber :to="estimated" :format="fmtSoc" />
|
||||
|
|
@ -36,13 +26,14 @@
|
|||
<script lang="ts">
|
||||
import LabelAndValue from "../Helper/LabelAndValue.vue";
|
||||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import CustomSelect from "../Helper/CustomSelect.vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { estimatedSoc, energyOptions, optionStep, fmtEnergy } from "@/utils/energyOptions.ts";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LimitEnergySelect",
|
||||
components: { LabelAndValue, AnimatedNumber },
|
||||
components: { LabelAndValue, AnimatedNumber, CustomSelect },
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
limitEnergy: { type: Number, default: 0 },
|
||||
|
|
@ -62,6 +53,13 @@ export default defineComponent({
|
|||
this.socPerKwh
|
||||
);
|
||||
},
|
||||
selectOptions() {
|
||||
return this.options.map(({ energy, text, disabled }) => ({
|
||||
value: energy,
|
||||
name: text,
|
||||
disabled,
|
||||
}));
|
||||
},
|
||||
step() {
|
||||
return optionStep(this.capacity || 100);
|
||||
},
|
||||
|
|
@ -96,13 +94,4 @@ export default defineComponent({
|
|||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.custom-select {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
<template>
|
||||
<LabelAndValue class="flex-grow-1" :label="title" align="end" data-testid="limit-soc">
|
||||
<h3 class="value m-0">
|
||||
<label class="position-relative" role="button">
|
||||
<select :value="limitSoc" class="custom-select" @change="change">
|
||||
<option v-for="{ soc, text } in options" :key="soc" :value="soc">
|
||||
{{ text }}
|
||||
</option>
|
||||
</select>
|
||||
<CustomSelect inline :options="options" :selected="limitSoc" @change="change">
|
||||
<span class="text-decoration-underline" data-testid="limit-soc-value">
|
||||
<AnimatedNumber :to="limitSoc" :format="formatSoc" />
|
||||
</span>
|
||||
</label>
|
||||
</CustomSelect>
|
||||
|
||||
<div v-if="estimatedTargetRange" class="extraValue text-nowrap">
|
||||
<AnimatedNumber :to="estimatedTargetRange" :format="formatKm" />
|
||||
|
|
@ -23,12 +18,13 @@
|
|||
import { defineComponent } from "vue";
|
||||
import LabelAndValue from "../Helper/LabelAndValue.vue";
|
||||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import CustomSelect from "../Helper/CustomSelect.vue";
|
||||
import { distanceUnit } from "@/units";
|
||||
import formatter from "@/mixins/formatter";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LimitSocSelect",
|
||||
components: { LabelAndValue, AnimatedNumber },
|
||||
components: { LabelAndValue, AnimatedNumber, CustomSelect },
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
limitSoc: { type: Number, default: 0 },
|
||||
|
|
@ -47,8 +43,8 @@ export default defineComponent({
|
|||
const start = this.rangeActive ? this.minTemp : 20;
|
||||
const end = this.rangeActive ? this.maxTemp : 100;
|
||||
for (let soc = start; soc <= end; soc += this.step) {
|
||||
const text = this.fmtSocOption(soc, this.rangePerSoc, distanceUnit(), this.heating);
|
||||
result.push({ soc, text });
|
||||
const name = this.fmtSocOption(soc, this.rangePerSoc, distanceUnit(), this.heating);
|
||||
result.push({ value: soc, name });
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
|
@ -90,7 +86,6 @@ export default defineComponent({
|
|||
<style scoped>
|
||||
.value {
|
||||
font-size: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.extraValue {
|
||||
margin-top: 0.1rem;
|
||||
|
|
@ -98,14 +93,4 @@ export default defineComponent({
|
|||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.custom-select {
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,13 @@
|
|||
role="button"
|
||||
data-testid="change-vehicle"
|
||||
>
|
||||
<select :id="dropdownId" :value="selected" class="custom-select" @change="change">
|
||||
<select
|
||||
:id="dropdownId"
|
||||
:value="selected"
|
||||
:aria-label="$t('main.vehicle.changeVehicle')"
|
||||
class="custom-select"
|
||||
@change="change"
|
||||
>
|
||||
<option
|
||||
v-for="{ name, value } in vehicleOptions"
|
||||
:key="name"
|
||||
|
|
|
|||
207
assets/js/components/Vehicles/SettingsModal.vue
Normal file
207
assets/js/components/Vehicles/SettingsModal.vue
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<template>
|
||||
<GenericModal
|
||||
id="vehicleSettingsModal"
|
||||
ref="modal"
|
||||
size="lg"
|
||||
:title="$t('main.vehicleSettings.title')"
|
||||
data-testid="vehicle-settings-modal"
|
||||
@open="modalVisible"
|
||||
@closed="modalInvisible"
|
||||
>
|
||||
<div v-if="isModalVisible">
|
||||
<p class="text-gray mt-0 mb-4">
|
||||
{{ $t("main.vehicleSettings.description") }}
|
||||
<a :href="docsUrl" target="_blank" rel="noopener">
|
||||
{{ $t("main.vehicleSettings.learnMore") }}
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
v-for="(vehicle, index) in vehicles"
|
||||
:key="vehicle.name"
|
||||
role="group"
|
||||
:aria-label="vehicle.title"
|
||||
:class="{ 'border-top pt-4': index > 0 }"
|
||||
class="mb-4"
|
||||
>
|
||||
<div class="d-flex flex-wrap align-items-center column-gap-2 row-gap-1 mb-3">
|
||||
<VehicleIcon :name="vehicle.icon" class="flex-shrink-0" />
|
||||
<strong class="text-truncate">{{ vehicle.title }}</strong>
|
||||
<Badge v-if="connectedLoadpoint(vehicle)" class="flex-shrink-0">
|
||||
{{
|
||||
$t("main.vehicleSettings.connectedTo", [
|
||||
connectedLoadpoint(vehicle)?.title ||
|
||||
$t("main.loadpoint.fallbackName"),
|
||||
])
|
||||
}}
|
||||
</Badge>
|
||||
<Badge v-else variant="muted" class="flex-shrink-0">
|
||||
{{ $t("main.vehicleSettings.notConnected") }}
|
||||
</Badge>
|
||||
</div>
|
||||
<SettingsFormRow
|
||||
:id="fieldId(vehicle, 'mode')"
|
||||
:label="$t('main.vehicleSettings.mode')"
|
||||
>
|
||||
<select
|
||||
:id="fieldId(vehicle, 'mode')"
|
||||
class="form-select form-select-sm"
|
||||
:value="vehicle.mode ?? ''"
|
||||
@change="changeMode(vehicle, $event)"
|
||||
>
|
||||
<option v-for="opt in modeOptions" :key="opt.value" :value="opt.value">
|
||||
{{ opt.name }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsFormRow>
|
||||
<template v-if="socSupported(vehicle)">
|
||||
<SettingsFormRow
|
||||
:id="fieldId(vehicle, 'limitSoc')"
|
||||
:label="$t('main.vehicleSettings.limitSoc')"
|
||||
>
|
||||
<select
|
||||
:id="fieldId(vehicle, 'limitSoc')"
|
||||
class="form-select form-select-sm"
|
||||
:value="vehicle.limitSoc ?? 0"
|
||||
@change="changeLimitSoc(vehicle, $event)"
|
||||
>
|
||||
<option
|
||||
v-for="opt in socOptions(vehicle)"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.name }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsFormRow>
|
||||
<SettingsFormRow
|
||||
:id="fieldId(vehicle, 'minSoc')"
|
||||
:label="$t('main.vehicleSettings.minSoc')"
|
||||
:description="$t('main.vehicleSettings.minSocDescription')"
|
||||
>
|
||||
<select
|
||||
:id="fieldId(vehicle, 'minSoc')"
|
||||
class="form-select form-select-sm"
|
||||
:value="vehicle.minSoc ?? 0"
|
||||
@change="changeMinSoc(vehicle, $event)"
|
||||
>
|
||||
<option
|
||||
v-for="opt in socOptions(vehicle)"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.name }}
|
||||
</option>
|
||||
</select>
|
||||
</SettingsFormRow>
|
||||
</template>
|
||||
</div>
|
||||
<p class="mb-0 border-top pt-4">
|
||||
<i18n-t keypath="main.vehicleSettings.editHint" tag="span" scope="global">
|
||||
<router-link to="/config#vehicles" @click="closeModal">
|
||||
{{ $t("config.main.title") }}
|
||||
</router-link>
|
||||
</i18n-t>
|
||||
</p>
|
||||
</div>
|
||||
</GenericModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Badge from "../Helper/Badge.vue";
|
||||
import GenericModal from "../Helper/GenericModal.vue";
|
||||
import SettingsFormRow from "../Helper/SettingsFormRow.vue";
|
||||
import VehicleIcon from "../VehicleIcon";
|
||||
import api from "@/api";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { docsPrefix } from "@/i18n";
|
||||
import { distanceUnit } from "@/units";
|
||||
import { vehicleHasSoc, vehicleNotReachable } from "@/uiLoadpoints";
|
||||
import { CHARGE_MODE, type SelectOption, type UiLoadpoint, type Vehicle } from "@/types/evcc";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
|
||||
const { OFF, PV, MINPV, NOW } = CHARGE_MODE;
|
||||
|
||||
export default defineComponent({
|
||||
name: "VehicleSettingsModal",
|
||||
components: { Badge, GenericModal, SettingsFormRow, VehicleIcon },
|
||||
mixins: [formatter],
|
||||
props: {
|
||||
vehicles: { type: Array as PropType<Vehicle[]>, default: () => [] },
|
||||
loadpoints: { type: Array as PropType<UiLoadpoint[]>, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isModalVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
docsUrl(): string {
|
||||
return `${docsPrefix()}/features/limits`;
|
||||
},
|
||||
modeOptions(): SelectOption<string>[] {
|
||||
return [
|
||||
{ value: "", name: this.$t("main.vehicleSettings.keepAsIs") },
|
||||
...[OFF, PV, MINPV, NOW].map((mode) => ({
|
||||
value: mode,
|
||||
name: this.$t(`main.mode.${mode}`),
|
||||
})),
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
modalVisible(): void {
|
||||
this.isModalVisible = true;
|
||||
},
|
||||
modalInvisible(): void {
|
||||
this.isModalVisible = false;
|
||||
},
|
||||
closeModal(): void {
|
||||
(this.$refs["modal"] as InstanceType<typeof GenericModal> | undefined)?.close();
|
||||
},
|
||||
fieldId(vehicle: Vehicle, field: string): string {
|
||||
return `vehicleSettings-${vehicle.name}-${field}`;
|
||||
},
|
||||
connectedLoadpoint(vehicle: Vehicle): UiLoadpoint | undefined {
|
||||
return this.loadpoints.find((lp) => lp.vehicleName === vehicle.name && lp.connected);
|
||||
},
|
||||
socSupported(vehicle: Vehicle): boolean {
|
||||
const loadpoint = this.connectedLoadpoint(vehicle);
|
||||
if (loadpoint) {
|
||||
return loadpoint.socBasedCharging || loadpoint.vehicleNotReachable;
|
||||
}
|
||||
return vehicleHasSoc(vehicle) || vehicleNotReachable(vehicle);
|
||||
},
|
||||
socOptions(vehicle: Vehicle): SelectOption<number>[] {
|
||||
// 0 = none, then 5-100 in steps of 5
|
||||
const rangePerSoc = this.connectedLoadpoint(vehicle)?.rangePerSoc;
|
||||
return Array.from(Array(21).keys()).map((i) => {
|
||||
const soc = i * 5;
|
||||
return {
|
||||
value: soc,
|
||||
name:
|
||||
soc === 0
|
||||
? this.$t("general.none")
|
||||
: this.fmtSocOption(soc, rangePerSoc, distanceUnit()),
|
||||
};
|
||||
});
|
||||
},
|
||||
selectValue(event: Event): string {
|
||||
return (event.target as HTMLSelectElement).value;
|
||||
},
|
||||
changeMode(vehicle: Vehicle, event: Event): void {
|
||||
const mode = this.selectValue(event);
|
||||
if (mode === "") {
|
||||
api.delete(`vehicles/${vehicle.name}/mode`);
|
||||
} else {
|
||||
api.post(`vehicles/${vehicle.name}/mode/${mode}`);
|
||||
}
|
||||
},
|
||||
changeMinSoc(vehicle: Vehicle, event: Event): void {
|
||||
api.post(`vehicles/${vehicle.name}/minsoc/${this.selectValue(event)}`);
|
||||
},
|
||||
changeLimitSoc(vehicle: Vehicle, event: Event): void {
|
||||
api.post(`vehicles/${vehicle.name}/limitsoc/${this.selectValue(event)}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="d-flex justify-content-between mb-3 align-items-center" data-testid="vehicle-title">
|
||||
<h4 class="d-flex align-items-center m-0 flex-grow-1 overflow-hidden">
|
||||
<h4 class="d-flex align-items-center flex-grow-1 overflow-hidden ring-space">
|
||||
<div
|
||||
v-if="iconType === 'refresh'"
|
||||
ref="refresh"
|
||||
|
|
@ -158,6 +158,11 @@ export default defineComponent({
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* overflow-hidden would clip the vehicle select focus ring */
|
||||
.ring-space {
|
||||
padding: 0.5rem;
|
||||
margin: -0.5rem;
|
||||
}
|
||||
.vehicle-name {
|
||||
text-decoration-color: var(--evcc-gray);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
v-bind="vehicleStatus"
|
||||
class="mb-2"
|
||||
@open-loadpoint-settings="$emit('open-loadpoint-settings')"
|
||||
@open-minsoc-settings="openPlanModal(true)"
|
||||
@open-plan-modal="openPlanModal"
|
||||
@open-minsoc-settings="openVehicleSettings"
|
||||
@open-plan-modal="$emit('open-modal')"
|
||||
/>
|
||||
<div class="mt-2 mb-4 d-flex gap-2">
|
||||
<BatteryBoostButton
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
v-bind="vehicleSocProps"
|
||||
@limit-soc-updated="limitSocUpdated"
|
||||
@limit-soc-drag="limitSocDrag"
|
||||
@plan-clicked="openPlanModal"
|
||||
@plan-clicked="$emit('open-modal')"
|
||||
/>
|
||||
</div>
|
||||
<div class="details d-flex flex-wrap justify-content-between">
|
||||
|
|
@ -80,6 +80,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Modal from "bootstrap/js/dist/modal";
|
||||
import collector from "@/mixins/collector.ts";
|
||||
import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
||||
import LabelAndValue from "../Helper/LabelAndValue.vue";
|
||||
|
|
@ -287,8 +288,10 @@ export default defineComponent({
|
|||
fmtEnergy(value: number) {
|
||||
return this.fmtWh(value, value == 0 ? POWER_UNIT.KW : POWER_UNIT.AUTO);
|
||||
},
|
||||
openPlanModal(openArrivalTab = false) {
|
||||
this.$emit("open-modal", openArrivalTab);
|
||||
openVehicleSettings() {
|
||||
Modal.getOrCreateInstance(
|
||||
document.getElementById("vehicleSettingsModal") as HTMLElement
|
||||
).show();
|
||||
},
|
||||
handleBoostStatus(status: VehicleStatus) {
|
||||
this.statusOverride = status;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,15 @@ const get = (id: string) => {
|
|||
return settings.loadpoints[id];
|
||||
};
|
||||
|
||||
export const vehicleHasSoc = (vehicle?: Vehicle): boolean => {
|
||||
return !vehicle?.features?.includes("Offline");
|
||||
};
|
||||
|
||||
export const vehicleNotReachable = (vehicle?: Vehicle): boolean => {
|
||||
const features = vehicle?.features || [];
|
||||
return features.includes("Offline") && features.includes("Retryable");
|
||||
};
|
||||
|
||||
export const convertToUiLoadpoints = (
|
||||
loadpoints: Loadpoint[],
|
||||
vehicles: Record<string, Vehicle>
|
||||
|
|
@ -24,8 +33,8 @@ export const convertToUiLoadpoints = (
|
|||
const capacity = vehicle?.capacity || 0;
|
||||
const range = distanceValue(vehicleRange);
|
||||
const vehicleKnown = !!lp.vehicleName;
|
||||
const vehicleHasSoc = vehicleKnown && !vehicle?.features?.includes("Offline");
|
||||
const socBasedCharging = vehicleHasSoc || lp.vehicleSoc > 0;
|
||||
const hasSoc = vehicleKnown && vehicleHasSoc(vehicle);
|
||||
const socBasedCharging = hasSoc || lp.vehicleSoc > 0;
|
||||
const socBasedPlanning = !!(socBasedCharging && capacity > 0);
|
||||
|
||||
return {
|
||||
|
|
@ -36,7 +45,7 @@ export const convertToUiLoadpoints = (
|
|||
vehicleSoc,
|
||||
capacity,
|
||||
vehicleKnown,
|
||||
vehicleHasSoc,
|
||||
vehicleHasSoc: hasSoc,
|
||||
socBasedCharging,
|
||||
socBasedPlanning,
|
||||
displayTitle: vehicle?.title || lp.title || "Charging point",
|
||||
|
|
@ -49,9 +58,7 @@ export const convertToUiLoadpoints = (
|
|||
rangePerSoc:
|
||||
vehicleSoc > 10 && range ? Math.round((range / vehicleSoc) * 1e2) / 1e2 : undefined,
|
||||
socPerKwh: capacity > 0 ? 100 / capacity : 0,
|
||||
vehicleNotReachable:
|
||||
(vehicle?.features || []).includes("Offline") &&
|
||||
(vehicle?.features || []).includes("Retryable"),
|
||||
vehicleNotReachable: vehicleNotReachable(vehicle),
|
||||
} satisfies UiLoadpoint;
|
||||
});
|
||||
|
||||
|
|
|
|||
6
assets/js/utils/vehicleList.ts
Normal file
6
assets/js/utils/vehicleList.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import type { Vehicle } from "@/types/evcc";
|
||||
|
||||
// convert the vehicles state record into a list, keys become vehicle names
|
||||
export default function vehicleList(vehicles?: Record<string, Vehicle>): Vehicle[] {
|
||||
return Object.entries(vehicles || {}).map(([name, vehicle]) => ({ ...vehicle, name }));
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
<BottomTabBar v-bind="bottomTabBarProps" />
|
||||
|
||||
<GlobalSettingsModal v-bind="globalSettingsProps" />
|
||||
<VehicleSettingsModal :vehicles="vehicleList" :loadpoints="state.uiLoadpoints" />
|
||||
<AboutModal v-bind="aboutModalProps" />
|
||||
<HelpModal />
|
||||
<PasswordModal />
|
||||
|
|
@ -21,12 +22,14 @@
|
|||
import store from "../store";
|
||||
import BottomTabBar from "../components/BottomTabs/Bar.vue";
|
||||
import GlobalSettingsModal from "../components/GlobalSettings/GlobalSettingsModal.vue";
|
||||
import VehicleSettingsModal from "../components/Vehicles/SettingsModal.vue";
|
||||
import OfflineIndicator from "../components/Footer/OfflineIndicator.vue";
|
||||
import PasswordModal from "../components/Auth/PasswordModal.vue";
|
||||
import LoginModal from "../components/Auth/LoginModal.vue";
|
||||
import AboutModal from "../components/AboutModal.vue";
|
||||
import HelpModal from "../components/HelpModal.vue";
|
||||
import collector from "../mixins/collector";
|
||||
import vehicleList from "@/utils/vehicleList";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
// assume offline if not data received for 5 minutes
|
||||
|
|
@ -45,6 +48,7 @@ export default defineComponent({
|
|||
AboutModal,
|
||||
BottomTabBar,
|
||||
GlobalSettingsModal,
|
||||
VehicleSettingsModal,
|
||||
HelpModal,
|
||||
PasswordModal,
|
||||
LoginModal,
|
||||
|
|
@ -77,6 +81,9 @@ export default defineComponent({
|
|||
const { state, uiLoadpoints } = store;
|
||||
return { ...state, uiLoadpoints: uiLoadpoints.value };
|
||||
},
|
||||
vehicleList() {
|
||||
return vehicleList(this.state.vehicles);
|
||||
},
|
||||
globalSettingsProps() {
|
||||
return this.collectProps(GlobalSettingsModal, this.state);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="my-4">{{ $t("config.section.vehicles") }}</h2>
|
||||
<h2 id="vehicles" class="my-4">{{ $t("config.section.vehicles") }}</h2>
|
||||
<div class="p-0 config-list box-pull-out">
|
||||
<DeviceCard
|
||||
v-for="vehicle in vehicles"
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ import DynamicPriceIcon from "../components/MaterialIcon/DynamicPrice.vue";
|
|||
import { TYPES, GROUPS, PERIODS, type Session } from "../components/Sessions/types";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { CURRENCY, type Notification } from "@/types/evcc";
|
||||
import vehicleList from "@/utils/vehicleList";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Sessions",
|
||||
|
|
@ -468,8 +469,7 @@ export default defineComponent({
|
|||
});
|
||||
},
|
||||
vehicleList() {
|
||||
const vehicles = store.state.vehicles || {};
|
||||
return Object.entries(vehicles).map(([name, vehicle]) => ({ ...vehicle, name }));
|
||||
return vehicleList(store.state.vehicles);
|
||||
},
|
||||
loadpointList() {
|
||||
const loadpoints = store.state.loadpoints || [];
|
||||
|
|
|
|||
12
i18n/bg.json
12
i18n/bg.json
|
|
@ -854,9 +854,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Активен",
|
||||
"addRepeatingPlan": "Добавяне на повтарящ се план",
|
||||
"arrivalTab": "Пристигане",
|
||||
"day": "Ден",
|
||||
"departureTab": "Заминаване",
|
||||
"goal": "Цел на зареждане",
|
||||
"modalTitle": "План за зареждане",
|
||||
"none": "няма",
|
||||
|
|
@ -880,8 +878,6 @@
|
|||
"strategySettings": "Настройки на стратегията",
|
||||
"time": "Време",
|
||||
"title": "План",
|
||||
"titleMinSoc": "Минимално зареждане",
|
||||
"titleTargetCharge": "Заминаване",
|
||||
"unsavedChanges": "Има незаписани промени. Приложи сега?",
|
||||
"update": "Приложи",
|
||||
"weekdays": "Дни"
|
||||
|
|
@ -939,20 +935,12 @@
|
|||
"currents": "Заряден ток",
|
||||
"default": "по подразбиране",
|
||||
"disclaimerHint": "Забележка:",
|
||||
"limitSoc": {
|
||||
"description": "Ограничение на зареждането, което се използва, когато този автомобил е свързан.",
|
||||
"label": "Лимит по подразбиране"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Макс. ток"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Мин. ток"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Превозното средство се зарежда „бързо“ до {0} в соларен режим. След това продължава със соларен излишък. Полезно за осигуряване на минимален пробег дори в по-тъмни дни.",
|
||||
"label": "Мин. заряд %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Тези опции са налични само за превозни средства с известен заряд.",
|
||||
"phasesConfigured": {
|
||||
"label": "Фази",
|
||||
|
|
|
|||
|
|
@ -393,9 +393,6 @@
|
|||
"remoteDisabledSoft": "{source}: isključeno prilagodljivo solarno punjenje"
|
||||
},
|
||||
"loadpointSettings": {
|
||||
"minSoc": {
|
||||
"description": "Vozilo se brzo puni do {0} u solarnom načinu rada. Zatim nastavlja punjenjem viškom solarne energije. Korisno za osiguranje minimalnog dometa čak i za tmurnije dane."
|
||||
},
|
||||
"phasesConfigured": {
|
||||
"phases_1_hint": "(od {min} do {max})",
|
||||
"phases_3_hint": "(od {min} do {max})"
|
||||
|
|
|
|||
10
i18n/ca.json
10
i18n/ca.json
|
|
@ -116,13 +116,9 @@
|
|||
},
|
||||
"main": {
|
||||
"chargingPlan": {
|
||||
"arrivalTab": "Arribades",
|
||||
"departureTab": "Sortides",
|
||||
"modalTitle": "Plà de càrrega",
|
||||
"none": "no definit",
|
||||
"title": "Plà",
|
||||
"titleMinSoc": "Càrrega mínima",
|
||||
"titleTargetCharge": "Sortides"
|
||||
"title": "Plà"
|
||||
},
|
||||
"energyflow": {
|
||||
"battery": "Bateria",
|
||||
|
|
@ -156,10 +152,6 @@
|
|||
"minCurrent": {
|
||||
"label": "Corriente de carrega mínima"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Marge per a emergències. El vehicle es carrega 'ràpidament' a {0} en mode solar. Després continua amb l'excedent fotovoltaic.",
|
||||
"label": "Càrrega de min. %"
|
||||
},
|
||||
"phasesConfigured": {
|
||||
"label": "Fases",
|
||||
"phases_0": "Canvi automàtic",
|
||||
|
|
|
|||
12
i18n/cs.json
12
i18n/cs.json
|
|
@ -1157,9 +1157,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktivní",
|
||||
"addRepeatingPlan": "Přidat opakující se plán",
|
||||
"arrivalTab": "Příchod",
|
||||
"day": "Den",
|
||||
"departureTab": "Odchod",
|
||||
"goal": "Cílový stav nabíjení",
|
||||
"modalTitle": "Plán nabíjení",
|
||||
"none": "Žádný",
|
||||
|
|
@ -1183,8 +1181,6 @@
|
|||
"strategySettings": "Nastavení strategie",
|
||||
"time": "Čas",
|
||||
"title": "Plán",
|
||||
"titleMinSoc": "Min. nabíjení",
|
||||
"titleTargetCharge": "Odjezd",
|
||||
"unsavedChanges": "Jsou zde neuložené změny. Použít nyní?",
|
||||
"update": "Nastavit",
|
||||
"weekdays": "Dny"
|
||||
|
|
@ -1282,20 +1278,12 @@
|
|||
"currents": "Nabíjecí proud",
|
||||
"default": "výchozí",
|
||||
"disclaimerHint": "Poznámka:",
|
||||
"limitSoc": {
|
||||
"description": "Nabíjecí limit, který je použit, když je připojeno toto vozidlo.",
|
||||
"label": "Výchozí limit nabití"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Proud"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Proud"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Vozidlo bude rychle nabito na {0} v solárním režimu. Poté bude pokračovat nabíjení z přebytku solární energie. Užitečné pro zajištění minimálního dojezdu i během zatažených dnů.",
|
||||
"label": "Minimální úroveň nabití"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Tyto volby jsou povolené pouze pro vozidla se známou nabíjecí úrovní.",
|
||||
"phasesConfigured": {
|
||||
"label": "Připojení fází",
|
||||
|
|
|
|||
12
i18n/da.json
12
i18n/da.json
|
|
@ -1194,9 +1194,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiv",
|
||||
"addRepeatingPlan": "Tilføj plan for gentagelse",
|
||||
"arrivalTab": "Ankomst",
|
||||
"day": "Dag",
|
||||
"departureTab": "Afgang",
|
||||
"goal": "Opladnings mål",
|
||||
"modalTitle": "Lade plan",
|
||||
"none": "ingen",
|
||||
|
|
@ -1220,8 +1218,6 @@
|
|||
"strategySettings": "Indstillinger for strategi",
|
||||
"time": "Tid",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min. ladning",
|
||||
"titleTargetCharge": "Afgang",
|
||||
"unsavedChanges": "Der er ændringer som ikke er gemt. Skal de benyttes nu?",
|
||||
"update": "Anvend",
|
||||
"weekdays": "Dage"
|
||||
|
|
@ -1320,20 +1316,12 @@
|
|||
"currents": "Ladestrøm",
|
||||
"default": "standard",
|
||||
"disclaimerHint": "Bemærk:",
|
||||
"limitSoc": {
|
||||
"description": "Opladningsgrænse som benyttes når dette køretøj er forbundet.",
|
||||
"label": "standard opladningsgrænse"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Nuværende"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Nuværende"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Køretøjet bliver „hurtigt” opladet til {0} i solar mode, og fortsætter derefter med solenergioverskuddet. Nyttig til at sikre en minimum rækkevidde selv på mørke dage.",
|
||||
"label": "Min. opladning %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Disse indstillinger er kun tilgængelig for køretøjer med kendt opladningsniveau.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faser",
|
||||
|
|
|
|||
30
i18n/de.json
30
i18n/de.json
|
|
@ -1146,6 +1146,7 @@
|
|||
},
|
||||
"general": {
|
||||
"download": "Download",
|
||||
"none": "keine",
|
||||
"note": "Hinweis:"
|
||||
},
|
||||
"header": {
|
||||
|
|
@ -1259,9 +1260,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiv",
|
||||
"addRepeatingPlan": "Wiederholenden Plan hinzufügen",
|
||||
"arrivalTab": "Ankunft",
|
||||
"day": "Tag",
|
||||
"departureTab": "Abfahrt",
|
||||
"goal": "Ladeziel",
|
||||
"modalTitle": "Ladeplanung",
|
||||
"none": "keiner",
|
||||
|
|
@ -1285,8 +1284,6 @@
|
|||
"strategySettings": "Strategie-Einstellungen",
|
||||
"time": "Zeit",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min. Ladung",
|
||||
"titleTargetCharge": "Abfahrt",
|
||||
"unsavedChanges": "Ungespeicherte Änderungen vorhanden. Jetzt anwenden?",
|
||||
"update": "Anwenden",
|
||||
"weekdays": "Tage"
|
||||
|
|
@ -1315,7 +1312,8 @@
|
|||
"pv": "Solaranlage",
|
||||
"pvExport": "Einspeisung",
|
||||
"pvProduction": "Erzeugung",
|
||||
"selfConsumption": "Eigenverbrauch"
|
||||
"selfConsumption": "Eigenverbrauch",
|
||||
"toggleDetails": "Energiefluss-Details"
|
||||
},
|
||||
"heatingStatus": {
|
||||
"charging": "Heize …",
|
||||
|
|
@ -1395,20 +1393,12 @@
|
|||
"currents": "Ladestrom",
|
||||
"default": "default",
|
||||
"heating": "Heizen",
|
||||
"limitSoc": {
|
||||
"description": "Dieses Ladelimit wird verwendet, wenn das Fahrzeug angeschlossen wird.",
|
||||
"label": "Standard Ladelimit"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Ladestrom"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Ladestrom"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Fahrzeug wird im PV-Modus „Schnell“ auf {0} geladen. Danach weiter mit PV-Überschuss. Nützlich, um auch an dunklen Tagen eine Mindestreichweite zu gewährleisten.",
|
||||
"label": "Min. Ladung %"
|
||||
},
|
||||
"minTemp": {
|
||||
"description": "Unter {0} wird im PV-Modus auch mit Batterie- und Netzstrom geheizt, um diese Temperatur zu halten.",
|
||||
"label": "Min. Temperatur"
|
||||
|
|
@ -1495,6 +1485,20 @@
|
|||
"unknown": "Gastfahrzeug",
|
||||
"vehicleSoc": "Ladestand"
|
||||
},
|
||||
"vehicleSettings": {
|
||||
"connectedTo": "verbunden mit {0}",
|
||||
"description": "Diese Standardwerte werden angewendet, wenn das Fahrzeug an einem Ladepunkt angeschlossen wird.",
|
||||
"editHint": "Fahrzeuge unter {0} hinzufügen oder bearbeiten.",
|
||||
"keepAsIs": "Beibehalten",
|
||||
"learnMore": "Mehr erfahren.",
|
||||
"limitSoc": "Standardlimit",
|
||||
"menu": "Fahrzeuge",
|
||||
"minSoc": "Minimale Ladung",
|
||||
"minSocDescription": "Wird im PV-Modus schnell bis zu diesem Wert geladen.",
|
||||
"mode": "Standardmodus",
|
||||
"notConnected": "nicht verbunden",
|
||||
"title": "Fahrzeugeinstellungen"
|
||||
},
|
||||
"vehicleStatus": {
|
||||
"awaitingAuthorization": "Warte auf Autorisierung.",
|
||||
"batteryBoost": "Batterie Boost aktiv.",
|
||||
|
|
|
|||
12
i18n/el.json
12
i18n/el.json
|
|
@ -859,9 +859,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Ενεργό",
|
||||
"addRepeatingPlan": "Προσθήκη επαναλαμβανόμενου προγράμματος",
|
||||
"arrivalTab": "Άφιξη",
|
||||
"day": "Ημέρα",
|
||||
"departureTab": "Αναχώρηση",
|
||||
"goal": "Επιθυμητή φόρτιση",
|
||||
"modalTitle": "Πρόγραμμα Φόρτισης",
|
||||
"none": "κανένα",
|
||||
|
|
@ -885,8 +883,6 @@
|
|||
"strategySettings": "Ρυθμίσεις στρατηγικής",
|
||||
"time": "Ώρα",
|
||||
"title": "Πρόγραμμα",
|
||||
"titleMinSoc": "Ελάχιστη φόρτιση",
|
||||
"titleTargetCharge": "Αναχώρηση",
|
||||
"unsavedChanges": "Υπάρχουν μη αποθηκευμένες αλλαγές. Υποβολή τώρα;",
|
||||
"update": "Υποβολή",
|
||||
"weekdays": "Ημέρες"
|
||||
|
|
@ -944,20 +940,12 @@
|
|||
"currents": "Ένταση φόρτισης",
|
||||
"default": "προεπιλογή",
|
||||
"disclaimerHint": "Σημείωση:",
|
||||
"limitSoc": {
|
||||
"description": "Όριο φόρτισης που χρησιμοποιείται όταν αυτό το όχημα είναι συνδεδεμένο.",
|
||||
"label": "Προεπιλεγμένο όριο"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Μέγιστη Ένταση"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Ελάχιστη Ένταση"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Το όχημα φορτίζεται (γρήγορα) μέχρι {0} σε λειτουργία ηλιακής Φ/Β ενέργειας. Μετά συνεχίζει μόνο με το πλεόνασμα Φ/Β ενέργειας. Χρήσιμο για να εξασφαλίσει μια ελάχιστη εμβέλεια ακόμη και για πιο συννεφιασμένες ημέρες.",
|
||||
"label": "Ελ. % φόρτισης"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Αυτές οι επιλογές είναι διαθέσιμες μόνο για οχήματα με γνωστό επίπεδο φόρτισης.",
|
||||
"phasesConfigured": {
|
||||
"label": "Φάσεις",
|
||||
|
|
|
|||
30
i18n/en.json
30
i18n/en.json
|
|
@ -1146,6 +1146,7 @@
|
|||
},
|
||||
"general": {
|
||||
"download": "Download",
|
||||
"none": "none",
|
||||
"note": "Note:"
|
||||
},
|
||||
"header": {
|
||||
|
|
@ -1259,9 +1260,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Active",
|
||||
"addRepeatingPlan": "Add repeating plan",
|
||||
"arrivalTab": "Arrival",
|
||||
"day": "Day",
|
||||
"departureTab": "Departure",
|
||||
"goal": "Charging goal",
|
||||
"modalTitle": "Charging Plan",
|
||||
"none": "none",
|
||||
|
|
@ -1285,8 +1284,6 @@
|
|||
"strategySettings": "Strategy settings",
|
||||
"time": "Time",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min charge",
|
||||
"titleTargetCharge": "Departure",
|
||||
"unsavedChanges": "There are unsaved changes. Apply now?",
|
||||
"update": "Apply",
|
||||
"weekdays": "Days"
|
||||
|
|
@ -1315,7 +1312,8 @@
|
|||
"pv": "Solar system",
|
||||
"pvExport": "Grid export",
|
||||
"pvProduction": "Production",
|
||||
"selfConsumption": "Self-consumption"
|
||||
"selfConsumption": "Self-consumption",
|
||||
"toggleDetails": "Energy flow details"
|
||||
},
|
||||
"heatingStatus": {
|
||||
"charging": "Heating…",
|
||||
|
|
@ -1395,20 +1393,12 @@
|
|||
"currents": "Charging Current",
|
||||
"default": "default",
|
||||
"heating": "Heating",
|
||||
"limitSoc": {
|
||||
"description": "Charging limit that is used when this vehicle is connected.",
|
||||
"label": "Default limit"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Current"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Current"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "The vehicle gets „fast” charged to {0} in solar mode. Then continues with solar surplus. Useful to ensure a minimum range even for darker days.",
|
||||
"label": "Min. charge %"
|
||||
},
|
||||
"minTemp": {
|
||||
"description": "Below {0} battery and grid power are also used in solar mode to maintain this temperature.",
|
||||
"label": "Min. temperature"
|
||||
|
|
@ -1495,6 +1485,20 @@
|
|||
"unknown": "Guest vehicle",
|
||||
"vehicleSoc": "Charge"
|
||||
},
|
||||
"vehicleSettings": {
|
||||
"connectedTo": "connected to {0}",
|
||||
"description": "These defaults are applied when the vehicle connects to a charging point.",
|
||||
"editHint": "Add or edit vehicles in {0}.",
|
||||
"keepAsIs": "Keep as is",
|
||||
"learnMore": "Learn more.",
|
||||
"limitSoc": "Default limit",
|
||||
"menu": "Vehicles",
|
||||
"minSoc": "Minimum charge",
|
||||
"minSocDescription": "Fast charge to this level in solar mode.",
|
||||
"mode": "Default mode",
|
||||
"notConnected": "not connected",
|
||||
"title": "Vehicle Settings"
|
||||
},
|
||||
"vehicleStatus": {
|
||||
"awaitingAuthorization": "Waiting for authorization.",
|
||||
"batteryBoost": "Battery boost active.",
|
||||
|
|
|
|||
12
i18n/es.json
12
i18n/es.json
|
|
@ -988,9 +988,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Activo",
|
||||
"addRepeatingPlan": "Añadir plan recurrente",
|
||||
"arrivalTab": "Llegadas",
|
||||
"day": "Día",
|
||||
"departureTab": "Salidas",
|
||||
"goal": "Objetivo de la carga",
|
||||
"modalTitle": "Plan de carga",
|
||||
"none": "ninguno",
|
||||
|
|
@ -1014,8 +1012,6 @@
|
|||
"strategySettings": "Configuración de la estrategia",
|
||||
"time": "Tiempo",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Carga mínima",
|
||||
"titleTargetCharge": "Partida",
|
||||
"unsavedChanges": "Hay cambios sin guardar. ¿Aplicar ahora?",
|
||||
"update": "Aplicar",
|
||||
"weekdays": "Días"
|
||||
|
|
@ -1073,20 +1069,12 @@
|
|||
"currents": "Corriente de carga",
|
||||
"default": "defecto",
|
||||
"disclaimerHint": "Nota:",
|
||||
"limitSoc": {
|
||||
"description": "Límite de carga que se utiliza cuando este vehículo está conectado.",
|
||||
"label": "Límite predeterminado"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Corriente de carga máxima"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Corriente de carga mínima"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "El vehículo se carga «rápidamente» hasta {0} en modo solar. A continuación, continúa con el excedente solar. Útil para garantizar una autonomía mínima incluso en días más oscuros.",
|
||||
"label": "Carga de min. %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Estas opciones solo están disponibles para vehículos con un nivel de carga conocido.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fases",
|
||||
|
|
|
|||
12
i18n/fi.json
12
i18n/fi.json
|
|
@ -1192,9 +1192,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiivinen",
|
||||
"addRepeatingPlan": "Lisää toistuva suunnitelma",
|
||||
"arrivalTab": "Saapuminen",
|
||||
"day": "Päivä",
|
||||
"departureTab": "Lähtö",
|
||||
"goal": "Lataus tavoite",
|
||||
"modalTitle": "Lataussuunnitelma",
|
||||
"none": "ei mitään",
|
||||
|
|
@ -1218,8 +1216,6 @@
|
|||
"strategySettings": "Strategiamääritykset",
|
||||
"time": "Aika",
|
||||
"title": "Suunnitelma",
|
||||
"titleMinSoc": "Minimi lataus",
|
||||
"titleTargetCharge": "Lähtö",
|
||||
"unsavedChanges": "Tallentamattomia muutoksia. Asetetaanko nyt?",
|
||||
"update": "Aseta",
|
||||
"weekdays": "Päivät"
|
||||
|
|
@ -1317,20 +1313,12 @@
|
|||
"currents": "Latausvirta",
|
||||
"default": "oletus",
|
||||
"disclaimerHint": "Huomautus:",
|
||||
"limitSoc": {
|
||||
"description": "Latausraja mitä käytetään kun ajoneuvo yhdistetty.",
|
||||
"label": "Oletusraja"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maksimivirta"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Minimivirta"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Ajoneuvo ladataan välittömästi {0} asti huolimatta aurinkoenergian ylijäämästä, tämän jälkeen vain ylijäämäenergialla. Tämä on hyödyllinen takaamaan minimi toimintamatka myös pimeinä päivinä.",
|
||||
"label": "Minimi lataustaso"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Nämä vaihtoehdot ovat käytettävissä vain ajoneuvoille, joiden lataustaso on tiedossa.",
|
||||
"phasesConfigured": {
|
||||
"label": "Vaihetila",
|
||||
|
|
|
|||
12
i18n/fr.json
12
i18n/fr.json
|
|
@ -1196,9 +1196,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Actif",
|
||||
"addRepeatingPlan": "Ajouter un plan récurrent",
|
||||
"arrivalTab": "Arrivée",
|
||||
"day": "Jour",
|
||||
"departureTab": "Départ",
|
||||
"goal": "Objectif de charge",
|
||||
"modalTitle": "Planification de la charge",
|
||||
"none": "aucune",
|
||||
|
|
@ -1222,8 +1220,6 @@
|
|||
"strategySettings": "Paramètres de stratégie",
|
||||
"time": "Heure",
|
||||
"title": "Planification",
|
||||
"titleMinSoc": "Charge min",
|
||||
"titleTargetCharge": "Départ",
|
||||
"unsavedChanges": "Il y a des modifications non sauvegardées. Appliquer ?",
|
||||
"update": "Appliquer",
|
||||
"weekdays": "Jours"
|
||||
|
|
@ -1320,20 +1316,12 @@
|
|||
"currents": "Courant de charge",
|
||||
"default": "défaut",
|
||||
"disclaimerHint": "Remarque :",
|
||||
"limitSoc": {
|
||||
"description": "Limite de charge à utiliser quand ce véhicule est connecté.",
|
||||
"label": "Limite par défaut"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Courant max."
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Courant min."
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Le véhicule est chargé en mode „Rapide” à {0} en mode solaire, puis avec le surplus solaire. Utile pour s'assurer d'un minimum d'autonomie, même pour les jours plus sombres.",
|
||||
"label": "Charge min. %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Ces options sont disponibles uniquement pour les véhicules avec un niveau de charge connu.",
|
||||
"phasesConfigured": {
|
||||
"label": "Phases",
|
||||
|
|
|
|||
12
i18n/hr.json
12
i18n/hr.json
|
|
@ -860,9 +860,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktivno",
|
||||
"addRepeatingPlan": "Dodaj ponavljajući plan",
|
||||
"arrivalTab": "Dolazak",
|
||||
"day": "Dan",
|
||||
"departureTab": "Odlazak",
|
||||
"goal": "Cilj punjenja",
|
||||
"modalTitle": "Plan punjenja",
|
||||
"none": "nema, dodaj",
|
||||
|
|
@ -886,8 +884,6 @@
|
|||
"strategySettings": "Postavke strategije",
|
||||
"time": "Vrijeme",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min. punjenje",
|
||||
"titleTargetCharge": "Odlazak",
|
||||
"unsavedChanges": "Postoje promjene koje nisu spremljene. Spremiti odmah?",
|
||||
"update": "Primijeni",
|
||||
"weekdays": "Dani"
|
||||
|
|
@ -945,20 +941,12 @@
|
|||
"currents": "Struja punjenja",
|
||||
"default": "zadano",
|
||||
"disclaimerHint": "Napomena:",
|
||||
"limitSoc": {
|
||||
"description": "Ograničenje punjenja koje se koristi kada je ovo vozilo spojeno.",
|
||||
"label": "Zadano ograničenje"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maksimalna struja punjenja"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Minimalna struja punjenja"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Vozilo se puni „brzo” na {0} u solarnom modusu. Zatim se nastavlja puniti sa solarnim viškom. Korisno za osiguravanje minimalnog raspona čak i u danima s manje sunca.",
|
||||
"label": "Min. % punjenja"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Navedene opcije dostupne samo za vozila s poznatom razinom napunjenosti baterije.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faze",
|
||||
|
|
|
|||
12
i18n/hu.json
12
i18n/hu.json
|
|
@ -870,9 +870,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktív",
|
||||
"addRepeatingPlan": "Ismétlődő terv hozzáadása",
|
||||
"arrivalTab": "Érkezés",
|
||||
"day": "Nap",
|
||||
"departureTab": "Indulás",
|
||||
"goal": "Töltési cél",
|
||||
"modalTitle": "Töltési Tervezet",
|
||||
"none": "nincs",
|
||||
|
|
@ -896,8 +894,6 @@
|
|||
"strategySettings": "Stratégiai beállítások",
|
||||
"time": "Idő",
|
||||
"title": "Terv",
|
||||
"titleMinSoc": "Min töltés",
|
||||
"titleTargetCharge": "Indulás",
|
||||
"unsavedChanges": "Vannak nem mentett módosítások. Alkalmazza most?",
|
||||
"update": "Alkalmazás",
|
||||
"weekdays": "Napok"
|
||||
|
|
@ -955,20 +951,12 @@
|
|||
"currents": "Töltőáram",
|
||||
"default": "alapértelmezett",
|
||||
"disclaimerHint": "Megjegyzés:",
|
||||
"limitSoc": {
|
||||
"description": "Töltési korlát, amikor a jármű csatlakoztatva van.",
|
||||
"label": "Alapértelmezett limit"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Áram"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Áram"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "A jármű gyorstöltve lesz {0}-ra szolár üzemmódban. Ezután folytatódik a töltés a napelemes többletenergiával. Hasznos egy minimum tartományt megadni a felhősebb napokra.",
|
||||
"label": "Min. töltés %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Ezek a beállítások csak olyan járművekre elérhetőek, amiknek ismert a töltési szintje.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fázis",
|
||||
|
|
|
|||
12
i18n/it.json
12
i18n/it.json
|
|
@ -1098,9 +1098,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Attivo",
|
||||
"addRepeatingPlan": "Aggiungi piano ricorrente",
|
||||
"arrivalTab": "Arrivo",
|
||||
"day": "Giorno",
|
||||
"departureTab": "Partenza",
|
||||
"goal": "Obiettivo di ricarica",
|
||||
"modalTitle": "Piano di ricarica",
|
||||
"none": "nessuno",
|
||||
|
|
@ -1124,8 +1122,6 @@
|
|||
"strategySettings": "Impostazioni strategia",
|
||||
"time": "Ora",
|
||||
"title": "Piano",
|
||||
"titleMinSoc": "Carica minima",
|
||||
"titleTargetCharge": "Partenza",
|
||||
"unsavedChanges": "Ci sono modifiche non salvate. Vuoi salvarle adesso?",
|
||||
"update": "Applica",
|
||||
"weekdays": "Giorni"
|
||||
|
|
@ -1191,20 +1187,12 @@
|
|||
"currents": "Corrente di carica",
|
||||
"default": "default",
|
||||
"disclaimerHint": "Nota:",
|
||||
"limitSoc": {
|
||||
"description": "Limite di carica applicato quando il veicolo è connesso.",
|
||||
"label": "Limite di default"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Corrente massima"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Corrente min."
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Per le emergenze. Il veicolo viene caricato 'velocemente' al {0} da tutto il solare disponibile, e poi continua con solo il surplus solare.",
|
||||
"label": "Carica min. %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Questa opzione è solo per i veicoli dei quali si sa lo stato di carica.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fasi",
|
||||
|
|
|
|||
12
i18n/ja.json
12
i18n/ja.json
|
|
@ -1121,9 +1121,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "アクティブ",
|
||||
"addRepeatingPlan": "繰り返しプランを追加",
|
||||
"arrivalTab": "到着",
|
||||
"day": "日",
|
||||
"departureTab": "出発",
|
||||
"goal": "充電目標",
|
||||
"modalTitle": "充電プラン",
|
||||
"none": "なし",
|
||||
|
|
@ -1147,8 +1145,6 @@
|
|||
"strategySettings": "戦略設定",
|
||||
"time": "時間",
|
||||
"title": "プラン",
|
||||
"titleMinSoc": "最小充電",
|
||||
"titleTargetCharge": "出発",
|
||||
"unsavedChanges": "保存されていない変更があります。今すぐ適用しますか?",
|
||||
"update": "適用",
|
||||
"weekdays": "日"
|
||||
|
|
@ -1243,20 +1239,12 @@
|
|||
"currents": "充電電流",
|
||||
"default": "デフォルト",
|
||||
"disclaimerHint": "注:",
|
||||
"limitSoc": {
|
||||
"description": "この車両が接続されたときに使用される充電制限。",
|
||||
"label": "デフォルト制限"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "最大電流"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "最小電流"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "太陽光モード時、車両はまず „高速” で {0} まで充電されます。その後は太陽光の余剰電力で充電を継続します。日照の少ない日でも最低限の航続距離を確保するのに役立ちます。",
|
||||
"label": "最小充電 %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "これらのオプションは、バッテリー残量が取得できている車両でのみ利用可能です。",
|
||||
"phasesConfigured": {
|
||||
"label": "相数",
|
||||
|
|
|
|||
12
i18n/lb.json
12
i18n/lb.json
|
|
@ -1119,9 +1119,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiv",
|
||||
"addRepeatingPlan": "Widderhuelte Planifikatioun derbäi fügen",
|
||||
"arrivalTab": "Arrivée",
|
||||
"day": "Dag",
|
||||
"departureTab": "Depart",
|
||||
"goal": "Luedzil",
|
||||
"modalTitle": "Luedplanifikatioun",
|
||||
"none": "keng",
|
||||
|
|
@ -1145,8 +1143,6 @@
|
|||
"strategySettings": "Strategie-Astellungen",
|
||||
"time": "Zäit",
|
||||
"title": "Planifikatioun",
|
||||
"titleMinSoc": "Min. Oplueden",
|
||||
"titleTargetCharge": "Depart",
|
||||
"unsavedChanges": "Net gespäichert Ännerungen leie vir. Elo uwennen?",
|
||||
"update": "Uwennen",
|
||||
"weekdays": "Deeg"
|
||||
|
|
@ -1241,20 +1237,12 @@
|
|||
"currents": "Luedstroum",
|
||||
"default": "Standard",
|
||||
"disclaimerHint": "Notiz:",
|
||||
"limitSoc": {
|
||||
"description": "Luedzil dat benotzt gëtt, wann dëst Gefier verbonnen ass.",
|
||||
"label": "Standard Luedzil"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Stroum"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Stroum"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "D'Gefier gëtt \"séier\" op {0} am Solarmodus opgelueden. Da geet et weider mat Solariwwerschëss. Dëst ass nëtzlech fir eng Minimum-Reechwäit och wärend méi däischter Deeg ze garantéieren.",
|
||||
"label": "Min. oplueden %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Dës Optioune sinn nëmme fir Gefierer mat bekannte Luedstatioune verfügbar.",
|
||||
"phasesConfigured": {
|
||||
"label": "Phasen",
|
||||
|
|
|
|||
12
i18n/lt.json
12
i18n/lt.json
|
|
@ -1198,9 +1198,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktyvus",
|
||||
"addRepeatingPlan": "Pridėti pasikartojantį įkrovimo planą",
|
||||
"arrivalTab": "Atvykimas",
|
||||
"day": "Diena",
|
||||
"departureTab": "Išvykimas",
|
||||
"goal": "Įkrovimo tikslas",
|
||||
"modalTitle": "Įkrovimo planas",
|
||||
"none": "nėra",
|
||||
|
|
@ -1224,8 +1222,6 @@
|
|||
"strategySettings": "Strategijos pasirinkimai",
|
||||
"time": "Laikas",
|
||||
"title": "Planas",
|
||||
"titleMinSoc": "Minimali įkrova",
|
||||
"titleTargetCharge": "Išvykimas",
|
||||
"unsavedChanges": "Yra neišsaugotų pakeitimų. Išsaugoti dabar?",
|
||||
"update": "Patvirtinti",
|
||||
"weekdays": "Dienos"
|
||||
|
|
@ -1324,20 +1320,12 @@
|
|||
"currents": "Įkrovimo Srovė",
|
||||
"default": "standartiškai",
|
||||
"disclaimerHint": "Pastaba:",
|
||||
"limitSoc": {
|
||||
"description": "Įkrovimo limitas, naudojamas, prijungus šį automobilį.",
|
||||
"label": "Standartinis limitas"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Srovė"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Srovė"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Automobilis įkraunamas „Greitai” iki {0} nustatyme „Saulė”, toliau įkraunamas tik saulės energijos pertekliumi. Padeda užtikrinti minimalią įkrovą dienomis, kai mažai saulės.",
|
||||
"label": "Minimali įkrova %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Šie pasirinkimai galimi tik automobiliams su žinomu įkrovos lygiu.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fazės",
|
||||
|
|
|
|||
12
i18n/lv.json
12
i18n/lv.json
|
|
@ -1171,9 +1171,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktīvs",
|
||||
"addRepeatingPlan": "Pievienot atkārtojošu plānu",
|
||||
"arrivalTab": "Ierašanās",
|
||||
"day": "Diena",
|
||||
"departureTab": "Izbraukšana",
|
||||
"goal": "Uzlādes mērķis",
|
||||
"modalTitle": "Uzlādes plāns",
|
||||
"none": "nav",
|
||||
|
|
@ -1197,8 +1195,6 @@
|
|||
"strategySettings": "Stratēģijas iestatījumi",
|
||||
"time": "Laiks",
|
||||
"title": "Plāns",
|
||||
"titleMinSoc": "Min. uzlāde",
|
||||
"titleTargetCharge": "Izbraukšana",
|
||||
"unsavedChanges": "Ir nesaglabātas izmaiņas. Piemērot tagad?",
|
||||
"update": "Piemērot",
|
||||
"weekdays": "Dienas"
|
||||
|
|
@ -1297,20 +1293,12 @@
|
|||
"currents": "Uzlādes strāva",
|
||||
"default": "noklusējuma",
|
||||
"disclaimerHint": "Piezīme:",
|
||||
"limitSoc": {
|
||||
"description": "Uzlādes ierobežojums, kas tiek izmantots, kad šis transportlīdzeklis ir pievienots.",
|
||||
"label": "Noklusējuma ierobežojums"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maks. strāva"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. strāva"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Transportlīdzeklis tiek „ātri” uzlādēts līdz {0} saules režīmā. Pēc tam turpina ar saules enerģijas pārpalikumu. Noderīgi, lai nodrošinātu minimālu nobraukumu pat tumšākām dienām.",
|
||||
"label": "Min. uzlāde %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Šīs iespējas ir pieejamas tikai transportlīdzekļiem ar zināmu uzlādes līmeni.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fāzes",
|
||||
|
|
|
|||
12
i18n/nl.json
12
i18n/nl.json
|
|
@ -1177,9 +1177,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Actief",
|
||||
"addRepeatingPlan": "Herhalend schema toevoegen",
|
||||
"arrivalTab": "Aankomst",
|
||||
"day": "Dag",
|
||||
"departureTab": "Vertrek",
|
||||
"goal": "Laaddoel",
|
||||
"modalTitle": "Laad Plan",
|
||||
"none": "geen",
|
||||
|
|
@ -1203,8 +1201,6 @@
|
|||
"strategySettings": "Strategie-instellingen",
|
||||
"time": "Tijd",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min lading",
|
||||
"titleTargetCharge": "Vertrek",
|
||||
"unsavedChanges": "Wijzigingen nog niet opgeslagen. Nu toepassen?",
|
||||
"update": "Toepassen",
|
||||
"weekdays": "Dagen"
|
||||
|
|
@ -1302,20 +1298,12 @@
|
|||
"currents": "Laadstroom",
|
||||
"default": "standaard",
|
||||
"disclaimerHint": "Let op:",
|
||||
"limitSoc": {
|
||||
"description": "Standaard laadlimiet wanneer dit voertuig aangesloten wordt.",
|
||||
"label": "Standaard limiet"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. Stroomsterkte"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Stroomsterkte"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Het voertuig wordt „snel” opgeladen tot {0} in zonne-modus. Het laden gaat daarna verder met zonneoverschot. Nuttig om een bepaald rijbereik te garanderen, ook gedurende periodes met weinig of geen zonnestroom.",
|
||||
"label": "Min. batterij %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Deze instellingen zijn alleen beschikbaar bij voertuigen waar het batterijpercentage bekend is.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fasen",
|
||||
|
|
|
|||
12
i18n/no.json
12
i18n/no.json
|
|
@ -900,9 +900,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiv",
|
||||
"addRepeatingPlan": "Legg til gjentakende plan",
|
||||
"arrivalTab": "Ankomst",
|
||||
"day": "Dag",
|
||||
"departureTab": "Avgang",
|
||||
"goal": "Ladingsmål",
|
||||
"modalTitle": "Ladeplan",
|
||||
"none": "ingen",
|
||||
|
|
@ -926,8 +924,6 @@
|
|||
"strategySettings": "Strategiinnstillinger",
|
||||
"time": "Tid",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min.-lading",
|
||||
"titleTargetCharge": "Avgang",
|
||||
"unsavedChanges": "Det er ubehandlede endringer. Vil du bruke dem nå?",
|
||||
"update": "Søk",
|
||||
"weekdays": "Dager"
|
||||
|
|
@ -985,20 +981,12 @@
|
|||
"currents": "Ladestrøm",
|
||||
"default": "forvalg",
|
||||
"disclaimerHint": "Merknad:",
|
||||
"limitSoc": {
|
||||
"description": "Ladegrense som brukes når dette kjøretøyet er tilkoblet.",
|
||||
"label": "Standardgrense"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maks. ladestrøm"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. ladestrøm"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "For nødstilfeller. Kjøretøyet blir „raskt” ladet til {0} fra all tilgjengelig solenergi, og fortsetter deretter med bare solenergioverskuddet.",
|
||||
"label": "Min. lading%"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Disse alternativene er kun tilgjengelige for kjøretøy med kjent ladenivå.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faser",
|
||||
|
|
|
|||
12
i18n/pl.json
12
i18n/pl.json
|
|
@ -1041,9 +1041,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktywny",
|
||||
"addRepeatingPlan": "Dodaj plan powtarzalny",
|
||||
"arrivalTab": "Przyjazd",
|
||||
"day": "Dzień",
|
||||
"departureTab": "Wyjazd",
|
||||
"goal": "Cel ładowania",
|
||||
"modalTitle": "Plan ładowania",
|
||||
"none": "brak",
|
||||
|
|
@ -1067,8 +1065,6 @@
|
|||
"strategySettings": "Ustawienia strategii",
|
||||
"time": "Czas",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Minimalne ładowanie",
|
||||
"titleTargetCharge": "Wyjazd",
|
||||
"unsavedChanges": "Są niezapisane zmiany. Zastosować teraz?",
|
||||
"update": "Zastosuj",
|
||||
"weekdays": "Dni"
|
||||
|
|
@ -1160,20 +1156,12 @@
|
|||
"currents": "Prąd ładowania",
|
||||
"default": "domyślny",
|
||||
"disclaimerHint": "Uwaga:",
|
||||
"limitSoc": {
|
||||
"description": "Limit ładowania stosowany, gdy ten pojazd jest podłączony.",
|
||||
"label": "Domyślny limit"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maks. prąd"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. Prąd"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Pojazd będzie „szybko” naładowany do {0} w trybie „Słońce”. Następnie kontynuuje tylko z nadwyżką fotowoltaiczną. Przydatne, aby zapewnić minimalny zasięg nawet w pochmurne dni.",
|
||||
"label": "Minimalne naładowanie %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Opcje te są dostępne tylko dla pojazdów ze znanym poziomem naładowania.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fazy",
|
||||
|
|
|
|||
12
i18n/pt.json
12
i18n/pt.json
|
|
@ -1110,9 +1110,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Ativo",
|
||||
"addRepeatingPlan": "Adicionar planos iguais",
|
||||
"arrivalTab": "Fim",
|
||||
"day": "Dia",
|
||||
"departureTab": "Inicio",
|
||||
"goal": "Carga pretendida",
|
||||
"modalTitle": "Plano de carga",
|
||||
"none": "nenhum",
|
||||
|
|
@ -1136,8 +1134,6 @@
|
|||
"strategySettings": "Definições da estratégia",
|
||||
"time": "Hora",
|
||||
"title": "Plano",
|
||||
"titleMinSoc": "Carga mínima",
|
||||
"titleTargetCharge": "Inicio",
|
||||
"unsavedChanges": "Há alterações não salvas. Aplicar agora?",
|
||||
"update": "Aplicar",
|
||||
"weekdays": "Dias"
|
||||
|
|
@ -1203,20 +1199,12 @@
|
|||
"currents": "Corrente de carga",
|
||||
"default": "predefinido",
|
||||
"disclaimerHint": "Aviso:",
|
||||
"limitSoc": {
|
||||
"description": "limite de partilha que é usado quando este veículo está conectado.",
|
||||
"label": "Limite padrão"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Corrente de carga máxima"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Corrente de carga mínima"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "O veículo é carregado “rápido” até {0} no modo solar. A seguir, continua com excedente solar. Útil para garantir um alcance mínimo mesmo em dias com pouco sol.",
|
||||
"label": "Min. de carga %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Estas opções só estão disponíveis para veículos com nível de carregamento conhecido.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fases elétricas",
|
||||
|
|
|
|||
12
i18n/ro.json
12
i18n/ro.json
|
|
@ -1017,9 +1017,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Activ",
|
||||
"addRepeatingPlan": "Adăugați plan repetitiv",
|
||||
"arrivalTab": "Ajungere",
|
||||
"day": "Zi",
|
||||
"departureTab": "Plecare",
|
||||
"goal": "Scopul incarcarii",
|
||||
"modalTitle": "Plan de incarcare",
|
||||
"none": "nimic",
|
||||
|
|
@ -1043,8 +1041,6 @@
|
|||
"strategySettings": "Setări strategie",
|
||||
"time": "Timp",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Incarcare minima",
|
||||
"titleTargetCharge": "Plecare",
|
||||
"unsavedChanges": "Există modificări nesalvate. Doriți să le aplicați acum?",
|
||||
"update": "Aplica",
|
||||
"weekdays": "Zile"
|
||||
|
|
@ -1109,20 +1105,12 @@
|
|||
"currents": "Curent încărcare",
|
||||
"default": "default",
|
||||
"disclaimerHint": "Nota:",
|
||||
"limitSoc": {
|
||||
"description": "Limita de incarcare care e folosita cand e conectat a aceasta masina.",
|
||||
"label": "Limita default"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Amperaj maxim"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Amperaj minim"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Vehiculul primește încărcare rapida {0} din toata energia solara disponibilă, apoi continua încărcarea cu surplusul solar.",
|
||||
"label": "Min. încărcare %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Aceste optiuni sunt active doar pentru masini carora le cunoaste gradul de incarcare.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faze",
|
||||
|
|
|
|||
12
i18n/ru.json
12
i18n/ru.json
|
|
@ -850,9 +850,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Активный",
|
||||
"addRepeatingPlan": "Добавить повторяющийся план",
|
||||
"arrivalTab": "Прибытие",
|
||||
"day": "День",
|
||||
"departureTab": "Отправление",
|
||||
"goal": "Цель зарядки",
|
||||
"modalTitle": "План тарификации",
|
||||
"none": "нет",
|
||||
|
|
@ -876,8 +874,6 @@
|
|||
"strategySettings": "Настройки стратегии",
|
||||
"time": "Время",
|
||||
"title": "План",
|
||||
"titleMinSoc": "Минимальная плата",
|
||||
"titleTargetCharge": "Отправление",
|
||||
"unsavedChanges": "Есть несохраненные изменения. Применить сейчас?",
|
||||
"update": "Применить",
|
||||
"weekdays": "Дни"
|
||||
|
|
@ -935,20 +931,12 @@
|
|||
"currents": "Ток зарядки",
|
||||
"default": "по умолчанию",
|
||||
"disclaimerHint": "Заметка:",
|
||||
"limitSoc": {
|
||||
"description": "Предел зарядки, который используется при подключении этого транспортного средства.",
|
||||
"label": "Предельное значение по умолчанию"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Макс. ток"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Мин. ток"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Автомобиль «быстро» заряжается до {0} в солнечном режиме. Затем продолжает работу за счет избытка солнечной энергии. Полезно для обеспечения минимального запаса хода даже в пасмурные дни.",
|
||||
"label": "Мин. заряд %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Эти опции доступны только для автомобилей с известным уровнем заряда.",
|
||||
"phasesConfigured": {
|
||||
"label": "Фазы",
|
||||
|
|
|
|||
12
i18n/sl.json
12
i18n/sl.json
|
|
@ -876,9 +876,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktivno",
|
||||
"addRepeatingPlan": "Dodaj ponavljajoči načrt",
|
||||
"arrivalTab": "Prihod",
|
||||
"day": "Dan",
|
||||
"departureTab": "Odhod",
|
||||
"goal": "Cilj polnjenja",
|
||||
"modalTitle": "Načrt polnjenja",
|
||||
"none": "brez",
|
||||
|
|
@ -907,8 +905,6 @@
|
|||
"strategySettings": "Nastavitve strategije",
|
||||
"time": "Čas",
|
||||
"title": "Načrt",
|
||||
"titleMinSoc": "Min. polnjenje",
|
||||
"titleTargetCharge": "Odhod",
|
||||
"unsavedChanges": "Obstajajo neshranjene spremembe. Želite uporabiti zdaj?",
|
||||
"update": "Uporabi",
|
||||
"weekdays": "Dnevi"
|
||||
|
|
@ -966,20 +962,12 @@
|
|||
"currents": "Polnilni tok",
|
||||
"default": "privzeto",
|
||||
"disclaimerHint": "Opomba:",
|
||||
"limitSoc": {
|
||||
"description": "Omejitev polnjenja, ki se uporablja, ko je to vozilo povezano.",
|
||||
"label": "Privzeta omejitev"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Maks. tok"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. tok"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Za nujne primere. Vozilo se »hitro« napolni na {0} od vse razpoložljive solarne energije, nato pa nadaljuje samo s solarnim presežkom.",
|
||||
"label": "Min. polnjenje %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Te možnosti so na voljo samo za vozila z znano stopnjo napolnjenosti.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faze",
|
||||
|
|
|
|||
12
i18n/sv.json
12
i18n/sv.json
|
|
@ -1169,9 +1169,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktiv",
|
||||
"addRepeatingPlan": "Lägg till en återkommande laddplan",
|
||||
"arrivalTab": "Ankomst",
|
||||
"day": "Dag",
|
||||
"departureTab": "Avfärd",
|
||||
"goal": "Laddmål",
|
||||
"modalTitle": "Laddplan",
|
||||
"none": "ingen",
|
||||
|
|
@ -1195,8 +1193,6 @@
|
|||
"strategySettings": "Strategiinställningar",
|
||||
"time": "Tid",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Min. laddning",
|
||||
"titleTargetCharge": "Avfärd",
|
||||
"unsavedChanges": "Det finns ej sparade ändringar. Spara nu?",
|
||||
"update": "Verkställ",
|
||||
"weekdays": "Dagar"
|
||||
|
|
@ -1295,20 +1291,12 @@
|
|||
"currents": "Laddström",
|
||||
"default": "förvald",
|
||||
"disclaimerHint": "Anmärkning:",
|
||||
"limitSoc": {
|
||||
"description": "Laddbegränsning när detta fordon är anslutet.",
|
||||
"label": "Förvald laddgräns"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Max. ström"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Min. ström"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Fordonet laddas i sol-läge „snabbt” till {0} och sedan med endast solel. Användbart för att alltid ha en minsta räckvidd tillgänglig .",
|
||||
"label": "Min. laddning %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Dessa inställningar är endast tillgängliga för fordon med känd laddnivå.",
|
||||
"phasesConfigured": {
|
||||
"label": "Faser",
|
||||
|
|
|
|||
12
i18n/ta.json
12
i18n/ta.json
|
|
@ -935,9 +935,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "செயலில்",
|
||||
"addRepeatingPlan": "மறுநிகழ்வு திட்டத்தைச் சேர்",
|
||||
"arrivalTab": "வருகை",
|
||||
"day": "நாள்",
|
||||
"departureTab": "புறப்படுதல்",
|
||||
"goal": "மின்சேர்வி இலக்கு",
|
||||
"modalTitle": "மின்சேர்வி திட்டம்",
|
||||
"none": "எதுவுமில்லை",
|
||||
|
|
@ -961,8 +959,6 @@
|
|||
"strategySettings": "மூலோபாய அமைப்புகள்",
|
||||
"time": "நேரம்",
|
||||
"title": "திட்டம்",
|
||||
"titleMinSoc": "\"குறை. மின்னூட்டு\"",
|
||||
"titleTargetCharge": "புறப்படுதல்",
|
||||
"unsavedChanges": "There அரே unsaved changes. இடு now?",
|
||||
"update": "இடு",
|
||||
"weekdays": "நாட்கள்"
|
||||
|
|
@ -1027,20 +1023,12 @@
|
|||
"currents": "மின்சேர்வி மின்னோட்டம்",
|
||||
"default": "இயல்புநிலை",
|
||||
"disclaimerHint": "குறிப்பு:",
|
||||
"limitSoc": {
|
||||
"description": "இந்த வாகனம் இணைக்கப்படும்போது பயன்படுத்தப்படும் மின்சேர்வி வரம்பு.",
|
||||
"label": "இயல்புநிலை வரம்பு"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "அதிக. மின்னோட்டம்"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "குறை. மின்னோட்டம்"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "The vehicle gets „fast” charged பெறுநர் {0} in solar mode. Then continues with solar surplus. Useful பெறுநர் ensure a சிறுமம் வீச்சு இரட்டை க்கு darker days.",
|
||||
"label": "Min. மின்னூட்டு %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "These விருப்பங்கள் அரே only available க்கு vehicles with known charging level.",
|
||||
"phasesConfigured": {
|
||||
"label": "கட்டங்கள்",
|
||||
|
|
|
|||
12
i18n/tr.json
12
i18n/tr.json
|
|
@ -1143,9 +1143,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Aktif",
|
||||
"addRepeatingPlan": "“Tekrar eden plan ekle”",
|
||||
"arrivalTab": "Varış",
|
||||
"day": "Gün",
|
||||
"departureTab": "Ayrılış",
|
||||
"goal": "Doldurma hedefi",
|
||||
"modalTitle": "Doldurma Planı",
|
||||
"none": "yok",
|
||||
|
|
@ -1169,8 +1167,6 @@
|
|||
"strategySettings": "Strateji ayarları",
|
||||
"time": "Zaman",
|
||||
"title": "Plan",
|
||||
"titleMinSoc": "Asgari doldurma",
|
||||
"titleTargetCharge": "Ayrılış",
|
||||
"unsavedChanges": "Kaydedilmemiş değişiklikler var. Şimdi uygulansın mı?",
|
||||
"update": "Uygula",
|
||||
"weekdays": "Günler"
|
||||
|
|
@ -1265,20 +1261,12 @@
|
|||
"currents": "Doldurma Akımı",
|
||||
"default": "varsayılan",
|
||||
"disclaimerHint": "Not:",
|
||||
"limitSoc": {
|
||||
"description": "Araç bağlandığında kullanılan dolum sınırı.",
|
||||
"label": "Varsayılan dolum sınır"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Azami Akım"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Asgari Akım"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Araç, güneş enerjisi modunda {0} seviyesine hızlı doldurulur. Ardından güneş enerjisi fazlalığıyla devam eder. Karanlık havalarda dahi asgari bir menzil sağlamak için kullanışlıdır.",
|
||||
"label": "Asgari dolum oranı"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Bu seçenekler, sadece doluluk seviyesi bilinen araçlar için açık.",
|
||||
"phasesConfigured": {
|
||||
"label": "Fazlar",
|
||||
|
|
|
|||
12
i18n/uk.json
12
i18n/uk.json
|
|
@ -863,9 +863,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "Активний",
|
||||
"addRepeatingPlan": "Додати повторюваний план",
|
||||
"arrivalTab": "Прибуття",
|
||||
"day": "День",
|
||||
"departureTab": "Від'їзд",
|
||||
"goal": "Зарядка гол",
|
||||
"modalTitle": "Тарифний план",
|
||||
"none": "немає",
|
||||
|
|
@ -889,8 +887,6 @@
|
|||
"strategySettings": "Налаштування стратегії",
|
||||
"time": "Час",
|
||||
"title": "План",
|
||||
"titleMinSoc": "Мінімальна плата",
|
||||
"titleTargetCharge": "Від'їзд",
|
||||
"unsavedChanges": "Є незбережені зміни. Застосувати зараз?",
|
||||
"update": "Застосувати",
|
||||
"weekdays": "Днів"
|
||||
|
|
@ -948,20 +944,12 @@
|
|||
"currents": "Струм Зарядки",
|
||||
"default": "за замовчуванням",
|
||||
"disclaimerHint": "Примітка:",
|
||||
"limitSoc": {
|
||||
"description": "Ліміт заряджання, який використовується, коли цей автомобіль під’єднано.",
|
||||
"label": "Ліміт за замовчуванням"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "Макс. струм"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "Мін. струм"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "Для надзвичайних ситуацій. Транспортний засіб «швидко» заряджається до {0} від усіх доступних сонячних батарей, а потім продовжує працювати лише з сонячним надлишком.",
|
||||
"label": "Мін. заряд %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "Ці параметри доступні лише для автомобілів із відомим рівнем заряду.",
|
||||
"phasesConfigured": {
|
||||
"label": "Фази",
|
||||
|
|
|
|||
|
|
@ -614,9 +614,7 @@
|
|||
"chargingPlan": {
|
||||
"active": "启用",
|
||||
"addRepeatingPlan": "添加重复计划",
|
||||
"arrivalTab": "抵达",
|
||||
"day": "日期",
|
||||
"departureTab": "离开",
|
||||
"goal": "充电目标",
|
||||
"modalTitle": "充电计划",
|
||||
"none": "无",
|
||||
|
|
@ -633,8 +631,6 @@
|
|||
"selectAll": "全选",
|
||||
"time": "时间",
|
||||
"title": "计划",
|
||||
"titleMinSoc": "最小充电量",
|
||||
"titleTargetCharge": "出发时间",
|
||||
"unsavedChanges": "有未保存的更改。是否立即应用?",
|
||||
"update": "应用",
|
||||
"weekdays": "天数"
|
||||
|
|
@ -688,20 +684,12 @@
|
|||
"currents": "充电电流",
|
||||
"default": "默认",
|
||||
"disclaimerHint": "注意:",
|
||||
"limitSoc": {
|
||||
"description": "连接此车辆时使用的充电上限。",
|
||||
"label": "默认上限"
|
||||
},
|
||||
"maxCurrent": {
|
||||
"label": "最大电流"
|
||||
},
|
||||
"minCurrent": {
|
||||
"label": "最小电流"
|
||||
},
|
||||
"minSoc": {
|
||||
"description": "在太阳能模式下,车辆将“快速”充电至 {0},然后继续使用太阳能余电充电。这有助于确保即使在光照不足的日子也能达到最低续航里程。",
|
||||
"label": "最小电量 %"
|
||||
},
|
||||
"onlyForSocBasedCharging": "这些选项仅适用于已知充电量的车辆。",
|
||||
"phasesConfigured": {
|
||||
"label": "相数",
|
||||
|
|
|
|||
|
|
@ -181,11 +181,14 @@ test.describe("vehicles", async () => {
|
|||
/active/
|
||||
);
|
||||
|
||||
await lp.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
const moreTab = page.getByTestId("tab-more");
|
||||
await moreTab.click();
|
||||
await moreTab.getByRole("button", { name: "Vehicles" }).click();
|
||||
const modal = page.getByTestId("vehicle-settings-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(modal.getByRole("combobox", { name: "Default mode" })).toHaveValue("now");
|
||||
await expect(
|
||||
modal.getByRole("group", { name: "Grey Car" }).getByRole("combobox", { name: "Default mode" })
|
||||
).toHaveValue("now");
|
||||
});
|
||||
|
||||
test("save and restore rfid identifiers", async ({ page }) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { start, stop, baseUrl } from "./evcc";
|
||||
import { expectModalVisible } from "./utils";
|
||||
|
||||
test.use({ baseURL: baseUrl() });
|
||||
|
||||
|
|
@ -20,16 +21,18 @@ test.describe("vehicle startup error (using failing Tesla API)", async () => {
|
|||
await expect(page.getByTestId("vehicle-name")).toHaveText("Broken Tesla");
|
||||
await expect(page.getByTestId("vehicle-not-reachable-icon")).toBeVisible();
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(modal.getByRole("combobox", { name: "Min. charge %" })).toBeEnabled();
|
||||
const moreTab = page.getByTestId("tab-more");
|
||||
await moreTab.click();
|
||||
await moreTab.getByRole("button", { name: "Vehicles" }).click();
|
||||
const modal = page.getByTestId("vehicle-settings-modal");
|
||||
await expectModalVisible(modal);
|
||||
await expect(modal.getByRole("combobox", { name: "Minimum charge" })).toBeEnabled();
|
||||
await expect(modal.getByRole("combobox", { name: "Default limit" })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("guest vehicle: normal title and no icon", async ({ page }) => {
|
||||
// switch to offline vehicle
|
||||
await page.getByTestId("change-vehicle").locator("select").selectOption("Guest vehicle");
|
||||
await page.getByRole("combobox", { name: "Change vehicle" }).selectOption("Guest vehicle");
|
||||
|
||||
await expect(page.getByTestId("vehicle-name")).toHaveText("Guest vehicle");
|
||||
await expect(page.getByTestId("vehicle-not-reachable-icon")).not.toBeVisible();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { test, expect, type Page } from "@playwright/test";
|
||||
import { test, expect, type Locator, type Page } from "@playwright/test";
|
||||
import { start, stop, restart, baseUrl } from "./evcc";
|
||||
import { expectModalVisible, expectModalHidden } from "./utils";
|
||||
import {
|
||||
|
|
@ -32,45 +32,109 @@ test.afterEach(async () => {
|
|||
await stop();
|
||||
});
|
||||
|
||||
async function switchVehicle(page: Page, title: string) {
|
||||
await page.getByRole("combobox", { name: "Change vehicle" }).selectOption(title);
|
||||
await expect(page.getByTestId("vehicle-name")).toHaveText(title);
|
||||
}
|
||||
|
||||
async function openVehicleSettings(page: Page) {
|
||||
const moreTab = page.getByTestId("tab-more");
|
||||
await moreTab.click();
|
||||
await moreTab.getByRole("button", { name: "Vehicles" }).click();
|
||||
const modal = page.getByTestId("vehicle-settings-modal");
|
||||
await expectModalVisible(modal);
|
||||
return modal;
|
||||
}
|
||||
|
||||
function vehicleRow(modal: Locator, title: string) {
|
||||
return modal.getByRole("group", { name: title });
|
||||
}
|
||||
|
||||
async function closeModal(page: Page) {
|
||||
const modal = page.getByTestId("vehicle-settings-modal");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
}
|
||||
|
||||
test.describe("overview", async () => {
|
||||
test("all configured vehicles listed", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
const modal = await openVehicleSettings(page);
|
||||
await expect(modal.getByRole("group")).toHaveCount(2);
|
||||
await expect(vehicleRow(modal, "blauer e-Golf")).toBeVisible();
|
||||
await expect(vehicleRow(modal, "grüner Honda e")).toBeVisible();
|
||||
await expect(modal).not.toContainText("Guest vehicle");
|
||||
});
|
||||
|
||||
test("connected indicator only on connected vehicle", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
const modal = await openVehicleSettings(page);
|
||||
await expect(
|
||||
vehicleRow(modal, "blauer e-Golf").getByText("connected to Carport")
|
||||
).toBeVisible();
|
||||
await expect(vehicleRow(modal, "grüner Honda e").getByText("not connected")).toBeVisible();
|
||||
});
|
||||
|
||||
test("per-vehicle values are independent", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
const modal = await openVehicleSettings(page);
|
||||
const golfMode = vehicleRow(modal, "blauer e-Golf").getByRole("combobox", {
|
||||
name: "Default mode",
|
||||
});
|
||||
const hondaMode = vehicleRow(modal, "grüner Honda e").getByRole("combobox", {
|
||||
name: "Default mode",
|
||||
});
|
||||
|
||||
await hondaMode.selectOption("Fast");
|
||||
await expect(hondaMode).toHaveValue("now");
|
||||
await expect(golfMode).toHaveValue("");
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("minSoc", async () => {
|
||||
test("apply and restart", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
|
||||
await expect(modal).toContainText("charged to x in solar mode");
|
||||
await modal.getByRole("combobox", { name: "Min. charge %" }).selectOption("20%");
|
||||
await expect(modal).toContainText("charged to 20% in solar mode");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
const modal = await openVehicleSettings(page);
|
||||
const minSoc = vehicleRow(modal, "blauer e-Golf").getByRole("combobox", {
|
||||
name: "Minimum charge",
|
||||
});
|
||||
await minSoc.selectOption("20");
|
||||
await expect(minSoc).toHaveValue("20");
|
||||
await closeModal(page);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await restart(simulatorConfig());
|
||||
await page.reload();
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(page.getByText("charged to 20% in solar mode")).toBeVisible();
|
||||
const modalAfterRestart = await openVehicleSettings(page);
|
||||
await expect(
|
||||
vehicleRow(modalAfterRestart, "blauer e-Golf").getByRole("combobox", {
|
||||
name: "Minimum charge",
|
||||
})
|
||||
).toHaveValue("20");
|
||||
});
|
||||
|
||||
test("show minsoc indicator when minsoc is active", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByTestId("charging-plan")).toContainText("Plan");
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("50%");
|
||||
await page.getByRole("button", { name: "Close" }).click();
|
||||
const modal = await openVehicleSettings(page);
|
||||
const minSoc = vehicleRow(modal, "blauer e-Golf").getByRole("combobox", {
|
||||
name: "Minimum charge",
|
||||
});
|
||||
await minSoc.selectOption("50");
|
||||
await closeModal(page);
|
||||
|
||||
await expect(page.getByTestId("vehicle-status-minsoc")).toBeVisible();
|
||||
await expect(page.getByTestId("vehicle-status-minsoc")).toHaveText("50%");
|
||||
|
||||
await page.getByTestId("vehicle-status-minsoc").click();
|
||||
await page.getByRole("combobox", { name: "Min. charge %" }).selectOption("---");
|
||||
await page.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalVisible(modal);
|
||||
await minSoc.selectOption("0");
|
||||
await closeModal(page);
|
||||
|
||||
await expect(page.getByTestId("vehicle-status-minsoc")).not.toBeVisible();
|
||||
});
|
||||
|
|
@ -80,26 +144,25 @@ test.describe("limitSoc", async () => {
|
|||
test("apply and restart", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
|
||||
await modal.getByRole("combobox", { name: "Default limit" }).selectOption("80%");
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
const modal = await openVehicleSettings(page);
|
||||
await vehicleRow(modal, "blauer e-Golf")
|
||||
.getByRole("combobox", { name: "Default limit" })
|
||||
.selectOption("80");
|
||||
await closeModal(page);
|
||||
await expect(page.getByTestId("limit-soc-value")).toContainText("80%");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(500); // bad practice but may help here :/
|
||||
|
||||
await restart(simulatorConfig());
|
||||
await page.reload();
|
||||
|
||||
await expect(page.getByTestId("limit-soc-value")).toContainText("80%");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(page.getByRole("combobox", { name: "Default limit" })).toHaveValue("80");
|
||||
const modalAfterRestart = await openVehicleSettings(page);
|
||||
await expect(
|
||||
vehicleRow(modalAfterRestart, "blauer e-Golf").getByRole("combobox", {
|
||||
name: "Default limit",
|
||||
})
|
||||
).toHaveValue("80");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -107,69 +170,64 @@ test.describe("minSoc and limitSoc", async () => {
|
|||
test("hidden for offline vehicles", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
// switch to offline vehicle
|
||||
await page.getByTestId("change-vehicle").locator("select").selectOption("grüner Honda e");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(page.getByRole("combobox", { name: "Min. charge %" })).not.toBeVisible();
|
||||
await expect(page.getByRole("combobox", { name: "Default limit" })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("no tabs for guest vehicles", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
// switch to guest vehicle (no known vehicle)
|
||||
await page.getByTestId("change-vehicle").locator("select").selectOption("Guest vehicle");
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await expect(modal.getByRole("link", { name: "Departure" })).toHaveCount(0);
|
||||
await expect(modal.getByRole("link", { name: "Arrival" })).toHaveCount(0);
|
||||
const modal = await openVehicleSettings(page);
|
||||
const honda = vehicleRow(modal, "grüner Honda e");
|
||||
await expect(honda.getByRole("combobox", { name: "Default mode" })).toBeVisible();
|
||||
await expect(honda.getByRole("combobox", { name: "Minimum charge" })).not.toBeVisible();
|
||||
await expect(honda.getByRole("combobox", { name: "Default limit" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("default mode", async () => {
|
||||
async function setDefaultMode(page: Page, option: string) {
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
const modal = page.getByTestId("charging-plan-modal");
|
||||
await expectModalVisible(modal);
|
||||
await modal.getByRole("link", { name: "Arrival" }).click();
|
||||
await modal.getByRole("combobox", { name: "Default mode" }).selectOption(option);
|
||||
await modal.getByRole("button", { name: "Close" }).click();
|
||||
await expectModalHidden(modal);
|
||||
async function setDefaultMode(page: Page, title: string, option: string) {
|
||||
const modal = await openVehicleSettings(page);
|
||||
await vehicleRow(modal, title)
|
||||
.getByRole("combobox", { name: "Default mode" })
|
||||
.selectOption(option);
|
||||
await closeModal(page);
|
||||
}
|
||||
|
||||
test("apply and restart", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await setDefaultMode(page, "Fast");
|
||||
await setDefaultMode(page, "blauer e-Golf", "Fast");
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await restart(simulatorConfig());
|
||||
await page.reload();
|
||||
|
||||
await page.getByTestId("charging-plan").getByRole("button", { name: "none" }).click();
|
||||
await page.getByRole("link", { name: "Arrival" }).click();
|
||||
await expect(page.getByRole("combobox", { name: "Default mode" })).toHaveValue("now");
|
||||
const modal = await openVehicleSettings(page);
|
||||
await expect(
|
||||
vehicleRow(modal, "blauer e-Golf").getByRole("combobox", { name: "Default mode" })
|
||||
).toHaveValue("now");
|
||||
});
|
||||
|
||||
test("applied on vehicle switch", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const mode = page.getByTestId("loadpoint").first().getByTestId("mode");
|
||||
const changeVehicle = page.getByTestId("change-vehicle").locator("select");
|
||||
|
||||
// per-vehicle default modes
|
||||
await setDefaultMode(page, "Solar");
|
||||
await changeVehicle.selectOption("grüner Honda e");
|
||||
await setDefaultMode(page, "Fast");
|
||||
await setDefaultMode(page, "blauer e-Golf", "Solar");
|
||||
await setDefaultMode(page, "grüner Honda e", "Fast");
|
||||
|
||||
// switching vehicles applies the vehicle's default mode
|
||||
await changeVehicle.selectOption("blauer e-Golf");
|
||||
await expect(mode.getByRole("button", { name: "Solar", exact: true })).toHaveClass(/active/);
|
||||
|
||||
await changeVehicle.selectOption("grüner Honda e");
|
||||
await switchVehicle(page, "grüner Honda e");
|
||||
await expect(mode.getByRole("button", { name: "Fast" })).toHaveClass(/active/);
|
||||
|
||||
await switchVehicle(page, "blauer e-Golf");
|
||||
await expect(mode.getByRole("button", { name: "Solar", exact: true })).toHaveClass(/active/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("menu entry", async () => {
|
||||
test("hidden when no vehicles are configured", async ({ page }) => {
|
||||
await stop();
|
||||
await start("basics.evcc.yaml");
|
||||
|
||||
await page.goto("/");
|
||||
const moreTab = page.getByTestId("tab-more");
|
||||
await moreTab.click();
|
||||
await expect(moreTab.getByRole("button", { name: "User Interface" })).toBeVisible();
|
||||
await expect(moreTab.getByRole("button", { name: "Vehicles" })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue