Fix linter and fix lint issues (#10141)

This commit is contained in:
Nicolas Mowen
2024-02-28 15:23:56 -07:00
committed by GitHub
parent b6ef1e4330
commit 3bf2a496e1
63 changed files with 527 additions and 418 deletions

View File

@@ -4,7 +4,7 @@ import { useMemo } from "react";
import { MdCircle } from "react-icons/md";
import useSWR from "swr";
export default function Statusbar({}) {
export default function Statusbar() {
const { data: initialStats } = useSWR<FrigateStats>("stats", {
revalidateOnFocus: false,
});

View File

@@ -3,7 +3,7 @@ import CameraImage from "./CameraImage";
type AutoUpdatingCameraImageProps = {
camera: string;
searchParams?: {};
searchParams?: URLSearchParams;
showFps?: boolean;
className?: string;
reloadInterval?: number;
@@ -13,7 +13,7 @@ const MIN_LOAD_TIMEOUT_MS = 200;
export default function AutoUpdatingCameraImage({
camera,
searchParams = "",
searchParams = undefined,
showFps = true,
className,
reloadInterval = MIN_LOAD_TIMEOUT_MS,
@@ -35,6 +35,8 @@ export default function AutoUpdatingCameraImage({
setTimeoutId(undefined);
}
};
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reloadInterval]);
const handleLoad = useCallback(() => {
@@ -53,9 +55,11 @@ export default function AutoUpdatingCameraImage({
() => {
setKey(Date.now());
},
loadTime > reloadInterval ? 1 : reloadInterval
)
loadTime > reloadInterval ? 1 : reloadInterval,
),
);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, setFps]);
return (

View File

@@ -7,7 +7,7 @@ type CameraImageProps = {
className?: string;
camera: string;
onload?: () => void;
searchParams?: {};
searchParams?: string;
};
export default function CameraImage({

View File

@@ -24,25 +24,25 @@ export default function DebugCameraImage({
const [showSettings, setShowSettings] = useState(false);
const [options, setOptions] = usePersistence<Options>(
`${cameraConfig?.name}-feed`,
emptyObject
emptyObject,
);
const handleSetOption = useCallback(
(id: string, value: boolean) => {
const newOptions = { ...options, [id]: value };
setOptions(newOptions);
},
[options]
[options, setOptions],
);
const searchParams = useMemo(
() =>
new URLSearchParams(
Object.keys(options || {}).reduce((memo, key) => {
//@ts-ignore we know this is correct
//@ts-expect-error we know this is correct
memo.push([key, options[key] === true ? "1" : "0"]);
return memo;
}, [])
}, []),
),
[options]
[options],
);
const handleToggleSettings = useCallback(() => {
setShowSettings(!showSettings);

View File

@@ -8,7 +8,7 @@ type CameraImageProps = {
className?: string;
camera: string;
onload?: (event: Event) => void;
searchParams?: {};
searchParams?: string;
stretch?: boolean; // stretch to fit width
fitAspect?: number; // shrink to fit height
};
@@ -58,10 +58,17 @@ export default function CameraImage({
}
return 100;
}, [availableWidth, aspectRatio, height, stretch]);
}, [
availableWidth,
aspectRatio,
containerHeight,
fitAspect,
height,
stretch,
]);
const scaledWidth = useMemo(
() => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth),
[scaledHeight, aspectRatio, scrollBarWidth]
[scaledHeight, aspectRatio, scrollBarWidth],
);
const img = useMemo(() => new Image(), []);
@@ -74,7 +81,7 @@ export default function CameraImage({
}
onload && onload(event);
},
[img, scaledHeight, scaledWidth, setHasLoaded, onload, canvasRef]
[img, scaledHeight, scaledWidth, setHasLoaded, onload, canvasRef],
);
useEffect(() => {

View File

@@ -2,7 +2,7 @@ import { useFrigateReviews } from "@/api/ws";
import { ReviewSeverity } from "@/types/review";
import { Button } from "../ui/button";
import { LuRefreshCcw } from "react-icons/lu";
import { MutableRefObject, useEffect, useState } from "react";
import { MutableRefObject, useEffect, useMemo, useState } from "react";
type NewReviewDataProps = {
className: string;
@@ -18,7 +18,8 @@ export default function NewReviewData({
}: NewReviewDataProps) {
const { payload: review } = useFrigateReviews();
const [reviewId, setReviewId] = useState("");
const startCheckTs = useMemo(() => Date.now() / 1000, []);
const [reviewTs, setReviewTs] = useState(startCheckTs);
const [hasUpdate, setHasUpdate] = useState(false);
useEffect(() => {
@@ -27,15 +28,15 @@ export default function NewReviewData({
}
if (review.type == "end" && review.review.severity == severity) {
setReviewId(review.review.id);
setReviewTs(review.review.start_time);
}
}, [review]);
}, [review, severity]);
useEffect(() => {
if (reviewId != "") {
if (reviewTs > startCheckTs) {
setHasUpdate(true);
}
}, [reviewId]);
}, [startCheckTs, reviewTs]);
return (
<div className={className}>

View File

@@ -91,7 +91,7 @@ const TimeAgo: FunctionComponent<IProp> = ({
} else {
return 3600000; // refresh every hour
}
}, [currentTime, manualRefreshInterval]);
}, [currentTime, manualRefreshInterval, time]);
useEffect(() => {
const intervalId: NodeJS.Timeout = setInterval(() => {
@@ -102,7 +102,7 @@ const TimeAgo: FunctionComponent<IProp> = ({
const timeAgoValue = useMemo(
() => timeAgo({ time, currentTime, ...rest }),
[currentTime, rest]
[currentTime, rest, time],
);
return <span>{timeAgoValue}</span>;

View File

@@ -54,7 +54,7 @@ export default function ReviewFilterGroup({
cameras: Object.keys(config?.cameras || {}),
labels: Object.values(allLabels || {}),
}),
[config, allLabels]
[config, allLabels],
);
// handle updating filters
@@ -67,7 +67,7 @@ export default function ReviewFilterGroup({
before: day == undefined ? undefined : getEndOfDayTimestamp(day),
});
},
[onUpdateFilter]
[filter, onUpdateFilter],
);
return (
@@ -111,7 +111,7 @@ function CamerasFilterButton({
updateCameraFilter,
}: CameraFilterButtonProps) {
const [currentCameras, setCurrentCameras] = useState<string[] | undefined>(
selectedCameras
selectedCameras,
);
return (
@@ -200,7 +200,7 @@ function CalendarFilterButton({
}, []);
const selectedDate = useFormattedTimestamp(
day == undefined ? 0 : day?.getTime() / 1000,
"%b %-d"
"%b %-d",
);
return (
@@ -273,7 +273,7 @@ function GeneralFilterButton({
<Button
className="capitalize flex justify-between items-center cursor-pointer w-full"
variant="secondary"
onClick={(_) => setShowReviewed(showReviewed == 0 ? 1 : 0)}
onClick={() => setShowReviewed(showReviewed == 0 ? 1 : 0)}
>
{showReviewed ? (
<LuCheck className="w-6 h-6" />
@@ -299,7 +299,7 @@ function LabelsFilterButton({
updateLabelFilter,
}: LabelFilterButtonProps) {
const [currentLabels, setCurrentLabels] = useState<string[] | undefined>(
selectedLabels
selectedLabels,
);
return (
@@ -375,7 +375,7 @@ function FilterCheckBox({
<Button
className="capitalize flex justify-between items-center cursor-pointer w-full"
variant="ghost"
onClick={(_) => onCheckedChange(!isChecked)}
onClick={() => onCheckedChange(!isChecked)}
>
{isChecked ? (
<LuCheck className="w-6 h-6" />

View File

@@ -30,7 +30,7 @@ export function AnimatedEventThumbnail({ event }: AnimatedEventThumbnailProps) {
}
return `${baseUrl}api/review/${event.id}/preview.gif`;
}, [event]);
}, [apiHost, event]);
const aspectRatio = useMemo(() => {
if (!config) {
@@ -39,7 +39,7 @@ export function AnimatedEventThumbnail({ event }: AnimatedEventThumbnailProps) {
const detect = config.cameras[event.camera].detect;
return detect.width / detect.height;
}, [config]);
}, [config, event]);
return (
<Tooltip>

View File

@@ -4,20 +4,20 @@ import SettingsNavItems from "../settings/SettingsNavItems";
function Bottombar() {
return (
<div className="absolute h-16 inset-x-4 bottom-0 flex flex-row items-center justify-between">
{navbarLinks.map((item) => (
<NavItem
className=""
variant="secondary"
key={item.id}
Icon={item.icon}
title={item.title}
url={item.url}
dev={item.dev}
/>
))}
<div className="absolute h-16 inset-x-4 bottom-0 flex flex-row items-center justify-between">
{navbarLinks.map((item) => (
<NavItem
className=""
variant="secondary"
key={item.id}
Icon={item.icon}
title={item.title}
url={item.url}
dev={item.dev}
/>
))}
<SettingsNavItems className="flex flex-shrink-0 justify-between gap-4" />
</div>
</div>
);
}

View File

@@ -14,19 +14,6 @@ export default function TimelineEventOverlay({
timeline,
cameraConfig,
}: TimelineEventOverlayProps) {
if (!timeline.data.box) {
return null;
}
const boxLeftEdge = Math.round(timeline.data.box[0] * 100);
const boxTopEdge = Math.round(timeline.data.box[1] * 100);
const boxRightEdge = Math.round(
(1 - timeline.data.box[2] - timeline.data.box[0]) * 100
);
const boxBottomEdge = Math.round(
(1 - timeline.data.box[3] - timeline.data.box[1]) * 100
);
const [isHovering, setIsHovering] = useState<boolean>(false);
const getHoverStyle = () => {
if (!timeline.data.box) {
@@ -67,6 +54,19 @@ export default function TimelineEventOverlay({
return Math.round(100 * (width / height)) / 100;
};
if (!timeline.data.box) {
return null;
}
const boxLeftEdge = Math.round(timeline.data.box[0] * 100);
const boxTopEdge = Math.round(timeline.data.box[1] * 100);
const boxRightEdge = Math.round(
(1 - timeline.data.box[2] - timeline.data.box[0]) * 100,
);
const boxBottomEdge = Math.round(
(1 - timeline.data.box[3] - timeline.data.box[1]) * 100,
);
return (
<>
<div

View File

@@ -14,6 +14,9 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import ActivityIndicator from "../ui/activity-indicator";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { Recording } from "@/types/record";
import { Preview } from "@/types/preview";
import { DynamicPlayback } from "@/types/playback";
/**
* Dynamically switches between video playback and scrubbing preview player.
@@ -37,7 +40,7 @@ export default function DynamicVideoPlayer({
const timezone = useMemo(
() =>
config?.ui?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
[config]
[config],
);
// playback behavior
@@ -51,7 +54,7 @@ export default function DynamicVideoPlayer({
config.cameras[camera].detect.height <
1.7
);
}, [config]);
}, [camera, config]);
// controlling playback
@@ -60,7 +63,7 @@ export default function DynamicVideoPlayer({
const [isScrubbing, setIsScrubbing] = useState(false);
const [hasPreview, setHasPreview] = useState(false);
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
undefined
undefined,
);
const controller = useMemo(() => {
if (!config) {
@@ -72,9 +75,9 @@ export default function DynamicVideoPlayer({
previewRef,
(config.cameras[camera]?.detect?.annotation_offset || 0) / 1000,
setIsScrubbing,
setFocusedItem
setFocusedItem,
);
}, [config]);
}, [camera, config]);
// keyboard control
@@ -115,11 +118,11 @@ export default function DynamicVideoPlayer({
break;
}
},
[playerRef]
[playerRef],
);
useKeyboardListener(
["ArrowLeft", "ArrowRight", "m", " "],
onKeyboardShortcut
onKeyboardShortcut,
);
// initial state
@@ -131,16 +134,18 @@ export default function DynamicVideoPlayer({
date.getMonth() + 1
}/${date.getDate()}/${date.getHours()}/${camera}/${timezone.replaceAll(
"/",
","
",",
)}/master.m3u8`,
type: "application/vnd.apple.mpegurl",
};
// we only want to calculate this once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initialPreviewSource = useMemo(() => {
const preview = cameraPreviews.find(
(preview) =>
Math.round(preview.start) >= timeRange.start &&
Math.floor(preview.end) <= timeRange.end
Math.floor(preview.end) <= timeRange.end,
);
if (preview) {
@@ -153,6 +158,9 @@ export default function DynamicVideoPlayer({
setHasPreview(false);
return undefined;
}
// we only want to calculate this once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// state of playback player
@@ -165,7 +173,7 @@ export default function DynamicVideoPlayer({
}, [timeRange]);
const { data: recordings } = useSWR<Recording[]>(
[`${camera}/recordings`, recordingParams],
{ revalidateOnFocus: false }
{ revalidateOnFocus: false },
);
useEffect(() => {
@@ -178,13 +186,13 @@ export default function DynamicVideoPlayer({
date.getMonth() + 1
}/${date.getDate()}/${date.getHours()}/${camera}/${timezone.replaceAll(
"/",
","
",",
)}/master.m3u8`;
const preview = cameraPreviews.find(
(preview) =>
Math.round(preview.start) >= timeRange.start &&
Math.floor(preview.end) <= timeRange.end
Math.floor(preview.end) <= timeRange.end,
);
setHasPreview(preview != undefined);
@@ -193,6 +201,9 @@ export default function DynamicVideoPlayer({
playbackUri,
preview,
});
// we only want this to change when recordings update
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [controller, recordings]);
if (!controller) {
@@ -300,7 +311,7 @@ export class DynamicVideoController {
previewRef: MutableRefObject<Player | undefined>,
annotationOffset: number,
setScrubbing: (isScrubbing: boolean) => void,
setFocusedItem: (timeline: Timeline) => void
setFocusedItem: (timeline: Timeline) => void,
) {
this.playerRef = playerRef;
this.previewRef = previewRef;
@@ -437,7 +448,7 @@ export class DynamicVideoController {
this.timeToSeek = time;
} else {
this.previewRef.current?.currentTime(
Math.max(0, time - this.preview.start)
Math.max(0, time - this.preview.start),
);
this.seeking = true;
}
@@ -453,7 +464,7 @@ export class DynamicVideoController {
this.timeToSeek != this.previewRef.current?.currentTime()
) {
this.previewRef.current?.currentTime(
this.timeToSeek - this.preview.start
this.timeToSeek - this.preview.start,
);
} else {
this.seeking = false;

View File

@@ -1,6 +1,6 @@
import { baseUrl } from "@/api/baseUrl";
import { useResizeObserver } from "@/hooks/resize-observer";
// @ts-ignore we know this doesn't have types
// @ts-expect-error we know this doesn't have types
import JSMpeg from "@cycjimmy/jsmpeg-player";
import { useEffect, useMemo, useRef } from "react";
@@ -47,10 +47,10 @@ export default function JSMpegPlayer({
}
return 100;
}, [availableWidth, aspectRatio, height]);
}, [availableWidth, aspectRatio, containerHeight, height]);
const scaledWidth = useMemo(
() => Math.ceil(scaledHeight * aspectRatio - scrollBarWidth),
[scaledHeight, aspectRatio, scrollBarWidth]
[scaledHeight, aspectRatio, scrollBarWidth],
);
useEffect(() => {
@@ -62,7 +62,7 @@ export default function JSMpegPlayer({
playerRef.current,
url,
{},
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 }
{ protocols: [], audio: false, videoBufferSize: 1024 * 1024 * 4 },
);
const fullscreen = () => {
@@ -79,6 +79,7 @@ export default function JSMpegPlayer({
if (playerRef.current) {
try {
video.destroy();
// eslint-disable-next-line no-empty
} catch (e) {}
playerRef.current = null;
}

View File

@@ -36,7 +36,7 @@ export default function LivePlayer({
const cameraActive = useMemo(
() => windowVisible && (activeMotion || activeTracking),
[activeMotion, activeTracking, windowVisible]
[activeMotion, activeTracking, windowVisible],
);
// camera live state
@@ -56,6 +56,8 @@ export default function LivePlayer({
if (!cameraActive) {
setLiveReady(false);
}
// live mode won't change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cameraActive, liveReady]);
const { payload: recording } = useRecordingsState(cameraConfig.name);
@@ -76,7 +78,7 @@ export default function LivePlayer({
}
return 30000;
}, []);
}, [liveReady, cameraActive, windowVisible]);
if (!cameraConfig) {
return <ActivityIndicator />;

View File

@@ -37,8 +37,10 @@ function MSEPlayer({
const videoRef = useRef<HTMLVideoElement>(null);
const wsRef = useRef<WebSocket | null>(null);
const reconnectTIDRef = useRef<number | null>(null);
const ondataRef = useRef<((data: any) => void) | null>(null);
const onmessageRef = useRef<{ [key: string]: (msg: any) => void }>({});
const ondataRef = useRef<((data: ArrayBufferLike) => void) | null>(null);
const onmessageRef = useRef<{
[key: string]: (msg: { value: string; type: string }) => void;
}>({});
const msRef = useRef<MediaSource | null>(null);
const wsURL = useMemo(() => {
@@ -49,7 +51,7 @@ function MSEPlayer({
const currentVideo = videoRef.current;
if (currentVideo) {
currentVideo.play().catch((er: any) => {
currentVideo.play().catch((er: { name: string }) => {
if (er.name === "NotAllowedError" && !currentVideo.muted) {
currentVideo.muted = true;
currentVideo.play().catch(() => {});
@@ -59,16 +61,19 @@ function MSEPlayer({
};
const send = useCallback(
(value: any) => {
(value: object) => {
if (wsRef.current) wsRef.current.send(JSON.stringify(value));
},
[wsRef]
[wsRef],
);
const codecs = useCallback((isSupported: (type: string) => boolean) => {
return CODECS.filter((codec) =>
isSupported(`video/mp4; codecs="${codec}"`)
isSupported(`video/mp4; codecs="${codec}"`),
).join();
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onConnect = useCallback(() => {
@@ -76,6 +81,8 @@ function MSEPlayer({
setWsState(WebSocket.CONNECTING);
// TODO may need to check this later
// eslint-disable-next-line
connectTS = Date.now();
wsRef.current = new WebSocket(wsURL);
@@ -110,6 +117,8 @@ function MSEPlayer({
onmessageRef.current = {};
onMse();
// only run once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onClose = useCallback(() => {
@@ -135,11 +144,11 @@ function MSEPlayer({
() => {
send({
type: "mse",
// @ts-ignore
// @ts-expect-error for typing
value: codecs(MediaSource.isTypeSupported),
});
},
{ once: true }
{ once: true },
);
if (videoRef.current) {
@@ -156,7 +165,7 @@ function MSEPlayer({
value: codecs(MediaSource.isTypeSupported),
});
},
{ once: true }
{ once: true },
);
videoRef.current!.src = URL.createObjectURL(msRef.current!);
videoRef.current!.srcObject = null;
@@ -184,6 +193,7 @@ function MSEPlayer({
}
}
} catch (e) {
// eslint-disable-next-line no-console
console.debug(e);
}
});
@@ -201,6 +211,7 @@ function MSEPlayer({
try {
sb?.appendBuffer(data);
} catch (e) {
// eslint-disable-next-line no-console
console.debug(e);
}
}
@@ -217,7 +228,7 @@ function MSEPlayer({
const MediaSourceConstructor =
"ManagedMediaSource" in window ? window.ManagedMediaSource : MediaSource;
// @ts-ignore
// @ts-expect-error for typing
msRef.current = new MediaSourceConstructor();
if ("hidden" in document && visibilityCheck) {
@@ -241,7 +252,7 @@ function MSEPlayer({
}
});
},
{ threshold: visibilityThreshold }
{ threshold: visibilityThreshold },
);
observer.observe(videoRef.current!);
}
@@ -251,6 +262,8 @@ function MSEPlayer({
return () => {
onDisconnect();
};
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playbackEnabled, onDisconnect, onConnect]);
return (

View File

@@ -78,13 +78,13 @@ export default function PreviewThumbnailPlayer({
const playingBack = useMemo(() => playback, [playback]);
const onPlayback = useCallback(
(isHovered: Boolean) => {
(isHovered: boolean) => {
if (isHovered) {
setHoverTimeout(
setTimeout(() => {
setPlayback(true);
setHoverTimeout(null);
}, 500)
}, 500),
);
} else {
if (hoverTimeout) {
@@ -95,14 +95,17 @@ export default function PreviewThumbnailPlayer({
setProgress(0);
}
},
[hoverTimeout, review]
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[hoverTimeout, review],
);
// date
const formattedDate = useFormattedTimestamp(
review.start_time,
config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p"
config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p",
);
return (
@@ -134,7 +137,7 @@ export default function PreviewThumbnailPlayer({
}`}
src={`${apiHost}${review.thumb_path.replace(
"/media/frigate/",
""
"",
)}`}
loading={isSafari ? "eager" : "lazy"}
onLoad={() => {
@@ -215,8 +218,11 @@ function PreviewContent({
// start with a bit of padding
return Math.max(
0,
review.start_time - relevantPreview.start - PREVIEW_PADDING
review.start_time - relevantPreview.start - PREVIEW_PADDING,
);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [lastPercent, setLastPercent] = useState(0.0);
@@ -234,6 +240,9 @@ function PreviewContent({
playerRef.current.currentTime = playerStartTime;
playerRef.current.playbackRate = 8;
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playerRef]);
// time progress update
@@ -269,6 +278,9 @@ function PreviewContent({
} else {
setProgress(playerPercent);
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setProgress, lastPercent]);
// manual playback
@@ -289,6 +301,9 @@ function PreviewContent({
}
}, 125);
return () => clearInterval(intervalId);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [manualPlayback, playerRef]);
// preview
@@ -333,7 +348,7 @@ function InProgressPreview({
const { data: previewFrames } = useSWR<string[]>(
`preview/${review.camera}/start/${Math.floor(review.start_time) - 4}/end/${
Math.ceil(review.end_time) + 4
}/frames`
}/frames`,
);
const [key, setKey] = useState(0);
@@ -361,6 +376,9 @@ function InProgressPreview({
setKey(key + 1);
}, MIN_LOAD_TIMEOUT_MS);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, previewFrames]);
if (!previewFrames || previewFrames.length == 0) {
@@ -394,7 +412,7 @@ function PreviewContextItems({
const exportReview = useCallback(() => {
axios.post(
`export/${review.camera}/start/${review.start_time}/end/${review.end_time}`,
{ playback: "realtime" }
{ playback: "realtime" },
);
}, [review]);

View File

@@ -7,7 +7,7 @@ import Player from "video.js/dist/types/player";
type VideoPlayerProps = {
children?: ReactElement | ReactElement[];
options?: {
[key: string]: any;
[key: string]: unknown;
};
seekOptions?: {
forward?: number;
@@ -23,7 +23,7 @@ export default function VideoPlayer({
options,
seekOptions = { forward: 30, backward: 10 },
remotePlayback = false,
onReady = (_) => {},
onReady = () => {},
onDispose = () => {},
}: VideoPlayerProps) {
const videoRef = useRef<HTMLDivElement | null>(null);
@@ -47,7 +47,7 @@ export default function VideoPlayer({
if (!playerRef.current) {
// The Video.js player needs to be _inside_ the component el for React 18 Strict Mode.
const videoElement = document.createElement(
"video-js"
"video-js",
) as HTMLVideoElement;
videoElement.controls = true;
videoElement.playsInline = true;
@@ -62,9 +62,12 @@ export default function VideoPlayer({
{ ...defaultOptions, ...options },
() => {
onReady && onReady(player);
}
},
));
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [options, videoRef]);
// Dispose the Video.js player when the functional component unmounts
@@ -78,6 +81,9 @@ export default function VideoPlayer({
onDispose();
}
};
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playerRef]);
return (

View File

@@ -58,7 +58,7 @@ export default function WebRtcPlayer({
.filter((kind) => media.indexOf(kind) >= 0)
.map(
(kind) =>
pc.addTransceiver(kind, { direction: "recvonly" }).receiver.track
pc.addTransceiver(kind, { direction: "recvonly" }).receiver.track,
);
localTracks.push(...tracks);
}
@@ -66,12 +66,12 @@ export default function WebRtcPlayer({
videoRef.current.srcObject = new MediaStream(localTracks);
return pc;
},
[videoRef]
[videoRef],
);
async function getMediaTracks(
media: string,
constraints: MediaStreamConstraints
constraints: MediaStreamConstraints,
) {
try {
const stream =
@@ -126,7 +126,7 @@ export default function WebRtcPlayer({
}
});
},
[]
[],
);
useEffect(() => {
@@ -140,7 +140,7 @@ export default function WebRtcPlayer({
const url = `${baseUrl.replace(
/^http/,
"ws"
"ws",
)}live/webrtc/api/ws?src=${camera}`;
const ws = new WebSocket(url);
const aPc = PeerConnection("video+audio");

View File

@@ -52,12 +52,12 @@ export function EventReviewTimeline({
const observer = useRef<ResizeObserver | null>(null);
const timelineDuration = useMemo(
() => timelineStart - timelineEnd,
[timelineEnd, timelineStart]
[timelineEnd, timelineStart],
);
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
events,
segmentDuration
segmentDuration,
);
const { handleMouseDown, handleMouseUp, handleMouseMove } =
@@ -79,6 +79,7 @@ export function EventReviewTimeline({
function handleResize() {
// TODO: handle screen resize for mobile
// eslint-disable-next-line no-empty
if (timelineRef.current && contentRef.current) {
}
}
@@ -94,6 +95,8 @@ export function EventReviewTimeline({
observer.current?.unobserve(content);
};
}
// should only be calculated at beginning
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Generate segments for the timeline
@@ -119,6 +122,8 @@ export function EventReviewTimeline({
/>
);
});
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
segmentDuration,
timestampSpread,
@@ -132,6 +137,8 @@ export function EventReviewTimeline({
const segments = useMemo(
() => generateSegments(),
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[
segmentDuration,
timestampSpread,
@@ -141,7 +148,7 @@ export function EventReviewTimeline({
minimapStartTime,
minimapEndTime,
events,
]
],
);
useEffect(() => {
@@ -149,7 +156,7 @@ export function EventReviewTimeline({
requestAnimationFrame(() => {
if (currentTimeRef.current && currentTimeSegment) {
currentTimeRef.current.textContent = new Date(
currentTimeSegment * 1000
currentTimeSegment * 1000,
).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
@@ -158,6 +165,8 @@ export function EventReviewTimeline({
}
});
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentTimeSegment, showHandlebar]);
useEffect(() => {
@@ -177,7 +186,7 @@ export function EventReviewTimeline({
// Calculate the segment index corresponding to the target time
const alignedHandlebarTime = alignStartDateToTimeline(handlebarTime);
const segmentIndex = Math.ceil(
(timelineStart - alignedHandlebarTime) / segmentDuration
(timelineStart - alignedHandlebarTime) / segmentDuration,
);
// Calculate the top position based on the segment index
@@ -193,6 +202,8 @@ export function EventReviewTimeline({
setCurrentTimeSegment(alignedHandlebarTime);
}
// should only be run once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
@@ -207,6 +218,8 @@ export function EventReviewTimeline({
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
currentTimeSegment,
generateSegments,

View File

@@ -150,17 +150,19 @@ export function EventSegment({
const { alignStartDateToTimeline, alignEndDateToTimeline } = useEventUtils(
events,
segmentDuration
segmentDuration,
);
const severity = useMemo(
() => getSeverity(segmentTime, displaySeverityType),
[getSeverity, segmentTime]
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[getSeverity, segmentTime],
);
const reviewed = useMemo(
() => getReviewed(segmentTime),
[getReviewed, segmentTime]
[getReviewed, segmentTime],
);
const {
@@ -170,7 +172,7 @@ export function EventSegment({
roundBottomSecondary,
} = useMemo(
() => shouldShowRoundedCorners(segmentTime),
[shouldShowRoundedCorners, segmentTime]
[shouldShowRoundedCorners, segmentTime],
);
const startTimestamp = useMemo(() => {
@@ -178,6 +180,8 @@ export function EventSegment({
if (eventStart) {
return alignStartDateToTimeline(eventStart);
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getEventStart, segmentTime]);
const apiHost = useApiHost();
@@ -191,11 +195,11 @@ export function EventSegment({
const alignedMinimapStartTime = useMemo(
() => alignStartDateToTimeline(minimapStartTime ?? 0),
[minimapStartTime, alignStartDateToTimeline]
[minimapStartTime, alignStartDateToTimeline],
);
const alignedMinimapEndTime = useMemo(
() => alignEndDateToTimeline(minimapEndTime ?? 0),
[minimapEndTime, alignEndDateToTimeline]
[minimapEndTime, alignEndDateToTimeline],
);
const isInMinimapRange = useMemo(() => {
@@ -236,6 +240,8 @@ export function EventSegment({
if (firstSegment && showMinimap && isFirstSegmentInMinimap) {
debounceScrollIntoView(firstSegment);
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showMinimap, isFirstSegmentInMinimap, events, segmentDuration]);
const segmentClasses = `h-2 relative w-full ${
@@ -267,13 +273,13 @@ export function EventSegment({
const segmentClick = useCallback(() => {
if (contentRef.current && startTimestamp) {
const element = contentRef.current.querySelector(
`[data-segment-start="${startTimestamp - segmentDuration}"]`
`[data-segment-start="${startTimestamp - segmentDuration}"]`,
);
if (element instanceof HTMLElement) {
debounceScrollIntoView(element);
element.classList.add(
`outline-severity_${severityType}`,
`shadow-severity_${severityType}`
`shadow-severity_${severityType}`,
);
element.classList.add("outline-4", "shadow-[0_0_6px_1px]");
element.classList.remove("outline-0", "shadow-none");
@@ -285,6 +291,8 @@ export function EventSegment({
}, 3000);
}
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [startTimestamp]);
return (