diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 7d93a4b..a2fd3d0 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -1,5 +1,6 @@ -import React from "react"; -import { Link } from 'react-router-dom'; +import React, { useEffect, useState } from "react"; +import { Link, useLocation} from 'react-router-dom'; +import { useParams } from "react-router-dom"; import MediaPlayer from "./MediaPlayer"; import FilterModal from "./FilterModal"; import GroupModal from "./GroupModal"; @@ -7,6 +8,10 @@ import SupportModal from "./SupportModal"; import CalendarModal from "./CalendarModalContainer"; import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; +import { useSelector, useDispatch } from 'react-redux' +import { setLive } from "../features/callPlayer/callPlayerSlice"; +import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' +import { selectAllCalls, useGetCallsQuery } from "../features/calls/callsSlice"; import { Container, Label, @@ -20,114 +25,117 @@ import { import "./CallPlayer.css"; //import setupSocket from '../socket/SetupSocket.js' import queryString from '../query-string'; -import {Groups, Calls} from "./call-data" + +import { setShortName } from "./call-actions"; // ---------------------------------------------------- -class CallPlayer extends React.Component { +function CallPlayer (props) { - constructor(props) { - super(props); - this.loadNewerCalls = this.loadNewerCalls.bind(this); - this.loadOlderCalls = this.loadOlderCalls.bind(this); - this.playCall = this.playCall.bind(this); - this.updateUri = this.updateUri.bind(this); - this.switchAutoPlay = this.switchAutoPlay.bind(this); - this.callEnded = this.callEnded.bind(this); - this.addCall = this.addCall.bind(this); - this.handlePusherClick = this.handlePusherClick.bind(this); - this.handleSidebarToggle = this.handleSidebarToggle.bind(this); - this.handleFilterClose = this.handleFilterClose.bind(this); - this.handleGroupClose = this.handleGroupClose.bind(this); - this.handleCalendarToggle = this.handleCalendarToggle.bind(this); - this.handleCalendarClose = this.handleCalendarClose.bind(this); - this.handleLiveToggle = this.handleLiveToggle.bind(this); - this.handlePlayPause = this.handlePlayPause.bind(this); - this.getFilter = this.getFilter.bind(this); - this.socket = window.socket; - this.setupSocket = this.setupSocket.bind(this); - this.endSocket = this.endSocket.bind(this); - this.changeUrl = this.changeUrl.bind(this); - this.audioRef = React.createRef(); - this.currentCallRef = React.createRef(); - this.state = { - requestMessage: "", - callUrl: "", - autoPlay: true, - callId: false, - playTime: 0, - callScroll: false, - callSelect: false, - urlOptions: false, - isPlaying: false, - sidebarOpened: false, - filterVisible: false, - groupVisible: false, - calendarVisible: false - } + const { shortName } = useParams(); + const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(shortName); + const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); + const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); + //const allCalls = selectAllCalls(shortName) + const [requestMessage, setRequestMessage] = useState(""); + const [callUrl, setCallUrl] = useState(""); + const [autoPlay, setAutoPlay] = useState(true); + const [callId, setCallId] = useState(false); + const [playTime, setPlayTime] = useState(0); + const [callScroll, setCallScroll] = useState(false); + const [callSelect, setCallSelect] = useState(false); + const [urlOptions, setUrlOptions] = useState(false); + const [isPlaying, setIsPlaying] = useState(false); + const [sidebarOpened, setSidebarOpened] = useState(false); + const [filterVisible, setFilterVisible] = useState(false); + const [groupVisible, setGroupVisible] = useState(false); + const [calendarVisible, setCalendarVisible] = useState(false); + + const dispatch = useDispatch(); + + + const filterType = useSelector((state) => state.callPlayer.filterType); + const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); + const filterTalkgroups = useSelector((state) => state.callPlayer.filterTalkgroups); + const filterStarred = useSelector((state) => state.callPlayer.filterStarred); + const filterDate = useSelector((state) => state.callPlayer.filterDate); + const live = useSelector((state) => state.callPlayer.live); + + const uri = queryString.parse(useLocation().search); + + const switchAutoPlay = () => setAutoPlay(!autoPlay); + const handlePlayPause = (playing) => setIsPlaying(playing); + + const handlePusherClick = () => { + if (sidebarOpened) setSidebarOpened(false); } - switchAutoPlay = () => this.setState({ - autoPlay: !this.state.autoPlay - }) - handlePlayPause = (playing) => this.setState({ - isPlaying: playing - }) - handlePusherClick = () => { - const { sidebarOpened } = this.state + const handleSidebarToggle = () => setSidebarOpened(!sidebarOpened); + const handleFilterToggle = () => setFilterVisible(!filterVisible); + const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); + const handleLiveToggle = () => { + dispatch(setLive(!live)); + } + + const audioRef = false; + + const loadNewerCalls = () => { + + } + const loadOlderCalls = () => { - if (sidebarOpened) this.setState({ sidebarOpened: false }) } - handleSidebarToggle = () => this.setState({ sidebarOpened: !this.state.sidebarOpened }) + const playCall = (data) => { - handleFilterToggle = () => this.setState({ - filterVisible: !this.state.filterVisible - }) + } + + const callEnded = () => { + + } + /*const changeUrl = url => callActions.changeUrl(url) handleContextRef = contextRef => this.setState({ contextRef }) - changeUrl = url => this.props.callActions.changeUrl(url) - loadNewerCalls() { console.log("Loading Newer Calls"); - this.props.callActions.fetchNewerCalls(this.props.newestCallTime.getTime()); + callActions.fetchNewerCalls(newestCallTime.getTime()); } loadOlderCalls() { console.log("Loading Older Calls"); - this.props.callActions.fetchOlderCalls(this.props.oldestCallTime.getTime()); + callActions.fetchOlderCalls(oldestCallTime.getTime()); - } + }*/ - handleCalendarToggle = () => this.setState({ - calendarVisible: !this.state.calendarVisible - }) - getFilter() { +const getFilter = () => { + var filter = { type: 'all', code: "", filterStarred: false }; - switch (this.props.filterType) { + + switch (filterType) { case 1: filter.type = "group"; - filter.code = this.props.filterGroupId; + filter.code = filterGroupId break; case 2: filter.type = "talkgroup"; - filter.code = this.props.filterTalkgroups; + filter.code = filterTalkgroups break; default: case 0: filter.type = "all"; filter.code = "" } - filter.filterStarred = this.props.filterStarred; + filter.filterStarred = filterStarred return filter; } - endSocket() { + /* + const endSocket = () => { this.socket.removeAllListeners("new message"); this.socket.removeAllListeners("reconnect"); } @@ -135,9 +143,9 @@ class CallPlayer extends React.Component { this.socket.on('new message', this.addCall); this.socket.on('reconnect', (attempts) => { console.log("Socket Reconnected after attempts: " + attempts); // true - if (this.props.live) { + if (live) { var filter = this.getFilter(); - this.startSocket(this.props.shortName, filter.type, filter.code, filter.filterStarred); + this.startSocket(shortName, filter.type, filter.code, filter.filterStarred); } }) } @@ -154,16 +162,17 @@ class CallPlayer extends React.Component { this.socket.emit("stop"); } handleLiveToggle() { - if (!this.props.live) { - this.props.callActions.setDateFilter(false); - this.props.callActions.setLive(false); + if (!live) { + callActions.setDateFilter(false); + callActions.setLive(false); this.setState({ callUrl: "", callId: false }); - this.props.callActions.fetchCalls(); + callActions.fetchCalls(); var filter = this.getFilter(); - this.startSocket(this.props.shortName, filter.type, filter.code, filter.filterStarred); + this.startSocket(shortName, filter.type, filter.code, filter.filterStarred); } } - +*/ +/* updateUri(props, state) { var search = "?" switch (props.filterType) { @@ -198,51 +207,48 @@ class CallPlayer extends React.Component { } search = search + `starred=true`; } -/* - this.props.history.push({ - pathname: this.props.history.location.pathname, + + history.push({ + pathname: history.location.pathname, search: search - });*/ - } - - handleCalendarClose(didUpdate) { - this.setState({ - calendarVisible: !this.state.calendarVisible }); - if (didUpdate) { - this.props.callActions.setLive(false); - this.props.callActions.fetchCalls(); + } +*/ - this.setState({ callUrl: "", callId: false }); - this.stopSocket(); + const handleCalendarClose = (didUpdate) => { + setCalendarVisible(!calendarVisible); + + if (didUpdate) { + dispatchEvent(setLive(false)); + setCallId(false); + setCallUrl(""); + //callActions.fetchCalls(); + //this.stopSocket(); //this.socket.close(); - } } - handleGroupClose(didUpdate) { - this.setState({ - groupVisible: !this.state.groupVisible - }); - this.props.callActions.fetchCalls(); + const handleGroupClose = (didUpdate) => { + setGroupVisible(!groupVisible); + //callActions.fetchCalls(); } - handleFilterClose(didUpdate) { - this.setState({ - filterVisible: !this.state.filterVisible - }); + const handleFilterClose = (didUpdate) => { + setFilterVisible(!filterVisible); + if (didUpdate) { - this.setState({ callUrl: "", callId: false }); - this.props.callActions.fetchCalls(); + setCallUrl(""); + setCallId(false); + //callActions.fetchCalls(); } } - +/* callEnded(data) { const audio = this.audioRef.current; - const currentIndex = this.props.callsAllIds.findIndex(callId => callId === this.state.callId); - if (this.state.autoPlay && (currentIndex > 0)) { - const nextCallId = this.props.callsAllIds[currentIndex - 1]; - const nextCall = this.props.callsById[nextCallId]; + const currentIndex = callsAllIds.findIndex(callId => callId === callId); + if (autoPlay && (currentIndex > 0)) { + const nextCallId = callsAllIds[currentIndex - 1]; + const nextCall = callsById[nextCallId]; var callUrl = nextCall.url; this.setState({ callUrl: callUrl, callId: nextCallId, isPlaying: true }, () => { audio.playSource(callUrl) }); //scrollToComponent(this.currentCallRef.current); @@ -252,7 +258,7 @@ class CallPlayer extends React.Component { block: "center", }); } - this.props.callActions.fetchCallInfo(nextCallId); + callActions.fetchCallInfo(nextCallId); } else { this.setState({ isPlaying: false }); } @@ -266,7 +272,7 @@ class CallPlayer extends React.Component { callId: data.call._id, isPlaying: true }, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current); - this.props.callActions.fetchCallInfo(data.call._id); + callActions.fetchCallInfo(data.call._id); } addCall(data) { @@ -274,9 +280,9 @@ class CallPlayer extends React.Component { switch (message.type) { case 'calls': const refs = this.refs.current; - this.props.callActions.addCall(message); - const newCall = this.props.callsById[this.props.callsAllIds[0]]; - if (!this.state.isPlaying && this.state.autoPlay) { + callActions.addCall(message); + const newCall = callsById[callsAllIds[0]]; + if (!isPlaying && autoPlay) { this.playCall({ call: newCall }); if (this.currentCallRef.current) { this.currentCallRef.current.scrollIntoView({ @@ -289,14 +295,9 @@ class CallPlayer extends React.Component { default: break } - } - componentWillUnmount() { - this.endSocket(); - } - componentDidMount() { - //const parsed = queryString.parse(location.search); - //console.log(parsed); - this.props.callActions.setShortName(this.props.shortName); + }*/ + + const setStateFromUri = () => { var filter = { filterDate: false, filterType: 0, // this is "all" @@ -305,7 +306,6 @@ class CallPlayer extends React.Component { filterStarred: false, live: true }; - const uri = queryString.parse(this.props.location.search); // is there a star filter? if (uri.hasOwnProperty('starred')) { @@ -335,10 +335,8 @@ class CallPlayer extends React.Component { callScroll: true, callSelect: true }); - this.props.callActions.fetchCallInfo(_id); - this.props.callActions.setCallTime(date); - - + //callActions.fetchCallInfo(_id); + //callActions.setCallTime(date); } @@ -367,23 +365,31 @@ class CallPlayer extends React.Component { } } - this.props.callActions.setFilter(filter); + //callActions.setFilter(filter); + } + useEffect(() => { // When a time/date is provided in the URL, it will be added to the filter // When fetchCalls is called it will selected calls that are older - this.props.callActions.fetchCalls(); - this.props.talkgroupActions.fetchTalkgroups(this.props.shortName); - this.props.groupActions.fetchGroups(this.props.shortName); - - - this.props.systemActions.fetchSystems(); - this.setupSocket(); + //callActions.fetchCalls(); + //talkgroupActions.fetchTalkgroups(shortName); + //groupActions.fetchGroups(shortName); + //systemActions.fetchSystems(); + /*this.setupSocket(); if (filter.live) { - this.startSocket(this.props.shortName, filterType, filterCode, filter.filterStarred); - } - } - compareArray(a1, a2) { + this.startSocket(shortName, filterType, filterCode, filter.filterStarred); + }*/ + dispatch(setShortName(shortName)); + return () => { + //this.endSocket(); + }; + }, []); + + + + + const compareArray = (a1, a2) => { var i = a1.length; while (i--) { if (a1[i] !== a2[i]) @@ -392,62 +398,60 @@ class CallPlayer extends React.Component { return true } + useEffect( () => { + if (!urlOptions && groupsData && (groupsData.length > 0)) { + setGroupVisible(true); + } + },[groupsData] ) + + useEffect( () => { + /*var typeString = 'all'; + var filterCode = ''; + switch (nextProps.filterType) { + + case 1: + typeString = 'group'; + filterCode = nextProps.filterGroupId; + break; + case 2: + typeString = 'talkgroup'; + filterCode = nextProps.filterTalkgroups; + break; + default: + case 0: + typeString = "all"; + filterCode = ''; + break; + } + + this.updateUri(nextProps, nextState); + if (live) { + this.startSocket(shortName, typeString, filterCode, nextProps.filterStarred); + }*/ + }, [filterType,filterTalkgroups,filterGroupId,filterStarred,filterDate]) + + + /* componentWillUpdate(nextProps, nextState) { - if (!this.props.groups && nextProps.groups) { - if (!this.state.urlOptions && (nextProps.groups.length > 0)) { - this.setState({ - groupVisible: true - }); - } - } - const filterChanged = (nextProps.filterType !== this.props.filterType) || !this.compareArray(nextProps.filterTalkgroups, this.props.filterTalkgroups) || (nextProps.filterGroupId !== this.props.filterGroupId) || (nextProps.filterDate !== this.props.filterDate) || (nextProps.filterStarred !== this.props.filterStarred); - if (filterChanged) { - var typeString = 'all'; - var filterCode = ''; - switch (nextProps.filterType) { - case 1: - typeString = 'group'; - filterCode = nextProps.filterGroupId; - break; - case 2: - typeString = 'talkgroup'; - filterCode = nextProps.filterTalkgroups; - break; - default: - case 0: - typeString = "all"; - filterCode = ''; - break; - } - - this.updateUri(nextProps, nextState); - if (this.props.live) { - this.startSocket(this.props.shortName, typeString, filterCode, nextProps.filterStarred); - } - } - - const callsChanged = nextProps.callsAllIds[0] !== this.props.callsAllIds[0]; + const callsChanged = nextProps.callsAllIds[0] !== callsAllIds[0]; if (callsChanged) { const { contextRef } = this.state; this.scrollPos = contextRef.scrollTop; this.prevHeight = contextRef.clientHeight; this.autoScroll = true; } - /* // if you do this, you also need to update time in the URI to be the call time... - if (this.state.callId != nextState.callId) { - this.updateUri(nextProps, nextState); - }*/ - if (this.state.callId && !this.state.callUrl) { - const call = this.props.callsById[this.state.callId]; + + if (callId && !callUrl) { + const call = callsById[callId]; if (call) { this.setState({ callUrl: call.url }); } } - } - + }*/ +/* componentDidUpdate(prevProps, prevState) { const { contextRef } = this.state //console.log( contextRef.clientHeight - this.prevHeight ); @@ -457,8 +461,8 @@ class CallPlayer extends React.Component { } // scroll to a call once the calls have loaded - if (this.state.callScroll && (this.props.callsAllIds.length > prevProps.callsAllIds.length)) { - const call = this.props.callsById[this.state.callId]; + if (callScroll && (callsAllIds.length > prevProps.callsAllIds.length)) { + const call = callsById[callId]; if (call) { this.setState({ callScroll: false }); if (this.currentCallRef.current) { @@ -472,8 +476,8 @@ class CallPlayer extends React.Component { } } - if (this.state.callSelect) { - const call = this.props.callsById[this.state.callId]; + if (callSelect) { + const call = callsById[callId]; if (call) { const audio = this.audioRef.current; var callUrl = call.url; @@ -484,37 +488,40 @@ class CallPlayer extends React.Component { }, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current); } } - } + }*/ //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 - render() { - const { contextRef } = this.state - const { sidebarOpened } = this.state + + //const { contextRef } = this.state + var archiveLabel = ""; - if (this.props.filterDate) { - const filterDate = new Date(this.props.filterDate); + if (filterDate) { + const filterDate = new Date(filterDate); archiveLabel = filterDate.toLocaleDateString() + " " + filterDate.toLocaleTimeString() } var callInfoHeader = "Call Info"; - const currentCall = this.props.callsById[this.state.callId]; + const currentCall = false;//callsById[callId]; + + var callsAllIds = []; + var callsById = false; var callLink = "" var callDownload = "" if (currentCall) { - if ((this.props.talkgroups) && this.props.talkgroups[currentCall.talkgroupNum]) { - callInfoHeader = this.props.talkgroups[currentCall.talkgroupNum].description; + if ((talkgroupsData) && talkgroupsData[currentCall.talkgroupNum]) { + callInfoHeader = talkgroupsData[currentCall.talkgroupNum].description; } const callDate = new Date(currentCall.time); - callLink = "/system/" + this.props.shortName + "?call-id=" + currentCall._id + "&time=" + (callDate.getTime() + 1); + callLink = "/system/" + shortName + "?call-id=" + currentCall._id + "&time=" + (callDate.getTime() + 1); callDownload = currentCall.url; } var archive = process.env.REACT_APP_ARCHIVE_DAYS var filterLabel = "All" - switch (this.props.filterType) { + switch (filterType) { case 1: filterLabel = "Group" break; @@ -526,41 +533,39 @@ class CallPlayer extends React.Component { case 0: filterLabel = "All" } - /* if (this.props.calls) { + /* if (calls) { var callRows = calls.map((call) => )) }*/ return ( -
- - - +
+ + + - + onClick={handlePusherClick} duration={50} width='thin'> + Home Systems About - - - + - + Filter - {this.props.system && this.props.system.name} + {/*system && system.name*/} - + { @@ -570,7 +575,7 @@ class CallPlayer extends React.Component { } - + Live @@ -583,24 +588,24 @@ class CallPlayer extends React.Component { - - + + {/* */} - + - this.switchAutoPlay()}>Autoplay - + this.switchAutoPlay()}>Autoplay + { /* */ } Download @@ -616,6 +621,6 @@ class CallPlayer extends React.Component { ); } -} + export default CallPlayer; diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js index 461b3cf..6101e9e 100644 --- a/frontend/src/features/api/apiSlice.js +++ b/frontend/src/features/api/apiSlice.js @@ -25,14 +25,7 @@ export const apiSlice = createApi({ // The URL for the request is '/fakeApi/posts' query: (shortName) => ({ url: `/${shortName}/talkgroups` }) }), - getCalls: builder.query({ - // The URL for the request is '/fakeApi/posts' - query: ({shortName,filterType=0,filterCode=0}) => ({ - url: `/${shortName}/calls`, - method: "GET", - params: { filterType, filterCode }, - }) - }) + }) }) diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js new file mode 100644 index 0000000..234d4d7 --- /dev/null +++ b/frontend/src/features/calls/callsSlice.js @@ -0,0 +1,48 @@ +import { + createSlice, + createEntityAdapter, + createSelector + } from '@reduxjs/toolkit' + + import { apiSlice } from '../api/apiSlice' + + const callsAdapter = createEntityAdapter({ + selectId: (call) => call._id, + sortComparer: (a, b) => a.time.localeCompare(b.time), + }) + + const initialState = callsAdapter.getInitialState() + + export const extendedApiSlice = apiSlice.injectEndpoints({ + endpoints: builder => ({ + getCalls: builder.query({ + query: ({shortName,filterType=0,filterCode=0}) => ({ + url: `/${shortName}/calls`, + method: "GET", + params: { filterType, filterCode }, + }), + transformResponse: responseData => { + responseData = responseData.calls; + return callsAdapter.setAll(initialState, responseData) + } + }) + }) + }) + + export const { useGetCallsQuery } = extendedApiSlice + + +// Calling `someEndpoint.select(someArg)` generates a new selector that will return +// the query result object for a query with those parameters. +// To generate a selector for a specific query argument, call `select(theQueryArg)`. +// In this case, the users query has no params, so we don't pass anything to select() +export const selectCallsResult = extendedApiSlice.endpoints.getCalls.select() + + +export const selectCallsData = createSelector( + selectCallsResult, + callsResult => callsResult.data +) + +export const { selectAll: selectAllCalls, selectById: selectCallById } = + callsAdapter.getSelectors(state => selectCallsData(state) ?? initialState) diff --git a/frontend/src/index.js b/frontend/src/index.js index d6d68a1..6e79eb3 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -17,7 +17,7 @@ import MainContainer from "./Main/MainContainer" import ListSystemsContainer from "./System/ListSystemsContainer" // Call -import CallPlayerContainer from "./Call/CallPlayerContainer" +import CallPlayer from "./Call/CallPlayer" import AboutComponent from "./About/AboutComponent" @@ -31,7 +31,7 @@ root.render( } /> } /> - } /> + } /> } />