UI Charts: swipe on charts to switch time range (#32614)
This commit is contained in:
parent
4314a64896
commit
950ca489e5
5 changed files with 50 additions and 10 deletions
|
|
@ -5,6 +5,8 @@
|
|||
:label="windowLabel"
|
||||
:prev-disabled="prevDisabled"
|
||||
:next-disabled="nextDisabled"
|
||||
:highlight-prev="highlightPrev"
|
||||
:highlight-next="highlightNext"
|
||||
@prev="changeOffset(-1)"
|
||||
@next="changeOffset(1)"
|
||||
/>
|
||||
|
|
@ -44,6 +46,8 @@ import { batteryColor } from "@/colors";
|
|||
import type { Legend } from "../Sessions/types";
|
||||
import type { BatterySeries } from "./types";
|
||||
import Card from "../Helper/Card.vue";
|
||||
import { attachSwipeHandler } from "@/utils/swipe";
|
||||
import { hapticFeedback } from "@/utils/haptic";
|
||||
import SelectGroup from "../Helper/SelectGroup.vue";
|
||||
import LegendList from "../Sessions/LegendList.vue";
|
||||
import WindowNav from "./WindowNav.vue";
|
||||
|
|
@ -71,6 +75,10 @@ export default defineComponent({
|
|||
dayOffset: 0,
|
||||
focusedBattery: null as number | null,
|
||||
selectedUnit: (settings.batteryUnit || "soc") as "soc" | "energy",
|
||||
highlightPrev: false,
|
||||
highlightNext: false,
|
||||
highlightTimer: null as ReturnType<typeof setTimeout> | null,
|
||||
detachSwipe: null as (() => void) | null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -145,7 +153,32 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.detachSwipe = attachSwipeHandler(this.$el as HTMLElement, {
|
||||
onSwipeLeft: () => this.swipe(1),
|
||||
onSwipeRight: () => this.swipe(-1),
|
||||
});
|
||||
},
|
||||
unmounted() {
|
||||
this.detachSwipe?.();
|
||||
if (this.highlightTimer) clearTimeout(this.highlightTimer);
|
||||
},
|
||||
methods: {
|
||||
swipe(dir: number) {
|
||||
if (dir < 0 ? this.prevDisabled : this.nextDisabled) return;
|
||||
this.changeOffset(dir);
|
||||
this.flashHighlight(dir < 0 ? "prev" : "next");
|
||||
hapticFeedback("medium");
|
||||
},
|
||||
flashHighlight(dir: "prev" | "next") {
|
||||
if (this.highlightTimer) clearTimeout(this.highlightTimer);
|
||||
this.highlightPrev = dir === "prev";
|
||||
this.highlightNext = dir === "next";
|
||||
this.highlightTimer = setTimeout(() => {
|
||||
this.highlightPrev = false;
|
||||
this.highlightNext = false;
|
||||
}, 300);
|
||||
},
|
||||
updateUnit(value: string | number | boolean | null) {
|
||||
this.selectedUnit = value === "energy" ? "energy" : "soc";
|
||||
settings.batteryUnit = this.selectedUnit;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<DateNavigatorButton
|
||||
prev
|
||||
:disabled="prevDisabled"
|
||||
:highlight="highlightPrev"
|
||||
:on-click="() => $emit('prev')"
|
||||
data-testid="battery-chart-prev"
|
||||
/>
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
<DateNavigatorButton
|
||||
next
|
||||
:disabled="nextDisabled"
|
||||
:highlight="highlightNext"
|
||||
:on-click="() => $emit('next')"
|
||||
data-testid="battery-chart-next"
|
||||
/>
|
||||
|
|
@ -29,6 +31,8 @@ export default defineComponent({
|
|||
label: { type: String, default: "" },
|
||||
prevDisabled: Boolean,
|
||||
nextDisabled: Boolean,
|
||||
highlightPrev: Boolean,
|
||||
highlightNext: Boolean,
|
||||
},
|
||||
emits: ["prev", "next"],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ import DateNavigatorButton from "./DateNavigatorButton.vue";
|
|||
import formatter from "@/mixins/formatter";
|
||||
import type { SelectOption } from "@/types/evcc";
|
||||
import { attachSwipeHandler } from "@/utils/swipe";
|
||||
import { hapticFeedback } from "@/utils/haptic";
|
||||
|
||||
function daysInMonth(year: number, month: number) {
|
||||
return new Date(year, month, 0).getDate();
|
||||
|
|
@ -279,7 +280,6 @@ export default defineComponent({
|
|||
this.detachSwipe = attachSwipeHandler(document.body, {
|
||||
onSwipeLeft: () => this.swipeNext(),
|
||||
onSwipeRight: () => this.swipePrev(),
|
||||
ignoreSelector: "canvas, [_echarts_instance_]",
|
||||
});
|
||||
},
|
||||
unmounted() {
|
||||
|
|
@ -393,6 +393,7 @@ export default defineComponent({
|
|||
else if (this.showYear && this.hasPrevYear) this.emitPrevYear();
|
||||
else return;
|
||||
this.flashHighlight("prev");
|
||||
hapticFeedback("medium");
|
||||
},
|
||||
swipeNext() {
|
||||
if (this.showDay && this.hasNextDay) this.emitNextDay();
|
||||
|
|
@ -400,6 +401,7 @@ export default defineComponent({
|
|||
else if (this.showYear && this.hasNextYear) this.emitNextYear();
|
||||
else return;
|
||||
this.flashHighlight("next");
|
||||
hapticFeedback("medium");
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export function hapticFeedback(): void {
|
||||
navigator.vibrate?.(5);
|
||||
// the native app shims navigator.vibrate; duration maps to impact strength (<=50 light, <=100 medium)
|
||||
export function hapticFeedback(style: "light" | "medium" = "light"): void {
|
||||
navigator.vibrate?.(style === "medium" ? 60 : 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
interface SwipeOptions {
|
||||
onSwipeLeft?: () => void;
|
||||
onSwipeRight?: () => void;
|
||||
ignoreSelector?: string;
|
||||
minDistance?: number;
|
||||
maxDuration?: number;
|
||||
}
|
||||
|
|
@ -119,8 +118,7 @@ export function attachSwipeHandler(el: HTMLElement, options: SwipeOptions): () =
|
|||
ignored = true;
|
||||
return;
|
||||
}
|
||||
const target = e.target as Element | null;
|
||||
ignored = !!(options.ignoreSelector && target?.closest(options.ignoreSelector));
|
||||
ignored = false;
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
startTime = Date.now();
|
||||
|
|
@ -139,11 +137,13 @@ export function attachSwipeHandler(el: HTMLElement, options: SwipeOptions): () =
|
|||
else options.onSwipeRight?.();
|
||||
};
|
||||
|
||||
el.addEventListener("touchstart", onTouchStart, { passive: true });
|
||||
el.addEventListener("touchend", onTouchEnd, { passive: true });
|
||||
// capture phase, so the touch tooltip gate's stopPropagation on chart elements
|
||||
// cannot starve the swipe detection
|
||||
el.addEventListener("touchstart", onTouchStart, { capture: true, passive: true });
|
||||
el.addEventListener("touchend", onTouchEnd, { capture: true, passive: true });
|
||||
|
||||
return () => {
|
||||
el.removeEventListener("touchstart", onTouchStart);
|
||||
el.removeEventListener("touchend", onTouchEnd);
|
||||
el.removeEventListener("touchstart", onTouchStart, { capture: true });
|
||||
el.removeEventListener("touchend", onTouchEnd, { capture: true });
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue