mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 04:05:26 +03:00
Show mini timeline bar for non selected items
This commit is contained in:
parent
fc259133ac
commit
11133cb05a
@ -391,6 +391,17 @@ def set_sub_label(id):
|
|||||||
new_sub_label = json.get("subLabel")
|
new_sub_label = json.get("subLabel")
|
||||||
new_score = json.get("subLabelScore")
|
new_score = json.get("subLabelScore")
|
||||||
|
|
||||||
|
if new_sub_label is None:
|
||||||
|
return make_response(
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"message": "A sub label must be supplied",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
400,
|
||||||
|
)
|
||||||
|
|
||||||
if new_sub_label and len(new_sub_label) > 100:
|
if new_sub_label and len(new_sub_label) > 100:
|
||||||
return make_response(
|
return make_response(
|
||||||
jsonify(
|
jsonify(
|
||||||
@ -416,6 +427,7 @@ def set_sub_label(id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not event.end_time:
|
if not event.end_time:
|
||||||
|
# update tracked object
|
||||||
tracked_obj: TrackedObject = (
|
tracked_obj: TrackedObject = (
|
||||||
current_app.detected_frames_processor.camera_states[
|
current_app.detected_frames_processor.camera_states[
|
||||||
event.camera
|
event.camera
|
||||||
@ -425,6 +437,11 @@ def set_sub_label(id):
|
|||||||
if tracked_obj:
|
if tracked_obj:
|
||||||
tracked_obj.obj_data["sub_label"] = (new_sub_label, new_score)
|
tracked_obj.obj_data["sub_label"] = (new_sub_label, new_score)
|
||||||
|
|
||||||
|
# update timeline items
|
||||||
|
Timeline.update(
|
||||||
|
data=Timeline.data.update({"sub_label": (new_sub_label, new_score)})
|
||||||
|
).where(Timeline.source_id == id).execute()
|
||||||
|
|
||||||
event.sub_label = new_sub_label
|
event.sub_label = new_sub_label
|
||||||
|
|
||||||
if new_score:
|
if new_score:
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from multiprocessing import Queue
|
|||||||
from multiprocessing.synchronize import Event as MpEvent
|
from multiprocessing.synchronize import Event as MpEvent
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.const import ALL_ATTRIBUTE_LABELS
|
|
||||||
from frigate.events.maintainer import EventTypeEnum
|
from frigate.events.maintainer import EventTypeEnum
|
||||||
from frigate.models import Timeline
|
from frigate.models import Timeline
|
||||||
from frigate.util.builtin import to_relative_box
|
from frigate.util.builtin import to_relative_box
|
||||||
@ -109,22 +108,6 @@ class TimelineProcessor(threading.Thread):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# make sure all timeline items have a sub label
|
|
||||||
if prev_event_data != None and prev_event_data.get(
|
|
||||||
"sub_label"
|
|
||||||
) != event_data.get("sub_label"):
|
|
||||||
# update existing timeline items to have sub label
|
|
||||||
sub_label = event_data["sub_label"]
|
|
||||||
|
|
||||||
if sub_label[0] not in ALL_ATTRIBUTE_LABELS:
|
|
||||||
if event_id in self.pre_event_cache.keys():
|
|
||||||
for e in self.pre_event_cache[event_id]:
|
|
||||||
e[Timeline.data]["sub_label"] = sub_label
|
|
||||||
else:
|
|
||||||
Timeline.update(
|
|
||||||
data=Timeline.data.update({"sub_label": sub_label})
|
|
||||||
).where(Timeline.source_id == event_id).execute()
|
|
||||||
|
|
||||||
if event_type == "start":
|
if event_type == "start":
|
||||||
timeline_entry[Timeline.class_type] = "visible"
|
timeline_entry[Timeline.class_type] = "visible"
|
||||||
save = True
|
save = True
|
||||||
|
|||||||
185
web/src/components/bar/TimelineBar.tsx
Normal file
185
web/src/components/bar/TimelineBar.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import { GraphDataPoint } from "@/types/graph";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
type TimelineBarProps = {
|
||||||
|
startTime: number;
|
||||||
|
graphData:
|
||||||
|
| {
|
||||||
|
objects: number[];
|
||||||
|
motion: GraphDataPoint[];
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
export default function TimelineBar({
|
||||||
|
startTime,
|
||||||
|
graphData,
|
||||||
|
onClick,
|
||||||
|
}: TimelineBarProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="my-1 p-1 w-full h-18 border rounded cursor-pointer hover:bg-secondary hover:bg-opacity-30"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{graphData != undefined && (
|
||||||
|
<div className="relative w-full h-8 flex">
|
||||||
|
{getHourBlocks().map((idx) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className={`h-2 flex-auto ${
|
||||||
|
(graphData.motion.at(idx)?.y || 0) == 0
|
||||||
|
? ""
|
||||||
|
: graphData.objects.includes(idx)
|
||||||
|
? "bg-object"
|
||||||
|
: "bg-motion"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className="absolute left-0 top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[8.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[16.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[25%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[33.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[41.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[50%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[58.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[66.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[75%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[83.3%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute left-[91.7%] top-0 bottom-0 align-bottom border-l border-gray-500">
|
||||||
|
<div className="absolute ml-1 bottom-0 text-sm text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-gray-500">
|
||||||
|
{formatUnixTimestampToDateTime(startTime, {
|
||||||
|
strftime_fmt: "%a %d %B",
|
||||||
|
time_style: "medium",
|
||||||
|
date_style: "medium",
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHourBlocks() {
|
||||||
|
const arr = [];
|
||||||
|
|
||||||
|
for (let x = 0; x <= 59; x++) {
|
||||||
|
arr.push(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import TimelineItemCard from "@/components/card/TimelineItemCard";
|
|||||||
import { getTimelineHoursForDay } from "@/utils/historyUtil";
|
import { getTimelineHoursForDay } from "@/utils/historyUtil";
|
||||||
import { GraphDataPoint } from "@/types/graph";
|
import { GraphDataPoint } from "@/types/graph";
|
||||||
import TimelineGraph from "@/components/graph/TimelineGraph";
|
import TimelineGraph from "@/components/graph/TimelineGraph";
|
||||||
|
import TimelineBar from "@/components/bar/TimelineBar";
|
||||||
|
|
||||||
type DesktopTimelineViewProps = {
|
type DesktopTimelineViewProps = {
|
||||||
timelineData: CardsData;
|
timelineData: CardsData;
|
||||||
@ -361,10 +362,9 @@ export default function DesktopTimelineView({
|
|||||||
<div
|
<div
|
||||||
ref={isInitiallySelected ? initialScrollRef : null}
|
ref={isInitiallySelected ? initialScrollRef : null}
|
||||||
key={timeline.range.start}
|
key={timeline.range.start}
|
||||||
className={`relative p-2 ${
|
|
||||||
isSelected ? "bg-secondary bg-opacity-30 rounded-md" : ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
|
{isSelected ? (
|
||||||
|
<div className="p-2 relative bg-secondary bg-opacity-30 rounded-md">
|
||||||
<ActivityScrubber
|
<ActivityScrubber
|
||||||
items={[]}
|
items={[]}
|
||||||
timeBars={
|
timeBars={
|
||||||
@ -385,6 +385,7 @@ export default function DesktopTimelineView({
|
|||||||
}}
|
}}
|
||||||
timechangeHandler={(data) => {
|
timechangeHandler={(data) => {
|
||||||
if (!timeline.relevantPreview) {
|
if (!timeline.relevantPreview) {
|
||||||
|
playerRef.current?.pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,10 +412,6 @@ export default function DesktopTimelineView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
doubleClickHandler={() => {
|
|
||||||
setScrubbing(false);
|
|
||||||
setSelectedPlayback(timeline);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{isSelected && graphData && (
|
{isSelected && graphData && (
|
||||||
<div className="absolute left-2 right-2 top-0 h-[84px]">
|
<div className="absolute left-2 right-2 top-0 h-[84px]">
|
||||||
@ -433,6 +430,17 @@ export default function DesktopTimelineView({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<TimelineBar
|
||||||
|
startTime={timeline.range.start}
|
||||||
|
graphData={graphData}
|
||||||
|
onClick={() => {
|
||||||
|
setScrubbing(false);
|
||||||
|
setSelectedPlayback(timeline);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user