support for multiple systems in an Event

This commit is contained in:
Luke Berndt
2023-01-25 21:35:00 -05:00
parent 755364c0b1
commit 8d0658a97f
4 changed files with 112 additions and 98 deletions

View File

@@ -4,9 +4,9 @@ import MediaPlayer from "./components/MediaPlayer";
import SupportModal from "./components/SupportModal"; import SupportModal from "./components/SupportModal";
import CallInfo from "./components/CallInfo"; import CallInfo from "./components/CallInfo";
import ListCalls from "./components/ListCalls"; import ListCalls from "./components/ListCalls";
import { useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { getOlderCalls, getNewerCalls } from "../features/calls/callsSlice"; import { getOlderCalls, getNewerCalls } from "../features/calls/callsSlice";
import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useGetTalkgroupsQuery } from '../features/api/apiSlice'
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
import { import {
Container, Container,
@@ -32,7 +32,6 @@ function CallPlayer(props) {
const { shortName } = useParams(); const { shortName } = useParams();
const selectCallId = props.selectCallId; const selectCallId = props.selectCallId;
const callsData = props.callsData; const callsData = props.callsData;
const { ref: loadOlderRef, inView: loadOlderInView } = useInView({ const { ref: loadOlderRef, inView: loadOlderInView } = useInView({
/* Optional options */ /* Optional options */
threshold: 0.5 threshold: 0.5
@@ -53,6 +52,7 @@ function CallPlayer(props) {
const stickyRef = useRef(); // lets us get the Y Scroll offset for the Call List const stickyRef = useRef(); // lets us get the Y Scroll offset for the Call List
const positionRef = useRef(); // lets us get the Y Scroll offset for the Call List const positionRef = useRef(); // lets us get the Y Scroll offset for the Call List
const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback const shouldPlayAddCallRef = useRef(); // we need to do this to make the current value of isPlaying available in the socket message callback
const live = useSelector((state) => state.callPlayer.live);
shouldPlayAddCallRef.current = (!isPlaying && autoPlay)?true:false; shouldPlayAddCallRef.current = (!isPlaying && autoPlay)?true:false;
let currentCallId = false; let currentCallId = false;
@@ -92,7 +92,7 @@ function CallPlayer(props) {
} }
useEffect(() => { useEffect(() => {
if (loadNewerInView && callsData && (callsData.ids.length > 0)) { if (loadNewerInView && !live && callsData && (callsData.ids.length > 0)) {
dispatch(getNewerCalls({})); dispatch(getNewerCalls({}));
} }
}, [loadNewerInView]); }, [loadNewerInView]);

View File

@@ -5,7 +5,7 @@ import GroupModal from "./components/GroupModal";
import CalendarModal from "./components/CalendarModal"; import CalendarModal from "./components/CalendarModal";
import CallPlayer from "./CallPlayer"; import CallPlayer from "./CallPlayer";
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import { setLive, setFilter,setDateFilter } from "../features/callPlayer/callPlayerSlice"; import { setLive, setFilter, setDateFilter } from "../features/callPlayer/callPlayerSlice";
import { getCalls, addCall } from "../features/calls/callsSlice"; import { getCalls, addCall } from "../features/calls/callsSlice";
import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice' import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../features/api/apiSlice'
@@ -52,7 +52,7 @@ function Calls(props) {
const positionRef = useRef(); // lets us get the Y Scroll offset for the Call List 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 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 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; shouldPlayAddCallRef.current = (!isPlaying && autoPlay) ? true : false;
const filterType = useSelector((state) => state.callPlayer.filterType); const filterType = useSelector((state) => state.callPlayer.filterType);
const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId); const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId);
@@ -106,8 +106,9 @@ function Calls(props) {
switch (message.type) { switch (message.type) {
case 'calls': case 'calls':
pageYOffset.current = positionRef.current.clientHeight; if (positionRef.current) {
pageYOffset.current = positionRef.current.clientHeight;
}
setSelectCallId(message._id); setSelectCallId(message._id);
dispatch(addCall(message)); dispatch(addCall(message));
@@ -273,7 +274,7 @@ function Calls(props) {
} }
}) })
socket.on("new message", handleSocketMessage ); socket.on("new message", handleSocketMessage);
return () => { return () => {
socket.off('connect'); socket.off('connect');
@@ -295,15 +296,18 @@ function Calls(props) {
updateUri(); updateUri();
}, [filterGroupId, filterTalkgroups, filterType, filterDate, filterStarred, selectCallId]) }, [filterGroupId, filterTalkgroups, filterType, filterDate, filterStarred, selectCallId])
useEffect(() => { useEffect(() => {
if (!urlOptions && groupsData && (groupsData.length > 0)) { if (!urlOptions && groupsData && (groupsData.length > 0)) {
setGroupVisible(true); setGroupVisible(true);
} }
}, [groupsData]) }, [groupsData])
useLayoutEffect( () => { useEffect(() => {
const scrollAmount = parseInt(positionRef.current.clientHeight) - parseInt(pageYOffset.current); pageYOffset.current = positionRef.current.clientHeight;
}, [callsData])
useLayoutEffect(() => {
const scrollAmount = parseInt(pageYOffset.current) - parseInt(positionRef.current.clientHeight);
if (scrollAmount > 0) { if (scrollAmount > 0) {
window.scrollBy(0, scrollAmount); window.scrollBy(0, scrollAmount);
} }
@@ -376,7 +380,7 @@ function Calls(props) {
</Menu.Item> </Menu.Item>
</Menu.Menu> </Menu.Menu>
</Menu> </Menu>
<CallPlayer callsData={callsData} selectCallId={selectCallId} /> <CallPlayer callsData={callsData} selectCallId={selectCallId} />
</div> </div>
); );
} }

View File

@@ -19,6 +19,7 @@ import {
Message Message
} from "semantic-ui-react"; } from "semantic-ui-react";
import { useAddNewEventMutation, useGetTalkgroupsQuery } from '../../features/api/apiSlice' import { useAddNewEventMutation, useGetTalkgroupsQuery } from '../../features/api/apiSlice'
import { setPlaylist, addToPlaylist, removeFromPLaylist } from '../../features/callPlayer/callPlayerSlice';
import PlaylistItem from "./PlaylistItem" import PlaylistItem from "./PlaylistItem"
function PlaylistBuilder(props) { function PlaylistBuilder(props) {
@@ -26,31 +27,17 @@ function PlaylistBuilder(props) {
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName);
const [addNewEvent, { isLoading }] = useAddNewEventMutation() const [addNewEvent, { isLoading }] = useAddNewEventMutation()
const { loading: callsLoading, data: callsData } = useSelector((state) => state.calls); const { loading: callsLoading, data: callsData } = useSelector((state) => state.calls);
const [playlist, setPlaylist] = useState([]);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [title, setTitle] = useState("") const [title, setTitle] = useState("")
const [description, setDescription] = useState("") const [description, setDescription] = useState("")
const handleTitleChange = (e, { name, value }) => setTitle(value); const handleTitleChange = (e, { name, value }) => setTitle(value);
const handleDescriptionChange = (e, { name, value }) => setDescription(value); const handleDescriptionChange = (e, { name, value }) => setDescription(value);
const playlist = useSelector((state) => state.callPlayer.playlist);
const dispatch = useDispatch();
const compareCalls = (a, b) => {
const aTimestamp = new Date(a.time).getTime()
const bTimestamp = new Date(b.time).getTime()
if (aTimestamp > bTimestamp) {
return -1
} else {
return 1
}
}
const addCall = (call) => {
const found = playlist.find(c => c._id == call._id);
if (!found) {
playlist.push(call);
setPlaylist([...playlist.sort(compareCalls)])
}
}
const onDragOver = (event) => { const onDragOver = (event) => {
// It also receives a DragEvent. // It also receives a DragEvent.
@@ -67,6 +54,9 @@ function PlaylistBuilder(props) {
event.preventDefault(); event.preventDefault();
} }
const removeItem = (call) => {
dispatch(removeFromPLaylist(call))
}
const onDrop = (event) => { const onDrop = (event) => {
// Here, we will: // Here, we will:
@@ -74,18 +64,10 @@ function PlaylistBuilder(props) {
// - and reset the DnD state // - and reset the DnD state
var callId = event.dataTransfer.getData("call-id"); var callId = event.dataTransfer.getData("call-id");
console.log("Dropping: " + callId); console.log("Dropping: " + callId);
addCall(callsData.entities[callId]) dispatch(addToPlaylist(callsData.entities[callId]))
} }
const removeItem = (call) => {
if (call) {
const found = playlist.findIndex(c => c._id == call._id);
if (found!=-1) {
playlist.splice(found,1)
setPlaylist([...playlist.sort(compareCalls)])
}
}
}
let listItems = ""; let listItems = "";
@@ -93,10 +75,12 @@ const removeItem = (call) => {
let tgAlpha = call.talkgroupNum; let tgAlpha = call.talkgroupNum;
if (talkgroupsData) { if (talkgroupsData) {
const talkgroup = talkgroupsData.talkgroups[call.talkgroupNum] const talkgroup = talkgroupsData.talkgroups[call.talkgroupNum]
tgAlpha = talkgroup.alpha?talkgroup.alpha:tgAlpha; if (talkgroup && talkgroup.alpha!=" ") {
tgAlpha = talkgroup.alpha;
}
} }
return ( <PlaylistItem call={call} tgAlpha={tgAlpha} index={index} removeItem={removeItem}/> ) return (<PlaylistItem call={call} tgAlpha={tgAlpha} index={index} removeItem={removeItem} />)
}) })
const submitPlaylist = () => { const submitPlaylist = () => {
@@ -104,81 +88,80 @@ const removeItem = (call) => {
} }
const handleSubmit = () => { const handleSubmit = () => {
console.log(title)
setOpen(false) setOpen(false)
const callIds = playlist.map( (call) => call._id) const callIds = playlist.map((call) => call._id)
const event = { const event = {
title, title,
description, description,
callIds callIds
} }
addNewEvent(event) addNewEvent(event);
setTitle("") setTitle("");
setDescription("") setDescription("");
setPlaylist([]) dispatch(setPlaylist([]));
} }
let content = (<Header as="h3" icon textAlign='center' style={{paddingTop: "3em"}}><Icon name='target' />Drag Calls Here</Header>) let content = (<Header as="h3" icon textAlign='center' style={{ paddingTop: "3em" }}><Icon name='target' />Drag Calls Here</Header>)
if (playlist.length > 0) { if (playlist.length > 0) {
content = (<Table basic="very"> content = (<Table basic="very">
<Table.Body> <Table.Body>
{listItems} {listItems}
</Table.Body> </Table.Body>
</Table>) </Table>)
} }
return ( return (
<> <>
<Modal <Modal
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
onOpen={() => setOpen(true)} onOpen={() => setOpen(true)}
open={open} open={open}
> >
<Modal.Header>Submit an Event</Modal.Header> <Modal.Header>Submit an Event</Modal.Header>
<Modal.Content> <Modal.Content>
<Modal.Description> <Modal.Description>
<Message icon> <Message icon>
<Icon name='warning sign' /> <Icon name='warning sign' />
<Message.Content>The Events feature is a work in progress! Things may break, change, or get deleted. Contact me with ideas or problems: luke@robotastic.com</Message.Content> <Message.Content>The Events feature is a work in progress! Things may break, change, or get deleted. Contact me with ideas or problems: luke@robotastic.com</Message.Content>
</Message> </Message>
<Header>Event</Header> <Header>Event</Header>
<Form> <Form>
<Form.Input <Form.Input
name="title" name="title"
value={title} value={title}
label="Title" label="Title"
onChange={handleTitleChange} onChange={handleTitleChange}
/> />
<Form.TextArea <Form.TextArea
name="description" name="description"
value={description} value={description}
label="Description" label="Description"
placeholder='Please describe the event' placeholder='Please describe the event'
onChange={handleDescriptionChange} onChange={handleDescriptionChange}
/> />
</Form> </Form>
</Modal.Description> </Modal.Description>
</Modal.Content> </Modal.Content>
<Modal.Actions> <Modal.Actions>
<Button color='black' onClick={() => setOpen(false)}> <Button color='black' onClick={() => setOpen(false)}>
Nope Nope
</Button> </Button>
<Button <Button
content="Submit" content="Submit"
labelPosition='right' labelPosition='right'
icon='checkmark' icon='checkmark'
onClick={handleSubmit} onClick={handleSubmit}
positive positive
/> />
</Modal.Actions> </Modal.Actions>
</Modal> </Modal>
<Tab.Pane attached='bottom' onDragOver={onDragOver} onDrop={onDrop} style={{overflowY: "auto", height: 300}}> <Tab.Pane attached='bottom' onDragOver={onDragOver} onDrop={onDrop} style={{ overflowY: "auto", height: 300 }}>
{content} {content}
</Tab.Pane> </Tab.Pane>
<Menu fluid widths={2}> <Menu fluid widths={2}>
<Menu.Item name="clear" onClick={() => setPlaylist([])}><Icon name='delete' />Clear</Menu.Item> <Menu.Item name="clear" onClick={() => dispatch(setPlaylist([]))}><Icon name='delete' />Clear</Menu.Item>
<Menu.Item active name="submit" onClick={submitPlaylist}><Icon name="cloud upload" />Submit</Menu.Item> <Menu.Item active name="submit" onClick={submitPlaylist}><Icon name="cloud upload" />Submit</Menu.Item>
</Menu> </Menu>
</> </>

View File

@@ -17,6 +17,16 @@ const initialState = {
live: false live: false
} }
const compareCalls = (a, b) => {
const aTimestamp = new Date(a.time).getTime()
const bTimestamp = new Date(b.time).getTime()
if (aTimestamp > bTimestamp) {
return -1
} else {
return 1
}
}
export const callPlayerSlice = createSlice({ export const callPlayerSlice = createSlice({
name: 'callPlayer', name: 'callPlayer',
initialState, initialState,
@@ -33,6 +43,23 @@ export const callPlayerSlice = createSlice({
state.playlist = action.payload; state.playlist = action.payload;
} }
}, },
removeFromPlaylist: (state, action) => {
const call = action.payload;
if (call) {
const found = state.playlist.findIndex(c => c._id == call._id);
if (found != -1) {
state.playlist = state.playlist.splice(found, 1)
}
}
},
addToPlaylist: (state, action) => {
const call = action.payload;
const found = state.playlist.find(c => c._id == call._id);
if (!found) {
const newlist = [...state.playlist, call];
state.playlist = newlist.sort(compareCalls);
}
},
setFilter: (state,action) => { setFilter: (state,action) => {
var filter={}; var filter={};
state.live = !action.payload.live ? state.live : action.payload.live; state.live = !action.payload.live ? state.live : action.payload.live;
@@ -75,7 +102,7 @@ export const callPlayerSlice = createSlice({
}) })
// Action creators are generated for each case reducer function // Action creators are generated for each case reducer function
export const { setCurrentCallId, setCallTime, setFilter, setLive, setShortName, setDateFilter, setStarredFilter, setAllFilter, setGroupFilter, setTalkgroupFilter } = callPlayerSlice.actions export const { setCurrentCallId, setCallTime, setFilter, setLive, setPlaylist, removeFromPLaylist, addToPlaylist, setShortName, setDateFilter, setStarredFilter, setAllFilter, setGroupFilter, setTalkgroupFilter } = callPlayerSlice.actions
export default callPlayerSlice.reducer export default callPlayerSlice.reducer