swr events refactor

This commit is contained in:
Blake Blackshear
2022-02-26 13:11:00 -06:00
parent 4bae3993da
commit 1c9ba11e07
76 changed files with 29753 additions and 9109 deletions

View File

@@ -64,7 +64,6 @@ export default function Button({
color = 'blue',
disabled = false,
href,
size,
type = 'contained',
...attrs
}) {
@@ -81,11 +80,11 @@ export default function Button({
classes = classes.replace(/(?:focus|active|hover):[^ ]+/g, '');
}
const handleMousenter = useCallback((event) => {
const handleMousenter = useCallback(() => {
setHovered(true);
}, []);
const handleMouseleave = useCallback((event) => {
const handleMouseleave = useCallback(() => {
setHovered(false);
}, []);

View File

@@ -7,27 +7,35 @@ export default function ButtonsTabbed({
setHeader = null,
headers = [''],
className = 'text-gray-600 py-0 px-4 block hover:text-gray-500',
selectedClassName = `${className} focus:outline-none border-b-2 font-medium border-gray-500`
selectedClassName = `${className} focus:outline-none border-b-2 font-medium border-gray-500`,
}) {
const [selected, setSelected] = useState(0);
const captitalize = (str) => { return (`${str.charAt(0).toUpperCase()}${str.slice(1)}`); };
const captitalize = (str) => {
return `${str.charAt(0).toUpperCase()}${str.slice(1)}`;
};
const getHeader = useCallback((i) => {
return (headers.length === viewModes.length ? headers[i] : captitalize(viewModes[i]));
}, [headers, viewModes]);
const getHeader = useCallback(
(i) => {
return headers.length === viewModes.length ? headers[i] : captitalize(viewModes[i]);
},
[headers, viewModes]
);
const handleClick = useCallback((i) => {
setSelected(i);
setViewMode && setViewMode(viewModes[i]);
setHeader && setHeader(getHeader(i));
}, [setViewMode, setHeader, setSelected, viewModes, getHeader]);
const handleClick = useCallback(
(i) => {
setSelected(i);
setViewMode && setViewMode(viewModes[i]);
setHeader && setHeader(getHeader(i));
},
[setViewMode, setHeader, setSelected, viewModes, getHeader]
);
setHeader && setHeader(getHeader(selected));
return (
<nav className="flex justify-end">
{viewModes.map((item, i) => {
return (
<button onClick={() => handleClick(i)} className={i === selected ? selectedClassName : className}>
<button key={i} onClick={() => handleClick(i)} className={i === selected ? selectedClassName : className}>
{captitalize(item)}
</button>
);

View File

@@ -5,7 +5,7 @@ import ArrowRightDouble from '../icons/ArrowRightDouble';
const todayTimestamp = new Date().setHours(0, 0, 0, 0).valueOf();
const Calender = ({ onChange, calenderRef, close }) => {
const Calendar = ({ onChange, calendarRef, close }) => {
const keyRef = useRef([]);
const date = new Date();
@@ -159,8 +159,8 @@ const Calender = ({ onChange, calenderRef, close }) => {
after,
before:
day.timestamp >= todayTimestamp
? new Date(todayTimestamp).setHours(24, 0, 0, 0)
: new Date(day.timestamp).setHours(24, 0, 0, 0),
? new Date(todayTimestamp).setHours(24, 0, 0, 0)
: new Date(day.timestamp).setHours(24, 0, 0, 0),
};
}
@@ -243,26 +243,26 @@ const Calender = ({ onChange, calenderRef, close }) => {
const days =
state.monthDetails &&
state.monthDetails.map((day, idx) => {
return (
<div
onClick={() => onDateClick(day)}
onkeydown={(e) => handleKeydown(e, day, idx)}
ref={(ref) => (keyRef.current[idx] = ref)}
tabIndex={day.month === 0 ? day.date : null}
className={`h-12 w-12 float-left flex flex-shrink justify-center items-center cursor-pointer ${
day.month !== 0 ? ' opacity-50 bg-gray-700 dark:bg-gray-700 pointer-events-none' : ''
}
return (
<div
onClick={() => onDateClick(day)}
onkeydown={(e) => handleKeydown(e, day, idx)}
ref={(ref) => (keyRef.current[idx] = ref)}
tabIndex={day.month === 0 ? day.date : null}
className={`h-12 w-12 float-left flex flex-shrink justify-center items-center cursor-pointer ${
day.month !== 0 ? ' opacity-50 bg-gray-700 dark:bg-gray-700 pointer-events-none' : ''
}
${isFirstDayInRange(day) ? ' rounded-l-xl ' : ''}
${isSelectedRange(day) ? ' bg-blue-600 dark:hover:bg-blue-600' : ''}
${isLastDayInRange(day) ? ' rounded-r-xl ' : ''}
${isCurrentDay(day) && !isLastDayInRange(day) ? 'rounded-full bg-gray-100 dark:hover:bg-gray-100 ' : ''}`}
key={idx}
>
<div className="font-light">
<span className="text-gray-400">{day.date}</span>
</div>
</div>
);
key={idx}
>
<div className="font-light">
<span className="text-gray-400">{day.date}</span>
</div>
</div>
);
});
return (
@@ -280,7 +280,7 @@ const Calender = ({ onChange, calenderRef, close }) => {
};
return (
<div className="select-none w-96 flex flex-shrink" ref={calenderRef}>
<div className="select-none w-96 flex flex-shrink" ref={calendarRef}>
<div className="py-4 px-6">
<div className="flex items-center">
<div className="w-1/6 relative flex justify-around">
@@ -326,4 +326,4 @@ const Calender = ({ onChange, calenderRef, close }) => {
);
};
export default Calender;
export default Calendar;

View File

@@ -1,11 +1,12 @@
import { h } from 'preact';
import ActivityIndicator from './ActivityIndicator';
import { useApiHost, useConfig } from '../api';
import { useApiHost } from '../api';
import useSWR from 'swr';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useResizeObserver } from '../hooks';
export default function CameraImage({ camera, onload, searchParams = '', stretch = false }) {
const { data: config } = useConfig();
const { data: config } = useSWR('config');
const apiHost = useApiHost();
const [hasLoaded, setHasLoaded] = useState(false);
const containerRef = useRef(null);

View File

@@ -66,7 +66,6 @@ export default function DatePicker({
onBlur,
onChangeText,
onFocus,
readonly,
trailingIcon: TrailingIcon,
value: propValue = '',
...props

View File

@@ -1,6 +1,6 @@
import { h } from 'preact';
import Heading from '../Heading';
import { TimelineEvent } from '../Timeline/Timeline';
import type { TimelineEvent } from '../Timeline/TimelineEvent';
interface HistoryHeaderProps {
event: TimelineEvent;

View File

@@ -1,17 +1,29 @@
import { Fragment, h } from 'preact';
import { useCallback, useEffect, useState } from 'preact/hooks';
import { useEvents } from '../../api';
import { useSearchString } from '../../hooks/useSearchString';
import { getNowYesterdayInLong } from '../../utils/dateUtil';
import useSWR from 'swr';
import axios from 'axios';
import Timeline from '../Timeline/Timeline';
import { TimelineChangeEvent } from '../Timeline/TimelineChangeEvent';
import { TimelineEvent } from '../Timeline/TimelineEvent';
import type { TimelineChangeEvent } from '../Timeline/TimelineChangeEvent';
import type { TimelineEvent } from '../Timeline/TimelineEvent';
import { HistoryHeader } from './HistoryHeader';
import { HistoryVideo } from './HistoryVideo';
export default function HistoryViewer({ camera }) {
const { searchString } = useSearchString(500, `camera=${camera}&after=${getNowYesterdayInLong()}`);
const { data: events } = useEvents(searchString);
const searchParams = {
before: null,
after: null,
camera,
label: 'all',
zone: 'all',
};
// TODO: refactor
const eventsFetcher = (path, params) => {
params = { ...params, include_thumbnails: 0, limit: 500 };
return axios.get(path, { params }).then((res) => res.data);
};
const { data: events } = useSWR(['events', searchParams], eventsFetcher);
const [timelineEvents, setTimelineEvents] = useState<TimelineEvent[]>(undefined);
const [currentEvent, setCurrentEvent] = useState<TimelineEvent>(undefined);

View File

@@ -5,7 +5,7 @@ import JSMpeg from '@cycjimmy/jsmpeg-player';
export default function JSMpegPlayer({ camera, width, height }) {
const playerRef = useRef();
const url = `${baseUrl.replace(/^http/, 'ws')}/live/${camera}`
const url = `${baseUrl.replace(/^http/, 'ws')}/live/${camera}`;
useEffect(() => {
const video = new JSMpeg.VideoElement(
@@ -16,15 +16,15 @@ export default function JSMpegPlayer({ camera, width, height }) {
);
const fullscreen = () => {
if(video.els.canvas.webkitRequestFullScreen) {
if (video.els.canvas.webkitRequestFullScreen) {
video.els.canvas.webkitRequestFullScreen();
}
else {
video.els.canvas.mozRequestFullScreen();
}
}
};
video.els.canvas.addEventListener('click',fullscreen)
video.els.canvas.addEventListener('click',fullscreen);
return () => {
video.destroy();

View File

@@ -16,18 +16,22 @@ export default function Menu({ className, children, onDismiss, relativeTo, width
) : null;
}
export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
export function MenuItem({ focus, icon: Icon, label, href, onSelect, value, ...attrs }) {
const handleClick = useCallback(() => {
onSelect && onSelect(value, label);
}, [onSelect, value, label]);
const Element = href ? 'a' : 'div';
return (
<div
<Element
className={`flex space-x-2 p-2 px-5 hover:bg-gray-200 dark:hover:bg-gray-800 dark:hover:text-white cursor-pointer ${
focus ? 'bg-gray-200 dark:bg-gray-800 dark:text-white' : ''
}`}
href={href}
onClick={handleClick}
role="option"
{...attrs}
>
{Icon ? (
<div className="w-6 h-6 self-center mr-4 text-gray-500 flex-shrink-0">
@@ -35,7 +39,7 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
</div>
) : null}
<div className="whitespace-nowrap">{label}</div>
</div>
</Element>
);
}

View File

@@ -47,7 +47,7 @@ export function Destination({ className = '', href, text, ...other }) {
const styleProps = {
[external
? 'className'
? className
: 'class']: 'block p-2 text-sm font-semibold text-gray-900 rounded hover:bg-blue-500 dark:text-gray-200 hover:text-white dark:hover:text-white focus:outline-none ring-opacity-50 focus:ring-2 ring-blue-300',
};

View File

@@ -7,7 +7,7 @@ import {
parseISO,
startOfHour,
differenceInMinutes,
differenceInHours,
differenceInHours
} from 'date-fns';
import ArrowDropdown from '../icons/ArrowDropdown';
import ArrowDropup from '../icons/ArrowDropup';
@@ -16,7 +16,7 @@ import Menu from '../icons/Menu';
import MenuOpen from '../icons/MenuOpen';
import { useApiHost } from '../api';
export default function RecordingPlaylist({ camera, recordings, selectedDate, selectedHour }) {
export default function RecordingPlaylist({ camera, recordings, selectedDate }) {
const [active, setActive] = useState(true);
const toggle = () => setActive(!active);
@@ -33,7 +33,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
.slice()
.reverse()
.map((item, i) => (
<div className="mb-2 w-full">
<div key={i} className="mb-2 w-full">
<div
className={`flex w-full text-md text-white px-8 py-2 mb-2 ${
i === 0 ? 'border-t border-white border-opacity-50' : ''
@@ -50,7 +50,7 @@ export default function RecordingPlaylist({ camera, recordings, selectedDate, se
.slice()
.reverse()
.map((event) => (
<EventCard camera={camera} event={event} delay={item.delay} />
<EventCard key={event.id} camera={camera} event={event} delay={item.delay} />
))}
</div>
))}

View File

@@ -110,7 +110,7 @@ export default function RelativeModal({
const menu = (
<Fragment>
<div data-testid="scrim" key="scrim" className="absolute inset-0 z-10" onClick={handleDismiss} />
<div data-testid="scrim" key="scrim" className="fixed inset-0 z-10" onClick={handleDismiss} />
<div
key="menu"
className={`z-10 bg-white dark:bg-gray-700 dark:text-white absolute shadow-lg rounded w-auto h-auto transition-transform transition-opacity duration-75 transform scale-90 opacity-0 overflow-x-hidden overflow-y-auto ${

View File

@@ -4,7 +4,7 @@ import ArrowDropup from '../icons/ArrowDropup';
import Menu, { MenuItem } from './Menu';
import TextField from './TextField';
import DatePicker from './DatePicker';
import Calender from './Calender';
import Calendar from './Calendar';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
export default function Select({
@@ -71,8 +71,8 @@ export default function Select({
}, [type, options, inputOptions, propSelected, setSelected]);
const [focused, setFocused] = useState(null);
const [showCalender, setShowCalender] = useState(false);
const calenderRef = useRef(null);
const [showCalendar, setShowCalendar] = useState(false);
const calendarRef = useRef(null);
const ref = useRef(null);
const handleSelect = useCallback(
@@ -80,8 +80,8 @@ export default function Select({
setSelected(options.findIndex(({ value }) => Object.values(propSelected).includes(value)));
setShowMenu(false);
//show calender date range picker
if (value === 'custom_range') return setShowCalender(true);
//show calendar date range picker
if (value === 'custom_range') return setShowCalendar(true);
onChange && onChange(value);
},
[onChange, options, propSelected, setSelected]
@@ -110,7 +110,7 @@ export default function Select({
setSelected(focused);
if (options[focused].value === 'custom_range') {
setShowMenu(false);
return setShowCalender(true);
return setShowCalendar(true);
}
onChange && onChange(options[focused].value);
@@ -184,8 +184,8 @@ export default function Select({
useEffect(() => {
const addBackDrop = (e) => {
if (showCalender && !findDOMNodes(calenderRef.current).contains(e.target)) {
setShowCalender(false);
if (showCalendar && !findDOMNodes(calendarRef.current).contains(e.target)) {
setShowCalendar(false);
}
};
window.addEventListener('click', addBackDrop);
@@ -193,7 +193,7 @@ export default function Select({
return function cleanup() {
window.removeEventListener('click', addBackDrop);
};
}, [showCalender]);
}, [showCalendar]);
switch (type) {
case 'datepicker':
@@ -208,9 +208,9 @@ export default function Select({
trailingIcon={showMenu ? ArrowDropup : ArrowDropdown}
value={datePickerValue}
/>
{showCalender && (
{showCalendar && (
<Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref}>
<Calender onChange={handleDateRange} calenderRef={calenderRef} close={() => setShowCalender(false)} />
<Calendar onChange={handleDateRange} calendarRef={calendarRef} close={() => setShowCalendar(false)} />
</Menu>
)}
{showMenu ? (
@@ -223,7 +223,7 @@ export default function Select({
</Fragment>
);
// case 'dropdown':
// case 'dropdown':
default:
return (
<Fragment>

View File

@@ -4,14 +4,11 @@ import { useCallback, useState } from 'preact/hooks';
export default function Switch({ checked, id, onChange, label, labelPosition = 'before' }) {
const [isFocused, setFocused] = useState(false);
const handleChange = useCallback(
(event) => {
if (onChange) {
onChange(id, !checked);
}
},
[id, onChange, checked]
);
const handleChange = useCallback(() => {
if (onChange) {
onChange(id, !checked);
}
}, [id, onChange, checked]);
const handleFocus = useCallback(() => {
onChange && setFocused(true);

View File

@@ -1,12 +1,12 @@
import { Fragment, h } from 'preact';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { getTimelineEventBlocksFromTimelineEvents } from '../../utils/Timeline/timelineEventUtils';
import { ScrollPermission } from './ScrollPermission';
import type { ScrollPermission } from './ScrollPermission';
import { TimelineBlocks } from './TimelineBlocks';
import { TimelineChangeEvent } from './TimelineChangeEvent';
import type { TimelineChangeEvent } from './TimelineChangeEvent';
import { DisabledControls, TimelineControls } from './TimelineControls';
import { TimelineEvent } from './TimelineEvent';
import { TimelineEventBlock } from './TimelineEventBlock';
import type { TimelineEvent } from './TimelineEvent';
import type { TimelineEventBlock } from './TimelineEventBlock';
interface TimelineProps {
events: TimelineEvent[];

View File

@@ -1,7 +1,7 @@
import { h } from 'preact';
import { useCallback } from 'preact/hooks';
import { getColorFromTimelineEvent } from '../../utils/tailwind/twTimelineEventUtil';
import { TimelineEventBlock } from './TimelineEventBlock';
import type { TimelineEventBlock } from './TimelineEventBlock';
interface TimelineBlockViewProps {
block: TimelineEventBlock;

View File

@@ -3,7 +3,7 @@ import { useMemo } from 'preact/hooks';
import { findLargestYOffsetInBlocks, getTimelineWidthFromBlocks } from '../../utils/Timeline/timelineEventUtils';
import { convertRemToPixels } from '../../utils/windowUtils';
import { TimelineBlockView } from './TimelineBlockView';
import { TimelineEventBlock } from './TimelineEventBlock';
import type { TimelineEventBlock } from './TimelineEventBlock';
interface TimelineBlocksProps {
timeline: TimelineEventBlock[];
@@ -36,7 +36,7 @@ export const TimelineBlocks = ({ timeline, firstBlockOffset, onEventClick }: Tim
...block,
yOffset: block.yOffset + timelineBlockOffset,
};
return <TimelineBlockView block={updatedBlock} onClick={onClickHandler} />;
return <TimelineBlockView key={block.id} block={updatedBlock} onClick={onClickHandler} />;
})}
</div>
);

View File

@@ -1,4 +1,4 @@
import { TimelineEvent } from './TimelineEvent';
import type { TimelineEvent } from './TimelineEvent';
export interface TimelineChangeEvent {
timelineEvent: TimelineEvent;

View File

@@ -1,4 +1,4 @@
import { TimelineEvent } from './TimelineEvent';
import type { TimelineEvent } from './TimelineEvent';
export interface TimelineEventBlock extends TimelineEvent {
index: number;

View File

@@ -6,14 +6,14 @@ describe('Tooltip', () => {
test('renders in a relative position', async () => {
jest
.spyOn(window.HTMLElement.prototype, 'getBoundingClientRect')
// relativeTo
// relativeTo
.mockReturnValueOnce({
x: 100,
y: 100,
width: 50,
height: 10,
})
// tooltip
// tooltip
.mockReturnValueOnce({ width: 40, height: 15 });
const ref = createRef();
@@ -34,14 +34,14 @@ describe('Tooltip', () => {
window.innerWidth = 1024;
jest
.spyOn(window.HTMLElement.prototype, 'getBoundingClientRect')
// relativeTo
// relativeTo
.mockReturnValueOnce({
x: 1000,
y: 100,
width: 24,
height: 10,
})
// tooltip
// tooltip
.mockReturnValueOnce({ width: 50, height: 15 });
const ref = createRef();
@@ -61,14 +61,14 @@ describe('Tooltip', () => {
test('if too far left, renders to the right', async () => {
jest
.spyOn(window.HTMLElement.prototype, 'getBoundingClientRect')
// relativeTo
// relativeTo
.mockReturnValueOnce({
x: 0,
y: 100,
width: 24,
height: 10,
})
// tooltip
// tooltip
.mockReturnValueOnce({ width: 50, height: 15 });
const ref = createRef();
@@ -89,14 +89,14 @@ describe('Tooltip', () => {
window.scrollY = 90;
jest
.spyOn(window.HTMLElement.prototype, 'getBoundingClientRect')
// relativeTo
// relativeTo
.mockReturnValueOnce({
x: 100,
y: 100,
width: 24,
height: 10,
})
// tooltip
// tooltip
.mockReturnValueOnce({ width: 50, height: 15 });
const ref = createRef();