forked from Github/frigate
feat: Timeline UI (#2830)
This commit is contained in:
@@ -4,7 +4,7 @@ import Heading from '../components/Heading';
|
||||
|
||||
export default function Birdseye() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading size="2xl">Birdseye</Heading>
|
||||
<div>
|
||||
<JSMpegPlayer camera="birdseye" />
|
||||
|
||||
@@ -112,7 +112,7 @@ export default function Camera({ camera }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading size="2xl">{camera}</Heading>
|
||||
<ButtonsTabbed viewModes={['live', 'debug']} setViewMode={setViewMode} />
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ ${Object.keys(objectMaskPoints)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex-col space-y-4">
|
||||
<div className="flex-col space-y-4 p-2 px-4">
|
||||
<Heading size="2xl">{camera} mask & zone creator</Heading>
|
||||
|
||||
<Card
|
||||
|
||||
76
web/src/routes/Camera_V2.jsx
Normal file
76
web/src/routes/Camera_V2.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { h, Fragment } from 'preact';
|
||||
import JSMpegPlayer from '../components/JSMpegPlayer';
|
||||
import Heading from '../components/Heading';
|
||||
import { useState } from 'preact/hooks';
|
||||
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/HistoryViewer.tsx';
|
||||
|
||||
export default function Camera({ camera }) {
|
||||
const { data: config } = useConfig();
|
||||
|
||||
const [playerType, setPlayerType] = useState('live');
|
||||
|
||||
const cameraConfig = config?.cameras[camera];
|
||||
|
||||
const handleTabChange = (index) => {
|
||||
if (index === 0) {
|
||||
setPlayerType('history');
|
||||
} else if (index === 1) {
|
||||
setPlayerType('live');
|
||||
} else if (index === 2) {
|
||||
setPlayerType('debug');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='flex bg-white dark: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'>
|
||||
<div className='flex pt-4 pl-4 items-center w-full h-16 z10'>
|
||||
{(playerType === 'live' || playerType === 'debug') && (
|
||||
<Fragment>
|
||||
<Heading size='xl' className='mr-2 text-black dark:text-white'>
|
||||
{camera}
|
||||
</Heading>
|
||||
<LiveChip className='text-green-400 border-2 border-solid border-green-400 bg-opacity-40 dark:bg-opacity-10' />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col justify-center h-full'>
|
||||
<RenderPlayer camera={camera} cameraConfig={cameraConfig} playerType={playerType} />
|
||||
</div>
|
||||
|
||||
<div className='absolute flex justify-center bottom-8 w-full'>
|
||||
<Tabs selectedIndex={1} onChange={handleTabChange} className='justify'>
|
||||
<TextTab text='History' />
|
||||
<TextTab text='Live' />
|
||||
<TextTab text='Debug' />
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const RenderPlayer = function ({ camera, cameraConfig, playerType }) {
|
||||
const liveWidth = Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height));
|
||||
if (playerType === 'live') {
|
||||
return (
|
||||
<Fragment>
|
||||
<div>
|
||||
<JSMpegPlayer camera={camera} width={liveWidth} height={cameraConfig.live.height} />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (playerType === 'history') {
|
||||
return <HistoryViewer camera={camera} />;
|
||||
} else if (playerType === 'debug') {
|
||||
return <DebugCamera camera={camera} />;
|
||||
}
|
||||
return <Fragment />;
|
||||
};
|
||||
@@ -15,7 +15,7 @@ export default function Cameras() {
|
||||
return status !== FetchStatus.LOADED ? (
|
||||
<ActivityIndicator />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4 p-2 px-4">
|
||||
{Object.entries(config.cameras).map(([camera, conf]) => (
|
||||
<Camera name={camera} conf={conf} />
|
||||
))}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function Debug() {
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading>
|
||||
Debug <span className="text-sm">{service.version}</span>
|
||||
</Heading>
|
||||
|
||||
@@ -10,11 +10,11 @@ import Close from '../icons/Close';
|
||||
import StarRecording from '../icons/StarRecording';
|
||||
import Delete from '../icons/Delete';
|
||||
import Snapshot from '../icons/Snapshot';
|
||||
import Dialog from '../components/Dialog';
|
||||
import Heading from '../components/Heading';
|
||||
import VideoPlayer from '../components/VideoPlayer';
|
||||
import { Table, Thead, Tbody, Th, Tr, Td } from '../components/Table';
|
||||
import { FetchStatus, useApiHost, useEvent, useDelete, useRetain } from '../api';
|
||||
import Prompt from '../components/Prompt';
|
||||
|
||||
const ActionButtonGroup = ({ className, isRetained, handleClickRetain, handleClickDelete, close }) => (
|
||||
<div className={`space-y-2 space-x-2 sm:space-y-0 xs:space-x-4 ${className}`}>
|
||||
@@ -145,7 +145,7 @@ export default function Event({ eventId, close, scrollRef }) {
|
||||
</div>
|
||||
<ActionButtonGroup isRetained={isRetained} handleClickRetain={handleClickRetain} handleClickDelete={handleClickDelete} close={close} className="hidden sm:block" />
|
||||
{showDialog ? (
|
||||
<Dialog
|
||||
<Prompt
|
||||
onDismiss={handleDismissDeleteDialog}
|
||||
title="Delete Event?"
|
||||
text={
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
|
||||
[apiHost, handleFilter, pathname, scrollToRef]
|
||||
);
|
||||
return (
|
||||
<div className="space-y-4 w-full">
|
||||
<div className="space-y-4 p-2 px-4 w-full">
|
||||
<Heading>Events</Heading>
|
||||
<Filters onChange={handleFilter} searchParams={searchParams} />
|
||||
<div className="min-w-0 overflow-auto">
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Recording({ camera, date, hour, seconds }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading>{camera} Recordings</Heading>
|
||||
|
||||
<VideoPlayer
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function StyleGuide() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="p-2 px-4">
|
||||
<Heading size="md">Button</Heading>
|
||||
<div className="flex space-x-4 mb-4">
|
||||
<Button>Default</Button>
|
||||
|
||||
@@ -8,6 +8,11 @@ export async function getCamera(url, cb, props) {
|
||||
return module.default;
|
||||
}
|
||||
|
||||
export async function getCameraV2(url, cb, props) {
|
||||
const module = await import('./Camera_V2.jsx');
|
||||
return module.default;
|
||||
}
|
||||
|
||||
export async function getEvent(url, cb, props) {
|
||||
const module = await import('./Event.jsx');
|
||||
return module.default;
|
||||
|
||||
Reference in New Issue
Block a user