From b02b4102fc9168806b9b9fc8cbf5d644aa7a09ff Mon Sep 17 00:00:00 2001 From: Emil Wahlqvist <emiwa210@student.liu.se> Date: Tue, 27 Apr 2021 13:05:55 +0000 Subject: [PATCH] fix: adaptive alternatives, feat: question settings --- .../components/SlideSettings.tsx | 22 +++-- .../components/TextComponentEdit.tsx | 6 +- .../{ => slideSettingsComponents}/Images.tsx | 68 ++++++-------- .../slideSettingsComponents/Instructions.tsx | 73 +++++++++++++++ .../MultipleChoiceAlternatives.tsx} | 83 +++++++++-------- .../QuestionSettings.tsx | 90 +++++++++++++++++++ .../SlideType.tsx | 12 +-- .../{ => slideSettingsComponents}/Texts.tsx | 12 +-- .../{ => slideSettingsComponents}/Timer.tsx | 43 +++++---- .../presentationEditor/components/styled.tsx | 11 +-- 10 files changed, 283 insertions(+), 137 deletions(-) rename client/src/pages/presentationEditor/components/{ => slideSettingsComponents}/Images.tsx (67%) create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx rename client/src/pages/presentationEditor/components/{Alternatives.tsx => slideSettingsComponents/MultipleChoiceAlternatives.tsx} (59%) create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx rename client/src/pages/presentationEditor/components/{ => slideSettingsComponents}/SlideType.tsx (93%) rename client/src/pages/presentationEditor/components/{ => slideSettingsComponents}/Texts.tsx (79%) rename client/src/pages/presentationEditor/components/{ => slideSettingsComponents}/Timer.tsx (50%) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index d0b72e39..2f403f41 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -4,12 +4,14 @@ import { Divider, List, ListItem, ListItemText, TextField, Typography } from '@m import React, { useState } from 'react' import { useParams } from 'react-router-dom' import { useAppSelector } from '../../../hooks' -import Alternatives from './Alternatives' -import SlideType from './SlideType' +import Instructions from './slideSettingsComponents/Instructions' +import MultipleChoiceAlternatives from './slideSettingsComponents/MultipleChoiceAlternatives' +import SlideType from './slideSettingsComponents/SlideType' import { Center, ImportedImage, SettingsList, SlidePanel } from './styled' -import Timer from './Timer' -import Images from './Images' -import Texts from './Texts' +import Timer from './slideSettingsComponents/Timer' +import Images from './slideSettingsComponents/Images' +import Texts from './slideSettingsComponents/Texts' +import QuestionSettings from './slideSettingsComponents/QuestionSettings' interface CompetitionParams { id: string @@ -31,7 +33,15 @@ const SlideSettings: React.FC = () => { {activeSlide && <Timer activeSlide={activeSlide} competitionId={id} />} </SettingsList> - {activeSlide && <Alternatives activeSlide={activeSlide} competitionId={id} />} + {activeSlide?.questions[0] && <QuestionSettings activeSlide={activeSlide} competitionId={id} />} + { + // Choose answer alternatives depending on the slide type + } + {activeSlide?.questions[0]?.type_id === 1 && <Instructions activeSlide={activeSlide} competitionId={id} />} + {activeSlide?.questions[0]?.type_id === 2 && <Instructions activeSlide={activeSlide} competitionId={id} />} + {activeSlide?.questions[0]?.type_id === 3 && ( + <MultipleChoiceAlternatives activeSlide={activeSlide} competitionId={id} /> + )} {activeSlide && <Texts activeSlide={activeSlide} competitionId={id} />} diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx index 4aede1b5..8ef64b27 100644 --- a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx @@ -27,8 +27,8 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { setContent(component.text) }, []) - const handleSaveText = async (a: string) => { - setContent(a) + const handleSaveText = async (newText: string) => { + setContent(newText) if (timerHandle) { clearTimeout(timerHandle) setTimerHandle(undefined) @@ -38,7 +38,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { window.setTimeout(async () => { console.log('Content was updated on server. id: ', component.id) await axios.put(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, { - text: a, + text: newText, }) dispatch(getEditorCompetition(id)) }, 250) diff --git a/client/src/pages/presentationEditor/components/Images.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx similarity index 67% rename from client/src/pages/presentationEditor/components/Images.tsx rename to client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx index b715ba2c..0a0d1233 100644 --- a/client/src/pages/presentationEditor/components/Images.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx @@ -4,22 +4,12 @@ import { ListItem, ListItemText, Typography } from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' import React, { useState } from 'react' import { useDispatch } from 'react-redux' -import { - Center, - HiddenInput, - SettingsList, - AddImageButton, - ImportedImage, - WhiteBackground, - AddButton, - Clickable, - NoPadding, -} from './styled' +import { Center, HiddenInput, SettingsList, AddImageButton, ImportedImage, AddButton } from '../styled' import axios from 'axios' -import { getEditorCompetition } from '../../../actions/editor' -import { RichSlide } from '../../../interfaces/ApiRichModels' -import { ImageComponent, Media } from '../../../interfaces/ApiModels' -import { useAppSelector } from '../../../hooks' +import { getEditorCompetition } from '../../../../actions/editor' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { ImageComponent, Media } from '../../../../interfaces/ApiModels' +import { useAppSelector } from '../../../../hooks' type ImagesProps = { activeSlide: RichSlide @@ -99,32 +89,30 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => { return ( <SettingsList> - <WhiteBackground> - <ListItem divider> - <Center> - <ListItemText primary="Bilder" /> - </Center> - </ListItem> - {images && - images.map((image) => ( - <div key={image.id}> - <ListItem divider button> - <ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.filename}`} /> - <Center> - <ListItemText primary={image.filename} /> - </Center> - <CloseIcon onClick={() => handleCloseimageClick(image)} /> - </ListItem> - </div> - ))} + <ListItem divider> + <Center> + <ListItemText primary="Bilder" /> + </Center> + </ListItem> + {images && + images.map((image) => ( + <div key={image.id}> + <ListItem divider button> + <ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.filename}`} /> + <Center> + <ListItemText primary={image.filename} /> + </Center> + <CloseIcon onClick={() => handleCloseimageClick(image)} /> + </ListItem> + </div> + ))} - <ListItem button> - <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> - <AddImageButton htmlFor="contained-button-file"> - <AddButton variant="button">Lägg till bild</AddButton> - </AddImageButton> - </ListItem> - </WhiteBackground> + <ListItem button> + <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> + <AddImageButton htmlFor="contained-button-file"> + <AddButton variant="button">Lägg till bild</AddButton> + </AddImageButton> + </ListItem> </SettingsList> ) } diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx new file mode 100644 index 00000000..97b27c9d --- /dev/null +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx @@ -0,0 +1,73 @@ +import { ListItem, ListItemText, TextField, withStyles } from '@material-ui/core' +import axios from 'axios' +import React from 'react' +import { useDispatch } from 'react-redux' +import { getEditorCompetition } from '../../../../actions/editor' +import { useAppDispatch, useAppSelector } from '../../../../hooks' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { Center, SettingsList } from '../styled' + +type InstructionsProps = { + activeSlide: RichSlide + competitionId: string +} + +const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => { + const dispatch = useDispatch() + const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined) + const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) + + const updateInstructionsText = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { + /* TODO: Implement instructions field in question and add put API + if (timerHandle) { + clearTimeout(timerHandle) + setTimerHandle(undefined) + } + //Only updates 250ms after last input was made to not spam + setTimerHandle( + window.setTimeout(async () => { + console.log('Content was updated on server. id: ', activeSlide.questions[0].id) + if (activeSlide && activeSlide.questions[0]) { + await axios + .put( + `/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, + { + name: event.target.value, + } + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + }, 250) + ) + */ + } + + return ( + <SettingsList> + <ListItem divider> + <Center> + <ListItemText + primary="Rättningsinstruktioner" + secondary="Den här texten kommer endast att visas för domarna." + /> + </Center> + </ListItem> + <ListItem divider> + <Center> + <TextField + id="outlined-basic" + defaultValue={''} + onChange={updateInstructionsText} + variant="outlined" + fullWidth={true} + /> + </Center> + </ListItem> + </SettingsList> + ) +} + +export default Instructions diff --git a/client/src/pages/presentationEditor/components/Alternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx similarity index 59% rename from client/src/pages/presentationEditor/components/Alternatives.tsx rename to client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx index e699d003..58053995 100644 --- a/client/src/pages/presentationEditor/components/Alternatives.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx @@ -4,20 +4,19 @@ import { green, grey } from '@material-ui/core/colors' import CloseIcon from '@material-ui/icons/Close' import axios from 'axios' import React from 'react' -import { getEditorCompetition } from '../../../actions/editor' -import { useAppDispatch, useAppSelector } from '../../../hooks' -import { QuestionAlternative } from '../../../interfaces/ApiModels' -import { RichSlide } from '../../../interfaces/ApiRichModels' -import { AddButton, Center, Clickable, SettingsList, TextInput, WhiteBackground } from './styled' +import { getEditorCompetition } from '../../../../actions/editor' +import { useAppDispatch, useAppSelector } from '../../../../hooks' +import { QuestionAlternative } from '../../../../interfaces/ApiModels' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { AddButton, AlternativeTextField, Center, Clickable, SettingsList } from '../styled' -type AlternativeProps = { +type MultipleChoiceAlternativeProps = { activeSlide: RichSlide competitionId: string } -const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { +const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoiceAlternativeProps) => { const dispatch = useAppDispatch() - const competition = useAppSelector((state) => state.editor.competition) const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const GreenCheckbox = withStyles({ root: { @@ -95,42 +94,40 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { return ( <SettingsList> - <WhiteBackground> - <ListItem divider> - <Center> - <ListItemText - primary="Svarsalternativ" - secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)" - /> - </Center> - </ListItem> - {activeSlide && - activeSlide.questions[0] && - activeSlide.questions[0].alternatives && - activeSlide.questions[0].alternatives.map((alt) => ( - <div key={alt.id}> - <ListItem divider> - <TextInput - id="outlined-basic" - defaultValue={alt.text} - onChange={(event) => updateAlternativeText(alt.id, event.target.value)} - variant="outlined" - /> - <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} /> - <Clickable> - <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} /> - </Clickable> - </ListItem> - </div> - ))} - <ListItem button onClick={addAlternative}> - <Center> - <AddButton variant="button">Lägg till svarsalternativ</AddButton> - </Center> - </ListItem> - </WhiteBackground> + <ListItem divider> + <Center> + <ListItemText + primary="Svarsalternativ" + secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)" + /> + </Center> + </ListItem> + {activeSlide && + activeSlide.questions[0] && + activeSlide.questions[0].alternatives && + activeSlide.questions[0].alternatives.map((alt) => ( + <div key={alt.id}> + <ListItem divider> + <AlternativeTextField + id="outlined-basic" + defaultValue={alt.text} + onChange={(event) => updateAlternativeText(alt.id, event.target.value)} + variant="outlined" + /> + <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} /> + <Clickable> + <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} /> + </Clickable> + </ListItem> + </div> + ))} + <ListItem button onClick={addAlternative}> + <Center> + <AddButton variant="button">Lägg till svarsalternativ</AddButton> + </Center> + </ListItem> </SettingsList> ) } -export default Alternatives +export default MultipleChoiceAlternatives diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx new file mode 100644 index 00000000..62afda68 --- /dev/null +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx @@ -0,0 +1,90 @@ +import { ListItem, ListItemText, TextField } from '@material-ui/core' +import axios from 'axios' +import React, { useEffect, useState } from 'react' +import { useDispatch } from 'react-redux' +import { getEditorCompetition } from '../../../../actions/editor' +import { useAppDispatch } from '../../../../hooks' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { Center, SettingsList } from '../styled' + +type QuestionSettingsProps = { + activeSlide: RichSlide + competitionId: string +} + +const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps) => { + const dispatch = useDispatch() + + const updateQuestion = async ( + updateTitle: boolean, + event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement> + ) => { + console.log('Content was updated on server. id: ', activeSlide.questions[0].id) + if (activeSlide && activeSlide.questions[0]) { + if (updateTitle) { + await axios + .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, { + name: event.target.value, + }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .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, + }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + } + + const [score, setScore] = useState<number | undefined>(0) + useEffect(() => { + setScore(activeSlide?.questions[0]?.total_score) + }, [activeSlide]) + + return ( + <SettingsList> + <ListItem divider> + <Center> + <ListItemText primary="Frågeinställningar" secondary="" /> + </Center> + </ListItem> + <ListItem divider> + <Center> + <TextField + id="outlined-basic" + defaultValue={''} + label="Frågans titel" + onChange={(event) => updateQuestion(true, event)} + variant="outlined" + fullWidth={true} + /> + </Center> + </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} + onChange={(event) => updateQuestion(false, event)} + /> + </Center> + </ListItem> + </SettingsList> + ) +} + +export default QuestionSettings diff --git a/client/src/pages/presentationEditor/components/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx similarity index 93% rename from client/src/pages/presentationEditor/components/SlideType.tsx rename to client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx index ba104fe6..3194f7c9 100644 --- a/client/src/pages/presentationEditor/components/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx @@ -13,10 +13,10 @@ import { } from '@material-ui/core' import axios from 'axios' import React, { useState } from 'react' -import { getEditorCompetition } from '../../../actions/editor' -import { useAppDispatch } from '../../../hooks' -import { RichSlide } from '../../../interfaces/ApiRichModels' -import { Center, FormControlDropdown, SlideTypeInputLabel, WhiteBackground } from './styled' +import { getEditorCompetition } from '../../../../actions/editor' +import { useAppDispatch } from '../../../../hooks' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { Center, FormControlDropdown, SlideTypeInputLabel } from '../styled' type SlideTypeProps = { activeSlide: RichSlide @@ -85,7 +85,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { } } return ( - <WhiteBackground> + <ListItem> <FormControlDropdown variant="outlined"> <SlideTypeInputLabel>Sidtyp</SlideTypeInputLabel> <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> @@ -130,7 +130,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { </Button> </DialogActions> </Dialog> - </WhiteBackground> + </ListItem> ) } diff --git a/client/src/pages/presentationEditor/components/Texts.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx similarity index 79% rename from client/src/pages/presentationEditor/components/Texts.tsx rename to client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx index 31ecf57c..dd0b4084 100644 --- a/client/src/pages/presentationEditor/components/Texts.tsx +++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.tsx @@ -1,12 +1,12 @@ import { Divider, ListItem, ListItemText, Typography } from '@material-ui/core' import React from 'react' -import { useAppSelector } from '../../../hooks' -import { TextComponent } from '../../../interfaces/ApiModels' -import { RichSlide } from '../../../interfaces/ApiRichModels' -import { AddButton, Center, SettingsList, TextCard } from './styled' -import TextComponentEdit from './TextComponentEdit' +import { useAppSelector } from '../../../../hooks' +import { TextComponent } from '../../../../interfaces/ApiModels' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { AddButton, Center, SettingsList, TextCard } from '../styled' +import TextComponentEdit from '../TextComponentEdit' import axios from 'axios' -import { getEditorCompetition } from '../../../actions/editor' +import { getEditorCompetition } from '../../../../actions/editor' import { useDispatch } from 'react-redux' type TextsProps = { diff --git a/client/src/pages/presentationEditor/components/Timer.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx similarity index 50% rename from client/src/pages/presentationEditor/components/Timer.tsx rename to client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.tsx index 124635f4..91825662 100644 --- a/client/src/pages/presentationEditor/components/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 { getEditorCompetition } from '../../../actions/editor' -import { useAppDispatch } from '../../../hooks' -import { RichSlide } from '../../../interfaces/ApiRichModels' -import { Center, WhiteBackground } from './styled' +import { getEditorCompetition } from '../../../../actions/editor' +import { useAppDispatch } from '../../../../hooks' +import { RichSlide } from '../../../../interfaces/ApiRichModels' +import { Center } from '../styled' type TimerProps = { activeSlide: RichSlide @@ -24,29 +24,26 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { .catch(console.log) } } - const [timer, setTimer] = useState<number | undefined>(0) + const [timer, setTimer] = useState<number | undefined>(activeSlide?.timer) useEffect(() => { setTimer(activeSlide?.timer) }, [activeSlide]) return ( - <WhiteBackground> - <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" - defaultValue={activeSlide?.timer || 0} - onChange={updateTimer} - value={timer} - /> - </Center> - </ListItem> - </WhiteBackground> + <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} + /> + </Center> + </ListItem> ) } diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index f178642c..af2108d6 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -57,18 +57,13 @@ export const ToolbarPadding = styled.div` export const FormControlDropdown = styled(FormControl)` width: 100%; margin-top: 10px; - padding: 8px; - padding-left: 16px; - padding-right: 16px; ` export const SlideTypeInputLabel = styled(InputLabel)` width: 100%; - padding: 10px; - padding-left: 22px; ` -export const TextInput = styled(TextField)` +export const AlternativeTextField = styled(TextField)` width: 87%; ` @@ -91,10 +86,6 @@ export const SlidePanel = styled.div` width: 100%; ` -export const WhiteBackground = styled.div` - background: white; -` - export const AddButton = styled(Typography)` padding-left: 8px; padding-right: 8px; -- GitLab