Events performance (#1645)

* rearrange event route and splitted into several components

* useIntersectionObserver

* re-arrange

* searchstring improvement

* added xs tailwind breakpoint

* useOuterClick hook

* cleaned up

* removed some video controls for mobile devices

* lint

* moved hooks to global folder

* moved buttons for small devices

* added button groups

Co-authored-by: Bernt Christian Egeland <cbegelan@gmail.com>
This commit is contained in:
Bernt Christian Egeland
2021-09-03 14:11:23 +02:00
committed by GitHub
parent b8df419bad
commit 00ff76a0b9
18 changed files with 572 additions and 352 deletions

View File

@@ -0,0 +1,31 @@
import { h } from 'preact';
import Select from '../../../components/Select';
import { useCallback, useMemo } from 'preact/hooks';
const Filter = ({ onChange, searchParams, paramName, options }) => {
const handleSelect = useCallback(
(key) => {
const newParams = new URLSearchParams(searchParams.toString());
if (key !== 'all') {
newParams.set(paramName, key);
} else {
newParams.delete(paramName);
}
onChange(newParams);
},
[searchParams, paramName, onChange]
);
const selectOptions = useMemo(() => ['all', ...options], [options]);
return (
<Select
label={`${paramName.charAt(0).toUpperCase()}${paramName.substr(1)}`}
onChange={handleSelect}
options={selectOptions}
selected={searchParams.get(paramName) || 'all'}
/>
);
};
export default Filter;

View File

@@ -0,0 +1,32 @@
import { h } from 'preact';
import { useCallback, useMemo } from 'preact/hooks';
import Link from '../../../components/Link';
import { route } from 'preact-router';
const Filterable = ({ onFilter, pathname, searchParams, paramName, name, removeDefaultSearchKeys }) => {
const href = useMemo(() => {
const params = new URLSearchParams(searchParams.toString());
params.set(paramName, name);
removeDefaultSearchKeys(params);
return `${pathname}?${params.toString()}`;
}, [searchParams, paramName, pathname, name, removeDefaultSearchKeys]);
const handleClick = useCallback(
(event) => {
event.preventDefault();
route(href, true);
const params = new URLSearchParams(searchParams.toString());
params.set(paramName, name);
onFilter(params);
},
[href, searchParams, onFilter, paramName, name]
);
return (
<Link href={href} onclick={handleClick}>
{name}
</Link>
);
};
export default Filterable;

View File

@@ -0,0 +1,39 @@
import { h } from 'preact';
import Filter from './filter';
import { useConfig } from '../../../api';
import { useMemo } from 'preact/hooks';
const Filters = ({ onChange, searchParams }) => {
const { data } = useConfig();
const cameras = useMemo(() => Object.keys(data.cameras), [data]);
const zones = useMemo(
() =>
Object.values(data.cameras)
.reduce((memo, camera) => {
memo = memo.concat(Object.keys(camera.zones));
return memo;
}, [])
.filter((value, i, self) => self.indexOf(value) === i),
[data]
);
const labels = useMemo(() => {
return Object.values(data.cameras)
.reduce((memo, camera) => {
memo = memo.concat(camera.objects?.track || []);
return memo;
}, data.objects?.track || [])
.filter((value, i, self) => self.indexOf(value) === i);
}, [data]);
return (
<div className="flex space-x-4">
<Filter onChange={onChange} options={cameras} paramName="camera" searchParams={searchParams} />
<Filter onChange={onChange} options={zones} paramName="zone" searchParams={searchParams} />
<Filter onChange={onChange} options={labels} paramName="label" searchParams={searchParams} />
</div>
);
};
export default Filters;

View File

@@ -0,0 +1,3 @@
export { default as TableHead } from './tableHead';
export { default as TableRow } from './tableRow';
export { default as Filters } from './filters';

View File

@@ -0,0 +1,18 @@
import { h } from 'preact';
import { Thead, Th, Tr } from '../../../components/Table';
const TableHead = () => (
<Thead>
<Tr>
<Th />
<Th>Camera</Th>
<Th>Label</Th>
<Th>Score</Th>
<Th>Zones</Th>
<Th>Date</Th>
<Th>Start</Th>
<Th>End</Th>
</Tr>
</Thead>
);
export default TableHead;

View File

@@ -0,0 +1,119 @@
import { h } from 'preact';
import { memo } from 'preact/compat';
import { useCallback, useState, useMemo } from 'preact/hooks';
import { Tr, Td, Tbody } from '../../../components/Table';
import Filterable from './filterable';
import Event from '../../Event';
import { useSearchString } from '../../../hooks/useSearchString';
import { useClickOutside } from '../../../hooks/useClickOutside';
const EventsRow = memo(
({
id,
apiHost,
start_time: startTime,
end_time: endTime,
scrollToRef,
lastRowRef,
handleFilter,
pathname,
limit,
camera,
label,
top_score: score,
zones,
}) => {
const [viewEvent, setViewEvent] = useState(null);
const { searchString, removeDefaultSearchKeys } = useSearchString(limit);
const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
const innerRef = useClickOutside(() => {
setViewEvent(null);
});
const viewEventHandler = useCallback(
(id) => {
//Toggle event view
if (viewEvent === id) return setViewEvent(null);
//Set event id to be rendered.
setViewEvent(id);
},
[viewEvent]
);
const start = new Date(parseInt(startTime * 1000, 10));
const end = new Date(parseInt(endTime * 1000, 10));
return (
<Tbody reference={innerRef}>
<Tr data-testid={`event-${id}`} className={`${viewEvent === id ? 'border-none' : ''}`}>
<Td className="w-40">
<a
onClick={() => viewEventHandler(id)}
ref={lastRowRef}
data-start-time={startTime}
// data-reached-end={reachedEnd} <-- Enable this will cause all events to re-render when reaching end.
>
<img
width="150"
height="150"
className="cursor-pointer"
style="min-height: 48px; min-width: 48px;"
src={`${apiHost}/api/events/${id}/thumbnail.jpg`}
/>
</a>
</Td>
<Td>
<Filterable
onFilter={handleFilter}
pathname={pathname}
searchParams={searchParams}
paramName="camera"
name={camera}
removeDefaultSearchKeys={removeDefaultSearchKeys}
/>
</Td>
<Td>
<Filterable
onFilter={handleFilter}
pathname={pathname}
searchParams={searchParams}
paramName="label"
name={label}
removeDefaultSearchKeys={removeDefaultSearchKeys}
/>
</Td>
<Td>{(score * 100).toFixed(2)}%</Td>
<Td>
<ul>
{zones.map((zone) => (
<li>
<Filterable
onFilter={handleFilter}
pathname={pathname}
searchParams={searchString}
paramName="zone"
name={zone}
removeDefaultSearchKeys={removeDefaultSearchKeys}
/>
</li>
))}
</ul>
</Td>
<Td>{start.toLocaleDateString()}</Td>
<Td>{start.toLocaleTimeString()}</Td>
<Td>{end.toLocaleTimeString()}</Td>
</Tr>
{viewEvent === id ? (
<Tr className="border-b-1">
<Td colSpan="8" reference={(el) => (scrollToRef[id] = el)}>
<Event eventId={id} close={() => setViewEvent(null)} scrollRef={scrollToRef} />
</Td>
</Tr>
) : null}
</Tbody>
);
}
);
export default EventsRow;