From d32b644b24cbfd64c3639509d643c11cc67f2082 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 31 Dec 2022 09:18:59 -0500 Subject: [PATCH 01/27] Update GroupModal.js --- frontend/src/Call/GroupModal.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/frontend/src/Call/GroupModal.js b/frontend/src/Call/GroupModal.js index 328fbd5..608177b 100644 --- a/frontend/src/Call/GroupModal.js +++ b/frontend/src/Call/GroupModal.js @@ -8,32 +8,21 @@ import { import "./FilterModal.css"; -class GroupModal extends Component { - constructor(props) { - super(props) - this.handleInputChange = this.handleInputChange.bind(this); - this.handleDone = this.handleDone.bind(this); - this.handleClose = this.handleClose.bind(this); +function GroupModal(props) { - this.state = { - open: false, - selectedGroup: false - } -} +const [open, setOpen] = useState(false); +const [selectedGroup, setSelectedGroup] = useState(false); +const handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); +const handleClose = () => this.props.onClose(false); -handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); -handleClose = () => this.props.onClose(false); - -handleDone(event) { - +const handleDone = () => { if (this.state.selectedGroup) { this.props.callActions.setGroupFilter(this.state.selectedGroup); this.props.onClose(true); } else { this.props.onClose(false); } - } componentDidUpdate(prevProps) { From f4813f87f46d880aaf894b7c7e95aee24971feae Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 31 Dec 2022 09:23:07 -0500 Subject: [PATCH 02/27] Create apiSlice.js --- frontend/src/features/api/apiSlice.js | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 frontend/src/features/api/apiSlice.js diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js new file mode 100644 index 0000000..6710ffb --- /dev/null +++ b/frontend/src/features/api/apiSlice.js @@ -0,0 +1,93 @@ +// Import the RTK Query methods from the React-specific entry point +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + + +function buildCallUrl(state, date, direction) { + var params = {}; + var url = ""; + + + if ((typeof direction === 'string') && (typeof date === 'number')) { + url = url + '/' + direction; + params["time"] = date; + } else { + if (state.call.filterDate){ + url = url + '/older' + params["time"] = state.call.filterDate; + } + } + if (state.call.filterStarred) { + params["filter-starred"] = true; + } + switch(state.call.filterType) { + case 1: + params["filter-type"] = "group"; + params["filter-code"] = state.call.filterGroupId; + break; + case 2: + params["filter-type"] = "talkgroup"; + params["filter-code"] = state.call.filterTalkgroups; + break; + default: + case 0: + } +/* + + if (this.filterCode != "") { + params["filter-code"] = this.filterCode; + } + + if (this.filterType == this.FilterType.Group) { + params["filter-type"] = "group"; + params["filter-name"] = this.filterName; + } + + if (this.filterType == this.FilterType.Talkgroup) { + params["filter-type"] = "talkgroup"; + } + + if (this.filterType == this.FilterType.Unit) { + params["filter-type"] = "unit"; + } + +*/ + // url = startUp.backend_server + "/" + startUp.shortName.toLowerCase() + "/calls" + url + '?' + buildUrlParams(params); + url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName + "/calls" + url + '?' + buildUrlParams(params); + console.log("Trying to fetch data from this url: " + url); + return url; +} + +// Define our single API slice object +export const apiSlice = createApi({ + // The cache reducer expects to be added at `state.api` (already default - this is optional) + reducerPath: 'api', + // All of our requests will have URLs starting with '/fakeApi' + baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_BACKEND_SERVER }), + // The "endpoints" represent operations and requests for this server + endpoints: builder => ({ + // The `getPosts` endpoint is a "query" operation that returns data + getGroups: builder.query({ + // The URL for the request is '/fakeApi/posts' + query: (shortName) => ({ url: '/${shortName}/groups' }) + }), + getSystems: builder.query({ + // The URL for the request is '/fakeApi/posts' + query: () => ({ url: '/systems' }) + }), + getTalkgroups: builder.query({ + // 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, filterCode}) => ({ + url: "/${shortName}/calls", + method: "GET", + params: { filterType, filterCode }, + }) + }) + }) +}) + +// Export the auto-generated hook for the `getPosts` query endpoint +export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice \ No newline at end of file From 1b785f3e956bdffe20946f1921cc2360470cd8de Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 31 Dec 2022 09:30:12 -0500 Subject: [PATCH 03/27] Update GroupModal.js --- frontend/src/Call/GroupModal.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/Call/GroupModal.js b/frontend/src/Call/GroupModal.js index 608177b..574d608 100644 --- a/frontend/src/Call/GroupModal.js +++ b/frontend/src/Call/GroupModal.js @@ -24,15 +24,16 @@ const handleDone = () => { this.props.onClose(false); } } +/* componentDidUpdate(prevProps) { const filterChanged = (prevProps.selectedGroup !== this.props.selectedGroup); if (filterChanged) { this.setState({ selectedGroup: this.props.selectedGroup}); } -} +}*/ - render() { + //render() { var groupList = []; @@ -69,6 +70,6 @@ componentDidUpdate(prevProps) { ) } -} +//} export default GroupModal; From 931c8590dbc9d27b32466aeb8b3c36a9a2ece016 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 31 Dec 2022 09:30:41 -0500 Subject: [PATCH 04/27] old ver --- frontend/src/Call/GroupModal.js | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/frontend/src/Call/GroupModal.js b/frontend/src/Call/GroupModal.js index 574d608..a5761fa 100644 --- a/frontend/src/Call/GroupModal.js +++ b/frontend/src/Call/GroupModal.js @@ -8,32 +8,42 @@ import { import "./FilterModal.css"; -function GroupModal(props) { +class GroupModal extends Component { + constructor(props) { + super(props) + this.handleInputChange = this.handleInputChange.bind(this); + this.handleDone = this.handleDone.bind(this); + this.handleClose = this.handleClose.bind(this); -const [open, setOpen] = useState(false); -const [selectedGroup, setSelectedGroup] = useState(false); + this.state = { + open: false, + selectedGroup: false + } +} -const handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); -const handleClose = () => this.props.onClose(false); -const handleDone = () => { +handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); +handleClose = () => this.props.onClose(false); + +handleDone(event) { + if (this.state.selectedGroup) { this.props.callActions.setGroupFilter(this.state.selectedGroup); this.props.onClose(true); } else { this.props.onClose(false); } -} -/* + +} componentDidUpdate(prevProps) { const filterChanged = (prevProps.selectedGroup !== this.props.selectedGroup); if (filterChanged) { this.setState({ selectedGroup: this.props.selectedGroup}); } -}*/ +} - //render() { + render() { var groupList = []; @@ -70,6 +80,6 @@ componentDidUpdate(prevProps) { ) } -//} +} export default GroupModal; From 10006a106b2b3f96beefe077b8c4cf1ad69d2880 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 31 Dec 2022 13:56:38 -0500 Subject: [PATCH 05/27] loading Groups --- .DS_Store | Bin 6148 -> 6148 bytes frontend/src/Call/CallPlayer.js | 5 +- frontend/src/Call/call-data.js | 29 +++++++++ frontend/src/features/api/apiSlice.js | 63 ++------------------ frontend/src/redux-router/configureStore.js | 3 + frontend/src/redux-router/reducers.js | 4 +- 6 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 frontend/src/Call/call-data.js diff --git a/.DS_Store b/.DS_Store index 2a559d693a35f56dbe5d52e55f9c002ced87b135..b4b7e59d95290f6827abaa1fbf4422f9ad3d9431 100644 GIT binary patch delta 59 zcmZoMXfc@JFUZ8ez`)4BAi$88Qk>Pjj0lsh$bN~PV delta 45 zcmZoMXfc@JFUrWkzyQPo3=AaHome Systems About + diff --git a/frontend/src/Call/call-data.js b/frontend/src/Call/call-data.js new file mode 100644 index 0000000..c6f8c76 --- /dev/null +++ b/frontend/src/Call/call-data.js @@ -0,0 +1,29 @@ +import React from 'react' +import { useGetGroupsQuery } from '../features/api/apiSlice' + +export const Groups = (args) => { + + const { data, isError, isLoading, isSuccess,error } = useGetGroupsQuery(args.shortName); + console.log(args.shortName) + let content + + if (isLoading) { + content =
Loading...
+ } else if (isSuccess) { + + for (const num in data) { + const group = data[num]; + content =
  • {group.groupName}
  • ; + } + + } else if (isError) { + content =
    {error.toString()}
    + } + + return ( +
    +

    Group

    + {content} +
    + ) + } \ No newline at end of file diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js index 6710ffb..d72cd04 100644 --- a/frontend/src/features/api/apiSlice.js +++ b/frontend/src/features/api/apiSlice.js @@ -2,65 +2,12 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' -function buildCallUrl(state, date, direction) { - var params = {}; - var url = ""; - - - if ((typeof direction === 'string') && (typeof date === 'number')) { - url = url + '/' + direction; - params["time"] = date; - } else { - if (state.call.filterDate){ - url = url + '/older' - params["time"] = state.call.filterDate; - } - } - if (state.call.filterStarred) { - params["filter-starred"] = true; - } - switch(state.call.filterType) { - case 1: - params["filter-type"] = "group"; - params["filter-code"] = state.call.filterGroupId; - break; - case 2: - params["filter-type"] = "talkgroup"; - params["filter-code"] = state.call.filterTalkgroups; - break; - default: - case 0: - } -/* - - if (this.filterCode != "") { - params["filter-code"] = this.filterCode; - } - - if (this.filterType == this.FilterType.Group) { - params["filter-type"] = "group"; - params["filter-name"] = this.filterName; - } - - if (this.filterType == this.FilterType.Talkgroup) { - params["filter-type"] = "talkgroup"; - } - - if (this.filterType == this.FilterType.Unit) { - params["filter-type"] = "unit"; - } - -*/ - // url = startUp.backend_server + "/" + startUp.shortName.toLowerCase() + "/calls" + url + '?' + buildUrlParams(params); - url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName + "/calls" + url + '?' + buildUrlParams(params); - console.log("Trying to fetch data from this url: " + url); - return url; -} // Define our single API slice object export const apiSlice = createApi({ // The cache reducer expects to be added at `state.api` (already default - this is optional) reducerPath: 'api', + // All of our requests will have URLs starting with '/fakeApi' baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_BACKEND_SERVER }), // The "endpoints" represent operations and requests for this server @@ -68,7 +15,7 @@ export const apiSlice = createApi({ // The `getPosts` endpoint is a "query" operation that returns data getGroups: builder.query({ // The URL for the request is '/fakeApi/posts' - query: (shortName) => ({ url: '/${shortName}/groups' }) + query: (shortName) => ({ url: `/${shortName}/groups` }) }), getSystems: builder.query({ // The URL for the request is '/fakeApi/posts' @@ -76,12 +23,12 @@ export const apiSlice = createApi({ }), getTalkgroups: builder.query({ // The URL for the request is '/fakeApi/posts' - query: (shortName) => ({ url: '/${shortName}/talkgroups' }) + query: (shortName) => ({ url: `/${shortName}/talkgroups` }) }), getCalls: builder.query({ // The URL for the request is '/fakeApi/posts' query: ({shortName, filterType, filterCode}) => ({ - url: "/${shortName}/calls", + url: `/${shortName}/calls`, method: "GET", params: { filterType, filterCode }, }) @@ -90,4 +37,4 @@ export const apiSlice = createApi({ }) // Export the auto-generated hook for the `getPosts` query endpoint -export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice \ No newline at end of file +export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice diff --git a/frontend/src/redux-router/configureStore.js b/frontend/src/redux-router/configureStore.js index 8982424..a825832 100644 --- a/frontend/src/redux-router/configureStore.js +++ b/frontend/src/redux-router/configureStore.js @@ -5,6 +5,8 @@ import { createBrowserHistory } from 'history' import { applyMiddleware, compose, createStore } from 'redux' import { routerMiddleware } from 'connected-react-router' import createRootReducer from './reducers' +import { apiSlice } from '../features/api/apiSlice' + import thunk from 'redux-thunk'; export const history = createBrowserHistory() @@ -18,6 +20,7 @@ export default function configureStore(preloadedState) { applyMiddleware( routerMiddleware(history), // for dispatching history actions thunk, + apiSlice.middleware // ... other middlewares ... ), ), diff --git a/frontend/src/redux-router/reducers.js b/frontend/src/redux-router/reducers.js index ac78ad6..209e0c6 100644 --- a/frontend/src/redux-router/reducers.js +++ b/frontend/src/redux-router/reducers.js @@ -6,11 +6,13 @@ import systemReducer from "../System/system-reducer" import talkgroupReducer from "../Talkgroups/talkgroup-reducer" import groupReducer from "../Group/group-reducer" import callReducer from "../Call/call-reducer" +import {apiSlice} from "../features/api/apiSlice" export default (history) => combineReducers({ router: connectRouter(history), system: systemReducer, talkgroup: talkgroupReducer, group: groupReducer, - call: callReducer + call: callReducer, + [apiSlice.reducerPath]: apiSlice.reducer }) \ No newline at end of file From 2afbe725335cfe8b63a37a7b429c8e19a3ae4d78 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 1 Jan 2023 09:52:31 -0500 Subject: [PATCH 06/27] call data fetching --- frontend/src/Call/CallPlayer.js | 3 ++- frontend/src/Call/call-data.js | 32 +++++++++++++++++++++++++-- frontend/src/features/api/apiSlice.js | 5 ++--- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index cf6cfaf..4aeb027 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -20,7 +20,7 @@ import { import "./CallPlayer.css"; //import setupSocket from '../socket/SetupSocket.js' import queryString from '../query-string'; -import {Groups} from "./call-data" +import {Groups, Calls} from "./call-data" @@ -545,6 +545,7 @@ class CallPlayer extends React.Component { Systems About + diff --git a/frontend/src/Call/call-data.js b/frontend/src/Call/call-data.js index c6f8c76..34e79d1 100644 --- a/frontend/src/Call/call-data.js +++ b/frontend/src/Call/call-data.js @@ -1,10 +1,38 @@ import React from 'react' -import { useGetGroupsQuery } from '../features/api/apiSlice' +import { useGetGroupsQuery, useGetCallsQuery } from '../features/api/apiSlice' export const Groups = (args) => { const { data, isError, isLoading, isSuccess,error } = useGetGroupsQuery(args.shortName); - console.log(args.shortName) + + let content + + if (isLoading) { + content =
    Loading...
    + } else if (isSuccess) { + + for (const num in data) { + const group = data[num]; + content =
  • {group.groupName}
  • ; + } + + } else if (isError) { + content =
    {error.toString()}
    + } + + return ( +
    +

    Group

    + {content} +
    + ) + } + + + export const Calls = (args) => { + + const { data, isError, isLoading, isSuccess,error } = useGetCallsQuery(args.shortName); + let content if (isLoading) { diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js index d72cd04..4b5e77b 100644 --- a/frontend/src/features/api/apiSlice.js +++ b/frontend/src/features/api/apiSlice.js @@ -27,10 +27,9 @@ export const apiSlice = createApi({ }), getCalls: builder.query({ // The URL for the request is '/fakeApi/posts' - query: ({shortName, filterType, filterCode}) => ({ + query: (shortName) => ({ url: `/${shortName}/calls`, - method: "GET", - params: { filterType, filterCode }, + method: "GET" //, params: { filterType, filterCode }, }) }) }) From 5a05ab509cd7e4142c5fff4f2e510c3b2b409ca1 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 2 Jan 2023 21:09:56 -0500 Subject: [PATCH 07/27] forward progress --- README.md | 4 ++++ frontend/src/Call/call-data.js | 2 +- frontend/src/features/api/apiSlice.js | 5 +++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba8488e..d290fde 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,10 @@ yarn start This should build the frontend and open a browser. In order to have all the cookies work correctly, you have to use the same domain name. Make sure you have setup the local domains as described above. Then goto the base domain, for me that is openmhz.test, at port 3000 `openmhz.test:3000` +## Upgrading a "Frontend" server + +https://create-react-app.dev/docs/updating-to-new-releases/ + ## Managing MongoDB diff --git a/frontend/src/Call/call-data.js b/frontend/src/Call/call-data.js index 34e79d1..ecd7e6d 100644 --- a/frontend/src/Call/call-data.js +++ b/frontend/src/Call/call-data.js @@ -31,7 +31,7 @@ export const Groups = (args) => { export const Calls = (args) => { - const { data, isError, isLoading, isSuccess,error } = useGetCallsQuery(args.shortName); + const { data, isError, isLoading, isSuccess,error } = useGetCallsQuery({shortName: args.shortName}); let content diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js index 4b5e77b..461b3cf 100644 --- a/frontend/src/features/api/apiSlice.js +++ b/frontend/src/features/api/apiSlice.js @@ -27,9 +27,10 @@ export const apiSlice = createApi({ }), getCalls: builder.query({ // The URL for the request is '/fakeApi/posts' - query: (shortName) => ({ + query: ({shortName,filterType=0,filterCode=0}) => ({ url: `/${shortName}/calls`, - method: "GET" //, params: { filterType, filterCode }, + method: "GET", + params: { filterType, filterCode }, }) }) }) From 271ecee5d5e6e301e6c9b78f6488128e1afe8ecc Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Wed, 4 Jan 2023 21:58:22 -0500 Subject: [PATCH 08/27] Filter modal is now working too --- README.md | 2 +- frontend/package.json | 1 + frontend/src/Call/CallPlayer.js | 8 +- frontend/src/Call/FilterModal.js | 243 ++--- frontend/src/Call/GroupModal.js | 67 +- frontend/src/Group/CreateGroup.js | 177 ---- frontend/src/Group/CreateGroupContainer.js | 25 - frontend/src/Group/ListGroups.js | 77 -- .../features/callPlayer/callPlayerSlice.js | 76 ++ frontend/src/redux-router/configureStore.js | 3 +- frontend/src/redux-router/reducers.js | 6 +- frontend/yarn.lock | 882 +++++++++++++++++- 12 files changed, 1156 insertions(+), 411 deletions(-) delete mode 100644 frontend/src/Group/CreateGroup.js delete mode 100644 frontend/src/Group/CreateGroupContainer.js delete mode 100644 frontend/src/Group/ListGroups.js create mode 100644 frontend/src/features/callPlayer/callPlayerSlice.js diff --git a/README.md b/README.md index d290fde..dc98aa7 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ https://create-react-app.dev/docs/updating-to-new-releases/ ## Managing MongoDB MongoDB is used in the backend to store data. It is pretty fast, flexible and has worked well enough for me. -All of the files that MongoDB uses to store the DB are in the */data* directory, which gets mapped into the contianer. +All of the files that MongoDB uses to store the DB are in the */data* directory, which gets mapped into the container. Mapping this directory makes sure that the data persists each time you run the mongo container. ### Working with the MongoDB Container diff --git a/frontend/package.json b/frontend/package.json index 687d23d..b407c80 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "date-fns": "2.29.3", "ejs": "3.1.8", "history": "^5.3.0", + "mongodb": "^4.13.0", "react": "^18.2.0", "react-audio-player": "0.17.0", "react-datepicker": "4.8.0", diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 4aeb027..7d93a4b 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -1,8 +1,8 @@ import React from "react"; import { Link } from 'react-router-dom'; import MediaPlayer from "./MediaPlayer"; -import FilterModal from "./FilterModalContainer"; -import GroupModal from "./GroupModalContainer"; +import FilterModal from "./FilterModal"; +import GroupModal from "./GroupModal"; import SupportModal from "./SupportModal"; import CalendarModal from "./CalendarModalContainer"; import CallInfo from "./CallInfo"; @@ -198,11 +198,11 @@ class CallPlayer extends React.Component { } search = search + `starred=true`; } - +/* this.props.history.push({ pathname: this.props.history.location.pathname, search: search - }); + });*/ } handleCalendarClose(didUpdate) { diff --git a/frontend/src/Call/FilterModal.js b/frontend/src/Call/FilterModal.js index a8df8d3..1b7c91c 100644 --- a/frontend/src/Call/FilterModal.js +++ b/frontend/src/Call/FilterModal.js @@ -1,4 +1,7 @@ -import React, { Component } from "react"; +import React, { Component, useState } from "react"; +import { setGroupFilter, setTalkgroupFilter, setAllFilter, setStarredFilter } from "../features/callPlayer/callPlayerSlice"; +import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' +import { useSelector, useDispatch } from 'react-redux' import { Modal, Button, @@ -12,136 +15,134 @@ import { import "./FilterModal.css"; -class FilterModal extends Component { - constructor(props) { - super(props) - this.handleInputChange = this.handleInputChange.bind(this); - this.handleTabChange = this.handleTabChange.bind(this); - this.toggleStarred = this.toggleStarred.bind(this); - this.handleDone = this.handleDone.bind(this); - this.handleClose = this.handleClose.bind(this); +function FilterModal(props) { + const shortName = useSelector((state) => state.callPlayer.shortName); + const globalFilterStarred = useSelector((state) => state.callPlayer.filterStarred); + const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(props.shortName); + const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(props.shortName); + const [open, setOpen] = useState(false); + const [selectedTalkgroup, setSelectedTalkgroup] = useState([]); + const [selectedGroup, setSelectedGroup] = useState(false); + const [filterStarred, setFilterStarred] = useState(false); + const [activeTab, setActiveTab] = useState(0); + const dispatch = useDispatch() - this.state = { - open: false, - selectedTalkgroup: this.props.selectedTalkgroups, - selectedGroup: this.props.selectedGroup, - filterStarred: this.props.filterStarred, - activeTab: this.props.activeTab + const handleTabChange = (e, data) => setActiveTab(data.activeIndex); + const handleGroupChange = (e, { name, value }) => setSelectedGroup(value); + const handleTalkgroupChange = (e, { name, value }) => setSelectedTalkgroup(value); + const toggleStarred = () => setFilterStarred(!filterStarred); + const handleClose = () => props.onClose(false); + const onClose = props.onClose; + + function handleDone(onClose) { + switch (activeTab) { + + case 1: + if (selectedGroup) { + dispatch(setGroupFilter(selectedGroup)); + props.onClose(true); + } else { + props.onClose(false); + } + break; + case 2: + if (selectedTalkgroup) { + dispatch(setTalkgroupFilter(selectedTalkgroup)) + props.onClose(true); + } else { + props.onClose(false); + } + break; + default: + case 0: + dispatch(setAllFilter()); + props.onClose(true); + break; + } + if (globalFilterStarred !== filterStarred) { + dispatch(setStarredFilter(filterStarred)); } -} - -handleTabChange = (e, data) => this.setState({activeTab: data.activeIndex}); -handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); -toggleStarred = () => this.setState(prevState => ({ filterStarred: !prevState.filterStarred })); -handleClose = () => this.props.onClose(false); -handleDone(event) { - switch(this.state.activeTab) { - - case 1: - if (this.state.selectedGroup) { - this.props.callActions.setGroupFilter(this.state.selectedGroup); - this.props.onClose(true); - } else { - this.props.onClose(false); - } - break; - case 2: - if (this.state.selectedTalkgroup) { - this.props.callActions.setTalkgroupFilter(this.state.selectedTalkgroup); - this.props.onClose(true); - } else { - this.props.onClose(false); - } - break; - default: - case 0: - this.props.callActions.setAllFilter(); - this.props.onClose(true); - break; } - if (this.state.filterStarred !== this.props.filterStarred) { - this.props.callActions.setStarred(this.state.filterStarred); - this.props.onClose(true); + + + var obj; + let talkgroupList = []; + if (isTalkgroupsSuccess) { + for (const num in talkgroupsData["talkgroups"]) { + const talkgroup = talkgroupsData["talkgroups"][num]; + obj = { + key: talkgroup.num, + value: talkgroup.num, + text: talkgroup.description + } + talkgroupList.push(obj); + } + } + + let groupList = []; + if (isGroupsSuccess) { + for (const num in groupsData) { + const group = groupsData[num] + obj = { + key: group._id, + value: group._id, + text: group.groupName + } + groupList.push(obj); + } } -} -componentDidUpdate(prevProps) { - - const filterChanged = ((prevProps.selectedTalkgroups !== this.props.selectedTalkgroups) || (prevProps.selectedGroup !== this.props.selectedGroup)); - if (filterChanged) { - this.setState({selectedTalkgroup: prevProps.selectedTalkgroups, selectedGroup: prevProps.selectedGroup, activeTab: prevProps.activeTab}); - } -} - - render() { - var obj; - var talkgroupList = []; - if (this.props.talkgroups) { - for (const num in this.props.talkgroups) { - const talkgroup = this.props.talkgroups[num]; - obj = { - key: talkgroup.num, - value: talkgroup.num, - text: talkgroup.description - } - talkgroupList.push(obj); + const panes = [ + { + menuItem: 'All', render: () => { + return ( + +
    All Calls
    +

    Dispaly all of the calls.

    +
    + ) } - } - var groupList = []; - if (this.props.groups) { - for (const num in this.props.groups) { - const group = this.props.groups[num]; - obj = { - key: group._id, - value: group._id, - text: group.groupName - } - groupList.push(obj); + }, + { + menuItem: 'Groups', render: () => { + return ( + + + + + ) } - } - const panes = [ - { menuItem: 'All', render: () => - { return ( - -
    All Calls
    -

    Dispaly all of the calls.

    -
    - )}}, - { menuItem: 'Groups', render: () => - { return ( - - + }, + { + menuItem: 'Talkgroups', render: () => { + return ( + + + + ) + } + }, + ] + return ( - - )} }, - { menuItem: 'Talkgroups', render: () => - { return ( - - - - )} }, -] - return ( + + Select a Filter + + + + + + + + + + + - - Select a Filter - - - - - - - - - - - - - ) - } + ) } export default FilterModal; diff --git a/frontend/src/Call/GroupModal.js b/frontend/src/Call/GroupModal.js index a5761fa..95e6eee 100644 --- a/frontend/src/Call/GroupModal.js +++ b/frontend/src/Call/GroupModal.js @@ -1,4 +1,8 @@ -import React, { Component } from "react"; +import React, { Component, useState } from "react"; +import { useGetGroupsQuery } from '../features/api/apiSlice' +import { setGroupFilter } from "../features/callPlayer/callPlayerSlice"; +import { useSelector, useDispatch } from 'react-redux' + import { Modal, Button, @@ -6,8 +10,67 @@ import { Dropdown } from "semantic-ui-react"; import "./FilterModal.css"; +import * as types from "./call-constants"; + +function GroupModal (props) { + const shortName = useSelector((state) => state.callPlayer.shortName); + const { data, isError, isLoading, isSuccess,error } = useGetGroupsQuery(props.shortName); + const [open, setOpen] = useState(false); + const [selectedGroup, setSelectedGroup] = useState(false); + const dispatch = useDispatch() + const onClose = props.onClose; + + function handleDone(onClose) { + + if (selectedGroup) { + dispatch(setGroupFilter(selectedGroup)) + //dispatch({type: types.SET_GROUP_FILTER, groupId: selectedGroup }) + onClose(true); + } else { + onClose(false); + } +} + let groupOptions = [] + + if (isSuccess) { + + for (const num in data) { + const group = data[num]; + var obj = { + key: group._id, + value: group._id, + text: group.groupName + } + groupOptions.push(obj); + } +} + + + return ( + + setOpen(false)} onOpen={() => setOpen(true)} size='tiny'> + + Select a Group + + +

    Choose the type of calls you want to listen to

    + setSelectedGroup(data.value)} /> + +
    + +
    + + + +
    + + ) +} +/* class GroupModal extends Component { constructor(props) { super(props) @@ -80,6 +143,6 @@ componentDidUpdate(prevProps) { ) } -} +}*/ export default GroupModal; diff --git a/frontend/src/Group/CreateGroup.js b/frontend/src/Group/CreateGroup.js deleted file mode 100644 index 2488c9d..0000000 --- a/frontend/src/Group/CreateGroup.js +++ /dev/null @@ -1,177 +0,0 @@ -import React, { Component } from "react"; -import ReactDOM from "react-dom"; -import { - Container, - Header, - Form, - Grid, - Rail, - Segment, - List, - Input, - Button, - Message, - Icon, - Table -} from "semantic-ui-react"; - -// ---------------------------------------------------- -const requestMessageStyle = { - color: "red" -}; - -// ---------------------------------------------------- -class CreateGroup extends Component { - constructor(props) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - - // Set initial state - this.state = { - groupName: "", - group: { - name: "", - items: [] - }, - talkgroups: (this.props.talkgroups ? this.props.talkgroups : []) - }; - } - - componentDidMount() { - //this.props.groupActions.fetchGroups(this.props.shortName); - - this.props.talkgroupActions.fetchTalkgroups(this.props.shortName); - } - - componentWillReceiveProps(nextProps) { - this.setState({ talkgroups: nextProps.talkgroups }); - } - - removeTalkgroup(event, index) { - const item = { - num: this.state.group.items[index].num, - description: this.state.group.items[index].description - }; - - const talkgroups = this.state.talkgroups.slice(0); // copy - - for (var i = 0, len = talkgroups.length; i < len; i++) { - if (item.num < talkgroups[i].num) { - talkgroups.splice(i, 0, item); - break; - } - } - this.setState({ - group: { items: this.state.group.items - .slice(0, index) - .concat(this.state.group.items.slice(index + 1))}, - talkgroups: talkgroups - }); - } - -handleInputChange = (e, { name, value }) => this.setState({ [name]: value }); - - addTalkgroup(event, index) { - const item = { - num: this.state.talkgroups[index].num, - description: this.state.talkgroups[index].description - }; - - this.setState({ - group: { items: this.state.group.items.concat(item) }, - talkgroups: this.state.talkgroups - .slice(0, index) - .concat(this.state.talkgroups.slice(index + 1)) - }); - } - handleSubmit() { - var talkgroupNums = this.state.group.items.map(a => a.num); - const data = {shortName: this.props.shortName, groupName: this.state.groupName, talkgroups: JSON.stringify(talkgroupNums) } - this.props.groupActions.createGroup(data).then(requestMessage => { - if (requestMessage) { - // report to the user is there was a problem during registration - this.setState({ - requestMessage - }); - } else { - this.props.groupActions.changeUrl("/system/" + this.props.shortName + "/2"); - } - }); - } - - render() { - const talkgroups = this.state.talkgroups; - const group = this.state.group.items; - const railStyle = { - top: "15px", - minHeight: "200px" - }; - return ( -
    - {talkgroups.length ? ( - - - - ) : ( - - - - - - Number - - Description - - - - - {talkgroups.map((talkgroup, i) => ( - - {talkgroup.num} - - {talkgroup.description} - - this.addTalkgroup(e, i)} - /> - - - ))} - -
    - - - - - {group.map((talkgroup, i) => ( - - this.removeTalkgroup(e, i)} - /> - - {talkgroup.description} - - - - ))} - - - -
    -
    - )} -
    - ); - } -} - -export default CreateGroup; diff --git a/frontend/src/Group/CreateGroupContainer.js b/frontend/src/Group/CreateGroupContainer.js deleted file mode 100644 index 24314ff..0000000 --- a/frontend/src/Group/CreateGroupContainer.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from "react-redux" -import * as groupActionCreators from "./group-actions" -import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions" -import { bindActionCreators } from 'redux' -import CreateGroup from "./CreateGroup" - -//groups: state.group.items[props.match.params.shortName], -function mapStateToProps(state,props) { - return { - talkgroups: state.talkgroup.items[props.match.params.shortName], - shortName: props.match.params.shortName - } -} - -function mapDispatchToProps(dispatch) { - return { - groupActions: bindActionCreators(groupActionCreators,dispatch), - talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch) - } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CreateGroup) diff --git a/frontend/src/Group/ListGroups.js b/frontend/src/Group/ListGroups.js deleted file mode 100644 index b3970bc..0000000 --- a/frontend/src/Group/ListGroups.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from "react"; -import ReactDOM from "react-dom"; -import { Link } from 'react-router-dom' -import { - Container, - Header, - Form, - Grid, - Segment, - Input, - Button, - Message, - Icon, - Table -} from "semantic-ui-react"; - -// ---------------------------------------------------- - - - -// ---------------------------------------------------- -class ListGroups extends Component { - constructor(props) { - super(props); - - } - - -//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 groups = this.props.groups; - if (groups) { - return ( - - - - - Name - Talkgroup Count - - - - - {groups.map((group, i) => ( - - {group.groupName} - {group.talkgroups.length} - this.props.reorderGroup(i, i-1)} - /> this.props.reorderGroup(i, i+1)} - /> this.props.deleteGroup(group._id)} - /> - - - ))} - -
    - - ); - } else { - return ( -
    - ); - } -} -} - -export default ListGroups; diff --git a/frontend/src/features/callPlayer/callPlayerSlice.js b/frontend/src/features/callPlayer/callPlayerSlice.js new file mode 100644 index 0000000..d1edcab --- /dev/null +++ b/frontend/src/features/callPlayer/callPlayerSlice.js @@ -0,0 +1,76 @@ +import { createSlice } from '@reduxjs/toolkit' +const now = new Date(); +const initialState = { + isWaiting: false, + shortName: "", + byId: {}, + allIds: [], + oldestCallTime: now, + newestCallTime: now, + filterType: 0, + filterTalkgroups: [], + filterGroupId: false, + filterDate: false, + filterStarred: false, + currentCallId: false, + live: false +} + +export const callPlayerSlice = createSlice({ + name: 'callPlayer', + initialState, + reducers: { + setCurrentCallId: (state, action) => { + state.currentCallId = action.payload; + }, + setCallTime: (state, action) => { + state.newestCallTime = action.payload; + state.oldestCallTime = action.payload; + }, + setFilter: (state,action) => { + var filter={}; + state.live = !action.payload.live ? state.live : action.payload.live; + state.filterStarred = !action.payload.filterStarred ? false : action.payload.filterStarred; + state.filterType = !action.payload.filterType ? 0 : action.payload.filterType; + state.filterTalkgroups = !action.payload.filterTalkgroups ? [] : action.payload.filterTalkgroups; + state.filterGroupId = !action.payload.filterGroupId ? false : action.payload.filterGroupId; + state.filterDate = !action.payload.filterDate ? 0 : action.payload.filterDate; + }, + setLive: (state,action) => { + state.live = action.payload; + }, + setShortName: (state, action) => { + state.shortName = action.payload; + }, + setDateFilter: (state, action) => { + state.filterDate = action.payload; + }, + setStarredFilter: (state, action) => { + state.filterStarred = action.payload; + }, + setAllFilter: (state) => { + state.filterType = 0; + state.filterTalkgroups = []; + state.filterGroupId = 0; + }, + setGroupFilter: (state, action) => { + state.filterType = 1; + state.filterTalkgroups = []; + state.filterGroupId = action.payload; + }, + setTalkgroupFilter: (state, action) => { + state.filterType = 2; + state.filterTalkgroups = action.payload; + state.filterGroupId = 0; + }, + + }, +}) + +// Action creators are generated for each case reducer function +export const { setCurrentCallId, setCallTime, setFilter, setLive, setShortName, setDateFilter, setStarredFilter, setAllFilter, setGroupFilter, setTalkgroupFilter } = callPlayerSlice.actions + +export default callPlayerSlice.reducer + + + diff --git a/frontend/src/redux-router/configureStore.js b/frontend/src/redux-router/configureStore.js index a825832..125bb4f 100644 --- a/frontend/src/redux-router/configureStore.js +++ b/frontend/src/redux-router/configureStore.js @@ -6,6 +6,7 @@ import { applyMiddleware, compose, createStore } from 'redux' import { routerMiddleware } from 'connected-react-router' import createRootReducer from './reducers' import { apiSlice } from '../features/api/apiSlice' +import {callPlayerSlice} from "../features/callPlayer/callPlayerSlice" import thunk from 'redux-thunk'; @@ -20,7 +21,7 @@ export default function configureStore(preloadedState) { applyMiddleware( routerMiddleware(history), // for dispatching history actions thunk, - apiSlice.middleware + apiSlice.middleware, // ... other middlewares ... ), ), diff --git a/frontend/src/redux-router/reducers.js b/frontend/src/redux-router/reducers.js index 209e0c6..c1bf77a 100644 --- a/frontend/src/redux-router/reducers.js +++ b/frontend/src/redux-router/reducers.js @@ -7,12 +7,16 @@ import talkgroupReducer from "../Talkgroups/talkgroup-reducer" import groupReducer from "../Group/group-reducer" import callReducer from "../Call/call-reducer" import {apiSlice} from "../features/api/apiSlice" +import callPlayerSlice from "../features/callPlayer/callPlayerSlice" export default (history) => combineReducers({ router: connectRouter(history), system: systemReducer, talkgroup: talkgroupReducer, group: groupReducer, + callPlayer: callPlayerSlice, call: callReducer, - [apiSlice.reducerPath]: apiSlice.reducer + + [apiSlice.reducerPath]: apiSlice.reducer, + }) \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index eaea32a..4d8363c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -29,6 +29,752 @@ resolved "https://registry.yarnpkg.com/@artsy/fresnel/-/fresnel-6.1.0.tgz#fac8aeb6a01cbd7791351dce523f5ab5e2fc8679" integrity sha512-PexfFmf3Un3C752SFpryYROgahfdSsHhNETSZlJcO0W3MJ14hyw/MbLQl3DnNdQGIJZE6y1MbnH7ntEt8PWB0Q== +"@aws-crypto/ie11-detection@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz#9c39f4a5558196636031a933ec1b4792de959d6a" + integrity sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw== + dependencies: + tslib "^1.11.1" + +"@aws-crypto/sha256-browser@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz#741c9024df55ec59b51e5b1f5d806a4852699fb5" + integrity sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A== + dependencies: + "@aws-crypto/ie11-detection" "^2.0.0" + "@aws-crypto/sha256-js" "^2.0.0" + "@aws-crypto/supports-web-crypto" "^2.0.0" + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-crypto/sha256-js@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz#f1f936039bdebd0b9e2dd834d65afdc2aac4efcb" + integrity sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig== + dependencies: + "@aws-crypto/util" "^2.0.0" + "@aws-sdk/types" "^3.1.0" + tslib "^1.11.1" + +"@aws-crypto/sha256-js@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-2.0.2.tgz#c81e5d378b8a74ff1671b58632779986e50f4c99" + integrity sha512-iXLdKH19qPmIC73fVCrHWCSYjN/sxaAvZ3jNNyw6FclmHyjLKg0f69WlC9KTnyElxCR5MO9SKaG00VwlJwyAkQ== + dependencies: + "@aws-crypto/util" "^2.0.2" + "@aws-sdk/types" "^3.110.0" + tslib "^1.11.1" + +"@aws-crypto/supports-web-crypto@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz#9f02aafad8789cac9c0ab5faaebb1ab8aa841338" + integrity sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ== + dependencies: + tslib "^1.11.1" + +"@aws-crypto/util@^2.0.0", "@aws-crypto/util@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-2.0.2.tgz#adf5ff5dfbc7713082f897f1d01e551ce0edb9c0" + integrity sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA== + dependencies: + "@aws-sdk/types" "^3.110.0" + "@aws-sdk/util-utf8-browser" "^3.0.0" + tslib "^1.11.1" + +"@aws-sdk/abort-controller@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/abort-controller/-/abort-controller-3.226.0.tgz#3adffb8ee5af57ddb154e8544a8eeec76ad32271" + integrity sha512-cJVzr1xxPBd08voknXvR0RLgtZKGKt6WyDpH/BaPCu3rfSqWCDZKzwqe940eqosjmKrxC6pUZNKASIqHOQ8xxQ== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/client-cognito-identity@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.241.0.tgz#6395e097a49433c75bd40a45aa94c6d8f8a263c3" + integrity sha512-9X/MwcnSwWfB0ijggFjyBWa4gtlUAyI39eBaVSE0AxMcgLlHKedEK6w5F1RrtvWqb7KyJDsyAysVecU4E9zQQQ== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/client-sts" "3.241.0" + "@aws-sdk/config-resolver" "3.234.0" + "@aws-sdk/credential-provider-node" "3.241.0" + "@aws-sdk/fetch-http-handler" "3.226.0" + "@aws-sdk/hash-node" "3.226.0" + "@aws-sdk/invalid-dependency" "3.226.0" + "@aws-sdk/middleware-content-length" "3.226.0" + "@aws-sdk/middleware-endpoint" "3.226.0" + "@aws-sdk/middleware-host-header" "3.226.0" + "@aws-sdk/middleware-logger" "3.226.0" + "@aws-sdk/middleware-recursion-detection" "3.226.0" + "@aws-sdk/middleware-retry" "3.235.0" + "@aws-sdk/middleware-serde" "3.226.0" + "@aws-sdk/middleware-signing" "3.226.0" + "@aws-sdk/middleware-stack" "3.226.0" + "@aws-sdk/middleware-user-agent" "3.226.0" + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/node-http-handler" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/smithy-client" "3.234.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + "@aws-sdk/util-base64" "3.208.0" + "@aws-sdk/util-body-length-browser" "3.188.0" + "@aws-sdk/util-body-length-node" "3.208.0" + "@aws-sdk/util-defaults-mode-browser" "3.234.0" + "@aws-sdk/util-defaults-mode-node" "3.234.0" + "@aws-sdk/util-endpoints" "3.241.0" + "@aws-sdk/util-retry" "3.229.0" + "@aws-sdk/util-user-agent-browser" "3.226.0" + "@aws-sdk/util-user-agent-node" "3.226.0" + "@aws-sdk/util-utf8-browser" "3.188.0" + "@aws-sdk/util-utf8-node" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/client-sso-oidc@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.241.0.tgz#409e61d9cd4f5c3fe2820b69c808af6bb51beded" + integrity sha512-/Ml2QBGpGfUEeBrPzBZhSTBkHuXFD2EAZEIHGCBH4tKaURDI6/FoGI8P1Rl4BzoFt+II/Cr91Eox6YT9EwChsQ== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.234.0" + "@aws-sdk/fetch-http-handler" "3.226.0" + "@aws-sdk/hash-node" "3.226.0" + "@aws-sdk/invalid-dependency" "3.226.0" + "@aws-sdk/middleware-content-length" "3.226.0" + "@aws-sdk/middleware-endpoint" "3.226.0" + "@aws-sdk/middleware-host-header" "3.226.0" + "@aws-sdk/middleware-logger" "3.226.0" + "@aws-sdk/middleware-recursion-detection" "3.226.0" + "@aws-sdk/middleware-retry" "3.235.0" + "@aws-sdk/middleware-serde" "3.226.0" + "@aws-sdk/middleware-stack" "3.226.0" + "@aws-sdk/middleware-user-agent" "3.226.0" + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/node-http-handler" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/smithy-client" "3.234.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + "@aws-sdk/util-base64" "3.208.0" + "@aws-sdk/util-body-length-browser" "3.188.0" + "@aws-sdk/util-body-length-node" "3.208.0" + "@aws-sdk/util-defaults-mode-browser" "3.234.0" + "@aws-sdk/util-defaults-mode-node" "3.234.0" + "@aws-sdk/util-endpoints" "3.241.0" + "@aws-sdk/util-retry" "3.229.0" + "@aws-sdk/util-user-agent-browser" "3.226.0" + "@aws-sdk/util-user-agent-node" "3.226.0" + "@aws-sdk/util-utf8-browser" "3.188.0" + "@aws-sdk/util-utf8-node" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/client-sso@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.241.0.tgz#b50a2539ba4ed162270c9fb4b59fb6334e8025a6" + integrity sha512-Jm4HR+RYAqKMEYZvvWaq0NYUKKonyInOeubObXH4BLXZpmUBSdYCSjjLdNJY3jkQoxbDVPVMIurVNh5zT5SMRw== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.234.0" + "@aws-sdk/fetch-http-handler" "3.226.0" + "@aws-sdk/hash-node" "3.226.0" + "@aws-sdk/invalid-dependency" "3.226.0" + "@aws-sdk/middleware-content-length" "3.226.0" + "@aws-sdk/middleware-endpoint" "3.226.0" + "@aws-sdk/middleware-host-header" "3.226.0" + "@aws-sdk/middleware-logger" "3.226.0" + "@aws-sdk/middleware-recursion-detection" "3.226.0" + "@aws-sdk/middleware-retry" "3.235.0" + "@aws-sdk/middleware-serde" "3.226.0" + "@aws-sdk/middleware-stack" "3.226.0" + "@aws-sdk/middleware-user-agent" "3.226.0" + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/node-http-handler" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/smithy-client" "3.234.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + "@aws-sdk/util-base64" "3.208.0" + "@aws-sdk/util-body-length-browser" "3.188.0" + "@aws-sdk/util-body-length-node" "3.208.0" + "@aws-sdk/util-defaults-mode-browser" "3.234.0" + "@aws-sdk/util-defaults-mode-node" "3.234.0" + "@aws-sdk/util-endpoints" "3.241.0" + "@aws-sdk/util-retry" "3.229.0" + "@aws-sdk/util-user-agent-browser" "3.226.0" + "@aws-sdk/util-user-agent-node" "3.226.0" + "@aws-sdk/util-utf8-browser" "3.188.0" + "@aws-sdk/util-utf8-node" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/client-sts@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.241.0.tgz#50568ac59e003ae6190221162f337df2500028ff" + integrity sha512-vmlG8cJzRf8skCtTJbA2wBvD2c3NQ5gZryzJvTKDS06KzBzcEpnjlLseuTekcnOiRNekbFUX5hRu5Zj3N2ReLg== + dependencies: + "@aws-crypto/sha256-browser" "2.0.0" + "@aws-crypto/sha256-js" "2.0.0" + "@aws-sdk/config-resolver" "3.234.0" + "@aws-sdk/credential-provider-node" "3.241.0" + "@aws-sdk/fetch-http-handler" "3.226.0" + "@aws-sdk/hash-node" "3.226.0" + "@aws-sdk/invalid-dependency" "3.226.0" + "@aws-sdk/middleware-content-length" "3.226.0" + "@aws-sdk/middleware-endpoint" "3.226.0" + "@aws-sdk/middleware-host-header" "3.226.0" + "@aws-sdk/middleware-logger" "3.226.0" + "@aws-sdk/middleware-recursion-detection" "3.226.0" + "@aws-sdk/middleware-retry" "3.235.0" + "@aws-sdk/middleware-sdk-sts" "3.226.0" + "@aws-sdk/middleware-serde" "3.226.0" + "@aws-sdk/middleware-signing" "3.226.0" + "@aws-sdk/middleware-stack" "3.226.0" + "@aws-sdk/middleware-user-agent" "3.226.0" + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/node-http-handler" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/smithy-client" "3.234.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + "@aws-sdk/util-base64" "3.208.0" + "@aws-sdk/util-body-length-browser" "3.188.0" + "@aws-sdk/util-body-length-node" "3.208.0" + "@aws-sdk/util-defaults-mode-browser" "3.234.0" + "@aws-sdk/util-defaults-mode-node" "3.234.0" + "@aws-sdk/util-endpoints" "3.241.0" + "@aws-sdk/util-retry" "3.229.0" + "@aws-sdk/util-user-agent-browser" "3.226.0" + "@aws-sdk/util-user-agent-node" "3.226.0" + "@aws-sdk/util-utf8-browser" "3.188.0" + "@aws-sdk/util-utf8-node" "3.208.0" + fast-xml-parser "4.0.11" + tslib "^2.3.1" + +"@aws-sdk/config-resolver@3.234.0": + version "3.234.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/config-resolver/-/config-resolver-3.234.0.tgz#29d8936b713b7ee59b26b335d4f6715d644fc089" + integrity sha512-uZxy4wzllfvgCQxVc+Iqhde0NGAnfmV2hWR6ejadJaAFTuYNvQiRg9IqJy3pkyDPqXySiJ8Bom5PoJfgn55J/A== + dependencies: + "@aws-sdk/signature-v4" "3.226.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-config-provider" "3.208.0" + "@aws-sdk/util-middleware" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-cognito-identity@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.241.0.tgz#5fca08a74b42e7df1aa623bbab3bd61920aa6f52" + integrity sha512-e2hlXWG9DH93uVe2wHIUrUOrgZTLzCV3gBd10D3/usSzS4FvVVU7OmidnRPYCLLnt3EvnL5b4REOedO1q8hv8g== + dependencies: + "@aws-sdk/client-cognito-identity" "3.241.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-env@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.226.0.tgz#0bcb89a9abc166b3a48f5c255b9fcabc4cb80daf" + integrity sha512-sd8uK1ojbXxaZXlthzw/VXZwCPUtU3PjObOfr3Evj7MPIM2IH8h29foOlggx939MdLQGboJf9gKvLlvKDWtJRA== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-imds@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.226.0.tgz#0a4558449eb261412b0490ea1c3242eb91659759" + integrity sha512-//z/COQm2AjYFI1Lb0wKHTQSrvLFTyuKLFQGPJsKS7DPoxGOCKB7hmYerlbl01IDoCxTdyL//TyyPxbZEOQD5Q== + dependencies: + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-ini@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.241.0.tgz#75a39aa9db1e6351fb8d76b485c419c659017e23" + integrity sha512-CI+mu6h74Kzmscw35TvNkc/wYHsHPGAwP7humSHoWw53H9mVw21Ggft/dT1iFQQZWQ8BNXkzuXlNo1IlqwMgOA== + dependencies: + "@aws-sdk/credential-provider-env" "3.226.0" + "@aws-sdk/credential-provider-imds" "3.226.0" + "@aws-sdk/credential-provider-process" "3.226.0" + "@aws-sdk/credential-provider-sso" "3.241.0" + "@aws-sdk/credential-provider-web-identity" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-node@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.241.0.tgz#8cd1ad5f6ad3ca7c9918751ffc45ebdabc055274" + integrity sha512-08zPQcD5o9brQmzEipWHeHgU85aQcEF8MWLfpeyjO6e1/l7ysQ35NsS+PYtv77nLpGCx/X+ZuW/KXWoRrbw77w== + dependencies: + "@aws-sdk/credential-provider-env" "3.226.0" + "@aws-sdk/credential-provider-imds" "3.226.0" + "@aws-sdk/credential-provider-ini" "3.241.0" + "@aws-sdk/credential-provider-process" "3.226.0" + "@aws-sdk/credential-provider-sso" "3.241.0" + "@aws-sdk/credential-provider-web-identity" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-process@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.226.0.tgz#bcd73a6d31d1b3181917d56e54aacbee242b077f" + integrity sha512-iUDMdnrTvbvaCFhWwqyXrhvQ9+ojPqPqXhwZtY1X/Qaz+73S9gXBPJHZaZb2Ke0yKE1Ql3bJbKvmmxC/qLQMng== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-sso@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.241.0.tgz#d424402cab7993a03f08174bf150ed0b38a916d2" + integrity sha512-6Bjd6eEIrVomRTrPrM4dlxusQm+KMJ9hLYKECCpFkwDKIK+pTgZNLRtQdalHyzwneHJPdimrm8cOv1kUQ8hPoA== + dependencies: + "@aws-sdk/client-sso" "3.241.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/token-providers" "3.241.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-provider-web-identity@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.226.0.tgz#2b7d20f93a40e2243c7e3857f54b103d19a946fb" + integrity sha512-CCpv847rLB0SFOHz2igvUMFAzeT2fD3YnY4C8jltuJoEkn0ITn1Hlgt13nTJ5BUuvyti2mvyXZHmNzhMIMrIlw== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/credential-providers@^3.186.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.241.0.tgz#0cd92721070ccef13ad0d496cacea859eebe2093" + integrity sha512-J3Q45t1o35OhUI6gWks7rmosPT+mFWXiaHl2LST509Ovjwx6SFs2PvbGP6n7xqUzxyq5Rk6FzZBwB8ItuAa6Qw== + dependencies: + "@aws-sdk/client-cognito-identity" "3.241.0" + "@aws-sdk/client-sso" "3.241.0" + "@aws-sdk/client-sts" "3.241.0" + "@aws-sdk/credential-provider-cognito-identity" "3.241.0" + "@aws-sdk/credential-provider-env" "3.226.0" + "@aws-sdk/credential-provider-imds" "3.226.0" + "@aws-sdk/credential-provider-ini" "3.241.0" + "@aws-sdk/credential-provider-node" "3.241.0" + "@aws-sdk/credential-provider-process" "3.226.0" + "@aws-sdk/credential-provider-sso" "3.241.0" + "@aws-sdk/credential-provider-web-identity" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/fetch-http-handler@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.226.0.tgz#350f78fc18fe9cb0a889ef4870838a8fcfa8855c" + integrity sha512-JewZPMNEBXfi1xVnRa7pVtK/zgZD8/lQ/YnD8pq79WuMa2cwyhDtr8oqCoqsPW+WJT5ScXoMtuHxN78l8eKWgg== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/querystring-builder" "3.226.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-base64" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/hash-node@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/hash-node/-/hash-node-3.226.0.tgz#252d98bcbb1e13c8f26d9d416db03cf8cceac185" + integrity sha512-MdlJhJ9/Espwd0+gUXdZRsHuostB2WxEVAszWxobP0FTT9PnicqnfK7ExmW+DUAc0ywxtEbR3e0UND65rlSTVw== + dependencies: + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-buffer-from" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/invalid-dependency@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/invalid-dependency/-/invalid-dependency-3.226.0.tgz#74586f60859ed1813985e3d642066cc46d2e9d40" + integrity sha512-QXOYFmap8g9QzRjumcRCIo2GEZkdCwd7ePQW0OABWPhKHzlJ74vvBxywjU3s39EEBEluWXtZ7Iufg6GxZM4ifw== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/is-array-buffer@3.201.0": + version "3.201.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz#06e557adc284fac2f26071c2944ae01f61b95854" + integrity sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/middleware-content-length@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-content-length/-/middleware-content-length-3.226.0.tgz#6cc952049f6e3cdc3a3778c9dce9f2aee942b5fe" + integrity sha512-ksUzlHJN2JMuyavjA46a4sctvnrnITqt2tbGGWWrAuXY1mel2j+VbgnmJUiwHKUO6bTFBBeft5Vd1TSOb4JmiA== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-endpoint@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.226.0.tgz#d776480be4b5a9534c2805b7425be05497f840b7" + integrity sha512-EvLFafjtUxTT0AC9p3aBQu1/fjhWdIeK58jIXaNFONfZ3F8QbEYUPuF/SqZvJM6cWfOO9qwYKkRDbCSTYhprIg== + dependencies: + "@aws-sdk/middleware-serde" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/signature-v4" "3.226.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/url-parser" "3.226.0" + "@aws-sdk/util-config-provider" "3.208.0" + "@aws-sdk/util-middleware" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-host-header@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.226.0.tgz#1e1ecb034929e0dbc532ae501fd93781438f9a24" + integrity sha512-haVkWVh6BUPwKgWwkL6sDvTkcZWvJjv8AgC8jiQuSl8GLZdzHTB8Qhi3IsfFta9HAuoLjxheWBE5Z/L0UrfhLA== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-logger@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.226.0.tgz#37fd0e62f555befd526b03748c3aab60dcefecf3" + integrity sha512-m9gtLrrYnpN6yckcQ09rV7ExWOLMuq8mMPF/K3DbL/YL0TuILu9i2T1W+JuxSX+K9FMG2HrLAKivE/kMLr55xA== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-recursion-detection@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.226.0.tgz#e149b9138e94d2fa70e7752ba6b1ccb537009706" + integrity sha512-mwRbdKEUeuNH5TEkyZ5FWxp6bL2UC1WbY+LDv6YjHxmSMKpAoOueEdtU34PqDOLrpXXxIGHDFmjeGeMfktyEcA== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-retry@3.235.0": + version "3.235.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-retry/-/middleware-retry-3.235.0.tgz#c0d938db85a771812204ed5e981eaf5eef6b580b" + integrity sha512-50WHbJGpD3SNp9763MAlHqIhXil++JdQbKejNpHg7HsJne/ao3ub+fDOfx//mMBjpzBV25BGd5UlfL6blrClSg== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/service-error-classification" "3.229.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-middleware" "3.226.0" + "@aws-sdk/util-retry" "3.229.0" + tslib "^2.3.1" + uuid "^8.3.2" + +"@aws-sdk/middleware-sdk-sts@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.226.0.tgz#e8a8cf42bba8963259546120cde1e408628863f9" + integrity sha512-NN9T/qoSD1kZvAT+VLny3NnlqgylYQcsgV3rvi/8lYzw/G/2s8VS6sm/VTWGGZhx08wZRv20MWzYu3bftcyqUg== + dependencies: + "@aws-sdk/middleware-signing" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/signature-v4" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-serde@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-serde/-/middleware-serde-3.226.0.tgz#c837ef33b34bec2af19a1c177a0c02a1ae20da5e" + integrity sha512-nPuOOAkSfx9TxzdKFx0X2bDlinOxGrqD7iof926K/AEflxGD1DBdcaDdjlYlPDW2CVE8LV/rAgbYuLxh/E/1VA== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-signing@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.226.0.tgz#ebb1d142ac2767466f2e464bb7dba9837143b4d1" + integrity sha512-E6HmtPcl+IjYDDzi1xI2HpCbBq2avNWcjvCriMZWuTAtRVpnA6XDDGW5GY85IfS3A8G8vuWqEVPr8JcYUcjfew== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/signature-v4" "3.226.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-middleware" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/middleware-stack@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-stack/-/middleware-stack-3.226.0.tgz#b0408370270188103987c457c758f9cf7651754f" + integrity sha512-85wF29LvPvpoed60fZGDYLwv1Zpd/cM0C22WSSFPw1SSJeqO4gtFYyCg2squfT3KI6kF43IIkOCJ+L7GtryPug== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/middleware-user-agent@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.226.0.tgz#26653189f3e8da86514f77688a80d0ad445c0799" + integrity sha512-N1WnfzCW1Y5yWhVAphf8OPGTe8Df3vmV7/LdsoQfmpkCZgLZeK2o0xITkUQhRj1mbw7yp8tVFLFV3R2lMurdAQ== + dependencies: + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/node-config-provider@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-config-provider/-/node-config-provider-3.226.0.tgz#a9e21512ef824142bb928a0b2f85b39a75b8964d" + integrity sha512-B8lQDqiRk7X5izFEUMXmi8CZLOKCTWQJU9HQf3ako+sF0gexo4nHN3jhoRWyLtcgC5S3on/2jxpAcqtm7kuY3w== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/node-http-handler@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/node-http-handler/-/node-http-handler-3.226.0.tgz#373886e949d214a99a3521bd6c141fa17b0e89fe" + integrity sha512-xQCddnZNMiPmjr3W7HYM+f5ir4VfxgJh37eqZwX6EZmyItFpNNeVzKUgA920ka1VPz/ZUYB+2OFGiX3LCLkkaA== + dependencies: + "@aws-sdk/abort-controller" "3.226.0" + "@aws-sdk/protocol-http" "3.226.0" + "@aws-sdk/querystring-builder" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/property-provider@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/property-provider/-/property-provider-3.226.0.tgz#ef0ff37c319dc37a52f08fa7544f861308a3bbd8" + integrity sha512-TsljjG+Sg0LmdgfiAlWohluWKnxB/k8xenjeozZfzOr5bHmNHtdbWv6BtNvD/R83hw7SFXxbJHlD5H4u9p2NFg== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/protocol-http@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/protocol-http/-/protocol-http-3.226.0.tgz#0af7bdc331508e556b722aad0cb78eefa93466e3" + integrity sha512-zWkVqiTA9RXL6y0hhfZc9bcU4DX2NI6Hw9IhQmSPeM59mdbPjJlY4bLlMr5YxywqO3yQ/ylNoAfrEzrDjlOSRg== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/querystring-builder@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-builder/-/querystring-builder-3.226.0.tgz#11cd751abeac66f1f9349225454bac3e39808926" + integrity sha512-LVurypuNeotO4lmirKXRC4NYrZRAyMJXuwO0f2a5ZAUJCjauwYrifKue6yCfU7bls7gut7nfcR6B99WBYpHs3g== + dependencies: + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-uri-escape" "3.201.0" + tslib "^2.3.1" + +"@aws-sdk/querystring-parser@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/querystring-parser/-/querystring-parser-3.226.0.tgz#ba6a26727c98d46c95180e6cdc463039c5e4740d" + integrity sha512-FzB+VrQ47KAFxiPt2YXrKZ8AOLZQqGTLCKHzx4bjxGmwgsjV8yIbtJiJhZLMcUQV4LtGeIY9ixIqQhGvnZHE4A== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/service-error-classification@3.229.0": + version "3.229.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/service-error-classification/-/service-error-classification-3.229.0.tgz#768f1eb92775ca2cc99c6451a2303a0008a28fc1" + integrity sha512-dnzWWQ0/NoWMUZ5C0DW3dPm0wC1O76Y/SpKbuJzWPkx1EYy6r8p32Ly4D9vUzrKDbRGf48YHIF2kOkBmu21CLg== + +"@aws-sdk/shared-ini-file-loader@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.226.0.tgz#d0ade86834b1803ce4b9dcab459e57e0376fd6cf" + integrity sha512-661VQefsARxVyyV2FX9V61V+nNgImk7aN2hYlFKla6BCwZfMng+dEtD0xVGyg1PfRw0qvEv5LQyxMVgHcUSevA== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/signature-v4@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4/-/signature-v4-3.226.0.tgz#100390b5c5b55a9b0abd05b06fceb36cfa0ecf98" + integrity sha512-/R5q5agdPd7HJB68XMzpxrNPk158EHUvkFkuRu5Qf3kkkHebEzWEBlWoVpUe6ss4rP9Tqcue6xPuaftEmhjpYw== + dependencies: + "@aws-sdk/is-array-buffer" "3.201.0" + "@aws-sdk/types" "3.226.0" + "@aws-sdk/util-hex-encoding" "3.201.0" + "@aws-sdk/util-middleware" "3.226.0" + "@aws-sdk/util-uri-escape" "3.201.0" + tslib "^2.3.1" + +"@aws-sdk/smithy-client@3.234.0": + version "3.234.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/smithy-client/-/smithy-client-3.234.0.tgz#8f0021e021f0e52730ed0a8f271f839eb63bc374" + integrity sha512-8AtR/k4vsFvjXeQbIzq/Wy7Nbk48Ou0wUEeVYPHWHPSU8QamFWORkOwmKtKMfHAyZvmqiAPeQqHFkq+UJhWyyQ== + dependencies: + "@aws-sdk/middleware-stack" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/token-providers@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.241.0.tgz#f3f01fc69cf88b9122dad360b099982aa5edc71a" + integrity sha512-79okvuOS7V559OIL/RalIPG98wzmWxeFOChFnbEjn2pKOyGQ6FJRwLPYZaVRtNdAtnkBNgRpmFq9dX843QxhtQ== + dependencies: + "@aws-sdk/client-sso-oidc" "3.241.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/shared-ini-file-loader" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/types@3.226.0", "@aws-sdk/types@^3.1.0", "@aws-sdk/types@^3.110.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.226.0.tgz#3dba2ba223fbb8ac1ebc84de0e036ce69a81d469" + integrity sha512-MmmNHrWeO4man7wpOwrAhXlevqtOV9ZLcH4RhnG5LmRce0RFOApx24HoKENfFCcOyCm5LQBlsXCqi0dZWDWU0A== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/url-parser@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/url-parser/-/url-parser-3.226.0.tgz#f53d1f868b27fe74aca091a799f2af56237b15a2" + integrity sha512-p5RLE0QWyP0OcTOLmFcLdVgUcUEzmEfmdrnOxyNzomcYb0p3vUagA5zfa1HVK2azsQJFBv28GfvMnba9bGhObg== + dependencies: + "@aws-sdk/querystring-parser" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/util-base64@3.208.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz#36b430e5396251f761590f7c2f0c5c12193f353c" + integrity sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg== + dependencies: + "@aws-sdk/util-buffer-from" "3.208.0" + tslib "^2.3.1" + +"@aws-sdk/util-body-length-browser@3.188.0": + version "3.188.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz#e1d949318c10a621b38575a9ef01e39f9857ddb0" + integrity sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-body-length-node@3.208.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz#baabd1fa1206ff2bd4ce3785122d86eb3258dd20" + integrity sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-buffer-from@3.208.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz#285e86f6dc9030148a4147d65239e75cb254a1b0" + integrity sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw== + dependencies: + "@aws-sdk/is-array-buffer" "3.201.0" + tslib "^2.3.1" + +"@aws-sdk/util-config-provider@3.208.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz#c485fd83fbac051337e5f6be60ea3f9fa61c0139" + integrity sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-defaults-mode-browser@3.234.0": + version "3.234.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.234.0.tgz#1151f0beabdb46c1aaca42a1ad0714b8e686acaa" + integrity sha512-IHMKXjTbOD8XMz5+2oCOsVP94BYb9YyjXdns0aAXr2NAo7k2+RCzXQ2DebJXppGda1F6opFutoKwyVSN0cmbMw== + dependencies: + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + bowser "^2.11.0" + tslib "^2.3.1" + +"@aws-sdk/util-defaults-mode-node@3.234.0": + version "3.234.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.234.0.tgz#0607f1dc7a4dc896dfcaf377522535ca9ffba7a9" + integrity sha512-UGjQ+OjBYYhxFVtUY+jtr0ZZgzZh6OHtYwRhFt8IHewJXFCfZTyfsbX20szBj5y1S4HRIUJ7cwBLIytTqMbI5w== + dependencies: + "@aws-sdk/config-resolver" "3.234.0" + "@aws-sdk/credential-provider-imds" "3.226.0" + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/property-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/util-endpoints@3.241.0": + version "3.241.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.241.0.tgz#dc2bc026b5afa437b4e49057412884020354b805" + integrity sha512-jVf8bKrN22Ey0xLmj75sL7EUvm5HFpuOMkXsZkuXycKhCwLBcEUWlvtJYtRjOU1zScPQv9GMJd2QXQglp34iOQ== + dependencies: + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/util-hex-encoding@3.201.0": + version "3.201.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz#21d7ec319240ee68c33d938e71cb79830bea315d" + integrity sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz#0f598fc238a1256e4bcb64d01459f03a922dd4c3" + integrity sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-middleware@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-middleware/-/util-middleware-3.226.0.tgz#7069ae96e2e00f6bb82c722e073922fb2b051ca2" + integrity sha512-B96CQnwX4gRvQdaQkdUtqvDPkrptV5+va6FVeJOocU/DbSYMAScLxtR3peMS8cnlOT6nL1Eoa42OI9AfZz1VwQ== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-retry@3.229.0": + version "3.229.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-retry/-/util-retry-3.229.0.tgz#17aad47b067e81acf644d5c2c0f2325f2d8faf4f" + integrity sha512-0zKTqi0P1inD0LzIMuXRIYYQ/8c1lWMg/cfiqUcIAF1TpatlpZuN7umU0ierpBFud7S+zDgg0oemh+Nj8xliJw== + dependencies: + "@aws-sdk/service-error-classification" "3.229.0" + tslib "^2.3.1" + +"@aws-sdk/util-uri-escape@3.201.0": + version "3.201.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz#5e708d4cde001a4558ee616f889ceacfadd2ab03" + integrity sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-user-agent-browser@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.226.0.tgz#164bb2da8d6353133784e47f0a0ae463bc9ebb73" + integrity sha512-PhBIu2h6sPJPcv2I7ELfFizdl5pNiL4LfxrasMCYXQkJvVnoXztHA1x+CQbXIdtZOIlpjC+6BjDcE0uhnpvfcA== + dependencies: + "@aws-sdk/types" "3.226.0" + bowser "^2.11.0" + tslib "^2.3.1" + +"@aws-sdk/util-user-agent-node@3.226.0": + version "3.226.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.226.0.tgz#7569460b9efc6bbd5295275c51357e480ff469c2" + integrity sha512-othPc5Dz/pkYkxH+nZPhc1Al0HndQT8zHD4e9h+EZ+8lkd8n+IsnLfTS/mSJWrfiC6UlNRVw55cItstmJyMe/A== + dependencies: + "@aws-sdk/node-config-provider" "3.226.0" + "@aws-sdk/types" "3.226.0" + tslib "^2.3.1" + +"@aws-sdk/util-utf8-browser@3.188.0", "@aws-sdk/util-utf8-browser@^3.0.0": + version "3.188.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz#484762bd600401350e148277731d6744a4a92225" + integrity sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q== + dependencies: + tslib "^2.3.1" + +"@aws-sdk/util-utf8-node@3.208.0": + version "3.208.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-node/-/util-utf8-node-3.208.0.tgz#eba17de0f92f87b98481c2e2d0ceaa05c7994d67" + integrity sha512-jKY87Acv0yWBdFxx6bveagy5FYjz+dtV8IPT7ay1E2WPWH1czoIdMAkc8tSInK31T6CRnHWkLZ1qYwCbgRfERQ== + dependencies: + "@aws-sdk/util-buffer-from" "3.208.0" + tslib "^2.3.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.8.3": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -2154,6 +2900,19 @@ resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== +"@types/webidl-conversions@*": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" + integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -2866,6 +3625,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -2929,6 +3693,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2973,11 +3742,26 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +bson@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.0.tgz#7874a60091ffc7a45c5dd2973b5cad7cded9718a" + integrity sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA== + dependencies: + buffer "^5.6.0" + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -4424,6 +5208,13 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-xml-parser@4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz#42332a9aca544520631c8919e6ea871c0185a985" + integrity sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA== + dependencies: + strnum "^1.0.5" + fastq@^1.6.0: version "1.14.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.14.0.tgz#107f69d7295b11e0fccc264e1fc6389f623731ce" @@ -5032,6 +5823,11 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -5105,6 +5901,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -6288,6 +7089,11 @@ memfs@^3.1.2, memfs@^3.4.3: dependencies: fs-monkey "^1.0.3" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6381,6 +7187,26 @@ mkdirp@~0.5.1: dependencies: minimist "^1.2.6" +mongodb-connection-string-url@^2.5.4: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz#57901bf352372abdde812c81be47b75c6b2ec5cf" + integrity sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.13.0.tgz#2aa832b827e2891eb2e52e8235c201cbb4701ed2" + integrity sha512-+taZ/bV8d1pYuHL4U+gSwkhmDrwkWbH1l4aah4YpmpscMwgFBkufIKxgP/G7m87/NUuQzc2Z75ZTI7ZOyqZLbw== + dependencies: + bson "^4.7.0" + mongodb-connection-string-url "^2.5.4" + socks "^2.7.1" + optionalDependencies: + "@aws-sdk/credential-providers" "^3.186.0" + saslprep "^1.0.3" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8014,6 +8840,13 @@ sanitize.css@*: resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-13.0.0.tgz#2675553974b27964c75562ade3bd85d79879f173" integrity sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA== +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + sass-loader@^12.3.0: version "12.6.0" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" @@ -8250,6 +9083,11 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -8259,6 +9097,14 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" +socks@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -8308,6 +9154,13 @@ sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -8494,6 +9347,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + style-loader@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" @@ -8736,6 +9594,13 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -8751,12 +9616,12 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.11.1, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: +tslib@^2.0.3, tslib@^2.3.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== @@ -9012,6 +9877,11 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-dev-middleware@^5.3.1: version "5.3.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" @@ -9148,6 +10018,14 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" From 9205224f23ecbcd6ec9651c17af8699441ff8d98 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 5 Jan 2023 21:17:13 -0500 Subject: [PATCH 09/27] Call Info and Cal Modals converted --- frontend/src/Call/CalendarModal.js | 100 ++++++++++++++--------------- frontend/src/Call/CallInfo.js | 34 +++++----- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/frontend/src/Call/CalendarModal.js b/frontend/src/Call/CalendarModal.js index 112f7fe..f733422 100644 --- a/frontend/src/Call/CalendarModal.js +++ b/frontend/src/Call/CalendarModal.js @@ -1,70 +1,64 @@ -import React, { Component } from "react"; +import React, { Component, useState } from "react"; import { Modal, Button, Icon } from "semantic-ui-react"; import "./CalendarModal.css"; +import { setDateFilter } from "../features/callPlayer/callPlayerSlice"; import DatePicker from 'react-datepicker'; -import { subDays } from 'date-fns' +import { useDispatch } from 'react-redux' +import { setDate, subDays } from 'date-fns' import 'react-datepicker/dist/react-datepicker.css'; -class CalendarModal extends Component { - constructor(props) { - super(props) - this.handleDateChange = this.handleDateChange.bind(this); - this.handleDone = this.handleDone.bind(this); - this.handleClose = this.handleClose.bind(this); +function CalendarModal(props) { - this.state = { - open: false, - startDate: new Date() - } -} + const [open, setOpen] = useState(false); + const [startDate, setStartDate] = useState(new Date()); + const dispatch = useDispatch() + const onClose = props.onClose; -handleDateChange = (date) => { - this.setState({ - startDate: date - }); + const handleDateChange = (date) => { + setStartDate(date); } -handleClose = () => this.props.onClose(false); -handleDone(event) { - this.props.callActions.setDateFilter(this.state.startDate.getTime()); - this.props.onClose(true); -} - - - render() { - - return ( - - - Select a Date & Time - - - - - - - - - - - ) + const handleClose = () => this.props.onClose(false); + const handleDone = (onClose) => { + dispatch(setDateFilter(startDate.getTime())); + onClose(true); } + + + + + return ( + + + Select a Date & Time + + + + + + + + + + + ) } + export default CalendarModal; diff --git a/frontend/src/Call/CallInfo.js b/frontend/src/Call/CallInfo.js index 107dab3..9d1543d 100644 --- a/frontend/src/Call/CallInfo.js +++ b/frontend/src/Call/CallInfo.js @@ -11,20 +11,19 @@ import { } from "semantic-ui-react"; // ---------------------------------------------------- -class CallInfo extends React.Component { +function CallInfo(props) { - render() { - var srcList = ""; - var callLength = "-"; - var callFreq = "-"; - var callDate = "-"; - var callTime = "-"; - var talkgroupNum = "-"; - var callDownload = ""; - var callTweet = ""; - if (this.props.call) { - const currentCall = this.props.call; + let srcList = ""; + let callLength = "-"; + let callFreq = "-"; + let callDate = "-"; + let callTime = "-"; + let talkgroupNum = "-"; + let callDownload = ""; + let callTweet = ""; + if (props.call) { + const currentCall = props.call; var time = new Date(currentCall.time); callTime = time.toLocaleTimeString(); callDate = time.toLocaleDateString(); @@ -33,12 +32,11 @@ class CallInfo extends React.Component { callFreq = Math.round(freq * 1000) / 1000; } - srcList = currentCall.srcList.map((source, index) => {source.src} - [{source.pos}]); + srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}]); callLength = currentCall.len; talkgroupNum = currentCall.talkgroupNum; callDownload = currentCall.url; - callTweet = "https://twitter.com/intent/tweet?url="+encodeURIComponent(document.location.origin+this.props.link)+"&via="+encodeURIComponent("OpenMHz"); + callTweet = "https://twitter.com/intent/tweet?url="+encodeURIComponent(document.location.origin+props.link)+"&via="+encodeURIComponent("OpenMHz"); } @@ -46,7 +44,7 @@ class CallInfo extends React.Component { return (
    -
    {this.props.header}
    +
    {props.header}
    {srcList} @@ -84,11 +82,11 @@ class CallInfo extends React.Component { Tweet Download - Link + Link
    ); - } + } export default CallInfo; From 1bfb796a49b18aad8458137c9b0646ad52635817 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 8 Jan 2023 15:13:41 -0500 Subject: [PATCH 10/27] Loads and gets calls --- frontend/src/Call/CallPlayer.js | 439 +++++++++++----------- frontend/src/features/api/apiSlice.js | 9 +- frontend/src/features/calls/callsSlice.js | 48 +++ frontend/src/index.js | 4 +- 4 files changed, 273 insertions(+), 227 deletions(-) create mode 100644 frontend/src/features/calls/callsSlice.js 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( } /> } /> - } /> + } /> } /> From a2deb4373e6773b10f416fbf483d48572d20e742 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 8 Jan 2023 22:02:49 -0500 Subject: [PATCH 11/27] showing calls --- frontend/src/Call/CallPlayer.js | 8 ++++-- frontend/src/features/calls/callsSlice.js | 35 ++++++++++++++++------- frontend/src/index.js | 1 + frontend/src/redux-router/reducers.js | 2 +- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index a2fd3d0..01f80d3 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -11,7 +11,7 @@ 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 { selectAllCalls, useGetCallsQuery, callsAdapter } from "../features/calls/callsSlice"; import { Container, Label, @@ -38,8 +38,9 @@ function CallPlayer (props) { const { shortName } = useParams(); const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(shortName); const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); + //console.log(callsData); const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); - //const allCalls = selectAllCalls(shortName) + const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] const [requestMessage, setRequestMessage] = useState(""); const [callUrl, setCallUrl] = useState(""); const [autoPlay, setAutoPlay] = useState(true); @@ -73,6 +74,7 @@ function CallPlayer (props) { if (sidebarOpened) setSidebarOpened(false); } + const handleSidebarToggle = () => setSidebarOpened(!sidebarOpened); const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); @@ -592,7 +594,7 @@ const getFilter = () => { style={{ minHeight: '100vh' }} > - {/* */} + diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 234d4d7..36822d6 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -6,7 +6,10 @@ import { import { apiSlice } from '../api/apiSlice' - const callsAdapter = createEntityAdapter({ +// in order for the initial fetch to happen this call needs to be made in index.js +// store.dispatch(extendedApiSlice.endpoints.getCalls.initiate()) + + export const callsAdapter = createEntityAdapter({ selectId: (call) => call._id, sortComparer: (a, b) => a.time.localeCompare(b.time), }) @@ -16,11 +19,15 @@ import { export const extendedApiSlice = apiSlice.injectEndpoints({ endpoints: builder => ({ getCalls: builder.query({ - query: ({shortName,filterType=0,filterCode=0}) => ({ + query: ({shortName,filterType=0,filterCode=0}) => ({ url: `/${shortName}/calls`, method: "GET", params: { filterType, filterCode }, }), + /*query: () => ({ + url: `/test/calls`, + method: "GET" + }),*/ transformResponse: responseData => { responseData = responseData.calls; return callsAdapter.setAll(initialState, responseData) @@ -29,20 +36,28 @@ import { }) }) + + // from tutorials: https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced#transforming-responses 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 selectCallsResult = extendedApiSlice.endpoints.getCalls.select({"shortName":"test"}) - -export const selectCallsData = createSelector( +const selectCallsData = createSelector( selectCallsResult, - callsResult => callsResult.data + (callsResult) => { + return callsResult.data + } ) -export const { selectAll: selectAllCalls, selectById: selectCallById } = - callsAdapter.getSelectors(state => selectCallsData(state) ?? initialState) +export const { selectAll: selectAllCalls, selectById: selectCallById } = callsAdapter.getSelectors(state => { + return selectCallsData(state) ?? initialState +}) + + /* +export const {selectAll: selectAllCalls} = callsAdapter.getSelectors((state) => { + return state.api} + );*/ + diff --git a/frontend/src/index.js b/frontend/src/index.js index 6e79eb3..e7ca077 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -23,6 +23,7 @@ import AboutComponent from "./About/AboutComponent" const store = configureStore(/* provide initial state if any */) + const container = document.getElementById('root'); const root = createRoot(container); root.render( diff --git a/frontend/src/redux-router/reducers.js b/frontend/src/redux-router/reducers.js index c1bf77a..4ef6dae 100644 --- a/frontend/src/redux-router/reducers.js +++ b/frontend/src/redux-router/reducers.js @@ -9,6 +9,7 @@ import callReducer from "../Call/call-reducer" import {apiSlice} from "../features/api/apiSlice" import callPlayerSlice from "../features/callPlayer/callPlayerSlice" + export default (history) => combineReducers({ router: connectRouter(history), system: systemReducer, @@ -16,7 +17,6 @@ export default (history) => combineReducers({ group: groupReducer, callPlayer: callPlayerSlice, call: callReducer, - [apiSlice.reducerPath]: apiSlice.reducer, }) \ No newline at end of file From 020f00b8fcb4c45fb8def424fbb13a4116d6864a Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Tue, 10 Jan 2023 10:14:01 -0500 Subject: [PATCH 12/27] loading using an Async Thunk --- frontend/src/Call/CallPlayer.js | 12 ++- frontend/src/Call/ListCalls.js | 17 ++-- frontend/src/features/calls/callsSlice.js | 110 ++++++++++++++++++++-- frontend/src/redux-router/reducers.js | 3 +- 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 01f80d3..773b461 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -10,6 +10,7 @@ import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' import { setLive } from "../features/callPlayer/callPlayerSlice"; +import { getCalls} from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { selectAllCalls, useGetCallsQuery, callsAdapter } from "../features/calls/callsSlice"; import { @@ -37,10 +38,12 @@ function CallPlayer (props) { const { shortName } = useParams(); const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(shortName); - const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); + //const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); + const {loading, data: callsData} = useSelector((state) => state.calls); + //console.log(callsData); const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); - const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] + //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] const [requestMessage, setRequestMessage] = useState(""); const [callUrl, setCallUrl] = useState(""); const [autoPlay, setAutoPlay] = useState(true); @@ -383,6 +386,7 @@ const getFilter = () => { this.startSocket(shortName, filterType, filterCode, filter.filterStarred); }*/ dispatch(setShortName(shortName)); + dispatch(getCalls({shortName})); return () => { //this.endSocket(); }; @@ -594,8 +598,8 @@ const getFilter = () => { style={{ minHeight: '100vh' }} > - - + + diff --git a/frontend/src/Call/ListCalls.js b/frontend/src/Call/ListCalls.js index 57b22bc..f5f7c74 100644 --- a/frontend/src/Call/ListCalls.js +++ b/frontend/src/Call/ListCalls.js @@ -13,6 +13,7 @@ class ListCalls extends React.Component { //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 callsData = this.props.callsData; return ( @@ -24,20 +25,24 @@ class ListCalls extends React.Component { - + {callsData != false && + - {this.props.callsAllIds.map((callId, index) => { + { + this.props.callsData.ids.map((callId, index) => { if (callId === this.props.activeCallId) { - return () + return () } else { - return + return } }) - } + + } + }
    - + ); } diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 36822d6..5e197d9 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -1,9 +1,11 @@ import { createSlice, createEntityAdapter, - createSelector + createSelector, + createAsyncThunk } from '@reduxjs/toolkit' + /* import { apiSlice } from '../api/apiSlice' // in order for the initial fetch to happen this call needs to be made in index.js @@ -24,10 +26,6 @@ import { method: "GET", params: { filterType, filterCode }, }), - /*query: () => ({ - url: `/test/calls`, - method: "GET" - }),*/ transformResponse: responseData => { responseData = responseData.calls; return callsAdapter.setAll(initialState, responseData) @@ -55,9 +53,105 @@ const selectCallsData = createSelector( export const { selectAll: selectAllCalls, selectById: selectCallById } = callsAdapter.getSelectors(state => { return selectCallsData(state) ?? initialState }) +*/ +const now = new Date(); +export const callsAdapter = createEntityAdapter({ + selectId: (call) => call._id, + sortComparer: (a, b) => a.time.localeCompare(b.time), +}) + +const initialState = { + loading: false, + data: callsAdapter.getInitialState(), + oldestCallTime: now, + newestCallTime: now, + } + + function buildUrlParams(a) { + return Object.keys(a).map(function(k) { + return encodeURIComponent(k) + '=' + encodeURIComponent(a[k]) + }).join('&') + } + + function buildCallUrl(shortName,filterType=0,filterGroupId=false, filterTalkgroups=false,filterDate=false, date=false, direction=false,filterStarred=false) { + var params = {}; + var url = ""; + + + if ((typeof direction === 'string') && (typeof date === 'number')) { + url = url + '/' + direction; + params["time"] = date; + } else { + if (filterDate){ + url = url + '/older' + params["time"] = filterDate; + } + } + if (filterStarred) { + params["filter-starred"] = true; + } + switch(filterType) { + case 1: + params["filter-type"] = "group"; + params["filter-code"] = filterGroupId; + break; + case 2: + params["filter-type"] = "talkgroup"; + params["filter-code"] = filterTalkgroups; + break; + default: + case 0: + } /* -export const {selectAll: selectAllCalls} = callsAdapter.getSelectors((state) => { - return state.api} - );*/ + if (this.filterCode != "") { + params["filter-code"] = this.filterCode; + } + if (this.filterType == this.FilterType.Group) { + params["filter-type"] = "group"; + params["filter-name"] = this.filterName; + } + if (this.filterType == this.FilterType.Talkgroup) { + params["filter-type"] = "talkgroup"; + } + if (this.filterType == this.FilterType.Unit) { + params["filter-type"] = "unit"; + } + */ + // url = startUp.backend_server + "/" + startUp.shortName.toLowerCase() + "/calls" + url + '?' + buildUrlParams(params); + url = process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/calls" + url + '?' + buildUrlParams(params); + console.log("Trying to fetch data from this url: " + url); + return url; + } +export const getCalls = createAsyncThunk( + 'calls/getCalls', + async({shortName,filterType=0,filterGroupId=false, filterTalkgroups=false, filterStarred=false }) => { + const url = buildCallUrl(shortName, filterType, filterGroupId,filterTalkgroups, filterStarred); + const res = await fetch(url).then( + (data) => data.json() + ) + return res; + }) + +export const callsSlice = createSlice({ + name: 'calls', + initialState, + reducers: {}, + extraReducers: { + [getCalls.pending]: (state) => { + state.loading = true; + }, + [getCalls.fulfilled]: (state, {payload}) => { + state.loading = false; + state.data = callsAdapter.setAll(state.data, payload.calls) + state.newestCallTime = new Date(payload.calls[0].time) + state.oldestCallTime = new Date(payloads.calls[payloads.calls.length-1].time) + }, + [getCalls.rejected]: (state) => { + state.loading = false; + } + } +}) + +export const callsReducer = callsSlice.reducer; diff --git a/frontend/src/redux-router/reducers.js b/frontend/src/redux-router/reducers.js index 4ef6dae..3b8bc7a 100644 --- a/frontend/src/redux-router/reducers.js +++ b/frontend/src/redux-router/reducers.js @@ -8,7 +8,7 @@ import groupReducer from "../Group/group-reducer" import callReducer from "../Call/call-reducer" import {apiSlice} from "../features/api/apiSlice" import callPlayerSlice from "../features/callPlayer/callPlayerSlice" - +import callsSlice, { callsReducer } from "../features/calls/callsSlice" export default (history) => combineReducers({ router: connectRouter(history), @@ -17,6 +17,7 @@ export default (history) => combineReducers({ group: groupReducer, callPlayer: callPlayerSlice, call: callReducer, + calls: callsReducer, [apiSlice.reducerPath]: apiSlice.reducer, }) \ No newline at end of file From 5bd5ab0fd53bf9de2f435543b31de83c8d8dab8d Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Tue, 10 Jan 2023 22:09:52 -0500 Subject: [PATCH 13/27] almost have loadolder working --- frontend/src/Call/CallPlayer.js | 29 ++++---- frontend/src/features/calls/callsSlice.js | 81 +++++++++++++++++++++-- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 773b461..3fad19a 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -9,8 +9,8 @@ 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 { getCalls} from "../features/calls/callsSlice"; +import { setLive, setShortName } from "../features/callPlayer/callPlayerSlice"; +import { getCalls, getOlderCalls} from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { selectAllCalls, useGetCallsQuery, callsAdapter } from "../features/calls/callsSlice"; import { @@ -21,13 +21,13 @@ import { Menu, Icon, Sidebar, - Visibility + Visibility, + Button } from "semantic-ui-react"; import "./CallPlayer.css"; //import setupSocket from '../socket/SetupSocket.js' import queryString from '../query-string'; -import { setShortName } from "./call-actions"; @@ -89,9 +89,6 @@ function CallPlayer (props) { const loadNewerCalls = () => { - } - const loadOlderCalls = () => { - } const playCall = (data) => { @@ -109,13 +106,13 @@ function CallPlayer (props) { console.log("Loading Newer Calls"); callActions.fetchNewerCalls(newestCallTime.getTime()); } - - loadOlderCalls() { +*/ + const loadOlderCalls = (getOlderCalls) => { console.log("Loading Older Calls"); - callActions.fetchOlderCalls(oldestCallTime.getTime()); + dispatch(getOlderCalls()); - }*/ + } const getFilter = () => { @@ -385,13 +382,17 @@ const getFilter = () => { if (filter.live) { this.startSocket(shortName, filterType, filterCode, filter.filterStarred); }*/ + console.log("Setting short name to: " + shortName) dispatch(setShortName(shortName)); - dispatch(getCalls({shortName})); + return () => { //this.endSocket(); }; }, []); + useEffect(() => { + dispatch(getCalls({shortName})); + }, [shortName,filterGroupId,filterTalkgroups,filterType,filterStarred]) @@ -599,6 +600,10 @@ const getFilter = () => { > + +
    + +
    diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 5e197d9..c178d93 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -74,10 +74,11 @@ const initialState = { }).join('&') } - function buildCallUrl(shortName,filterType=0,filterGroupId=false, filterTalkgroups=false,filterDate=false, date=false, direction=false,filterStarred=false) { + function buildCallUrl(getState, direction=false, date=false) { var params = {}; var url = ""; + const {shortName,filterType,filterGroupId, filterTalkgroups, filterStarred, filterDate} = getState().callPlayer; if ((typeof direction === 'string') && (typeof date === 'number')) { url = url + '/' + direction; @@ -126,14 +127,38 @@ const initialState = { export const getCalls = createAsyncThunk( 'calls/getCalls', - async({shortName,filterType=0,filterGroupId=false, filterTalkgroups=false, filterStarred=false }) => { - const url = buildCallUrl(shortName, filterType, filterGroupId,filterTalkgroups, filterStarred); + async({},{getState, requestId}) => { + const state = getState() + const {loading,oldestCallTime, newestCallTime} = getState().call; + const url = buildCallUrl(getState); const res = await fetch(url).then( (data) => data.json() ) return res; }) + export const getOlderCalls = createAsyncThunk( + 'calls/getOlderCalls', + async({},{getState, requestId} ) => { + const {loading,oldestCallTime, newestCallTime} = getState().call; + const url = buildCallUrl(getState,"older",oldestCallTime.getTime()); + const res = await fetch(url).then( + (data) => data.json() + ) + return res; + }) + + export const getNewerCalls = createAsyncThunk( + 'calls/getNewerCalls', + async({},{getState, requestId} ) => { + const {loading,oldestCallTime, newestCallTime} = getState().call; + const url = buildCallUrl(getState,"newer",newestCallTime.getTime()); + const res = await fetch(url).then( + (data) => data.json() + ) + return res; + }) + export const callsSlice = createSlice({ name: 'calls', initialState, @@ -142,15 +167,57 @@ export const callsSlice = createSlice({ [getCalls.pending]: (state) => { state.loading = true; }, + [getCalls.rejected]: (state) => { + state.loading = false; + }, [getCalls.fulfilled]: (state, {payload}) => { state.loading = false; state.data = callsAdapter.setAll(state.data, payload.calls) - state.newestCallTime = new Date(payload.calls[0].time) - state.oldestCallTime = new Date(payloads.calls[payloads.calls.length-1].time) + if (state.data && (state.data.ids.length > 0)) { + const first = state.data.ids[0] + const firstTime = state.data.entities[first].time; + const last = state.data.ids[state.data.ids[0].length-1]; + const lastTime = state.data.entities[last].time; + state.newestCallTime = new Date(firstTime) + state.oldestCallTime = new Date(lastTime) + } }, - [getCalls.rejected]: (state) => { + [getOlderCalls.pending]: (state) => { + state.loading = true; + }, + [getOlderCalls.rejected]: (state) => { state.loading = false; - } + }, + [getOlderCalls.fulfilled]: (state, {payload}) => { + state.loading = false; + state.data = callsAdapter.addMany(state.data, payload.calls) + if (state.data && (state.data.ids.length > 0)) { + const first = state.data.ids[0] + const firstTime = state.data.entities[first].time; + const last = state.data.ids[state.data.ids[0].length-1]; + const lastTime = state.data.entities[last].time; + state.newestCallTime = new Date(firstTime) + state.oldestCallTime = new Date(lastTime) + } + }, + [getNewerCalls.pending]: (state) => { + state.loading = true; + }, + [getNewerCalls.rejected]: (state) => { + state.loading = false; + }, + [getNewerCalls.fulfilled]: (state, {payload}) => { + state.loading = false; + state.data = callsAdapter.addMany(state.data, payload.calls) + if (state.data && (state.data.ids.length > 0)) { + const first = state.data.ids[0] + const firstTime = state.data.entities[first].time; + const last = state.data.ids[state.data.ids[0].length-1]; + const lastTime = state.data.entities[last].time; + state.newestCallTime = new Date(firstTime) + state.oldestCallTime = new Date(lastTime) + } + }, } }) From 28ecf67cbdd258ef06dfa11e40dbee9f8b50a324 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Wed, 11 Jan 2023 22:36:34 -0500 Subject: [PATCH 14/27] infiinite scroll is here! --- .env.test | 1 + frontend/package.json | 1 + frontend/src/Call/CallPlayer.js | 56 +++++++++++++++-------- frontend/src/features/calls/callsSlice.js | 8 ++-- frontend/yarn.lock | 5 ++ 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/.env.test b/.env.test index dd4df7c..1c0acdb 100644 --- a/.env.test +++ b/.env.test @@ -2,6 +2,7 @@ MEDIA=/Users/luke/Testing/media DATA=/Users/luke/Testing/data MAILJET_KEY="4d2a0f6deb20a7890afee2daf595c22d" MAILJET_SECRET="fb74f682d68bf6b05ab626c0ac93a6da" +GOOGLE_ANALYTICS="UA-45563211-1" STAGE=test TAG=1.7 DOMAIN_NAME="openmhz.test" diff --git a/frontend/package.json b/frontend/package.json index b407c80..8acbda3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "react-datepicker": "4.8.0", "react-dom": "^18.2.0", "react-ga": "^3.3.1", + "react-intersection-observer": "^9.4.1", "react-redux": "^8.0.5", "react-router-dom": "6.6.1", "react-scripts": "5.0.1", diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 3fad19a..15c3f0e 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -9,10 +9,10 @@ import CalendarModal from "./CalendarModalContainer"; import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' -import { setLive, setShortName } from "../features/callPlayer/callPlayerSlice"; -import { getCalls, getOlderCalls} from "../features/calls/callsSlice"; +import { setLive, setShortName, setFilter } from "../features/callPlayer/callPlayerSlice"; +import { getCalls, getOlderCalls, getNewerCalls} from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' -import { selectAllCalls, useGetCallsQuery, callsAdapter } from "../features/calls/callsSlice"; +import { useInView } from 'react-intersection-observer'; import { Container, Label, @@ -21,7 +21,6 @@ import { Menu, Icon, Sidebar, - Visibility, Button } from "semantic-ui-react"; import "./CallPlayer.css"; @@ -37,14 +36,21 @@ import queryString from '../query-string'; function CallPlayer (props) { const { shortName } = useParams(); + const { ref: loadOlderRef, inView: loadOlderInView, entry: loadOlderEntry } = useInView({ + /* Optional options */ + threshold: 0.5 + }); + const { ref: loadNewerRef, inView: loadNewerInView, entry: loadNewerEntry } = useInView({ + /* Optional options */ + threshold: 0.5 + }); const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(shortName); //const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); - const {loading, data: callsData} = useSelector((state) => state.calls); + const {loading: callsLoading, data: callsData} = useSelector((state) => state.calls); //console.log(callsData); const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] - const [requestMessage, setRequestMessage] = useState(""); const [callUrl, setCallUrl] = useState(""); const [autoPlay, setAutoPlay] = useState(true); const [callId, setCallId] = useState(false); @@ -67,7 +73,6 @@ function CallPlayer (props) { 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); @@ -107,10 +112,11 @@ function CallPlayer (props) { callActions.fetchNewerCalls(newestCallTime.getTime()); } */ + const loadOlderCalls = (getOlderCalls) => { console.log("Loading Older Calls"); - dispatch(getOlderCalls()); + dispatch(getOlderCalls({})); } @@ -232,7 +238,6 @@ const getFilter = () => { const handleGroupClose = (didUpdate) => { setGroupVisible(!groupVisible); - //callActions.fetchCalls(); } const handleFilterClose = (didUpdate) => { @@ -241,7 +246,6 @@ const getFilter = () => { if (didUpdate) { setCallUrl(""); setCallId(false); - //callActions.fetchCalls(); } } /* @@ -299,7 +303,7 @@ const getFilter = () => { } }*/ - const setStateFromUri = () => { + const setStateFromUri = async () => { var filter = { filterDate: false, filterType: 0, // this is "all" @@ -367,9 +371,23 @@ const getFilter = () => { } } - //callActions.setFilter(filter); + await dispatch(setFilter(filter)); } + useEffect(() => { + if (loadNewerInView && callsData && (callsData.ids.length > 0)) { + dispatch(getNewerCalls({})); + } + }, [loadNewerInView]); + + + useEffect(() => { + if (loadOlderInView && callsData && (callsData.ids.length > 0)) { + dispatch(getOlderCalls({})); + } + }, [loadOlderInView]); + + 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 @@ -382,6 +400,7 @@ const getFilter = () => { if (filter.live) { this.startSocket(shortName, filterType, filterCode, filter.filterStarred); }*/ + setStateFromUri(); console.log("Setting short name to: " + shortName) dispatch(setShortName(shortName)); @@ -391,7 +410,8 @@ const getFilter = () => { }, []); useEffect(() => { - dispatch(getCalls({shortName})); + dispatch(getCalls({})); + //dispatch(getOlderCalls()); }, [shortName,filterGroupId,filterTalkgroups,filterType,filterStarred]) @@ -598,13 +618,11 @@ const getFilter = () => { onClick={handlePusherClick} style={{ minHeight: '100vh' }} > - +
    - -
    - -
    - +
    + + diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index c178d93..2d446ca 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -58,7 +58,7 @@ const now = new Date(); export const callsAdapter = createEntityAdapter({ selectId: (call) => call._id, - sortComparer: (a, b) => a.time.localeCompare(b.time), + sortComparer: (a, b) => b.time.localeCompare(a.time), }) const initialState = { @@ -129,7 +129,7 @@ export const getCalls = createAsyncThunk( 'calls/getCalls', async({},{getState, requestId}) => { const state = getState() - const {loading,oldestCallTime, newestCallTime} = getState().call; + const {loading,oldestCallTime, newestCallTime} = getState().calls; const url = buildCallUrl(getState); const res = await fetch(url).then( (data) => data.json() @@ -140,7 +140,7 @@ export const getCalls = createAsyncThunk( export const getOlderCalls = createAsyncThunk( 'calls/getOlderCalls', async({},{getState, requestId} ) => { - const {loading,oldestCallTime, newestCallTime} = getState().call; + const {loading,oldestCallTime, newestCallTime} = getState().calls; const url = buildCallUrl(getState,"older",oldestCallTime.getTime()); const res = await fetch(url).then( (data) => data.json() @@ -151,7 +151,7 @@ export const getCalls = createAsyncThunk( export const getNewerCalls = createAsyncThunk( 'calls/getNewerCalls', async({},{getState, requestId} ) => { - const {loading,oldestCallTime, newestCallTime} = getState().call; + const {loading,oldestCallTime, newestCallTime} = getState().calls; const url = buildCallUrl(getState,"newer",newestCallTime.getTime()); const res = await fetch(url).then( (data) => data.json() diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4d8363c..a308473 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -8431,6 +8431,11 @@ react-ga@^3.3.1: resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-3.3.1.tgz#d8e1f4e05ec55ed6ff944dcb14b99011dfaf9504" integrity sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ== +react-intersection-observer@^9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-9.4.1.tgz#4ccb21e16acd0b9cf5b28d275af7055bef878f6b" + integrity sha512-IXpIsPe6BleFOEHKzKh5UjwRUaz/JYS0lT/HPsupWEQou2hDqjhLMStc5zyE3eQVT4Fk3FufM8Fw33qW1uyeiw== + react-is@^16.13.1, react-is@^16.6.3, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" From dad7dc1bd2ac33b05ea5fb7c69d11219d312e5c7 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 12 Jan 2023 07:09:40 -0500 Subject: [PATCH 15/27] adding more --- frontend/src/Call/CallPlayer.js | 7 ++++--- frontend/src/features/callPlayer/callPlayerSlice.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 15c3f0e..3c47ba8 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -310,7 +310,8 @@ const getFilter = () => { filterTalkgroups: [], filterGroupId: false, filterStarred: false, - live: true + live: true, + shortName: shortName }; // is there a star filter? @@ -401,8 +402,8 @@ const getFilter = () => { this.startSocket(shortName, filterType, filterCode, filter.filterStarred); }*/ setStateFromUri(); - console.log("Setting short name to: " + shortName) - dispatch(setShortName(shortName)); + //console.log("Setting short name to: " + shortName) + //dispatch(setShortName(shortName)); return () => { //this.endSocket(); diff --git a/frontend/src/features/callPlayer/callPlayerSlice.js b/frontend/src/features/callPlayer/callPlayerSlice.js index d1edcab..fa13ce1 100644 --- a/frontend/src/features/callPlayer/callPlayerSlice.js +++ b/frontend/src/features/callPlayer/callPlayerSlice.js @@ -35,6 +35,7 @@ export const callPlayerSlice = createSlice({ state.filterTalkgroups = !action.payload.filterTalkgroups ? [] : action.payload.filterTalkgroups; state.filterGroupId = !action.payload.filterGroupId ? false : action.payload.filterGroupId; state.filterDate = !action.payload.filterDate ? 0 : action.payload.filterDate; + state.shortName = !action.payload.shortName ? state.shortName : action.payload.shortName; }, setLive: (state,action) => { state.live = action.payload; From 844e8e7fdd30f282e7956d3034405aefd7e2d646 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 12 Jan 2023 22:16:18 -0500 Subject: [PATCH 16/27] websocket atleast connects! --- backend/controllers/uploads.js | 422 ++++++++++++++++---------------- backend/index.js | 9 +- backend/package.json | 2 +- frontend/package.json | 3 +- frontend/src/Call/CallPlayer.js | 150 +++++++----- frontend/yarn.lock | 51 +++- 6 files changed, 355 insertions(+), 282 deletions(-) diff --git a/backend/controllers/uploads.js b/backend/controllers/uploads.js index a4d12aa..c77c413 100644 --- a/backend/controllers/uploads.js +++ b/backend/controllers/uploads.js @@ -13,9 +13,9 @@ var secrets = require("../config/secrets"); var util = require("util") var config = require('../config/config.json'); -var s3_endpoint = process.env['S3_ENDPOINT'] != null ? process.env['S3_ENDPOINT'] : 's3.us-west-1.wasabisys.com'; -var s3_bucket = process.env['S3_BUCKET'] != null ? process.env['S3_BUCKET'] : 'openmhz-west'; -var s3_profile = process.env['S3_PROFILE'] != null ? process.env['S3_PROFILE'] : 'wasabi-account'; +var s3_endpoint = process.env['S3_ENDPOINT'] != null ? process.env['S3_ENDPOINT'] : 's3.us-west-1.wasabisys.com'; +var s3_bucket = process.env['S3_BUCKET'] != null ? process.env['S3_BUCKET'] : 'openmhz-west'; +var s3_profile = process.env['S3_PROFILE'] != null ? process.env['S3_PROFILE'] : 'wasabi-account'; const wasabi = require('aws-sdk'); @@ -31,19 +31,19 @@ const wasabiS3 = new wasabi.S3({ async function callLength(file) { - return new Promise((resolve, reject) => { - probe(file, function(err, probeData) { - if (err) { - reject(err); - } else { - resolve(Math.round(probeData.format.duration)); - } - }); - }) + return new Promise((resolve, reject) => { + probe(file, function (err, probeData) { + if (err) { + reject(err); + } else { + resolve(Math.round(probeData.format.duration)); + } + }); + }) } -exports.upload = function(req, res, next) { -process.nextTick(function() { +exports.upload = function (req, res, next) { + process.nextTick(function () { @@ -61,235 +61,235 @@ process.nextTick(function() { res.send("Error, invalid filename:\n"); return; } - var shortName = req.params.shortName.toLowerCase(); - var apiKey = req.body.api_key; + var shortName = req.params.shortName.toLowerCase(); + var apiKey = req.body.api_key; - System.findOne({ - 'shortName': shortName - },["key", "ignoreUnknownTalkgroup"], async function(err, item) { + System.findOne({ + 'shortName': shortName + }, ["key", "ignoreUnknownTalkgroup"], async function (err, item) { - if (err) { - console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err); - res.status(500); - res.send("Error, invalid shotName:\n" + err); - return; - } - if (item === null) { - console.warn("[" + req.params.shortName + "] Error /:shortName/upload ShortName does not exist"); - res.status(500); - res.send("ShortName does not exist: " + shortName + "\n"); - return; - } - if (apiKey != item.key) { - console.warn("[" + req.params.shortName + "] Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey); - res.status(500); - res.send("API Keys do not match!\n"); - return; - } + if (err) { + console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err); + res.status(500); + res.send("Error, invalid shotName:\n" + err); + return; + } + if (item === null) { + console.warn("[" + req.params.shortName + "] Error /:shortName/upload ShortName does not exist"); + res.status(500); + res.send("ShortName does not exist: " + shortName + "\n"); + return; + } + if (apiKey != item.key) { + console.warn("[" + req.params.shortName + "] Error /:shortName/upload API Key Mismatch - Provided key: " + apiKey); + res.status(500); + res.send("API Keys do not match!\n"); + return; + } - var talkgroupNum = parseInt(req.body.talkgroup_num); - var freq = parseFloat(req.body.freq); - var time = new Date(parseInt(req.body.start_time) * 1000); - var stopTime = new Date(); - if (req.body.stop_time) { - var stopTime = new Date(parseInt(req.body.stop_time) * 1000); - } - var startTime = req.body.start_time; - var emergency = parseInt(req.body.emergency); - + var talkgroupNum = parseInt(req.body.talkgroup_num); + var freq = parseFloat(req.body.freq); + var time = new Date(parseInt(req.body.start_time) * 1000); + var stopTime = new Date(); + if (req.body.stop_time) { + var stopTime = new Date(parseInt(req.body.stop_time) * 1000); + } + var startTime = req.body.start_time; + var emergency = parseInt(req.body.emergency); - if (item.ignoreUnknownTalkgroup == true) { - //console.log("ignore unknown talkgroup "); - //await Talkgroup.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc) - talkgroupExists = await Talkgroup.exists({ - 'shortName': shortName, - 'num': talkgroupNum - }); - //console.log("In talkgroup: " + talkgroupExists); - //talkgroupExists = await Talkgroup.findOne({ 'shortName': shortName, 'num': talkgroupNum }).select({ _id: 1 }).lean().then(doc => !!doc).exec(); - if (!talkgroupExists) { - try { - fs.unlinkSync(req.file.path) - //file removed - } catch(err) { - console.log("[" + call.shortName + "] error deleting: " + req.file.path); - } - - res.status(500); - res.send("Talkgroup does not exist, skipping.\n"); - return; - + + if (item.ignoreUnknownTalkgroup == true) { + //console.log("ignore unknown talkgroup "); + //await Talkgroup.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc) + talkgroupExists = await Talkgroup.exists({ + 'shortName': shortName, + 'num': talkgroupNum + }); + //console.log("In talkgroup: " + talkgroupExists); + //talkgroupExists = await Talkgroup.findOne({ 'shortName': shortName, 'num': talkgroupNum }).select({ _id: 1 }).lean().then(doc => !!doc).exec(); + if (!talkgroupExists) { + try { + fs.unlinkSync(req.file.path) + //file removed + } catch (err) { + console.log("[" + call.shortName + "] error deleting: " + req.file.path); } - } - // Add in an API Key check - - try { - - var srcList = JSON.parse(req.body.source_list); - - // Mongoose does not allow 'errors' to be used in the Schema for call. need to rename to error. - var badFreqList = JSON.parse(req.body.freq_list); - var freqList = badFreqList.map(obj =>{ - obj.error = obj.errors; - delete obj.errors; - return obj; - }); - - } catch (err) { - var srcList = []; - var freqList = []; - console.warn("[" + req.params.shortName + "] Error /:shortName/upload Parsing Source/Freq List - Error: " + err); res.status(500); - res.send("Error parsing sourcelist " + err); + res.send("Talkgroup does not exist, skipping.\n"); + return; + } + } + // Add in an API Key check - var base_path = config.mediaDirectory; - var local_path = "/" + shortName + "/" + time.getFullYear() + "/" + (time.getMonth() + 1) + "/" + time.getDate() + "/"; - var target_file = base_path + local_path + path.basename(req.file.originalname); - var object_key = "media/" + shortName + "-" + talkgroupNum + "-" + startTime + ".m4a"; - - var endpoint = s3_endpoint; - var bucket = s3_bucket; - + try { - // If not a pro plan, use S3 storage - var url = 'https://' + s3_endpoint + "/" + s3_bucket + "/" + object_key; - var objectStore = true; - + var srcList = JSON.parse(req.body.source_list); - var call = new Call({ - shortName: shortName, - talkgroupNum: talkgroupNum, - objectKey: object_key, - endpoint: endpoint, - bucket: bucket, - time: time, - name: talkgroupNum + "-" + startTime + ".m4a", - freq: freq, - url: url, - emergency: emergency, - path: local_path, - srcList: srcList, - freqList: freqList, - len: -1 + // Mongoose does not allow 'errors' to be used in the Schema for call. need to rename to error. + var badFreqList = JSON.parse(req.body.freq_list); + var freqList = badFreqList.map(obj => { + obj.error = obj.errors; + delete obj.errors; + return obj; }); - try { - if (req.body.call_length) { - call.len = parseInt(req.body.call_length); - } else { - call.len = (stopTime - time)/1000; //await callLength(req.file.path); - } - res.status(200).end(); + } catch (err) { + var srcList = []; + var freqList = []; + console.warn("[" + req.params.shortName + "] Error /:shortName/upload Parsing Source/Freq List - Error: " + err); + + res.status(500); + res.send("Error parsing sourcelist " + err); + } + + var base_path = config.mediaDirectory; + var local_path = "/" + shortName + "/" + time.getFullYear() + "/" + (time.getMonth() + 1) + "/" + time.getDate() + "/"; + var target_file = base_path + local_path + path.basename(req.file.originalname); + var object_key = "media/" + shortName + "-" + talkgroupNum + "-" + startTime + ".m4a"; + + var endpoint = s3_endpoint; + var bucket = s3_bucket; - if (!objectStore) { - mkdirp.sync(base_path + local_path, function(err) { - if (err) console.error(err); - }); - var src = fs.createReadStream(req.file.path); - var dest = fs.createWriteStream(target_file); - dest.on('unpipe', (src) => { - src.end(); - dest.end(); - }); - src.pipe(dest); - src.unpipe() - src.on('error', function(err) { - console.warn("[" + call.shortName + "] Error /:shortName/upload copying to location - Error: " + err + " Read: " + req.file.path + " Target: " + target_file); - fs.unlink(req.file.path, (err) => { - if (err) - console.log("[" + call.shortName + "] error deleting: " + req.file.path); - }); - res.contentType('json'); - res.send(JSON.stringify({ - success: false, - error: "Upload failed" - })); - }); - src.on('end', function() { - fs.unlink(req.file.path); - }); - } else { - //var spacesSrc = fs.createReadStream(req.file.path); - //var awsSrc = fs.createReadStream(req.file.path); - var wasabiSrc = fs.createReadStream(req.file.path); + // If not a pro plan, use S3 storage + var url = 'https://' + s3_endpoint + "/" + s3_bucket + "/" + object_key; + var objectStore = true; + + var call = new Call({ + shortName: shortName, + talkgroupNum: talkgroupNum, + objectKey: object_key, + endpoint: endpoint, + bucket: bucket, + time: time, + name: talkgroupNum + "-" + startTime + ".m4a", + freq: freq, + url: url, + emergency: emergency, + path: local_path, + srcList: srcList, + freqList: freqList, + len: -1 + }); + + try { + if (req.body.call_length) { + call.len = parseInt(req.body.call_length); + } else { + call.len = (stopTime - time) / 1000; //await callLength(req.file.path); + } + res.status(200).end(); + + + if (!objectStore) { + mkdirp.sync(base_path + local_path, function (err) { + if (err) console.error(err); + }); + var src = fs.createReadStream(req.file.path); + var dest = fs.createWriteStream(target_file); + dest.on('unpipe', (src) => { + src.end(); + dest.end(); + }); + src.pipe(dest); + src.unpipe() + src.on('error', function (err) { + console.warn("[" + call.shortName + "] Error /:shortName/upload copying to location - Error: " + err + " Read: " + req.file.path + " Target: " + target_file); + fs.unlink(req.file.path, (err) => { + if (err) + console.log("[" + call.shortName + "] error deleting: " + req.file.path); + }); + res.contentType('json'); + res.send(JSON.stringify({ + success: false, + error: "Upload failed" + })); + }); + src.on('end', function () { + fs.unlink(req.file.path); + }); + } else { + //var spacesSrc = fs.createReadStream(req.file.path); + //var awsSrc = fs.createReadStream(req.file.path); + var wasabiSrc = fs.createReadStream(req.file.path); + + // call S3 to retrieve upload file to specified bucket + + /* var spacesParams = {Bucket: 'openmhz', Key: object_key, Body: spacesSrc, ACL: 'public-read'}; // call S3 to retrieve upload file to specified bucket - - /* var spacesParams = {Bucket: 'openmhz', Key: object_key, Body: spacesSrc, ACL: 'public-read'}; - // call S3 to retrieve upload file to specified bucket - spacesS3.upload (spacesParams, function (err, data) { - //fs.unlink(req.file.path); - if (err) { - console.log("Spaces Error", err); - } - });*/ - /* - var awsParams = {Bucket: 'openmhz', Key: object_key, Body: awsSrc, ACL: 'public-read'}; - - awsS3.upload (awsParams, function (err, data) { - fs.unlink(req.file.path); + spacesS3.upload (spacesParams, function (err, data) { + //fs.unlink(req.file.path); if (err) { - console.log("AWS Error", err); + console.log("Spaces Error", err); } });*/ + /* + var awsParams = {Bucket: 'openmhz', Key: object_key, Body: awsSrc, ACL: 'public-read'}; - var wasabiParams = { - Bucket: s3_bucket, - Key: object_key, - Body: wasabiSrc, - ACL: 'public-read' - }; + awsS3.upload (awsParams, function (err, data) { + fs.unlink(req.file.path); + if (err) { + console.log("AWS Error", err); + } + });*/ + + var wasabiParams = { + Bucket: s3_bucket, + Key: object_key, + Body: wasabiSrc, + ACL: 'public-read' + }; - //var options = {partSize: 10 * 1024 * 1024, queueSize: 1}; - /*var upload = new wasabiS3.ManagedUpload({ - leavePartsOnError: true, - partSize: 10 * 1024 * 1024, queueSize: 1, - params: wasabiParams - });*/ - //wasabiS3.upload(wasabiParams, options, function(err, data) { - wasabiS3.upload(wasabiParams, function(err, data) { - wasabiSrc.destroy(); - fs.unlink(req.file.path, (err) => { - if (err) { - console.log("[" + call.shortName + "]error deleting: " + req.file.path); - } - call.save(); - sysStats.addCall(call.toObject()); - - // we only want to notify clients if the clip is longer than 1 second. - if (call.len >= 1) { - req.call = call.toObject(); - next(); - } - }); + //var options = {partSize: 10 * 1024 * 1024, queueSize: 1}; + /*var upload = new wasabiS3.ManagedUpload({ + leavePartsOnError: true, + partSize: 10 * 1024 * 1024, queueSize: 1, + params: wasabiParams + });*/ + //wasabiS3.upload(wasabiParams, options, function(err, data) { + wasabiS3.upload(wasabiParams, function (err, data) { + wasabiSrc.destroy(); + fs.unlink(req.file.path, (err) => { if (err) { - console.log("[" + call.shortName + "] " + call.name + " - content-length: " + req.headers['content-length'] + " Wasabi Error", err); + console.log("[" + call.shortName + "]error deleting: " + req.file.path); + } + call.save(); + sysStats.addCall(call.toObject()); + + // we only want to notify clients if the clip is longer than 1 second. + if (call.len >= 1) { + req.call = call.toObject(); + next(); } }); - } - } catch (err) { - console.warn("[" + call.shortName + "] Upload Error: " + err + " Filename: " + call.name + " content-length: " + req.headers['content-length']); - res.status(500); - sysStats.addError(call.toObject()); - res.contentType('json'); - res.send(JSON.stringify({ - success: false, - error: "FFProbe" - })); - fs.unlink(req.file.path, (err) => { - if (err) - console.log("error deleting: " + req.file.path); + if (err) { + console.log("[" + call.shortName + "] " + call.name + " - content-length: " + req.headers['content-length'] + " Wasabi Error", err); + } }); } + } catch (err) { + console.warn("[" + call.shortName + "] Upload Error: " + err + " Filename: " + call.name + " content-length: " + req.headers['content-length']); + res.status(500); + sysStats.addError(call.toObject()); + res.contentType('json'); + res.send(JSON.stringify({ + success: false, + error: "FFProbe" + })); + fs.unlink(req.file.path, (err) => { + if (err) + console.log("error deleting: " + req.file.path); + }); + } - }); }); + }); } diff --git a/backend/index.js b/backend/index.js index 80c0f0e..3783940 100644 --- a/backend/index.js +++ b/backend/index.js @@ -30,12 +30,17 @@ var app = express(); const server = require('http').createServer(app); -const io = require('socket.io')(server, {}); +const io = require('socket.io')(server, { + cors: { + origin: "*", + methods: ["GET", "POST"], + } +}); var Twitter = require('twitter-lite'); -io.origins('*:*'); +//io.origins('*:*'); /* io.origins((origin, callback) => { //if (origin !== 'https://foo.example.com') { diff --git a/backend/package.json b/backend/package.json index b633309..c1a0b6f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,7 +26,7 @@ "node-schedule": "1.2.4", "passport": "0.4.0", "passport-local": "1.0.0", - "socket.io": "2.1.1", + "socket.io": "^4.0.0", "twitter-lite": "1.1.0", "websocket": "^1.0.24", "ws": "^3.1.0" diff --git a/frontend/package.json b/frontend/package.json index 8acbda3..ac813ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,8 @@ "react-scripts": "5.0.1", "redux-thunk": "2.4.2", "semantic-ui-css": "2.5.0", - "semantic-ui-react": "2.1.4" + "semantic-ui-react": "2.1.4", + "socket.io-client": "^4.5.4" }, "scripts": { "start": "react-scripts start", diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 3c47ba8..ea44de6 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -26,10 +26,11 @@ import { import "./CallPlayer.css"; //import setupSocket from '../socket/SetupSocket.js' import queryString from '../query-string'; +import io from 'socket.io-client'; +import { setDateFilter } from "./call-actions"; - - +const socket = io(process.env.REACT_APP_BACKEND_SERVER ); // ---------------------------------------------------- @@ -63,7 +64,10 @@ function CallPlayer (props) { const [filterVisible, setFilterVisible] = useState(false); const [groupVisible, setGroupVisible] = useState(false); const [calendarVisible, setCalendarVisible] = useState(false); - + const [isConnected, setIsConnected] = useState(socket.connected); + + + const dispatch = useDispatch(); @@ -86,10 +90,7 @@ function CallPlayer (props) { const handleSidebarToggle = () => setSidebarOpened(!sidebarOpened); const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); - const handleLiveToggle = () => { - dispatch(setLive(!live)); - } - + const audioRef = false; const loadNewerCalls = () => { @@ -121,7 +122,7 @@ function CallPlayer (props) { } -const getFilter = () => { +const getFilterDescription = () => { var filter = { type: 'all', code: "", filterStarred: false }; @@ -141,45 +142,86 @@ const getFilter = () => { } filter.filterStarred = filterStarred return filter; +} + + + const addCall = (data) =>{ + const message = JSON.parse(data) + switch (message.type) { + case 'calls': + /*const refs = this.refs.current; + callActions.addCall(message); + const newCall = callsById[callsAllIds[0]]; + if (!isPlaying && autoPlay) { + this.playCall({ call: newCall }); + if (this.currentCallRef.current) { + this.currentCallRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + }*/ + console.log("Got: " + message); + break + default: + break + } } - /* - const endSocket = () => { - this.socket.removeAllListeners("new message"); - this.socket.removeAllListeners("reconnect"); - } - setupSocket() { - this.socket.on('new message', this.addCall); - this.socket.on('reconnect', (attempts) => { + + useEffect(() => { + socket.on('connect', () => { + setIsConnected(true); + }); + + socket.on('disconnect', () => { + setIsConnected(false); + }); + + socket.on('reconnect', (attempts) => { console.log("Socket Reconnected after attempts: " + attempts); // true + if (live) { - var filter = this.getFilter(); - this.startSocket(shortName, filter.type, filter.code, filter.filterStarred); + this.startSocket(); } }) - } - startSocket(shortName, filterType = "", filterCode = "", filterStarred = false) { - this.socket.emit("start", { - filterCode: filterCode, - filterType: filterType, + + socket.on("new message", addCall); + + return () => { + socket.off('connect'); + socket.off('disconnect'); + socket.off('pong'); + }; + }, []); + + const startSocket = () => { + const filter = getFilterDescription(); + socket.emit("start", { + filterCode: filter.Code, + filterType: filter.Type, filterName: "OpenMHz", - filterStarred: filterStarred, + filterStarred: filter.Starred, shortName: shortName }); } - stopSocket() { - this.socket.emit("stop"); + const stopSocket = () => { + socket.emit("stop"); } - handleLiveToggle() { + const handleLiveToggle = () => { if (!live) { - callActions.setDateFilter(false); - callActions.setLive(false); - this.setState({ callUrl: "", callId: false }); - callActions.fetchCalls(); - var filter = this.getFilter(); - this.startSocket(shortName, filter.type, filter.code, filter.filterStarred); + dispatch(setDateFilter(false)); + dispatch(setLive(true)); + setCallUrl(""); + setCallId(false); + + dispatch(getCalls({})); + + this.startSocket(); } } -*/ + + + /* updateUri(props, state) { var search = "?" @@ -396,11 +438,9 @@ const getFilter = () => { //talkgroupActions.fetchTalkgroups(shortName); //groupActions.fetchGroups(shortName); //systemActions.fetchSystems(); - /*this.setupSocket(); - if (filter.live) { - this.startSocket(shortName, filterType, filterCode, filter.filterStarred); - }*/ + + setStateFromUri(); //console.log("Setting short name to: " + shortName) //dispatch(setShortName(shortName)); @@ -411,9 +451,14 @@ const getFilter = () => { }, []); useEffect(() => { + /* + this.updateUri(nextProps, nextState);*/ dispatch(getCalls({})); + if (live) { + startSocket(); + } //dispatch(getOlderCalls()); - }, [shortName,filterGroupId,filterTalkgroups,filterType,filterStarred]) + }, [shortName,filterGroupId,filterTalkgroups,filterType,filterDate,filterStarred]) @@ -432,33 +477,6 @@ const getFilter = () => { } },[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) { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a308473..a6a5ecd 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2430,6 +2430,11 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053" @@ -4371,7 +4376,7 @@ debug@2.6.9, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4691,6 +4696,22 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +engine.io-client@~6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458" + integrity sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.0.3" + ws "~8.2.3" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.0.3: + version "5.0.5" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.5.tgz#c6fa52e57d8d2dc68b24754348f779aa6e44f886" + integrity sha512-mjEyaa4zhuuRhaSLOdjEb57X0XPP9JEsnXI4E+ivhwT0GgzUogARx4MqoY1jQyB+4Bkz3BUOmzL7t9RMKmlG3g== + enhanced-resolve@^5.10.0: version "5.12.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" @@ -9093,6 +9114,24 @@ smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socket.io-client@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.4.tgz#d3cde8a06a6250041ba7390f08d2468ccebc5ac9" + integrity sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.2.3" + socket.io-parser "~4.2.1" + +socket.io-parser@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.1.tgz#01c96efa11ded938dcb21cbe590c26af5eff65e5" + integrity sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -10304,6 +10343,11 @@ ws@^8.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +ws@~8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" + integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -10314,6 +10358,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From c108e6177a5ffa465da4f683c4050577e10110f2 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 14 Jan 2023 10:00:15 -0500 Subject: [PATCH 17/27] playing and auto play is working --- backend/controllers/calls.js | 2 + frontend/public/index.html | 4 - frontend/src/Call/CallPlayer.js | 69 ++++++++++---- frontend/src/Call/MediaPlayer.js | 157 +++++++++++++++---------------- 4 files changed, 129 insertions(+), 103 deletions(-) diff --git a/backend/controllers/calls.js b/backend/controllers/calls.js index 762c2ad..64c282e 100644 --- a/backend/controllers/calls.js +++ b/backend/controllers/calls.js @@ -21,6 +21,7 @@ function get_calls(query, numResults, res) { name: true, time: true, srcList: true, + freq: true, star: true, len: true, url: true @@ -43,6 +44,7 @@ function get_calls(query, numResults, res) { time: item.time, srcList: item.srcList, star: item.star, + freq: item.freq, len: Math.round(item.len) }; calls.push(call); diff --git a/frontend/public/index.html b/frontend/public/index.html index f8fac76..cd503df 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -27,10 +27,6 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - - %REACT_APP_SITE_NAME% diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index ea44de6..09062a1 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -52,9 +52,10 @@ function CallPlayer (props) { //console.log(callsData); const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] - const [callUrl, setCallUrl] = useState(""); + const [autoPlay, setAutoPlay] = useState(true); - const [callId, setCallId] = useState(false); + + const [currentCall, setCurrentCall] = useState(false); const [playTime, setPlayTime] = useState(0); const [callScroll, setCallScroll] = useState(false); const [callSelect, setCallSelect] = useState(false); @@ -79,7 +80,17 @@ function CallPlayer (props) { const live = useSelector((state) => state.callPlayer.live); const uri = queryString.parse(useLocation().search); - const switchAutoPlay = () => setAutoPlay(!autoPlay); + let currentCallId = false; + let currentCallMedia = false; + + if (currentCall) { + currentCallId = currentCall._id; + currentCallMedia = currentCall.url; + } + + + + const handlePlayPause = (playing) => setIsPlaying(playing); const handlePusherClick = () => { @@ -91,18 +102,43 @@ function CallPlayer (props) { const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); - const audioRef = false; + const loadNewerCalls = () => { } const playCall = (data) => { - - } + setCurrentCall(data.call); + setIsPlaying(true); + //callActions.fetchCallInfo(data.call._id); + } + const callEnded = () => { + if (callsData) { + const currentIndex = callsData.ids.findIndex(callId => callId === currentCallId); + if (autoPlay && (currentIndex > 0)) { + const nextCallId = callsData.ids[currentIndex - 1]; + const nextCall = callsData.entities[nextCallId]; + setCurrentCall(nextCall); + setIsPlaying(true); + /* + this.setState({ callUrl: callUrl, callId: nextCallId, isPlaying: true }, () => { audio.playSource(callUrl) }); //scrollToComponent(this.currentCallRef.current); + if (this.currentCallRef.current) { + this.currentCallRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + callActions.fetchCallInfo(nextCallId);*/ + } else { + setIsPlaying(false); + } + } else { + console.log("Somehow called, callEnded() but callsData was false"); + } } /*const changeUrl = url => callActions.changeUrl(url) @@ -211,8 +247,7 @@ const getFilterDescription = () => { if (!live) { dispatch(setDateFilter(false)); dispatch(setLive(true)); - setCallUrl(""); - setCallId(false); + setCurrentCall(false); dispatch(getCalls({})); @@ -270,8 +305,7 @@ const getFilterDescription = () => { if (didUpdate) { dispatchEvent(setLive(false)); - setCallId(false); - setCallUrl(""); + setCurrentCall(false); //callActions.fetchCalls(); //this.stopSocket(); //this.socket.close(); @@ -286,8 +320,7 @@ const getFilterDescription = () => { setFilterVisible(!filterVisible); if (didUpdate) { - setCallUrl(""); - setCallId(false); + setCurrentCall(false); } } /* @@ -414,7 +447,7 @@ const getFilterDescription = () => { } } - await dispatch(setFilter(filter)); + dispatch(setFilter(filter)); } useEffect(() => { @@ -549,7 +582,7 @@ const getFilterDescription = () => { var callInfoHeader = "Call Info"; - const currentCall = false;//callsById[callId]; + var callsAllIds = []; @@ -584,6 +617,8 @@ const getFilterDescription = () => { )) }*/ + + return (
    @@ -638,7 +673,7 @@ const getFilterDescription = () => { style={{ minHeight: '100vh' }} >
    - +
    @@ -652,8 +687,8 @@ const getFilterDescription = () => { - this.switchAutoPlay()}>Autoplay - { /* */ } + setAutoPlay(!autoPlay)}>Autoplay + Download diff --git a/frontend/src/Call/MediaPlayer.js b/frontend/src/Call/MediaPlayer.js index 0e491f6..0afcfe5 100644 --- a/frontend/src/Call/MediaPlayer.js +++ b/frontend/src/Call/MediaPlayer.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Menu, Icon, @@ -8,29 +8,29 @@ import { import ReactAudioPlayer from 'react-audio-player' -class MediaPlayer extends React.Component { - constructor(props) { - super(props); - this.updatePlayProgress = this.updatePlayProgress.bind(this); - this.handlePlay = this.handlePlay.bind(this); - this.handlePause = this.handlePause.bind(this); - this.playPause = this.playPause.bind(this); - this.audioRef = React.createRef(); - this.state = { - sourceIndex: 0, - playProgress: 0, - isPlaying: false - } - } - handlePause = () => { this.setState({ isPlaying: false }); this.props.onPlayPause(false); } - handlePlay = () => { this.setState({ isPlaying: true }); this.props.onPlayPause(true); } - playPause = () => { const audio = this.audioRef.current.audioEl.current; if (this.state.isPlaying) { audio.pause(); } else { audio.play(); } } - playSource(callUrl) { - const audio = this.audioRef.current.audioEl.current; - var onEnded = this.props.onEnded; - audio.src = callUrl; +const MediaPlayer = (props) => { + const audioRef = React.createRef(); + const [sourceIndex, setSourceIndex] = useState(0); + const [playProgress, setPlayProgress] = useState(0); + const [playTime, setPlayTime] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); + const parentHandlePlayPause = props.onPlayPause + + const handlePause = () => { setIsPlaying(false); parentHandlePlayPause(false); } + const handlePlay = () => { setIsPlaying(true); parentHandlePlayPause(true); } + const playPause = () => { const audio = audioRef.current.audioEl.current; if (isPlaying) { audio.pause(); } else { audio.play(); } } + + var call = props.call; + + useEffect(() => { + + const audio = audioRef.current.audioEl.current; + var onEnded = props.onEnded; + setSourceIndex(0); + if (call) { + audio.src = call.url; var playPromise = audio.play(); // In browsers that don’t yet support this functionality, @@ -43,82 +43,75 @@ class MediaPlayer extends React.Component { onEnded(); // Show a UI element to let the user manually start playback. }); + } else { + audio.src = false; } - } + } + }, [call]); - updatePlayProgress() { - const audio = this.audioRef.current.audioEl.current; + const updatePlayProgress = () => { + const audio = audioRef.current.audioEl.current; const { currentTime, duration } = audio; - var call = this.props.call; + // this checks to see if it should display the next Source ID - if (call && ((call.srcList.length - 1) >= (this.state.sourceIndex + 1)) && (currentTime > call.srcList[this.state.sourceIndex + 1].pos)) { - this.setState({ - sourceIndex: this.state.sourceIndex + 1 - }); + if (call && ((call.srcList.length - 1) >= (sourceIndex + 1)) && (currentTime > call.srcList[sourceIndex + 1].pos)) { + setSourceIndex(sourceIndex + 1); } // updates the play percentage progress and current playing time - this.setState({ - playProgress: currentTime / duration * 100, - playTime: Math.floor(currentTime) - }); + setPlayProgress(currentTime / duration * 100); + setPlayTime(Math.floor(currentTime)); + + } - componentDidUpdate(prevProps) { - if (this.props.call && (prevProps.call !== this.props.call)) { - this.setState({ - sourceIndex: 0 - }); + + + let playEnabled = { "disabled": true } + let sourceId = "-"; + + if (call) { + if (call.srcList.length > sourceIndex) { + sourceId = call.srcList[sourceIndex].src; } - + playEnabled = {}; } - - render() { - var playEnabled = { "disabled": true } - var sourceId = "-"; - - if (this.props.call) { - if (this.props.call.srcList.length > this.state.sourceIndex) { - sourceId = this.props.call.srcList[this.state.sourceIndex].src; - } - playEnabled = {}; - } - return ( - - + return ( + + - + - { - this.state.isPlaying - ? () - : () - } - - - - - + { + isPlaying + ? () + : () + } + + + + + - - - ) - } + + + ) } + export default MediaPlayer; From 6e331ba901729213f571bc85460aefc5b543f9ae Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 14 Jan 2023 14:36:21 -0500 Subject: [PATCH 18/27] added in path search --- frontend/src/Call/CallPlayer.js | 160 ++++++++++---------------------- frontend/src/Call/ListCalls.js | 28 ++++-- 2 files changed, 67 insertions(+), 121 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 09062a1..1fd8c1d 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; -import { Link, useLocation} from 'react-router-dom'; -import { useParams } from "react-router-dom"; +import React, { useEffect, useState, useRef } from "react"; +import { Link, useLocation, useParams, useNavigate} from 'react-router-dom'; + import MediaPlayer from "./MediaPlayer"; import FilterModal from "./FilterModal"; import GroupModal from "./GroupModal"; @@ -54,10 +54,11 @@ function CallPlayer (props) { //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] const [autoPlay, setAutoPlay] = useState(true); - + const [addCallScroll, setAddCallScroll] = useState(false); const [currentCall, setCurrentCall] = useState(false); + const [prevScrollHeight, setPrevScrollHeight] = useState(0); const [playTime, setPlayTime] = useState(0); - const [callScroll, setCallScroll] = useState(false); + const [selectLoadCall, setSelectLoadCall] = useState(false); const [callSelect, setCallSelect] = useState(false); const [urlOptions, setUrlOptions] = useState(false); const [isPlaying, setIsPlaying] = useState(false); @@ -66,11 +67,11 @@ function CallPlayer (props) { const [groupVisible, setGroupVisible] = useState(false); const [calendarVisible, setCalendarVisible] = useState(false); const [isConnected, setIsConnected] = useState(socket.connected); + const [loadCallId, setLoadCallId] = useState(false); - - + const navigate = useNavigate(); const dispatch = useDispatch(); - + const positionRef = useRef(); const filterType = useSelector((state) => state.callPlayer.filterType); const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); @@ -79,6 +80,7 @@ function CallPlayer (props) { const filterDate = useSelector((state) => state.callPlayer.filterDate); const live = useSelector((state) => state.callPlayer.live); const uri = queryString.parse(useLocation().search); + const pathname = useLocation().pathname; let currentCallId = false; let currentCallMedia = false; @@ -102,16 +104,9 @@ function CallPlayer (props) { const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); - - - const loadNewerCalls = () => { - - } - const playCall = (data) => { setCurrentCall(data.call); setIsPlaying(true); - //callActions.fetchCallInfo(data.call._id); } @@ -124,15 +119,6 @@ function CallPlayer (props) { setCurrentCall(nextCall); setIsPlaying(true); - /* - this.setState({ callUrl: callUrl, callId: nextCallId, isPlaying: true }, () => { audio.playSource(callUrl) }); //scrollToComponent(this.currentCallRef.current); - if (this.currentCallRef.current) { - this.currentCallRef.current.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - } - callActions.fetchCallInfo(nextCallId);*/ } else { setIsPlaying(false); } @@ -142,21 +128,9 @@ function CallPlayer (props) { } /*const changeUrl = url => callActions.changeUrl(url) - handleContextRef = contextRef => this.setState({ contextRef }) - loadNewerCalls() { - console.log("Loading Newer Calls"); - callActions.fetchNewerCalls(newestCallTime.getTime()); - } */ - const loadOlderCalls = (getOlderCalls) => { - - console.log("Loading Older Calls"); - dispatch(getOlderCalls({})); - - } - const getFilterDescription = () => { @@ -185,6 +159,9 @@ const getFilterDescription = () => { const message = JSON.parse(data) switch (message.type) { case 'calls': + const ref = positionRef; + setPrevScrollHeight(positionRef.clientHeight); + setAddCallScroll(true); /*const refs = this.refs.current; callActions.addCall(message); const newCall = callsById[callsAllIds[0]]; @@ -257,48 +234,45 @@ const getFilterDescription = () => { -/* - updateUri(props, state) { + +const updateUri = () => { var search = "?" - switch (props.filterType) { + switch (filterType) { case 1: - search = search + `filter-type=group&filter-code=${props.filterGroupId}`; + search = search + `filter-type=group&filter-code=${filterGroupId}`; break; case 2: - search = search + `filter-type=talkgroup&filter-code=${props.filterTalkgroups}`; + search = search + `filter-type=talkgroup&filter-code=${filterTalkgroups}`; break; default: case 0: break; } - if (state.callId) { + if (loadCallId) { if (search.length !== 1) { search = search + '&'; } - search = search + `call-id=${state.callId}` + search = search + `call-id=${loadCallId}` } - if (props.filterDate) { + if (filterDate) { if (search.length !== 1) { search = search + '&'; } - search = search + `time=${props.filterDate}`; + search = search + `time=${filterDate}`; } - if (props.filterStarred) { + if (filterStarred) { if (search.length !== 1) { search = search + '&'; } search = search + `starred=true`; } + navigate(pathname + search, {replace:true}); - history.push({ - pathname: history.location.pathname, - search: search - }); } -*/ + const handleCalendarClose = (didUpdate) => { setCalendarVisible(!calendarVisible); @@ -306,8 +280,7 @@ const getFilterDescription = () => { if (didUpdate) { dispatchEvent(setLive(false)); setCurrentCall(false); - //callActions.fetchCalls(); - //this.stopSocket(); + stopSocket(); //this.socket.close(); } } @@ -323,60 +296,7 @@ const getFilterDescription = () => { setCurrentCall(false); } } -/* - callEnded(data) { - const audio = this.audioRef.current; - 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); - if (this.currentCallRef.current) { - this.currentCallRef.current.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - } - callActions.fetchCallInfo(nextCallId); - } else { - this.setState({ isPlaying: false }); - } - } - - playCall(data) { - const audio = this.audioRef.current; - var callUrl = data.call.url; - this.setState({ - callUrl: callUrl, - callId: data.call._id, - isPlaying: true - }, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current); - callActions.fetchCallInfo(data.call._id); - } - - addCall(data) { - const message = JSON.parse(data) - switch (message.type) { - case 'calls': - const refs = this.refs.current; - callActions.addCall(message); - const newCall = callsById[callsAllIds[0]]; - if (!isPlaying && autoPlay) { - this.playCall({ call: newCall }); - if (this.currentCallRef.current) { - this.currentCallRef.current.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - } - } - break - default: - break - } - }*/ const setStateFromUri = async () => { var filter = { @@ -393,6 +313,7 @@ const getFilterDescription = () => { if (uri.hasOwnProperty('starred')) { const starred = uri['starred']; filter.filterStarred = starred === 'true' ? true : false; + this.setState({ urlOptions: true }); } @@ -408,8 +329,9 @@ const getFilterDescription = () => { if (uri.hasOwnProperty('call-id')) { const _id = uri['call-id']; const date = new Date(parseInt(uri['time'])); - - filter.live = false; + setLoadCallId(_id); + setSelectLoadCall(true); + setAutoPlay(false); this.setState({ callId: _id, urlOptions: true, @@ -463,6 +385,21 @@ const getFilterDescription = () => { } }, [loadOlderInView]); + useEffect(() => { + if (addCallScroll) { + const ref = positionRef; + window.scrollBy(0, positionRef.clientHeight - prevScrollHeight); + setAddCallScroll(false); + } + if (selectLoadCall && loadCallId && callsData) { + + const call = callsData.entities[loadCallId]; + if (call) { + setSelectLoadCall(false); + setCurrentCall(call); + } + } + },[callsData]) useEffect(() => { // When a time/date is provided in the URL, it will be added to the filter @@ -484,8 +421,7 @@ const getFilterDescription = () => { }, []); useEffect(() => { - /* - this.updateUri(nextProps, nextState);*/ + updateUri(); dispatch(getCalls({})); if (live) { startSocket(); @@ -620,7 +556,7 @@ const getFilterDescription = () => { return ( -
    +
    @@ -680,7 +616,7 @@ const getFilterDescription = () => { - + diff --git a/frontend/src/Call/ListCalls.js b/frontend/src/Call/ListCalls.js index f5f7c74..7d187e5 100644 --- a/frontend/src/Call/ListCalls.js +++ b/frontend/src/Call/ListCalls.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import CallItem from "./CallItem"; import { Icon, @@ -8,12 +8,22 @@ import { import "./CallPlayer.css"; // ---------------------------------------------------- -class ListCalls extends React.Component { +const ListCalls = (props) => { + const activeCallRef = useRef(); //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 callsData = this.props.callsData; + + useEffect(() => { + if (activeCallRef.current) { + activeCallRef.current.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + } + }); + + const callsData = props.callsData; return ( @@ -29,11 +39,11 @@ class ListCalls extends React.Component { { - this.props.callsData.ids.map((callId, index) => { - if (callId === this.props.activeCallId) { - return () + props.callsData.ids.map((callId, index) => { + if (callId === props.activeCallId) { + return () } else { - return + return } }) @@ -45,7 +55,7 @@ class ListCalls extends React.Component { ); - } + } export default ListCalls; From ba15de8d3091842f48c5bac071c258b98fc6ccf2 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 14 Jan 2023 22:43:10 -0500 Subject: [PATCH 19/27] added stars and new calls --- backend/controllers/calls.js | 34 +- backend/index.js | 2 +- frontend/src/Call/CallItem.js | 111 ++--- frontend/src/Call/CallPlayer.js | 559 +++++++++------------- frontend/src/features/calls/callsSlice.js | 51 +- 5 files changed, 345 insertions(+), 412 deletions(-) diff --git a/backend/controllers/calls.js b/backend/controllers/calls.js index 64c282e..ef3170a 100644 --- a/backend/controllers/calls.js +++ b/backend/controllers/calls.js @@ -241,7 +241,39 @@ function package_call(item) { return call; } - +exports.remove_star = function(req, res, next) { + var objectId = req.params.id; + try { + var o_id = ObjectID.createFromHexString(objectId); + } catch (err) { + console.warn("[" + req.params.shortName + "] Error - /:shortName/call/:id generating ObjectID " + err); + res.status(500); + res.send(JSON.stringify({ + success: false, + message: err, + "_id": objectId + })); + return; + } + Call.findOneAndUpdate({ _id: objectId }, { $inc: { star: -1 } }, {new: true },function(err, item) { + if (err) { + res.status(500); + res.send(JSON.stringify({ + success: false, + message: err, + "_id": objectId + })); + } else { + var call = package_call(item); + req.call = call; + res.send(JSON.stringify({ + success: true, + call: call + })); + next(); + } + }) +} exports.add_star = function(req, res, next) { diff --git a/backend/index.js b/backend/index.js index 3783940..a748c0f 100644 --- a/backend/index.js +++ b/backend/index.js @@ -178,7 +178,7 @@ app.get('/:shortName/calls/older', calls.get_older_calls); app.get('/:shortName/calls/:time/older', calls.get_iphone_calls); app.get('/:shortName/calls', calls.get_calls); app.post('/:shortName/star/:id', calls.add_star); - +app.post('/:shortName/removeStar/:id', calls.remove_star); /*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/ app.post('/:shortName/upload', upload.single('call'), uploads.upload, async function (req, res) { diff --git a/frontend/src/Call/CallItem.js b/frontend/src/Call/CallItem.js index d41949d..572ac3a 100644 --- a/frontend/src/Call/CallItem.js +++ b/frontend/src/Call/CallItem.js @@ -1,88 +1,44 @@ -import React, { Component } from "react"; +import React, { useState } from "react"; import { Table, Icon, Label } from "semantic-ui-react"; import { connect } from "react-redux" -import { bindActionCreators } from 'redux' -import * as callActionCreators from "./call-actions" +import { addStar, removeStar} from "../features/calls/callsSlice"; +import { useDispatch } from 'react-redux' -function mapStateToProps(state, props) { - return { - shortName: props.shortName +const CallItem = (props) => { - } -} - -function mapDispatchToProps(dispatch) { - return { - callActions: bindActionCreators(callActionCreators, dispatch) - } -} - - -class CallItem extends Component { -constructor(props) { - super(props); - this.hideStar = this.hideStar.bind(this); - this.showStar = this.showStar.bind(this); - this.addStar = this.addStar.bind(this); - this.state = { - starVisible: false, - starClicked: false - } -} -sourceString(call) { - var srcString = ""; - if (call.srcList) { - for (var src in call.srcList) { - /* srcNum = call.srcList[src].src; - if (sources.hasOwnProperty(srcNum)) { - srcString = srcString + sources[srcNum].codeName + ", "; - }*/ - } - } - - return srcString; -} - -componentDidUpdate(){ - if (this.props.activeCall) { - console.log("Active call mounted") - } -} -addStar(e) { - e.preventDefault(); - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - if (!this.state.starClicked) { - - this.props.callActions.addStar(this.props.call._id); - this.setState({starClicked: true}); - } -} - -hideStar() { - this.setState({ starVisible: false }) -} - -showStar() { - this.setState({ starVisible: true }) -} - -render() { - const call = this.props.call; + const [starVisible, setStarVisible] = useState(false); + const [starClicked, setStarClicked] = useState(false); + const dispatch = useDispatch(); + const call = props.call; const time = new Date(call.time); + const activeCall = props.activeCall; + + const handleStarClicked = (e) => { + e.preventDefault(); + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + if (!starClicked) { + setStarClicked(true); + dispatch(addStar(props.call._id)); + } else { + setStarClicked(false); + dispatch(removeStar(props.call._id)); + } + } + var rowSelected={}; let starButton; var starClickable = {}; - if (!this.state.starClicked) { + if (!starClicked) { starClickable = {link: true }; } - if (!call.star && this.state.starVisible) { + if (!call.star && starVisible) { starButton = } @@ -98,7 +54,7 @@ render() { ) } - if (this.props.activeCall) { + if (activeCall) { rowSelected={positive: true, color: "blue", key: "blue", @@ -106,26 +62,21 @@ render() { } } var talkgroup; - if ((typeof this.props.talkgroups == 'undefined') || (typeof this.props.talkgroups[call.talkgroupNum] == 'undefined')) { + if ((typeof props.talkgroups == 'undefined') || (typeof props.talkgroups[call.talkgroupNum] == 'undefined')) { talkgroup = call.talkgroupNum; } else { - talkgroup = this.props.talkgroups[call.talkgroupNum].description; + talkgroup = props.talkgroups[call.talkgroupNum].description; } return ( - this.props.onClick({call: call}, e)} {...rowSelected}> + props.onClick({call: call}, e)} {...rowSelected}> {call.len} {talkgroup} {time.toLocaleTimeString()} - {starButton} + setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton} ); } -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CallItem) +export default CallItem; diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 1fd8c1d..21675b5 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -1,5 +1,5 @@ -import React, { useEffect, useState, useRef } from "react"; -import { Link, useLocation, useParams, useNavigate} from 'react-router-dom'; +import React, { useEffect, useState, useRef } from "react"; +import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'; import MediaPlayer from "./MediaPlayer"; import FilterModal from "./FilterModal"; @@ -10,7 +10,7 @@ import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' import { setLive, setShortName, setFilter } from "../features/callPlayer/callPlayerSlice"; -import { getCalls, getOlderCalls, getNewerCalls} from "../features/calls/callsSlice"; +import { getCalls, getOlderCalls, getNewerCalls, addCall } from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useInView } from 'react-intersection-observer'; import { @@ -24,78 +24,76 @@ import { Button } from "semantic-ui-react"; import "./CallPlayer.css"; -//import setupSocket from '../socket/SetupSocket.js' import queryString from '../query-string'; import io from 'socket.io-client'; import { setDateFilter } from "./call-actions"; -const socket = io(process.env.REACT_APP_BACKEND_SERVER ); +const socket = io(process.env.REACT_APP_BACKEND_SERVER); // ---------------------------------------------------- -function CallPlayer (props) { +function CallPlayer(props) { - const { shortName } = useParams(); - const { ref: loadOlderRef, inView: loadOlderInView, entry: loadOlderEntry } = useInView({ - /* Optional options */ - threshold: 0.5 - }); - const { ref: loadNewerRef, inView: loadNewerInView, entry: loadNewerEntry } = useInView({ - /* Optional options */ - threshold: 0.5 - }); - const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(shortName); - //const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); - const {loading: callsLoading, data: callsData} = useSelector((state) => state.calls); - - //console.log(callsData); - const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); - //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] + const { shortName } = useParams(); + const { ref: loadOlderRef, inView: loadOlderInView, entry: loadOlderEntry } = useInView({ + /* Optional options */ + threshold: 0.5 + }); + const { ref: loadNewerRef, inView: loadNewerInView, entry: loadNewerEntry } = useInView({ + /* Optional options */ + threshold: 0.5 + }); + const { data: groupsData, isSuccess: isGroupsSuccess } = useGetGroupsQuery(shortName); + //const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); + const { loading: callsLoading, data: callsData } = useSelector((state) => state.calls); - const [autoPlay, setAutoPlay] = useState(true); - const [addCallScroll, setAddCallScroll] = useState(false); - const [currentCall, setCurrentCall] = useState(false); - const [prevScrollHeight, setPrevScrollHeight] = useState(0); - const [playTime, setPlayTime] = useState(0); - const [selectLoadCall, setSelectLoadCall] = 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 [isConnected, setIsConnected] = useState(socket.connected); - const [loadCallId, setLoadCallId] = useState(false); + //console.log(callsData); + const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); + //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] - const navigate = useNavigate(); - const dispatch = useDispatch(); - const positionRef = useRef(); + const [autoPlay, setAutoPlay] = useState(true); + const [addCallScroll, setAddCallScroll] = useState(false); + const [currentCall, setCurrentCall] = useState(false); + const [prevScrollHeight, setPrevScrollHeight] = useState(0); + const [playTime, setPlayTime] = useState(0); + const [selectLoadCall, setSelectLoadCall] = 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 [isConnected, setIsConnected] = useState(socket.connected); + const [loadCallId, setLoadCallId] = useState(false); - 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 pathname = useLocation().pathname; + const navigate = useNavigate(); + const dispatch = useDispatch(); + const positionRef = useRef(); - let currentCallId = false; - let currentCallMedia = false; + 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 pathname = useLocation().pathname; - if (currentCall) { - currentCallId = currentCall._id; - currentCallMedia = currentCall.url; - } + let currentCallId = false; + let currentCallMedia = false; + + if (currentCall) { + currentCallId = currentCall._id; + currentCallMedia = currentCall.url; + } const handlePlayPause = (playing) => setIsPlaying(playing); - - const handlePusherClick = () => { + + const handlePusherClick = () => { if (sidebarOpened) setSidebarOpened(false); } @@ -105,35 +103,30 @@ function CallPlayer (props) { const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); const playCall = (data) => { - setCurrentCall(data.call); - setIsPlaying(true); - } - + setCurrentCall(data.call); + setIsPlaying(true); + } + const callEnded = () => { if (callsData) { - const currentIndex = callsData.ids.findIndex(callId => callId === currentCallId); - if (autoPlay && (currentIndex > 0)) { - const nextCallId = callsData.ids[currentIndex - 1]; - const nextCall = callsData.entities[nextCallId]; + const currentIndex = callsData.ids.findIndex(callId => callId === currentCallId); + if (autoPlay && (currentIndex > 0)) { + const nextCallId = callsData.ids[currentIndex - 1]; + const nextCall = callsData.entities[nextCallId]; - setCurrentCall(nextCall); - setIsPlaying(true); + setCurrentCall(nextCall); + setIsPlaying(true); + } else { + setIsPlaying(false); + } } else { - setIsPlaying(false); + console.log("Somehow called, callEnded() but callsData was false"); } - } else { - console.log("Somehow called, callEnded() but callsData was false"); } - } - /*const changeUrl = url => callActions.changeUrl(url) + const getFilterDescription = () => { -*/ - - -const getFilterDescription = () => { - var filter = { type: 'all', code: "", filterStarred: false }; switch (filterType) { @@ -152,16 +145,17 @@ const getFilterDescription = () => { } filter.filterStarred = filterStarred return filter; -} + } - const addCall = (data) =>{ + const handleSocketMessage = (data) => { const message = JSON.parse(data) switch (message.type) { case 'calls': const ref = positionRef; setPrevScrollHeight(positionRef.clientHeight); setAddCallScroll(true); + dispatch(addCall(message)) /*const refs = this.refs.current; callActions.addCall(message); const newCall = callsById[callsAllIds[0]]; @@ -181,32 +175,6 @@ const getFilterDescription = () => { } } - useEffect(() => { - socket.on('connect', () => { - setIsConnected(true); - }); - - socket.on('disconnect', () => { - setIsConnected(false); - }); - - socket.on('reconnect', (attempts) => { - console.log("Socket Reconnected after attempts: " + attempts); // true - - if (live) { - this.startSocket(); - } - }) - - socket.on("new message", addCall); - - return () => { - socket.off('connect'); - socket.off('disconnect'); - socket.off('pong'); - }; - }, []); - const startSocket = () => { const filter = getFilterDescription(); socket.emit("start", { @@ -227,7 +195,7 @@ const getFilterDescription = () => { setCurrentCall(false); dispatch(getCalls({})); - + this.startSocket(); } } @@ -235,7 +203,7 @@ const getFilterDescription = () => { -const updateUri = () => { + const updateUri = () => { var search = "?" switch (filterType) { @@ -269,7 +237,7 @@ const updateUri = () => { } search = search + `starred=true`; } - navigate(pathname + search, {replace:true}); + navigate(pathname + search, { replace: true }); } @@ -313,8 +281,7 @@ const updateUri = () => { if (uri.hasOwnProperty('starred')) { const starred = uri['starred']; filter.filterStarred = starred === 'true' ? true : false; - - this.setState({ urlOptions: true }); + if (!urlOptions) setUrlOptions(true); } // is there a time based filter? @@ -322,7 +289,7 @@ const updateUri = () => { const date = new Date(parseInt(uri['time'])); filter.filterDate = date.getTime(); filter.live = false; - this.setState({ urlOptions: true }); + if (!urlOptions) setUrlOptions(true); } // is this just for one call? @@ -332,13 +299,7 @@ const updateUri = () => { setLoadCallId(_id); setSelectLoadCall(true); setAutoPlay(false); - this.setState({ - callId: _id, - urlOptions: true, - autoPlay: false, - callScroll: true, - callSelect: true - }); + if (!urlOptions) setUrlOptions(true); //callActions.fetchCallInfo(_id); //callActions.setCallTime(date); } @@ -349,7 +310,7 @@ const updateUri = () => { // is there a Filter set? if ((uri.hasOwnProperty('filter-code')) && (uri.hasOwnProperty('filter-type'))) { - this.setState({ urlOptions: true }); + if (!urlOptions) setUrlOptions(true); // The Filter is a group if (uri['filter-type'] === "group") { @@ -399,247 +360,187 @@ const updateUri = () => { setCurrentCall(call); } } - },[callsData]) + }, [callsData]) 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 - //callActions.fetchCalls(); - //talkgroupActions.fetchTalkgroups(shortName); - //groupActions.fetchGroups(shortName); - //systemActions.fetchSystems(); - - - setStateFromUri(); - //console.log("Setting short name to: " + shortName) - //dispatch(setShortName(shortName)); - return () => { - //this.endSocket(); + stopSocket(); + }; + }, []); + + useEffect(() => { + socket.on('connect', () => { + setIsConnected(true); + }); + + socket.on('disconnect', () => { + setIsConnected(false); + }); + + socket.on('reconnect', (attempts) => { + console.log("Socket Reconnected after attempts: " + attempts); // true + + if (live) { + this.startSocket(); + } + }) + + socket.on("new message", handleSocketMessage); + + return () => { + socket.off('connect'); + socket.off('disconnect'); + socket.off('pong'); }; }, []); useEffect(() => { - updateUri(); dispatch(getCalls({})); if (live) { startSocket(); } - //dispatch(getOlderCalls()); - }, [shortName,filterGroupId,filterTalkgroups,filterType,filterDate,filterStarred]) + }, [shortName, filterGroupId, filterTalkgroups, filterType, filterDate, filterStarred]) + // Update the Browser URI when any relevant values change + useEffect(() => { + updateUri(); + }, [filterGroupId, filterTalkgroups, filterType, filterDate, filterStarred, loadCallId]) - const compareArray = (a1, a2) => { - var i = a1.length; - while (i--) { - if (a1[i] !== a2[i]) - return false; - } - return true - } - useEffect( () => { + useEffect(() => { if (!urlOptions && groupsData && (groupsData.length > 0)) { setGroupVisible(true); } - },[groupsData] ) - - /* - componentWillUpdate(nextProps, nextState) { + }, [groupsData]) - const callsChanged = nextProps.callsAllIds[0] !== callsAllIds[0]; - if (callsChanged) { - const { contextRef } = this.state; - this.scrollPos = contextRef.scrollTop; - this.prevHeight = contextRef.clientHeight; - this.autoScroll = true; + var archiveLabel = ""; + if (filterDate) { + const filterDateObj = new Date(filterDate); + archiveLabel = filterDateObj.toLocaleDateString() + " " + filterDateObj.toLocaleTimeString() + } + + var callInfoHeader = "Call Info"; + var callLink = "" + var callDownload = "" + if (currentCall) { + if ((talkgroupsData) && talkgroupsData[currentCall.talkgroupNum]) { + callInfoHeader = talkgroupsData[currentCall.talkgroupNum].description; } - - - 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 ); - if (this.autoScroll) { - this.autoScroll = false; - window.scrollBy(0, contextRef.clientHeight - this.prevHeight); - } - - // scroll to a call once the calls have loaded - if (callScroll && (callsAllIds.length > prevProps.callsAllIds.length)) { - const call = callsById[callId]; - if (call) { - this.setState({ callScroll: false }); - if (this.currentCallRef.current) { - this.currentCallRef.current.scrollIntoView({ - behavior: "auto", - block: "center", - }); - } - this.scrollPos = contextRef.scrollTop; - this.prevHeight = contextRef.clientHeight; - } - } - - if (callSelect) { - const call = callsById[callId]; - if (call) { - const audio = this.audioRef.current; - var callUrl = call.url; - this.setState({ - callUrl: callUrl, - isPlaying: true, - callSelect: false - }, () => { 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 - - //const { contextRef } = this.state - - var archiveLabel = ""; - if (filterDate) { - const filterDate = new Date(filterDate); - archiveLabel = filterDate.toLocaleDateString() + " " + filterDate.toLocaleTimeString() - } - - var callInfoHeader = "Call Info"; - - - - - var callsAllIds = []; - var callsById = false; - var callLink = "" - var callDownload = "" - if (currentCall) { - if ((talkgroupsData) && talkgroupsData[currentCall.talkgroupNum]) { - callInfoHeader = talkgroupsData[currentCall.talkgroupNum].description; - } - const callDate = new Date(currentCall.time); - 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" + const callDate = new Date(currentCall.time); + var search = "" switch (filterType) { - case 1: - filterLabel = "Group" - break; + case 1: + search = `filter-type=group&filter-code=${filterGroupId}&`; + break; case 2: - filterLabel = "Talkgroups" + search = `filter-type=talkgroup&filter-code=${filterTalkgroups}&`; break; default: case 0: - filterLabel = "All" + break; + } - /* if (calls) { - var callRows = calls.map((call) => - - )) - }*/ - - - return ( -
    - - - - - - - - Home - Systems - About - - - - - - - - Filter - - - - {/*system && system.name*/} - - - - - - { - archiveLabel - ? archiveLabel - : "Archive" - } - - - - - Live - - - - - - - - - - -
    - -
    - - - - - - - - - - - - - setAutoPlay(!autoPlay)}>Autoplay - - - - Download - Link - - - -
    - - - - - - ); + callLink = "/system/" + shortName + "?" + search + "call-id=" + currentCall._id + "&time=" + (callDate.getTime() + 1); + callDownload = currentCall.url; } + const archive = process.env.REACT_APP_ARCHIVE_DAYS + + var filterLabel = "All" + switch (filterType) { + case 1: + filterLabel = "Group" + break; + + case 2: + filterLabel = "Talkgroups" + break; + default: + case 0: + filterLabel = "All" + } + + return ( +
    + + + + + + + + Home + Systems + About + + + + + + + + Filter + + + + {/*system && system.name*/} + + + + + + { + archiveLabel + ? archiveLabel + : "Archive" + } + + + + + Live + + + + + + + +
    + +
    + + + + + + + + + + + + + setAutoPlay(!autoPlay)}>Autoplay + + + + Download + Link + + + +
    + ); +} + export default CallPlayer; diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 2d446ca..9ed497c 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -125,6 +125,44 @@ const initialState = { return url; } +export const addStar = createAsyncThunk( + 'calls/addStar', + async(callId,{getState, requestIdleCallback}) => { + const {shortName} = getState().callPlayer; + const url = process.env.REACT_APP_BACKEND_SERVER + "/" + shortName.toLowerCase() + "/star/" + callId + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: "{}" + + }).then( + (data) => data.json() + ) + return res; + } +) + +export const removeStar = createAsyncThunk( + 'calls/removeStar', + async(callId,{getState, requestIdleCallback}) => { + const {shortName} = getState().callPlayer; + const url = process.env.REACT_APP_BACKEND_SERVER + "/" + shortName.toLowerCase() + "/removeStar/" + callId + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: "{}" + + }).then( + (data) => data.json() + ) + return res; + } +) + export const getCalls = createAsyncThunk( 'calls/getCalls', async({},{getState, requestId}) => { @@ -162,7 +200,12 @@ export const getCalls = createAsyncThunk( export const callsSlice = createSlice({ name: 'calls', initialState, - reducers: {}, + reducers: { + + addCall: (call) => { + state.data = callsAdapter.addOne(state.data, call) + }, + }, extraReducers: { [getCalls.pending]: (state) => { state.loading = true; @@ -218,6 +261,12 @@ export const callsSlice = createSlice({ state.oldestCallTime = new Date(lastTime) } }, + [addStar.fulfilled]: (state, {payload}) => { + state.data.entities[payload.call._id] = payload.call; + }, + [removeStar.fulfilled]: (state, {payload}) => { + state.data.entities[payload.call._id] = payload.call; + } } }) From be002d3c84f981caaea255726e38f6fb087f0081 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 15 Jan 2023 09:40:20 -0500 Subject: [PATCH 20/27] websockets working! --- frontend/src/Call/CallInfo.js | 5 -- frontend/src/Call/CallPlayer.js | 59 +++++++++++------------ frontend/src/features/calls/callsSlice.js | 6 +-- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/frontend/src/Call/CallInfo.js b/frontend/src/Call/CallInfo.js index 9d1543d..1818242 100644 --- a/frontend/src/Call/CallInfo.js +++ b/frontend/src/Call/CallInfo.js @@ -12,8 +12,6 @@ import { // ---------------------------------------------------- function CallInfo(props) { - - let srcList = ""; let callLength = "-"; let callFreq = "-"; @@ -39,8 +37,6 @@ function CallInfo(props) { callTweet = "https://twitter.com/intent/tweet?url="+encodeURIComponent(document.location.origin+props.link)+"&via="+encodeURIComponent("OpenMHz"); } - - return (
    @@ -86,7 +82,6 @@ function CallInfo(props) {
    ); - } export default CallInfo; diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 21675b5..b794f64 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -70,6 +70,8 @@ function CallPlayer(props) { const navigate = useNavigate(); const dispatch = useDispatch(); const positionRef = useRef(); + const isPlayingRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback + isPlayingRef.current = isPlaying; const filterType = useSelector((state) => state.callPlayer.filterType); const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); @@ -91,7 +93,9 @@ function CallPlayer(props) { - const handlePlayPause = (playing) => setIsPlaying(playing); + const handlePlayPause = (playing) => { + setIsPlaying(playing); + } const handlePusherClick = () => { if (sidebarOpened) setSidebarOpened(false); @@ -155,19 +159,12 @@ function CallPlayer(props) { const ref = positionRef; setPrevScrollHeight(positionRef.clientHeight); setAddCallScroll(true); - dispatch(addCall(message)) - /*const refs = this.refs.current; - callActions.addCall(message); - const newCall = callsById[callsAllIds[0]]; - if (!isPlaying && autoPlay) { - this.playCall({ call: newCall }); - if (this.currentCallRef.current) { - this.currentCallRef.current.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - } - }*/ + + if (!isPlayingRef.current) { + setCurrentCall(message); + } + + dispatch(addCall(message)); console.log("Got: " + message); break default: @@ -177,31 +174,18 @@ function CallPlayer(props) { const startSocket = () => { const filter = getFilterDescription(); + socket.emit("start", { - filterCode: filter.Code, - filterType: filter.Type, + filterCode: filter.code, + filterType: filter.type, filterName: "OpenMHz", - filterStarred: filter.Starred, + filterStarred: filter.starred, shortName: shortName }); } const stopSocket = () => { socket.emit("stop"); } - const handleLiveToggle = () => { - if (!live) { - dispatch(setDateFilter(false)); - dispatch(setLive(true)); - setCurrentCall(false); - - dispatch(getCalls({})); - - this.startSocket(); - } - } - - - const updateUri = () => { var search = "?" @@ -241,6 +225,16 @@ function CallPlayer(props) { } + const handleLiveToggle = () => { + if (!live) { + dispatch(setDateFilter(false)); + dispatch(setLive(true)); + setCurrentCall(false); + dispatch(getCalls({})); + + this.startSocket(); + } + } const handleCalendarClose = (didUpdate) => { setCalendarVisible(!calendarVisible); @@ -386,7 +380,7 @@ function CallPlayer(props) { } }) - socket.on("new message", handleSocketMessage); + socket.on("new message", handleSocketMessage ); return () => { socket.off('connect'); @@ -416,6 +410,7 @@ function CallPlayer(props) { }, [groupsData]) + var archiveLabel = ""; if (filterDate) { const filterDateObj = new Date(filterDate); diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 9ed497c..22ee104 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -202,8 +202,8 @@ export const callsSlice = createSlice({ initialState, reducers: { - addCall: (call) => { - state.data = callsAdapter.addOne(state.data, call) + addCall: (state, action) => { + state.data = callsAdapter.addOne(state.data, action.payload) }, }, extraReducers: { @@ -269,5 +269,5 @@ export const callsSlice = createSlice({ } } }) - +export const { addCall } = callsSlice.actions export const callsReducer = callsSlice.reducer; From dfae44d861c7be512d050f0f2ae726047f536604 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 15 Jan 2023 09:56:25 -0500 Subject: [PATCH 21/27] really really working now --- frontend/src/Call/CallPlayer.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index b794f64..fb8b13e 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -70,8 +70,8 @@ function CallPlayer(props) { const navigate = useNavigate(); const dispatch = useDispatch(); const positionRef = useRef(); - const isPlayingRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback - isPlayingRef.current = isPlaying; + const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback + shouldPlayAddCallRef.current = (!isPlaying && autoPlay)?true:false; const filterType = useSelector((state) => state.callPlayer.filterType); const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); @@ -156,11 +156,11 @@ function CallPlayer(props) { const message = JSON.parse(data) switch (message.type) { case 'calls': - const ref = positionRef; - setPrevScrollHeight(positionRef.clientHeight); + const ref = positionRef.current; + setPrevScrollHeight(positionRef.current.clientHeight); setAddCallScroll(true); - if (!isPlayingRef.current) { + if (shouldPlayAddCallRef.current) { setCurrentCall(message); } @@ -342,8 +342,8 @@ function CallPlayer(props) { useEffect(() => { if (addCallScroll) { - const ref = positionRef; - window.scrollBy(0, positionRef.clientHeight - prevScrollHeight); + const ref = positionRef.current; + window.scrollBy(0, positionRef.current.clientHeight - prevScrollHeight); setAddCallScroll(false); } if (selectLoadCall && loadCallId && callsData) { From 14906865a15407c3563072507975d0d41ad1a9a3 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 16 Jan 2023 09:40:12 -0500 Subject: [PATCH 22/27] Fixed AddCall Scrolling --- frontend/src/Call/CallPlayer.js | 65 ++++++++++++----------- frontend/src/features/calls/callsSlice.js | 9 ++-- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index fb8b13e..b687f63 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useLayoutEffect, useState, useRef } from "react"; import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'; import MediaPlayer from "./MediaPlayer"; @@ -9,7 +9,7 @@ import CalendarModal from "./CalendarModalContainer"; import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' -import { setLive, setShortName, setFilter } from "../features/callPlayer/callPlayerSlice"; +import { setLive, setShortName, setFilter,setDateFilter } from "../features/callPlayer/callPlayerSlice"; import { getCalls, getOlderCalls, getNewerCalls, addCall } from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useInView } from 'react-intersection-observer'; @@ -26,7 +26,7 @@ import { import "./CallPlayer.css"; import queryString from '../query-string'; import io from 'socket.io-client'; -import { setDateFilter } from "./call-actions"; + const socket = io(process.env.REACT_APP_BACKEND_SERVER); @@ -45,19 +45,14 @@ function CallPlayer(props) { threshold: 0.5 }); const { data: groupsData, isSuccess: isGroupsSuccess } = useGetGroupsQuery(shortName); - //const { data:callsData, isSuccess:isCallsSuccess } = useGetCallsQuery({shortName}); const { loading: callsLoading, data: callsData } = useSelector((state) => state.calls); - - //console.log(callsData); const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); - //const allCalls = callsData?callsData.ids.map( id => callsData.entities[id] ):[] + const [autoPlay, setAutoPlay] = useState(true); const [addCallScroll, setAddCallScroll] = useState(false); const [currentCall, setCurrentCall] = useState(false); const [prevScrollHeight, setPrevScrollHeight] = useState(0); - const [playTime, setPlayTime] = useState(0); - const [selectLoadCall, setSelectLoadCall] = useState(false); const [urlOptions, setUrlOptions] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [sidebarOpened, setSidebarOpened] = useState(false); @@ -69,7 +64,8 @@ function CallPlayer(props) { const navigate = useNavigate(); const dispatch = useDispatch(); - const positionRef = useRef(); + const positionRef = useRef(); // lets us get the Y Scroll offset for the Call List + const pageYOffset = useRef(); // Store the current Scroll Offset in a way that guarantees the latest value is available when Call Data is updated const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback shouldPlayAddCallRef.current = (!isPlaying && autoPlay)?true:false; @@ -83,11 +79,10 @@ function CallPlayer(props) { const pathname = useLocation().pathname; let currentCallId = false; - let currentCallMedia = false; + if (currentCall) { currentCallId = currentCall._id; - currentCallMedia = currentCall.url; } @@ -101,7 +96,9 @@ function CallPlayer(props) { if (sidebarOpened) setSidebarOpened(false); } - + const handleAutoPlay = (currentAutoPlay) => { + setAutoPlay(!currentAutoPlay); + } const handleSidebarToggle = () => setSidebarOpened(!sidebarOpened); const handleFilterToggle = () => setFilterVisible(!filterVisible); const handleCalendarToggle = () => setCalendarVisible(!calendarVisible); @@ -157,15 +154,17 @@ function CallPlayer(props) { switch (message.type) { case 'calls': const ref = positionRef.current; + pageYOffset.current = positionRef.current.clientHeight; setPrevScrollHeight(positionRef.current.clientHeight); + console.log("Prior to setAddCallScroll, addCallScroll was: " + addCallScroll); setAddCallScroll(true); - if (shouldPlayAddCallRef.current) { setCurrentCall(message); } - + console.log("Prior to addCall, addCallScroll was: " + addCallScroll); dispatch(addCall(message)); - console.log("Got: " + message); + + console.log("Got: " + message._id); break default: break @@ -225,14 +224,14 @@ function CallPlayer(props) { } - const handleLiveToggle = () => { + const handleLiveToggle = (currentlyLive) => { if (!live) { dispatch(setDateFilter(false)); dispatch(setLive(true)); setCurrentCall(false); dispatch(getCalls({})); - this.startSocket(); + startSocket(); } } @@ -240,10 +239,9 @@ function CallPlayer(props) { setCalendarVisible(!calendarVisible); if (didUpdate) { - dispatchEvent(setLive(false)); + dispatch(setLive(false)); setCurrentCall(false); stopSocket(); - //this.socket.close(); } } @@ -291,7 +289,6 @@ function CallPlayer(props) { const _id = uri['call-id']; const date = new Date(parseInt(uri['time'])); setLoadCallId(_id); - setSelectLoadCall(true); setAutoPlay(false); if (!urlOptions) setUrlOptions(true); //callActions.fetchCallInfo(_id); @@ -341,21 +338,27 @@ function CallPlayer(props) { }, [loadOlderInView]); useEffect(() => { - if (addCallScroll) { - const ref = positionRef.current; - window.scrollBy(0, positionRef.current.clientHeight - prevScrollHeight); - setAddCallScroll(false); - } - if (selectLoadCall && loadCallId && callsData) { - + if ( loadCallId && callsData) { const call = callsData.entities[loadCallId]; if (call) { - setSelectLoadCall(false); setCurrentCall(call); } } }, [callsData]) + useLayoutEffect( () => { + const scrollAmount = parseInt(positionRef.current.clientHeight) - parseInt(pageYOffset.current); + if (scrollAmount > 0) { + console.log("useLayoutEffect for callsData - state prevScrollHeight: " + prevScrollHeight + " ref: " + pageYOffset.current + " current: " + positionRef.current.clientHeight + " Scroll Amount: " + scrollAmount) + window.scrollBy(0, scrollAmount); + } + }, [callsData]) + +/* + useEffect(() => { + + }, [callsData]); +*/ useEffect(() => { setStateFromUri(); return () => { @@ -510,7 +513,7 @@ function CallPlayer(props) { style={{ minHeight: '100vh' }} >
    - +
    @@ -524,7 +527,7 @@ function CallPlayer(props) { - setAutoPlay(!autoPlay)}>Autoplay + handleAutoPlay(autoPlay)}>Autoplay diff --git a/frontend/src/features/calls/callsSlice.js b/frontend/src/features/calls/callsSlice.js index 22ee104..a0c5b87 100644 --- a/frontend/src/features/calls/callsSlice.js +++ b/frontend/src/features/calls/callsSlice.js @@ -66,6 +66,7 @@ const initialState = { data: callsAdapter.getInitialState(), oldestCallTime: now, newestCallTime: now, + adjustCallScroll: false } function buildUrlParams(a) { @@ -201,9 +202,9 @@ export const callsSlice = createSlice({ name: 'calls', initialState, reducers: { - addCall: (state, action) => { state.data = callsAdapter.addOne(state.data, action.payload) + state.adjustCallScroll = true; }, }, extraReducers: { @@ -219,7 +220,7 @@ export const callsSlice = createSlice({ if (state.data && (state.data.ids.length > 0)) { const first = state.data.ids[0] const firstTime = state.data.entities[first].time; - const last = state.data.ids[state.data.ids[0].length-1]; + const last = state.data.ids[state.data.ids.length-1]; const lastTime = state.data.entities[last].time; state.newestCallTime = new Date(firstTime) state.oldestCallTime = new Date(lastTime) @@ -237,7 +238,7 @@ export const callsSlice = createSlice({ if (state.data && (state.data.ids.length > 0)) { const first = state.data.ids[0] const firstTime = state.data.entities[first].time; - const last = state.data.ids[state.data.ids[0].length-1]; + const last = state.data.ids[state.data.ids.length-1]; const lastTime = state.data.entities[last].time; state.newestCallTime = new Date(firstTime) state.oldestCallTime = new Date(lastTime) @@ -255,7 +256,7 @@ export const callsSlice = createSlice({ if (state.data && (state.data.ids.length > 0)) { const first = state.data.ids[0] const firstTime = state.data.entities[first].time; - const last = state.data.ids[state.data.ids[0].length-1]; + const last = state.data.ids[state.data.ids.length-1]; const lastTime = state.data.entities[last].time; state.newestCallTime = new Date(firstTime) state.oldestCallTime = new Date(lastTime) From 6f50a7b9c8c8cb2c57eb0a06c7fdde43602b2102 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 16 Jan 2023 21:16:13 -0500 Subject: [PATCH 23/27] fixed up the other windows --- frontend/src/About/AboutComponent.js | 77 ++++++------ frontend/src/Call/CalendarModalContainer.js | 28 ----- frontend/src/Call/CallPlayer.js | 11 +- frontend/src/Call/CallPlayerContainer.js | 69 ----------- frontend/src/Call/FilterModalContainer.js | 34 ------ frontend/src/Call/GroupModalContainer.js | 27 ----- frontend/src/Main/Main.js | 123 +++++++++++--------- frontend/src/Main/MainContainer.js | 16 --- frontend/src/System/ListSystems.js | 20 ++-- frontend/src/features/api/apiSlice.js | 23 +++- frontend/src/index.js | 8 +- 11 files changed, 141 insertions(+), 295 deletions(-) delete mode 100644 frontend/src/Call/CalendarModalContainer.js delete mode 100644 frontend/src/Call/CallPlayerContainer.js delete mode 100644 frontend/src/Call/FilterModalContainer.js delete mode 100644 frontend/src/Call/GroupModalContainer.js delete mode 100644 frontend/src/Main/MainContainer.js diff --git a/frontend/src/About/AboutComponent.js b/frontend/src/About/AboutComponent.js index 9a41605..afb6345 100644 --- a/frontend/src/About/AboutComponent.js +++ b/frontend/src/About/AboutComponent.js @@ -3,45 +3,42 @@ import { Link } from 'react-router-dom' import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react"; -class AboutComponent extends Component { +const AboutComponent = () => { + return ( +
    + + Home + Listen + + +
    About
    + +

    I wrote Trunk Recorder 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.

    +

    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.

    + +

    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.

    +

    - Luke luke@robotastic.com

    + +
    Open Source
    +

    Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:

    +

    Trunk Server

    +
    Support
    +

    If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.

    + +

    + +
    +
    + ); +} - render() { - return ( -
    - - Home - Listen - - -
    About
    - -

    I wrote Trunk Recorder 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.

    -

    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.

    - -

    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.

    -

    - Luke luke@robotastic.com

    - -
    Open Source
    -

    Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:

    -

    Trunk Server

    -
    Support
    -

    If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.

    - -

    - -
    -
    - ); - } - } - - export default AboutComponent; \ No newline at end of file +export default AboutComponent; \ No newline at end of file diff --git a/frontend/src/Call/CalendarModalContainer.js b/frontend/src/Call/CalendarModalContainer.js deleted file mode 100644 index 4e30e1a..0000000 --- a/frontend/src/Call/CalendarModalContainer.js +++ /dev/null @@ -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) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index b687f63..b476b65 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -5,7 +5,7 @@ import MediaPlayer from "./MediaPlayer"; import FilterModal from "./FilterModal"; import GroupModal from "./GroupModal"; import SupportModal from "./SupportModal"; -import CalendarModal from "./CalendarModalContainer"; +import CalendarModal from "./CalendarModal"; import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' @@ -50,9 +50,7 @@ function CallPlayer(props) { const [autoPlay, setAutoPlay] = useState(true); - const [addCallScroll, setAddCallScroll] = useState(false); const [currentCall, setCurrentCall] = useState(false); - const [prevScrollHeight, setPrevScrollHeight] = useState(0); const [urlOptions, setUrlOptions] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [sidebarOpened, setSidebarOpened] = useState(false); @@ -155,13 +153,10 @@ function CallPlayer(props) { case 'calls': const ref = positionRef.current; pageYOffset.current = positionRef.current.clientHeight; - setPrevScrollHeight(positionRef.current.clientHeight); - console.log("Prior to setAddCallScroll, addCallScroll was: " + addCallScroll); - setAddCallScroll(true); + if (shouldPlayAddCallRef.current) { setCurrentCall(message); } - console.log("Prior to addCall, addCallScroll was: " + addCallScroll); dispatch(addCall(message)); console.log("Got: " + message._id); @@ -349,7 +344,7 @@ function CallPlayer(props) { useLayoutEffect( () => { const scrollAmount = parseInt(positionRef.current.clientHeight) - parseInt(pageYOffset.current); 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); } }, [callsData]) diff --git a/frontend/src/Call/CallPlayerContainer.js b/frontend/src/Call/CallPlayerContainer.js deleted file mode 100644 index fb12af3..0000000 --- a/frontend/src/Call/CallPlayerContainer.js +++ /dev/null @@ -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 ( - - ); - } - - 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)) diff --git a/frontend/src/Call/FilterModalContainer.js b/frontend/src/Call/FilterModalContainer.js deleted file mode 100644 index 0181368..0000000 --- a/frontend/src/Call/FilterModalContainer.js +++ /dev/null @@ -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) diff --git a/frontend/src/Call/GroupModalContainer.js b/frontend/src/Call/GroupModalContainer.js deleted file mode 100644 index b5109f0..0000000 --- a/frontend/src/Call/GroupModalContainer.js +++ /dev/null @@ -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) diff --git a/frontend/src/Main/Main.js b/frontend/src/Main/Main.js index 8545836..dc1aa96 100644 --- a/frontend/src/Main/Main.js +++ b/frontend/src/Main/Main.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { Component, useState, useEffect, useRef } from "react"; import { Link } from 'react-router-dom' import SystemCard from "../System/SystemCard"; import "./Main.css"; @@ -17,6 +17,9 @@ import { } 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 */ 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 */ -class DesktopContainer extends Component { - state = {} +const DesktopContainer = (props) => { + const [fixed, setFixed] = useState(false); + const hideFixedMenu = () => setFixed(false) + const showFixedMenu = () => setFixed(true) - hideFixedMenu = () => this.setState({ fixed: false }) - showFixedMenu = () => this.setState({ fixed: true }) + + const { children } = props - render() { - const { children } = this.props - const { fixed } = this.state return ( @@ -128,26 +130,24 @@ class DesktopContainer extends Component { ) } -} + /* DesktopContainer.propTypes = { children: PropTypes.node, } */ -class MobileContainer extends Component { - state = {} +const MobileContainer = (props) => { - handlePusherClick = () => { - const { sidebarOpened } = this.state + const [sidebarOpened, setSidebarOpened] = useState(false); - 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 { sidebarOpened } = this.state + + const { children } = props return ( @@ -162,7 +162,7 @@ class MobileContainer extends Component {
    @@ -181,7 +181,7 @@ class MobileContainer extends Component { - + {process.env.REACT_APP_SITE_NAME} @@ -196,7 +196,6 @@ class MobileContainer extends Component { ) } -} /* MobileContainer.propTypes = { children: PropTypes.node, @@ -210,41 +209,62 @@ const ResponsiveContainer = ({ children }) => ( ) // ---------------------------------------------------- -class Main extends Component { - constructor(props) { - super(props); - this.advanceSystem = this.advanceSystem.bind(this); - this.state = { duration: 500, visible: true, currentSystem: 0} - } +const Main = (props) => { - - - handleChange = (e, { name, value }) => this.setState({ [name]: value }) - - handleVisibility = () => this.setState({ visible: !this.state.visible }) - - advanceSystem() { - var currentSystem = this.state.currentSystem + 1; - if (currentSystem > this.props.system.items.length) { - currentSystem = 0; - } - this.setState({ visible: !this.state.visible, currentSystem }); - } - - componentDidMount() { - this.props.fetchSystems(); - this.interval = setInterval(() => this.advanceSystem(), 3000); - } + const [visible, setVisible] = useState(true); + const [duration, setDuration] = useState(500); + const [currentSystem, setCurrentSystem] = useState(0); + const dispatch = useDispatch(); + const {data:systems, isSuccess} = useGetSystemsQuery(); //= selectAllSystems(); - componentWillUnmount() { - clearInterval(this.interval); + + + + 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 = () => { + 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 -render() { - const system = this.props.system.items[this.state.currentSystem]; + let system = false; + if (isSuccess) { + system = systems.systems[currentSystem]; + } return ( <> @@ -254,9 +274,9 @@ render() { - { system && ( + { system && ( - this.props.changeUrl("/system/" + system.shortName)}/> + props.changeUrl("/system/" + system.shortName)}/> )} @@ -351,6 +371,5 @@ render() { ); } -} export default Main; diff --git a/frontend/src/Main/MainContainer.js b/frontend/src/Main/MainContainer.js deleted file mode 100644 index 8ba167c..0000000 --- a/frontend/src/Main/MainContainer.js +++ /dev/null @@ -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) diff --git a/frontend/src/System/ListSystems.js b/frontend/src/System/ListSystems.js index f5f8cf0..7e01cbf 100644 --- a/frontend/src/System/ListSystems.js +++ b/frontend/src/System/ListSystems.js @@ -10,22 +10,15 @@ import { Divider } from "semantic-ui-react"; +import { selectAllSystems, selectActiveSystems, useGetSystemsQuery } from "../features/api/apiSlice"; // ---------------------------------------------------- -class ListSystems extends Component { - state = { - requestMessage: "", - } +const ListSystems = (props) => { + - componentDidMount() { - this.props.fetchSystems(); - } + const {data:systems, isSuccess} = useGetSystemsQuery(); -//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 systems = this.props.system.items; return (
    @@ -35,7 +28,8 @@ class ListSystems extends Component {
    Radio Systems
    - {systems.map((system) => + + {isSuccess&&systems.systems.map((system) => system.active&& this.props.changeUrl("/system/" + system.shortName)}/> // this.props.changeUrl("/system/" + system.shortName)}/> )} @@ -44,6 +38,6 @@ class ListSystems extends Component {
    ); } -} + export default ListSystems; diff --git a/frontend/src/features/api/apiSlice.js b/frontend/src/features/api/apiSlice.js index 6101e9e..59c2425 100644 --- a/frontend/src/features/api/apiSlice.js +++ b/frontend/src/features/api/apiSlice.js @@ -1,13 +1,13 @@ // Import the RTK Query methods from the React-specific entry point import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' - +import { createSelector} from '@reduxjs/toolkit' // Define our single API slice object export const apiSlice = createApi({ // The cache reducer expects to be added at `state.api` (already default - this is optional) reducerPath: 'api', - + // All of our requests will have URLs starting with '/fakeApi' baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_BACKEND_SERVER }), // The "endpoints" represent operations and requests for this server @@ -25,9 +25,24 @@ export const apiSlice = createApi({ // The URL for the request is '/fakeApi/posts' query: (shortName) => ({ url: `/${shortName}/talkgroups` }) }), - + }) }) +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 const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice +export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery } = apiSlice diff --git a/frontend/src/index.js b/frontend/src/index.js index e7ca077..34b44a9 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -11,10 +11,10 @@ import 'semantic-ui-css/semantic.min.css' //import withTracker from './withTracker'; // Main -import MainContainer from "./Main/MainContainer" +import Main from "./Main/Main" // System -import ListSystemsContainer from "./System/ListSystemsContainer" +import ListSystems from "./System/ListSystems" // Call import CallPlayer from "./Call/CallPlayer" @@ -30,8 +30,8 @@ root.render( - } /> - } /> + } /> + } /> } /> } /> From ce921093472c8774d3fa1e93bd34ee5085fa41b6 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 16 Jan 2023 21:24:10 -0500 Subject: [PATCH 24/27] cleaned things up --- frontend/src/Call/GroupModal.js | 1 - frontend/src/Call/call-actions.js | 358 ------------------ frontend/src/Call/call-constants.js | 23 -- frontend/src/Call/call-data.js | 57 --- frontend/src/Call/call-reducer.js | 230 ----------- frontend/src/Group/group-actions.js | 73 ---- frontend/src/Group/group-constants.js | 3 - frontend/src/Group/group-reducer.js | 42 -- frontend/src/Main/Main.js | 7 +- frontend/src/System/ListSystemsContainer.js | 16 - frontend/src/System/system-actions.js | 66 ---- frontend/src/System/system-constants.js | 3 - frontend/src/System/system-reducer.js | 41 -- frontend/src/Talkgroups/ListTalkgroups.js | 66 ---- frontend/src/Talkgroups/talkgroup-actions.js | 98 ----- .../src/Talkgroups/talkgroup-constants.js | 9 - frontend/src/Talkgroups/talkgroup-reducer.js | 51 --- frontend/src/redux-router/configureStore.js | 1 - frontend/src/redux-router/reducers.js | 11 +- 19 files changed, 5 insertions(+), 1151 deletions(-) delete mode 100644 frontend/src/Call/call-actions.js delete mode 100644 frontend/src/Call/call-constants.js delete mode 100644 frontend/src/Call/call-data.js delete mode 100644 frontend/src/Call/call-reducer.js delete mode 100644 frontend/src/Group/group-actions.js delete mode 100644 frontend/src/Group/group-constants.js delete mode 100644 frontend/src/Group/group-reducer.js delete mode 100644 frontend/src/System/ListSystemsContainer.js delete mode 100644 frontend/src/System/system-actions.js delete mode 100644 frontend/src/System/system-constants.js delete mode 100644 frontend/src/System/system-reducer.js delete mode 100644 frontend/src/Talkgroups/ListTalkgroups.js delete mode 100644 frontend/src/Talkgroups/talkgroup-actions.js delete mode 100644 frontend/src/Talkgroups/talkgroup-constants.js delete mode 100644 frontend/src/Talkgroups/talkgroup-reducer.js diff --git a/frontend/src/Call/GroupModal.js b/frontend/src/Call/GroupModal.js index 95e6eee..59ebcbe 100644 --- a/frontend/src/Call/GroupModal.js +++ b/frontend/src/Call/GroupModal.js @@ -10,7 +10,6 @@ import { Dropdown } from "semantic-ui-react"; import "./FilterModal.css"; -import * as types from "./call-constants"; function GroupModal (props) { const shortName = useSelector((state) => state.callPlayer.shortName); diff --git a/frontend/src/Call/call-actions.js b/frontend/src/Call/call-actions.js deleted file mode 100644 index ac225d8..0000000 --- a/frontend/src/Call/call-actions.js +++ /dev/null @@ -1,358 +0,0 @@ -import axios from "axios"; - -import * as types from "./call-constants"; -import { push } from 'connected-react-router'; - -function beginFetchCallInfo() { - return { type: types.FETCH_CALL_INFO }; -} - -function fetchCallInfoSuccess(data) { - return { type: types.FETCH_SUCCESS_CALL_INFO, - data }; -} - -function fetchCallInfoError(data) { - return { type: types.FETCH_ERROR_CALL_INFO, - data }; -} - -function prependCall() { - return { type: types.PREPEND_CALL }; -} - -function beginAddStar() { - return { type: types.ADD_STAR }; -} - -function addStarSuccess(data) { - return { type: types.ADD_STAR_SUCCESS, data }; -} - -function addStarError(data) { - return { type: types.ADD_STAR_ERROR, data }; -} - -function beginFetchCall() { - return { type: types.FETCH_CALL }; -} - -function fetchCallSuccess(data) { - return { type: types.FETCH_SUCCESS_CALL, - data }; -} - - - -function fetchOlderCallSuccess(data) { - return { type: types.FETCH_SUCCESS_OLDER_CALL, - data }; -} - -function fetchNewerCallSuccess(data) { - return { type: types.FETCH_SUCCESS_NEWER_CALL, - data }; -} - -function fetchCallError(data) { - return { type: types.FETCH_ERROR_CALL, - data }; -} - -// The data field should be a JS date object -export function setCallTime(data) { - return {type: types.SET_CALL_TIME, data}; -} - -export function changeUrl(url) { - return dispatch => { - dispatch(push(url)); - }; -} - -// Example of an Async Action Creator -// http://redux.js.org/docs/advanced/AsyncActions.html - -function shouldFetchCalls(state, subreddit) { - return true; - if ((!state.call.items) || (state.call.items.length <1)) { - return true - } else if (state.call.isWaiting) { - return false - } else { - return false - } -} - - - - -function buildUrlParams(a) { - return Object.keys(a).map(function(k) { - return encodeURIComponent(k) + '=' + encodeURIComponent(a[k]) - }).join('&') -} - -function buildCallUrl(state, date, direction) { - var params = {}; - var url = ""; - - - if ((typeof direction === 'string') && (typeof date === 'number')) { - url = url + '/' + direction; - params["time"] = date; - } else { - if (state.call.filterDate){ - url = url + '/older' - params["time"] = state.call.filterDate; - } - } - if (state.call.filterStarred) { - params["filter-starred"] = true; - } - switch(state.call.filterType) { - case 1: - params["filter-type"] = "group"; - params["filter-code"] = state.call.filterGroupId; - break; - case 2: - params["filter-type"] = "talkgroup"; - params["filter-code"] = state.call.filterTalkgroups; - break; - default: - case 0: - } -/* - - if (this.filterCode != "") { - params["filter-code"] = this.filterCode; - } - - if (this.filterType == this.FilterType.Group) { - params["filter-type"] = "group"; - params["filter-name"] = this.filterName; - } - - if (this.filterType == this.FilterType.Talkgroup) { - params["filter-type"] = "talkgroup"; - } - - if (this.filterType == this.FilterType.Unit) { - params["filter-type"] = "unit"; - } - -*/ - // url = startUp.backend_server + "/" + startUp.shortName.toLowerCase() + "/calls" + url + '?' + buildUrlParams(params); - url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName + "/calls" + url + '?' + buildUrlParams(params); - console.log("Trying to fetch data from this url: " + url); - return url; -} - -export function fetchNewerCalls(timestamp) { - return (dispatch, getState) => { - const state = getState(); - if (shouldFetchCalls(state)) { - dispatch(beginFetchCall()); - const url = buildCallUrl(state, timestamp, "newer") - return axios - .get(url) - .then(response => { - if ((response.data) && (response.data.calls.length >0)) { - var data = {shortName: state.call.shortName, calls: response.data.calls} - dispatch(fetchNewerCallSuccess(data)); - } else { - dispatch(fetchCallError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; -} - - -export function fetchOlderCalls( timestamp) { - return (dispatch, getState) => { - const state = getState(); - if (shouldFetchCalls(state)) { - dispatch(beginFetchCall()); - const url = buildCallUrl(state, timestamp, "older") - return axios - .get(url) - .then(response => { - if (response.data) { - var data = {shortName: state.call.shortName, calls: response.data.calls} - dispatch(fetchOlderCallSuccess(data)); - } else { - dispatch(fetchCallError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; -} - - -export function addStar( callId) { - return (dispatch, getState) => { - const state = getState(); - dispatch(beginAddStar()); - - const url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName.toLowerCase() + "/star/" + callId - return axios - .post(url,{}, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }).then(response => { - if (response.data.success) { - dispatch(addStarSuccess(response.data.call)); - } else { - dispatch(addStarError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; - - - -export function fetchCallInfo( callId) { - return (dispatch, getState) => { - const state = getState(); - dispatch(beginFetchCallInfo()); - - const url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName.toLowerCase() + "/call/" + callId - return axios - .get(url) - .then(response => { - if (response.data.success) { - dispatch(fetchCallInfoSuccess(response.data.call)); - } else { - dispatch(fetchCallInfoError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; - -export function fetchCalls() { - return (dispatch, getState) => { - const state = getState(); - if (shouldFetchCalls(state)) { - dispatch(beginFetchCall()); - const url=buildCallUrl(state); - return axios - .get(url) - .then(response => { - if (response.data) { - // check to make sure you actually got some calls back. - if (response.data.calls.length > 0) { - var data = {shortName: state.call.shortName, calls: response.data.calls} - dispatch(fetchCallSuccess(data)); - } else { - dispatch(fetchCallError()); - } - } else { - dispatch(fetchCallError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; -} - -export function addCall(call) { - return (dispatch, getState) => { - dispatch({ type: types.UPSERT_CALL, data: call}); - }; -} - -export function setStarred(starred) { - return (dispatch, getState) => { - dispatch({ type: types.SET_STARRED_FILTER, data: starred}); - }; -} - -export function setLive(live) { - return (dispatch, getState) => { - dispatch({ type: types.SET_LIVE, data: live}); - }; -} - -export function setCurrentCallId(callId) { - return (dispatch, getState) => { - dispatch({ type: types.SET_CURRENT_CALL_ID, data: callId}); - }; -} - -// should be as a unix timestamp -export function setDateFilter(date) { - return (dispatch, getState) => { - dispatch({ type: types.SET_DATE_FILTER, date: date}); - }; -} - -export function setAllFilter() { - return (dispatch, getState) => { - dispatch({ type: types.SET_ALL_FILTER}); - }; -} - -export function setTalkgroupFilter(tgs) { - return (dispatch, getState) => { - dispatch({ type: types.SET_TALKGROUP_FILTER, talkgroups: tgs}); - }; -} - -export function setGroupFilter(groupId) { - return (dispatch, getState) => { - dispatch({ type: types.SET_GROUP_FILTER, groupId: groupId}); - }; -} - -export function setShortName(shortName) { - return (dispatch, getState) => { - dispatch({ type: types.SET_SHORT_NAME, data: shortName}); - }; -} - -export function setFilter(filter) { - return (dispatch, getState) => { - dispatch({ type: types.SET_FILTER, data: filter}); - }; -} diff --git a/frontend/src/Call/call-constants.js b/frontend/src/Call/call-constants.js deleted file mode 100644 index d6ac331..0000000 --- a/frontend/src/Call/call-constants.js +++ /dev/null @@ -1,23 +0,0 @@ -export const PREPEND_CALL = "PREPEND_CALL" -export const ADD_STAR = "ADD_STAR" -export const ADD_STAR_SUCCESS = "ADD_STAR_SUCCESS" -export const ADD_STAR_ERROR = "ADD_STAR_ERROR" -export const UPSERT_CALL = "UPSERT_CALL" -export const FETCH_CALL = "FETCH_CALL" -export const FETCH_SUCCESS_CALL = "FETCH_SUCCESS_CALL" -export const FETCH_SUCCESS_NEWER_CALL = "FETCH_SUCCESS_NEWER_CALL" -export const FETCH_SUCCESS_OLDER_CALL = "FETCH_SUCCESS_OLDER_CALL" -export const FETCH_ERROR_CALL = "FETCH_ERROR_CALL" -export const SET_SHORT_NAME = "SET_SHORT_NAME" -export const FETCH_CALL_INFO = "FETCH_CALL_INFO" -export const FETCH_SUCCESS_CALL_INFO = "FETCH_SUCCESS_CALL_INFO" -export const FETCH_ERROR_CALL_INFO = "FETCH_ERROR_CALL_INFO" -export const SET_ALL_FILTER = "SET_ALL_FILTER" -export const SET_GROUP_FILTER = "SET_GROUP_FILTER" -export const SET_TALKGROUP_FILTER = "SET_TALKGROUP_FILTER" -export const SET_DATE_FILTER = "SET_DATE_FILTER" -export const SET_FILTER = "SET_FILTER" -export const SET_STARRED_FILTER = "SET_STARRED_FILTER" -export const SET_LIVE = "SET_LIVE" -export const SET_CALL_TIME = "SET_CALL_TIME" -export const SET_CURRENT_CALL_ID = "SET_CURRENT_CALL_ID" diff --git a/frontend/src/Call/call-data.js b/frontend/src/Call/call-data.js deleted file mode 100644 index ecd7e6d..0000000 --- a/frontend/src/Call/call-data.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import { useGetGroupsQuery, useGetCallsQuery } from '../features/api/apiSlice' - -export const Groups = (args) => { - - const { data, isError, isLoading, isSuccess,error } = useGetGroupsQuery(args.shortName); - - let content - - if (isLoading) { - content =
    Loading...
    - } else if (isSuccess) { - - for (const num in data) { - const group = data[num]; - content =
  • {group.groupName}
  • ; - } - - } else if (isError) { - content =
    {error.toString()}
    - } - - return ( -
    -

    Group

    - {content} -
    - ) - } - - - export const Calls = (args) => { - - const { data, isError, isLoading, isSuccess,error } = useGetCallsQuery({shortName: args.shortName}); - - let content - - if (isLoading) { - content =
    Loading...
    - } else if (isSuccess) { - - for (const num in data) { - const group = data[num]; - content =
  • {group.groupName}
  • ; - } - - } else if (isError) { - content =
    {error.toString()}
    - } - - return ( -
    -

    Group

    - {content} -
    - ) - } \ No newline at end of file diff --git a/frontend/src/Call/call-reducer.js b/frontend/src/Call/call-reducer.js deleted file mode 100644 index 8fe2935..0000000 --- a/frontend/src/Call/call-reducer.js +++ /dev/null @@ -1,230 +0,0 @@ -import { - FETCH_CALL_INFO, - FETCH_SUCCESS_CALL_INFO, - FETCH_ERROR_CALL_INFO, - FETCH_CALL, - PREPEND_CALL, - UPSERT_CALL, - FETCH_SUCCESS_CALL, - FETCH_SUCCESS_OLDER_CALL, - FETCH_SUCCESS_NEWER_CALL, - FETCH_ERROR_CALL, - SET_SHORT_NAME, - SET_ALL_FILTER, - SET_GROUP_FILTER, - SET_TALKGROUP_FILTER, - SET_DATE_FILTER, - SET_STARRED_FILTER, - SET_FILTER, - SET_CURRENT_CALL_ID, - SET_LIVE, - SET_CALL_TIME, - ADD_STAR_SUCCESS -} from "./call-constants"; - -function updateObject(oldObject, newValues) { - // Encapsulate the idea of passing a new object as the first parameter - // to Object.assign to ensure we correctly copy data instead of mutating - return Object.assign({}, oldObject, newValues); -} - -function updateCallInItems(calls, action) { -return calls.map( (item) => { - if (item._id !== action.data._id) { - // This isn't the item we care about - keep it as-is - return item; - } - - // Otherwise, this is the one we want - return an updated value - return { - ...action.data - }; - }); -} -/* -function updateObjectInArray(array, action) { - return array.map((item, index) => { - if (index !== action.index) { - // This isn't the item we care about - keep it as-is - return item - } - - // Otherwise, this is the one we want - return an updated value - return { - ...item, - ...action.item - } - }) -} - -function updateObjectInArray(array, shortName, updatedItem) { - return array.map( item => { - if(item.shortName !== shortName) { - // This isn't the item we care about - keep it as-is - return item; - } - - // Otherwise, this is the one we want - return an updated value - return { - ...item, - ...updatedItem - }; - }); -} -*/ -const now = new Date(); -const call = ( - state = { - isWaiting: false, - shortName: "", - byId: {}, - allIds: [], - oldestCallTime: now, - newestCallTime: now, - filterType: 0, - filterTalkgroups: [], - filterGroupId: false, - filterDate: false, - filterStarred: false, - currentCallId: false, - live: false - }, - action -) => { - switch (action.type) { - - case SET_CURRENT_CALL_ID: - return Object.assign({}, state, {currentCallId: action.data}); - - case SET_CALL_TIME: - return Object.assign({}, state, {newestCallTime: action.data, oldestCallTime: action.data }); - case SET_FILTER: - { - var filter={}; - filter.live = !action.data.live ? state.live : action.data.live; - filter.filterStarred = !action.data.filterStarred ? false : action.data.filterStarred; - filter.filterType = !action.data.filterType ? 0 : action.data.filterType; - filter.filterTalkgroups = !action.data.filterTalkgroups ? [] : action.data.filterTalkgroups; - filter.filterGroupId = !action.data.filterGroupId ? false : action.data.filterGroupId; - filter.filterDate = !action.data.filterDate ? 0 : action.data.filterDate; - return Object.assign({}, state, { - live: action.data.live, - filterType: action.data.filterType, - filterTalkgroups: action.data.filterTalkgroups, - filterGroupId: action.data.filterGroupId, - filterDate: action.data.filterDate, - filterStarred: action.data.filterStarred - }); - } - - case SET_LIVE: - return Object.assign({}, state, {live: action.data}); - - case SET_SHORT_NAME: - return Object.assign({}, state, {shortName: action.data}); - - case SET_DATE_FILTER: - return Object.assign({}, state, {filterDate: action.date}); - - case SET_STARRED_FILTER: - return Object.assign({}, state, {filterStarred: action.data}); - - case SET_ALL_FILTER: - return Object.assign({}, state, {filterType: 0, filterTalkgroups:[], filterGroupId: false}); - - case SET_GROUP_FILTER: - return Object.assign({}, state, {filterType: 1, filterTalkgroups:[], filterGroupId: action.groupId}); - - case SET_TALKGROUP_FILTER: - return Object.assign({}, state, {filterType: 2, filterTalkgroups: action.talkgroups, filterGroupId: false}); - - case FETCH_CALL_INFO: - case FETCH_CALL: - return Object.assign({}, state, { isWaiting: true}); - - case FETCH_SUCCESS_CALL_INFO: - return Object.assign({}, state, { - byId: {...state.byId, [action.data._id]: action.data }, - isWaiting: false - - }); - - case ADD_STAR_SUCCESS: - return Object.assign({}, state,{ - byId: {...state.byId, [action.data._id]: action.data }, - isWaiting: false - - }); - // This will set the items to the rx data - case FETCH_SUCCESS_CALL: - { - const byId = action.data.calls.reduce((acc, call) => ({ ...acc, [call._id]: call }), {}); - const allIds = action.data.calls.map(call => call._id); - return Object.assign({}, state, { - isWaiting: false, - shortName: action.data.shortName, - newestCallTime: new Date(action.data.calls[0].time), - oldestCallTime: new Date(action.data.calls[action.data.calls.length-1].time), - byId: { ...byId}, - allIds: [...allIds] - }); - } - - case UPSERT_CALL: - if (typeof state.byId[action.data._id] !== "undefined") { - return Object.assign({}, state, { - byId: {...state.byId, [action.data._id]: action.data }, - }); - } else { - return Object.assign({}, state, { - newestCallTime: new Date(action.data.time), - byId: {[action.data._id]: action.data, ...state.byId}, - allIds: [action.data._id, ...state.allIds] - }); - } - - case PREPEND_CALL: - return Object.assign({}, state, { - isWaiting: false, - newestCallTime: new Date(action.data.time), - byId: {[action.data._id]: action.data, ...state.byId}, - allIds: [action.data._id, ...state.allIds] - }); - -// This will add the array of Calls in the data to the beginning of the existing array of Calls - case FETCH_SUCCESS_NEWER_CALL: - const byId = action.data.calls.reduce((acc, call) => ({ ...acc, [call._id]: call }), {}); - const allIds = action.data.calls.map(call => call._id).reverse(); - return Object.assign({}, state, { - isWaiting: false, - shortName: action.data.shortName, - newestCallTime: new Date(action.data.calls[action.data.calls.length-1].time), - byId: {...state.byId, ...byId}, - allIds: [...allIds, ...state.allIds] - }); - -// This will add the array of Calls in the data to the end of the existing array of Calls - case FETCH_SUCCESS_OLDER_CALL: - { - const byId = action.data.calls.reduce((acc, call) => ({ ...acc, [call._id]: call }), {}); - const allIds = action.data.calls.map(call => call._id); - return Object.assign({}, state, { - isWaiting: false, - shortName: action.data.shortName, - oldestCallTime: new Date(action.data.calls[action.data.calls.length-1].time), - byId: {...state.byId, ...byId}, - allIds: [ ...state.allIds, ...allIds] - }); - } - case FETCH_ERROR_CALL_INFO: - case FETCH_ERROR_CALL: - return Object.assign({}, state, { - isWaiting: false - }); - - default: - return state; - } -}; - -export default call; diff --git a/frontend/src/Group/group-actions.js b/frontend/src/Group/group-actions.js deleted file mode 100644 index 7d1c2d3..0000000 --- a/frontend/src/Group/group-actions.js +++ /dev/null @@ -1,73 +0,0 @@ -import axios from "axios"; -//import { browserHistory } from "react-router" -import * as types from "./group-constants"; -import { push } from 'connected-react-router'; - -// "Log In" action creators - - -function beginFetchGroup() { - return { type: types.FETCH_GROUP }; -} - -function fetchGroupSuccess(data) { - return { - type: types.FETCH_SUCCESS_GROUP, - data - }; -} - -function fetchGroupError(data) { - return { - type: types.FETCH_ERROR_GROUP, - data - }; -} - - -export function changeUrl(url) { - return dispatch => { - dispatch(push(url)); - }; -} - -// Example of an Async Action Creator -// http://redux.js.org/docs/advanced/AsyncActions.html - - -function shouldFetchGroups(state,shortName) { - if (!state.group.items || !state.group.items[shortName]) { - return true; - } else if (state.group.isWaiting) { - return false; - } else { - return false; - } -} - - -export function fetchGroups(shortName) { - return (dispatch, getState) => { - if (shouldFetchGroups(getState(), shortName)) { - dispatch(beginFetchGroup()); - return axios - .get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/groups") - .then(response => { - if (true) { //response.data.success) { - const data = { shortName: shortName, groups: response.data }; - dispatch(fetchGroupSuccess(data)); - } else { - dispatch(fetchGroupError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - } - }; -} diff --git a/frontend/src/Group/group-constants.js b/frontend/src/Group/group-constants.js deleted file mode 100644 index 914364f..0000000 --- a/frontend/src/Group/group-constants.js +++ /dev/null @@ -1,3 +0,0 @@ -export const FETCH_GROUP = "FETCH_GROUP" -export const FETCH_SUCCESS_GROUP = "FETCH_SUCCESS_GROUP" -export const FETCH_ERROR_GROUP = "FETCH_ERROR_GROUP" diff --git a/frontend/src/Group/group-reducer.js b/frontend/src/Group/group-reducer.js deleted file mode 100644 index fb4993b..0000000 --- a/frontend/src/Group/group-reducer.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - FETCH_GROUP, - FETCH_SUCCESS_GROUP, - FETCH_ERROR_GROUP -} from "./group-constants"; - - -const group = ( - state = { - orderChange: false, - isWaiting: false, - items: {}}, - action -) => { - switch (action.type) { - - - case FETCH_GROUP: - return Object.assign({}, state, { isWaiting: true}); - - - case FETCH_SUCCESS_GROUP: - return Object.assign({}, state, { - isWaiting: false, - items: { - ...state.items, - [action.data.shortName]: action.data.groups - } - }); - - case FETCH_ERROR_GROUP: - return Object.assign({}, state, { - isWaiting: false - }); - - - default: - return state; - } -}; - -export default group; diff --git a/frontend/src/Main/Main.js b/frontend/src/Main/Main.js index dc1aa96..372d169 100644 --- a/frontend/src/Main/Main.js +++ b/frontend/src/Main/Main.js @@ -1,5 +1,5 @@ import React, { Component, useState, useEffect, useRef } from "react"; -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import SystemCard from "../System/SystemCard"; import "./Main.css"; import { createMedia } from "@artsy/fresnel"; @@ -214,6 +214,7 @@ const Main = (props) => { const [visible, setVisible] = useState(true); const [duration, setDuration] = useState(500); const [currentSystem, setCurrentSystem] = useState(0); + const navigate = useNavigate(); const dispatch = useDispatch(); const {data:systems, isSuccess} = useGetSystemsQuery(); //= selectAllSystems(); @@ -276,13 +277,13 @@ const Main = (props) => { { system && ( - props.changeUrl("/system/" + system.shortName)}/> + navigate("/system/" + system.shortName)}/> )} -
    - - - Mode - Number - Alpha - Description - - - - {talkgroups.map((talkgroup, i) => ( - - {talkgroup.mode} - {talkgroup.num} - {talkgroup.alpha} - {talkgroup.description} - - ))} - -
    - - ); - } else { - return ( -
    - ); - } -} -} - -export default ListTalkgroups; diff --git a/frontend/src/Talkgroups/talkgroup-actions.js b/frontend/src/Talkgroups/talkgroup-actions.js deleted file mode 100644 index 17320e6..0000000 --- a/frontend/src/Talkgroups/talkgroup-actions.js +++ /dev/null @@ -1,98 +0,0 @@ -import axios from 'axios'; -//import { browserHistory } from "react-router" -import * as types from "./talkgroup-constants"; -import { push } from 'connected-react-router'; - -// "Log In" action creators -function beginImportTalkgroup() { - return { type: types.IMPORT_TALKGROUP }; -} - -function importTalkgroupSuccess(data) { - return { - type: types.IMPORT_SUCCESS_TALKGROUP, - data - }; -} - -function importTalkgroupError(data) { - return { - type: types.IMPORT_ERROR_TALKGROUP, - data - }; -} - -function beginExportTalkgroup() { - return { type: types.EXPORT_TALKGROUP }; -} - -function exportTalkgroupSuccess() { - return { type: types.EXPORT_SUCCESS_TALKGROUP }; -} - -function exportTalkgroupError() { - return { type: types.EXPORT_ERROR_TALKGROUP }; -} - - -function beginFetchTalkgroup() { - return { type: types.FETCH_TALKGROUP }; -} - -function fetchTalkgroupSuccess(data) { - return { type: types.FETCH_SUCCESS_TALKGROUP, - data }; -} - -function fetchTalkgroupError(data) { - return { type: types.FETCH_ERROR_TALKGROUP, - data }; -} - -export function changeUrl(url) { - return dispatch => { - dispatch(push(url)); - }; -} - - -// https://stackoverflow.com/questions/41878838/how-do-i-set-multipart-in-axios-with-react - - - -function shouldFetchTalkgroups(state, shortName) { - if (!state.talkgroup.items.hasOwnProperty(shortName)) { - return true - } else if (state.talkgroup.isWaiting) { - return false - } else { - return false - } -} - -export function fetchTalkgroups(shortName) { - return (dispatch, getState) => { - if (shouldFetchTalkgroups(getState(), shortName)) { - dispatch(beginFetchTalkgroup()); - - return axios - .get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/talkgroups") - .then(response => { - if (response.data) { - var data = {shortName: shortName, talkgroups: response.data.talkgroups} - dispatch(fetchTalkgroupSuccess( data)); - } else { - dispatch(fetchTalkgroupError()); - let registerMessage = response.data.message; - return registerMessage; - } - }) - .catch(response => { - if (response instanceof Error) { - // Something happened in setting up the request that triggered an Error - console.log("Error", response.message); - } - }); - }; -}; -} diff --git a/frontend/src/Talkgroups/talkgroup-constants.js b/frontend/src/Talkgroups/talkgroup-constants.js deleted file mode 100644 index e5df09f..0000000 --- a/frontend/src/Talkgroups/talkgroup-constants.js +++ /dev/null @@ -1,9 +0,0 @@ -export const IMPORT_TALKGROUP = "IMPORT_TALKGROUP" -export const IMPORT_SUCCESS_TALKGROUP = "IMPORT_SUCCESS_TALKGROUP" -export const IMPORT_ERROR_TALKGROUP = "IMPORT_ERROR_TALKGROUP" -export const EXPORT_TALKGROUP = "EXPORT_TALKGROUP" -export const EXPORT_SUCCESS_TALKGROUP = "EXPORT_SUCCESS_TALKGROUP" -export const EXPORT_ERROR_TALKGROUP = "EXPORT_ERROR_TALKGROUP" -export const FETCH_TALKGROUP = "FETCH_TALKGROUP" -export const FETCH_SUCCESS_TALKGROUP = "FETCH_SUCCESS_TALKGROUP" -export const FETCH_ERROR_TALKGROUP = "FETCH_ERROR_TALKGROUP" diff --git a/frontend/src/Talkgroups/talkgroup-reducer.js b/frontend/src/Talkgroups/talkgroup-reducer.js deleted file mode 100644 index 396d0d1..0000000 --- a/frontend/src/Talkgroups/talkgroup-reducer.js +++ /dev/null @@ -1,51 +0,0 @@ -import { - IMPORT_TALKGROUP, - IMPORT_SUCCESS_TALKGROUP, - IMPORT_ERROR_TALKGROUP, - EXPORT_TALKGROUP, - EXPORT_SUCCESS_TALKGROUP, - EXPORT_ERROR_TALKGROUP, - FETCH_TALKGROUP, - FETCH_SUCCESS_TALKGROUP, - FETCH_ERROR_TALKGROUP -} from "./talkgroup-constants"; - -const talkgroup = ( - state = { - isWaiting: false, - items: {}}, - action -) => { - switch (action.type) { - - case FETCH_TALKGROUP: - case IMPORT_TALKGROUP: - case EXPORT_TALKGROUP: - return Object.assign({}, state, { isWaiting: true}); - - case IMPORT_SUCCESS_TALKGROUP: - case FETCH_SUCCESS_TALKGROUP: - return Object.assign({}, state, { - isWaiting: false, - items: { - ...state.items, - [action.data.shortName]: action.data.talkgroups - } - }); - - - - case FETCH_ERROR_TALKGROUP: - case IMPORT_ERROR_TALKGROUP: - case EXPORT_ERROR_TALKGROUP: - case EXPORT_SUCCESS_TALKGROUP: - return Object.assign({}, state, { - isWaiting: false - }); - - default: - return state; - } -}; - -export default talkgroup; diff --git a/frontend/src/redux-router/configureStore.js b/frontend/src/redux-router/configureStore.js index 125bb4f..54059eb 100644 --- a/frontend/src/redux-router/configureStore.js +++ b/frontend/src/redux-router/configureStore.js @@ -6,7 +6,6 @@ import { applyMiddleware, compose, createStore } from 'redux' import { routerMiddleware } from 'connected-react-router' import createRootReducer from './reducers' import { apiSlice } from '../features/api/apiSlice' -import {callPlayerSlice} from "../features/callPlayer/callPlayerSlice" import thunk from 'redux-thunk'; diff --git a/frontend/src/redux-router/reducers.js b/frontend/src/redux-router/reducers.js index 3b8bc7a..cd105a9 100644 --- a/frontend/src/redux-router/reducers.js +++ b/frontend/src/redux-router/reducers.js @@ -2,22 +2,13 @@ // from https://github.com/supasate/connected-react-router import { combineReducers } from 'redux' import { connectRouter } from 'connected-react-router' -import systemReducer from "../System/system-reducer" -import talkgroupReducer from "../Talkgroups/talkgroup-reducer" -import groupReducer from "../Group/group-reducer" -import callReducer from "../Call/call-reducer" import {apiSlice} from "../features/api/apiSlice" import callPlayerSlice from "../features/callPlayer/callPlayerSlice" -import callsSlice, { callsReducer } from "../features/calls/callsSlice" +import { callsReducer } from "../features/calls/callsSlice" export default (history) => combineReducers({ router: connectRouter(history), - system: systemReducer, - talkgroup: talkgroupReducer, - group: groupReducer, callPlayer: callPlayerSlice, - call: callReducer, calls: callsReducer, [apiSlice.reducerPath]: apiSlice.reducer, - }) \ No newline at end of file From 02c710198b9dd10c364e87a42cf915e866caa13f Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Tue, 17 Jan 2023 09:10:50 -0500 Subject: [PATCH 25/27] fix call display a little --- frontend/src/Call/CallPlayer.js | 2 +- frontend/src/Call/ListCalls.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index b476b65..22706c9 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -508,7 +508,7 @@ function CallPlayer(props) { style={{ minHeight: '100vh' }} >
    - +
    diff --git a/frontend/src/Call/ListCalls.js b/frontend/src/Call/ListCalls.js index 7d187e5..3aa621f 100644 --- a/frontend/src/Call/ListCalls.js +++ b/frontend/src/Call/ListCalls.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useLayoutEffect, useRef } from "react"; import CallItem from "./CallItem"; import { Icon, @@ -17,7 +17,6 @@ const ListCalls = (props) => { useEffect(() => { if (activeCallRef.current) { activeCallRef.current.scrollIntoView({ - behavior: "smooth", block: "center", }); } From 0a5e88c6521c806063acfbee6dc76a4af7e85870 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Tue, 17 Jan 2023 11:14:37 -0500 Subject: [PATCH 26/27] Update ListSystems.js --- frontend/src/System/ListSystems.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/System/ListSystems.js b/frontend/src/System/ListSystems.js index 7e01cbf..79772e6 100644 --- a/frontend/src/System/ListSystems.js +++ b/frontend/src/System/ListSystems.js @@ -1,5 +1,5 @@ import React, { Component } from "react"; -import { Link } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' import SystemCard from "./SystemCard"; import { Container, @@ -18,7 +18,7 @@ const ListSystems = (props) => { const {data:systems, isSuccess} = useGetSystemsQuery(); - + const navigate = useNavigate(); return (
    @@ -30,7 +30,7 @@ const ListSystems = (props) => { {isSuccess&&systems.systems.map((system) => - system.active&& this.props.changeUrl("/system/" + system.shortName)}/> + system.active&& navigate("/system/" + system.shortName)}/> // this.props.changeUrl("/system/" + system.shortName)}/> )} From 1d55a63f7f0303e8400f64d452aa3e53a139398e Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Tue, 17 Jan 2023 20:50:47 -0500 Subject: [PATCH 27/27] cleaned up some errors --- frontend/src/About/AboutComponent.js | 2 +- frontend/src/Call/CalendarModal.js | 6 ++-- frontend/src/Call/CallInfo.js | 1 - frontend/src/Call/CallItem.js | 46 ++++++++++++++-------------- frontend/src/Call/CallPlayer.js | 25 +++------------ frontend/src/Call/FilterModal.js | 4 +-- frontend/src/Call/ListCalls.js | 2 +- frontend/src/Main/Main.js | 17 +++------- 8 files changed, 37 insertions(+), 66 deletions(-) diff --git a/frontend/src/About/AboutComponent.js b/frontend/src/About/AboutComponent.js index afb6345..45db264 100644 --- a/frontend/src/About/AboutComponent.js +++ b/frontend/src/About/AboutComponent.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React from "react"; import { Link } from 'react-router-dom' import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react"; diff --git a/frontend/src/Call/CalendarModal.js b/frontend/src/Call/CalendarModal.js index f733422..76322c2 100644 --- a/frontend/src/Call/CalendarModal.js +++ b/frontend/src/Call/CalendarModal.js @@ -1,4 +1,4 @@ -import React, { Component, useState } from "react"; +import React, { useState } from "react"; import { Modal, Button, @@ -8,12 +8,10 @@ import "./CalendarModal.css"; import { setDateFilter } from "../features/callPlayer/callPlayerSlice"; import DatePicker from 'react-datepicker'; import { useDispatch } from 'react-redux' -import { setDate, subDays } from 'date-fns' +import { subDays } from 'date-fns' import 'react-datepicker/dist/react-datepicker.css'; function CalendarModal(props) { - - const [open, setOpen] = useState(false); const [startDate, setStartDate] = useState(new Date()); const dispatch = useDispatch() const onClose = props.onClose; diff --git a/frontend/src/Call/CallInfo.js b/frontend/src/Call/CallInfo.js index 1818242..71ae3c6 100644 --- a/frontend/src/Call/CallInfo.js +++ b/frontend/src/Call/CallInfo.js @@ -1,7 +1,6 @@ import React from "react"; import { Header, - Button, Divider, List, Segment, diff --git a/frontend/src/Call/CallItem.js b/frontend/src/Call/CallItem.js index 572ac3a..dcfd011 100644 --- a/frontend/src/Call/CallItem.js +++ b/frontend/src/Call/CallItem.js @@ -1,13 +1,12 @@ - import React, { useState } from "react"; import { Table, Icon, Label } from "semantic-ui-react"; -import { connect } from "react-redux" -import { addStar, removeStar} from "../features/calls/callsSlice"; + +import { addStar, removeStar } from "../features/calls/callsSlice"; import { useDispatch } from 'react-redux' const CallItem = (props) => { @@ -22,7 +21,7 @@ const CallItem = (props) => { const handleStarClicked = (e) => { e.preventDefault(); e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); + e.nativeEvent.stopImmediatePropagation(); if (!starClicked) { setStarClicked(true); dispatch(addStar(props.call._id)); @@ -32,11 +31,11 @@ const CallItem = (props) => { } } - var rowSelected={}; + var rowSelected = {}; let starButton; var starClickable = {}; if (!starClicked) { - starClickable = {link: true }; + starClickable = { link: true }; } if (!call.star && starVisible) { starButton = @@ -47,32 +46,33 @@ const CallItem = (props) => { } if (call.star && call.star > 1) { starButton = ( - - - ) + + + ) } if (activeCall) { - rowSelected={positive: true, + rowSelected = { + positive: true, color: "blue", - key: "blue", - inverted: "true" - } + key: "blue", + inverted: "true" + } } var talkgroup; - if ((typeof props.talkgroups == 'undefined') || (typeof props.talkgroups[call.talkgroupNum] == 'undefined')) { - talkgroup = call.talkgroupNum; + if ((typeof props.talkgroups == 'undefined') || (typeof props.talkgroups[call.talkgroupNum] == 'undefined')) { + talkgroup = call.talkgroupNum; } else { - talkgroup = props.talkgroups[call.talkgroupNum].description; + talkgroup = props.talkgroups[call.talkgroupNum].description; } return ( - props.onClick({call: call}, e)} {...rowSelected}> - {call.len} - {talkgroup} - {time.toLocaleTimeString()} - setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton} + props.onClick({ call: call }, e)} {...rowSelected}> + {call.len} + {talkgroup} + {time.toLocaleTimeString()} + setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton} diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 22706c9..ee39862 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -9,7 +9,7 @@ import CalendarModal from "./CalendarModal"; import CallInfo from "./CallInfo"; import ListCalls from "./ListCalls"; import { useSelector, useDispatch } from 'react-redux' -import { setLive, setShortName, setFilter,setDateFilter } from "../features/callPlayer/callPlayerSlice"; +import { setLive, setFilter,setDateFilter } from "../features/callPlayer/callPlayerSlice"; import { getCalls, getOlderCalls, getNewerCalls, addCall } from "../features/calls/callsSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useInView } from 'react-intersection-observer'; @@ -20,8 +20,7 @@ import { Sticky, Menu, Icon, - Sidebar, - Button + Sidebar } from "semantic-ui-react"; import "./CallPlayer.css"; import queryString from '../query-string'; @@ -36,11 +35,11 @@ const socket = io(process.env.REACT_APP_BACKEND_SERVER); function CallPlayer(props) { const { shortName } = useParams(); - const { ref: loadOlderRef, inView: loadOlderInView, entry: loadOlderEntry } = useInView({ + const { ref: loadOlderRef, inView: loadOlderInView } = useInView({ /* Optional options */ threshold: 0.5 }); - const { ref: loadNewerRef, inView: loadNewerInView, entry: loadNewerEntry } = useInView({ + const { ref: loadNewerRef, inView: loadNewerInView } = useInView({ /* Optional options */ threshold: 0.5 }); @@ -83,9 +82,6 @@ function CallPlayer(props) { currentCallId = currentCall._id; } - - - const handlePlayPause = (playing) => { setIsPlaying(playing); } @@ -151,7 +147,7 @@ function CallPlayer(props) { const message = JSON.parse(data) switch (message.type) { case 'calls': - const ref = positionRef.current; + pageYOffset.current = positionRef.current.clientHeight; if (shouldPlayAddCallRef.current) { @@ -282,18 +278,11 @@ function CallPlayer(props) { // is this just for one call? if (uri.hasOwnProperty('call-id')) { const _id = uri['call-id']; - const date = new Date(parseInt(uri['time'])); setLoadCallId(_id); setAutoPlay(false); if (!urlOptions) setUrlOptions(true); - //callActions.fetchCallInfo(_id); - //callActions.setCallTime(date); } - - var filterType = "all"; - var filterCode = ""; - // is there a Filter set? if ((uri.hasOwnProperty('filter-code')) && (uri.hasOwnProperty('filter-type'))) { if (!urlOptions) setUrlOptions(true); @@ -302,8 +291,6 @@ function CallPlayer(props) { if (uri['filter-type'] === "group") { filter.filterType = 1; filter.filterGroupId = uri["filter-code"]; - filterType = "group"; - filterCode = uri["filter-code"]; } // The Filter is talkgroups @@ -311,8 +298,6 @@ function CallPlayer(props) { const tg = uri["filter-code"].split(',').map(Number); filter.filterType = 2; filter.filterTalkgroups = tg; - filterType = "talkgroup"; - filterCode = tg; } } diff --git a/frontend/src/Call/FilterModal.js b/frontend/src/Call/FilterModal.js index 1b7c91c..bf55082 100644 --- a/frontend/src/Call/FilterModal.js +++ b/frontend/src/Call/FilterModal.js @@ -1,4 +1,4 @@ -import React, { Component, useState } from "react"; +import React, { useState } from "react"; import { setGroupFilter, setTalkgroupFilter, setAllFilter, setStarredFilter } from "../features/callPlayer/callPlayerSlice"; import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useSelector, useDispatch } from 'react-redux' @@ -16,11 +16,9 @@ import "./FilterModal.css"; function FilterModal(props) { - const shortName = useSelector((state) => state.callPlayer.shortName); const globalFilterStarred = useSelector((state) => state.callPlayer.filterStarred); const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(props.shortName); const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(props.shortName); - const [open, setOpen] = useState(false); const [selectedTalkgroup, setSelectedTalkgroup] = useState([]); const [selectedGroup, setSelectedGroup] = useState(false); const [filterStarred, setFilterStarred] = useState(false); diff --git a/frontend/src/Call/ListCalls.js b/frontend/src/Call/ListCalls.js index 3aa621f..ed11a25 100644 --- a/frontend/src/Call/ListCalls.js +++ b/frontend/src/Call/ListCalls.js @@ -1,4 +1,4 @@ -import React, { useEffect, useLayoutEffect, useRef } from "react"; +import React, { useEffect, useRef } from "react"; import CallItem from "./CallItem"; import { Icon, diff --git a/frontend/src/Main/Main.js b/frontend/src/Main/Main.js index 372d169..52801ab 100644 --- a/frontend/src/Main/Main.js +++ b/frontend/src/Main/Main.js @@ -1,4 +1,4 @@ -import React, { Component, useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Link, useNavigate } from 'react-router-dom' import SystemCard from "../System/SystemCard"; import "./Main.css"; @@ -17,8 +17,8 @@ import { } from 'semantic-ui-react' -import { useSelector, useDispatch } from 'react-redux' -import { selectAllSystems, selectActiveSystems, useGetSystemsQuery } from "../features/api/apiSlice"; +import { useDispatch } from 'react-redux' +import { 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 */ @@ -83,15 +83,13 @@ 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 showFixedMenu = () => setFixed(true) - const { children } = props - return (
    @@ -212,14 +210,9 @@ const ResponsiveContainer = ({ children }) => ( const Main = (props) => { const [visible, setVisible] = useState(true); - const [duration, setDuration] = useState(500); const [currentSystem, setCurrentSystem] = useState(0); const navigate = useNavigate(); - const dispatch = useDispatch(); const {data:systems, isSuccess} = useGetSystemsQuery(); //= selectAllSystems(); - - - function useInterval(callback, delay) { const savedCallback = useRef(); @@ -241,8 +234,6 @@ const Main = (props) => { }, [delay]); } - const handleVisibility = () => setVisible(!visible); - const advanceSystem = () => { if (isSuccess) { let nextSystem = currentSystem + 1;