mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-28 14:49:01 +03:00
lint: fixes from lint and updates to typescript linting
This commit is contained in:
@@ -2,7 +2,7 @@ import { h } from 'preact';
|
||||
|
||||
interface BubbleButtonProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
children?: JSX.Element;
|
||||
children?: preact.JSX.Element;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
@@ -22,12 +22,10 @@ export const BubbleButton = ({
|
||||
|
||||
if (disabled) {
|
||||
computedClass += ' text-gray-200 dark:text-gray-200';
|
||||
} else {
|
||||
if (variant === 'primary') {
|
||||
computedClass += ` ${PRIMARY_CLASS}`;
|
||||
} else if (variant === 'secondary') {
|
||||
computedClass += ` ${SECONDARY_CLASS}`;
|
||||
}
|
||||
} else if (variant === 'primary') {
|
||||
computedClass += ` ${PRIMARY_CLASS}`;
|
||||
} else if (variant === 'secondary') {
|
||||
computedClass += ` ${SECONDARY_CLASS}`;
|
||||
}
|
||||
|
||||
const onClickHandler = () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { h } from 'preact';
|
||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
||||
import { useApiHost } from '../../api';
|
||||
import { isNullOrUndefined } from '../../utils/objectUtils';
|
||||
|
||||
interface OnTimeUpdateEvent {
|
||||
timestamp: number;
|
||||
@@ -17,13 +18,11 @@ interface HistoryVideoProps {
|
||||
id: string;
|
||||
isPlaying: boolean;
|
||||
currentTime: number;
|
||||
onTimeUpdate: (event: OnTimeUpdateEvent) => void;
|
||||
onTimeUpdate?: (event: OnTimeUpdateEvent) => void;
|
||||
onPause: () => void;
|
||||
onPlay: () => void;
|
||||
}
|
||||
|
||||
const isNullOrUndefined = (object: any): boolean => object === null || object === undefined;
|
||||
|
||||
export const HistoryVideo = ({
|
||||
id,
|
||||
isPlaying: videoIsPlaying,
|
||||
@@ -47,7 +46,7 @@ export const HistoryVideo = ({
|
||||
setVideoHeight(videoHeight);
|
||||
}
|
||||
}
|
||||
}, [videoRef.current]);
|
||||
}, [videoRef]);
|
||||
|
||||
useEffect(() => {
|
||||
initializeVideoContainerHeight();
|
||||
@@ -56,6 +55,10 @@ export const HistoryVideo = ({
|
||||
useEffect(() => {
|
||||
const idExists = !isNullOrUndefined(id);
|
||||
if (idExists) {
|
||||
if (videoRef.current && !videoRef.current.paused) {
|
||||
videoRef.current = undefined;
|
||||
}
|
||||
|
||||
setVideoProperties({
|
||||
posterUrl: `${apiHost}/api/events/${id}/snapshot.jpg`,
|
||||
videoUrl: `${apiHost}/vod/event/${id}/index.m3u8`,
|
||||
@@ -64,28 +67,15 @@ export const HistoryVideo = ({
|
||||
} else {
|
||||
setVideoProperties(undefined);
|
||||
}
|
||||
}, [id, videoHeight]);
|
||||
}, [id, videoHeight, videoRef, apiHost]);
|
||||
|
||||
useEffect(() => {
|
||||
const playVideo = (video: HTMLMediaElement) => {
|
||||
console.debug('playVideo: attempt playback');
|
||||
video
|
||||
.play()
|
||||
.then(() => {
|
||||
console.debug('playVideo: video started');
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('Fail', { e });
|
||||
});
|
||||
};
|
||||
const playVideo = (video: HTMLMediaElement) => video.play();
|
||||
|
||||
const attemptPlayVideo = (video: HTMLMediaElement) => {
|
||||
const videoHasNotLoaded = video.readyState <= 1;
|
||||
console.debug('playVideo', { videoHasNotLoaded });
|
||||
if (videoHasNotLoaded) {
|
||||
console.debug('playVideo: attempt to load video');
|
||||
video.oncanplay = () => {
|
||||
console.debug('onLoad: video loaded');
|
||||
playVideo(video);
|
||||
};
|
||||
video.load();
|
||||
@@ -97,7 +87,6 @@ export const HistoryVideo = ({
|
||||
const video = videoRef.current;
|
||||
const videoExists = !isNullOrUndefined(video);
|
||||
if (videoExists) {
|
||||
console.log('check should start', { videoIsPlaying });
|
||||
if (videoIsPlaying) {
|
||||
attemptPlayVideo(video);
|
||||
} else {
|
||||
@@ -122,19 +111,22 @@ export const HistoryVideo = ({
|
||||
isPlaying: videoIsPlaying,
|
||||
timestamp: target.currentTime,
|
||||
};
|
||||
onTimeUpdate(timeUpdateEvent);
|
||||
|
||||
onTimeUpdate && onTimeUpdate(timeUpdateEvent);
|
||||
},
|
||||
[videoIsPlaying]
|
||||
[videoIsPlaying, onTimeUpdate]
|
||||
);
|
||||
|
||||
const videoPropertiesIsUndefined = isNullOrUndefined(videoProperties);
|
||||
if (videoPropertiesIsUndefined) {
|
||||
return <div style={{ height: `${videoHeight}px`, width: '100%' }}></div>;
|
||||
return <div style={{ height: `${videoHeight}px`, width: '100%' }} />;
|
||||
}
|
||||
|
||||
const { posterUrl, videoUrl, height } = videoProperties;
|
||||
return (
|
||||
<video
|
||||
ref={videoRef}
|
||||
key={posterUrl}
|
||||
onTimeUpdate={onTimeUpdateHandler}
|
||||
onPause={onPause}
|
||||
onPlay={onPlay}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Fragment, h } from 'preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks';
|
||||
import { useEvents } from '../../api';
|
||||
import { useSearchString } from '../../hooks/useSearchString';
|
||||
import { getNowYesterdayInLong } from '../../utils/dateUtil';
|
||||
@@ -25,18 +25,19 @@ export default function HistoryViewer({ camera }) {
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
const onTimeUpdateHandler = ({ timestamp, isPlaying }) => {};
|
||||
const handleTimelineChange = useCallback(
|
||||
(event: TimelineChangeEvent) => {
|
||||
if (event.seekComplete) {
|
||||
setCurrentEvent(event.timelineEvent);
|
||||
|
||||
const handleTimelineChange = (event: TimelineChangeEvent) => {
|
||||
if (event.seekComplete) {
|
||||
setCurrentEvent(event.timelineEvent);
|
||||
|
||||
if (isPlaying && event.timelineEvent) {
|
||||
const eventTime = (event.markerTime.getTime() - event.timelineEvent.startTime.getTime()) / 1000;
|
||||
setCurrentTime(eventTime);
|
||||
if (isPlaying && event.timelineEvent) {
|
||||
const eventTime = (event.markerTime.getTime() - event.timelineEvent.startTime.getTime()) / 1000;
|
||||
setCurrentTime(eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
[isPlaying]
|
||||
);
|
||||
|
||||
const onPlayHandler = () => {
|
||||
setIsPlaying(true);
|
||||
@@ -47,7 +48,6 @@ export default function HistoryViewer({ camera }) {
|
||||
};
|
||||
|
||||
const onPlayPauseHandler = (isPlaying: boolean) => {
|
||||
console.debug('onPlayPauseHandler: setting isPlaying', { isPlaying });
|
||||
setIsPlaying(isPlaying);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,6 @@ export default function HistoryViewer({ camera }) {
|
||||
id={currentEvent ? currentEvent.id : undefined}
|
||||
isPlaying={isPlaying}
|
||||
currentTime={currentTime}
|
||||
onTimeUpdate={onTimeUpdateHandler}
|
||||
onPlay={onPlayHandler}
|
||||
onPause={onPausedHandler}
|
||||
/>
|
||||
|
||||
@@ -9,7 +9,7 @@ export function LiveChip({ className }) {
|
||||
class='animate-ping absolute inline-flex h-full w-full rounded-full opacity-75'
|
||||
style={{ backgroundColor: 'rgb(74 222 128)' }}
|
||||
/>
|
||||
<span class='relative inline-flex rounded-full h-3 w-3' style={{ backgroundColor: 'rgb(74 222 128)' }}/>
|
||||
<span class='relative inline-flex rounded-full h-3 w-3' style={{ backgroundColor: 'rgb(74 222 128)' }} />
|
||||
</span>
|
||||
</div>
|
||||
<span>Live</span>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Fragment, h } from 'preact';
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { longToDate } from '../../utils/dateUtil';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { getTimelineEventBlocksFromTimelineEvents } from '../../utils/Timeline/timelineEventUtils';
|
||||
import { ScrollPermission } from './ScrollPermission';
|
||||
import { TimelineBlocks } from './TimelineBlocks';
|
||||
import { TimelineChangeEvent } from './TimelineChangeEvent';
|
||||
import { DisabledControls, TimelineControls } from './TimelineControls';
|
||||
@@ -11,7 +12,7 @@ interface TimelineProps {
|
||||
events: TimelineEvent[];
|
||||
isPlaying: boolean;
|
||||
onChange: (event: TimelineChangeEvent) => void;
|
||||
onPlayPause: (isPlaying: boolean) => void;
|
||||
onPlayPause?: (isPlaying: boolean) => void;
|
||||
}
|
||||
|
||||
export default function Timeline({ events, isPlaying, onChange, onPlayPause }: TimelineProps) {
|
||||
@@ -26,12 +27,36 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
const [timelineOffset, setTimelineOffset] = useState<number | undefined>(undefined);
|
||||
const [markerTime, setMarkerTime] = useState<Date | undefined>(undefined);
|
||||
const [currentEvent, setCurrentEvent] = useState<TimelineEventBlock | undefined>(undefined);
|
||||
const [scrollTimeout, setScrollTimeout] = useState<any | undefined>(undefined);
|
||||
const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | undefined>(undefined);
|
||||
const [scrollPermission, setScrollPermission] = useState<ScrollPermission>({
|
||||
allowed: true,
|
||||
resetAfterSeeked: false,
|
||||
});
|
||||
|
||||
const scrollToPosition = useCallback(
|
||||
(positionX: number) => {
|
||||
if (timelineContainerRef.current) {
|
||||
const permission: ScrollPermission = {
|
||||
allowed: true,
|
||||
resetAfterSeeked: true,
|
||||
};
|
||||
setScrollPermission(permission);
|
||||
timelineContainerRef.current.scroll({
|
||||
left: positionX,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
},
|
||||
[timelineContainerRef]
|
||||
);
|
||||
|
||||
const scrollToEvent = useCallback(
|
||||
(event, offset = 0) => {
|
||||
scrollToPosition(event.positionX + offset - timelineOffset);
|
||||
},
|
||||
[timelineOffset, scrollToPosition]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (timeline.length > 0 && currentEvent) {
|
||||
const currentIndex = currentEvent.index;
|
||||
@@ -57,86 +82,18 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
}
|
||||
}, [timeline, currentEvent]);
|
||||
|
||||
const checkEventForOverlap = (firstEvent: TimelineEvent, secondEvent: TimelineEvent) => {
|
||||
if (secondEvent.startTime < firstEvent.endTime && secondEvent.startTime > firstEvent.startTime) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const determineOffset = (currentEvent: TimelineEventBlock, previousEvents: TimelineEventBlock[]): number => {
|
||||
const OFFSET_DISTANCE_IN_PIXELS = 10;
|
||||
const previousIndex = previousEvents.length - 1;
|
||||
const previousEvent = previousEvents[previousIndex];
|
||||
if (previousEvent) {
|
||||
const isOverlap = checkEventForOverlap(previousEvent, currentEvent);
|
||||
if (isOverlap) {
|
||||
return OFFSET_DISTANCE_IN_PIXELS + determineOffset(currentEvent, previousEvents.slice(0, previousIndex));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const buildTimelineView = (events: TimelineEvent[]): TimelineEventBlock[] => {
|
||||
const firstEvent = events[0];
|
||||
const firstEventTime = longToDate(firstEvent.start_time);
|
||||
return events
|
||||
.map((e, index) => {
|
||||
const startTime = longToDate(e.start_time);
|
||||
const endTime = e.end_time ? longToDate(e.end_time) : new Date();
|
||||
const seconds = Math.round(Math.abs(endTime.getTime() - startTime.getTime()) / 1000);
|
||||
const positionX = Math.round(Math.abs(startTime.getTime() - firstEventTime.getTime()) / 1000 + timelineOffset);
|
||||
return {
|
||||
...e,
|
||||
startTime,
|
||||
endTime,
|
||||
width: seconds,
|
||||
positionX,
|
||||
index,
|
||||
} as TimelineEventBlock;
|
||||
})
|
||||
.reduce((rowMap, current) => {
|
||||
for (let i = 0; i < rowMap.length; i++) {
|
||||
const row = rowMap[i] ?? [];
|
||||
const lastItem = row[row.length - 1];
|
||||
if (lastItem) {
|
||||
const isOverlap = checkEventForOverlap(lastItem, current);
|
||||
if (isOverlap) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rowMap[i] = [...row, current];
|
||||
return rowMap;
|
||||
}
|
||||
rowMap.push([current]);
|
||||
return rowMap;
|
||||
}, [] as TimelineEventBlock[][])
|
||||
.flatMap((rows, rowPosition) => {
|
||||
rows.forEach((eventBlock) => {
|
||||
const OFFSET_DISTANCE_IN_PIXELS = 10;
|
||||
eventBlock.yOffset = OFFSET_DISTANCE_IN_PIXELS * rowPosition;
|
||||
});
|
||||
return rows;
|
||||
})
|
||||
.sort((a, b) => a.startTime.getTime() - b.startTime.getTime());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (events && events.length > 0 && timelineOffset) {
|
||||
const timelineEvents = buildTimelineView(events);
|
||||
const timelineEvents = getTimelineEventBlocksFromTimelineEvents(events, timelineOffset);
|
||||
const lastEventIndex = timelineEvents.length - 1;
|
||||
const recentEvent = timelineEvents[lastEventIndex];
|
||||
|
||||
setTimeline(timelineEvents);
|
||||
setMarkerTime(recentEvent.startTime);
|
||||
setCurrentEvent(recentEvent);
|
||||
onChange({
|
||||
timelineEvent: recentEvent,
|
||||
markerTime: recentEvent.startTime,
|
||||
seekComplete: true,
|
||||
});
|
||||
scrollToEvent(recentEvent);
|
||||
}
|
||||
}, [events, timelineOffset]);
|
||||
}, [events, timelineOffset, scrollToEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
const timelineIsLoaded = timeline.length > 0;
|
||||
@@ -144,7 +101,7 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
const lastEvent = timeline[timeline.length - 1];
|
||||
scrollToEvent(lastEvent);
|
||||
}
|
||||
}, [timeline]);
|
||||
}, [timeline, scrollToEvent]);
|
||||
|
||||
const checkMarkerForEvent = (markerTime: Date) => {
|
||||
const adjustedMarkerTime = new Date(markerTime);
|
||||
@@ -197,32 +154,14 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToPosition = (positionX: number) => {
|
||||
if (timelineContainerRef.current) {
|
||||
const permission: ScrollPermission = {
|
||||
allowed: true,
|
||||
resetAfterSeeked: true,
|
||||
};
|
||||
setScrollPermission(permission);
|
||||
timelineContainerRef.current.scroll({
|
||||
left: positionX,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToEvent = (event, offset = 0) => {
|
||||
scrollToPosition(event.positionX + offset - timelineOffset);
|
||||
};
|
||||
|
||||
const getCurrentMarkerTime = () => {
|
||||
const getCurrentMarkerTime = useCallback(() => {
|
||||
if (timelineContainerRef.current && timeline.length > 0) {
|
||||
const scrollPosition = timelineContainerRef.current.scrollLeft;
|
||||
const firstTimelineEvent = timeline[0] as TimelineEventBlock;
|
||||
const firstTimelineEventStartTime = firstTimelineEvent.startTime.getTime();
|
||||
return new Date(firstTimelineEventStartTime + scrollPosition * 1000);
|
||||
}
|
||||
};
|
||||
}, [timeline, timelineContainerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (timelineContainerRef) {
|
||||
@@ -232,10 +171,13 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
}
|
||||
}, [timelineContainerRef]);
|
||||
|
||||
const handleViewEvent = (event: TimelineEventBlock) => {
|
||||
scrollToEvent(event);
|
||||
setMarkerTime(getCurrentMarkerTime());
|
||||
};
|
||||
const handleViewEvent = useCallback(
|
||||
(event: TimelineEventBlock) => {
|
||||
scrollToEvent(event);
|
||||
setMarkerTime(getCurrentMarkerTime());
|
||||
},
|
||||
[scrollToEvent, getCurrentMarkerTime]
|
||||
);
|
||||
|
||||
const onPlayPauseHandler = (isPlaying: boolean) => {
|
||||
onPlayPause(isPlaying);
|
||||
@@ -260,7 +202,7 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
if (timelineOffset && timeline.length > 0) {
|
||||
return <TimelineBlocks timeline={timeline} firstBlockOffset={timelineOffset} onEventClick={handleViewEvent} />;
|
||||
}
|
||||
}, [timeline, timelineOffset]);
|
||||
}, [timeline, timelineOffset, handleViewEvent]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -279,7 +221,7 @@ export default function Timeline({ events, isPlaying, onChange, onPlayPause }: T
|
||||
left: 'calc(100% / 2)',
|
||||
borderRight: '2px solid rgba(252, 211, 77)',
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={timelineContainerRef} onScroll={onTimelineScrollHandler} className='overflow-x-auto hide-scroll'>
|
||||
|
||||
@@ -9,7 +9,7 @@ interface TimelineBlockViewProps {
|
||||
}
|
||||
|
||||
export const TimelineBlockView = ({ block, onClick }: TimelineBlockViewProps) => {
|
||||
const onClickHandler = useCallback(() => onClick(block), [block]);
|
||||
const onClickHandler = useCallback(() => onClick(block), [block, onClick]);
|
||||
return (
|
||||
<div
|
||||
key={block.id}
|
||||
@@ -20,6 +20,6 @@ export const TimelineBlockView = ({ block, onClick }: TimelineBlockViewProps) =>
|
||||
left: `${block.positionX}px`,
|
||||
width: `${block.width}px`,
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useConfig } from '../api';
|
||||
import { Tabs, TextTab } from '../components/Tabs';
|
||||
import { LiveChip } from '../components/LiveChip';
|
||||
import { DebugCamera } from '../components/DebugCamera';
|
||||
import { HistoryViewer } from '../components/HistoryViewer'
|
||||
import HistoryViewer from '../components/HistoryViewer/HistoryViewer.tsx';
|
||||
|
||||
export default function Camera({ camera }) {
|
||||
const { data: config } = useConfig();
|
||||
|
||||
Reference in New Issue
Block a user