From d488a39fed5732129f8adc7b12b9d77e077c8354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Mod=C3=A9e?= <bjomo323@student.liu.se> Date: Wed, 21 Apr 2021 13:31:06 +0000 Subject: [PATCH] Resolve "Competition view" --- .../admin/competitions/AddCompetition.tsx | 24 +++- .../admin/competitions/CompetitionManager.tsx | 13 +- client/src/pages/views/AudienceViewPage.tsx | 3 +- client/src/pages/views/JudgeViewPage.tsx | 18 ++- client/src/pages/views/PresenterViewPage.tsx | 127 ++++++++++++++---- .../pages/views/components/SlideDisplay.tsx | 17 ++- client/src/pages/views/components/Timer.tsx | 3 +- client/src/pages/views/components/styled.tsx | 7 + client/src/pages/views/styled.tsx | 23 +++- client/src/sockets.ts | 9 +- 10 files changed, 199 insertions(+), 45 deletions(-) diff --git a/client/src/pages/admin/competitions/AddCompetition.tsx b/client/src/pages/admin/competitions/AddCompetition.tsx index e9666770..0ce9a6ff 100644 --- a/client/src/pages/admin/competitions/AddCompetition.tsx +++ b/client/src/pages/admin/competitions/AddCompetition.tsx @@ -10,10 +10,17 @@ import { City } from '../../../interfaces/ApiModels' import { AddCompetitionModel, FormModel } from '../../../interfaces/FormModels' import { AddButton, AddContent, AddForm } from '../styledComp' +/** + * Component description: + * This component handles the functionality when adding a competition to the system + * This component is a child component to CompetitionManager.tsx + */ + type formType = FormModel<AddCompetitionModel> const noCitySelected = 'Välj stad' +//Description of the form and what is required const competitionSchema: Yup.SchemaOf<formType> = Yup.object({ model: Yup.object() .shape({ @@ -45,20 +52,25 @@ const AddCompetition: React.FC = (props: any) => { const dispatch = useAppDispatch() const id = open ? 'simple-popover' : undefined const currentYear = new Date().getFullYear() + + // Handles the actual submition to the database const handleCompetitionSubmit = async (values: formType, actions: FormikHelpers<formType>) => { + // The parameters sent const params = { name: values.model.name, year: values.model.year, city_id: selectedCity?.id as number, } + await axios - .post('/competitions', params) + .post('/competitions', params) // send to database .then(() => { - actions.resetForm() + actions.resetForm() // reset the form setAnchorEl(null) - dispatch(getCompetitions()) + dispatch(getCompetitions()) // refresh competitions setSelectedCity(undefined) }) + // if the post request fails .catch(({ response }) => { console.warn(response.data) if (response.data && response.data.message) @@ -83,6 +95,12 @@ const AddCompetition: React.FC = (props: any) => { > Ny Tävling </AddButton> + + {/** + * The "pop up" menu for adding a competition + * contains 3 fields; Name, Region and Year + * + */} <Popover id={id} open={open} diff --git a/client/src/pages/admin/competitions/CompetitionManager.tsx b/client/src/pages/admin/competitions/CompetitionManager.tsx index 81e644ad..e7085a5e 100644 --- a/client/src/pages/admin/competitions/CompetitionManager.tsx +++ b/client/src/pages/admin/competitions/CompetitionManager.tsx @@ -21,6 +21,13 @@ import { CompetitionFilterParams } from '../../../interfaces/FilterParams' import { FilterContainer, RemoveMenuItem, TopBar, YearFilterTextField } from '../styledComp' import AddCompetition from './AddCompetition' +/** + * Component description: + * This component shows a list of all the competitions which a user can search through + * We can also start, duplicate or delete a competition + */ + +// Use defined styling const useStyles = makeStyles((theme: Theme) => createStyles({ table: { @@ -41,6 +48,7 @@ const CompetitionManager: React.FC = (props: any) => { const filterParams = useAppSelector((state) => state.competitions.filterParams) const competitionTotal = useAppSelector((state) => state.competitions.total) const cities = useAppSelector((state) => state.cities.cities) + const classes = useStyles() const noFilterText = 'Alla' const dispatch = useAppDispatch() @@ -59,6 +67,7 @@ const CompetitionManager: React.FC = (props: any) => { dispatch(getCompetitions()) }, []) + // Search funtion to search for a specific string const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { if (timerHandle) { clearTimeout(timerHandle) @@ -69,13 +78,14 @@ const CompetitionManager: React.FC = (props: any) => { dispatch(setFilterParams({ ...filterParams, name: event.target.value })) } + // Function to remove a competition from the systems database const handleDeleteCompetition = async () => { if (activeId) { await axios .delete(`/competitions/${activeId}`) .then(() => { setAnchorEl(null) - dispatch(getCompetitions()) + dispatch(getCompetitions()) // refresh the competition list }) .catch(({ response }) => { console.warn(response.data) @@ -177,6 +187,7 @@ const CompetitionManager: React.FC = (props: any) => { ))} </TableBody> </Table> + {/** We can't find any competitions at all or with a specific filter */} {(!competitions || competitions.length === 0) && ( <Typography>Inga tävlingar hittades med nuvarande filter</Typography> )} diff --git a/client/src/pages/views/AudienceViewPage.tsx b/client/src/pages/views/AudienceViewPage.tsx index 45ec132d..00a821f3 100644 --- a/client/src/pages/views/AudienceViewPage.tsx +++ b/client/src/pages/views/AudienceViewPage.tsx @@ -1,7 +1,8 @@ import React from 'react' +import SlideDisplay from './components/SlideDisplay' const AudienceViewPage: React.FC = () => { - return <div>Publik</div> + return <SlideDisplay /> } export default AudienceViewPage diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 12e866a0..60018624 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -1,4 +1,4 @@ -import { Divider, List, ListItemText } from '@material-ui/core' +import { Divider, List, ListItemText, Typography } from '@material-ui/core' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import React, { useEffect, useState } from 'react' import { useParams } from 'react-router-dom' @@ -10,6 +10,7 @@ import { } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' +import { socket_connect } from '../../sockets' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import SlideDisplay from './components/SlideDisplay' @@ -43,17 +44,20 @@ const JudgeViewPage: React.FC = () => { const { id, code }: ViewParams = useParams() const dispatch = useAppDispatch() const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0) - useEffect(() => { - dispatch(getPresentationCompetition(id)) - dispatch(getPresentationTeams(id)) - dispatch(setPresentationCode(code)) - }, []) const teams = useAppSelector((state) => state.presentation.teams) const slides = useAppSelector((state) => state.presentation.competition.slides) const handleSelectSlide = (index: number) => { setActiveSlideIndex(index) dispatch(setCurrentSlide(slides[index])) } + + useEffect(() => { + socket_connect() + dispatch(getPresentationCompetition(id)) + dispatch(getPresentationTeams(id)) + dispatch(setPresentationCode(code)) + }, []) + return ( <div> <JudgeAppBar position="fixed"> @@ -80,6 +84,7 @@ const JudgeViewPage: React.FC = () => { button key={slide.id} > + <Typography variant="h6">Slide ID: {slide.id} </Typography> <ListItemText primary={slide.title} /> </SlideListItem> ))} @@ -103,7 +108,6 @@ const JudgeViewPage: React.FC = () => { ))} </List> </RightDrawer> - aaa <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}> <div className={classes.toolbar} /> <SlideDisplay /> diff --git a/client/src/pages/views/PresenterViewPage.tsx b/client/src/pages/views/PresenterViewPage.tsx index 22a672ba..ff61343f 100644 --- a/client/src/pages/views/PresenterViewPage.tsx +++ b/client/src/pages/views/PresenterViewPage.tsx @@ -1,61 +1,139 @@ -import { List, ListItem, Popover } from '@material-ui/core' +import { List, ListItem, Popover, Tooltip, Typography } from '@material-ui/core' +import AssignmentIcon from '@material-ui/icons/Assignment' +import BackspaceIcon from '@material-ui/icons/Backspace' +import ChevronLeftIcon from '@material-ui/icons/ChevronLeft' import ChevronRightIcon from '@material-ui/icons/ChevronRight' +import PlayArrowIcon from '@material-ui/icons/PlayArrow' +import TimerIcon from '@material-ui/icons/Timer' import React, { useEffect } from 'react' import { useHistory, useParams } from 'react-router-dom' import { getPresentationCompetition, getPresentationTeams, setPresentationCode } from '../../actions/presentation' import { useAppDispatch, useAppSelector } from '../../hooks' import { ViewParams } from '../../interfaces/ViewParams' +import { + socketEndPresentation, + socketSetSlide, + socketSetSlideNext, + socketSetSlidePrev, + socketStartPresentation, + socketStartTimer, + socket_connect, +} from '../../sockets' import SlideDisplay from './components/SlideDisplay' -import SocketTest from './components/SocketTest' import Timer from './components/Timer' -import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled' +import { + PresenterButton, + PresenterContainer, + PresenterFooter, + PresenterHeader, + SlideCounter, + ToolBarContainer, +} from './styled' const PresenterViewPage: React.FC = () => { const teams = useAppSelector((state) => state.presentation.teams) const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null) const { id, code }: ViewParams = useParams() + const presentation = useAppSelector((state) => state.presentation) const history = useHistory() const dispatch = useAppDispatch() + useEffect(() => { + socket_connect() + socketSetSlide dispatch(getPresentationCompetition(id)) dispatch(getPresentationTeams(id)) dispatch(setPresentationCode(code)) }, []) + const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => { setAnchorEl(event.currentTarget) } const handleClose = () => { setAnchorEl(null) } - const handleNextSlidePressed = () => { - // dispatch(setCurrentSlideNext()) - // syncSlide() + + const startCompetition = () => { + socketStartPresentation() + const haveStarted = true + console.log('You have started the competition! GLHF!') + console.log(haveStarted) } - const handlePreviousSlidePressed = () => { - // dispatch(setCurrentSlidePrevious()) - // syncSlide() + + const endCompetition = () => { + if (confirm('Är du säker på att du vill avsluta tävlingen för alla?')) { + const haveStarted = false + socketEndPresentation() + history.push('/admin') + window.location.reload(false) // TODO: fix this ugly hack, we "need" to refresh site to be able to run the competition correctly again + } } return ( <PresenterContainer> <PresenterHeader> - <PresenterButton onClick={handleOpenPopover} color="primary" variant="contained"> - Visa ställning - </PresenterButton> - <PresenterButton onClick={() => history.push('/admin')} variant="contained" color="secondary"> - Avsluta tävling - </PresenterButton> + <Tooltip title="Avsluta tävling" arrow> + <PresenterButton onClick={endCompetition} variant="contained" color="secondary"> + <BackspaceIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + <SlideCounter> + <Typography variant="h3"> + {presentation.slide.id} / {presentation.competition.slides.length} + </Typography> + </SlideCounter> </PresenterHeader> <SlideDisplay /> <PresenterFooter> - <PresenterButton onClick={handlePreviousSlidePressed} variant="contained"> - <ChevronRightIcon fontSize="large" /> - </PresenterButton> - <SocketTest></SocketTest> - <Timer></Timer> - <PresenterButton onClick={handleNextSlidePressed} variant="contained"> - <ChevronRightIcon fontSize="large" /> - </PresenterButton> + <ToolBarContainer> + <Tooltip title="Previous Slide" arrow> + <PresenterButton onClick={socketSetSlidePrev} variant="contained"> + <ChevronLeftIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + + <Tooltip title="Start Presentation" arrow> + <PresenterButton onClick={startCompetition} variant="contained"> + <PlayArrowIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + + {/* + // This creates a join button, but presenter should not join others, others should join presenter + <Tooltip title="Join Presentation" arrow> + <PresenterButton onClick={socketJoinPresentation} variant="contained"> + <GroupAddIcon fontSize="large" /> + </PresenterButton> + </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"> + <CancelIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + */} + + <Tooltip title="Start Timer" arrow> + <PresenterButton onClick={socketStartTimer} variant="contained"> + <TimerIcon fontSize="large" /> + <Timer></Timer> + </PresenterButton> + </Tooltip> + + <Tooltip title="Scoreboard" arrow> + <PresenterButton onClick={handleOpenPopover} variant="contained"> + <AssignmentIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + + <Tooltip title="Next Slide" arrow> + <PresenterButton onClick={socketSetSlideNext} variant="contained"> + <ChevronRightIcon fontSize="large" /> + </PresenterButton> + </Tooltip> + </ToolBarContainer> </PresenterFooter> <Popover open={Boolean(anchorEl)} @@ -71,6 +149,9 @@ const PresenterViewPage: React.FC = () => { }} > <List> + {/** TODO: + * Fix scoreboard + */} {teams.map((team) => ( <ListItem key={team.id}>{team.name} score: 20</ListItem> ))} diff --git a/client/src/pages/views/components/SlideDisplay.tsx b/client/src/pages/views/components/SlideDisplay.tsx index 1c10f9cf..b7e6dcbe 100644 --- a/client/src/pages/views/components/SlideDisplay.tsx +++ b/client/src/pages/views/components/SlideDisplay.tsx @@ -1,14 +1,21 @@ import { Typography } from '@material-ui/core' -import React from 'react' -import { useAppSelector } from '../../../hooks' +import React, { useEffect } from 'react' +import { useAppDispatch, useAppSelector } from '../../../hooks' import { SlideContainer } from './styled' const SlideDisplay: React.FC = () => { const currentSlide = useAppSelector((state) => state.presentation.slide) + const dispatch = useAppDispatch() + useEffect(() => {}, []) + return ( - <SlideContainer> - <Typography variant="h3">{currentSlide.title}</Typography> - </SlideContainer> + <div> + <SlideContainer> + <Typography variant="h3">Slide Title: {currentSlide.title} </Typography> + <Typography variant="h3">Timer: {currentSlide.timer} </Typography> + <Typography variant="h3">Slide ID: {currentSlide.id} </Typography> + </SlideContainer> + </div> ) } diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx index b4401a69..0cbd1fdf 100644 --- a/client/src/pages/views/components/Timer.tsx +++ b/client/src/pages/views/components/Timer.tsx @@ -38,8 +38,7 @@ const Timer: React.FC = (props: any) => { return ( <> - <div>Timer: {props.timer.value}</div> - <div>Enabled: {props.timer.enabled.toString()}</div> + <div>{props.timer.value}</div> </> ) } diff --git a/client/src/pages/views/components/styled.tsx b/client/src/pages/views/components/styled.tsx index 0034c39b..b522b20d 100644 --- a/client/src/pages/views/components/styled.tsx +++ b/client/src/pages/views/components/styled.tsx @@ -3,7 +3,14 @@ import styled from 'styled-components' export const SlideContainer = styled.div` display: flex; + flex-direction: column; + margin-left: auto; + margin-right: auto; + margin-top: 5%; justify-content: center; + background-color: grey; + width: 1280px; + height: 720px; ` export const ScoreDisplayContainer = styled.div` diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index d4a9cf9e..1f3a61c6 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -50,8 +50,15 @@ export const PresenterFooter = styled.div` export const PresenterButton = styled(Button)` width: 100px; height: 100px; - padding-top: 16px; - padding-bottom: 16px; + margin-left: 16px; + margin-right: 16px; + margin-top: 16px; +` + +export const SlideCounter = styled(Button)` + margin-left: 16px; + margin-right: 16px; + margin-top: 16px; ` export const PresenterContainer = styled.div` @@ -61,6 +68,18 @@ export const PresenterContainer = styled.div` height: 100%; ` +export const ToolBarContainer = styled.div` + align-self: center; + display: flex; + flex-direction: row; + justify-content: space-between; + height: 100%; + width: auto; + margin-right: auto; + margin-left: auto; + margin-bottom: 20px; +` + interface DrawerProps { width: number } diff --git a/client/src/sockets.ts b/client/src/sockets.ts index 874bfc46..54f84c39 100644 --- a/client/src/sockets.ts +++ b/client/src/sockets.ts @@ -38,22 +38,27 @@ export const socket_connect = () => { export const socketStartPresentation = () => { socket.emit('start_presentation', { competition_id: store.getState().presentation.competition.id }) + console.log('START PRESENTATION') } export const socketJoinPresentation = () => { - socket.emit('join_presentation', { code: 'OEM1V4' }) // TODO: Send code gotten from auth/login/<code> api call + socket.emit('join_presentation', { code: 'CO0ART' }) // TODO: Send code gotten from auth/login/<code> api call + console.log('JOIN PRESENTATION') } export const socketEndPresentation = () => { socket.emit('end_presentation', { competition_id: store.getState().presentation.competition.id }) + console.log('END PRESENTATION') } export const socketSetSlideNext = () => { socketSetSlide(store.getState().presentation.slide.order + 1) // TODO: Check that this slide exists + console.log('NEXT SLIDE +1') } export const socketSetSlidePrev = () => { socketSetSlide(store.getState().presentation.slide.order - 1) // TODO: Check that this slide exists + console.log('PREVIOUS SLIDE -1') } export const socketSetSlide = (slide_order: number) => { @@ -69,6 +74,7 @@ export const socketSetSlide = (slide_order: number) => { } export const socketSetTimer = (timer: Timer) => { + console.log('SET TIMER') socket.emit('set_timer', { competition_id: store.getState().presentation.competition.id, timer: timer, @@ -76,5 +82,6 @@ export const socketSetTimer = (timer: Timer) => { } export const socketStartTimer = () => { + console.log('START TIMER') socketSetTimer({ enabled: true, value: store.getState().presentation.timer.value }) } -- GitLab