fixed up the other windows

This commit is contained in:
Luke Berndt
2023-01-16 21:16:13 -05:00
parent 14906865a1
commit 6f50a7b9c8
11 changed files with 141 additions and 295 deletions

View File

@@ -3,45 +3,42 @@ import { Link } from 'react-router-dom'
import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react"; import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react";
class AboutComponent extends Component { const AboutComponent = () => {
return (
<div>
<Menu fixed="top">
<Link to="/"><Menu.Item link><Icon name='arrow left' /> Home</Menu.Item></Link>
<Link to="/systems"><Menu.Item link>Listen</Menu.Item></Link>
</Menu>
<Container text>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h1">About<Icon name='compass' /></Header></Divider>
<p>I wrote <a href="https://github.com/robotastic/trunk-recorder">Trunk Recorder</a> because I was curious
about what my local fire station was up to and I put together the original version of OpenMHz because I
figured other people might want to listen to the recordings too.</p>
<p>The latest version of this site makes it easy for other people running Trunk Recorder to share their recrodings.
I am hoping that making it easier to listen to what our local fire, police and EMS have to go through everyday will
lead to greater appreciation for all the work they do that goes largely unseen. I am personally amazed by the number
of medical runs the DC's Fire and EMS department has to cover every day, and the shocking number of them that are ODs.</p>
<p>Each system should have a 30 day archive. This is to help make sure things don't get overloaded and make sure I don't get a crazy bill
at the end of the month.</p>
<p> - Luke <a href="mailto:luke@robotastic.com?Subject=OpenMHz" target="_top">luke@robotastic.com</a> </p>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Open Source</Header></Divider>
<p>Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:</p>
<p><a href="https://github.com/openmhz/trunk-server"><Icon name='github' />Trunk Server</a></p>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Support</Header></Divider>
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
<p><a href="https://github.com/sponsors/robotastic"><Button>
Support
</Button></a></p>
</Container>
</div>
);
}
render() { export default AboutComponent;
return (
<div>
<Menu fixed="top">
<Link to="/"><Menu.Item link><Icon name='arrow left' /> Home</Menu.Item></Link>
<Link to="/systems"><Menu.Item link>Listen</Menu.Item></Link>
</Menu>
<Container text>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h1">About<Icon name='compass' /></Header></Divider>
<p>I wrote <a href="https://github.com/robotastic/trunk-recorder">Trunk Recorder</a> because I was curious
about what my local fire station was up to and I put together the original version of OpenMHz because I
figured other people might want to listen to the recordings too.</p>
<p>The latest version of this site makes it easy for other people running Trunk Recorder to share their recrodings.
I am hoping that making it easier to listen to what our local fire, police and EMS have to go through everyday will
lead to greater appreciation for all the work they do that goes largely unseen. I am personally amazed by the number
of medical runs the DC's Fire and EMS department has to cover every day, and the shocking number of them that are ODs.</p>
<p>Each system should have a 30 day archive. This is to help make sure things don't get overloaded and make sure I don't get a crazy bill
at the end of the month.</p>
<p> - Luke <a href="mailto:luke@robotastic.com?Subject=OpenMHz" target="_top">luke@robotastic.com</a> </p>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Open Source</Header></Divider>
<p>Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:</p>
<p><a href="https://github.com/openmhz/trunk-server"><Icon name='github' />Trunk Server</a></p>
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Support</Header></Divider>
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
<p><a href="https://github.com/sponsors/robotastic"><Button>
Support
</Button></a></p>
</Container>
</div>
);
}
}
export default AboutComponent;

View File

@@ -1,28 +0,0 @@
import { connect } from "react-redux"
import * as callActionCreators from "./call-actions"
import { bindActionCreators } from 'redux'
import CalendarModal from "./CalendarModal"
function mapStateToProps(state, props) {
return {
groups: state.group.items[props.shortName],
talkgroups: state.talkgroup.items[props.shortName],
talkgroupsIsWaiting: state.talkgroup.isWaiting,
groupsIsWaiting: state.group.isWaiting,
shortName: props.shortName,
activeTab: state.call.filterType,
selectedTalkgroups: state.call.filterTalkgroups
}
}
function mapDispatchToProps(dispatch) {
return {
callActions: bindActionCreators(callActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CalendarModal)

View File

@@ -5,7 +5,7 @@ import MediaPlayer from "./MediaPlayer";
import FilterModal from "./FilterModal"; import FilterModal from "./FilterModal";
import GroupModal from "./GroupModal"; import GroupModal from "./GroupModal";
import SupportModal from "./SupportModal"; import SupportModal from "./SupportModal";
import CalendarModal from "./CalendarModalContainer"; import CalendarModal from "./CalendarModal";
import CallInfo from "./CallInfo"; import CallInfo from "./CallInfo";
import ListCalls from "./ListCalls"; import ListCalls from "./ListCalls";
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
@@ -50,9 +50,7 @@ function CallPlayer(props) {
const [autoPlay, setAutoPlay] = useState(true); const [autoPlay, setAutoPlay] = useState(true);
const [addCallScroll, setAddCallScroll] = useState(false);
const [currentCall, setCurrentCall] = useState(false); const [currentCall, setCurrentCall] = useState(false);
const [prevScrollHeight, setPrevScrollHeight] = useState(0);
const [urlOptions, setUrlOptions] = useState(false); const [urlOptions, setUrlOptions] = useState(false);
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [sidebarOpened, setSidebarOpened] = useState(false); const [sidebarOpened, setSidebarOpened] = useState(false);
@@ -155,13 +153,10 @@ function CallPlayer(props) {
case 'calls': case 'calls':
const ref = positionRef.current; const ref = positionRef.current;
pageYOffset.current = positionRef.current.clientHeight; pageYOffset.current = positionRef.current.clientHeight;
setPrevScrollHeight(positionRef.current.clientHeight);
console.log("Prior to setAddCallScroll, addCallScroll was: " + addCallScroll);
setAddCallScroll(true);
if (shouldPlayAddCallRef.current) { if (shouldPlayAddCallRef.current) {
setCurrentCall(message); setCurrentCall(message);
} }
console.log("Prior to addCall, addCallScroll was: " + addCallScroll);
dispatch(addCall(message)); dispatch(addCall(message));
console.log("Got: " + message._id); console.log("Got: " + message._id);
@@ -349,7 +344,7 @@ function CallPlayer(props) {
useLayoutEffect( () => { useLayoutEffect( () => {
const scrollAmount = parseInt(positionRef.current.clientHeight) - parseInt(pageYOffset.current); const scrollAmount = parseInt(positionRef.current.clientHeight) - parseInt(pageYOffset.current);
if (scrollAmount > 0) { if (scrollAmount > 0) {
console.log("useLayoutEffect for callsData - state prevScrollHeight: " + prevScrollHeight + " ref: " + pageYOffset.current + " current: " + positionRef.current.clientHeight + " Scroll Amount: " + scrollAmount) console.log("useLayoutEffect for callsData - ref: " + pageYOffset.current + " current: " + positionRef.current.clientHeight + " Scroll Amount: " + scrollAmount)
window.scrollBy(0, scrollAmount); window.scrollBy(0, scrollAmount);
} }
}, [callsData]) }, [callsData])

View File

@@ -1,69 +0,0 @@
import { connect } from "react-redux"
import * as callActionCreators from "./call-actions"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import * as groupActionCreators from "../Group/group-actions"
import * as systemActionCreators from "../System/system-actions"
import { bindActionCreators } from 'redux'
import CallPlayer from "./CallPlayer"
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
// glorious solution here: https://stackoverflow.com/questions/71097375/how-can-i-use-withrouter-in-react-router-v6-for-passing-props
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
function mapStateToProps(state, props) {
const shortName = props.router.params.shortName;
return {
callsById: state.call.byId,
callsAllIds: state.call.allIds,
oldestCallTime: state.call.oldestCallTime,
newestCallTime: state.call.newestCallTime,
live: state.call.live,
filterDate: state.call.filterDate,
filterType: state.call.filterType,
filterTalkgroups: state.call.filterTalkgroups,
filterGroupId: state.call.filterGroupId,
filterStarred: state.call.filterStarred,
system: state.system.items.find((item) => item.shortName === shortName),
groups: state.group.items[shortName],
talkgroups: state.talkgroup.items[shortName],
callsIsWaiting: state.call.isWaiting,
systemIsWaiting: state.system.isWaiting,
talkgroupsIsWaiting: state.talkgroup.isWaiting,
groupsIsWaiting: state.group.isWaiting,
location: props.router.location,
shortName: shortName
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators, dispatch),
systemActions: bindActionCreators(systemActionCreators, dispatch),
callActions: bindActionCreators(callActionCreators, dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch)
}
}
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(CallPlayer))

View File

@@ -1,34 +0,0 @@
import { connect } from "react-redux"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import * as groupActionCreators from "../Group/group-actions"
import * as callActionCreators from "./call-actions"
import { bindActionCreators } from 'redux'
import FilterModal from "./FilterModal"
function mapStateToProps(state, props) {
return {
groups: state.group.items[props.shortName],
talkgroups: state.talkgroup.items[props.shortName],
talkgroupsIsWaiting: state.talkgroup.isWaiting,
groupsIsWaiting: state.group.isWaiting,
shortName: props.shortName,
activeTab: state.call.filterType,
selectedTalkgroups: state.call.filterTalkgroups,
selectedGroup: state.call.filterGroupId,
filterStarred: state.call.filterStarred
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators, dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch),
callActions: bindActionCreators(callActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterModal)

View File

@@ -1,27 +0,0 @@
import { connect } from "react-redux"
import * as groupActionCreators from "../Group/group-actions"
import * as callActionCreators from "./call-actions"
import { bindActionCreators } from 'redux'
import GroupModal from "./GroupModal"
function mapStateToProps(state, props) {
return {
groups: state.group.items[props.shortName],
groupsIsWaiting: state.group.isWaiting,
shortName: props.shortName,
selectedGroup: state.call.filterGroupId
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators, dispatch),
callActions: bindActionCreators(callActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(GroupModal)

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react"; import React, { Component, useState, useEffect, useRef } from "react";
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import SystemCard from "../System/SystemCard"; import SystemCard from "../System/SystemCard";
import "./Main.css"; import "./Main.css";
@@ -17,6 +17,9 @@ import {
} from 'semantic-ui-react' } from 'semantic-ui-react'
import { useSelector, useDispatch } from 'react-redux'
import { selectAllSystems, selectActiveSystems, useGetSystemsQuery } 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 */ /* Responsive component was removed from Semantic UI. This is discussed here: https://github.com/Semantic-Org/Semantic-UI-React/pull/4008 */
const AppMedia = createMedia({ const AppMedia = createMedia({
@@ -80,15 +83,14 @@ General flow:
- componentDidMount(): it will call fetchSystem() this will perform an HTTP request if Systems does not exist, and then add System to Props - componentDidMount(): it will call fetchSystem() this will perform an HTTP request if Systems does not exist, and then add System to Props
*/ */
class DesktopContainer extends Component { const DesktopContainer = (props) => {
state = {} const [fixed, setFixed] = useState(false);
const hideFixedMenu = () => setFixed(false)
const showFixedMenu = () => setFixed(true)
hideFixedMenu = () => this.setState({ fixed: false })
showFixedMenu = () => this.setState({ fixed: true })
render() { const { children } = props
const { children } = this.props
const { fixed } = this.state
return ( return (
<Media greaterThanOrEqual="tablet"> <Media greaterThanOrEqual="tablet">
@@ -128,26 +130,24 @@ class DesktopContainer extends Component {
</Media> </Media>
) )
} }
}
/* /*
DesktopContainer.propTypes = { DesktopContainer.propTypes = {
children: PropTypes.node, children: PropTypes.node,
} }
*/ */
class MobileContainer extends Component { const MobileContainer = (props) => {
state = {}
handlePusherClick = () => { const [sidebarOpened, setSidebarOpened] = useState(false);
const { sidebarOpened } = this.state
if (sidebarOpened) this.setState({ sidebarOpened: false }) const handlePusherClick = () => {
setSidebarOpened(!sidebarOpened)
} }
handleToggle = () => this.setState({ sidebarOpened: !this.state.sidebarOpened }) const handleToggle = () => setSidebarOpened(!sidebarOpened)
render() {
const { children } = this.props const { children } = props
const { sidebarOpened } = this.state
return ( return (
<Media lessThan="tablet"> <Media lessThan="tablet">
@@ -162,7 +162,7 @@ class MobileContainer extends Component {
<Sidebar.Pusher <Sidebar.Pusher
dimmed={sidebarOpened} dimmed={sidebarOpened}
onClick={this.handlePusherClick} onClick={handlePusherClick}
style={{ minHeight: '100vh' }} style={{ minHeight: '100vh' }}
> >
<div className='relative'> <div className='relative'>
@@ -181,7 +181,7 @@ class MobileContainer extends Component {
<Container> <Container>
<Menu inverted secondary size='large'> <Menu inverted secondary size='large'>
<Menu.Item onClick={this.handleToggle}> <Menu.Item onClick={handleToggle}>
<Icon name='sidebar' /> <Icon name='sidebar' />
</Menu.Item> </Menu.Item>
<Menu.Item header>{process.env.REACT_APP_SITE_NAME}</Menu.Item> <Menu.Item header>{process.env.REACT_APP_SITE_NAME}</Menu.Item>
@@ -196,7 +196,6 @@ class MobileContainer extends Component {
</Media> </Media>
) )
} }
}
/* /*
MobileContainer.propTypes = { MobileContainer.propTypes = {
children: PropTypes.node, children: PropTypes.node,
@@ -210,41 +209,62 @@ const ResponsiveContainer = ({ children }) => (
) )
// ---------------------------------------------------- // ----------------------------------------------------
class Main extends Component { const Main = (props) => {
constructor(props) {
super(props); const [visible, setVisible] = useState(true);
this.advanceSystem = this.advanceSystem.bind(this); const [duration, setDuration] = useState(500);
this.state = { duration: 500, visible: true, currentSystem: 0} const [currentSystem, setCurrentSystem] = useState(0);
const dispatch = useDispatch();
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() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
} }
const handleVisibility = () => setVisible(!visible);
const advanceSystem = () => {
handleChange = (e, { name, value }) => this.setState({ [name]: value }) if (isSuccess) {
let nextSystem = currentSystem + 1;
handleVisibility = () => this.setState({ visible: !this.state.visible }) if (nextSystem > systems.systems.length) {
nextSystem = 0;
advanceSystem() {
var currentSystem = this.state.currentSystem + 1;
if (currentSystem > this.props.system.items.length) {
currentSystem = 0;
} }
this.setState({ visible: !this.state.visible, currentSystem }); setCurrentSystem(nextSystem);
setVisible(!visible);
}
//this.setState({ visible: !this.state.visible, currentSystem });
} }
componentDidMount() { useInterval( ()=>{ advanceSystem()}, 3000)
this.props.fetchSystems();
this.interval = setInterval(() => this.advanceSystem(), 3000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux //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/42597602/react-onclick-pass-event-with-parameter
render() {
const system = this.props.system.items[this.state.currentSystem]; let system = false;
if (isSuccess) {
system = systems.systems[currentSystem];
}
return ( return (
<> <>
<style>{mediaStyles}</style> <style>{mediaStyles}</style>
@@ -254,9 +274,9 @@ render() {
<Grid columns='equal' stackable textAlign='center' style={{ height: '350px', marginRight: '0px' }}> <Grid columns='equal' stackable textAlign='center' style={{ height: '350px', marginRight: '0px' }}>
<Grid.Row textAlign='center'> <Grid.Row textAlign='center'>
<Grid.Column style={{ paddingBottom: '4em', paddingTop: '2em', maxWidth: 450 }}> <Grid.Column style={{ paddingBottom: '4em', paddingTop: '2em', maxWidth: 450 }}>
{ system && (<Transition visible={this.state.visible} animation='pulse' duration={500}> { system && (<Transition visible={visible} animation='pulse' duration={500}>
<SystemCard keepShort={true} system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/> <SystemCard keepShort={true} system={system} key={system.shortName} onClick={(e) => props.changeUrl("/system/" + system.shortName)}/>
</Transition>)} </Transition>)}
</Grid.Column> </Grid.Column>
@@ -351,6 +371,5 @@ render() {
</>); </>);
} }
}
export default Main; export default Main;

View File

@@ -1,16 +0,0 @@
import { connect } from "react-redux"
import * as systemActions from "../System/system-actions"
import Main from "./Main"
function mapStateToProps(state) {
return {
system: state.system
}
}
export default connect(
mapStateToProps,
systemActions
)(Main)

View File

@@ -10,22 +10,15 @@ import {
Divider Divider
} from "semantic-ui-react"; } from "semantic-ui-react";
import { selectAllSystems, selectActiveSystems, useGetSystemsQuery } from "../features/api/apiSlice";
// ---------------------------------------------------- // ----------------------------------------------------
class ListSystems extends Component {
state = { const ListSystems = (props) => {
requestMessage: "",
}
componentDidMount() {
this.props.fetchSystems();
}
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux const {data:systems, isSuccess} = useGetSystemsQuery();
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
render() {
const systems = this.props.system.items;
return ( return (
<div> <div>
<Menu fixed="top"> <Menu fixed="top">
@@ -35,7 +28,8 @@ class ListSystems extends Component {
<Container > <Container >
<Divider horizontal style={{paddingTop:"5em", paddingBottom:"2em"}}><Header as="h1">Radio Systems<Icon name='rss' /></Header></Divider> <Divider horizontal style={{paddingTop:"5em", paddingBottom:"2em"}}><Header as="h1">Radio Systems<Icon name='rss' /></Header></Divider>
<Card.Group itemsPerRow={4} stackable={true}> <Card.Group itemsPerRow={4} stackable={true}>
{systems.map((system) =>
{isSuccess&&systems.systems.map((system) =>
system.active&&<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/> system.active&&<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/>
// <SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/> // <SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/>
)} )}
@@ -44,6 +38,6 @@ class ListSystems extends Component {
</div> </div>
); );
} }
}
export default ListSystems; export default ListSystems;

View File

@@ -1,6 +1,6 @@
// Import the RTK Query methods from the React-specific entry point // Import the RTK Query methods from the React-specific entry point
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createSelector} from '@reduxjs/toolkit'
// Define our single API slice object // Define our single API slice object
@@ -29,5 +29,20 @@ export const apiSlice = createApi({
}) })
}) })
export const selectSystemsResult = apiSlice.endpoints.getSystems.select();
const emptySystems = []
export const selectAllSystems = createSelector(
selectSystemsResult,
systemsResult => systemsResult?.data ?? emptySystems
)
export const selectActiveSystems = createSelector(
selectAllSystems,
(state, userId) => userId,
(systems, userId) => systems.find(system => system.active === true)
)
// Export the auto-generated hook for the `getPosts` query endpoint // Export the auto-generated hook for the `getPosts` query endpoint
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery } = apiSlice

View File

@@ -11,10 +11,10 @@ import 'semantic-ui-css/semantic.min.css'
//import withTracker from './withTracker'; //import withTracker from './withTracker';
// Main // Main
import MainContainer from "./Main/MainContainer" import Main from "./Main/Main"
// System // System
import ListSystemsContainer from "./System/ListSystemsContainer" import ListSystems from "./System/ListSystems"
// Call // Call
import CallPlayer from "./Call/CallPlayer" import CallPlayer from "./Call/CallPlayer"
@@ -30,8 +30,8 @@ root.render(
<Provider store={store}> <Provider store={store}>
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
<Route exact path="/" element={<MainContainer/>} /> <Route exact path="/" element={<Main/>} />
<Route exact path="/systems" element={<ListSystemsContainer/>} /> <Route exact path="/systems" element={<ListSystems/>} />
<Route exact path="/system/:shortName" element={<CallPlayer/>} /> <Route exact path="/system/:shortName" element={<CallPlayer/>} />
<Route exact path="/about" element={<AboutComponent/>} /> <Route exact path="/about" element={<AboutComponent/>} />