chore: proper formatting

This commit is contained in:
JohnMark Sill 2022-01-12 17:10:59 -06:00
parent c7d632ec3b
commit 37384cfe33
2 changed files with 159 additions and 136 deletions

View File

@ -6,13 +6,11 @@ import { Next } from '../icons/Next';
import { Play } from '../icons/Play'; import { Play } from '../icons/Play';
import { Previous } from '../icons/Previous'; import { Previous } from '../icons/Previous';
import { TextTab } from './Tabs'; import { TextTab } from './Tabs';
import { longToDate } from '../utils/dateUtil' import { longToDate } from '../utils/dateUtil';
export default function Timeline({ camera, onChange }) { export default function Timeline({ events, onChange }) {
const timelineContainerRef = useRef(undefined); const timelineContainerRef = useRef(undefined);
const { searchString } = useSearchString(25, `camera=${camera}`);
const { data: events, status } = useEvents(searchString);
const [timeline, setTimeline] = useState([]); const [timeline, setTimeline] = useState([]);
const [timelineOffset, setTimelineOffset] = useState(); const [timelineOffset, setTimelineOffset] = useState();
const [markerTime, setMarkerTime] = useState(); const [markerTime, setMarkerTime] = useState();
@ -21,19 +19,19 @@ export default function Timeline({ camera, onChange }) {
const [scrollActive, setScrollActive] = useState(true); const [scrollActive, setScrollActive] = useState(true);
useEffect(() => { useEffect(() => {
if (status === FetchStatus.LOADED && timelineOffset) { if (events && timelineOffset) {
const filteredEvents = [...events].reverse().filter(e => e.end_time !== undefined) const filteredEvents = [...events].reverse().filter((e) => e.end_time !== undefined);
const firstEvent = events[events.length - 1]; const firstEvent = events[events.length - 1];
if (firstEvent) { if (firstEvent) {
setMarkerTime(longToDate(firstEvent.start_time)) setMarkerTime(longToDate(firstEvent.start_time));
} }
const firstEventTime = longToDate(firstEvent.start_time); const firstEventTime = longToDate(firstEvent.start_time);
const eventsMap = filteredEvents.map((e, i) => { const eventsMap = filteredEvents.map((e, i) => {
const startTime = longToDate(e.start_time) const startTime = longToDate(e.start_time);
const endTime = e.end_time ? longToDate(e.end_time) : new Date(); const endTime = e.end_time ? longToDate(e.end_time) : new Date();
const seconds = Math.round(Math.abs(endTime - startTime) / 1000); const seconds = Math.round(Math.abs(endTime - startTime) / 1000);
const positionX = Math.round((Math.abs(startTime - firstEventTime) / 1000) + timelineOffset); const positionX = Math.round(Math.abs(startTime - firstEventTime) / 1000 + timelineOffset);
return { return {
...e, ...e,
startTime, startTime,
@ -41,146 +39,153 @@ export default function Timeline({ camera, onChange }) {
seconds, seconds,
width: seconds, width: seconds,
positionX, positionX,
} };
}) });
const recentEvent = eventsMap[eventsMap.length - 1] const recentEvent = eventsMap[eventsMap.length - 1];
const event = { const event = {
...recentEvent, ...recentEvent,
id: recentEvent.id, id: recentEvent.id,
index: eventsMap.length - 1, index: eventsMap.length - 1,
startTime: recentEvent.start_time, startTime: recentEvent.start_time,
endTime: recentEvent.end_time endTime: recentEvent.end_time,
};
setCurrentEvent(event);
setTimeline(eventsMap);
} }
setCurrentEvent(event) }, [events, timelineOffset]);
setTimeline(eventsMap)
}
}, [events, timelineOffset])
const checkMarkerForEvent = (markerTime) => { const checkMarkerForEvent = (markerTime) => {
if (!scrollActive) { if (!scrollActive) {
setScrollActive(true) setScrollActive(true);
return return;
} }
if (timeline) { if (timeline) {
const foundIndex = timeline.findIndex((event => event.startTime <= markerTime && markerTime <= event.endTime)) const foundIndex = timeline.findIndex((event) => event.startTime <= markerTime && markerTime <= event.endTime);
if (foundIndex > -1) { if (foundIndex > -1) {
const found = timeline[foundIndex] const found = timeline[foundIndex];
setCurrentEvent({ setCurrentEvent({
...found, ...found,
id: found.id, id: found.id,
index: foundIndex, index: foundIndex,
startTime: found.start_time, startTime: found.start_time,
endTime: found.end_time endTime: found.end_time,
}) });
}
} }
} }
};
const handleScroll = event => { const handleScroll = (event) => {
clearTimeout(scrollTimeout) clearTimeout(scrollTimeout);
const scrollPosition = event.target.scrollLeft const scrollPosition = event.target.scrollLeft;
const startTime = longToDate(timeline[0].start_time) const startTime = longToDate(timeline[0].start_time);
const markerTime = new Date((startTime.getTime()) + (scrollPosition * 1000)); const markerTime = new Date(startTime.getTime() + scrollPosition * 1000);
setMarkerTime(markerTime) setMarkerTime(markerTime);
setScrollTimeout(setTimeout(() => { setScrollTimeout(
checkMarkerForEvent(markerTime) setTimeout(() => {
}, 250)) checkMarkerForEvent(markerTime);
} }, 250)
);
};
useEffect(() => { useEffect(() => {
if (timelineContainerRef) { if (timelineContainerRef) {
const timelineContainerWidth = timelineContainerRef.current.offsetWidth const timelineContainerWidth = timelineContainerRef.current.offsetWidth;
const offset = Math.round(timelineContainerWidth / 2) const offset = Math.round(timelineContainerWidth / 2);
setTimelineOffset(offset); setTimelineOffset(offset);
} }
}, [timelineContainerRef]) }, [timelineContainerRef]);
useEffect(() => { useEffect(() => {
onChange && onChange(currentEvent) onChange && onChange(currentEvent);
}, [currentEvent]) }, [currentEvent]);
const RenderTimeline = useCallback(() => { const RenderTimeline = useCallback(() => {
if (timeline && timeline.length > 0) { if (timeline && timeline.length > 0) {
const lastEvent = timeline[timeline.length - 1] const lastEvent = timeline[timeline.length - 1];
const timelineLength = timelineOffset + lastEvent.positionX + lastEvent.width; const timelineLength = timelineOffset + lastEvent.positionX + lastEvent.width;
return ( return (
<div className="relative flex items-center h-20" style={{ <div
className="relative flex items-center h-20"
style={{
width: `${timelineLength}px`, width: `${timelineLength}px`,
background: "url('/marker.png')", background: "url('/marker.png')",
backgroundPosition: "center", backgroundPosition: 'center',
backgroundSize: "30px", backgroundSize: '30px',
backgroundRepeat: "repeat-x" backgroundRepeat: 'repeat-x',
}}> }}
{ >
timeline.map((e) => { {timeline.map((e) => {
return ( return (
<div key={e.id} className='absolute z-10 rounded-full bg-blue-300 h-2' style={{ <div
key={e.id}
className="absolute z-10 rounded-full bg-blue-300 h-2"
style={{
left: `${e.positionX}px`, left: `${e.positionX}px`,
width: `${e.seconds}px` width: `${e.seconds}px`,
}}></div> }}
) ></div>
}) );
} })}
</div> </div>
) );
} }
}, [timeline]) }, [timeline]);
const setNextCurrentEvent = function(offset) { const setNextCurrentEvent = function (offset) {
setScrollActive(false) setScrollActive(false);
setCurrentEvent(currentEvent => { setCurrentEvent((currentEvent) => {
const index = currentEvent.index + offset; const index = currentEvent.index + offset;
const nextEvent = timeline[index]; const nextEvent = timeline[index];
const positionX = nextEvent.positionX - timelineOffset const positionX = nextEvent.positionX - timelineOffset;
timelineContainerRef.current.scrollLeft = positionX timelineContainerRef.current.scrollLeft = positionX;
return { return {
...nextEvent, ...nextEvent,
id: nextEvent.id, id: nextEvent.id,
index, index,
startTime: nextEvent.start_time, startTime: nextEvent.start_time,
endTime: nextEvent.end_time endTime: nextEvent.end_time,
} };
}) });
} };
const handlePrevious = function() { const handlePrevious = function () {
setNextCurrentEvent(-1); setNextCurrentEvent(-1);
} };
const handleNext = function () { const handleNext = function () {
setNextCurrentEvent(1); setNextCurrentEvent(1);
} };
return ( return (
<Fragment> <Fragment>
<div className='relative flex-grow-1'> <div className="relative flex-grow-1">
<div className='absolute left-0 top-0 h-full w-full' style={{ textAlign: 'center' }}> <div className="absolute left-0 top-0 h-full w-full" style={{ textAlign: 'center' }}>
<div className="h-full" style={{ margin: "0 auto", textAlign: "center" }}> <div className="h-full" style={{ margin: '0 auto', textAlign: 'center' }}>
<span className='z-20 text-white'>{markerTime && (<span>{markerTime.toLocaleTimeString()}</span>)}</span> <span className="z-20 text-white">{markerTime && <span>{markerTime.toLocaleTimeString()}</span>}</span>
<div className='z-20 h-full absolute' style={{ <div
left: "calc(100% / 2)", className="z-20 h-full absolute"
height: "calc(100% - 24px)", style={{
borderRight: "2px solid rgba(252, 211, 77)" left: 'calc(100% / 2)',
}}></div> height: 'calc(100% - 24px)',
borderRight: '2px solid rgba(252, 211, 77)',
}}
></div>
</div> </div>
</div> </div>
<div ref={timelineContainerRef} className='overflow-x-auto' onScroll={handleScroll}> <div ref={timelineContainerRef} className="overflow-x-auto" onScroll={handleScroll}>
<RenderTimeline /> <RenderTimeline />
</div> </div>
</div> </div>
<div className='flex self-center'> <div className="flex self-center">
<TextTab onClick={handlePrevious} text={<Previous />} /> <TextTab onClick={handlePrevious} text={<Previous />} />
<TextTab text={<Play />}/> <TextTab text={<Play />} />
<TextTab onClick={handleNext} text={<Next />} /> <TextTab onClick={handleNext} text={<Next />} />
</div> </div>
</Fragment> </Fragment>
) );
} }

View File

@ -6,27 +6,31 @@ import Link from '../components/Link';
import Switch from '../components/Switch'; import Switch from '../components/Switch';
import { usePersistence } from '../context'; import { usePersistence } from '../context';
import { useCallback, useMemo, useState } from 'preact/hooks'; import { useCallback, useMemo, useState } from 'preact/hooks';
import { useApiHost, useConfig } from '../api'; import { useApiHost, useConfig, useEvents } from '../api';
import { Tabs, TextTab } from '../components/Tabs'; import { Tabs, TextTab } from '../components/Tabs';
import Timeline from '../components/Timeline'; import Timeline from '../components/Timeline';
import { LiveChip } from '../components/LiveChip'; import { LiveChip } from '../components/LiveChip';
import { HistoryHeader } from './HistoryHeader'; import { HistoryHeader } from './HistoryHeader';
import { longToDate } from '../utils/dateUtil' import { longToDate } from '../utils/dateUtil';
import { useSearchString } from '../hooks/useSearchString';
const emptyObject = Object.freeze({}); const emptyObject = Object.freeze({});
export default function Camera({ camera }) { export default function Camera({ camera }) {
const apiHost = useApiHost(); const apiHost = useApiHost();
const { data: config } = useConfig(); const { data: config } = useConfig();
const { searchString } = useSearchString(25, `camera=${camera}`);
const { data: events } = useEvents(searchString);
const [hideBanner, setHideBanner] = useState(false); const [hideBanner, setHideBanner] = useState(false);
const [playerType, setPlayerType] = useState("live"); const [playerType, setPlayerType] = useState('live');
const cameraConfig = config?.cameras[camera]; const cameraConfig = config?.cameras[camera];
const liveWidth = Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height)) const liveWidth = Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height));
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject); const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
const [currentEvent, setCurrentEvent] = useState(undefined) const [currentEvent, setCurrentEvent] = useState(undefined);
const handleSetOption = useCallback( const handleSetOption = useCallback(
(id, value) => { (id, value) => {
@ -81,76 +85,90 @@ export default function Camera({ camera }) {
/> />
<Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link> <Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
</div> </div>
) );
const RenderPlayer = useCallback(() => { const RenderPlayer = useCallback(() => {
if (playerType === "live") { if (playerType === 'live') {
return ( return <JSMpegPlayer camera={camera} width={liveWidth} height={cameraConfig.live.height} />;
<JSMpegPlayer camera={camera} width={liveWidth} height={cameraConfig.live.height} /> } else if (playerType === 'debug') {
)
} else if (playerType === "debug") {
return ( return (
<div> <div>
<AutoUpdatingCameraImage camera={camera} searchParams={searchParams} className="w-full" /> <AutoUpdatingCameraImage camera={camera} searchParams={searchParams} className="w-full" />
{/* {optionContent} */} {/* {optionContent} */}
</div> </div>
); );
} else if (playerType === "history") { } else if (playerType === 'history') {
return currentEvent && ( return (
<video onClick={handleVideoTouch} poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`} preload='none' playsInline controls> currentEvent && (
<source src={`${apiHost}/api/${camera}/start/${currentEvent.startTime}/end/${currentEvent.endTime}/clip.mp4`} /> <video
onClick={handleVideoTouch}
poster={`${apiHost}/api/events/${currentEvent.id}/snapshot.jpg`}
preload="none"
playsInline
controls
>
<source
src={`${apiHost}/api/${camera}/start/${currentEvent.startTime}/end/${currentEvent.endTime}/clip.mp4`}
/>
</video> </video>
) )
);
} }
}, [playerType, currentEvent]); }, [playerType, currentEvent]);
const handleVideoTouch = () => { const handleVideoTouch = () => {
setHideBanner(true); setHideBanner(true);
} };
const handleTabChange = (index) => { const handleTabChange = (index) => {
if (index === 0) { if (index === 0) {
setPlayerType("history") setPlayerType('history');
} } else if (index === 1) {
else if (index === 1) { setPlayerType('live');
setPlayerType("live")
} else if (index === 2) { } else if (index === 2) {
setPlayerType("debug"); setPlayerType('debug');
}
} }
};
const handleTimelineChange = (event) => { const handleTimelineChange = (event) => {
setCurrentEvent(event); setCurrentEvent(event);
} };
return ( return (
<div className='flex bg-black w-full h-full justify-center'> <div className="flex bg-black w-full h-full justify-center">
<div className='relative max-w-screen-md flex-grow w-full'> <div className="relative max-w-screen-md flex-grow w-full">
<div className={`absolute top-0 text-white w-full transition-opacity duration-300 ${hideBanner && "opacity-0"}`}> <div
className={`absolute top-0 text-white w-full transition-opacity duration-300 ${hideBanner && 'opacity-0'}`}
>
<div className="flex pt-4 pl-4 items-center bg-gradient-to-b from-black to-transparent w-full h-16 z10"> <div className="flex pt-4 pl-4 items-center bg-gradient-to-b from-black to-transparent w-full h-16 z10">
{ (playerType === "live" || playerType === "debug") && ( {(playerType === 'live' || playerType === 'debug') && (
<Fragment> <Fragment>
<Heading size="xl" className="mr-2">{camera}</Heading> <Heading size="xl" className="mr-2">
{camera}
</Heading>
<LiveChip /> <LiveChip />
</Fragment> </Fragment>
) } )}
</div> </div>
</div> </div>
<div className='flex flex-col justify-center h-full'> <div className="flex flex-col justify-center h-full">
<div className='relative'> <div className="relative">
{ currentEvent && <HistoryHeader {currentEvent && (
<HistoryHeader
camera={camera} camera={camera}
date={longToDate(currentEvent.start_time)} date={longToDate(currentEvent.start_time)}
objectLabel={currentEvent.label} objectLabel={currentEvent.label}
className="mb-2" /> } className="mb-2"
/>
)}
<RenderPlayer /> <RenderPlayer />
</div> </div>
{playerType === "history" && <Timeline camera={camera} onChange={handleTimelineChange} />} {playerType === 'history' && <Timeline events={events} onChange={handleTimelineChange} />}
</div> </div>
<div className='absolute flex justify-center bottom-8 w-full'> <div className="absolute flex justify-center bottom-8 w-full">
<Tabs selectedIndex={1} onChange={handleTabChange} className="justify"> <Tabs selectedIndex={1} onChange={handleTabChange} className="justify">
<TextTab text="History" /> <TextTab text="History" />
<TextTab text="Live" /> <TextTab text="Live" />