test(web): add eslint and PR lint validation

This commit is contained in:
Paul Armstrong
2021-02-09 11:35:33 -08:00
committed by Blake Blackshear
parent 513a099c24
commit daa759cc55
33 changed files with 5190 additions and 505 deletions

View File

@@ -12,16 +12,14 @@ import { useLayoutEffect, useCallback, useRef, useState } from 'preact/hooks';
// We would typically preserve these in component state
// But need to avoid too many re-renders
let ticking = false;
let lastScrollY = window.scrollY;
export default function AppBar({ title }) {
const [show, setShow] = useState(true);
const [atZero, setAtZero] = useState(window.scrollY === 0);
const [_, setDrawerVisible] = useState(true);
const [showMoreMenu, setShowMoreMenu] = useState(false);
const { currentMode, persistedMode, setDarkMode } = useDarkMode();
const { showDrawer, setShowDrawer } = useDrawer();
const { setDarkMode } = useDarkMode();
const { setShowDrawer } = useDrawer();
const handleSelectDarkMode = useCallback(
(value, label) => {
@@ -37,15 +35,11 @@ export default function AppBar({ title }) {
(event) => {
const scrollY = window.scrollY;
// if (!ticking) {
window.requestAnimationFrame(() => {
setShow(scrollY <= 0 || lastScrollY > scrollY);
setAtZero(scrollY === 0);
ticking = false;
lastScrollY = scrollY;
});
ticking = true;
// }
},
[setShow]
);
@@ -55,7 +49,7 @@ export default function AppBar({ title }) {
return () => {
document.removeEventListener('scroll', scrollListener);
};
}, []);
}, [scrollListener]);
const handleShowMenu = useCallback(() => {
setShowMoreMenu(true);

View File

@@ -17,7 +17,7 @@ export default function AutoUpdatingCameraImage({ camera, searchParams, showFps
},
loadTime > MIN_LOAD_TIMEOUT_MS ? 1 : MIN_LOAD_TIMEOUT_MS
);
}, [key, searchParams, setFps]);
}, [key, setFps]);
return (
<div>

View File

@@ -1,7 +1,7 @@
import { h } from 'preact';
import ActivityIndicator from './ActivityIndicator';
import { useApiHost, useConfig } from '../api';
import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'preact/hooks';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
export default function CameraImage({ camera, onload, searchParams = '' }) {
const { data: config } = useConfig();
@@ -22,14 +22,11 @@ export default function CameraImage({ camera, onload, searchParams = '' }) {
}
});
});
}, [setAvailableWidth, width]);
}, []);
useEffect(() => {
if (!containerRef.current) {
return;
}
resizeObserver.observe(containerRef.current);
}, [resizeObserver, containerRef.current]);
}, [resizeObserver, containerRef]);
const scaledHeight = useMemo(() => Math.min(Math.ceil(availableWidth / aspectRatio), height), [
availableWidth,
@@ -38,26 +35,28 @@ export default function CameraImage({ camera, onload, searchParams = '' }) {
]);
const scaledWidth = useMemo(() => Math.ceil(scaledHeight * aspectRatio), [scaledHeight, aspectRatio]);
const img = useMemo(() => new Image(), [camera]);
const img = useMemo(() => new Image(), []);
img.onload = useCallback(
(event) => {
setHasLoaded(true);
const ctx = canvasRef.current.getContext('2d');
ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
if (canvasRef.current) {
const ctx = canvasRef.current.getContext('2d');
ctx.drawImage(img, 0, 0, scaledWidth, scaledHeight);
}
onload && onload(event);
},
[setHasLoaded, onload, canvasRef.current]
[img, scaledHeight, scaledWidth, setHasLoaded, onload, canvasRef]
);
useEffect(() => {
if (!scaledHeight || !canvasRef.current) {
if (scaledHeight === 0 || !canvasRef.current) {
return;
}
img.src = `${apiHost}/api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
}, [apiHost, name, img, searchParams, scaledHeight]);
}, [apiHost, canvasRef, name, img, searchParams, scaledHeight]);
return (
<div className="relative" ref={containerRef}>
<div className="relative w-full" ref={containerRef}>
<canvas height={scaledHeight} ref={canvasRef} width={scaledWidth} />
{!hasLoaded ? (
<div className="absolute inset-0 flex justify-center" style={`height: ${scaledHeight}px`}>

View File

@@ -26,14 +26,14 @@ export default function Box({
{media || header ? (
<Element href={href} {...props}>
{media}
<div class="p-4 pb-2">{header ? <Heading size="base">{header}</Heading> : null}</div>
<div className="p-4 pb-2">{header ? <Heading size="base">{header}</Heading> : null}</div>
</Element>
) : null}
{buttons.length || content ? (
<div class="pl-4 pb-2">
<div className="pl-4 pb-2">
{content || null}
{buttons.length ? (
<div class="flex space-x-4 -ml-2">
<div className="flex space-x-4 -ml-2">
{buttons.map(({ name, href }) => (
<Button key={name} href={href} type="text">
{name}

View File

@@ -6,7 +6,7 @@ export default function LinkedLogo() {
return (
<Heading size="lg">
<a className="transition-colors flex items-center space-x-4 dark:text-white hover:text-blue-500" href="/">
<div class="w-10">
<div className="w-10">
<Logo />
</div>
Frigate

View File

@@ -1,6 +1,6 @@
import { h } from 'preact';
import RelativeModal from './RelativeModal';
import { useCallback, useEffect } from 'preact/hooks';
import { useCallback } from 'preact/hooks';
export default function Menu({ className, children, onDismiss, relativeTo, widthRelative }) {
return relativeTo ? (
@@ -21,21 +21,12 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
onSelect && onSelect(value, label);
}, [onSelect, value, label]);
const handleKeydown = useCallback(
(event) => {
if (event.key === 'Enter') {
onSelect && onSelect(value, label);
}
},
[onSelect, value, label]
);
return (
<div
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' : ''
}`}
onclick={handleClick}
onClick={handleClick}
role="option"
>
{Icon ? (
@@ -43,7 +34,7 @@ export function MenuItem({ focus, icon: Icon, label, onSelect, value }) {
<Icon />
</div>
) : null}
<div class="whitespace-nowrap">{label}</div>
<div className="whitespace-nowrap">{label}</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { h, Fragment } from 'preact';
import { Link } from 'preact-router/match';
import { useCallback, useState } from 'preact/hooks';
import { useCallback } from 'preact/hooks';
import { useDrawer } from '../context';
export default function NavigationDrawer({ children, header }) {

View File

@@ -44,7 +44,7 @@ export default function RelativeModal({
return;
}
},
[ref.current]
[ref]
);
useLayoutEffect(() => {
@@ -84,7 +84,7 @@ export default function RelativeModal({
const focusable = ref.current.querySelector('[tabindex]');
focusable && focusable.focus();
}
}, [relativeTo && relativeTo.current, ref && ref.current, widthRelative]);
}, [relativeTo, ref, widthRelative]);
useEffect(() => {
if (position.top >= 0) {
@@ -92,7 +92,7 @@ export default function RelativeModal({
} else {
setShow(false);
}
}, [show, position.top, ref.current]);
}, [show, position, ref]);
const menu = (
<Fragment>
@@ -102,7 +102,7 @@ export default function RelativeModal({
className={`z-10 bg-white dark:bg-gray-700 dark:text-white absolute shadow-lg rounded w-auto h-auto transition-all duration-75 transform scale-90 opacity-0 overflow-scroll ${
show ? 'scale-100 opacity-100' : ''
} ${className}`}
onkeydown={handleKeydown}
onKeyDown={handleKeydown}
role={role}
ref={ref}
style={position.top >= 0 ? position : null}

View File

@@ -28,7 +28,7 @@ export default function Select({ label, onChange, options: inputOptions = [], se
onChange && onChange(value, label);
setShowMenu(false);
},
[onChange]
[onChange, options]
);
const handleClick = useCallback(() => {
@@ -38,32 +38,34 @@ export default function Select({ label, onChange, options: inputOptions = [], se
const handleKeydown = useCallback(
(event) => {
switch (event.key) {
case 'Enter': {
if (!showMenu) {
setShowMenu(true);
setFocused(selected);
} else {
setSelected(focused);
onChange && onChange(options[focused].value, options[focused].label);
setShowMenu(false);
}
break;
case 'Enter': {
if (!showMenu) {
setShowMenu(true);
setFocused(selected);
} else {
setSelected(focused);
onChange && onChange(options[focused].value, options[focused].label);
setShowMenu(false);
}
break;
}
case 'ArrowDown': {
const newIndex = focused + 1;
newIndex < options.length && setFocused(newIndex);
break;
}
case 'ArrowDown': {
const newIndex = focused + 1;
newIndex < options.length && setFocused(newIndex);
break;
}
case 'ArrowUp': {
const newIndex = focused - 1;
newIndex > -1 && setFocused(newIndex);
break;
}
case 'ArrowUp': {
const newIndex = focused - 1;
newIndex > -1 && setFocused(newIndex);
break;
}
// no default
}
},
[setShowMenu, setFocused, focused, selected]
[onChange, options, showMenu, setShowMenu, setFocused, focused, selected]
);
const handleDismiss = useCallback(() => {
@@ -80,7 +82,8 @@ export default function Select({ label, onChange, options: inputOptions = [], se
setSelected(selectedIndex);
setFocused(selectedIndex);
}
}, [propSelected]);
// DO NOT include `selected`
}, [options, propSelected]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Fragment>

View File

@@ -2,9 +2,7 @@ import { h } from 'preact';
import { useCallback, useState } from 'preact/hooks';
export default function Switch({ checked, id, onChange }) {
const [internalState, setInternalState] = useState(checked);
const [isFocused, setFocused] = useState(false);
const [isHovered, setHovered] = useState(false);
const handleChange = useCallback(
(event) => {
@@ -25,12 +23,12 @@ export default function Switch({ checked, id, onChange }) {
return (
<label
for={id}
htmlFor={id}
className={`flex items-center justify-center ${onChange ? 'cursor-pointer' : 'cursor-not-allowed'}`}
>
<div
onmouseover={handleFocus}
onmouseout={handleBlur}
onMouseOver={handleFocus}
onMouseOut={handleBlur}
className={`w-8 h-5 relative ${!onChange ? 'opacity-60' : ''}`}
>
<div className="relative overflow-hidden">
@@ -38,7 +36,7 @@ export default function Switch({ checked, id, onChange }) {
className="absolute left-48"
onBlur={handleBlur}
onFocus={handleFocus}
tabindex="0"
tabIndex="0"
id={id}
type="checkbox"
onChange={handleChange}

View File

@@ -30,7 +30,7 @@ export function Tr({ children, className = '' }) {
export function Th({ children, className = '', colspan }) {
return (
<th className={`border-b border-gray-400 p-2 px-1 lg:p-4 text-left ${className}`} colspan={colspan}>
<th className={`border-b border-gray-400 p-2 px-1 lg:p-4 text-left ${className}`} colSpan={colspan}>
{children}
</th>
);
@@ -38,7 +38,7 @@ export function Th({ children, className = '', colspan }) {
export function Td({ children, className = '', colspan }) {
return (
<td className={`p-2 px-1 lg:p-4 ${className}`} colspan={colspan}>
<td className={`p-2 px-1 lg:p-4 ${className}`} colSpan={colspan}>
{children}
</td>
);

View File

@@ -43,12 +43,12 @@ export default function TextField({
[onChangeText, setValue]
);
// Reset the state if the prop value changes
useEffect(() => {
if (propValue !== value) {
setValue(propValue);
}
}, [propValue, setValue]);
// DO NOT include `value`
}, [propValue, setValue]); // eslint-disable-line react-hooks/exhaustive-deps
const labelMoved = isFocused || value !== '';
@@ -62,7 +62,7 @@ export default function TextField({
>
<label className="flex space-x-2 items-center">
{LeadingIcon ? (
<div class="w-10 h-full">
<div className="w-10 h-full">
<LeadingIcon />
</div>
) : null}
@@ -72,8 +72,8 @@ export default function TextField({
onBlur={handleBlur}
onFocus={handleFocus}
onInput={handleChange}
readonly={readonly}
tabindex="0"
readOnly={readonly}
tabIndex="0"
type={keyboardType}
value={value}
{...props}
@@ -87,7 +87,7 @@ export default function TextField({
</div>
</div>
{TrailingIcon ? (
<div class="w-10 h-10">
<div className="w-10 h-10">
<TrailingIcon />
</div>
) : null}