initial commit

This commit is contained in:
Luke Berndt
2024-02-02 22:09:37 -05:00
parent 75cb239a6c
commit 85864f4b84
13 changed files with 421 additions and 289 deletions

View File

@@ -14,13 +14,16 @@ import {
Sticky,
Menu,
Icon,
Sidebar
Sidebar,
Message,
MessageHeader
} from "semantic-ui-react";
import "./CallPlayer.css";
import queryString from '../query-string';
import io from 'socket.io-client';
import { useCallLink } from "./components/CallLinks";
import "./CallPlayer.css";
import { set } from "date-fns";
const socket = io(process.env.REACT_APP_BACKEND_SERVER);
@@ -30,6 +33,7 @@ const socket = io(process.env.REACT_APP_BACKEND_SERVER);
function CallPlayer(props) {
const { shortName } = useParams();
const system = props.system;
const selectCallId = props.selectCallId;
const callsData = props.callsData;
const handleNewer = props.handleNewer;
@@ -45,6 +49,7 @@ function CallPlayer(props) {
const backgroundAutoplay = useSelector((state) => state.callPlayer.backgroundAutoplay);
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName);
const [statusVisible, setStatusVisible] = useState(false);
const [autoPlay, setAutoPlay] = useState(true);
const [currentCall, setCurrentCall] = useState(false);
const [isPlaying, setIsPlaying] = useState(false);
@@ -160,15 +165,31 @@ useEffect(() => {
}
}, [selectCallId])
useEffect(() => {
if (system.status && system.status.length > 0) {
setStatusVisible(true);
}
}, [system])
const handleStatusDismiss = () => {
setStatusVisible(false);
}
return (
<div ref={positionRef}>
<Container className="main" >
{statusVisible &&
<Sticky offset={60} context={positionRef}>
<Message floating onDismiss={handleStatusDismiss} warning> <MessageHeader>System Status</MessageHeader>{system.status}</Message>
</Sticky>
}
<Sidebar.Pushable>
<Sidebar.Pusher
style={{ minHeight: '100vh' }}
>
<div ref={loadNewerRef} />
<ListCalls callsData={callsData} activeCallId={currentCallId} talkgroups={talkgroupsData ? talkgroupsData.talkgroups : false} playCall={playCall} />
<div ref={loadOlderRef} style={{ height: 50 }} />
</Sidebar.Pusher>

View File

@@ -14,7 +14,9 @@ import {
Label,
Menu,
Icon,
Sidebar
Sidebar,
Message,
Sticky
} from "semantic-ui-react";
import "./CallPlayer.css";
import queryString from '../query-string';
@@ -447,9 +449,10 @@ function Calls(props) {
</Menu.Item>
</Menu.Menu>
</Menu>
{activityVisible
? <Activity navigate={handleActivityNavigate} />
: <CallPlayer callsData={callsData} selectCallId={selectCallId} initialCallId={initialCallId} handleNewer={handleNewer} handleOlder={handleOlder} />
: <CallPlayer system={system} callsData={callsData} selectCallId={selectCallId} initialCallId={initialCallId} handleNewer={handleNewer} handleOlder={handleOlder} />
}
</div>
);

View File

@@ -5,6 +5,8 @@ import "./Main.css";
import { createMedia } from "@artsy/fresnel";
import {
Button,
ButtonGroup,
ButtonContent,
Container,
Divider,
Grid,
@@ -40,7 +42,7 @@ const { Media } = AppMedia;
* such things.
*/
const HomepageHeading = ({ mobile }) => (
<Container text style={{paddingBottom: '0px'}} >
<Container text style={{ paddingBottom: '0px' }} >
<Header
as='h1'
content='Share the Air'
@@ -83,52 +85,53 @@ 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 DesktopContainer = (props) => {
const [fixed, setFixed] = useState(false);
const hideFixedMenu = () => setFixed(false)
const hideFixedMenu = () => setFixed(false)
const showFixedMenu = () => setFixed(true)
const { children } = props
return (
<Media greaterThanOrEqual="tablet">
<div className="relative">
const { children } = props
return (
<Media greaterThanOrEqual="tablet">
<div className="relative">
<div className="static-gradient blue absolute z-0" >
<div className="static-gradient-bg absolute z-5"></div>
</div>
<Segment
textAlign='center'
style={{ padding: '1em 0em',
<Segment
textAlign='center'
style={{
padding: '1em 0em',
borderBottom: '0px',
boxShadow: 'none',
height: '400px'
}}
vertical
}}
vertical
>
<Menu
fixed={fixed ? 'top' : null}
inverted={true}
pointing={false}
secondary={true}
size='huge'
style={{ marginRight: '0px' }}
>
<Container>
<Menu.Item ><Header as='h3' inverted>{process.env.REACT_APP_SITE_NAME}</Header></Menu.Item>
<Link to="/systems"><Menu.Item link >Listen</Menu.Item></Link>
<Link to="/events"><Menu.Item link >Events</Menu.Item></Link>
<Link to="/about"><Menu.Item link >About</Menu.Item></Link>
</Container>
</Menu>
<HomepageHeading />
</Segment>
</div>
<Menu
fixed={fixed ? 'top' : null}
inverted={true}
pointing={false}
secondary={true}
size='huge'
style={{ marginRight: '0px'}}
>
<Container>
<Menu.Item ><Header as='h3' inverted>{process.env.REACT_APP_SITE_NAME}</Header></Menu.Item>
<Link to="/systems"><Menu.Item link >Listen</Menu.Item></Link>
<Link to="/events"><Menu.Item link >Events</Menu.Item></Link>
<Link to="/about"><Menu.Item link >About</Menu.Item></Link>
</Container>
</Menu>
<HomepageHeading />
</Segment>
</div>
{children}
</Media>
)
}
{children}
</Media>
)
}
/*
DesktopContainer.propTypes = {
@@ -146,55 +149,57 @@ const MobileContainer = (props) => {
const handleToggle = () => setSidebarOpened(!sidebarOpened)
const { children } = props
const { children } = props
return (
<Media lessThan="tablet">
<Sidebar.Pushable>
<Sidebar as={Menu} animation='uncover' inverted vertical visible={sidebarOpened} id="menu-bar">
<Menu.Item active>
Home
</Menu.Item>
<Menu.Item ><Link to="/systems">Systems</Link></Menu.Item>
<Menu.Item ><Link to="/about">About</Link></Menu.Item>
</Sidebar>
return (
<Media lessThan="tablet">
<Sidebar.Pushable>
<Sidebar as={Menu} animation='uncover' inverted vertical visible={sidebarOpened} id="menu-bar">
<Menu.Item active>
Home
</Menu.Item>
<Menu.Item ><Link to="/systems">Systems</Link></Menu.Item>
<Menu.Item ><Link to="/about">About</Link></Menu.Item>
</Sidebar>
<Sidebar.Pusher
dimmed={sidebarOpened}
onClick={handlePusherClick}
style={{ minHeight: '100vh' }}
>
<Sidebar.Pusher
dimmed={sidebarOpened}
onClick={handlePusherClick}
style={{ minHeight: '100vh' }}
>
<div className='relative'>
<div className="static-gradient blue absolute z-0">
<div className="static-gradient-bg absolute"></div>
</div>
<div className="static-gradient blue absolute z-0">
<div className="static-gradient-bg absolute"></div>
</div>
<Segment
textAlign='center'
style={{ minHeight: 350, padding: '1em 0em',
borderBottom: '0px',
boxShadow: 'none',
height: '300px'}}
style={{
minHeight: 350, padding: '1em 0em',
borderBottom: '0px',
boxShadow: 'none',
height: '300px'
}}
vertical
>
<Container>
<Container>
<Menu inverted secondary size='large'>
<Menu.Item onClick={handleToggle}>
<Icon name='sidebar' />
</Menu.Item>
<Menu.Item header>{process.env.REACT_APP_SITE_NAME}</Menu.Item>
</Menu>
</Container>
<Menu inverted secondary size='large'>
<Menu.Item onClick={handleToggle}>
<Icon name='sidebar' />
</Menu.Item>
<Menu.Item header>{process.env.REACT_APP_SITE_NAME}</Menu.Item>
</Menu>
</Container>
<HomepageHeading mobile />
</Segment>
</div>
{children}
</Sidebar.Pusher>
</Sidebar.Pushable>
</Media>
)
}
{children}
</Sidebar.Pusher>
</Sidebar.Pushable>
</Media>
)
}
/*
MobileContainer.propTypes = {
children: PropTypes.node,
@@ -213,16 +218,16 @@ const Main = (props) => {
const [visible, setVisible] = useState(true);
const [currentSystem, setCurrentSystem] = useState(0);
const navigate = useNavigate();
const {data:systems, isSuccess} = useGetSystemsQuery(); //= selectAllSystems();
const { data: systems, isSuccess } = useGetSystemsQuery(); //= selectAllSystems();
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
@@ -235,133 +240,157 @@ const Main = (props) => {
}, [delay]);
}
const advanceSystem = () => {
if (isSuccess) {
let nextSystem = currentSystem + 1;
if (nextSystem > systems.systems.length) {
nextSystem = 0;
}
setCurrentSystem(nextSystem);
setVisible(!visible);
const advanceSystem = () => {
if (isSuccess) {
let nextSystem = currentSystem + 1;
if (nextSystem > systems.systems.length) {
nextSystem = 0;
}
//this.setState({ visible: !this.state.visible, currentSystem });
setCurrentSystem(nextSystem);
setVisible(!visible);
}
useInterval( ()=>{ advanceSystem()}, 3000)
//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
//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];
system = systems.systems[currentSystem];
}
return (
<>
<style>{mediaStyles}</style>
<ResponsiveContainer>
<div style={{ top: '-120px', position: 'relative' }}>
<Segment style={{ padding: ' 0em', height: '350px', backgroudColor: '#FFF'}} vertical>
<Grid columns='equal' stackable textAlign='center' style={{ height: '350px', marginRight: '0px' }}>
<Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '4em', paddingTop: '2em', maxWidth: 450 }}>
{ system && (<Transition visible={visible} animation='pulse' duration={500}>
<>
<style>{mediaStyles}</style>
<ResponsiveContainer>
<div style={{ top: '-120px', position: 'relative' }}>
<Segment style={{ padding: ' 0em', height: '350px', backgroudColor: '#FFF' }} vertical basic>
<Grid columns='equal' stackable textAlign='center' style={{ height: '350px', marginRight: '0px' }}>
<Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '4em', paddingTop: '2em', maxWidth: 450 }}>
{system && (<Transition visible={visible} animation='pulse' duration={500}>
<SystemCard keepShort={true} system={system} key={system.shortName} onClick={(e) => navigate("/system/" + system.shortName)}/>
<SystemCard keepShort={true} system={system} key={system.shortName} onClick={(e) => navigate("/system/" + system.shortName)} />
</Transition>)}
</Grid.Column>
<Grid.Column style={{ paddingBottom: '0em', paddingTop: '6em', maxWidth: 450 }}>
<Link to="/systems">
<Button primary size='huge' id="listen" animated onClick={(e) => navigate("/system/" + system.shortName)}>
<Button.Content visible><Icon name='headphones' />
<Icon name='right arrow' /></Button.Content>
<Button.Content hidden>Listen</Button.Content>
</Button>
</Link>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Segment style={{ padding: '0em' }} vertical>
<Grid celled='internally' columns='equal' stackable>
<Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '5em', paddingTop: '5em' }}>
<Header as='h3' style={{ fontSize: '2em' }}>
<Icon color='orange' name='list' />All The Calls
</Header>
<p style={{ fontSize: '1.33em' }}>Every call, on every talkgroup is recorded</p>
</Grid.Column>
<Grid.Column style={{ paddingBottom: '5em', paddingTop: '5em' }}>
<Header as='h3' style={{ fontSize: '2em' }}>
<Icon color='orange' name='checked calendar' />
Go back in time
</Header>
<p style={{ fontSize: '1.33em' }}>
Missed a call? Not a problem, everything is archived!
</p>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Segment style={{ padding: '8em 0em' }} vertical>
<Container text>
<Header as='h3' style={{ fontSize: '2em' }}>
Don't just scan, hear it all!
</Header>
<p style={{ fontSize: '1.33em' }}>
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.
</p>
<Divider
as='h4'
className='header'
horizontal
style={{ margin: '3em 0em', textTransform: 'uppercase' }}
>
Get Involved
</Divider>
<Header as='h3' style={{ fontSize: '2em' }}>
Want to record your local radio system?
</Header>
<p style={{ fontSize: '1.33em' }}>
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.
</p>
<a href="https://github.com/robotastic/trunk-recorder/blob/master/README.md">
<Button size='large'>
Learn More
</Button>
</a>
</Container>
</Segment>
</div>
<Segment inverted vertical style={{ padding: '5em 0em' }} id="footer">
<Container>
<Grid divided inverted stackable>
<Grid.Row>
<Grid.Column width={6}>
</Grid.Column>
<Grid.Column width={4}>
<Header as='h4' inverted>
The End <Icon name='rocket' />
</Header>
</Grid.Column>
<Grid.Column width={6}>
</Transition>)}
</Grid.Column>
<Grid.Column style={{ paddingBottom: '0em', paddingTop: '6em', maxWidth: 450 }}>
<Link to="/systems">
<Button primary size='huge' id="listen" animated onClick={(e) => navigate("/system/" + system.shortName)}>
<Button.Content visible><Icon name='headphones' />
<Icon name='right arrow' /></Button.Content>
<Button.Content hidden>Listen</Button.Content>
</Button>
</Link>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Segment style={{ padding: '0em' }} vertical>
<Grid columns='equal' stackable textAlign='center' style={{ height: '150px', marginRight: '0px' }}>
<Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '4em', paddingTop: '2em', maxWidth: 450 }}>
<ButtonGroup size='large'>
<Button color='red' animated='fade' href="https://patreon.com/OpenMHz">
<ButtonContent visible>
<Icon name='heart' /> Donate
</ButtonContent>
<ButtonContent hidden>Thank You</ButtonContent>
</Button>
<Button href="https://podcasts.apple.com/us/podcast/openmhz/id1675187518">
<Icon name='podcast' /> Events Podcast
</Button>
<Button href="https://apps.apple.com/us/app/openmhz/id6466666994">
<Icon name='app store ios' /> iOS App
</Button>
</ButtonGroup>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
</Segment>
</ResponsiveContainer>
</>);
<Segment style={{ padding: '0em' }} vertical>
<Grid celled='internally' columns='equal' stackable>
<Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '5em', paddingTop: '5em' }}>
<Header as='h3' style={{ fontSize: '2em' }}>
<Icon color='orange' name='list' />All The Calls
</Header>
<p style={{ fontSize: '1.33em' }}>Every call, on every talkgroup is recorded</p>
</Grid.Column>
<Grid.Column style={{ paddingBottom: '5em', paddingTop: '5em' }}>
<Header as='h3' style={{ fontSize: '2em' }}>
<Icon color='orange' name='checked calendar' />
Go back in time
</Header>
<p style={{ fontSize: '1.33em' }}>
Missed a call? Not a problem, everything is archived!
</p>
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
<Segment style={{ padding: '8em 0em' }} vertical>
<Container text>
<Header as='h3' style={{ fontSize: '2em' }}>
Don't just scan, hear it all!
</Header>
<p style={{ fontSize: '1.33em' }}>
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.
</p>
<Divider
as='h4'
className='header'
horizontal
style={{ margin: '3em 0em', textTransform: 'uppercase' }}
>
Get Involved
</Divider>
<Header as='h3' style={{ fontSize: '2em' }}>
Want to record your local radio system?
</Header>
<p style={{ fontSize: '1.33em' }}>
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.
</p>
<a href="https://github.com/robotastic/trunk-recorder/blob/master/README.md">
<Button size='large'>
Learn More
</Button>
</a>
</Container>
</Segment>
</div>
<Segment inverted vertical style={{ padding: '5em 0em' }} id="footer">
<Container>
<Grid divided inverted stackable>
<Grid.Row>
<Grid.Column width={6}>
</Grid.Column>
<Grid.Column width={4}>
<Header as='h4' inverted>
The End <Icon name='rocket' />
</Header>
</Grid.Column>
<Grid.Column width={6}>
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
</Segment>
</ResponsiveContainer>
</>);
}

View File

@@ -3,114 +3,143 @@ import {
Card,
Icon,
Label,
Grid
Grid,
Segment,
Popup,
Button
} from "semantic-ui-react";
function SystemCard (props) {
function SystemCard(props) {
const { system, keepShort, onClick, onContactClick } = props;
var location = "";
var clientCount = "";
var callAvg = "";
let description = system.description;
let statusMessage = "";
const {system, keepShort, onClick, onContactClick} = props;
var location = "";
var clientCount = "";
var callAvg = "";
let description = system.description;
const handleContactClick = (event) => {
event.stopPropagation();
onContactClick(system);
if (system.status && system.status.length > 0) {
statusMessage = system.status
if (statusMessage.length > 25) {
statusMessage = statusMessage.substring(0, 25) + "...";
}
}
const onCardClick = (event) => {
onClick();
}
if (keepShort && (description.length > 100)) {
description = description.substring(0, 96) + "...";
}
if (system.callAvg) {
callAvg = Math.round(system.callAvg * 10) / 10 + "/min"
}
if (system.clientCount === 1) {
clientCount = "1 listener";
} else if (system.clientCount > 1) {
clientCount = system.clientCount + " listeners"
}
let status = (<></>)
if (system.active) {
status = (
<Grid >
const handleContactClick = (event) => {
event.stopPropagation();
onContactClick(system);
}
const onCardClick = (event) => {
onClick();
}
if (keepShort && (description.length > 100)) {
description = description.substring(0, 96) + "...";
}
if (keepShort) {
statusMessage = "";
}
if (system.callAvg) {
callAvg = Math.round(system.callAvg * 10) / 10 + "/min"
}
if (system.clientCount === 1) {
clientCount = "1 listener";
} else if (system.clientCount > 1) {
clientCount = system.clientCount + " listeners"
}
let status = (<></>)
if (system.active) {
status = (
<Grid >
<Grid.Row>
<Grid.Column width="8">
{clientCount && <div><Icon name='headphones' />{clientCount}</div>}
</Grid.Column>
<Grid.Column width="8" textAlign='right'>
{callAvg && <div><Icon name='file audio outline' />{callAvg}</div>}
</Grid.Column>
</Grid.Row>
</Grid>
)
} else {
const lastActive = new Date(system.lastActive);
status = (
<Grid >
<Grid.Row>
<Grid.Column>
<Icon name="calendar times outline"/> Last Active {lastActive.toLocaleDateString()}
</Grid.Column>
</Grid.Row>
</Grid>
)
}
</Grid>
)
} else {
const lastActive = new Date(system.lastActive);
status = (
<Grid >
<Grid.Row>
<Grid.Column>
<Icon name="calendar times outline" /> Last Active {lastActive.toLocaleDateString()}
</Grid.Column>
</Grid.Row>
</Grid>
if (system) {
switch (system.systemType) {
case "state":
location = system.state;
break;
case "city":
location = system.city + ", " + system.state;
break;
case "county":
location = system.county + ", " + system.state;
break;
case "international":
location = system.country;
break;
default:
location = system.city + ", " + system.state;
}
)
}
if (system) {
switch (system.systemType) {
case "state":
location = system.state;
break;
case "city":
location = system.city + ", " + system.state;
break;
case "county":
location = system.county + ", " + system.state;
break;
case "international":
location = system.country;
break;
default:
location = system.city + ", " + system.state;
}
return (
<Card link onClick={onCardClick} raised={true} centered={true} >
<Card.Content>
<Card.Header >{system.name}</Card.Header>
<Card.Meta>{location} </Card.Meta>
<Card.Description>
<Grid >
}
return (
<Card link onClick={onCardClick} raised={true} centered={true} >
<Card.Content>
<Card.Header >{system.name}</Card.Header>
<Card.Meta>{location} </Card.Meta>
<Card.Description>
<Grid >
<Grid.Row>
<Grid.Column >
<Grid.Column >
{system.screenName && <Label size="mini"><Icon name='user' /> {system.screenName}</Label>}
{system.allowContact && <Label size="mini" onClick={handleContactClick}><Icon name='mail' />Message</Label>}
</Grid.Column>
</Grid.Row>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column>
{description}
</Grid.Column>
</Grid.Row>
{statusMessage.length &&
<Grid.Row>
<Grid.Column>
{description}
<Popup content={system.status} trigger={<Button icon labelPosition='left' size='mini'><Icon name="info"/>{statusMessage}</Button>} />
</Grid.Column>
</Grid.Row>
</Grid>
</Card.Description>
</Card.Content>
<Card.Content extra>
{status}
</Card.Content>
</Card>
);
}
</Grid>
</Card.Description>
</Card.Content>
<Card.Content extra>
{status}
</Card.Content>
</Card>
);
}
export default SystemCard;