feat: Timeline UI (#2830)

This commit is contained in:
JohnMark Sill
2022-02-27 08:04:12 -06:00
committed by GitHub
parent 4004048add
commit 3e07d4eddb
54 changed files with 1950 additions and 50 deletions

View File

@@ -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" />

View File

@@ -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} />

View File

@@ -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

View 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 />;
};

View File

@@ -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} />
))}

View File

@@ -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>

View File

@@ -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={

View File

@@ -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">

View File

@@ -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

View File

@@ -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>

View File

@@ -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;