CameraImage.jsx 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import { h } from 'preact';
  2. import ActivityIndicator from './ActivityIndicator';
  3. import { useApiHost, useConfig } from '../api';
  4. import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'preact/hooks';
  5. export default function CameraImage({ camera, onload, searchParams = '' }) {
  6. const { data: config } = useConfig();
  7. const apiHost = useApiHost();
  8. const [availableWidth, setAvailableWidth] = useState(0);
  9. const [loadedSrc, setLoadedSrc] = useState(null);
  10. const containerRef = useRef(null);
  11. const { name, width, height } = config.cameras[camera];
  12. const aspectRatio = width / height;
  13. const resizeObserver = useMemo(() => {
  14. return new ResizeObserver((entries) => {
  15. window.requestAnimationFrame(() => {
  16. if (Array.isArray(entries) && entries.length) {
  17. setAvailableWidth(entries[0].contentRect.width);
  18. }
  19. });
  20. });
  21. }, [setAvailableWidth, width]);
  22. useEffect(() => {
  23. if (!containerRef.current) {
  24. return;
  25. }
  26. resizeObserver.observe(containerRef.current);
  27. }, [resizeObserver, containerRef.current]);
  28. const scaledHeight = useMemo(() => Math.min(Math.ceil(availableWidth / aspectRatio), height), [
  29. availableWidth,
  30. aspectRatio,
  31. height,
  32. ]);
  33. const img = useMemo(() => new Image(), [camera]);
  34. img.onload = useCallback(
  35. (event) => {
  36. const src = event.srcElement.currentSrc;
  37. setLoadedSrc(src);
  38. onload && onload(event);
  39. },
  40. [searchParams, onload]
  41. );
  42. useEffect(() => {
  43. if (!scaledHeight) {
  44. return;
  45. }
  46. img.src = `${apiHost}/api/${name}/latest.jpg?h=${scaledHeight}${searchParams ? `&${searchParams}` : ''}`;
  47. }, [apiHost, name, img, searchParams, scaledHeight]);
  48. return (
  49. <div ref={containerRef}>
  50. {loadedSrc ? (
  51. <img width={scaledHeight * aspectRatio} height={scaledHeight} src={loadedSrc} alt={name} />
  52. ) : (
  53. <ActivityIndicator />
  54. )}
  55. </div>
  56. );
  57. }