mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-12 18:17:36 +03:00
i18n and timestamp formatting
This commit is contained in:
parent
5fdd426e12
commit
cc48dc7c4e
@ -18,6 +18,16 @@
|
|||||||
"aria": "Select events",
|
"aria": "Select events",
|
||||||
"noFoundForTimePeriod": "No events found for this time period."
|
"noFoundForTimePeriod": "No events found for this time period."
|
||||||
},
|
},
|
||||||
|
"activity": {
|
||||||
|
"noActivitiesFound": "No activities found",
|
||||||
|
"activitiesCount_one": "{{count}} activity",
|
||||||
|
"activitiesCount_other": "{{count}} activities",
|
||||||
|
"object": "Object"
|
||||||
|
},
|
||||||
|
"objectTrack": {
|
||||||
|
"trackedPoint": "Tracked point",
|
||||||
|
"clickToSeek": "Click to seek to this time"
|
||||||
|
},
|
||||||
"documentTitle": "Review - Frigate",
|
"documentTitle": "Review - Frigate",
|
||||||
"recordings": {
|
"recordings": {
|
||||||
"documentTitle": "Recordings - Frigate"
|
"documentTitle": "Recordings - Frigate"
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type ObjectTrackOverlayProps = {
|
type ObjectTrackOverlayProps = {
|
||||||
camera: string;
|
camera: string;
|
||||||
@ -32,6 +33,7 @@ export default function ObjectTrackOverlay({
|
|||||||
onSeekToTime,
|
onSeekToTime,
|
||||||
objectTimeline,
|
objectTimeline,
|
||||||
}: ObjectTrackOverlayProps) {
|
}: ObjectTrackOverlayProps) {
|
||||||
|
const { t } = useTranslation("views/events");
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
const { annotationOffset } = useActivityStream();
|
const { annotationOffset } = useActivityStream();
|
||||||
|
|
||||||
@ -358,10 +360,10 @@ export default function ObjectTrackOverlay({
|
|||||||
<TooltipContent side="top" className="smart-capitalize">
|
<TooltipContent side="top" className="smart-capitalize">
|
||||||
{pos.lifecycle_item
|
{pos.lifecycle_item
|
||||||
? `${pos.lifecycle_item.class_type.replace("_", " ")} at ${new Date(pos.timestamp * 1000).toLocaleTimeString()}`
|
? `${pos.lifecycle_item.class_type.replace("_", " ")} at ${new Date(pos.timestamp * 1000).toLocaleTimeString()}`
|
||||||
: "Tracked point"}
|
: t("objectTrack.trackedPoint")}
|
||||||
{onSeekToTime && (
|
{onSeekToTime && (
|
||||||
<div className="mt-1 text-xs text-muted-foreground">
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
Click to seek to this time
|
{t("objectTrack.clickToSeek")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
|||||||
@ -5,6 +5,11 @@ import { getLifecycleItemDescription } from "@/utils/lifecycleUtil";
|
|||||||
import { useActivityStream } from "@/contexts/ActivityStreamContext";
|
import { useActivityStream } from "@/contexts/ActivityStreamContext";
|
||||||
import scrollIntoView from "scroll-into-view-if-needed";
|
import scrollIntoView from "scroll-into-view-if-needed";
|
||||||
import useUserInteraction from "@/hooks/use-user-interaction";
|
import useUserInteraction from "@/hooks/use-user-interaction";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import ActivityIndicator from "../indicators/activity-indicator";
|
||||||
|
|
||||||
type ActivityStreamProps = {
|
type ActivityStreamProps = {
|
||||||
timelineData: ObjectLifecycleSequence[];
|
timelineData: ObjectLifecycleSequence[];
|
||||||
@ -17,6 +22,8 @@ export default function ActivityStream({
|
|||||||
currentTime,
|
currentTime,
|
||||||
onSeek,
|
onSeek,
|
||||||
}: ActivityStreamProps) {
|
}: ActivityStreamProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
const { t } = useTranslation("views/events");
|
||||||
const { selectedObjectId, annotationOffset } = useActivityStream();
|
const { selectedObjectId, annotationOffset } = useActivityStream();
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -101,6 +108,10 @@ export default function ActivityStream({
|
|||||||
setProgrammaticScroll,
|
setProgrammaticScroll,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return <ActivityIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
@ -109,13 +120,14 @@ export default function ActivityStream({
|
|||||||
<div className="space-y-2 p-4">
|
<div className="space-y-2 p-4">
|
||||||
{filteredGroups.length === 0 ? (
|
{filteredGroups.length === 0 ? (
|
||||||
<div className="py-8 text-center text-muted-foreground">
|
<div className="py-8 text-center text-muted-foreground">
|
||||||
No activities found
|
{t("activity.noActivitiesFound")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredGroups.map((group) => (
|
filteredGroups.map((group) => (
|
||||||
<ActivityGroup
|
<ActivityGroup
|
||||||
key={group.timestamp}
|
key={group.timestamp}
|
||||||
group={group}
|
group={group}
|
||||||
|
config={config}
|
||||||
isCurrent={group.effectiveTimestamp <= currentTime}
|
isCurrent={group.effectiveTimestamp <= currentTime}
|
||||||
onSeek={onSeek}
|
onSeek={onSeek}
|
||||||
/>
|
/>
|
||||||
@ -132,11 +144,18 @@ type ActivityGroupProps = {
|
|||||||
effectiveTimestamp: number;
|
effectiveTimestamp: number;
|
||||||
activities: ObjectLifecycleSequence[];
|
activities: ObjectLifecycleSequence[];
|
||||||
};
|
};
|
||||||
|
config: FrigateConfig;
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
onSeek: (timestamp: number) => void;
|
onSeek: (timestamp: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ActivityGroup({ group, isCurrent, onSeek }: ActivityGroupProps) {
|
function ActivityGroup({
|
||||||
|
group,
|
||||||
|
config,
|
||||||
|
isCurrent,
|
||||||
|
onSeek,
|
||||||
|
}: ActivityGroupProps) {
|
||||||
|
const { t } = useTranslation("views/events");
|
||||||
const shouldExpand = group.activities.length > 1;
|
const shouldExpand = group.activities.length > 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -152,11 +171,25 @@ function ActivityGroup({ group, isCurrent, onSeek }: ActivityGroupProps) {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-sm font-medium">
|
<div className="text-sm font-medium">
|
||||||
{new Date(group.timestamp * 1000).toLocaleTimeString()}
|
{formatUnixTimestampToDateTime(group.timestamp, {
|
||||||
|
timezone: config.ui.timezone,
|
||||||
|
date_format:
|
||||||
|
config.ui.time_format == "24hour"
|
||||||
|
? t("time.formattedTimestamp.24hour", {
|
||||||
|
ns: "common",
|
||||||
|
})
|
||||||
|
: t("time.formattedTimestamp.12hour", {
|
||||||
|
ns: "common",
|
||||||
|
}),
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
{shouldExpand && (
|
{shouldExpand && (
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{group.activities.length} activities
|
{t("activity.activitiesCount", {
|
||||||
|
count: group.activities.length,
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -177,6 +210,7 @@ type ActivityItemProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function ActivityItem({ activity }: ActivityItemProps) {
|
function ActivityItem({ activity }: ActivityItemProps) {
|
||||||
|
const { t } = useTranslation("views/events");
|
||||||
const { selectedObjectId, setSelectedObjectId } = useActivityStream();
|
const { selectedObjectId, setSelectedObjectId } = useActivityStream();
|
||||||
const handleObjectClick = (e: React.MouseEvent) => {
|
const handleObjectClick = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@ -202,7 +236,7 @@ function ActivityItem({ activity }: ActivityItemProps) {
|
|||||||
: "bg-muted hover:bg-muted/80"
|
: "bg-muted hover:bg-muted/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Object
|
{t("activity.object")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user