Compare commits

...
Author SHA1 Message Date
dependabot[bot]andGitHub 3efb224f99 Bump js-yaml, @typescript-eslint/eslint-plugin, @typescript-eslint/parser, eslint and eslint-plugin-jest
Bumps [js-yaml](https://github.com/nodeca/js-yaml), [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [eslint](https://github.com/eslint/eslint) and [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest). These dependencies needed to be updated together.

Updates `js-yaml` from 4.1.1 to 5.2.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...5.2.2)

Updates `@typescript-eslint/eslint-plugin` from 7.12.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 7.12.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/parser)

Updates `eslint` from 8.57.0 to 10.8.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v10.8.0)

Updates `eslint-plugin-jest` from 28.6.0 to 28.14.0
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v28.6.0...v28.14.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.2
  dependency-type: direct:production
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.65.0
  dependency-type: direct:development
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.65.0
  dependency-type: direct:development
- dependency-name: eslint
  dependency-version: 10.8.0
  dependency-type: direct:development
- dependency-name: eslint-plugin-jest
  dependency-version: 28.14.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 13:22:16 +00:00
Josh HawkinsandGitHub 12dd242151 Miscellaneous fixes (0.18 beta) (#23809)
* fix calendars greying out the current day after midnight

The cutoff for disabling future days was computed with setHours(getHours() + 24, -1, 0, 0), which is not "24 hours from now" but tomorrow at the current hour minus one minute. Between 00:00 and 00:59 that lands back on today, and react-day-picker matches range matchers by calendar day, so today itself was disabled, leaving the export dialog's start time stuck on the previous day. TimezoneAwareCalendar also added the configured timezone's raw UTC offset instead of its difference from the browser's, widening the broken window to several hours in negative-offset zones and letting future days through in positive-offset ones. Derive the current date in the display timezone once, then build each cutoff in the space its calendar uses: ReviewActivityCalendar passes timeZone to react-day-picker so its day cells are TZDate and need a real instant, while TimezoneAwareCalendar is handed pre-shifted dates and needs a local one. Also corrects the today prop, which was off by the browser's offset, and the truthiness check that treated a configured timezone of UTC as unset.

* pin react-zoom-pan-pinch to 3.6.1

3.7.0 attaches a ResizeObserver to the transform wrapper and content unconditionally and clamps the pan position into the current bounds on every resize. The history player hides itself with display:none while scrubbing and while a new hour of recordings loads, so the observer measures it as 0x0, collapses the bounds to zero, and snaps a zoomed in view back to the top left corner. Zoom scale survives, only the position is lost.

That observer was only created for centerOnInit in 3.4.4 through 3.6.1 and 4.0.0 reverted it again, so 3.7.0 is the only affected release. The caret is what picked it up during the React 19 upgrade, so pin the version exactly.

Reported in #23807
2026-07-25 07:19:58 -06:00
Josh HawkinsandGitHub a573ea49bf update icons and i18n for 2026.2 frigate+ labels (#23803)
CI / AMD64 Build (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
2026-07-24 16:15:58 -05:00
6 changed files with 647 additions and 583 deletions
+532 -530
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -66,7 +66,7 @@
"i18next-http-backend": "^3.0.1",
"idb-keyval": "^6.2.1",
"immer": "^10.1.1",
"js-yaml": "^4.1.1",
"js-yaml": "^5.2.2",
"konva": "^10.2.3",
"lodash": "^4.18.1",
"lucide-react": "^0.577.0",
@@ -87,7 +87,7 @@
"react-markdown": "^9.0.1",
"react-router-dom": "^6.30.3",
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "^3.7.0",
"react-zoom-pan-pinch": "3.6.1",
"remark-gfm": "^4.0.0",
"scroll-into-view-if-needed": "^3.1.0",
"sonner": "^2.0.7",
@@ -112,14 +112,14 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/strftime": "^0.9.8",
"@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/parser": "^7.5.0",
"@typescript-eslint/eslint-plugin": "^8.65.0",
"@typescript-eslint/parser": "^8.65.0",
"@vitejs/plugin-react-swc": "^3.8.0",
"@vitest/coverage-v8": "^3.0.7",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint": "^10.8.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.2.0",
"eslint-plugin-jest": "^28.14.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.8",
+3 -1
View File
@@ -125,5 +125,7 @@
"baby": "Baby",
"baby_stroller": "Baby Stroller",
"rickshaw": "Rickshaw",
"rodent": "Rodent"
"rodent": "Rodent",
"possum": "Possum",
"garbage_truck": "Garbage Truck"
}
+39
View File
@@ -0,0 +1,39 @@
import { SVGProps } from "react";
/**
* Skunk silhouette for the `skunk` object label.
*
* react-icons has no skunk in any of its packs. The usable stand-ins are
* either squirrels, which are indistinguishable from the `squirrel` label, or
* animals such as porcupine and hedgehog that are themselves Frigate+
* candidate labels.
*
* Adapted from "skunk silhouette" by dear_theophilus, published by Openclipart
* and released into the public domain, which permits reproduction,
* distribution and derivative works:
* https://openclipart.org/detail/170808/skunk-silhouette-by-dear_theophilus-170808
*
* Changes from the original: the unused Inkscape text region was dropped, the
* layer translate was folded into the viewBox, the viewBox was padded to give
* the same optical margin as the surrounding react-icons, and the fill was
* switched to currentColor. The white back stripe is negative space in a
* single path under the default nonzero fill rule, so no fill-rule override is
* needed here.
*
* Sized to sit alongside the react-icons set: currentColor fill and a 1em
* default box.
*/
export default function SkunkIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="96.87 93.174 110.58 109.81"
fill="currentColor"
height="1em"
width="1em"
{...props}
>
<path d="m169.58 196.93c-0.18864-0.85889 0.0134-1.3274 0.99603-2.31 0.67796-0.67797 1.7065-1.3304 2.2857-1.4498 1.3854-0.2857 5.2538-1.7692 5.8796-2.2549 0.27087-0.2102 0.4925-0.61406 0.4925-0.89746 0-0.77202-6.6659-6.0863-7.6343-6.0863-0.46161 0-2.0798 0.36441-3.596 0.80981-3.4618 1.0169-10.449 1.4599-16.39 1.0391-7.1408-0.50577-6.6473-0.58559-8.3439 1.3494-0.8327 0.94969-1.9642 2.5979-2.5144 3.6627-1.164 2.2525-4.5952 6.5163-5.8025 7.2106-1.0288 0.59154-4.5445 0.64844-5.9949 0.097-0.71722-0.27269-1.4467-0.28055-2.184-0.0235-2.4986 0.871-1.4597-1.341 1.2885-2.7437 0.78536-0.40084 1.9099-1.3016 2.499-2.0016 1.0163-1.2078 1.0634-1.4452 0.92116-4.6468-0.12622-2.8412-0.38301-3.9062-1.6262-6.7446-2.6443-6.0373-7.3573-11.099-12.678-13.617-1.5889-0.75179-2.62-0.93285-5.3302-0.93595-3.0016-0.004-3.6532-0.14511-6.107-1.3278-5.2109-2.5116-5.8227-4.8841-1.4913-5.7833 1.8591-0.38596 2.618-0.82052 4.4338-2.539 1.2049-1.1403 2.7581-2.3618 3.4516-2.7146 1.9684-1.001 7.7872-0.84521 13.816 0.37 5.7138 1.1517 10.339 1.3571 13.488 0.59905 1.1919-0.28691 4.6492-1.5446 7.683-2.7949 9.8002-4.0389 14.311-4.9373 19.109-3.806 1.4086 0.33208 2.7106 0.4572 2.8935 0.27806 1.0374-1.0164-1.1645-3.1382-6.6365-6.3947-5.6439-3.3588-9.8091-9.1231-10.764-14.897-0.60953-3.6839 0.72362-12.081 1.2923-13.596 0.56871-1.5152 1.4436-3.3754 1.9442-4.1338 2.9223-4.4276 8.177-9.1772 12.256-11.078 6.691-3.1177 17.754-2.3107 25.847 1.8855 3.5353 1.8329 6.7739 4.7994 8.0035 7.3309 1.3563 2.7924 2.3788 9.4802 1.6321 10.675-0.28831 0.46149-1.0187 1.9915-1.6231 3.4001-1.0066 2.3458-2.3392 4.3362-2.9009 4.3326-0.12475-0.00078-0.81827-1.1453-1.5412-2.5434-1.642-3.1756-6.3156-7.6992-7.9544-7.6992-0.58481 0-2.2156 0.52248-3.624 1.1611-3.1804 1.4421-4.0269 2.8569-3.7769 6.3127 0.24247 3.3521 2.1549 6.8946 9.8511 18.247 3.3307 4.9132 4.5143 10.219 3.7106 16.634-0.40898 3.2645-0.77579 4.5016-2.1396 7.2158-0.90527 1.8017-2.1169 3.8932-2.6924 4.6478-1.3686 1.7944-3.6318 10.202-4.0199 14.935-0.34632 4.2221-1.4143 6.7775-3.7479 8.9681l-1.5853 1.4881s-10.87 1.3087-11.076 0.36929zm-0.94988-19.482c5.4569-0.84327 10.316-2.9685 15.305-6.694 2.2799-1.7026 7.2867-9.9628 7.5372-11.17 1.1907-5.7355-0.81743-10.348-8.1457-18.709-1.1818-1.3484-3.251-4.3132-4.5981-6.5886 0 0-1.8246-3.0395-2.2503-4.734-0.4037-1.6068-0.24133-4.9643-0.24133-4.9643-0.0233-3.0587 1.7125-7.8176 2.7258-8.9913 2.4126-2.7945 5.1792-3.93 8.9395-3.6689 2.4539 0.17036 6.2631 1.6358 6.8904 2.6509 0.46463 0.75179 1.7776 0.45185 1.7776-0.40608 0-2.1062-2.1313-4.3793-5.8702-6.261-8.5709-4.3134-18.12-1.6112-22.089 6.2506-0.84198 1.6679-1.9752 7.8544-1.5778 9.9776 0.98137 5.2435 7.314 15.066 15.216 23.602 2.3474 2.5357 2.891 3.3814 3.2505 5.0579 0.62386 2.909 0.53762 3.9625-0.45679 5.5804-2.2494 3.6597-6.0764 5.9532-12.424 7.446-5.4931 1.2918-14.085 0.80251-19.932-1.135-2.8291-0.93745-3.2146-1.1799-3.3204-2.0877-0.26031-2.2347 5.8542-6.1292 12.294-7.8306 1.712-0.45228 3.469-1.013 3.9045-1.2461 2.613-1.3984-5.139-1.3894-10.001 0.0116-1.4244 0.41047-4.1726 1.3784-6.107 2.151-4.0916 1.6341-9.9725 3.2146-13.412 3.6046-1.9171 0.21734-3.2711 0.0654-6.6311-0.74437-6.6813-1.6101-8.8678-1.2892-6.6918 0.98197 1.1258 1.1751 6.0865 4.3046 6.8234 4.3046 0.22537 0 0.58888 0.3347 0.80781 0.74378 0.50815 0.94949 5.6856 4.2866 11.253 7.2529 3.5996 1.918 5.4022 2.5812 10.638 3.9141 3.4672 0.88264 7.7224 1.7278 9.456 1.8781 1.7336 0.15029 3.1675 0.30072 3.1864 0.33427 0.0189 0.0336 1.7032-0.1969 3.743-0.5121zm-55.67-16.07c0.0817-0.43098-0.25131-0.85888-0.89769-1.1534-0.8518-0.38811-1.1373-0.35853-1.6687 0.17289-0.53858 0.53858-0.55739 0.74197-0.11767 1.2718 0.67722 0.816 2.5119 0.61688 2.6841-0.29131zm7.5598-0.11521c0-0.95213-0.94275-1.7372-1.4622-1.2177-0.30143 0.30141 0.67899 2.2151 1.1348 2.2151 0.18008 0 0.32742-0.44882 0.32742-0.99738z" />
</svg>
);
}
@@ -20,6 +20,26 @@ function formatCalendarDay(day: Date): string {
return `${y}-${m}-${d}`;
}
function getTodayInTimezone(timezone?: string): {
year: number;
month: number;
day: number;
offset: number;
} {
const now = new Date();
const offset = Math.round(getUTCOffset(now, timezone));
// shifting by the offset makes the UTC getters read the timezone's wall clock
const wallClock = new Date(now.getTime() + offset * 60000);
return {
year: wallClock.getUTCFullYear(),
month: wallClock.getUTCMonth(),
day: wallClock.getUTCDate(),
offset,
};
}
type ReviewActivityCalendarProps = {
reviewSummary?: ReviewSummary;
recordingsSummary?: RecordingsSummary;
@@ -37,12 +57,14 @@ export default function ReviewActivityCalendar({
const [weekStartsOn] = useUserPersistence("weekStartsOn", 0);
const disabledDates = useMemo(() => {
const tomorrow = new Date();
tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0);
const future = new Date();
future.setFullYear(tomorrow.getFullYear() + 10);
return { from: tomorrow, to: future };
}, []);
// day cells are TZDate in `timezone`, so the cutoff must be a real instant
const { year, month, day, offset } = getTodayInTimezone(timezone);
// midday: ranges match by calendar day, so this dodges DST edges
const from = new Date(Date.UTC(year, month, day + 1, 12) - offset * 60000);
const to = new Date(from);
to.setFullYear(from.getFullYear() + 10);
return { from, to };
}, [timezone]);
const modifiers = useMemo(() => {
const recordingsSet = new Set<string>();
@@ -182,48 +204,25 @@ export function TimezoneAwareCalendar({
};
}, [recordingsSummary]);
const timezoneOffset = useMemo(
() =>
timezone ? Math.round(getUTCOffset(new Date(), timezone)) : undefined,
// callers pre-shift dates so the local clock reads `timezone`, so boundaries
// are built in local time rather than as instants
const { year, month, day } = useMemo(
() => getTodayInTimezone(timezone),
[timezone],
);
const disabledDates = useMemo(() => {
const tomorrow = new Date();
// midday: ranges match by calendar day, so this dodges DST edges
const from = new Date(year, month, day + 1, 12);
const to = new Date(from);
to.setFullYear(from.getFullYear() + 10);
return { from, to };
}, [year, month, day]);
if (timezoneOffset) {
tomorrow.setHours(
tomorrow.getHours() + 24,
tomorrow.getMinutes() + timezoneOffset,
0,
0,
);
} else {
tomorrow.setHours(tomorrow.getHours() + 24, -1, 0, 0);
}
const future = new Date();
future.setFullYear(tomorrow.getFullYear() + 10);
return { from: tomorrow, to: future };
}, [timezoneOffset]);
const today = useMemo(() => {
if (!timezoneOffset) {
return undefined;
}
const date = new Date();
const utc = Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
);
const todayUtc = new Date(utc);
todayUtc.setMinutes(todayUtc.getMinutes() + timezoneOffset, 0, 0);
return todayUtc;
}, [timezoneOffset]);
const today = useMemo(
() => new Date(year, month, day, 12),
[year, month, day],
);
return (
<Calendar
+24 -2
View File
@@ -1,9 +1,12 @@
import { IconName } from "@/components/icons/IconPicker";
import SkunkIcon from "@/components/icons/SkunkIcon";
import { FrigateConfig } from "@/types/frigateConfig";
import { EventType } from "@/types/search";
import { BsPersonWalking } from "react-icons/bs";
import {
FaAmazon,
FaBaby,
FaBabyCarriage,
FaBicycle,
FaBus,
FaCarSide,
@@ -24,6 +27,8 @@ import {
FaUsps,
} from "react-icons/fa";
import {
GiBarbecue,
GiCow,
GiDeer,
GiFox,
GiGoat,
@@ -32,7 +37,9 @@ import {
GiPostStamp,
GiRabbit,
GiRaccoonHead,
GiRat,
GiSailboat,
GiSeatedMouse,
GiSoundWaves,
GiSquirrel,
} from "react-icons/gi";
@@ -40,6 +47,7 @@ import { LuBox, LuLassoSelect, LuScanBarcode } from "react-icons/lu";
import * as LuIcons from "react-icons/lu";
import { MdRecordVoiceOver } from "react-icons/md";
import { PiBirdFill } from "react-icons/pi";
import { HiMiniTruck } from "react-icons/hi2";
export function getAttributeLabels(config?: FrigateConfig) {
if (!config) {
@@ -74,6 +82,12 @@ export function getIconForLabel(
switch (label) {
// objects
case "baby":
return <FaBaby key={iconKey} className={className} />;
case "baby_stroller":
return <FaBabyCarriage key={iconKey} className={className} />;
case "bbq_grill":
return <GiBarbecue key={iconKey} className={className} />;
case "bear":
return <GiPolarBear key={iconKey} className={className} />;
case "bicycle":
@@ -90,6 +104,8 @@ export function getIconForLabel(
return <FaCarSide key={iconKey} className={className} />;
case "cat":
return <FaCat key={iconKey} className={className} />;
case "cow":
return <GiCow key={iconKey} className={className} />;
case "deer":
return <GiDeer key={iconKey} className={className} />;
case "animal":
@@ -98,6 +114,8 @@ export function getIconForLabel(
return <FaDog key={iconKey} className={className} />;
case "fox":
return <GiFox key={iconKey} className={className} />;
case "garbage_truck":
return <HiMiniTruck key={iconKey} className={className} />;
case "goat":
return <GiGoat key={iconKey} className={className} />;
case "horse":
@@ -114,18 +132,22 @@ export function getIconForLabel(
return <LuBox key={iconKey} className={className} />;
case "person":
return <BsPersonWalking key={iconKey} className={className} />;
case "possum":
return <GiSeatedMouse key={iconKey} className={className} />;
case "rabbit":
return <GiRabbit key={iconKey} className={className} />;
case "raccoon":
return <GiRaccoonHead key={iconKey} className={className} />;
case "robot_lawnmower":
return <FaHockeyPuck key={iconKey} className={className} />;
case "rodent":
return <GiRat key={iconKey} className={className} />;
case "sports_ball":
return <FaFootballBall key={iconKey} className={className} />;
case "skunk":
return <GiSquirrel key={iconKey} className={className} />;
return <SkunkIcon key={iconKey} className={className} />;
case "squirrel":
return <LuIcons.LuSquirrel key={iconKey} className={className} />;
return <GiSquirrel key={iconKey} className={className} />;
case "umbrella":
return <FaUmbrella key={iconKey} className={className} />;
case "waste_bin":