import React, { useState, useEffect, useRef, useMemo } from "react"; import { Link, useNavigate } from 'react-router-dom' import SystemCard from "../System/SystemCard"; import SupportModal from "../Common/SupportModal"; import "./Main.css"; import { createMedia } from "@artsy/fresnel"; import { Button, ButtonGroup, ButtonContent, Container, Divider, Grid, Header, Icon, Menu, Segment, Sidebar, Loader, Statistic, Transition, } from 'semantic-ui-react' import { AreaClosed, Line, Bar } from '@visx/shape'; import { curveMonotoneX } from '@visx/curve'; import { GridRows, GridColumns } from '@visx/grid'; import { scaleTime, scaleLinear } from '@visx/scale'; import { withTooltip, Tooltip, TooltipWithBounds, defaultStyles } from '@visx/tooltip'; import { useTooltip, useTooltipInPortal } from '@visx/tooltip'; import { localPoint } from '@visx/event'; import { LinearGradient } from '@visx/gradient'; import { max, extent, bisector } from 'd3-array'; import { timeFormat } from 'd3-time-format'; import { AxisLeft } from '@visx/axis'; import { useDispatch } from 'react-redux' import { useGetStatsQuery, useGetSystemsQuery, useGetSiteStatsQuery } from "../features/api/apiSlice"; /* Responsive component was removed from Semantic UI. This is discussed here: https://github.com/Semantic-Org/Semantic-UI-React/pull/4008 */ const AppMedia = createMedia({ breakpoints: { mobile: 320, tablet: 768, computer: 992, largeScreen: 1200, widescreen: 1920 } }); const mediaStyles = AppMedia.createMediaStyle(); const { Media } = AppMedia; /* eslint-disable react/no-multi-comp */ /* Heads up! HomepageHeading uses inline styling, however it's not the best practice. Use CSS or styled components for * such things. */ const HomepageHeading = ({ mobile }) => (
) /* HomepageHeading.propTypes = { mobile: PropTypes.bool, } */ /* Heads up! * Neither Semantic UI nor Semantic UI React offer a responsive navbar, however, it can be implemented easily. * It can be more complicated, but you can create really flexible markup. */ /* General flow: - componentDidMount(): it will call fetchSystem() this will perform an HTTP request if Systems does not exist, and then add System to Props */ const DesktopContainer = (props) => { const [fixed, setFixed] = useState(false); const hideFixedMenu = () => setFixed(false) const showFixedMenu = () => setFixed(true) const { children } = props return (
{process.env.REACT_APP_SITE_NAME}
Listen Events About
{children}
) } /* DesktopContainer.propTypes = { children: PropTypes.node, } */ const MobileContainer = (props) => { const [sidebarOpened, setSidebarOpened] = useState(false); const handlePusherClick = () => { setSidebarOpened(!sidebarOpened) } const handleToggle = () => setSidebarOpened(!sidebarOpened) const { children } = props return ( Home Systems About
{process.env.REACT_APP_SITE_NAME}
{children}
) } /* MobileContainer.propTypes = { children: PropTypes.node, } */ const ResponsiveContainer = ({ children }) => (
{children} {children}
) const BetterSiteStatsChart = ({siteStats}) => { //const props = {data}; const background = '#9f0000'; //'#3b6978'; const background2 = '#9f0000'; //'#204051'; const accentColor = '#edffea'; const accentColorDark = '#ff7543'; //#75daad'; const tooltipStyles = { ...defaultStyles, background, border: '1px solid white', color: 'white', }; const formatDate = timeFormat("%b %d, %H:%MM"); // accessors const getDate = (d) => { return d.x; } const getCallActivity = (d) => { return parseInt(d.y); } const { tooltipData, tooltipLeft, tooltipTop, tooltipOpen, showTooltip, hideTooltip, } = useTooltip(); const width = 500; const height = 250; const { containerRef, TooltipInPortal } = useTooltipInPortal({ // use TooltipWithBounds detectBounds: true, // when tooltip containers are scrolled, this will correctly update the Tooltip position scroll: true, }) const bisectDate = bisector((d) => d.x).left; const getMouseData = (event) => { const coords = localPoint(event.target.ownerSVGElement, event); const { x } = coords; const x0 = dateScale.invert(x); const index = bisectDate(calls, x0, 1); const d0 = calls[index - 1]; const d1 = calls[index]; let d = d0; if (d1 && getDate(d1)) { d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0; } return { data: d, coords } } const handleMouseOver = (event, datum) => { const { coords, data } = getMouseData(event); showTooltip({ tooltipLeft: coords.x, tooltipTop: callActivityScale(getCallActivity(data)), tooltipData: data }); }; // bounds const margin = { top: 10, right: 0, bottom: 0, left: 25 }; const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; const calls = useMemo( () => { let callTotals = []; const now = new Date(); var MS_PER_MINUTE = 60000; for (let j = 0; j < siteStats.length; j++) { let spotsBack = siteStats.length - j; let time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); callTotals.push({ y: siteStats[j], x: time }); } callTotals.sort((a,b) => a.x-b.x); return callTotals }, [siteStats] ); const dateScale = useMemo( () => scaleTime({ range: [margin.left, innerWidth + margin.left], domain: extent(calls, getDate), }), [innerWidth, margin.left, calls], ); const callActivityScale = useMemo( () => { return scaleLinear({ range: [innerHeight + margin.top, margin.top], domain: [0, (max(calls, getCallActivity) || 0)], // + innerHeight / 3], nice: true, }) }, [margin.top, innerHeight, calls], ); return ( // Set `ref={containerRef}` on the element corresponding to the coordinate system that // `left/top` (passed to `TooltipInPortal`) are relative to.
dateScale(getDate(d)) ?? 0} y={(d) => callActivityScale(getCallActivity(d)) ?? 0} yScale={callActivityScale} strokeWidth={1} stroke="url(#area-gradient)" fill="url(#area-gradient)" curve={curveMonotoneX} /> {tooltipData && ( )} {tooltipOpen && (
{`${getCallActivity(tooltipData)}`} {formatDate(getDate(tooltipData))}
)}
) }; const SiteStatsContainer = () => { const { data: siteStats, isSuccess: siteStatsSuccess } = useGetSiteStatsQuery(); if (!siteStatsSuccess) { return Loading Site Stats; } return (

Site Activity

Calls per Minute
); }; // ---------------------------------------------------- const Main = (props) => { const [visible, setVisible] = useState(true); const [currentSystem, setCurrentSystem] = useState(0); const navigate = useNavigate(); const { data: systems, isSuccess } = useGetSystemsQuery(); //= selectAllSystems(); const { data: siteStats, isSuccess: siteStatsSuccess } = useGetSiteStatsQuery(); function useInterval(callback, delay) { const savedCallback = useRef(); // Remember the latest callback. useEffect(() => { savedCallback.current = callback; }, [callback]); // Set up the interval. useEffect(() => { function tick() { savedCallback.current(); } if (delay !== null) { let id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]); } const advanceSystem = () => { if (isSuccess) { let nextSystem = currentSystem + 1; if (nextSystem > systems.systems.length) { nextSystem = 0; } setCurrentSystem(nextSystem); setVisible(!visible); } //this.setState({ visible: !this.state.visible, currentSystem }); } useInterval(() => { advanceSystem() }, 3000) //https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux //https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter let system = false; if (isSuccess) { system = systems.systems[currentSystem]; } return ( <>
{system && ( navigate("/system/" + system.shortName)} /> )} Donate } />
{siteStats?.activeSystems || 0} Active Systems {siteStats?.totalClients || 0} People Listening
All The Calls

Every call, on every talkgroup is recorded

Go back in time

Missed a call? Not a problem, everything is archived!

Don't just scan, hear it all!

Most large cities use trunked radio systems to get the most use out of their assigned radio spectrum. With trunked systems, the transmission are constantly hopping to different frequencies. Using a cheap Software Defined Radio (SDR), it is possible capture all of the transmission on a system. Instead of scanning to a single frequency, SDR capturea wide swathes of spectrum, covering all of the frequencies a system could use.

Get Involved
Want to record your local radio system?

Join us! Record a local radio system and share it with the world. If you have a spare computer, it is easy to get started. With a $25 SDR or two, you can capture an entire system. It does take a little bit of work to get things setup, but we are here to help and answer questions.

The End
); } export default Main;