Add ability to choose custom time range on timeline

This commit is contained in:
Nicolas Mowen 2024-03-26 14:00:13 -06:00
parent 549b40d7c7
commit 3c1e266655
2 changed files with 101 additions and 40 deletions

View File

@ -16,6 +16,7 @@ import { FaArrowDown } from "react-icons/fa";
import axios from "axios";
import { toast } from "sonner";
import { Input } from "../ui/input";
import { TimeRange } from "@/types/timeline";
const EXPORT_OPTIONS = [
"1",
@ -30,50 +31,70 @@ type ExportOption = (typeof EXPORT_OPTIONS)[number];
type ExportDialogProps = {
camera: string;
latestTime: number;
currentTime: number;
range?: TimeRange;
mode: ExportMode;
setRange: (range: TimeRange) => void;
setMode: (mode: ExportMode) => void;
};
export default function ExportDialog({
camera,
latestTime,
currentTime,
range,
mode,
setRange,
setMode,
}: ExportDialogProps) {
const [selectedOption, setSelectedOption] = useState<ExportOption>("1");
const [name, setName] = useState("");
const onStartExport = useCallback(() => {
const now = new Date();
let end = now.getTime() / 1000;
const onSelectTime = useCallback(
(option: ExportOption) => {
setSelectedOption(option);
let start;
switch (selectedOption) {
case "1":
now.setHours(now.getHours() - 1);
start = now.getTime() / 1000;
break;
case "4":
now.setHours(now.getHours() - 4);
start = now.getTime() / 1000;
break;
case "8":
now.setHours(now.getHours() - 8);
start = now.getTime() / 1000;
break;
case "12":
now.setHours(now.getHours() - 12);
start = now.getTime() / 1000;
break;
case "24":
now.setHours(now.getHours() - 24);
start = now.getTime() / 1000;
break;
case "custom":
end = 0;
break;
const now = new Date(latestTime * 1000);
let start = 0;
switch (option) {
case "1":
now.setHours(now.getHours() - 1);
start = now.getTime() / 1000;
break;
case "4":
now.setHours(now.getHours() - 4);
start = now.getTime() / 1000;
break;
case "8":
now.setHours(now.getHours() - 8);
start = now.getTime() / 1000;
break;
case "12":
now.setHours(now.getHours() - 12);
start = now.getTime() / 1000;
break;
case "24":
now.setHours(now.getHours() - 24);
start = now.getTime() / 1000;
break;
}
setRange({
before: latestTime,
after: start,
});
},
[latestTime, setRange],
);
const onStartExport = useCallback(() => {
if (!range) {
toast.error("No valid time range selected", { position: "top-center" });
return;
}
axios
.post(`export/${camera}/start/${start}/end/${end}`, {
.post(`export/${camera}/start/${range.after}/end/${range.before}`, {
playback: "realtime",
name,
})
@ -97,10 +118,17 @@ export default function ExportDialog({
});
}
});
}, [camera, name, selectedOption]);
}, [camera, name, range]);
return (
<Dialog open={mode == "select"}>
<Dialog
open={mode == "select"}
onOpenChange={(open) => {
if (!open) {
setMode("none");
}
}}
>
<DialogTrigger asChild>
<Button
className="flex items-center gap-2"
@ -109,7 +137,7 @@ export default function ExportDialog({
onClick={() => setMode("select")}
>
<FaArrowDown className="p-1 fill-secondary bg-muted-foreground rounded-md" />
Export
{mode != "timeline" ? "Export" : "Save"}
</Button>
</DialogTrigger>
<DialogContent>
@ -117,7 +145,7 @@ export default function ExportDialog({
<DialogTitle>Export</DialogTitle>
</DialogHeader>
<RadioGroup
onValueChange={(value) => setSelectedOption(value as ExportOption)}
onValueChange={(value) => onSelectTime(value as ExportOption)}
>
{EXPORT_OPTIONS.map((opt) => {
return (
@ -154,6 +182,7 @@ export default function ExportDialog({
size="sm"
onClick={() => {
if (selectedOption == "timeline") {
setRange({ before: currentTime + 30, after: currentTime - 30 });
setMode("timeline");
} else {
onStartExport();

View File

@ -36,6 +36,7 @@ import { IoMdArrowRoundBack } from "react-icons/io";
import { useNavigate } from "react-router-dom";
import { Toaster } from "@/components/ui/sonner";
import useSWR from "swr";
import { TimeRange } from "@/types/timeline";
const SEGMENT_DURATION = 30;
type TimelineType = "timeline" | "events";
@ -77,10 +78,6 @@ export function RecordingView({
[reviewItems, mainCamera],
);
// export
const [exportMode, setExportMode] = useState<ExportMode>("none");
// timeline
const [timelineType, setTimelineType] = useOverlayState<TimelineType>(
@ -99,6 +96,11 @@ export function RecordingView({
[selectedRangeIdx, timeRange],
);
// export
const [exportMode, setExportMode] = useState<ExportMode>("none");
const [exportRange, setExportRange] = useState<TimeRange>();
// move to next clip
const onClipEnded = useCallback(() => {
@ -255,7 +257,11 @@ export function RecordingView({
)}
<ExportDialog
camera={mainCamera}
currentTime={currentTime}
latestTime={timeRange.end}
mode={exportMode}
range={exportRange}
setRange={setExportRange}
setMode={setExportMode}
/>
<ReviewFilterGroup
@ -327,7 +333,7 @@ export function RecordingView({
onControllerReady={(controller) => {
mainControllerRef.current = controller;
}}
isScrubbing={scrubbing}
isScrubbing={scrubbing || exportMode == "timeline"}
/>
</div>
{isDesktop && (
@ -395,8 +401,10 @@ export function RecordingView({
timeRange={timeRange}
mainCameraReviewItems={mainCameraReviewItems}
currentTime={currentTime}
exportRange={exportMode == "timeline" ? exportRange : undefined}
setCurrentTime={setCurrentTime}
setScrubbing={setScrubbing}
setExportRange={setExportRange}
/>
</div>
</div>
@ -410,8 +418,10 @@ type TimelineProps = {
timeRange: { start: number; end: number };
mainCameraReviewItems: ReviewSegment[];
currentTime: number;
exportRange?: TimeRange;
setCurrentTime: React.Dispatch<React.SetStateAction<number>>;
setScrubbing: React.Dispatch<React.SetStateAction<boolean>>;
setExportRange: (range: TimeRange) => void;
};
function Timeline({
contentRef,
@ -420,8 +430,10 @@ function Timeline({
timeRange,
mainCameraReviewItems,
currentTime,
exportRange,
setCurrentTime,
setScrubbing,
setExportRange,
}: TimelineProps) {
const { data: motionData } = useSWR<MotionData[]>([
"review/activity/motion",
@ -433,7 +445,22 @@ function Timeline({
},
]);
if (timelineType == "timeline") {
const [exportStart, setExportStartTime] = useState<number>(0);
const [exportEnd, setExportEndTime] = useState<number>(0);
useEffect(() => {
if (exportRange && exportStart != 0 && exportEnd != 0) {
if (exportRange.after != exportStart) {
setCurrentTime(exportStart);
} else if (exportRange?.before != exportEnd) {
setCurrentTime(exportEnd);
}
setExportRange({ after: exportStart, before: exportEnd });
}
}, [exportRange, exportStart, exportEnd, setExportRange, setCurrentTime]);
if (exportRange != undefined || timelineType == "timeline") {
return (
<div
className={
@ -447,7 +474,12 @@ function Timeline({
timestampSpread={15}
timelineStart={timeRange.end}
timelineEnd={timeRange.start}
showHandlebar
showHandlebar={exportRange == undefined}
showExportHandles={exportRange != undefined}
exportStartTime={exportRange?.after}
exportEndTime={exportRange?.before}
setExportStartTime={setExportStartTime}
setExportEndTime={setExportEndTime}
handlebarTime={currentTime}
setHandlebarTime={setCurrentTime}
onlyInitialHandlebarScroll={true}