tank alarm animation

This commit is contained in:
2026-03-20 14:38:55 +00:00
parent 68e8e3fc85
commit b3ceddcdfa
2 changed files with 46 additions and 6 deletions

View File

@@ -4,6 +4,10 @@ import { useTank } from "../../../utils/hooks/use-custom-mqtt"
import { useWatermaker } from "../../../utils/hooks/use-watermaker"
import { cardBase, cardTitle, cardStatusBadge } from "./cardStyles"
function AlarmPulseOverlay() {
return <div className="absolute inset-0 bg-red-600 animate-alarm-pulse rounded-[inherit]" />
}
function FreshWaterGauge() {
const bow = useTank(2)
const stern = useTank(3)
@@ -11,6 +15,8 @@ function FreshWaterGauge() {
const sternLevel = stern.level != null ? Math.round(stern.level) : null
const avgLevel =
bowLevel != null && sternLevel != null ? Math.round((bowLevel + sternLevel) / 2) : (bowLevel ?? sternLevel)
const bowAlarming = bow.lowLevelAlarm === 2
const sternAlarming = stern.lowLevelAlarm === 2
return (
<div>
@@ -19,14 +25,21 @@ function FreshWaterGauge() {
<span className="text-content-secondary text-xs font-bold">{avgLevel != null ? `${avgLevel}%` : "--"}</span>
</div>
<div className="h-3 bg-outline-primary rounded-full overflow-hidden flex">
<div className="h-full bg-blue-500 transition-all duration-500" style={{ width: `${(bowLevel ?? 0) / 2}%` }} />
<div
className="relative h-full bg-blue-500 transition-all duration-500"
style={{ width: `${(bowLevel ?? 0) / 2}%` }}
>
{bowAlarming && <AlarmPulseOverlay />}
</div>
{(bowLevel ?? 0) > 0 && (sternLevel ?? 0) > 0 && (
<div className="h-full flex-shrink-0 bg-outline-primary" style={{ width: "4px" }} />
)}
<div
className="h-full bg-blue-500 transition-all duration-500"
className="relative h-full bg-blue-500 transition-all duration-500"
style={{ width: `${(sternLevel ?? 0) / 2}%` }}
/>
>
{sternAlarming && <AlarmPulseOverlay />}
</div>
</div>
</div>
)
@@ -35,6 +48,7 @@ function FreshWaterGauge() {
function BlackWaterGauge() {
const tank = useTank(4)
const level = tank.level != null ? Math.round(tank.level) : null
const alarming = tank.highLevelAlarm === 2
return (
<div>
<div className="flex items-baseline justify-between mb-1">
@@ -43,9 +57,11 @@ function BlackWaterGauge() {
</div>
<div className="h-3 bg-outline-primary rounded-full overflow-hidden">
<div
className="h-full bg-content-victronYellow transition-all duration-500"
className="relative h-full bg-content-victronYellow transition-all duration-500"
style={{ width: `${level ?? 0}%` }}
/>
>
{alarming && <AlarmPulseOverlay />}
</div>
</div>
</div>
)
@@ -184,11 +200,26 @@ interface Props {
onClick?: () => void
}
function useAnyTankAlarm(): boolean {
const bow = useTank(2)
const stern = useTank(3)
const black = useTank(4)
return bow.lowLevelAlarm === 2 || stern.lowLevelAlarm === 2 || black.highLevelAlarm === 2
}
const CompactTankCard = ({ onClick }: Props) => {
const hasAlarm = useAnyTankAlarm()
return (
<div className={`${cardBase} h-full`} onClick={onClick}>
<div className="flex items-center justify-between mb-1">
<span className={cardTitle}>Tanks / Watermaker</span>
<div className="flex items-center gap-1.5">
<span className={cardTitle}>Tanks / Watermaker</span>
{hasAlarm && (
<svg className="w-3.5 h-3.5 text-content-victronRed" viewBox="0 0 24 24" fill="currentColor">
<path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z" />
</svg>
)}
</div>
</div>
<div className="flex flex-col gap-3">

View File

@@ -161,6 +161,15 @@ module.exports = {
5: "1.25rem",
7: "1.75rem",
},
keyframes: {
"alarm-pulse": {
"0%, 100%": { opacity: "0" },
"50%": { opacity: "0.85" },
},
},
animation: {
"alarm-pulse": "alarm-pulse 2.4s ease-in-out infinite",
},
},
},
// Garmin and Furuno devices don't support the RGB colors with opacity,