Camera.jsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { h } from 'preact';
  2. import AutoUpdatingCameraImage from '../components/AutoUpdatingCameraImage';
  3. import Button from '../components/Button';
  4. import Card from '../components/Card';
  5. import Heading from '../components/Heading';
  6. import Link from '../components/Link';
  7. import SettingsIcon from '../icons/Settings';
  8. import Switch from '../components/Switch';
  9. import { usePersistence } from '../context';
  10. import { useCallback, useMemo, useState } from 'preact/hooks';
  11. import { useApiHost, useConfig } from '../api';
  12. const emptyObject = Object.freeze({});
  13. export default function Camera({ camera }) {
  14. const { data: config } = useConfig();
  15. const apiHost = useApiHost();
  16. const [showSettings, setShowSettings] = useState(false);
  17. const cameraConfig = config?.cameras[camera];
  18. const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
  19. const handleSetOption = useCallback(
  20. (id, value) => {
  21. const newOptions = { ...options, [id]: value };
  22. setOptions(newOptions);
  23. },
  24. [options, setOptions]
  25. );
  26. const searchParams = useMemo(
  27. () =>
  28. new URLSearchParams(
  29. Object.keys(options).reduce((memo, key) => {
  30. memo.push([key, options[key] === true ? '1' : '0']);
  31. return memo;
  32. }, [])
  33. ),
  34. [options]
  35. );
  36. const handleToggleSettings = useCallback(() => {
  37. setShowSettings(!showSettings);
  38. }, [showSettings, setShowSettings]);
  39. const optionContent = showSettings ? (
  40. <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
  41. <div className="flex space-x-3">
  42. <Switch checked={options['bbox']} id="bbox" onChange={handleSetOption} />
  43. <span className="inline-flex">Bounding box</span>
  44. </div>
  45. <div className="flex space-x-3">
  46. <Switch checked={options['timestamp']} id="timestamp" onChange={handleSetOption} />
  47. <span className="inline-flex">Timestamp</span>
  48. </div>
  49. <div className="flex space-x-3">
  50. <Switch checked={options['zones']} id="zones" onChange={handleSetOption} />
  51. <span className="inline-flex">Zones</span>
  52. </div>
  53. <div className="flex space-x-3">
  54. <Switch checked={options['mask']} id="mask" onChange={handleSetOption} />
  55. <span className="inline-flex">Masks</span>
  56. </div>
  57. <div className="flex space-x-3">
  58. <Switch checked={options['motion']} id="motion" onChange={handleSetOption} />
  59. <span className="inline-flex">Motion boxes</span>
  60. </div>
  61. <div className="flex space-x-3">
  62. <Switch checked={options['regions']} id="regions" onChange={handleSetOption} />
  63. <span className="inline-flex">Regions</span>
  64. </div>
  65. <Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
  66. </div>
  67. ) : null;
  68. return (
  69. <div className="space-y-4">
  70. <Heading size="2xl">{camera}</Heading>
  71. <div>
  72. <AutoUpdatingCameraImage camera={camera} searchParams={searchParams} />
  73. </div>
  74. <Button onClick={handleToggleSettings} type="text">
  75. <span className="w-5 h-5">
  76. <SettingsIcon />
  77. </span>{' '}
  78. <span>{showSettings ? 'Hide' : 'Show'} Options</span>
  79. </Button>
  80. {showSettings ? <Card header="Options" elevated={false} content={optionContent} /> : null}
  81. <div className="space-y-4">
  82. <Heading size="sm">Tracked objects</Heading>
  83. <div className="flex flex-wrap justify-start">
  84. {cameraConfig.objects.track.map((objectType) => (
  85. <Card
  86. className="mb-4 mr-4"
  87. key={objectType}
  88. header={objectType}
  89. href={`/events?camera=${camera}&label=${objectType}`}
  90. media={<img src={`${apiHost}/api/${camera}/${objectType}/best.jpg?crop=1&h=150`} />}
  91. />
  92. ))}
  93. </div>
  94. </div>
  95. </div>
  96. );
  97. }