Camera.jsx 4.1 KB

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