From 7ef2d370ef52c4caf7db135c66e33dc064d8f718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20R=C3=BCdiger?= <maxru105@student.liu.se> Date: Wed, 28 Apr 2021 09:01:44 +0000 Subject: [PATCH] 139 operator view --- client/src/Main.tsx | 8 +- .../admin/competitions/CompetitionManager.tsx | 2 +- ...age.test.tsx => OperatorViewPage.test.tsx} | 4 +- ...enterViewPage.tsx => OperatorViewPage.tsx} | 182 +++++++++++++----- ...iewPage.test.tsx => TeamViewPage.test.tsx} | 4 +- ...rticipantViewPage.tsx => TeamViewPage.tsx} | 14 +- client/src/pages/views/ViewSelectPage.tsx | 6 +- client/src/pages/views/styled.tsx | 14 +- 8 files changed, 160 insertions(+), 74 deletions(-) rename client/src/pages/views/{PresenterViewPage.test.tsx => OperatorViewPage.test.tsx} (91%) rename client/src/pages/views/{PresenterViewPage.tsx => OperatorViewPage.tsx} (53%) rename client/src/pages/views/{ParticipantViewPage.test.tsx => TeamViewPage.test.tsx} (85%) rename client/src/pages/views/{ParticipantViewPage.tsx => TeamViewPage.tsx} (77%) diff --git a/client/src/Main.tsx b/client/src/Main.tsx index b999a977..b1f0675d 100644 --- a/client/src/Main.tsx +++ b/client/src/Main.tsx @@ -7,8 +7,8 @@ import LoginPage from './pages/login/LoginPage' import PresentationEditorPage from './pages/presentationEditor/PresentationEditorPage' import AudienceViewPage from './pages/views/AudienceViewPage' import JudgeViewPage from './pages/views/JudgeViewPage' -import ParticipantViewPage from './pages/views/ParticipantViewPage' -import PresenterViewPage from './pages/views/PresenterViewPage' +import TeamViewPage from './pages/views/TeamViewPage' +import OperatorViewPage from './pages/views/OperatorViewPage' import ViewSelectPage from './pages/views/ViewSelectPage' import SecureRoute from './utils/SecureRoute' @@ -24,8 +24,8 @@ const Main: React.FC = () => { <SecureRoute path="/admin" component={AdminPage} /> <SecureRoute path="/editor/competition-id=:competitionId" component={PresentationEditorPage} /> <Route exact path="/:code" component={ViewSelectPage} /> - <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} /> - <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} /> + <Route exact path="/team/id=:id&code=:code" component={TeamViewPage} /> + <SecureRoute exact path="/operator/id=:id&code=:code" component={OperatorViewPage} /> <Route exact path="/judge/id=:id&code=:code" component={JudgeViewPage} /> <Route exact path="/audience/id=:id&code=:code" component={AudienceViewPage} /> </Switch> diff --git a/client/src/pages/admin/competitions/CompetitionManager.tsx b/client/src/pages/admin/competitions/CompetitionManager.tsx index 83f399b9..8ec3ee29 100644 --- a/client/src/pages/admin/competitions/CompetitionManager.tsx +++ b/client/src/pages/admin/competitions/CompetitionManager.tsx @@ -94,7 +94,7 @@ const CompetitionManager: React.FC = (props: any) => { } const handleStartCompetition = () => { - history.push(`/presenter/id=${activeId}&code=123123`) + history.push(`/operator/id=${activeId}&code=123123`) console.log('GLHF!') } diff --git a/client/src/pages/views/PresenterViewPage.test.tsx b/client/src/pages/views/OperatorViewPage.test.tsx similarity index 91% rename from client/src/pages/views/PresenterViewPage.test.tsx rename to client/src/pages/views/OperatorViewPage.test.tsx index fd7b0a96..e658fe3b 100644 --- a/client/src/pages/views/PresenterViewPage.test.tsx +++ b/client/src/pages/views/OperatorViewPage.test.tsx @@ -4,7 +4,7 @@ import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' import store from '../../store' -import PresenterViewPage from './PresenterViewPage' +import OperatorViewPage from './OperatorViewPage' it('renders presenter view page', () => { const compRes: any = { @@ -36,7 +36,7 @@ it('renders presenter view page', () => { render( <BrowserRouter> <Provider store={store}> - <PresenterViewPage /> + <OperatorViewPage /> </Provider> </BrowserRouter> ) diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx similarity index 53% rename from client/src/pages/views/PresenterViewPage.tsx rename to client/src/pages/views/OperatorViewPage.tsx index 6c853a60..53af321c 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/OperatorViewPage.tsx @@ -7,6 +7,7 @@ import { DialogTitle, List, ListItem, + ListItemText, Popover, Tooltip, Typography, @@ -14,6 +15,8 @@ import { useTheme, } from '@material-ui/core' import AssignmentIcon from '@material-ui/icons/Assignment' +import FileCopyIcon from '@material-ui/icons/FileCopy' +import SupervisorAccountIcon from '@material-ui/icons/SupervisorAccount' import BackspaceIcon from '@material-ui/icons/Backspace' import ChevronLeftIcon from '@material-ui/icons/ChevronLeft' import ChevronRightIcon from '@material-ui/icons/ChevronRight' @@ -36,23 +39,39 @@ import SlideDisplay from '../presentationEditor/components/SlideDisplay' import PresentationComponent from './components/PresentationComponent' import Timer from './components/Timer' import { - PresenterButton, - PresenterContainer, - PresenterContent, - PresenterFooter, - PresenterHeader, - PresenterInnerContent, + OperatorButton, + OperatorContainer, + OperatorFooter, + OperatorHeader, + OperatorContent, + OperatorInnerContent, SlideCounter, ToolBarContainer, } from './styled' /** + * Description: + * * Presentation is an active competition + * + * + * =========================================== + * TODO: + * - Instead of copying code for others to join the competition, copy URL. + * + * - Make code popup less code by using .map instead + * + * - Fix scoreboard + * + * - When two userers are connected to the same Localhost:5000 and updates/starts/end competition it + * creates a bug where the competition can't be started. + * =========================================== */ -const PresenterViewPage: React.FC = () => { +const OperatorViewPage: React.FC = () => { // for dialog alert const [openAlert, setOpen] = React.useState(false) + const [openAlertCode, setOpenCode] = React.useState(true) const theme = useTheme() const fullScreen = useMediaQuery(theme.breakpoints.down('sm')) const teams = useAppSelector((state) => state.presentation.competition.teams) @@ -62,14 +81,14 @@ const PresenterViewPage: React.FC = () => { const history = useHistory() const dispatch = useAppDispatch() const viewTypes = useAppSelector((state) => state.types.viewTypes) - const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Presenter')?.id + const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Operator')?.id useEffect(() => { dispatch(getPresentationCompetition(id)) dispatch(setPresentationCode(code)) socket_connect() socketSetSlide // Behövs denna? - setTimeout(startCompetition, 500) // Ghetto, wait for everything to load + setTimeout(startCompetition, 1000) // Ghetto, wait for everything to load // console.log(id) }, []) @@ -79,6 +98,7 @@ const PresenterViewPage: React.FC = () => { const handleClose = () => { setOpen(false) + setOpenCode(false) setAnchorEl(null) } @@ -92,6 +112,14 @@ const PresenterViewPage: React.FC = () => { setOpen(true) } + const handleOpenCodes = () => { + setOpenCode(true) + } + + const handleCopy = () => { + console.log('copied code to clipboard') + } + const endCompetition = () => { setOpen(false) socketEndPresentation() @@ -100,12 +128,66 @@ const PresenterViewPage: React.FC = () => { } return ( - <PresenterContainer> - <PresenterHeader> + <OperatorContainer> + <Dialog + fullScreen={fullScreen} + open={openAlertCode} + onClose={handleClose} + aria-labelledby="responsive-dialog-title" + > + <DialogTitle id="responsive-dialog-title">{'Koder för tävlingen'}</DialogTitle> + <DialogContent> + <DialogContentText> + <ListItem> + <ListItemText primary={`Domare: ${presentation.code}`} /> + <Tooltip title="Kopiera kod" arrow> + <Button + onClick={() => { + navigator.clipboard.writeText(presentation.code) + }} + > + <FileCopyIcon fontSize="small" /> + </Button> + </Tooltip> + </ListItem> + <ListItem> + <ListItemText primary={`Tävlande: ${presentation.code}`} /> + <Tooltip title="Kopiera kod" arrow> + <Button + onClick={() => { + navigator.clipboard.writeText(presentation.code) + }} + > + <FileCopyIcon fontSize="small" /> + </Button> + </Tooltip> + </ListItem> + <ListItem> + <ListItemText primary={`Publik: ${presentation.code}`} /> + <Tooltip title="Kopiera kod" arrow> + <Button + onClick={() => { + navigator.clipboard.writeText(presentation.code) + }} + > + <FileCopyIcon fontSize="small" /> + </Button> + </Tooltip> + </ListItem> + </DialogContentText> + </DialogContent> + <DialogActions> + <Button autoFocus onClick={handleClose} color="primary"> + Ok + </Button> + </DialogActions> + </Dialog> + + <OperatorHeader> <Tooltip title="Avsluta tävling" arrow> - <PresenterButton onClick={handleVerifyExit} variant="contained" color="secondary"> + <OperatorButton onClick={handleVerifyExit} variant="contained" color="secondary"> <BackspaceIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> <Dialog @@ -135,67 +217,73 @@ const PresenterViewPage: React.FC = () => { {presentation.slide.order + 1} / {presentation.competition.slides.length} </Typography> </SlideCounter> - </PresenterHeader> + </OperatorHeader> <div style={{ height: 0, paddingTop: 120 }} /> - <PresenterContent> - <PresenterInnerContent> + <OperatorContent> + <OperatorInnerContent> {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />} - </PresenterInnerContent> - </PresenterContent> + </OperatorInnerContent> + </OperatorContent> <div style={{ height: 0, paddingTop: 140 }} /> - <PresenterFooter> + <OperatorFooter> <ToolBarContainer> - <Tooltip title="Previous Slide" arrow> - <PresenterButton onClick={socketSetSlidePrev} variant="contained"> + <Tooltip title="Föregående" arrow> + <OperatorButton onClick={socketSetSlidePrev} variant="contained"> <ChevronLeftIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> {/* // Manual start button <Tooltip title="Start Presentation" arrow> - <PresenterButton onClick={startCompetition} variant="contained"> + <OperatorButton onClick={startCompetition} variant="contained"> start - </PresenterButton> + </OperatorButton> </Tooltip> - // This creates a join button, but presenter should not join others, others should join presenter + // This creates a join button, but Operator should not join others, others should join Operator <Tooltip title="Join Presentation" arrow> - <PresenterButton onClick={socketJoinPresentation} variant="contained"> + <OperatorButton onClick={socketJoinPresentation} variant="contained"> <GroupAddIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> // This creates another end button, it might not be needed since we already have one <Tooltip title="End Presentation" arrow> - <PresenterButton onClick={socketEndPresentation} variant="contained"> + <OperatorButton onClick={socketEndPresentation} variant="contained"> <CancelIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> */} - <Tooltip title="Start Timer" arrow> - <PresenterButton onClick={socketStartTimer} variant="contained"> + <Tooltip title="Starta Timer" arrow> + <OperatorButton onClick={socketStartTimer} variant="contained"> <TimerIcon fontSize="large" /> <Timer></Timer> - </PresenterButton> + </OperatorButton> </Tooltip> - <Tooltip title="Scoreboard" arrow> - <PresenterButton onClick={handleOpenPopover} variant="contained"> + <Tooltip title="Ställning" arrow> + <OperatorButton onClick={handleOpenPopover} variant="contained"> <AssignmentIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> - <Tooltip title="Next Slide" arrow> - <PresenterButton onClick={socketSetSlideNext} variant="contained"> + <Tooltip title="Koder" arrow> + <OperatorButton onClick={handleOpenCodes} variant="contained"> + <SupervisorAccountIcon fontSize="large" /> + </OperatorButton> + </Tooltip> + + <Tooltip title="Nästa" arrow> + <OperatorButton onClick={socketSetSlideNext} variant="contained"> <ChevronRightIcon fontSize="large" /> - </PresenterButton> + </OperatorButton> </Tooltip> </ToolBarContainer> - </PresenterFooter> + </OperatorFooter> <Popover open={Boolean(anchorEl)} anchorEl={anchorEl} @@ -210,17 +298,15 @@ const PresenterViewPage: React.FC = () => { }} > <List> - {/** TODO: - * Fix scoreboard - */} - {teams && teams.map((team) => <ListItem key={team.id}>{team.name} score: 20</ListItem>)} + {teams.map((team) => ( + <ListItem key={team.id}> + {team.name} score: {team.question_answers}{' '} + </ListItem> + ))} </List> </Popover> - </PresenterContainer> + </OperatorContainer> ) } -export default PresenterViewPage -function componentDidMount() { - throw new Error('Function not implemented.') -} +export default OperatorViewPage diff --git a/client/src/pages/views/ParticipantViewPage.test.tsx b/client/src/pages/views/TeamViewPage.test.tsx similarity index 85% rename from client/src/pages/views/ParticipantViewPage.test.tsx rename to client/src/pages/views/TeamViewPage.test.tsx index e25ab6b9..10574f7e 100644 --- a/client/src/pages/views/ParticipantViewPage.test.tsx +++ b/client/src/pages/views/TeamViewPage.test.tsx @@ -3,7 +3,7 @@ import React from 'react' import { Provider } from 'react-redux' import { BrowserRouter } from 'react-router-dom' import store from '../../store' -import ParticipantViewPage from './ParticipantViewPage' +import TeamViewPage from './TeamViewPage' import mockedAxios from 'axios' it('renders participant view page', () => { @@ -16,7 +16,7 @@ it('renders participant view page', () => { render( <BrowserRouter> <Provider store={store}> - <ParticipantViewPage /> + <TeamViewPage /> </Provider> </BrowserRouter> ) diff --git a/client/src/pages/views/ParticipantViewPage.tsx b/client/src/pages/views/TeamViewPage.tsx similarity index 77% rename from client/src/pages/views/ParticipantViewPage.tsx rename to client/src/pages/views/TeamViewPage.tsx index 0c4db2c0..32eef28b 100644 --- a/client/src/pages/views/ParticipantViewPage.tsx +++ b/client/src/pages/views/TeamViewPage.tsx @@ -2,28 +2,28 @@ import React, { useEffect } from 'react' import PresentationComponent from './components/PresentationComponent' import { useHistory } from 'react-router-dom' import SlideDisplay from '../presentationEditor/components/SlideDisplay' -import { ParticipantContainer } from './styled' +import { TeamContainer } from './styled' import { socketJoinPresentation, socket_connect } from '../../sockets' import { useAppSelector } from '../../hooks' -const ParticipantViewPage: React.FC = () => { +const TeamViewPage: React.FC = () => { const history = useHistory() const code = useAppSelector((state) => state.presentation.code) const viewTypes = useAppSelector((state) => state.types.viewTypes) - const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Participant')?.id + const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Team')?.id useEffect(() => { //hides the url so people can't sneak peak - history.push('participant') + history.push('team') if (code && code !== '') { socket_connect() socketJoinPresentation() } }, []) return ( - <ParticipantContainer> + <TeamContainer> {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />} - </ParticipantContainer> + </TeamContainer> ) } -export default ParticipantViewPage +export default TeamViewPage diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx index 686ce4f1..845ba21c 100644 --- a/client/src/pages/views/ViewSelectPage.tsx +++ b/client/src/pages/views/ViewSelectPage.tsx @@ -4,9 +4,9 @@ import { Link, useRouteMatch } from 'react-router-dom' import { ViewSelectButtonGroup, ViewSelectContainer } from './styled' import { useParams } from 'react-router-dom' import { CircularProgress, Typography } from '@material-ui/core' -import ParticipantViewPage from './ParticipantViewPage' +import TeamViewPage from './TeamViewPage' import axios from 'axios' -import PresenterViewPage from './PresenterViewPage' +import OperatorViewPage from './OperatorViewPage' import JudgeViewPage from './JudgeViewPage' import AudienceViewPage from './AudienceViewPage' import { useAppDispatch, useAppSelector } from '../../hooks' @@ -29,7 +29,7 @@ const ViewSelectPage: React.FC = () => { if (competitionId) { switch (viewType) { case 'Team': - return <ParticipantViewPage /> + return <TeamViewPage /> case 'Judge': return <JudgeViewPage code={code} competitionId={competitionId} /> case 'Audience': diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index 8f63892a..96999027 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -35,7 +35,7 @@ export const ViewSelectButtonGroup = styled.div` margin-right: auto; ` -export const PresenterHeader = styled.div` +export const OperatorHeader = styled.div` display: flex; justify-content: space-between; height: 120px; @@ -43,7 +43,7 @@ export const PresenterHeader = styled.div` position: absolute; ` -export const PresenterFooter = styled.div` +export const OperatorFooter = styled.div` display: flex; justify-content: space-between; height: 140px; @@ -52,7 +52,7 @@ export const PresenterFooter = styled.div` width: 100%; ` -export const PresenterButton = styled(Button)` +export const OperatorButton = styled(Button)` width: 100px; height: 100px; margin-left: 16px; @@ -66,7 +66,7 @@ export const SlideCounter = styled(Button)` margin-top: 16px; ` -export const PresenterContainer = styled.div` +export const OperatorContainer = styled.div` display: flex; flex-direction: column; justify-content: space-between; @@ -127,7 +127,7 @@ export const InnerContent = styled.div` max-width: calc(((100vh - 64px) / 9) * 16); ` -export const PresenterContent = styled.div` +export const OperatorContent = styled.div` height: 100%; width: 100%; display: flex; @@ -135,7 +135,7 @@ export const PresenterContent = styled.div` background-color: rgba(0, 0, 0, 0.08); ` -export const PresenterInnerContent = styled.div` +export const OperatorInnerContent = styled.div` height: 100%; width: 100%; /* Makes sure width is not bigger than where a 16:9 display can fit @@ -143,7 +143,7 @@ export const PresenterInnerContent = styled.div` max-width: calc(((100vh - 260px) / 9) * 16); ` -export const ParticipantContainer = styled.div` +export const TeamContainer = styled.div` max-width: calc((100vh / 9) * 16); ` -- GitLab