mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-02 01:05:20 +03:00
chore: proper formatting
This commit is contained in:
parent
c7d632ec3b
commit
37384cfe33
@ -6,34 +6,32 @@ import { Next } from '../icons/Next';
|
||||
import { Play } from '../icons/Play';
|
||||
import { Previous } from '../icons/Previous';
|
||||
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 { searchString } = useSearchString(25, `camera=${camera}`);
|
||||
const { data: events, status } = useEvents(searchString);
|
||||
const [timeline, setTimeline] = useState([]);
|
||||
const [timelineOffset, setTimelineOffset] = useState();
|
||||
const [markerTime, setMarkerTime] = useState();
|
||||
const [currentEvent, setCurrentEvent] = useState();
|
||||
const [scrollTimeout, setScrollTimeout] = useState();
|
||||
const [scrollActive, setScrollActive] = useState(true);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (status === FetchStatus.LOADED && timelineOffset) {
|
||||
const filteredEvents = [...events].reverse().filter(e => e.end_time !== undefined)
|
||||
if (events && timelineOffset) {
|
||||
const filteredEvents = [...events].reverse().filter((e) => e.end_time !== undefined);
|
||||
const firstEvent = events[events.length - 1];
|
||||
if (firstEvent) {
|
||||
setMarkerTime(longToDate(firstEvent.start_time))
|
||||
}
|
||||
|
||||
setMarkerTime(longToDate(firstEvent.start_time));
|
||||
}
|
||||
|
||||
const firstEventTime = longToDate(firstEvent.start_time);
|
||||
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 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 {
|
||||
...e,
|
||||
startTime,
|
||||
@ -41,146 +39,153 @@ export default function Timeline({ camera, onChange }) {
|
||||
seconds,
|
||||
width: seconds,
|
||||
positionX,
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
const recentEvent = eventsMap[eventsMap.length - 1]
|
||||
const recentEvent = eventsMap[eventsMap.length - 1];
|
||||
const event = {
|
||||
...recentEvent,
|
||||
id: recentEvent.id,
|
||||
index: eventsMap.length - 1,
|
||||
startTime: recentEvent.start_time,
|
||||
endTime: recentEvent.end_time
|
||||
}
|
||||
setCurrentEvent(event)
|
||||
setTimeline(eventsMap)
|
||||
endTime: recentEvent.end_time,
|
||||
};
|
||||
setCurrentEvent(event);
|
||||
setTimeline(eventsMap);
|
||||
}
|
||||
}, [events, timelineOffset])
|
||||
}, [events, timelineOffset]);
|
||||
|
||||
const checkMarkerForEvent = (markerTime) => {
|
||||
if (!scrollActive) {
|
||||
setScrollActive(true)
|
||||
return
|
||||
setScrollActive(true);
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
const found = timeline[foundIndex]
|
||||
const found = timeline[foundIndex];
|
||||
setCurrentEvent({
|
||||
...found,
|
||||
id: found.id,
|
||||
index: foundIndex,
|
||||
startTime: found.start_time,
|
||||
endTime: found.end_time
|
||||
})
|
||||
endTime: found.end_time,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = event => {
|
||||
clearTimeout(scrollTimeout)
|
||||
const handleScroll = (event) => {
|
||||
clearTimeout(scrollTimeout);
|
||||
|
||||
const scrollPosition = event.target.scrollLeft
|
||||
const startTime = longToDate(timeline[0].start_time)
|
||||
const markerTime = new Date((startTime.getTime()) + (scrollPosition * 1000));
|
||||
setMarkerTime(markerTime)
|
||||
const scrollPosition = event.target.scrollLeft;
|
||||
const startTime = longToDate(timeline[0].start_time);
|
||||
const markerTime = new Date(startTime.getTime() + scrollPosition * 1000);
|
||||
setMarkerTime(markerTime);
|
||||
|
||||
setScrollTimeout(setTimeout(() => {
|
||||
checkMarkerForEvent(markerTime)
|
||||
}, 250))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (timelineContainerRef) {
|
||||
const timelineContainerWidth = timelineContainerRef.current.offsetWidth
|
||||
const offset = Math.round(timelineContainerWidth / 2)
|
||||
setTimelineOffset(offset);
|
||||
}
|
||||
}, [timelineContainerRef])
|
||||
setScrollTimeout(
|
||||
setTimeout(() => {
|
||||
checkMarkerForEvent(markerTime);
|
||||
}, 250)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onChange && onChange(currentEvent)
|
||||
}, [currentEvent])
|
||||
if (timelineContainerRef) {
|
||||
const timelineContainerWidth = timelineContainerRef.current.offsetWidth;
|
||||
const offset = Math.round(timelineContainerWidth / 2);
|
||||
setTimelineOffset(offset);
|
||||
}
|
||||
}, [timelineContainerRef]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange && onChange(currentEvent);
|
||||
}, [currentEvent]);
|
||||
|
||||
const RenderTimeline = useCallback(() => {
|
||||
if (timeline && timeline.length > 0) {
|
||||
const lastEvent = timeline[timeline.length - 1]
|
||||
const lastEvent = timeline[timeline.length - 1];
|
||||
const timelineLength = timelineOffset + lastEvent.positionX + lastEvent.width;
|
||||
return (
|
||||
<div className="relative flex items-center h-20" style={{
|
||||
width: `${timelineLength}px`,
|
||||
background: "url('/marker.png')",
|
||||
backgroundPosition: "center",
|
||||
backgroundSize: "30px",
|
||||
backgroundRepeat: "repeat-x"
|
||||
}}>
|
||||
{
|
||||
timeline.map((e) => {
|
||||
return (
|
||||
<div key={e.id} className='absolute z-10 rounded-full bg-blue-300 h-2' style={{
|
||||
<div
|
||||
className="relative flex items-center h-20"
|
||||
style={{
|
||||
width: `${timelineLength}px`,
|
||||
background: "url('/marker.png')",
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '30px',
|
||||
backgroundRepeat: 'repeat-x',
|
||||
}}
|
||||
>
|
||||
{timeline.map((e) => {
|
||||
return (
|
||||
<div
|
||||
key={e.id}
|
||||
className="absolute z-10 rounded-full bg-blue-300 h-2"
|
||||
style={{
|
||||
left: `${e.positionX}px`,
|
||||
width: `${e.seconds}px`
|
||||
}}></div>
|
||||
)
|
||||
})
|
||||
}
|
||||
width: `${e.seconds}px`,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [timeline])
|
||||
}, [timeline]);
|
||||
|
||||
const setNextCurrentEvent = function(offset) {
|
||||
setScrollActive(false)
|
||||
setCurrentEvent(currentEvent => {
|
||||
const setNextCurrentEvent = function (offset) {
|
||||
setScrollActive(false);
|
||||
setCurrentEvent((currentEvent) => {
|
||||
const index = currentEvent.index + offset;
|
||||
const nextEvent = timeline[index];
|
||||
const positionX = nextEvent.positionX - timelineOffset
|
||||
timelineContainerRef.current.scrollLeft = positionX
|
||||
const positionX = nextEvent.positionX - timelineOffset;
|
||||
timelineContainerRef.current.scrollLeft = positionX;
|
||||
return {
|
||||
...nextEvent,
|
||||
id: nextEvent.id,
|
||||
index,
|
||||
startTime: nextEvent.start_time,
|
||||
endTime: nextEvent.end_time
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handlePrevious = function() {
|
||||
endTime: nextEvent.end_time,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handlePrevious = function () {
|
||||
setNextCurrentEvent(-1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = function () {
|
||||
setNextCurrentEvent(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className='relative flex-grow-1'>
|
||||
<div className='absolute left-0 top-0 h-full w-full' style={{ textAlign: 'center' }}>
|
||||
<div className="h-full" style={{ margin: "0 auto", textAlign: "center" }}>
|
||||
<span className='z-20 text-white'>{markerTime && (<span>{markerTime.toLocaleTimeString()}</span>)}</span>
|
||||
<div className='z-20 h-full absolute' style={{
|
||||
left: "calc(100% / 2)",
|
||||
height: "calc(100% - 24px)",
|
||||
borderRight: "2px solid rgba(252, 211, 77)"
|
||||
}}></div>
|
||||
<div className="relative flex-grow-1">
|
||||
<div className="absolute left-0 top-0 h-full w-full" style={{ textAlign: 'center' }}>
|
||||
<div className="h-full" style={{ margin: '0 auto', textAlign: 'center' }}>
|
||||
<span className="z-20 text-white">{markerTime && <span>{markerTime.toLocaleTimeString()}</span>}</span>
|
||||
<div
|
||||
className="z-20 h-full absolute"
|
||||
style={{
|
||||
left: 'calc(100% / 2)',
|
||||
height: 'calc(100% - 24px)',
|
||||
borderRight: '2px solid rgba(252, 211, 77)',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={timelineContainerRef} className='overflow-x-auto' onScroll={handleScroll}>
|
||||
<div ref={timelineContainerRef} className="overflow-x-auto" onScroll={handleScroll}>
|
||||
<RenderTimeline />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex self-center'>
|
||||
<div className="flex self-center">
|
||||
<TextTab onClick={handlePrevious} text={<Previous />} />
|
||||
<TextTab text={<Play />}/>
|
||||
<TextTab text={<Play />} />
|
||||
<TextTab onClick={handleNext} text={<Next />} />
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,27 +6,31 @@ import Link from '../components/Link';
|
||||
import Switch from '../components/Switch';
|
||||
import { usePersistence } from '../context';
|
||||
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 Timeline from '../components/Timeline';
|
||||
import { LiveChip } from '../components/LiveChip';
|
||||
import { HistoryHeader } from './HistoryHeader';
|
||||
import { longToDate } from '../utils/dateUtil'
|
||||
import { longToDate } from '../utils/dateUtil';
|
||||
import { useSearchString } from '../hooks/useSearchString';
|
||||
|
||||
const emptyObject = Object.freeze({});
|
||||
|
||||
export default function Camera({ camera }) {
|
||||
const apiHost = useApiHost();
|
||||
|
||||
const { data: config } = useConfig();
|
||||
const { searchString } = useSearchString(25, `camera=${camera}`);
|
||||
const { data: events } = useEvents(searchString);
|
||||
|
||||
const [hideBanner, setHideBanner] = useState(false);
|
||||
const [playerType, setPlayerType] = useState("live");
|
||||
const [playerType, setPlayerType] = useState('live');
|
||||
|
||||
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 [currentEvent, setCurrentEvent] = useState(undefined)
|
||||
const [currentEvent, setCurrentEvent] = useState(undefined);
|
||||
|
||||
const handleSetOption = useCallback(
|
||||
(id, value) => {
|
||||
@ -81,76 +85,90 @@ export default function Camera({ camera }) {
|
||||
/>
|
||||
<Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
const RenderPlayer = useCallback(() => {
|
||||
if (playerType === "live") {
|
||||
return (
|
||||
<JSMpegPlayer camera={camera} width={liveWidth} height={cameraConfig.live.height} />
|
||||
)
|
||||
} else if (playerType === "debug") {
|
||||
const RenderPlayer = useCallback(() => {
|
||||
if (playerType === 'live') {
|
||||
return <JSMpegPlayer camera={camera} width={liveWidth} height={cameraConfig.live.height} />;
|
||||
} else if (playerType === 'debug') {
|
||||
return (
|
||||
<div>
|
||||
<AutoUpdatingCameraImage camera={camera} searchParams={searchParams} className="w-full" />
|
||||
{/* {optionContent} */}
|
||||
</div>
|
||||
);
|
||||
} else if (playerType === "history") {
|
||||
return currentEvent && (
|
||||
<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>
|
||||
)
|
||||
} else if (playerType === 'history') {
|
||||
return (
|
||||
currentEvent && (
|
||||
<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>
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [playerType, currentEvent]);
|
||||
|
||||
const handleVideoTouch = () => {
|
||||
setHideBanner(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (index) => {
|
||||
if (index === 0) {
|
||||
setPlayerType("history")
|
||||
}
|
||||
else if (index === 1) {
|
||||
setPlayerType("live")
|
||||
setPlayerType('history');
|
||||
} else if (index === 1) {
|
||||
setPlayerType('live');
|
||||
} else if (index === 2) {
|
||||
setPlayerType("debug");
|
||||
setPlayerType('debug');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTimelineChange = (event) => {
|
||||
setCurrentEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='flex bg-black w-full h-full justify-center'>
|
||||
<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="flex bg-black w-full h-full justify-center">
|
||||
<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="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>
|
||||
<Heading size="xl" className="mr-2">{camera}</Heading>
|
||||
<Heading size="xl" className="mr-2">
|
||||
{camera}
|
||||
</Heading>
|
||||
<LiveChip />
|
||||
</Fragment>
|
||||
) }
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col justify-center h-full'>
|
||||
<div className='relative'>
|
||||
{ currentEvent && <HistoryHeader
|
||||
camera={camera}
|
||||
date={longToDate(currentEvent.start_time)}
|
||||
objectLabel={currentEvent.label}
|
||||
className="mb-2" /> }
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<div className="relative">
|
||||
{currentEvent && (
|
||||
<HistoryHeader
|
||||
camera={camera}
|
||||
date={longToDate(currentEvent.start_time)}
|
||||
objectLabel={currentEvent.label}
|
||||
className="mb-2"
|
||||
/>
|
||||
)}
|
||||
<RenderPlayer />
|
||||
</div>
|
||||
|
||||
{playerType === "history" && <Timeline camera={camera} onChange={handleTimelineChange} />}
|
||||
{playerType === 'history' && <Timeline events={events} onChange={handleTimelineChange} />}
|
||||
</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">
|
||||
<TextTab text="History" />
|
||||
<TextTab text="Live" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user