mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-23 12:19:04 +03:00
Compare commits
210
Commits
v0.8.0
..
v0.8.0-rc6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0232a9f94a | ||
|
|
e8586d6459 | ||
|
|
9cf49b5bc4 | ||
|
|
4a18fc58de | ||
|
|
5ddfde2e72 | ||
|
|
714d76887d | ||
|
|
0c0e1416ff | ||
|
|
c6044ba9a1 | ||
|
|
a7739a0a62 | ||
|
|
84ed126db6 | ||
|
|
a76f54c326 | ||
|
|
b93d354c60 | ||
|
|
14d218af46 | ||
|
|
bd4973e3f7 | ||
|
|
d94f81969b | ||
|
|
d32fed2c01 | ||
|
|
7b4e510b95 | ||
|
|
bb4f79cdfe | ||
|
|
e32e69c2d0 | ||
|
|
a71ae053e4 | ||
|
|
fcc9cd56cc | ||
|
|
b981a3110b | ||
|
|
2da50cc538 | ||
|
|
cb4a0aa594 | ||
|
|
52da1fddc7 | ||
|
|
14645ce4f8 | ||
|
|
97ce7f3028 | ||
|
|
3b5302f6ea | ||
|
|
74eb16f213 | ||
|
|
a3d6bf214c | ||
|
|
16121ffd00 | ||
|
|
91628bd5d8 | ||
|
|
b10b64bf57 | ||
|
|
749c34be9f | ||
|
|
8cfdfab985 | ||
|
|
ef25f8a31e | ||
|
|
2a0551a08a | ||
|
|
0b80419f15 | ||
|
|
0dc81117aa | ||
|
|
49b29d72a7 | ||
|
|
21ece238ff | ||
|
|
f6ba3f2daa | ||
|
|
bb0d3cb59a | ||
|
|
ca9b6d6c5c | ||
|
|
3103ad2bfe | ||
|
|
eab3998ad0 | ||
|
|
a3dfd3a8e0 | ||
|
|
f1c3087775 | ||
|
|
1be91ed3f2 | ||
|
|
fd83c4f229 | ||
|
|
de99221ad5 | ||
|
|
6892ce56ac | ||
|
|
41cea6f62e | ||
|
|
4bbffa97df | ||
|
|
614f8abfef | ||
|
|
14289b5fd1 | ||
|
|
4164beff1c | ||
|
|
9b3ab486de | ||
|
|
232a49814a | ||
|
|
6c61f0b135 | ||
|
|
c572cec253 | ||
|
|
d4941f2a5f | ||
|
|
bf5ec2f65f | ||
|
|
f8e21584b6 | ||
|
|
3cba83f84b | ||
|
|
dcb4255d7e | ||
|
|
9fc3c0dc2f | ||
|
|
a78830b48e | ||
|
|
949fbadcdc | ||
|
|
12c9e63b13 | ||
|
|
157b230702 | ||
|
|
c69299d659 | ||
|
|
285d630770 | ||
|
|
b9318092f4 | ||
|
|
905c361d52 | ||
|
|
4443abbc49 | ||
|
|
dabb36ad93 | ||
|
|
2bc8736fd9 | ||
|
|
e9b3b09cc2 | ||
|
|
ca337c32b4 | ||
|
|
24b8bd7c85 | ||
|
|
3ad75a441d | ||
|
|
f006e9be8d | ||
|
|
03f3ba8008 | ||
|
|
96a44eb7bf | ||
|
|
006782fe3d | ||
|
|
ff3e95bbf7 | ||
|
|
4b95a37e65 | ||
|
|
38c661b3a8 | ||
|
|
0d6e4f6a66 | ||
|
|
1ad2219f1c | ||
|
|
dfcdd289c3 | ||
|
|
32f5f2cca9 | ||
|
|
24bfe9f3e8 | ||
|
|
004667dc99 | ||
|
|
9d785dc781 | ||
|
|
cbba5a7af0 | ||
|
|
29b29ee349 | ||
|
|
9ad53e09af | ||
|
|
c9278991c9 | ||
|
|
729de48934 | ||
|
|
7476bff5fb | ||
|
|
1e9eae8d9a | ||
|
|
8113a53381 | ||
|
|
72833686f1 | ||
|
|
096c21f105 | ||
|
|
181f66357b | ||
|
|
a54fbc483c | ||
|
|
92d5a002d3 | ||
|
|
f9184903d7 | ||
|
|
91cde6ce7b | ||
|
|
186a4587c7 | ||
|
|
6049acb1f3 | ||
|
|
2d2ebf313c | ||
|
|
3d329dcb52 | ||
|
|
06854fc34f | ||
|
|
e01e14d866 | ||
|
|
3dfd251ebb | ||
|
|
dcea807f77 | ||
|
|
87d83ff33a | ||
|
|
1d31cbdf0d | ||
|
|
e05b27b8dc | ||
|
|
7111bd208e | ||
|
|
04a80280da | ||
|
|
3bda092140 | ||
|
|
9086820479 | ||
|
|
d1da57aedc | ||
|
|
6ded12c566 | ||
|
|
70352566a7 | ||
|
|
cf5cc86588 | ||
|
|
e41db49ab8 | ||
|
|
1b7effafee | ||
|
|
69e9e0b0bf | ||
|
|
89624df411 | ||
|
|
d1a7405211 | ||
|
|
040f8c7c20 | ||
|
|
6d7acabf4c | ||
|
|
45a8b42157 | ||
|
|
8785be24b7 | ||
|
|
cc0812540c | ||
|
|
5cf38ca4f7 | ||
|
|
7e4395c30e | ||
|
|
598d3aeda2 | ||
|
|
012dbf81f7 | ||
|
|
f869def12e | ||
|
|
31f7666337 | ||
|
|
9e339acbca | ||
|
|
8f8054a299 | ||
|
|
f7021eec4c | ||
|
|
c124153da4 | ||
|
|
706c2f921e | ||
|
|
de1d66bcb9 | ||
|
|
4502ca8e80 | ||
|
|
32a66fe5e8 | ||
|
|
e1251aafdb | ||
|
|
587494068c | ||
|
|
7a4d90a47a | ||
|
|
d06b587d33 | ||
|
|
eef70e434b | ||
|
|
b39da3ee01 | ||
|
|
e07c4e0d8c | ||
|
|
2f41ba6f77 | ||
|
|
bf95af0f22 | ||
|
|
2e15847f86 | ||
|
|
5992e85dc8 | ||
|
|
24d416b869 | ||
|
|
5dbf368c4b | ||
|
|
7d56fe105f | ||
|
|
e9327aa18c | ||
|
|
df56e079de | ||
|
|
8c5bfbd187 | ||
|
|
2613e74f97 | ||
|
|
9a7fb96357 | ||
|
|
37f9dfed92 | ||
|
|
68c1544808 | ||
|
|
2b3d3c5824 | ||
|
|
efea87a3ea | ||
|
|
977785fb10 | ||
|
|
4e113e62c0 | ||
|
|
5080b2d781 | ||
|
|
5cfd6d1edb | ||
|
|
27ae4d8ab0 | ||
|
|
3db33302ec | ||
|
|
f2910d48e0 | ||
|
|
cf0f8892e2 | ||
|
|
4d22e172ff | ||
|
|
8874a55b0f | ||
|
|
24b703a875 | ||
|
|
8b8f5b5c40 | ||
|
|
eac81136d2 | ||
|
|
d1e27b43ea | ||
|
|
105dcb7094 | ||
|
|
c0a16efdc1 | ||
|
|
2800c54743 | ||
|
|
2a24e8abcb | ||
|
|
37ee746ebb | ||
|
|
7ee6bfe855 | ||
|
|
40f57a8754 | ||
|
|
e0da462223 | ||
|
|
47a9fc4292 | ||
|
|
03fe5158db | ||
|
|
72be6b480d | ||
|
|
a8964dcc1f | ||
|
|
732e91ee42 | ||
|
|
27da080ce6 | ||
|
|
075d06b108 | ||
|
|
95dc17ffcd | ||
|
|
408b53f8b4 | ||
|
|
3ef68a297a | ||
|
|
3e9b3711dc |
+2
-1
@@ -26,7 +26,7 @@ export default function App() {
|
||||
<Config.Provider value={config}>
|
||||
<div className="md:flex flex-col md:flex-row md:min-h-screen w-full bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white">
|
||||
<Sidebar />
|
||||
<div className="flex-auto p-4 lg:pl-8 lg:pr-8 min-w-0">
|
||||
<div className="p-4 min-w-0">
|
||||
<Router>
|
||||
<CameraMap path="/cameras/:camera/editor" />
|
||||
<Camera path="/cameras/:camera" />
|
||||
@@ -39,4 +39,5 @@ export default function App() {
|
||||
</div>
|
||||
</Config.Provider>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
+8
-37
@@ -1,6 +1,7 @@
|
||||
import { h } from 'preact';
|
||||
import Box from './components/Box';
|
||||
import Button from './components/Button';
|
||||
import CameraImage from './components/CameraImage';
|
||||
import Heading from './components/Heading';
|
||||
import Switch from './components/Switch';
|
||||
import { route } from 'preact-router';
|
||||
@@ -189,7 +190,7 @@ ${Object.keys(zonePoints)
|
||||
const handleAddObjectMask = useCallback(() => {
|
||||
const n = Object.keys(objectMaskPoints).filter((name) => name.startsWith('object_')).length;
|
||||
const newObjectName = `object_${n}`;
|
||||
const newObjectMaskPoints = { ...objectMaskPoints, [newObjectName]: [[]] };
|
||||
const newObjectMaskPoints = { ...objectMaskPoints, [newObjectName]: [] };
|
||||
setObjectMaskPoints(newObjectMaskPoints);
|
||||
setEditing({ set: newObjectMaskPoints, key: newObjectName, subkey: 0, fn: setObjectMaskPoints });
|
||||
}, [objectMaskPoints, setObjectMaskPoints, setEditing]);
|
||||
@@ -197,7 +198,7 @@ ${Object.keys(zonePoints)
|
||||
const handleRemoveObjectMask = useCallback(
|
||||
(key, subkey) => {
|
||||
const newObjectMaskPoints = { ...objectMaskPoints };
|
||||
delete newObjectMaskPoints[key][subkey];
|
||||
delete newObjectMaskPoints[key];
|
||||
setObjectMaskPoints(newObjectMaskPoints);
|
||||
},
|
||||
[objectMaskPoints, setObjectMaskPoints]
|
||||
@@ -217,20 +218,6 @@ ${Object.keys(objectMaskPoints)
|
||||
.join('\n')}`);
|
||||
}, [objectMaskPoints]);
|
||||
|
||||
const handleAddToObjectMask = useCallback(
|
||||
(key) => {
|
||||
const newObjectMaskPoints = { ...objectMaskPoints, [key]: [...objectMaskPoints[key], []] };
|
||||
setObjectMaskPoints(newObjectMaskPoints);
|
||||
setEditing({
|
||||
set: newObjectMaskPoints,
|
||||
key,
|
||||
subkey: newObjectMaskPoints[key].length - 1,
|
||||
fn: setObjectMaskPoints,
|
||||
});
|
||||
},
|
||||
[objectMaskPoints, setObjectMaskPoints, setEditing]
|
||||
);
|
||||
|
||||
const handleChangeSnap = useCallback(
|
||||
(id, value) => {
|
||||
setSnap(value);
|
||||
@@ -252,10 +239,10 @@ ${Object.keys(objectMaskPoints)
|
||||
|
||||
<Box className="space-y-4">
|
||||
<div className="relative">
|
||||
<img ref={imageRef} src={`${apiHost}/api/${camera}/latest.jpg`} />
|
||||
<CameraImage imageRef={imageRef} camera={camera} />
|
||||
<EditableMask
|
||||
onChange={handleUpdateEditable}
|
||||
points={'subkey' in editing ? editing.set[editing.key][editing.subkey] : editing.set[editing.key]}
|
||||
points={editing.subkey ? editing.set[editing.key][editing.subkey] : editing.set[editing.key]}
|
||||
scale={imageScale}
|
||||
snap={snap}
|
||||
width={width}
|
||||
@@ -294,7 +281,6 @@ ${Object.keys(objectMaskPoints)
|
||||
isMulti
|
||||
editing={editing}
|
||||
title="Object masks"
|
||||
onAdd={handleAddToObjectMask}
|
||||
onCopy={handleCopyObjectMasks}
|
||||
onCreate={handleAddObjectMask}
|
||||
onEdit={handleEditObjectMask}
|
||||
@@ -424,7 +410,6 @@ function MaskValues({
|
||||
isMulti = false,
|
||||
editing,
|
||||
title,
|
||||
onAdd,
|
||||
onCopy,
|
||||
onCreate,
|
||||
onEdit,
|
||||
@@ -466,14 +451,6 @@ function MaskValues({
|
||||
[onRemove]
|
||||
);
|
||||
|
||||
const handleAdd = useCallback(
|
||||
(event) => {
|
||||
const { key } = event.target.dataset;
|
||||
onAdd(key);
|
||||
},
|
||||
[onAdd]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className="overflow-hidden" onmouseover={handleMousein} onmouseout={handleMouseout}>
|
||||
<div class="flex space-x-4">
|
||||
@@ -490,20 +467,15 @@ function MaskValues({
|
||||
return (
|
||||
<div>
|
||||
{` ${mainkey}:\n mask:\n`}
|
||||
{onAdd && showButtons ? (
|
||||
<Button className="absolute -mt-12 right-0 font-sans" data-key={mainkey} onClick={handleAdd}>
|
||||
{`Add to ${mainkey}`}
|
||||
</Button>
|
||||
) : null}
|
||||
{points[mainkey].map((item, subkey) => (
|
||||
<Item
|
||||
mainkey={mainkey}
|
||||
subkey={subkey}
|
||||
editing={editing}
|
||||
handleEdit={handleEdit}
|
||||
handleRemove={handleRemove}
|
||||
points={item}
|
||||
showButtons={showButtons}
|
||||
handleRemove={handleRemove}
|
||||
yamlKeyPrefix={yamlKeyPrefix}
|
||||
/>
|
||||
))}
|
||||
@@ -514,11 +486,10 @@ function MaskValues({
|
||||
<Item
|
||||
mainkey={mainkey}
|
||||
editing={editing}
|
||||
handleAdd={onAdd ? handleAdd : undefined}
|
||||
handleEdit={handleEdit}
|
||||
handleRemove={handleRemove}
|
||||
points={points[mainkey]}
|
||||
showButtons={showButtons}
|
||||
handleRemove={handleRemove}
|
||||
yamlKeyPrefix={yamlKeyPrefix}
|
||||
/>
|
||||
);
|
||||
@@ -529,7 +500,7 @@ function MaskValues({
|
||||
);
|
||||
}
|
||||
|
||||
function Item({ mainkey, subkey, editing, handleEdit, points, showButtons, handleAdd, handleRemove, yamlKeyPrefix }) {
|
||||
function Item({ mainkey, subkey, editing, handleEdit, points, showButtons, handleRemove, yamlKeyPrefix }) {
|
||||
return (
|
||||
<span
|
||||
data-key={mainkey}
|
||||
|
||||
+41
-57
@@ -1,6 +1,4 @@
|
||||
import { h } from 'preact';
|
||||
import Box from './components/Box';
|
||||
import Button from './components/Button';
|
||||
import Heading from './components/Heading';
|
||||
import Link from './components/Link';
|
||||
import { ApiHost, Config } from './context';
|
||||
@@ -41,73 +39,59 @@ export default function Debug() {
|
||||
const cameraNames = Object.keys(cameras);
|
||||
const cameraDataKeys = Object.keys(cameras[cameraNames[0]]);
|
||||
|
||||
const handleCopyConfig = useCallback(async () => {
|
||||
await window.navigator.clipboard.writeText(JSON.stringify(config, null, 2));
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<Heading>
|
||||
Debug <span className="text-sm">{service.version}</span>
|
||||
</Heading>
|
||||
|
||||
<Box>
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>detector</Th>
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>detector</Th>
|
||||
{detectorDataKeys.map((name) => (
|
||||
<Th>{name.replace('_', ' ')}</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{detectorNames.map((detector, i) => (
|
||||
<Tr index={i}>
|
||||
<Td>{detector}</Td>
|
||||
{detectorDataKeys.map((name) => (
|
||||
<Th>{name.replace('_', ' ')}</Th>
|
||||
<Td key={`${name}-${detector}`}>{detectors[detector][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{detectorNames.map((detector, i) => (
|
||||
<Tr index={i}>
|
||||
<Td>{detector}</Td>
|
||||
{detectorDataKeys.map((name) => (
|
||||
<Td key={`${name}-${detector}`}>{detectors[detector][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
||||
<Box>
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>camera</Th>
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>camera</Th>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Th>{name.replace('_', ' ')}</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{cameraNames.map((camera, i) => (
|
||||
<Tr index={i}>
|
||||
<Td>
|
||||
<Link href={`/cameras/${camera}`}>{camera}</Link>
|
||||
</Td>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Th>{name.replace('_', ' ')}</Th>
|
||||
<Td key={`${name}-${camera}`}>{cameras[camera][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{cameraNames.map((camera, i) => (
|
||||
<Tr index={i}>
|
||||
<Td>
|
||||
<Link href={`/cameras/${camera}`}>{camera}</Link>
|
||||
</Td>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Td key={`${name}-${camera}`}>{cameras[camera][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
||||
<Box className="relative">
|
||||
<Heading size="sm">Config</Heading>
|
||||
<Button className="absolute top-4 right-8" onClick={handleCopyConfig}>
|
||||
Copy to Clipboard
|
||||
</Button>
|
||||
<pre className="overflow-auto font-mono text-gray-900 dark:text-gray-100 rounded bg-gray-100 dark:bg-gray-800 p-2 max-h-96">
|
||||
{JSON.stringify(config, null, 2)}
|
||||
</pre>
|
||||
</Box>
|
||||
<Heading size="sm">Config</Heading>
|
||||
<pre className="font-mono overflow-y-scroll overflow-x-scroll max-h-96 rounded bg-white dark:bg-gray-900">
|
||||
{JSON.stringify(config, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ export default function Events({ url } = {}) {
|
||||
const searchKeys = Array.from(searchParams.keys());
|
||||
|
||||
return (
|
||||
<div className="space-y-4 w-full">
|
||||
<div className="space-y-4">
|
||||
<Heading>Events</Heading>
|
||||
|
||||
{searchKeys.length ? (
|
||||
@@ -43,7 +43,7 @@ export default function Events({ url } = {}) {
|
||||
) : null}
|
||||
|
||||
<Box className="min-w-0 overflow-auto">
|
||||
<Table className="w-full">
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th></Th>
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { h } from 'preact';
|
||||
import CameraImage from './CameraImage';
|
||||
import { ApiHost, Config } from '../context';
|
||||
import { useCallback, useState } from 'preact/hooks';
|
||||
import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
|
||||
|
||||
const MIN_LOAD_TIMEOUT_MS = 200;
|
||||
export default function AutoUpdatingCameraImage({ camera, searchParams }) {
|
||||
const apiHost = useContext(ApiHost);
|
||||
|
||||
export default function AutoUpdatingCameraImage({ camera, searchParams, showFps = true }) {
|
||||
const [key, setKey] = useState(Date.now());
|
||||
const [fps, setFps] = useState(0);
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
setKey(Date.now());
|
||||
}, 500);
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [key, searchParams]);
|
||||
|
||||
const handleLoad = useCallback(() => {
|
||||
const loadTime = Date.now() - key;
|
||||
setFps((1000 / Math.max(loadTime, MIN_LOAD_TIMEOUT_MS)).toFixed(1));
|
||||
setTimeout(
|
||||
() => {
|
||||
setKey(Date.now());
|
||||
},
|
||||
loadTime > MIN_LOAD_TIMEOUT_MS ? 1 : MIN_LOAD_TIMEOUT_MS
|
||||
);
|
||||
}, [key, searchParams, setFps]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CameraImage camera={camera} onload={handleLoad} searchParams={`cache=${key}&${searchParams}`} />
|
||||
{showFps ? <span className="text-xs">Displaying at {fps}fps</span> : null}
|
||||
</div>
|
||||
);
|
||||
return <CameraImage camera={camera} searchParams={`cache=${key}&${searchParams}`} />;
|
||||
}
|
||||
|
||||
@@ -1,60 +1,38 @@
|
||||
import { h } from 'preact';
|
||||
import { ApiHost, Config } from '../context';
|
||||
import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { useCallback, useEffect, useContext, useState } from 'preact/hooks';
|
||||
|
||||
export default function CameraImage({ camera, onload, searchParams = '' }) {
|
||||
export default function CameraImage({ camera, searchParams = '', imageRef }) {
|
||||
const config = useContext(Config);
|
||||
const apiHost = useContext(ApiHost);
|
||||
const [availableWidth, setAvailableWidth] = useState(0);
|
||||
const [loadedSrc, setLoadedSrc] = useState(null);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const { name, width, height } = config.cameras[camera];
|
||||
|
||||
const aspectRatio = width / height;
|
||||
const innerWidth = parseInt(window.innerWidth, 10);
|
||||
|
||||
const resizeObserver = useMemo(() => {
|
||||
return new ResizeObserver((entries) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (Array.isArray(entries) && entries.length) {
|
||||
setAvailableWidth(entries[0].contentRect.width);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [setAvailableWidth, width]);
|
||||
const responsiveWidths = [640, 768, 1024, 1280];
|
||||
if (innerWidth > responsiveWidths[responsiveWidths.length - 1]) {
|
||||
responsiveWidths.push(innerWidth);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}, [resizeObserver, containerRef.current]);
|
||||
|
||||
const scaledHeight = useMemo(() => Math.min(Math.ceil(availableWidth / aspectRatio), height), [
|
||||
availableWidth,
|
||||
aspectRatio,
|
||||
height,
|
||||
]);
|
||||
|
||||
const img = useMemo(() => new Image(), [camera]);
|
||||
img.onload = useCallback(
|
||||
(event) => {
|
||||
const src = event.path[0].currentSrc;
|
||||
setLoadedSrc(src);
|
||||
onload && onload(event);
|
||||
const src = `${apiHost}/api/${camera}/latest.jpg`;
|
||||
const { srcset, sizes } = responsiveWidths.reduce(
|
||||
(memo, w, i) => {
|
||||
memo.srcset.push(`${src}?h=${Math.ceil(w / aspectRatio)}&${searchParams} ${w}w`);
|
||||
memo.sizes.push(`(max-width: ${w}) ${Math.ceil((w / innerWidth) * 100)}vw`);
|
||||
return memo;
|
||||
},
|
||||
[searchParams, onload]
|
||||
{ srcset: [], sizes: [] }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scaledHeight) {
|
||||
return;
|
||||
}
|
||||
img.src = `${apiHost}/api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
|
||||
}, [apiHost, name, img, searchParams, scaledHeight]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
{loadedSrc ? <img width={scaledHeight * aspectRatio} height={scaledHeight} src={loadedSrc} alt={name} /> : null}
|
||||
</div>
|
||||
<img
|
||||
className="w-full"
|
||||
srcset={srcset.join(', ')}
|
||||
sizes={sizes.join(', ')}
|
||||
src={`${srcset[srcset.length - 1]}`}
|
||||
alt={name}
|
||||
ref={imageRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user