Skip to content
Snippets Groups Projects
OperatorViewPage.tsx 11.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • import {
    
      Button,
    
      createStyles,
    
      Dialog,
      DialogActions,
      DialogContent,
      DialogContentText,
      DialogTitle,
      ListItem,
    
    Max Rüdiger's avatar
    Max Rüdiger committed
      ListItemText,
    
      makeStyles,
    
    Albin Henriksson's avatar
    Albin Henriksson committed
      Snackbar,
    
      Tooltip,
      Typography,
    } from '@material-ui/core'
    
    import AssignmentIcon from '@material-ui/icons/Assignment'
    import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
    
    import ChevronRightIcon from '@material-ui/icons/ChevronRight'
    
    Victor Löfgren's avatar
    Victor Löfgren committed
    import CloseIcon from '@material-ui/icons/Close'
    
    import FileCopyIcon from '@material-ui/icons/FileCopy'
    
    import LinkIcon from '@material-ui/icons/Link'
    
    import SupervisorAccountIcon from '@material-ui/icons/SupervisorAccount'
    
    import TimerIcon from '@material-ui/icons/Timer'
    
    Albin Henriksson's avatar
    Albin Henriksson committed
    import { Alert } from '@material-ui/lab'
    
    import axios from 'axios'
    
    Albin Henriksson's avatar
    Albin Henriksson committed
    import React, { useEffect, useState } from 'react'
    
    import { useHistory } from 'react-router-dom'
    import { useAppSelector } from '../../hooks'
    
    import { socketConnect, socketEndPresentation, socketSync } from '../../sockets'
    
    import SlideDisplay from '../presentationEditor/components/SlideDisplay'
    
    Albin Henriksson's avatar
    Albin Henriksson committed
    import { Center } from '../presentationEditor/components/styled'
    
    Victor Löfgren's avatar
    Victor Löfgren committed
    import Scoreboard from './components/Scoreboard'
    
    Victor Löfgren's avatar
    Victor Löfgren committed
    import Timer from './components/Timer'
    
    import {
    
    Max Rüdiger's avatar
    Max Rüdiger committed
      OperatorButton,
      OperatorContainer,
    
      OperatorContent,
    
    Max Rüdiger's avatar
    Max Rüdiger committed
      OperatorFooter,
      OperatorHeader,
    
      OperatorHeaderItem,
    
    Max Rüdiger's avatar
    Max Rüdiger committed
      OperatorInnerContent,
    
    Victor Löfgren's avatar
    Victor Löfgren committed
      OperatorQuitButton,
    
    } from './styled'
    
    Max Rüdiger's avatar
    Max Rüdiger committed
     *  Description:
     *
    
     *  Presentation is an active competition
    
    Max Rüdiger's avatar
    Max Rüdiger committed
     *
     *
     *  ===========================================
     *  TODO:
     *  - 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 useStyles = makeStyles((theme: Theme) =>
      createStyles({
        table: {
          width: '100%',
        },
        margin: {
          margin: theme.spacing(1),
        },
        paper: {
          backgroundColor: theme.palette.background.paper,
          boxShadow: theme.shadows[5],
          padding: 4,
          outline: 'none',
        },
      })
    )
    
    interface Code {
      id: number
      code: string
      view_type_id: number
      competition_id: number
      team_id: number
    }
    
    Max Rüdiger's avatar
    Max Rüdiger committed
    const OperatorViewPage: React.FC = () => {
    
      // for dialog alert
      const [openAlert, setOpen] = React.useState(false)
    
      const [openAlertCode, setOpenCode] = React.useState(false)
      const [codes, setCodes] = React.useState<Code[]>([])
    
      const competitionName = useAppSelector((state) => state.presentation.competition.name)
    
    
      //const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
    
      const classes = useStyles()
    
      const teams = useAppSelector((state) => state.presentation.competition.teams)
    
      const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
    
      const presentation = useAppSelector((state) => state.presentation)
    
      const activeId = useAppSelector((state) => state.presentation.competition.id)
    
      const timer = useAppSelector((state) => state.presentation.timer)
    
      const history = useHistory()
    
      const viewTypes = useAppSelector((state) => state.types.viewTypes)
    
      const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Audience')?.id
    
    Albin Henriksson's avatar
    Albin Henriksson committed
      const [successMessageOpen, setSuccessMessageOpen] = useState(true)
      const activeSlideOrder = useAppSelector(
        (state) =>
          state.presentation.competition.slides.find((slide) => slide.id === state.presentation.activeSlideId)?.order
      )
    
      const slideTimer = useAppSelector((state) =>
        activeSlideOrder !== undefined ? state.presentation.competition.slides[activeSlideOrder].timer : null
      )
      const isFirstSlide = activeSlideOrder === 0
      const isLastSlide = useAppSelector((state) => activeSlideOrder === state.presentation.competition.slides.length - 1)
    
    Victor Löfgren's avatar
    Victor Löfgren committed
      const showScoreboard = useAppSelector((state) => state.presentation.show_scoreboard)
    
    
    Albin Henriksson's avatar
    Albin Henriksson committed
        socketConnect('Operator')
    
    Björn Modée's avatar
    Björn Modée committed
      /** Handles the browsers back button and if pressed cancels the ongoing competition */
    
    Max Rüdiger's avatar
    Max Rüdiger committed
      window.onpopstate = () => {
        alert('Tävlingen avslutas för alla')
        endCompetition()
      }
    
    
      const handleClose = () => {
    
        setOpen(false)
    
    Max Rüdiger's avatar
    Max Rüdiger committed
        setOpenCode(false)
    
    Björn Modée's avatar
    Björn Modée committed
      /** Making sure the user wants to exit the competition by displaying a dialog box */
    
      const handleVerifyExit = () => {
        setOpen(true)
    
      const handleOpenCodes = async () => {
        await getCodes()
    
    Max Rüdiger's avatar
    Max Rüdiger committed
        setOpenCode(true)
      }
    
    
      const endCompetition = () => {
    
        setOpen(false)
        socketEndPresentation()
    
    Albin Henriksson's avatar
    Albin Henriksson committed
        history.push('/admin/competition-manager')
    
    Björn Modée's avatar
    Björn Modée committed
        window.location.reload(false) // TODO: fix this, we "need" to refresh site to be able to run the competition correctly again
    
      const getCodes = async () => {
        await axios
          .get(`/api/competitions/${activeId}/codes`)
          .then((response) => {
            setCodes(response.data.items)
          })
          .catch(console.log)
      }
      const getTypeName = (code: Code) => {
        let typeName = ''
        switch (code.view_type_id) {
          case 1:
            const team = teams.find((team) => team.id === code.team_id)
            if (team) {
              typeName = team.name
            } else {
              typeName = 'Lagnamn hittades ej'
            }
            break
          case 2:
            typeName = 'Domare'
            break
          case 3:
            typeName = 'Publik'
            break
          case 4:
            typeName = 'Tävlingsoperatör'
            break
          default:
            typeName = 'Typ hittades ej'
            break
        }
        return typeName
      }
    
    
      const handleStartTimer = () => {
        if (!slideTimer) return
    
        if (!timer.enabled) socketSync({ timer: { value: Date.now() + 1000 * slideTimer, enabled: true } })
        else socketSync({ timer: { ...timer, enabled: false } })
      }
    
      const handleSetNextSlide = () => {
        if (activeSlideOrder !== undefined)
          socketSync({ slide_order: activeSlideOrder + 1, timer: { value: null, enabled: false } })
      }
    
      const handleSetPrevSlide = () => {
        if (activeSlideOrder !== undefined)
          socketSync({ slide_order: activeSlideOrder - 1, timer: { value: null, enabled: false } })
      }
    
    
    Max Rüdiger's avatar
    Max Rüdiger committed
        <OperatorContainer>
    
          <Dialog open={openAlertCode} onClose={handleClose} aria-labelledby="max-width-dialog-title" maxWidth="xl">
    
    Albin Henriksson's avatar
    Albin Henriksson committed
            <Center>
              <DialogTitle id="max-width-dialog-title" className={classes.paper} style={{ width: '100%' }}>
                Koder för {competitionName}
              </DialogTitle>
            </Center>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
            <DialogContent>
    
              {/* <DialogContentText>Här visas tävlingskoderna till den valda tävlingen.</DialogContentText> */}
    
              {codes &&
                codes.map((code) => (
                  <ListItem key={code.id} style={{ display: 'flex' }}>
                    <ListItemText primary={`${getTypeName(code)}: `} />
                    <Typography component="div">
                      <ListItemText style={{ textAlign: 'right', marginLeft: '10px' }}>
                        <Box fontFamily="Monospace" fontWeight="fontWeightBold">
                          {code.code}
                        </Box>
                      </ListItemText>
                    </Typography>
                    <Tooltip title="Kopiera kod" arrow>
                      <Button
                        margin-right="0px"
                        onClick={() => {
                          navigator.clipboard.writeText(code.code)
                        }}
                      >
                        <FileCopyIcon fontSize="small" />
                      </Button>
                    </Tooltip>
    
                    <Tooltip title="Kopiera länk" arrow>
                      <Button
                        margin-right="0px"
                        onClick={() => {
                          navigator.clipboard.writeText(`${window.location.host}/${code.code}`)
                        }}
                      >
                        <LinkIcon fontSize="small" />
                      </Button>
                    </Tooltip>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
            </DialogContent>
            <DialogActions>
    
              <Button onClick={handleClose} color="primary">
                Stäng
    
    Max Rüdiger's avatar
    Max Rüdiger committed
              </Button>
            </DialogActions>
          </Dialog>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
          <OperatorHeader color="primary" position="fixed">
    
            <Tooltip title="Avsluta tävling" arrow>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
              <OperatorQuitButton onClick={handleVerifyExit} variant="contained" color="secondary">
                <CloseIcon fontSize="large" />
              </OperatorQuitButton>
    
            </Tooltip>
    
            <Dialog open={openAlert} onClose={handleClose} aria-labelledby="responsive-dialog-title">
    
              <DialogTitle id="responsive-dialog-title">{'Vill du avsluta tävlingen?'}</DialogTitle>
              <DialogContent>
                <DialogContentText>
                  Genom att avsluta tävlingen kommer den avslutas för alla. Du kommer gå tillbaka till startsidan.
                </DialogContentText>
              </DialogContent>
              <DialogActions>
                <Button autoFocus onClick={handleClose} color="primary">
                  Avbryt
                </Button>
                <Button onClick={endCompetition} color="primary" autoFocus>
                  Avsluta tävling
                </Button>
              </DialogActions>
            </Dialog>
    
            <OperatorHeaderItem>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
              <Typography variant="h4">{presentation.competition.name}</Typography>
    
            </OperatorHeaderItem>
            <OperatorHeaderItem>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
              <Typography variant="h4">
    
    Albin Henriksson's avatar
    Albin Henriksson committed
                {activeSlideOrder !== undefined && activeSlideOrder + 1} / {presentation.competition.slides.length}
    
              </Typography>
    
            </OperatorHeaderItem>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
          </OperatorHeader>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
          {<div style={{ minHeight: 64 }} />}
    
    Max Rüdiger's avatar
    Max Rüdiger committed
          <OperatorContent>
            <OperatorInnerContent>
    
              {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
    
    Max Rüdiger's avatar
    Max Rüdiger committed
            </OperatorInnerContent>
          </OperatorContent>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
          {<div style={{ minHeight: 128 }} />}
          <OperatorFooter position="fixed">
            <Tooltip title="Föregående sida" arrow>
              <OperatorButton onClick={handleSetPrevSlide} variant="contained" disabled={isFirstSlide} color="primary">
                <ChevronLeftIcon fontSize="large" />
              </OperatorButton>
            </Tooltip>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
            {slideTimer !== null && (
              <Tooltip title="Starta timer" arrow>
                <OperatorButton
                  onClick={handleStartTimer}
                  variant="contained"
                  disabled={timer.value !== null && !timer.enabled}
                  color="primary"
                >
                  <TimerIcon fontSize="large" />
                  <Timer disableText />
    
    Max Rüdiger's avatar
    Max Rüdiger committed
                </OperatorButton>
    
              </Tooltip>
    
    Victor Löfgren's avatar
    Victor Löfgren committed
            )}
    
    Victor Löfgren's avatar
    Victor Löfgren committed
            <Tooltip title="Visa ställning för publik" arrow>
              <OperatorButton onClick={() => socketSync({ show_scoreboard: true })} variant="contained" color="primary">
                <AssignmentIcon fontSize="large" />
              </OperatorButton>
            </Tooltip>
            {showScoreboard && <Scoreboard isOperator />}
    
    Max Rüdiger's avatar
    Max Rüdiger committed
    
    
    Victor Löfgren's avatar
    Victor Löfgren committed
            <Tooltip title="Visa koder" arrow>
              <OperatorButton onClick={handleOpenCodes} variant="contained" color="primary">
                <SupervisorAccountIcon fontSize="large" />
              </OperatorButton>
            </Tooltip>
    
            <Tooltip title="Nästa sida" arrow>
              <OperatorButton onClick={handleSetNextSlide} variant="contained" disabled={isLastSlide} color="primary">
                <ChevronRightIcon fontSize="large" />
              </OperatorButton>
            </Tooltip>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
          </OperatorFooter>
    
          <Snackbar
            open={successMessageOpen && Boolean(competitionName)}
            autoHideDuration={4000}
            onClose={() => setSuccessMessageOpen(false)}
          >
    
    Albin Henriksson's avatar
    Albin Henriksson committed
            <Alert severity="success">{`Du har gått med i tävlingen "${competitionName}" som operatör`}</Alert>
          </Snackbar>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
        </OperatorContainer>
    
    Max Rüdiger's avatar
    Max Rüdiger committed
    export default OperatorViewPage