mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-13 01:07:42 -05:00
Merge branch 'master' into playlists
This commit is contained in:
6
frontend/src/Call/components/CalendarModal.css
Normal file
6
frontend/src/Call/components/CalendarModal.css
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
.react-datepicker__time-container .react-datepicker__time .react-datepicker__time-box ul.react-datepicker__time-list {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
62
frontend/src/Call/components/CalendarModal.js
Normal file
62
frontend/src/Call/components/CalendarModal.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import React, { 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 { useDispatch } from 'react-redux'
|
||||
import { subDays } from 'date-fns'
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
function CalendarModal(props) {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
const dispatch = useDispatch()
|
||||
const onClose = props.onClose;
|
||||
|
||||
const handleDateChange = (date) => {
|
||||
setStartDate(date);
|
||||
}
|
||||
|
||||
const handleClose = () => this.props.onClose(false);
|
||||
const handleDone = (onClose) => {
|
||||
dispatch(setDateFilter(startDate.getTime()));
|
||||
onClose(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<Modal open={props.open} onClose={handleClose} centered={false} size="tiny">
|
||||
<Modal.Header>Select a Date & Time</Modal.Header>
|
||||
<Modal.Content >
|
||||
<Modal.Description>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={handleDateChange}
|
||||
maxDate={new Date()}
|
||||
minDate={subDays(new Date(), process.env.REACT_APP_ARCHIVE_DAYS)}
|
||||
showTimeSelect
|
||||
timeFormat="HH:mm"
|
||||
timeIntervals={15}
|
||||
dateFormat="LLL"
|
||||
inline
|
||||
/>
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={()=>handleDone(onClose)} >
|
||||
<Icon name='checkmark' /> Done
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default CalendarModal;
|
||||
96
frontend/src/Call/components/CallInfo.js
Normal file
96
frontend/src/Call/components/CallInfo.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import React from "react";
|
||||
import { useCallLink } from "./CallLinks";
|
||||
import { useGetGroupsQuery, useGetTalkgroupsQuery } from '../../features/api/apiSlice'
|
||||
import { Link, useLocation, useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Header,
|
||||
Divider,
|
||||
List,
|
||||
Segment,
|
||||
Statistic,
|
||||
Icon,
|
||||
Menu
|
||||
} from "semantic-ui-react";
|
||||
|
||||
// ----------------------------------------------------
|
||||
function CallInfo(props) {
|
||||
const { shortName } = useParams();
|
||||
const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName);
|
||||
|
||||
let srcList = "";
|
||||
let callLength = "-";
|
||||
let callFreq = "-";
|
||||
let callDate = "-";
|
||||
let callTime = "-";
|
||||
let talkgroupNum = "-";
|
||||
let header = "Call Info"
|
||||
|
||||
if (props.call) {
|
||||
const currentCall = props.call;
|
||||
if ((talkgroupsData) && talkgroupsData.talkgroups[currentCall.talkgroupNum]) {
|
||||
header = talkgroupsData.talkgroups[currentCall.talkgroupNum].description;
|
||||
}
|
||||
const time = new Date(currentCall.time);
|
||||
callTime = time.toLocaleTimeString();
|
||||
callDate = time.toLocaleDateString();
|
||||
if (currentCall.freq) {
|
||||
const freq = currentCall.freq / 1000000;
|
||||
callFreq = Math.round(freq * 1000) / 1000;
|
||||
}
|
||||
|
||||
|
||||
srcList = currentCall.srcList.map((source, index) => <List.Item key={index}>{source.src}[{source.pos}]</List.Item>);
|
||||
callLength = currentCall.len;
|
||||
talkgroupNum = currentCall.talkgroupNum;
|
||||
|
||||
}
|
||||
const {callLink,callDownload,callTweet} = useCallLink(props.call)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Segment padded attached>
|
||||
<Header as='h1'>{header}</Header>
|
||||
<List bulleted horizontal link>
|
||||
{srcList}
|
||||
</List>
|
||||
<Divider />
|
||||
<Statistic size='small'>
|
||||
<Statistic.Label>Seconds</Statistic.Label>
|
||||
<Statistic.Value>{callLength}</Statistic.Value>
|
||||
</Statistic>
|
||||
<Statistic size='small'>
|
||||
<Statistic.Label>Talkgroup</Statistic.Label>
|
||||
<Statistic.Value>{talkgroupNum}</Statistic.Value>
|
||||
</Statistic>
|
||||
|
||||
<List divided verticalAlign='middle'>
|
||||
<List.Item>
|
||||
<Icon name="wait" />
|
||||
<List.Content>
|
||||
{callTime}
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Icon name="calendar outline" />
|
||||
<List.Content>
|
||||
{callDate}
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<Icon name="cubes" />
|
||||
<List.Content>
|
||||
{callFreq} MHz
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Segment>
|
||||
<Menu attached='bottom'>
|
||||
<a href={callTweet}><Menu.Item name="tweet" ><Icon name='twitter' />Tweet</Menu.Item></a>
|
||||
<a href={callDownload}><Menu.Item name="download"><Icon name="download" />Download</Menu.Item></a>
|
||||
<a href={callLink}><Menu.Item name="link"><Icon name="at" />Link</Menu.Item></a>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CallInfo;
|
||||
99
frontend/src/Call/components/CallItem.js
Normal file
99
frontend/src/Call/components/CallItem.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Table,
|
||||
Icon,
|
||||
Label
|
||||
} from "semantic-ui-react";
|
||||
|
||||
|
||||
import { addStar, removeStar } from "../../features/calls/callsSlice";
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
const CallItem = (props) => {
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
let rowSelected = {};
|
||||
let starButton;
|
||||
let starClickable = {};
|
||||
|
||||
const onDragStart = (event) => {
|
||||
// It receives a DragEvent
|
||||
// which inherits properties from
|
||||
// MouseEvent and Event
|
||||
// so we can access the element
|
||||
// through event.currentTarget
|
||||
|
||||
// Later, we'll save
|
||||
// in a hook variable
|
||||
// the item being dragged
|
||||
const callId = event.currentTarget.dataset.callid;
|
||||
event.dataTransfer.setData("call-id", callId);
|
||||
console.log("Dragging: " + callId);
|
||||
|
||||
}
|
||||
|
||||
if (!starClicked) {
|
||||
starClickable = { link: true };
|
||||
}
|
||||
if (!call.star && starVisible) {
|
||||
starButton = <Icon name='star outline' />
|
||||
}
|
||||
|
||||
if (call.star) {
|
||||
starButton = <Icon name='star' />
|
||||
}
|
||||
if (call.star && call.star > 1) {
|
||||
starButton = (<Icon.Group >
|
||||
<Icon {...starClickable} name='star' />
|
||||
<Label circular color='red' size='mini' floating>
|
||||
{call.star}
|
||||
</Label>
|
||||
</Icon.Group>)
|
||||
}
|
||||
|
||||
if (activeCall) {
|
||||
rowSelected = {
|
||||
positive: true,
|
||||
color: "blue",
|
||||
key: "blue",
|
||||
inverted: "true"
|
||||
}
|
||||
}
|
||||
let talkgroup;
|
||||
if ((typeof props.talkgroups == 'undefined') || (typeof props.talkgroups[call.talkgroupNum] == 'undefined')) {
|
||||
talkgroup = call.talkgroupNum;
|
||||
} else {
|
||||
talkgroup = props.talkgroups[call.talkgroupNum].description;
|
||||
}
|
||||
return (
|
||||
<Table.Row draggable="true" onClick={(e) => props.onClick({ call: call }, e)} {...rowSelected} onDragStart={onDragStart} data-callid={call._id}>
|
||||
<Table.Cell> {call.len} </Table.Cell>
|
||||
<Table.Cell> {talkgroup} </Table.Cell>
|
||||
<Table.Cell> {time.toLocaleTimeString()} </Table.Cell>
|
||||
<Table.Cell onMouseEnter={() => setStarVisible(true)} onMouseLeave={() => setStarVisible(false)} onClick={handleStarClicked}>{starButton}</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default CallItem;
|
||||
36
frontend/src/Call/components/CallLinks.js
Normal file
36
frontend/src/Call/components/CallLinks.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
export function useCallLink(currentCall) {
|
||||
const filterType = useSelector((state) => state.callPlayer.filterType);
|
||||
const filterGroupId = useSelector((state) => state.callPlayer.filterGroupId);
|
||||
const filterTalkgroups = useSelector((state) => state.callPlayer.filterTalkgroups);
|
||||
const shortName = useSelector((state) => state.callPlayer.shortName);
|
||||
if (currentCall) {
|
||||
let search = ""
|
||||
const time = new Date(currentCall.time);
|
||||
const callTime = time.toLocaleTimeString();
|
||||
const callDate = time.toLocaleDateString();
|
||||
|
||||
switch (filterType) {
|
||||
|
||||
case 1:
|
||||
search = `filter-type=group&filter-code=${filterGroupId}&`;
|
||||
break;
|
||||
case 2:
|
||||
search = `filter-type=talkgroup&filter-code=${filterTalkgroups}&`;
|
||||
break;
|
||||
default:
|
||||
case 0:
|
||||
break;
|
||||
|
||||
}
|
||||
const callLink = "/system/" + shortName + "?" + search + "call-id=" + currentCall._id + "&time=" + (time.getTime() + 1);
|
||||
const callDownload = currentCall.url;
|
||||
const callTweet = "https://twitter.com/intent/tweet?url=" + encodeURIComponent(document.location.origin + callLink) + "&via=" + encodeURIComponent("OpenMHz");
|
||||
|
||||
return { callLink, callDownload, callTweet }
|
||||
} else {
|
||||
return { callLink: "", callDownload: "", callTweet: "" }
|
||||
}
|
||||
}
|
||||
3
frontend/src/Call/components/FilterModal.css
Normal file
3
frontend/src/Call/components/FilterModal.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.active.tab {
|
||||
min-height: 250px;
|
||||
}
|
||||
146
frontend/src/Call/components/FilterModal.js
Normal file
146
frontend/src/Call/components/FilterModal.js
Normal file
@@ -0,0 +1,146 @@
|
||||
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'
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
Icon,
|
||||
Tab,
|
||||
Header,
|
||||
Dropdown,
|
||||
Checkbox,
|
||||
Divider
|
||||
} from "semantic-ui-react";
|
||||
import "./FilterModal.css";
|
||||
|
||||
|
||||
function FilterModal(props) {
|
||||
const globalFilterStarred = useSelector((state) => state.callPlayer.filterStarred);
|
||||
const { data:groupsData, isSuccess:isGroupsSuccess } = useGetGroupsQuery(props.shortName);
|
||||
const { data:talkgroupsData, isSuccess:isTalkgroupsSuccess } = useGetTalkgroupsQuery(props.shortName);
|
||||
const [selectedTalkgroup, setSelectedTalkgroup] = useState([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState(false);
|
||||
const [filterStarred, setFilterStarred] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const dispatch = useDispatch()
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let talkgroupList = [];
|
||||
if (isTalkgroupsSuccess) {
|
||||
for (const num in talkgroupsData["talkgroups"]) {
|
||||
const talkgroup = talkgroupsData["talkgroups"][num];
|
||||
const 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]
|
||||
const obj = {
|
||||
key: group._id,
|
||||
value: group._id,
|
||||
text: group.groupName
|
||||
}
|
||||
groupList.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
const panes = [
|
||||
{
|
||||
menuItem: 'All', render: () => {
|
||||
return (
|
||||
<Tab.Pane attached={false}>
|
||||
<Header>All Calls</Header>
|
||||
<p>Dispaly all of the calls.</p>
|
||||
</Tab.Pane>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
menuItem: 'Groups', render: () => {
|
||||
return (
|
||||
<Tab.Pane attached={false}>
|
||||
<Dropdown placeholder='Groups' fluid selection options={groupList} value={selectedGroup} name='selectedGroup' onChange={handleGroupChange} />
|
||||
|
||||
</Tab.Pane>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
menuItem: 'Talkgroups', render: () => {
|
||||
return (
|
||||
<Tab.Pane attached={false}>
|
||||
<Dropdown placeholder='Talkgroups' fluid multiple selection options={talkgroupList} value={selectedTalkgroup} name='selectedTalkgroup' onChange={handleTalkgroupChange} />
|
||||
</Tab.Pane>
|
||||
)
|
||||
}
|
||||
},
|
||||
]
|
||||
return (
|
||||
|
||||
<Modal open={props.open} onClose={handleClose} centered={false} size="tiny">
|
||||
<Modal.Header>Select a Filter</Modal.Header>
|
||||
<Modal.Content >
|
||||
<Modal.Description>
|
||||
<Tab menu={{ pointing: true }} panes={panes} defaultActiveIndex={activeTab} onTabChange={handleTabChange} />
|
||||
<Divider />
|
||||
<Checkbox label='Show only Starred calls' checked={filterStarred} name='filterStarred' onChange={toggleStarred} />
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={()=> handleDone(onClose)} >
|
||||
<Icon name='checkmark' /> Done
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default FilterModal;
|
||||
73
frontend/src/Call/components/GroupModal.js
Normal file
73
frontend/src/Call/components/GroupModal.js
Normal file
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
Icon,
|
||||
Dropdown
|
||||
} from "semantic-ui-react";
|
||||
import "./FilterModal.css";
|
||||
|
||||
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];
|
||||
const obj = {
|
||||
key: group._id,
|
||||
value: group._id,
|
||||
text: group.groupName
|
||||
}
|
||||
groupOptions.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<Modal open={props.open} onClose={() => setOpen(false)} onOpen={() => setOpen(true)} size='tiny'>
|
||||
|
||||
<Modal.Header>Select a Group</Modal.Header>
|
||||
<Modal.Content >
|
||||
<Modal.Description>
|
||||
<p>Choose the type of calls you want to listen to</p>
|
||||
<Dropdown placeholder='Group...' fluid selection options={groupOptions} name='selectedGroup' onChange={(e, data) => setSelectedGroup(data.value)} />
|
||||
|
||||
</Modal.Description>
|
||||
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={()=> handleDone(onClose)} >
|
||||
<Icon name='headphones' /> Listen
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default GroupModal;
|
||||
60
frontend/src/Call/components/ListCalls.js
Normal file
60
frontend/src/Call/components/ListCalls.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import CallItem from "./CallItem";
|
||||
import {
|
||||
Icon,
|
||||
Table,
|
||||
Ref
|
||||
} from "semantic-ui-react";
|
||||
import "../CallPlayer.css";
|
||||
|
||||
// ----------------------------------------------------
|
||||
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
|
||||
|
||||
useEffect(() => {
|
||||
if (activeCallRef.current) {
|
||||
activeCallRef.current.scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const callsData = props.callsData;
|
||||
return (
|
||||
|
||||
<Table id="calls" unstackable >
|
||||
<Table.Header >
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Len</Table.HeaderCell>
|
||||
<Table.HeaderCell>Talkgroup</Table.HeaderCell>
|
||||
<Table.HeaderCell>Time</Table.HeaderCell>
|
||||
<Table.HeaderCell><Icon name='star' /></Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
{callsData &&
|
||||
<Table.Body>
|
||||
|
||||
{
|
||||
props.callsData.ids.map((callId, index) => {
|
||||
if (callId === props.activeCallId) {
|
||||
return (<Ref innerRef={activeCallRef} key={index} ><CallItem activeCall={true} call={props.callsData.entities[callId]} talkgroups={props.talkgroups} key={index} onClick={props.playCall}/></Ref>)
|
||||
} else {
|
||||
return <CallItem activeCall={false} call={props.callsData.entities[callId]} talkgroups={props.talkgroups} key={index} onClick={props.playCall}/>
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
</Table.Body>
|
||||
}
|
||||
</Table>
|
||||
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default ListCalls;
|
||||
117
frontend/src/Call/components/MediaPlayer.js
Normal file
117
frontend/src/Call/components/MediaPlayer.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Menu,
|
||||
Icon,
|
||||
Progress,
|
||||
Label
|
||||
} from "semantic-ui-react";
|
||||
import ReactAudioPlayer from 'react-audio-player'
|
||||
|
||||
|
||||
|
||||
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(); } }
|
||||
|
||||
const call = props.call;
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const audio = audioRef.current.audioEl.current;
|
||||
const onEnded = props.onEnded;
|
||||
setSourceIndex(0);
|
||||
if (call) {
|
||||
audio.src = call.url;
|
||||
const playPromise = audio.play();
|
||||
|
||||
// In browsers that don’t yet support this functionality,
|
||||
// playPromise won’t be defined.
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.then(function () {
|
||||
|
||||
}).catch(function (error) {
|
||||
console.log("Automatic playback failed: " + error);
|
||||
onEnded();
|
||||
// Show a UI element to let the user manually start playback.
|
||||
});
|
||||
} else {
|
||||
audio.src = false;
|
||||
}
|
||||
}
|
||||
}, [call]);
|
||||
|
||||
const updatePlayProgress = () => {
|
||||
const audio = audioRef.current.audioEl.current;
|
||||
const { currentTime, duration } = audio;
|
||||
|
||||
|
||||
// this checks to see if it should display the next Source ID
|
||||
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
|
||||
setPlayProgress(currentTime / duration * 100);
|
||||
setPlayTime(Math.floor(currentTime));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let playEnabled = { "disabled": true }
|
||||
let sourceId = "-";
|
||||
|
||||
if (call) {
|
||||
if (call.srcList.length > sourceIndex) {
|
||||
sourceId = call.srcList[sourceIndex].src;
|
||||
}
|
||||
playEnabled = {};
|
||||
}
|
||||
return (
|
||||
<Menu.Menu>
|
||||
<ReactAudioPlayer
|
||||
ref={audioRef}
|
||||
onPause={handlePause}
|
||||
onPlay={handlePlay}
|
||||
listenInterval={100}
|
||||
onListen={updatePlayProgress}
|
||||
onEnded={props.onEnded}
|
||||
autoPlay
|
||||
/>
|
||||
|
||||
|
||||
<Menu.Item onClick={playPause} >
|
||||
|
||||
{
|
||||
isPlaying
|
||||
? (<Icon name="pause" />)
|
||||
: (<Icon name="play" />)
|
||||
}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Progress inverted percent={playProgress} />
|
||||
<Label color="black">
|
||||
{playTime}
|
||||
Sec
|
||||
</Label>
|
||||
<Label color="black" className="desktop-only">
|
||||
{sourceId}
|
||||
</Label>
|
||||
|
||||
</Menu.Item>
|
||||
</Menu.Menu>
|
||||
)
|
||||
}
|
||||
|
||||
export default MediaPlayer;
|
||||
41
frontend/src/Call/components/SupportModal.js
Normal file
41
frontend/src/Call/components/SupportModal.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import {useState} from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
Icon
|
||||
} from "semantic-ui-react";
|
||||
import "./FilterModal.css";
|
||||
|
||||
|
||||
function SupportModal(props) {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleClose = () => props.onClose();
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={() => setOpen(false)} onOpen={() => setOpen(true)} trigger={<div><Icon name="coffee" />Support OpenMHz</div>} size='tiny' >
|
||||
<Modal.Header>Support OpenMHz</Modal.Header>
|
||||
<Modal.Content image>
|
||||
<Icon size='massive' name="coffee" />
|
||||
<Modal.Description>
|
||||
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
|
||||
<p>No worries if not, I am not having problems keeping it going.</p>
|
||||
<a href="https://github.com/sponsors/robotastic"><Button>
|
||||
Support
|
||||
</Button></a>
|
||||
</Modal.Description>
|
||||
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setOpen(false)} >
|
||||
Done
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default SupportModal;
|
||||
Reference in New Issue
Block a user