Skip to content

Commit

Permalink
option to show motion only on motion timeline (#10626)
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkeye217 authored Mar 23, 2024
1 parent 8e1d18d commit 4159334
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 126 deletions.
84 changes: 70 additions & 14 deletions web/src/components/filter/ReviewFilterGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { ReviewFilter, ReviewSummary } from "@/types/review";
import { ReviewFilter, ReviewSeverity, ReviewSummary } from "@/types/review";
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { FaCalendarAlt, FaFilter, FaVideo } from "react-icons/fa";
import { FaCalendarAlt, FaFilter, FaRunning, FaVideo } from "react-icons/fa";
import { isMobile } from "react-device-detect";
import { Drawer, DrawerContent, DrawerTrigger } from "../ui/drawer";
import { Switch } from "../ui/switch";
Expand All @@ -27,12 +27,18 @@ type ReviewFilterGroupProps = {
reviewSummary?: ReviewSummary;
filter?: ReviewFilter;
onUpdateFilter: (filter: ReviewFilter) => void;
severity: ReviewSeverity;
motionOnly: boolean;
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
};

export default function ReviewFilterGroup({
reviewSummary,
filter,
onUpdateFilter,
severity,
motionOnly,
setMotionOnly,
}: ReviewFilterGroupProps) {
const { data: config } = useSWR<FrigateConfig>("config");

Expand Down Expand Up @@ -94,7 +100,7 @@ export default function ReviewFilterGroup({
);

return (
<div>
<div className="flex justify-center">
<CamerasFilterButton
allCameras={filterValues.cameras}
groups={groups}
Expand All @@ -110,17 +116,24 @@ export default function ReviewFilterGroup({
}
updateSelectedDay={onUpdateSelectedDay}
/>
<GeneralFilterButton
allLabels={filterValues.labels}
selectedLabels={filter?.labels}
updateLabelFilter={(newLabels) => {
onUpdateFilter({ ...filter, labels: newLabels });
}}
showReviewed={filter?.showReviewed || 0}
setShowReviewed={(reviewed) =>
onUpdateFilter({ ...filter, showReviewed: reviewed })
}
/>
{severity == "significant_motion" ? (
<ShowMotionOnlyButton
motionOnly={motionOnly}
setMotionOnly={setMotionOnly}
/>
) : (
<GeneralFilterButton
allLabels={filterValues.labels}
selectedLabels={filter?.labels}
updateLabelFilter={(newLabels) => {
onUpdateFilter({ ...filter, labels: newLabels });
}}
showReviewed={filter?.showReviewed || 0}
setShowReviewed={(reviewed) =>
onUpdateFilter({ ...filter, showReviewed: reviewed })
}
/>
)}
</div>
);
}
Expand Down Expand Up @@ -485,3 +498,46 @@ function GeneralFilterButton({
</Popover>
);
}

type ShowMotionOnlyButtonProps = {
motionOnly: boolean;
setMotionOnly: React.Dispatch<React.SetStateAction<boolean>>;
};
function ShowMotionOnlyButton({
motionOnly,
setMotionOnly,
}: ShowMotionOnlyButtonProps) {
return (
<>
<div className="hidden md:inline-flex items-center justify-center whitespace-nowrap text-sm bg-secondary text-secondary-foreground h-9 rounded-md md:px-3 md:mx-1">
<Switch
className="ml-1"
id="collapse-motion"
checked={motionOnly}
onCheckedChange={() => {
setMotionOnly(!motionOnly);
}}
/>
<Label
className="mx-2 text-secondary-foreground"
htmlFor="collapse-motion"
>
Motion only
</Label>
</div>

<div className="block md:hidden">
<Button
size="sm"
className="ml-1"
variant="secondary"
onClick={() => setMotionOnly(!motionOnly)}
>
<FaRunning
className={`${motionOnly ? "text-selected" : "text-muted-foreground"}`}
/>
</Button>
</div>
</>
);
}
10 changes: 6 additions & 4 deletions web/src/components/timeline/EventReviewTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,12 @@ export function EventReviewTimeline({
const element = selectedTimelineRef.current?.querySelector(
`[data-segment-id="${Math.max(...alignedVisibleTimestamps)}"]`,
);
scrollIntoView(element as HTMLDivElement, {
scrollMode: "if-needed",
behavior: "smooth",
});
if (element) {
scrollIntoView(element, {
scrollMode: "if-needed",
behavior: "smooth",
});
}
}
}, [
selectedTimelineRef,
Expand Down
2 changes: 1 addition & 1 deletion web/src/components/timeline/EventSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export function EventSegment({
<div
key={segmentKey}
data-segment-id={segmentKey}
className={segmentClasses}
className={`segment ${segmentClasses}`}
onClick={segmentClick}
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
>
Expand Down
6 changes: 6 additions & 0 deletions web/src/components/timeline/MotionReviewTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type MotionReviewTimelineProps = {
showHandlebar?: boolean;
handlebarTime?: number;
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
motionOnly?: boolean;
showMinimap?: boolean;
minimapStartTime?: number;
minimapEndTime?: number;
Expand All @@ -45,6 +46,7 @@ export function MotionReviewTimeline({
showHandlebar = false,
handlebarTime,
setHandlebarTime,
motionOnly = false,
showMinimap = false,
minimapStartTime,
minimapEndTime,
Expand Down Expand Up @@ -113,6 +115,7 @@ export function MotionReviewTimeline({
draggableElementTime: handlebarTime,
setDraggableElementTime: setHandlebarTime,
timelineDuration,
timelineCollapsed: motionOnly,
timelineStartAligned,
isDragging,
setIsDragging,
Expand Down Expand Up @@ -176,6 +179,7 @@ export function MotionReviewTimeline({
segmentDuration={segmentDuration}
segmentTime={segmentTime}
timestampSpread={timestampSpread}
motionOnly={motionOnly}
showMinimap={showMinimap}
minimapStartTime={minimapStartTime}
minimapEndTime={minimapEndTime}
Expand All @@ -195,6 +199,7 @@ export function MotionReviewTimeline({
minimapEndTime,
events,
motion_events,
motionOnly,
]);

const segments = useMemo(
Expand All @@ -211,6 +216,7 @@ export function MotionReviewTimeline({
minimapEndTime,
events,
motion_events,
motionOnly,
],
);

Expand Down
151 changes: 85 additions & 66 deletions web/src/components/timeline/MotionSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type MotionSegmentProps = {
segmentTime: number;
segmentDuration: number;
timestampSpread: number;
motionOnly: boolean;
showMinimap: boolean;
minimapStartTime?: number;
minimapEndTime?: number;
Expand All @@ -26,6 +27,7 @@ export function MotionSegment({
segmentTime,
segmentDuration,
timestampSpread,
motionOnly,
showMinimap,
minimapStartTime,
minimapEndTime,
Expand Down Expand Up @@ -180,79 +182,96 @@ export function MotionSegment({
}, [segmentTime, setHandlebarTime]);

return (
<div
key={segmentKey}
data-segment-id={segmentKey}
className={`segment ${firstHalfSegmentWidth > 1 || secondHalfSegmentWidth > 1 ? "has-data" : ""} ${segmentClasses}`}
onClick={segmentClick}
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
>
<MinimapBounds
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
isLastSegmentInMinimap={isLastSegmentInMinimap}
alignedMinimapStartTime={alignedMinimapStartTime}
alignedMinimapEndTime={alignedMinimapEndTime}
firstMinimapSegmentRef={firstMinimapSegmentRef}
/>
<>
{(((firstHalfSegmentWidth > 1 || secondHalfSegmentWidth > 1) &&
motionOnly &&
severity[0] < 2) ||
!motionOnly) && (
<div
key={segmentKey}
data-segment-id={segmentKey}
className={`segment ${firstHalfSegmentWidth > 1 || secondHalfSegmentWidth > 1 ? "has-data" : ""} ${segmentClasses}`}
onClick={segmentClick}
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
>
{!motionOnly && (
<>
<MinimapBounds
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
isLastSegmentInMinimap={isLastSegmentInMinimap}
alignedMinimapStartTime={alignedMinimapStartTime}
alignedMinimapEndTime={alignedMinimapEndTime}
firstMinimapSegmentRef={firstMinimapSegmentRef}
/>

<Tick timestamp={timestamp} timestampSpread={timestampSpread} />
<Tick
key={`${segmentKey}_tick`}
timestamp={timestamp}
timestampSpread={timestampSpread}
/>

<Timestamp
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
isLastSegmentInMinimap={isLastSegmentInMinimap}
timestamp={timestamp}
timestampSpread={timestampSpread}
segmentKey={segmentKey}
/>
<Timestamp
key={`${segmentKey}_timestamp`}
isFirstSegmentInMinimap={isFirstSegmentInMinimap}
isLastSegmentInMinimap={isLastSegmentInMinimap}
timestamp={timestamp}
timestampSpread={timestampSpread}
segmentKey={segmentKey}
/>
</>
)}

<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-2 z-10 cursor-pointer">
<div className="flex flex-row justify-center w-[20px] md:w-[40px] mb-[1px]">
<div className="flex justify-center">
<div
key={`${segmentKey}_motion_data_1`}
className={`${isDesktop && animationClassesSecondHalf} h-[2px] rounded-full ${severity[0] != 0 ? "bg-motion_review-dimmed" : "bg-motion_review"}`}
style={{
width: secondHalfSegmentWidth,
}}
></div>
</div>
</div>

<div className="flex flex-row justify-center w-[20px] md:w-[40px]">
<div className="flex justify-center">
<div
key={`${segmentKey}_motion_data_2`}
className={`${isDesktop && animationClassesFirstHalf} h-[2px] rounded-full ${severity[0] != 0 ? "bg-motion_review-dimmed" : "bg-motion_review"}`}
style={{
width: firstHalfSegmentWidth,
}}
></div>
</div>
</div>
</div>
<div className="absolute left-1/2 transform -translate-x-1/2 w-[20px] md:w-[40px] h-2 z-10 cursor-pointer">
<div className="flex flex-row justify-center w-[20px] md:w-[40px] mb-[1px]">
<div className="flex justify-center">
<div
key={`${segmentKey}_motion_data_1`}
className={`${isDesktop && animationClassesSecondHalf} h-[2px] rounded-full ${severity[0] != 0 ? "bg-motion_review-dimmed" : "bg-motion_review"}`}
style={{
width: secondHalfSegmentWidth,
}}
></div>
</div>
</div>

{severity.map((severityValue: number, index: number) => {
if (severityValue > 1) {
return (
<React.Fragment key={index}>
<div className="absolute right-0 h-2 z-10">
<div className="flex flex-row justify-center w-[20px] md:w-[40px]">
<div className="flex justify-center">
<div
key={`${segmentKey}_${index}_secondary_data`}
className={`
w-1 h-2 bg-gradient-to-r
${roundBottomSecondary ? "rounded-bl-full rounded-br-full" : ""}
${roundTopSecondary ? "rounded-tl-full rounded-tr-full" : ""}
${severityColors[severityValue]}
`}
key={`${segmentKey}_motion_data_2`}
className={`${isDesktop && animationClassesFirstHalf} h-[2px] rounded-full ${severity[0] != 0 ? "bg-motion_review-dimmed" : "bg-motion_review"}`}
style={{
width: firstHalfSegmentWidth,
}}
></div>
</div>
</React.Fragment>
);
} else {
return null;
}
})}
</div>
</div>
</div>

{!motionOnly &&
severity.map((severityValue: number, index: number) => {
if (severityValue > 1) {
return (
<React.Fragment key={index}>
<div className="absolute right-0 h-2 z-10">
<div
key={`${segmentKey}_${index}_secondary_data`}
className={`
w-1 h-2 bg-gradient-to-r
${roundBottomSecondary ? "rounded-bl-full rounded-br-full" : ""}
${roundTopSecondary ? "rounded-tl-full rounded-tr-full" : ""}
${severityColors[severityValue]}
`}
></div>
</div>
</React.Fragment>
);
} else {
return null;
}
})}
</div>
)}
</>
);
}

Expand Down
Loading

0 comments on commit 4159334

Please sign in to comment.