From bfba672f7a65109db842023b0dea66eb53cd748c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 09:26:39 +0200 Subject: [PATCH 01/29] Return status code 409 if deleting a record fails due to integrity constraint --- server/app/core/http_codes.py | 1 + server/app/database/controller/delete.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/server/app/core/http_codes.py b/server/app/core/http_codes.py index f6f19ed1..1f6f5524 100644 --- a/server/app/core/http_codes.py +++ b/server/app/core/http_codes.py @@ -8,6 +8,7 @@ BAD_REQUEST = 400 UNAUTHORIZED = 401 FORBIDDEN = 403 NOT_FOUND = 404 +CONFLICT = 409 GONE = 410 INTERNAL_SERVER_ERROR = 500 SERVICE_UNAVAILABLE = 503 diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py index 65737cb6..51070ed9 100644 --- a/server/app/database/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -7,6 +7,7 @@ import app.database.controller as dbc from app.core import db from app.database.models import Whitelist from flask_restx import abort +from sqlalchemy.exc import IntegrityError def default(item): @@ -15,6 +16,9 @@ def default(item): try: db.session.delete(item) db.session.commit() + except IntegrityError: + db.session.rollback() + abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") except: db.session.rollback() abort( -- GitLab From cbbe3d5ebfcae236525874f93924f0a774e7ebd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 09:40:37 +0200 Subject: [PATCH 02/29] Timer for slide is now nullable --- client/src/interfaces/ApiModels.ts | 2 +- client/src/interfaces/ApiRichModels.ts | 2 +- server/app/database/models.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts index 9bcd24c1..254c2bfd 100644 --- a/client/src/interfaces/ApiModels.ts +++ b/client/src/interfaces/ApiModels.ts @@ -36,7 +36,7 @@ export interface Slide { competition_id: number id: number order: number - timer: number + timer: number | null title: string background_image?: Media } diff --git a/client/src/interfaces/ApiRichModels.ts b/client/src/interfaces/ApiRichModels.ts index 253565a4..9eac0181 100644 --- a/client/src/interfaces/ApiRichModels.ts +++ b/client/src/interfaces/ApiRichModels.ts @@ -13,7 +13,7 @@ export interface RichCompetition { export interface RichSlide { id: number order: number - timer: number + timer: number | null title: string competition_id: number background_image?: Media diff --git a/server/app/database/models.py b/server/app/database/models.py index 97eb6097..b352c688 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -146,7 +146,7 @@ class Slide(db.Model): order = db.Column(db.Integer, nullable=False) title = db.Column(db.String(STRING_SIZE), nullable=False, default="") body = db.Column(db.Text, nullable=False, default="") - timer = db.Column(db.Integer, nullable=False, default=0) + timer = db.Column(db.Integer, nullable=True) settings = db.Column(db.Text, nullable=False, default="{}") competition_id = db.Column(db.Integer, db.ForeignKey("competition.id"), nullable=False) -- GitLab From 2a8c232311c4983631615f63ad66c58a96a176b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 10:05:54 +0200 Subject: [PATCH 03/29] Fix bug when copying components --- server/app/database/controller/add.py | 22 ++++++++++++---------- server/app/database/controller/copy.py | 1 + 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index d0adf5a0..f0b205ff 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -64,7 +64,7 @@ def db_add(item): return item -def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, **data): +def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, **data): """ Adds a component to the slide at the specified coordinates with the provided size and data. @@ -77,15 +77,17 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, **data): current_app.config["UPLOADED_PHOTOS_DEST"], filename, ) - with Image.open(path) as im: - h = im.height - w = im.width - - largest = max(w, h) - if largest > 600: - ratio = 600 / largest - w *= ratio - h *= ratio + + if not copy: + with Image.open(path) as im: + h = im.height + w = im.width + + largest = max(w, h) + if largest > 600: + ratio = 600 / largest + w *= ratio + h *= ratio if type_id == ID_TEXT_COMPONENT: item = db_add( diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index dd807334..07816e3e 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -68,6 +68,7 @@ def component(item_component, slide_id_new, view_type_id): item_component.y, item_component.w, item_component.h, + copy=True, **data, ) -- GitLab From 38e0c3f64401f3058f3471ea7c965d7526907235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 10:13:53 +0200 Subject: [PATCH 04/29] Update to current slide when joining competition and default to first slide --- server/app/core/sockets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/app/core/sockets.py b/server/app/core/sockets.py index 71871cda..a1675b2f 100644 --- a/server/app/core/sockets.py +++ b/server/app/core/sockets.py @@ -98,7 +98,7 @@ def start_presentation(data: Dict) -> None: presentations[competition_id] = { "clients": {request.sid: {"view_type": "Operator"}}, - "slide": None, + "slide": 0, "timer": {"enabled": False, "start_value": None, "value": None}, } @@ -185,6 +185,8 @@ def join_presentation(data: Dict) -> None: logger.info(f"Client '{request.sid}' joined competition '{competition_id}'") + emit("set_slide", {"slide_order": presentations[competition_id]["slide"]}) + @protect_route(allowed_views=["Operator"]) @sio.on("set_slide") -- GitLab From 050838564975052f2c1165c3b02c441205514b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 10:47:17 +0200 Subject: [PATCH 05/29] Return 409 if something cant be done due to integrity constraint --- server/app/database/controller/add.py | 2 ++ server/app/database/controller/edit.py | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index f0b205ff..751d5675 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -46,6 +46,8 @@ def db_add(item): db.session.add(item) db.session.commit() db.session.refresh(item) + except (exc.IntegrityError): + abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") except (exc.SQLAlchemyError, exc.DBAPIError): db.session.rollback() # SQL errors such as item already exists diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index efb49096..0934f58d 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -2,8 +2,11 @@ This file contains functionality to get data from the database. """ +import app.core.http_codes as codes from app.core import db from app.core.parsers import sentinel +from flask_restx.errors import abort +from sqlalchemy import exc def switch_order(item1, item2): @@ -49,6 +52,10 @@ def default(item, **kwargs): raise AttributeError(f"Item of type {type(item)} has no attribute '{key}'") if value is not sentinel: setattr(item, key, value) - db.session.commit() + try: + db.session.commit() + except exc.IntegrityError: + abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") + db.session.refresh(item) return item -- GitLab From e20f6adc380b4f57c29642df484e6953bbf638cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 10:47:54 +0200 Subject: [PATCH 06/29] Remove unused function switch_order in edit --- server/app/database/controller/edit.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 0934f58d..197f1fff 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -9,27 +9,6 @@ from flask_restx.errors import abort from sqlalchemy import exc -def switch_order(item1, item2): - """ Switches order between two slides. """ - - old_order = item1.order - new_order = item2.order - - item2.order = -1 - db.session.commit() - db.session.refresh(item2) - - item1.order = new_order - db.session.commit() - db.session.refresh(item1) - - item2.order = old_order - db.session.commit() - db.session.refresh(item2) - - return item1 - - def default(item, **kwargs): """ For every keyword argument, set that attribute on item to the given value. -- GitLab From 19efb902a004054e53c543040b4ce6043f993c60 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 15:34:18 +0200 Subject: [PATCH 07/29] Add error message when unable to remove region --- client/src/pages/admin/regions/Regions.tsx | 12 ++++++++---- .../components/slideSettingsComponents/Timer.tsx | 2 +- client/src/pages/views/components/Timer.tsx | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/src/pages/admin/regions/Regions.tsx b/client/src/pages/admin/regions/Regions.tsx index 57f4c101..0458266f 100644 --- a/client/src/pages/admin/regions/Regions.tsx +++ b/client/src/pages/admin/regions/Regions.tsx @@ -1,4 +1,4 @@ -import { Button, Menu, Typography } from '@material-ui/core' +import { Button, Menu, Snackbar, Typography } from '@material-ui/core' import Paper from '@material-ui/core/Paper' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import Table from '@material-ui/core/Table' @@ -8,6 +8,7 @@ import TableContainer from '@material-ui/core/TableContainer' import TableHead from '@material-ui/core/TableHead' import TableRow from '@material-ui/core/TableRow' import MoreHorizIcon from '@material-ui/icons/MoreHoriz' +import { Alert } from '@material-ui/lab' import axios from 'axios' import React, { useEffect } from 'react' import { getCities } from '../../../actions/cities' @@ -31,7 +32,7 @@ const useStyles = makeStyles((theme: Theme) => const RegionManager: React.FC = (props: any) => { const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null) const [activeId, setActiveId] = React.useState<number | undefined>(undefined) - const citiesTotal = useAppSelector((state) => state.cities.total) + const [errorActive, setErrorActive] = React.useState(false) const cities = useAppSelector((state) => state.cities.cities) const [newCity, setNewCity] = React.useState<string>() const classes = useStyles() @@ -53,8 +54,8 @@ const RegionManager: React.FC = (props: any) => { setAnchorEl(null) dispatch(getCities()) }) - .catch(({ response }) => { - console.warn(response.data) + .catch((response) => { + if (response?.response?.status === 409) setErrorActive(true) }) } } @@ -98,6 +99,9 @@ const RegionManager: React.FC = (props: any) => { Ta bort </RemoveMenuItem> </Menu> + <Snackbar open={errorActive} autoHideDuration={4000} onClose={() => setErrorActive(false)}> + <Alert severity="error">{`Du kan inte ta bort regionen eftersom det finns användare eller tävlingar kopplade till den.`}</Alert> + </Snackbar> </div> ) } diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index a633efec..7e90d0d3 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx @@ -24,7 +24,7 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { .catch(console.log) } } - const [timer, setTimer] = useState<number | undefined>(activeSlide?.timer) + const [timer, setTimer] = useState<number | null>(activeSlide?.timer) useEffect(() => { setTimer(activeSlide?.timer) }, [activeSlide]) diff --git a/client/src/pages/views/components/Timer.tsx b/client/src/pages/views/components/Timer.tsx index 29f95349..1bab9968 100644 --- a/client/src/pages/views/components/Timer.tsx +++ b/client/src/pages/views/components/Timer.tsx @@ -26,7 +26,7 @@ const Timer: React.FC = () => { const timerStartValue = slide?.timer const timer = useAppSelector((state) => state.presentation.timer) useEffect(() => { - if (!slide) return + if (!slide || !slide.timer) return dispatch(setPresentationTimer({ enabled: false, value: slide.timer })) }, [timerStartValue]) -- GitLab From 8022583852c10260322af86328e1c54cfa52aef3 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 16:09:19 +0200 Subject: [PATCH 08/29] Force region to be selected when creating competition --- client/src/pages/admin/competitions/AddCompetition.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/pages/admin/competitions/AddCompetition.tsx b/client/src/pages/admin/competitions/AddCompetition.tsx index 165d17d8..f2cdd1d1 100644 --- a/client/src/pages/admin/competitions/AddCompetition.tsx +++ b/client/src/pages/admin/competitions/AddCompetition.tsx @@ -74,9 +74,11 @@ const AddCompetition: React.FC = (props: any) => { // if the post request fails .catch(({ response }) => { console.warn(response.data) - if (response.data && response.data.message) + if (response?.status === 409) + actions.setFieldError('error', 'En tävling med det namnet finns redan, välj ett nytt namn och försök igen') + else if (response.data && response.data.message) actions.setFieldError('error', response.data && response.data.message) - else actions.setFieldError('error', 'Something went wrong, please try again') + else actions.setFieldError('error', 'Någonting gick fel, försök igen') }) .finally(() => { actions.setSubmitting(false) @@ -184,7 +186,7 @@ const AddCompetition: React.FC = (props: any) => { fullWidth variant="contained" color="secondary" - disabled={!formik.isValid || !formik.values.model?.name || !formik.values.model?.city} + disabled={!formik.isValid || !formik.values.model?.name || !selectedCity} > Skapa </Button> -- GitLab From f7d98797bfb7b6a772978346b15f8992b29bb31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Tue, 11 May 2021 16:17:20 +0200 Subject: [PATCH 09/29] Update error messages --- server/app/database/controller/add.py | 2 +- server/app/database/controller/edit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 751d5675..6dfc6cca 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -47,7 +47,7 @@ def db_add(item): db.session.commit() db.session.refresh(item) except (exc.IntegrityError): - abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") + abort(codes.CONFLICT, f"Item of type {type(item)} cannot be added due to an Integrity Constraint") except (exc.SQLAlchemyError, exc.DBAPIError): db.session.rollback() # SQL errors such as item already exists diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 197f1fff..9f54df3e 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -34,7 +34,7 @@ def default(item, **kwargs): try: db.session.commit() except exc.IntegrityError: - abort(codes.CONFLICT, f"Item of type {type(item)} cannot be deleted due to an Integrity Constraint") + abort(codes.CONFLICT, f"Item of type {type(item)} cannot be edited due to an Integrity Constraint") db.session.refresh(item) return item -- GitLab From f8489c6122b1af514f8378a7bfaa2b6bfa7558d2 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 16:32:24 +0200 Subject: [PATCH 10/29] Force all fields except name when creating user --- client/src/pages/admin/users/AddUser.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/pages/admin/users/AddUser.tsx b/client/src/pages/admin/users/AddUser.tsx index 2634751d..5670c18b 100644 --- a/client/src/pages/admin/users/AddUser.tsx +++ b/client/src/pages/admin/users/AddUser.tsx @@ -212,7 +212,13 @@ const AddUser: React.FC = (props: any) => { fullWidth variant="contained" color="secondary" - disabled={!formik.isValid || !formik.values.model?.name || !formik.values.model?.city} + disabled={ + !formik.isValid || + !formik.values.model?.email || + !formik.values.model?.password || + !selectedCity?.name || + !selectedRole?.name + } > Lägg till </Button> -- GitLab From a99b3ac87a038f7544faee5006fb8e2b09b1bc84 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 16:48:03 +0200 Subject: [PATCH 11/29] force credentials when creating user --- .../components/CompetitionSettings.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx index fd63a4b3..64b4a820 100644 --- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx +++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx @@ -1,6 +1,6 @@ import { Divider, FormControl, InputLabel, ListItem, MenuItem, Select, TextField, Typography } from '@material-ui/core' import axios from 'axios' -import React from 'react' +import React, { useState } from 'react' import { useParams } from 'react-router-dom' import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch, useAppSelector } from '../../../hooks' @@ -15,6 +15,7 @@ interface CompetitionParams { const CompetitionSettings: React.FC = () => { const { competitionId }: CompetitionParams = useParams() + const [nameErrorText, setNameErrorText] = useState<string | undefined>(undefined) const dispatch = useAppDispatch() const competition = useAppSelector((state) => state.editor.competition) const cities = useAppSelector((state) => state.cities.cities) @@ -23,9 +24,12 @@ const CompetitionSettings: React.FC = () => { await axios .put(`/api/competitions/${competitionId}`, { name: event.target.value }) .then(() => { + setNameErrorText(undefined) dispatch(getEditorCompetition(competitionId)) }) - .catch(console.log) + .catch((response) => { + if (response?.response.status === 409) setNameErrorText('Det finns redan en tävling med det namnet.') + }) } const updateCompetitionCity = async (city: City) => { @@ -52,6 +56,8 @@ const CompetitionSettings: React.FC = () => { <FirstItem> <ListItem> <TextField + error={Boolean(nameErrorText)} + helperText={nameErrorText} id="outlined-basic" label={'Tävlingsnamn'} defaultValue={competition.name} -- GitLab From e5533405594e53625ae00d9cc9609838dac8aae7 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 16:58:31 +0200 Subject: [PATCH 12/29] Fix error messages when entering codes in url --- client/src/actions/competitionLogin.ts | 9 ++++++++- client/src/pages/views/ViewSelectPage.tsx | 4 ++-- client/src/pages/views/styled.tsx | 4 ++-- client/src/reducers/competitionLoginReducer.ts | 8 ++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/client/src/actions/competitionLogin.ts b/client/src/actions/competitionLogin.ts index cfc222b3..42a05053 100644 --- a/client/src/actions/competitionLogin.ts +++ b/client/src/actions/competitionLogin.ts @@ -35,7 +35,14 @@ export const loginCompetition = (code: string, history: History, redirect: boole } }) .catch((err) => { - dispatch({ type: Types.SET_COMPETITION_LOGIN_ERRORS, payload: err && err.response && err.response.data }) + let errorMessage = err?.response?.data?.message + if (err?.response?.status === 401) { + errorMessage = 'Inkorrekt kod. Dubbelkolla koden och försök igen.' + } + if (err?.response?.status === 404) { + errorMessage = 'En tävling med den koden existerar inte. Dubbelkolla koden och försök igen.' + } + dispatch({ type: Types.SET_COMPETITION_LOGIN_ERRORS, payload: errorMessage }) console.log(err) }) } diff --git a/client/src/pages/views/ViewSelectPage.tsx b/client/src/pages/views/ViewSelectPage.tsx index 75bf8498..9bd8d2ae 100644 --- a/client/src/pages/views/ViewSelectPage.tsx +++ b/client/src/pages/views/ViewSelectPage.tsx @@ -12,14 +12,14 @@ const ViewSelectPage: React.FC = () => { const dispatch = useAppDispatch() const history = useHistory() const competitionId = useAppSelector((state) => state.competitionLogin.data?.competition_id) - const errorMessage = useAppSelector((state) => state.competitionLogin.errors?.message) + const errorMessage = useAppSelector((state) => state.competitionLogin.errors) const loading = useAppSelector((state) => state.competitionLogin.loading) const { code }: ViewSelectParams = useParams() const viewType = useAppSelector((state) => state.competitionLogin.data?.view) const renderView = () => { //Renders the correct view depending on view type - if (competitionId) { + if (competitionId && !errorMessage) { switch (viewType) { case 'Team': return <Redirect to={`/view/team`} /> diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index 4b01d63a..c8946c22 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -21,8 +21,8 @@ export const JudgeAnswersLabel = styled(Typography)` export const ViewSelectContainer = styled.div` display: flex; justify-content: center; - margin-top: 12%; - height: 100%; + padding-top: 12%; + height: calc(100%-12%); ` export const ViewSelectButtonGroup = styled.div` diff --git a/client/src/reducers/competitionLoginReducer.ts b/client/src/reducers/competitionLoginReducer.ts index f3ca764c..b95efdf5 100644 --- a/client/src/reducers/competitionLoginReducer.ts +++ b/client/src/reducers/competitionLoginReducer.ts @@ -7,15 +7,11 @@ interface CompetitionLoginData { team_id: number | null view: string } -/** Define a type for UI error */ -interface UIError { - message: string -} /** Define a type for the competition login state */ interface CompetitionLoginState { loading: boolean - errors: null | UIError + errors: null | string authenticated: boolean data: CompetitionLoginData | null initialized: boolean @@ -43,7 +39,7 @@ export default function (state = initialState, action: AnyAction) { case Types.SET_COMPETITION_LOGIN_ERRORS: return { ...state, - errors: action.payload as UIError, + errors: action.payload as string, loading: false, } case Types.CLEAR_COMPETITION_LOGIN_ERRORS: -- GitLab From 83f76c5a0f7f23251510578996c343d0f461cc2b Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 17:18:48 +0200 Subject: [PATCH 13/29] Fix operator code scroll-bar --- client/src/pages/login/components/CompetitionLogin.tsx | 6 +++--- client/src/pages/presentationEditor/components/styled.tsx | 1 + client/src/pages/views/OperatorViewPage.tsx | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/pages/login/components/CompetitionLogin.tsx b/client/src/pages/login/components/CompetitionLogin.tsx index 44463474..4e2429a3 100644 --- a/client/src/pages/login/components/CompetitionLogin.tsx +++ b/client/src/pages/login/components/CompetitionLogin.tsx @@ -61,11 +61,11 @@ const CompetitionLogin: React.FC = () => { <Button type="submit" fullWidth variant="contained" color="secondary" disabled={!formik.isValid}> Anslut till tävling </Button> - {errors && errors.message && ( + {errors && ( <Alert severity="error"> <AlertTitle>Error</AlertTitle> - <Typography>En tävling med den koden hittades ej.</Typography> - <Typography>kontrollera koden och försök igen</Typography> + <Typography>En tävling med den koden existerar ej.</Typography> + <Typography>Dubbelkolla koden och försök igen</Typography> </Alert> )} {loading && <CenteredCircularProgress color="secondary" />} diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 31e40d51..6e98f03a 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -58,6 +58,7 @@ export const Center = styled.div` text-align: center; height: 100%; width: 100%; + overflow-x: hidden; ` export const ImageTextContainer = styled.div` diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx index 5bb96fcb..6018f5e6 100644 --- a/client/src/pages/views/OperatorViewPage.tsx +++ b/client/src/pages/views/OperatorViewPage.tsx @@ -122,7 +122,6 @@ const OperatorViewPage: React.FC = () => { useEffect(() => { socketConnect('Operator') socketSetSlide - handleOpenCodes() setTimeout(startCompetition, 1000) // Wait for socket to connect }, []) @@ -232,7 +231,7 @@ const OperatorViewPage: React.FC = () => { fullWidth={false} fullScreen={false} > - <Center> + <Center style={{ overflowX: 'hidden' }}> <DialogTitle id="max-width-dialog-title" className={classes.paper} style={{ width: '100%' }}> Koder för {competitionName} </DialogTitle> -- GitLab From 3e74193d5ec0055071006b3029b37efd76db3618 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 18:28:13 +0200 Subject: [PATCH 14/29] Fix timer and score inputs --- client/src/interfaces/ApiModels.ts | 2 +- client/src/pages/admin/users/UserManager.tsx | 17 ------ .../QuestionSettings.tsx | 54 +++++++++++-------- .../slideSettingsComponents/Timer.tsx | 45 +++++++++------- .../presentationEditor/components/styled.tsx | 4 ++ client/src/pages/views/OperatorViewPage.tsx | 33 ++++-------- server/app/database/models.py | 5 +- 7 files changed, 75 insertions(+), 85 deletions(-) diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts index 254c2bfd..5ea091a0 100644 --- a/client/src/interfaces/ApiModels.ts +++ b/client/src/interfaces/ApiModels.ts @@ -54,7 +54,7 @@ export interface Team extends NameID { export interface Question extends NameID { slide_id: number - total_score: number + total_score: number | null type_id: number correcting_instructions: string } diff --git a/client/src/pages/admin/users/UserManager.tsx b/client/src/pages/admin/users/UserManager.tsx index 20f57386..9713e90f 100644 --- a/client/src/pages/admin/users/UserManager.tsx +++ b/client/src/pages/admin/users/UserManager.tsx @@ -47,23 +47,6 @@ const UserManager: React.FC = (props: any) => { const dispatch = useAppDispatch() const open = Boolean(anchorEl) - const id = open ? 'simple-popover' : undefined - - const handleClick = (event: React.MouseEvent<HTMLButtonElement>, user: User) => { - setAnchorEl(event.currentTarget) - setSelectedUser(user) - } - - const handleClose = () => { - setAnchorEl(null) - setSelectedUser(undefined) - console.log('close') - } - - const handleEditClose = () => { - setEditAnchorEl(null) - console.log('edit close') - } useEffect(() => { dispatch(getSearchUsers()) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx index f714fe23..367ebd8a 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx @@ -1,10 +1,10 @@ import { ListItem, ListItemText, TextField } from '@material-ui/core' import axios from 'axios' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' -import { Center, SettingsList } from '../styled' +import { Center, SettingsItemContainer, SettingsList } from '../styled' type QuestionSettingsProps = { activeSlide: RichSlide @@ -13,7 +13,18 @@ type QuestionSettingsProps = { const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) => { const dispatch = useAppDispatch() - + const [timerHandle, setTimerHandle] = useState<number | undefined>(undefined) + const handleChangeQuestion = ( + updateTitle: boolean, + event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement> + ) => { + if (timerHandle) { + clearTimeout(timerHandle) + setTimerHandle(undefined) + } + //Only updates question and api 100ms after last input was made + setTimerHandle(window.setTimeout(() => updateQuestion(updateTitle, event), 100)) + } const updateQuestion = async ( updateTitle: boolean, event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement> @@ -29,10 +40,9 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) }) .catch(console.log) } else { - setScore(+event.target.value) await axios .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, { - total_score: event.target.value, + total_score: event.target.value || null, }) .then(() => { dispatch(getEditorCompetition(competitionId)) @@ -41,11 +51,7 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) } } } - - const [score, setScore] = useState<number | undefined>(0) - useEffect(() => { - setScore(activeSlide?.questions?.[0]?.total_score) - }, [activeSlide]) + const score = activeSlide?.questions?.[0]?.total_score return ( <SettingsList> @@ -57,26 +63,28 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) <ListItem divider> <TextField id="outlined-basic" - defaultValue={''} + defaultValue={activeSlide?.questions?.[0]?.name} label="Frågans titel" - onChange={(event) => updateQuestion(true, event)} + onChange={(event) => handleChangeQuestion(true, event)} variant="outlined" fullWidth={true} /> </ListItem> <ListItem> <Center> - <TextField - fullWidth={true} - variant="outlined" - placeholder="Antal poäng" - helperText="Välj hur många poäng frågan ska ge för rätt svar." - label="Poäng" - type="number" - InputProps={{ inputProps: { min: 0 } }} - value={score || 0} - onChange={(event) => updateQuestion(false, event)} - /> + <SettingsItemContainer> + <TextField + fullWidth={true} + variant="outlined" + placeholder="Antal poäng" + helperText="Välj hur många poäng frågan ska ge för rätt svar." + label="Poäng" + type="number" + InputProps={{ inputProps: { min: 0, max: 1000000 } }} + defaultValue={score || 0} + onChange={(event) => handleChangeQuestion(false, event)} + /> + </SettingsItemContainer> </Center> </ListItem> </SettingsList> diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index 7e90d0d3..22919582 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx @@ -1,10 +1,10 @@ import { ListItem, TextField } from '@material-ui/core' import axios from 'axios' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' -import { Center } from '../styled' +import { Center, SettingsItemContainer } from '../styled' type TimerProps = { activeSlide: RichSlide @@ -13,8 +13,17 @@ type TimerProps = { const Timer = ({ activeSlide, competitionId }: TimerProps) => { const dispatch = useAppDispatch() + const [timerHandle, setTimerHandle] = useState<number | undefined>(undefined) + const handleChangeTimer = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { + if (timerHandle) { + clearTimeout(timerHandle) + setTimerHandle(undefined) + } + //Only updates question and api 100ms after last input was made + setTimerHandle(window.setTimeout(() => updateTimer(event), 100)) + } + const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { - setTimer(+event.target.value) if (activeSlide) { await axios .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}`, { timer: event.target.value || null }) @@ -24,24 +33,24 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { .catch(console.log) } } - const [timer, setTimer] = useState<number | null>(activeSlide?.timer) - useEffect(() => { - setTimer(activeSlide?.timer) - }, [activeSlide]) + return ( <ListItem> <Center> - <TextField - id="standard-number" - fullWidth={true} - variant="outlined" - placeholder="Antal sekunder" - helperText="Lämna blank för att inte använda timerfunktionen" - label="Timer" - type="number" - onChange={updateTimer} - value={timer || ''} - /> + <SettingsItemContainer> + <TextField + id="standard-number" + fullWidth={true} + variant="outlined" + placeholder="Antal sekunder" + helperText="Lämna blank för att inte använda timerfunktionen" + label="Timer" + type="number" + onChange={handleChangeTimer} + InputProps={{ inputProps: { min: 0, max: 1000000 } }} + defaultValue={activeSlide?.timer} + /> + </SettingsItemContainer> </Center> </ListItem> ) diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 6e98f03a..c214a7ce 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -141,3 +141,7 @@ export const ImageNameText = styled(ListItemText)` export const QuestionComponent = styled.div` outline-style: double; ` + +export const SettingsItemContainer = styled.div` + padding: 5px; +` diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx index 6018f5e6..b091e21f 100644 --- a/client/src/pages/views/OperatorViewPage.tsx +++ b/client/src/pages/views/OperatorViewPage.tsx @@ -101,7 +101,7 @@ const OperatorViewPage: React.FC = () => { const [openAlert, setOpen] = React.useState(false) const [openAlertCode, setOpenCode] = React.useState(false) const [codes, setCodes] = React.useState<Code[]>([]) - const [competitionName, setCompetitionName] = React.useState<string | undefined>(undefined) + const competitionName = useAppSelector((state) => state.presentation.competition.name) //const fullScreen = useMediaQuery(theme.breakpoints.down('sm')) @@ -154,7 +154,6 @@ const OperatorViewPage: React.FC = () => { const handleOpenCodes = async () => { await getCodes() - await getCompetitionName() setOpenCode(true) } @@ -173,18 +172,6 @@ const OperatorViewPage: React.FC = () => { }) .catch(console.log) } - - const getCompetitionName = async () => { - await axios - .get(`/api/competitions/${activeId}`) - .then((response) => { - setCompetitionName(response.data.name) - }) - .catch((err) => { - console.log(err) - }) - } - const getTypeName = (code: Code) => { let typeName = '' switch (code.view_type_id) { @@ -223,15 +210,8 @@ const OperatorViewPage: React.FC = () => { return ( <OperatorContainer> - <Dialog - open={openAlertCode} - onClose={handleClose} - aria-labelledby="max-width-dialog-title" - maxWidth="xl" - fullWidth={false} - fullScreen={false} - > - <Center style={{ overflowX: 'hidden' }}> + <Dialog open={openAlertCode} onClose={handleClose} aria-labelledby="max-width-dialog-title" maxWidth="xl"> + <Center> <DialogTitle id="max-width-dialog-title" className={classes.paper} style={{ width: '100%' }}> Koder för {competitionName} </DialogTitle> @@ -363,6 +343,7 @@ const OperatorViewPage: React.FC = () => { horizontal: 'center', }} > + {(!teams || teams.length === 0) && 'Det finns inga lag i denna tävling'} <List> {teams && teams.map((team) => ( @@ -372,7 +353,11 @@ const OperatorViewPage: React.FC = () => { ))} </List> </Popover> - <Snackbar open={successMessageOpen} autoHideDuration={4000} onClose={() => setSuccessMessageOpen(false)}> + <Snackbar + open={successMessageOpen && Boolean(competitionName)} + autoHideDuration={4000} + onClose={() => setSuccessMessageOpen(false)} + > <Alert severity="success">{`Du har gått med i tävlingen "${competitionName}" som operatör`}</Alert> </Snackbar> </OperatorContainer> diff --git a/server/app/database/models.py b/server/app/database/models.py index b352c688..bc13c094 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -5,7 +5,8 @@ each other. """ from app.core import bcrypt, db -from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEXT_COMPONENT +from app.database.types import (ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, + ID_TEXT_COMPONENT) from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property STRING_SIZE = 254 @@ -164,7 +165,7 @@ class Slide(db.Model): class Question(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(STRING_SIZE), nullable=False) - total_score = db.Column(db.Integer, nullable=False, default=1) + total_score = db.Column(db.Integer, nullable=True, default=None) type_id = db.Column(db.Integer, db.ForeignKey("question_type.id"), nullable=False) slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False) correcting_instructions = db.Column(db.Text, nullable=True, default=None) -- GitLab From e31d37160fdeebcea629b4ee37fa91cf6cf1aead Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Tue, 11 May 2021 18:51:43 +0200 Subject: [PATCH 15/29] Fix timer, question title and question score --- .../QuestionSettings.tsx | 22 +++++++++++++------ .../slideSettingsComponents/Timer.tsx | 14 +++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx index 367ebd8a..3b474923 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx @@ -1,6 +1,6 @@ import { ListItem, ListItemText, TextField } from '@material-ui/core' import axios from 'axios' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' @@ -22,8 +22,11 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) clearTimeout(timerHandle) setTimerHandle(undefined) } - //Only updates question and api 100ms after last input was made - setTimerHandle(window.setTimeout(() => updateQuestion(updateTitle, event), 100)) + //Only updates question and api 500ms after last input was made + setTimerHandle(window.setTimeout(() => updateQuestion(updateTitle, event), 300)) + if (updateTitle) { + setName(event.target.value) + } else setScore(+event.target.value) } const updateQuestion = async ( updateTitle: boolean, @@ -51,7 +54,12 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) } } } - const score = activeSlide?.questions?.[0]?.total_score + const [score, setScore] = useState<number | undefined>(0) + const [name, setName] = useState<string | undefined>('') + useEffect(() => { + setName(activeSlide?.questions?.[0]?.name) + setScore(activeSlide?.questions?.[0]?.total_score) + }, [activeSlide]) return ( <SettingsList> @@ -63,11 +71,11 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) <ListItem divider> <TextField id="outlined-basic" - defaultValue={activeSlide?.questions?.[0]?.name} label="Frågans titel" onChange={(event) => handleChangeQuestion(true, event)} variant="outlined" fullWidth={true} + value={name || ''} /> </ListItem> <ListItem> @@ -77,11 +85,11 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) fullWidth={true} variant="outlined" placeholder="Antal poäng" - helperText="Välj hur många poäng frågan ska ge för rätt svar." + helperText="Välj hur många poäng frågan ska ge för rätt svar. Lämna blank för att inte använda poängfunktionen" label="Poäng" type="number" InputProps={{ inputProps: { min: 0, max: 1000000 } }} - defaultValue={score || 0} + value={score || ''} onChange={(event) => handleChangeQuestion(false, event)} /> </SettingsItemContainer> diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index 22919582..89e4ce01 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx @@ -1,6 +1,6 @@ import { ListItem, TextField } from '@material-ui/core' import axios from 'axios' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { getEditorCompetition } from '../../../../actions/editor' import { useAppDispatch } from '../../../../hooks' import { RichSlide } from '../../../../interfaces/ApiRichModels' @@ -19,8 +19,9 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { clearTimeout(timerHandle) setTimerHandle(undefined) } - //Only updates question and api 100ms after last input was made - setTimerHandle(window.setTimeout(() => updateTimer(event), 100)) + //Only updates slide and api 300s after last input was made + setTimerHandle(window.setTimeout(() => updateTimer(event), 300)) + setTimer(+event.target.value) } const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { @@ -33,7 +34,10 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { .catch(console.log) } } - + const [timer, setTimer] = useState<number | null>(activeSlide?.timer) + useEffect(() => { + setTimer(activeSlide?.timer) + }, [activeSlide]) return ( <ListItem> <Center> @@ -48,7 +52,7 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { type="number" onChange={handleChangeTimer} InputProps={{ inputProps: { min: 0, max: 1000000 } }} - defaultValue={activeSlide?.timer} + value={timer || ''} /> </SettingsItemContainer> </Center> -- GitLab From ea7a33dfc7cf2f053580e02d13da2746d7a3aaeb Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 09:54:35 +0200 Subject: [PATCH 16/29] Fix color of view select buttons --- client/src/pages/presentationEditor/styled.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/src/pages/presentationEditor/styled.tsx b/client/src/pages/presentationEditor/styled.tsx index 779599da..4b6e7a0c 100644 --- a/client/src/pages/presentationEditor/styled.tsx +++ b/client/src/pages/presentationEditor/styled.tsx @@ -20,11 +20,7 @@ export const ToolBarContainer = styled(Toolbar)` ` export const ViewButton = styled(Button)<ViewButtonProps>` - background: ${(props) => (props.$activeView ? '#5a0017' : undefined)}; -` - -export const ViewButtonClicked = styled(Button)` - background: #5a0017; + background: ${(props) => (!props.$activeView ? '#5a0017' : undefined)}; ` export const SlideList = styled(List)` -- GitLab From 5c1ffeb9fd71026f1f1a07124ea8805500c4a33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 12 May 2021 09:57:00 +0200 Subject: [PATCH 17/29] Refactor add.component --- server/app/database/controller/add.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 6dfc6cca..9ba36dc9 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -72,15 +72,20 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, * coordinates with the provided size and data. """ - if type_id == 2: # 2 is image - item_image = get.one(Media, data["media_id"]) - filename = item_image.filename - path = os.path.join( - current_app.config["UPLOADED_PHOTOS_DEST"], - filename, + if type_id == ID_TEXT_COMPONENT: + item = db_add( + TextComponent(slide_id, type_id, view_type_id, x, y, w, h), ) + item.text = data.get("text") + elif type_id == ID_IMAGE_COMPONENT: + if not copy: # Scale image if adding a new one, a copied image should keep it's size + item_image = get.one(Media, data["media_id"]) + filename = item_image.filename + path = os.path.join( + current_app.config["UPLOADED_PHOTOS_DEST"], + filename, + ) - if not copy: with Image.open(path) as im: h = im.height w = im.width @@ -91,12 +96,6 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, copy=False, * w *= ratio h *= ratio - if type_id == ID_TEXT_COMPONENT: - item = db_add( - TextComponent(slide_id, type_id, view_type_id, x, y, w, h), - ) - item.text = data.get("text") - elif type_id == ID_IMAGE_COMPONENT: item = db_add( ImageComponent(slide_id, type_id, view_type_id, x, y, w, h), ) -- GitLab From 34d398174196594a144f3d67c0809a1000957dfe Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 10:03:30 +0200 Subject: [PATCH 18/29] Fix styling of competition name in operator view --- client/src/pages/views/OperatorViewPage.tsx | 10 ++++++---- client/src/pages/views/styled.tsx | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx index b091e21f..0936abaf 100644 --- a/client/src/pages/views/OperatorViewPage.tsx +++ b/client/src/pages/views/OperatorViewPage.tsx @@ -49,8 +49,8 @@ import { OperatorContent, OperatorFooter, OperatorHeader, + OperatorHeaderItem, OperatorInnerContent, - SlideCounter, ToolBarContainer, } from './styled' @@ -282,12 +282,14 @@ const OperatorViewPage: React.FC = () => { </Button> </DialogActions> </Dialog> - <Typography variant="h3">{presentation.competition.name}</Typography> - <SlideCounter> + <OperatorHeaderItem> + <Typography variant="h3">{presentation.competition.name}</Typography> + </OperatorHeaderItem> + <OperatorHeaderItem> <Typography variant="h3"> {activeSlideOrder !== undefined && activeSlideOrder + 1} / {presentation.competition.slides.length} </Typography> - </SlideCounter> + </OperatorHeaderItem> </OperatorHeader> <div style={{ height: 0, paddingTop: 120 }} /> <OperatorContent> diff --git a/client/src/pages/views/styled.tsx b/client/src/pages/views/styled.tsx index c8946c22..3fe60445 100644 --- a/client/src/pages/views/styled.tsx +++ b/client/src/pages/views/styled.tsx @@ -60,7 +60,7 @@ export const OperatorButton = styled(Button)` margin-top: 16px; ` -export const SlideCounter = styled(Button)` +export const OperatorHeaderItem = styled(Button)` margin-left: 16px; margin-right: 16px; margin-top: 16px; -- GitLab From e2a9e7b3978d8def2cef0d859dc26728bc94728e Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 10:09:03 +0200 Subject: [PATCH 19/29] Reset slide id when joining admin view --- client/src/pages/admin/AdminPage.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx index 8d193557..7b132f2a 100644 --- a/client/src/pages/admin/AdminPage.tsx +++ b/client/src/pages/admin/AdminPage.tsx @@ -20,6 +20,7 @@ import React, { useEffect } from 'react' import { Link, Route, Switch, useRouteMatch } from 'react-router-dom' import { getCities } from '../../actions/cities' import { setEditorLoading } from '../../actions/competitions' +import { setEditorSlideId } from '../../actions/editor' import { getRoles } from '../../actions/roles' import { getStatistics } from '../../actions/statistics' import { getTypes } from '../../actions/typesAction' @@ -75,6 +76,7 @@ const AdminView: React.FC = () => { dispatch(getTypes()) dispatch(getStatistics()) dispatch(setEditorLoading(true)) + dispatch(setEditorSlideId(-1)) }, []) const menuAdminItems = [ -- GitLab From ca73320e22fcfd150ecd08d655a6a7154bbf064c Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 10:21:52 +0200 Subject: [PATCH 20/29] Fix typing for timer in presentation state --- client/src/pages/views/JudgeViewPage.tsx | 8 ++++++++ client/src/reducers/presentationReducer.ts | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index a28348a7..6211aaf0 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -53,6 +53,7 @@ const JudgeViewPage: React.FC = () => { const [currentSlide, setCurrentSlide] = useState<RichSlide | undefined>(undefined) const currentQuestion = currentSlide?.questions[0] const operatorActiveSlideId = useAppSelector((state) => state.presentation.activeSlideId) + const timer = useAppSelector((state) => state.presentation.timer) const operatorActiveSlideOrder = useAppSelector( (state) => state.presentation.competition.slides.find((slide) => slide.id === operatorActiveSlideId)?.order ) @@ -74,9 +75,16 @@ const JudgeViewPage: React.FC = () => { dispatch(getPresentationCompetition(competitionId.toString())) } }, [operatorActiveSlideId]) + useEffect(() => { + // Every third tic of the timer, load new answers + if (timer.value % 3 === 0 && competitionId) { + dispatch(getPresentationCompetition(competitionId.toString())) + } + }, [timer.value]) return ( <div style={{ height: '100%' }}> <JudgeAppBar position="fixed"> + {timer.value} <JudgeToolbar> <JudgeQuestionsLabel variant="h5">Frågor</JudgeQuestionsLabel> {operatorActiveSlideOrder !== undefined && ( diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts index 4df814a8..cc183fad 100644 --- a/client/src/reducers/presentationReducer.ts +++ b/client/src/reducers/presentationReducer.ts @@ -49,12 +49,13 @@ export default function (state = initialState, action: AnyAction) { activeSlideId: action.payload as number, } case Types.SET_PRESENTATION_TIMER: + const timer = action.payload as Timer if (action.payload.value == 0) { - action.payload.enabled = false + timer.enabled = false } return { ...state, - timer: action.payload, + timer, } default: return state -- GitLab From 3b31d2dd69ade8308572efaaa6984f84c718e81a Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 10:35:23 +0200 Subject: [PATCH 21/29] Get competition every second tic of timer --- client/src/pages/views/JudgeViewPage.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 6211aaf0..9efae217 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -11,6 +11,7 @@ import SlideDisplay from '../presentationEditor/components/SlideDisplay' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import JudgeScoringInstructions from './components/JudgeScoringInstructions' +import Timer from './components/Timer' import { Content, InnerContent, @@ -76,15 +77,15 @@ const JudgeViewPage: React.FC = () => { } }, [operatorActiveSlideId]) useEffect(() => { - // Every third tic of the timer, load new answers - if (timer.value % 3 === 0 && competitionId) { + // Every second tic of the timer, load new answers + if (timer.value % 2 === 0 && competitionId) { dispatch(getPresentationCompetition(competitionId.toString())) } }, [timer.value]) return ( <div style={{ height: '100%' }}> <JudgeAppBar position="fixed"> - {timer.value} + <Timer /> <JudgeToolbar> <JudgeQuestionsLabel variant="h5">Frågor</JudgeQuestionsLabel> {operatorActiveSlideOrder !== undefined && ( -- GitLab From 6f1997bdcde1aba06dad8ea3ba97a77e7dc33512 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 10:36:51 +0200 Subject: [PATCH 22/29] remove testing timer --- client/src/pages/views/JudgeViewPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/pages/views/JudgeViewPage.tsx b/client/src/pages/views/JudgeViewPage.tsx index 9efae217..c25b09dd 100644 --- a/client/src/pages/views/JudgeViewPage.tsx +++ b/client/src/pages/views/JudgeViewPage.tsx @@ -11,7 +11,6 @@ import SlideDisplay from '../presentationEditor/components/SlideDisplay' import { SlideListItem } from '../presentationEditor/styled' import JudgeScoreDisplay from './components/JudgeScoreDisplay' import JudgeScoringInstructions from './components/JudgeScoringInstructions' -import Timer from './components/Timer' import { Content, InnerContent, @@ -85,7 +84,6 @@ const JudgeViewPage: React.FC = () => { return ( <div style={{ height: '100%' }}> <JudgeAppBar position="fixed"> - <Timer /> <JudgeToolbar> <JudgeQuestionsLabel variant="h5">Frågor</JudgeQuestionsLabel> {operatorActiveSlideOrder !== undefined && ( -- GitLab From 2c22c95d7ab24b90fcd11da10ca07cb85515d6bc Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 15:48:56 +0200 Subject: [PATCH 23/29] Fix inconsistency in selecting question type --- .../slideSettingsComponents/SlideType.tsx | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx index 8fbae438..1c08a7f1 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx @@ -125,30 +125,20 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { <FormControl fullWidth variant="outlined"> <InputLabel>Sidtyp</InputLabel> <Select fullWidth={true} value={activeSlide?.questions?.[0]?.type_id || 0} label="Sidtyp"> - <MenuItem value={0}> - <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> - Informationssida - </Typography> + <MenuItem value={0} button onClick={() => openSlideTypeDialog(0)}> + <Typography>Informationssida</Typography> </MenuItem> - <MenuItem value={1}> - <Typography variant="button" onClick={() => openSlideTypeDialog(1)}> - Skriftlig fråga - </Typography> + <MenuItem value={1} button onClick={() => openSlideTypeDialog(1)}> + <Typography>Skriftlig fråga</Typography> </MenuItem> - <MenuItem value={2}> - <Typography variant="button" onClick={() => openSlideTypeDialog(2)}> - Praktisk fråga - </Typography> + <MenuItem value={2} button onClick={() => openSlideTypeDialog(2)}> + <Typography>Praktisk fråga</Typography> </MenuItem> - <MenuItem value={3}> - <Typography variant="button" onClick={() => openSlideTypeDialog(3)}> - Kryssfråga - </Typography> + <MenuItem value={3} button onClick={() => openSlideTypeDialog(3)}> + <Typography>Kryssfråga</Typography> </MenuItem> - <MenuItem value={4}> - <Typography variant="button" onClick={() => openSlideTypeDialog(4)}> - Alternativfråga - </Typography> + <MenuItem value={4} button onClick={() => openSlideTypeDialog(4)}> + <Typography>Alternativfråga</Typography> </MenuItem> </Select> </FormControl> -- GitLab From e2dde13087cf5326282177a521d0fb233ca07fb1 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 16:14:56 +0200 Subject: [PATCH 24/29] Avoid creating double questions and residual question components --- .../slideSettingsComponents/SlideType.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx index 1c08a7f1..5f11cffe 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx @@ -56,6 +56,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { ) .then(() => { dispatch(getEditorCompetition(competitionId)) + removeQuestionComponent() }) .catch(console.log) } else { @@ -73,11 +74,11 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { }) .then(() => { dispatch(getEditorCompetition(competitionId)) - createQuestionComponent() + removeQuestionComponent().then(() => createQuestionComponent()) }) .catch(console.log) } - } else if (selectedSlideType !== 0) { + } else if (activeSlide.questions[0].type_id === 0 && selectedSlideType !== 0) { // Change slide type from information to a question type await axios .post(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions`, { @@ -94,8 +95,8 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { } } - const createQuestionComponent = () => { - axios + const createQuestionComponent = async () => { + await axios .post(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components`, { x: 0, y: 0, @@ -111,6 +112,13 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { .catch(console.log) } + const removeQuestionComponent = async () => { + const questionComponentId = activeSlide.components.find((component) => component.type_id === 3)?.id + await axios + .delete(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components/${questionComponentId}`) + .catch(console.log) + } + const deleteQuestionComponent = (componentId: number | undefined) => { if (componentId) { axios -- GitLab From 531bc4addb113e9321f7bda7785778d2930ea18c Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 12 May 2021 16:15:46 +0200 Subject: [PATCH 25/29] only remove qcomponent if questionComponentId is found --- .../components/slideSettingsComponents/SlideType.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx index 5f11cffe..2e4ee216 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx @@ -114,9 +114,11 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { const removeQuestionComponent = async () => { const questionComponentId = activeSlide.components.find((component) => component.type_id === 3)?.id - await axios - .delete(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components/${questionComponentId}`) - .catch(console.log) + if (questionComponentId) { + await axios + .delete(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components/${questionComponentId}`) + .catch(console.log) + } } const deleteQuestionComponent = (componentId: number | undefined) => { -- GitLab From 389e3df94a079f5fd705a54ec6a96d2537af974e Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Fri, 14 May 2021 09:16:49 +0200 Subject: [PATCH 26/29] fix tests --- client/src/actions/competitionLogin.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/actions/competitionLogin.test.ts b/client/src/actions/competitionLogin.test.ts index 9a988c23..a95dd002 100644 --- a/client/src/actions/competitionLogin.test.ts +++ b/client/src/actions/competitionLogin.test.ts @@ -62,7 +62,7 @@ it('dispatches correct action when failing to log in user', async () => { console.log = jest.fn() const errorMessage = 'getting teams failed' ;(mockedAxios.post as jest.Mock).mockImplementation(() => { - return Promise.reject({ response: { data: errorMessage } }) + return Promise.reject({ response: { data: { message: errorMessage } } }) }) const store = mockStore({}) const history = createMemoryHistory() -- GitLab From a4b8095b68836b262be1ad23cb34482a58b02caa Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Fri, 14 May 2021 09:37:06 +0200 Subject: [PATCH 27/29] fix team view page --- client/src/pages/views/TeamViewPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/views/TeamViewPage.tsx b/client/src/pages/views/TeamViewPage.tsx index 6c459475..e9566cef 100644 --- a/client/src/pages/views/TeamViewPage.tsx +++ b/client/src/pages/views/TeamViewPage.tsx @@ -34,11 +34,11 @@ const TeamViewPage: React.FC = () => { <Typography variant="h1"> <Timer /> </Typography> - <SlideCounter> + <OperatorHeaderItem> <Typography variant="h3"> {activeSlideOrder !== undefined && activeSlideOrder + 1} / {presentation.competition.slides.length} </Typography> - </SlideCounter> + </OperatorHeaderItem> </OperatorHeader> <PresentationBackground> <PresentationContainer> -- GitLab From ece0650f1f5e4d306f26ddf4d2fdcbedc11f053e Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Fri, 14 May 2021 09:38:02 +0200 Subject: [PATCH 28/29] fix incorrect import --- client/src/pages/views/TeamViewPage.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/pages/views/TeamViewPage.tsx b/client/src/pages/views/TeamViewPage.tsx index e9566cef..4c3266bc 100644 --- a/client/src/pages/views/TeamViewPage.tsx +++ b/client/src/pages/views/TeamViewPage.tsx @@ -5,7 +5,13 @@ import { useAppSelector } from '../../hooks' import { socketConnect, socketJoinPresentation } from '../../sockets' import SlideDisplay from '../presentationEditor/components/SlideDisplay' import Timer from '../views/components/Timer' -import { OperatorContainer, OperatorHeader, PresentationBackground, PresentationContainer } from './styled' +import { + OperatorContainer, + OperatorHeader, + OperatorHeaderItem, + PresentationBackground, + PresentationContainer, +} from './styled' const TeamViewPage: React.FC = () => { const code = useAppSelector((state) => state.presentation.code) -- GitLab From f1c6c1b025ac2719c43d0ec0a208d556f89d3d69 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Fri, 14 May 2021 09:43:55 +0200 Subject: [PATCH 29/29] Make sure timer and score is above 0 --- .../components/slideSettingsComponents/QuestionSettings.tsx | 3 ++- .../components/slideSettingsComponents/Timer.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx index a9f9c195..fb70e055 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx @@ -45,7 +45,8 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) }) .catch(console.log) } else { - const score = Math.min(+event.target.value, maxScore) + // Sets score to event.target.value if it's between 0 and max + const score = Math.max(0, Math.min(+event.target.value, maxScore)) setScore(score) await axios .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, { diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index f8d871d0..086a64a4 100644 --- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx @@ -27,7 +27,8 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { /** If timer value is above the max value, set the timer value to max value to not overflow the server */ - const timerValue = Math.min(+event.target.value, maxTime) + // Sets score to event.target.value if it's between 0 and max + const timerValue = Math.max(0, Math.min(+event.target.value, maxTime)) if (activeSlide) { setTimer(timerValue) await axios -- GitLab