UI: show inactive phases as dots in phase indicator (#33173)
This commit is contained in:
parent
fb120252a0
commit
8ccd72e141
10 changed files with 187 additions and 98 deletions
|
|
@ -2,6 +2,7 @@ import { type Preview, setup } from "@storybook/vue3";
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import smoothscroll from "smoothscroll-polyfill";
|
import smoothscroll from "smoothscroll-polyfill";
|
||||||
import setupI18n from "../assets/js/i18n";
|
import setupI18n from "../assets/js/i18n";
|
||||||
|
import tooltip from "../assets/js/directives/tooltip";
|
||||||
import "../assets/css/app.css";
|
import "../assets/css/app.css";
|
||||||
import { watchThemeChanges } from "../assets/js/theme";
|
import { watchThemeChanges } from "../assets/js/theme";
|
||||||
|
|
||||||
|
|
@ -35,6 +36,7 @@ export default {
|
||||||
|
|
||||||
setup((app) => {
|
setup((app) => {
|
||||||
app.use(setupI18n());
|
app.use(setupI18n());
|
||||||
|
app.directive("tooltip", tooltip);
|
||||||
|
|
||||||
// Mock router-link for Storybook
|
// Mock router-link for Storybook
|
||||||
app.component("router-link", {
|
app.component("router-link", {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
--evcc-gray: var(--bs-gray-medium);
|
--evcc-gray: var(--bs-gray-medium);
|
||||||
--evcc-gray-50: #93949e80;
|
--evcc-gray-50: #93949e80;
|
||||||
--evcc-gray-25: #93949e40;
|
--evcc-gray-25: #93949e40;
|
||||||
|
--evcc-gray-15: #93949e26;
|
||||||
--evcc-gray-10: #93949e20;
|
--evcc-gray-10: #93949e20;
|
||||||
--evcc-accent1: var(--evcc-dark-yellow);
|
--evcc-accent1: var(--evcc-dark-yellow);
|
||||||
--evcc-accent2: var(--evcc-darker-green);
|
--evcc-accent2: var(--evcc-darker-green);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { VueHeadMixin, createHead } from "@unhead/vue/client";
|
||||||
import App from "./views/App.vue";
|
import App from "./views/App.vue";
|
||||||
import setupRouter from "./router.ts";
|
import setupRouter from "./router.ts";
|
||||||
import setupI18n from "./i18n.ts";
|
import setupI18n from "./i18n.ts";
|
||||||
|
import tooltip from "./directives/tooltip.ts";
|
||||||
import { watchThemeChanges } from "./theme.ts";
|
import { watchThemeChanges } from "./theme.ts";
|
||||||
import { applyUrlSettings } from "./urlSettings.ts";
|
import { applyUrlSettings } from "./urlSettings.ts";
|
||||||
import { appDetection, sendToApp } from "./utils/native";
|
import { appDetection, sendToApp } from "./utils/native";
|
||||||
|
|
@ -99,6 +100,7 @@ app.use(i18n);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(head);
|
app.use(head);
|
||||||
app.mixin(VueHeadMixin);
|
app.mixin(VueHeadMixin);
|
||||||
|
app.directive("tooltip", tooltip);
|
||||||
window.app = app.mount("#app");
|
window.app = app.mount("#app");
|
||||||
|
|
||||||
watchThemeChanges();
|
watchThemeChanges();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
ref="root"
|
v-tooltip="tooltip"
|
||||||
class="d-flex align-items-center gap-2 text-nowrap evcc-gray"
|
class="d-flex align-items-center gap-2 text-nowrap evcc-gray"
|
||||||
:role="tooltip ? 'img' : undefined"
|
:role="tooltip ? 'img' : undefined"
|
||||||
:aria-label="tooltip || undefined"
|
:aria-label="tooltip || undefined"
|
||||||
:title="tooltip || undefined"
|
|
||||||
:data-bs-toggle="tooltip ? 'tooltip' : undefined"
|
|
||||||
>
|
>
|
||||||
<span class="d-inline-block rounded-circle status-dot" :class="dotClass"></span>
|
<span class="d-inline-block rounded-circle status-dot" :class="dotClass"></span>
|
||||||
<slot />
|
<slot />
|
||||||
|
|
@ -14,7 +12,6 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, type PropType } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
import Tooltip from "bootstrap/js/dist/tooltip";
|
|
||||||
|
|
||||||
type Variant = "success" | "warning" | "danger" | "muted";
|
type Variant = "success" | "warning" | "danger" | "muted";
|
||||||
|
|
||||||
|
|
@ -24,9 +21,6 @@ export default defineComponent({
|
||||||
variant: { type: String as PropType<Variant>, default: "muted" },
|
variant: { type: String as PropType<Variant>, default: "muted" },
|
||||||
tooltip: { type: String, default: "" },
|
tooltip: { type: String, default: "" },
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return { tooltipInstance: null as Tooltip | null };
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
dotClass(): string {
|
dotClass(): string {
|
||||||
switch (this.variant) {
|
switch (this.variant) {
|
||||||
|
|
@ -41,30 +35,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
tooltip() {
|
|
||||||
this.initTooltip();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initTooltip();
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
this.tooltipInstance?.dispose();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initTooltip() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.tooltipInstance?.dispose();
|
|
||||||
this.tooltipInstance = null;
|
|
||||||
const el = this.$refs["root"] as Element | undefined;
|
|
||||||
if (el && this.tooltip) {
|
|
||||||
// explicit title: bootstrap clears the attr, so :title alone goes stale
|
|
||||||
this.tooltipInstance = new Tooltip(el, { title: this.tooltip });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import Loadpoints from "./Loadpoints.vue";
|
import Loadpoints from "./Loadpoints.vue";
|
||||||
import type { Meta, StoryFn } from "@storybook/vue3";
|
import type { Meta, StoryFn } from "@storybook/vue3";
|
||||||
import { CURRENCY, SMART_COST_TYPE } from "@/types/evcc";
|
import { CHARGE_MODE, CURRENCY, SMART_COST_TYPE } from "@/types/evcc";
|
||||||
|
|
||||||
// Create LoadpointCompact structure for the Loadpoints component
|
// Create LoadpointCompact structure for the Loadpoints component
|
||||||
const createLoadpoint = (opts: any = {}) => {
|
const createLoadpoint = (opts: any = {}) => {
|
||||||
|
|
@ -117,11 +117,18 @@ TwoLoadpoints.args = {
|
||||||
index: 0,
|
index: 0,
|
||||||
vehicleName: "vehicle_3",
|
vehicleName: "vehicle_3",
|
||||||
charging: true,
|
charging: true,
|
||||||
chargePower: 8050,
|
chargePower: 1380,
|
||||||
power: 8050,
|
power: 1380,
|
||||||
connected: true,
|
connected: true,
|
||||||
soc: 66,
|
soc: 66,
|
||||||
vehicleSoc: 66,
|
vehicleSoc: 66,
|
||||||
|
mode: CHARGE_MODE.PV,
|
||||||
|
phasesConfigured: 3,
|
||||||
|
phasesActive: 1,
|
||||||
|
offeredCurrent: 9,
|
||||||
|
chargeCurrents: [6, 0, 0],
|
||||||
|
minCurrent: 6,
|
||||||
|
maxCurrent: 16,
|
||||||
}),
|
}),
|
||||||
createLoadpoint({
|
createLoadpoint({
|
||||||
title: "Charging point with a very very very long title that tests layout compatibility!!!",
|
title: "Charging point with a very very very long title that tests layout compatibility!!!",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import Phases from "./Phases.vue";
|
import Phases from "./Phases.vue";
|
||||||
import type { Meta, StoryFn } from "@storybook/vue3";
|
import type { Meta, StoryFn } from "@storybook/vue3";
|
||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Loadpoints/Phases",
|
title: "Loadpoints/Phases",
|
||||||
|
|
@ -9,6 +10,7 @@ export default {
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
phasesActive: { control: { type: "number" } },
|
phasesActive: { control: { type: "number" } },
|
||||||
|
phasesConfigured: { control: { type: "number" } },
|
||||||
minCurrent: { control: { type: "number" } },
|
minCurrent: { control: { type: "number" } },
|
||||||
maxCurrent: { control: { type: "number" } },
|
maxCurrent: { control: { type: "number" } },
|
||||||
offeredCurrent: { control: { type: "number" } },
|
offeredCurrent: { control: { type: "number" } },
|
||||||
|
|
@ -24,72 +26,117 @@ const Template: StoryFn<typeof Phases> = (args) => ({
|
||||||
template: '<Phases v-bind="args" />',
|
template: '<Phases v-bind="args" />',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OnePhase = Template.bind({});
|
const base = {
|
||||||
OnePhase.args = {
|
phasesConfigured: 3,
|
||||||
phasesActive: 1,
|
|
||||||
minCurrent: 6,
|
minCurrent: 6,
|
||||||
maxCurrent: 16,
|
maxCurrent: 16,
|
||||||
offeredCurrent: 8,
|
offeredCurrent: 8,
|
||||||
chargeCurrents: undefined,
|
chargeCurrents: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TwoPhase = Template.bind({});
|
// --- expected phases only (no per-phase measurement) ---
|
||||||
TwoPhase.args = {
|
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ThreePhase = Template.bind({});
|
export const NoData = Template.bind({});
|
||||||
ThreePhase.args = {
|
NoData.args = { ...base, phasesActive: undefined, offeredCurrent: 0 };
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RealCurrents = Template.bind({});
|
export const Expected1p = Template.bind({});
|
||||||
RealCurrents.args = {
|
Expected1p.args = { ...base, phasesActive: 1 };
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 3,
|
|
||||||
offeredCurrent: 13,
|
|
||||||
chargeCurrents: [11, 9, 12],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OnePhaseMoreAvailable = Template.bind({});
|
export const Expected2p = Template.bind({});
|
||||||
OnePhaseMoreAvailable.args = {
|
Expected2p.args = { ...base, phasesActive: 2 };
|
||||||
...OnePhase.args,
|
|
||||||
offeredCurrent: 12,
|
|
||||||
chargeCurrents: [6, 0.2, 0],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TwoPhasesActive = Template.bind({});
|
export const Expected3p = Template.bind({});
|
||||||
TwoPhasesActive.args = {
|
Expected3p.args = { ...base, phasesActive: 3 };
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 2,
|
|
||||||
offeredCurrent: 16,
|
|
||||||
chargeCurrents: [16, 16, 0.3],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AsymetricPhases = Template.bind({});
|
// --- measured per-phase currents ---
|
||||||
AsymetricPhases.args = {
|
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 2,
|
|
||||||
offeredCurrent: 16,
|
|
||||||
chargeCurrents: [8, 0.9, 14],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OnlySecondPhase = Template.bind({});
|
export const Measured1p = Template.bind({});
|
||||||
OnlySecondPhase.args = {
|
Measured1p.args = { ...base, phasesActive: 1, offeredCurrent: 12, chargeCurrents: [6, 0.2, 0] };
|
||||||
...OnePhase.args,
|
|
||||||
|
export const Measured2p = Template.bind({});
|
||||||
|
Measured2p.args = { ...base, phasesActive: 2, offeredCurrent: 16, chargeCurrents: [16, 16, 0.3] };
|
||||||
|
|
||||||
|
export const Measured3p = Template.bind({});
|
||||||
|
Measured3p.args = { ...base, phasesActive: 3, offeredCurrent: 13, chargeCurrents: [11, 9, 12] };
|
||||||
|
|
||||||
|
export const Asymmetric = Template.bind({});
|
||||||
|
Asymmetric.args = { ...base, phasesActive: 2, offeredCurrent: 16, chargeCurrents: [8, 0.9, 14] };
|
||||||
|
|
||||||
|
export const OnlyL2 = Template.bind({});
|
||||||
|
OnlyL2.args = { ...base, phasesActive: 1, offeredCurrent: 13, chargeCurrents: [0, 13, 0] };
|
||||||
|
|
||||||
|
export const MainlyL3 = Template.bind({});
|
||||||
|
MainlyL3.args = {
|
||||||
|
...base,
|
||||||
phasesActive: 1,
|
phasesActive: 1,
|
||||||
offeredCurrent: 13,
|
offeredCurrent: 20,
|
||||||
chargeCurrents: [0, 13, 0],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MainlyThirdPhase = Template.bind({});
|
|
||||||
MainlyThirdPhase.args = {
|
|
||||||
...OnePhase.args,
|
|
||||||
phasesActive: 1,
|
|
||||||
offeredCurrent: 10,
|
|
||||||
chargeCurrents: [0.007, 0.009, 5.945],
|
|
||||||
minCurrent: 6,
|
|
||||||
maxCurrent: 20,
|
maxCurrent: 20,
|
||||||
|
chargeCurrents: [0.007, 0.009, 5.945],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 1p device but 3 phases measured: configuration error, warning color + tooltip
|
||||||
|
export const Mismatch1p = Template.bind({});
|
||||||
|
Mismatch1p.args = {
|
||||||
|
...base,
|
||||||
|
phasesConfigured: 1,
|
||||||
|
phasesActive: 3,
|
||||||
|
offeredCurrent: 10,
|
||||||
|
chargeCurrents: [10, 10, 10],
|
||||||
|
};
|
||||||
|
|
||||||
|
// cycles 1p → 3p → 1p every few seconds to inspect the transition
|
||||||
|
export const Switching: StoryFn<typeof Phases> = (args) => ({
|
||||||
|
components: { Phases },
|
||||||
|
setup() {
|
||||||
|
const phasesActive = ref(1);
|
||||||
|
let timer: ReturnType<typeof setInterval>;
|
||||||
|
onMounted(() => {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
phasesActive.value = phasesActive.value === 1 ? 3 : 1;
|
||||||
|
}, 3000);
|
||||||
|
});
|
||||||
|
onUnmounted(() => clearInterval(timer));
|
||||||
|
return { args, phasesActive };
|
||||||
|
},
|
||||||
|
template: '<Phases v-bind="args" :phasesActive="phasesActive" />',
|
||||||
|
});
|
||||||
|
Switching.args = { ...base, phasesConfigured: 0, offeredCurrent: 12 };
|
||||||
|
|
||||||
|
// --- overview: rows = scenario, columns = device phase configuration ---
|
||||||
|
|
||||||
|
const scenarios = [
|
||||||
|
{ name: "NoData", args: NoData.args },
|
||||||
|
{ name: "Expected1p", args: Expected1p.args },
|
||||||
|
{ name: "Expected2p", args: Expected2p.args },
|
||||||
|
{ name: "Expected3p", args: Expected3p.args },
|
||||||
|
{ name: "Measured1p", args: Measured1p.args },
|
||||||
|
{ name: "Measured2p", args: Measured2p.args },
|
||||||
|
{ name: "Measured3p", args: Measured3p.args },
|
||||||
|
{ name: "Asymmetric", args: Asymmetric.args },
|
||||||
|
{ name: "OnlyL2", args: OnlyL2.args },
|
||||||
|
{ name: "MainlyL3", args: MainlyL3.args },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 1p3p auto (0) behaves like 3p
|
||||||
|
const configs = [
|
||||||
|
{ name: "1p device", phasesConfigured: 1 },
|
||||||
|
{ name: "3p/auto", phasesConfigured: 3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Overview: StoryFn<typeof Phases> = () => ({
|
||||||
|
components: { Phases },
|
||||||
|
setup() {
|
||||||
|
return { scenarios, configs };
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="display: grid; grid-template-columns: 140px repeat(2, 120px); gap: 0.75rem 1.5rem; align-items: center; padding: 1rem; background: var(--evcc-box); border-radius: 0.5rem; color: var(--evcc-default-text); font-size: 0.85rem;">
|
||||||
|
<div></div>
|
||||||
|
<div v-for="c in configs" :key="c.name" style="opacity: 0.7;">{{ c.name }}</div>
|
||||||
|
<template v-for="s in scenarios" :key="s.name">
|
||||||
|
<div style="opacity: 0.7;">{{ s.name }}</div>
|
||||||
|
<Phases v-for="c in configs" :key="c.name" v-bind="{ ...s.args, phasesConfigured: c.phasesConfigured }" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
Overview.parameters = { controls: { disable: true } };
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="`phases d-flex justify-content-between`">
|
<div
|
||||||
|
v-tooltip="phasesMismatch ? $t('main.loadpoint.phasesMismatch') : undefined"
|
||||||
|
class="phases d-flex"
|
||||||
|
:class="{ 'phases-warning': phasesMismatch }"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="num in [1, 2, 3]"
|
v-for="num in [1, 2, 3]"
|
||||||
:key="num"
|
:key="num"
|
||||||
class="phase me-1"
|
class="phase me-1"
|
||||||
:class="{ 'phase-inactive': !isPhaseActive(num) }"
|
:class="{
|
||||||
|
'phase-inactive': !isPhaseActive(num),
|
||||||
|
'phase-hidden': !isPhaseActive(num) && (maxPhases === 1 || num > maxPhases),
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div class="target" :style="{ width: `${targetWidth()}%` }"></div>
|
<div
|
||||||
|
v-show="targetWidth() > 0"
|
||||||
|
class="target"
|
||||||
|
:style="{ width: `${targetWidth()}%` }"
|
||||||
|
></div>
|
||||||
<div class="real" :style="{ width: `${realWidth(num)}%` }"></div>
|
<div class="real" :style="{ width: `${realWidth(num)}%` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -23,6 +34,7 @@ export default defineComponent({
|
||||||
offeredCurrent: { type: Number, default: 0 },
|
offeredCurrent: { type: Number, default: 0 },
|
||||||
chargeCurrents: { type: Array as PropType<number[]> },
|
chargeCurrents: { type: Array as PropType<number[]> },
|
||||||
phasesActive: { type: Number as PropType<PHASES> },
|
phasesActive: { type: Number as PropType<PHASES> },
|
||||||
|
phasesConfigured: { type: Number, default: 3 },
|
||||||
minCurrent: { type: Number, default: 6 },
|
minCurrent: { type: Number, default: 6 },
|
||||||
maxCurrent: { type: Number, default: 16 },
|
maxCurrent: { type: Number, default: 16 },
|
||||||
},
|
},
|
||||||
|
|
@ -31,9 +43,17 @@ export default defineComponent({
|
||||||
if (!this.chargeCurrents) return false;
|
if (!this.chargeCurrents) return false;
|
||||||
return this.chargeCurrents.filter((c) => c >= MIN_ACTIVE_CURRENT).length > 0;
|
return this.chargeCurrents.filter((c) => c >= MIN_ACTIVE_CURRENT).length > 0;
|
||||||
},
|
},
|
||||||
|
maxPhases() {
|
||||||
|
// 0 = automatic 1p3p switching
|
||||||
|
return this.phasesConfigured || 3;
|
||||||
|
},
|
||||||
|
phasesMismatch() {
|
||||||
|
return [1, 2, 3].filter((num) => this.isPhaseActive(num)).length > this.maxPhases;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
targetWidth() {
|
targetWidth() {
|
||||||
|
if (this.offeredCurrent <= 0) return 0;
|
||||||
const current = Math.min(
|
const current = Math.min(
|
||||||
Math.max(this.minCurrent, this.offeredCurrent),
|
Math.max(this.minCurrent, this.offeredCurrent),
|
||||||
this.maxCurrent
|
this.maxCurrent
|
||||||
|
|
@ -52,7 +72,8 @@ export default defineComponent({
|
||||||
const current = this.chargeCurrents[num - 1];
|
const current = this.chargeCurrents[num - 1];
|
||||||
return current !== undefined && current >= MIN_ACTIVE_CURRENT;
|
return current !== undefined && current >= MIN_ACTIVE_CURRENT;
|
||||||
}
|
}
|
||||||
return num <= (this.phasesActive || 0);
|
// unknown active phases: assume all
|
||||||
|
return num <= (this.phasesActive || this.maxPhases);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -63,15 +84,14 @@ export default defineComponent({
|
||||||
width: 73px;
|
width: 73px;
|
||||||
}
|
}
|
||||||
.phase {
|
.phase {
|
||||||
background-color: var(--bs-gray-bright);
|
background-color: var(--evcc-gray-15);
|
||||||
height: 4px;
|
height: 4px;
|
||||||
flex-grow: 1;
|
flex: 1 1 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 1px;
|
border-radius: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-basis: 100%;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition-property: flex-basis, margin, opacity;
|
transition-property: flex-basis, flex-grow, margin, opacity;
|
||||||
transition-duration: var(--evcc-transition-slow);
|
transition-duration: var(--evcc-transition-slow);
|
||||||
transition-timing-function: ease-in;
|
transition-timing-function: ease-in;
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +99,13 @@ html.dark .phase {
|
||||||
background-color: var(--bs-gray-bright);
|
background-color: var(--bs-gray-bright);
|
||||||
}
|
}
|
||||||
.phase-inactive {
|
.phase-inactive {
|
||||||
|
flex: 0 0 4px;
|
||||||
|
}
|
||||||
|
.phase-inactive .target,
|
||||||
|
.phase-inactive .real {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.phase-hidden {
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -96,9 +123,15 @@ html.dark .phase {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.target {
|
.target {
|
||||||
background-color: var(--evcc-green);
|
background-color: color-mix(in srgb, var(--evcc-dark-green) 40%, white);
|
||||||
}
|
}
|
||||||
.real {
|
.real {
|
||||||
background-color: var(--evcc-dark-green);
|
background-color: var(--evcc-dark-green);
|
||||||
}
|
}
|
||||||
|
.phases-warning .target {
|
||||||
|
background-color: color-mix(in srgb, var(--bs-warning) 40%, white);
|
||||||
|
}
|
||||||
|
.phases-warning .real {
|
||||||
|
background-color: var(--bs-warning);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
25
assets/js/directives/tooltip.ts
Normal file
25
assets/js/directives/tooltip.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Tooltip from "bootstrap/js/dist/tooltip";
|
||||||
|
import type { Directive, DirectiveBinding } from "vue";
|
||||||
|
|
||||||
|
function update(el: HTMLElement, { value, oldValue }: DirectiveBinding<string | undefined>) {
|
||||||
|
if (value === oldValue) return;
|
||||||
|
const instance = Tooltip.getInstance(el);
|
||||||
|
if (!value) {
|
||||||
|
instance?.dispose();
|
||||||
|
} else if (instance) {
|
||||||
|
instance.setContent({ ".tooltip-inner": value });
|
||||||
|
} else {
|
||||||
|
new Tooltip(el, { title: value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// v-tooltip="text": bootstrap tooltip bound to text, removed when text is empty
|
||||||
|
const tooltip: Directive<HTMLElement, string | undefined> = {
|
||||||
|
mounted: update,
|
||||||
|
updated: update,
|
||||||
|
beforeUnmount(el) {
|
||||||
|
Tooltip.getInstance(el)?.dispose();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default tooltip;
|
||||||
|
|
@ -1432,6 +1432,7 @@
|
||||||
"finished": "Ladeende",
|
"finished": "Ladeende",
|
||||||
"last24hEnergy": "Letzte 24h",
|
"last24hEnergy": "Letzte 24h",
|
||||||
"last7dEnergy": "Letzte 7 Tage",
|
"last7dEnergy": "Letzte 7 Tage",
|
||||||
|
"phasesMismatch": "Mehr Phasen aktiv als konfiguriert. Phasenkonfiguration prüfen.",
|
||||||
"power": "Leistung",
|
"power": "Leistung",
|
||||||
"price": "Kosten",
|
"price": "Kosten",
|
||||||
"remaining": "Restzeit",
|
"remaining": "Restzeit",
|
||||||
|
|
|
||||||
|
|
@ -1432,6 +1432,7 @@
|
||||||
"finished": "Finish time",
|
"finished": "Finish time",
|
||||||
"last24hEnergy": "Last 24h",
|
"last24hEnergy": "Last 24h",
|
||||||
"last7dEnergy": "Last 7 days",
|
"last7dEnergy": "Last 7 days",
|
||||||
|
"phasesMismatch": "More phases active than configured. Check phase configuration.",
|
||||||
"power": "Power",
|
"power": "Power",
|
||||||
"price": "Cost",
|
"price": "Cost",
|
||||||
"remaining": "Remaining",
|
"remaining": "Remaining",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue