Skip to content
Snippets Groups Projects
Commit b02b4102 authored by Emil Wahlqvist's avatar Emil Wahlqvist
Browse files

fix: adaptive alternatives, feat: question settings

parent 21736a46
No related branches found
No related tags found
1 merge request!98fix: adaptive alternatives, feat: question settings
Pipeline #42665 passed with warnings
Showing
with 283 additions and 137 deletions
...@@ -4,12 +4,14 @@ import { Divider, List, ListItem, ListItemText, TextField, Typography } from '@m ...@@ -4,12 +4,14 @@ import { Divider, List, ListItem, ListItemText, TextField, Typography } from '@m
import React, { useState } from 'react' import React, { useState } from 'react'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { useAppSelector } from '../../../hooks' import { useAppSelector } from '../../../hooks'
import Alternatives from './Alternatives' import Instructions from './slideSettingsComponents/Instructions'
import SlideType from './SlideType' import MultipleChoiceAlternatives from './slideSettingsComponents/MultipleChoiceAlternatives'
import SlideType from './slideSettingsComponents/SlideType'
import { Center, ImportedImage, SettingsList, SlidePanel } from './styled' import { Center, ImportedImage, SettingsList, SlidePanel } from './styled'
import Timer from './Timer' import Timer from './slideSettingsComponents/Timer'
import Images from './Images' import Images from './slideSettingsComponents/Images'
import Texts from './Texts' import Texts from './slideSettingsComponents/Texts'
import QuestionSettings from './slideSettingsComponents/QuestionSettings'
interface CompetitionParams { interface CompetitionParams {
id: string id: string
...@@ -31,7 +33,15 @@ const SlideSettings: React.FC = () => { ...@@ -31,7 +33,15 @@ const SlideSettings: React.FC = () => {
{activeSlide && <Timer activeSlide={activeSlide} competitionId={id} />} {activeSlide && <Timer activeSlide={activeSlide} competitionId={id} />}
</SettingsList> </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} />} {activeSlide && <Texts activeSlide={activeSlide} competitionId={id} />}
......
...@@ -27,8 +27,8 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { ...@@ -27,8 +27,8 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => {
setContent(component.text) setContent(component.text)
}, []) }, [])
const handleSaveText = async (a: string) => { const handleSaveText = async (newText: string) => {
setContent(a) setContent(newText)
if (timerHandle) { if (timerHandle) {
clearTimeout(timerHandle) clearTimeout(timerHandle)
setTimerHandle(undefined) setTimerHandle(undefined)
...@@ -38,7 +38,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { ...@@ -38,7 +38,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => {
window.setTimeout(async () => { window.setTimeout(async () => {
console.log('Content was updated on server. id: ', component.id) console.log('Content was updated on server. id: ', component.id)
await axios.put(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, { await axios.put(`/api/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, {
text: a, text: newText,
}) })
dispatch(getEditorCompetition(id)) dispatch(getEditorCompetition(id))
}, 250) }, 250)
......
...@@ -4,22 +4,12 @@ import { ListItem, ListItemText, Typography } from '@material-ui/core' ...@@ -4,22 +4,12 @@ import { ListItem, ListItemText, Typography } from '@material-ui/core'
import CloseIcon from '@material-ui/icons/Close' import CloseIcon from '@material-ui/icons/Close'
import React, { useState } from 'react' import React, { useState } from 'react'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { import { Center, HiddenInput, SettingsList, AddImageButton, ImportedImage, AddButton } from '../styled'
Center,
HiddenInput,
SettingsList,
AddImageButton,
ImportedImage,
WhiteBackground,
AddButton,
Clickable,
NoPadding,
} from './styled'
import axios from 'axios' import axios from 'axios'
import { getEditorCompetition } from '../../../actions/editor' import { getEditorCompetition } from '../../../../actions/editor'
import { RichSlide } from '../../../interfaces/ApiRichModels' import { RichSlide } from '../../../../interfaces/ApiRichModels'
import { ImageComponent, Media } from '../../../interfaces/ApiModels' import { ImageComponent, Media } from '../../../../interfaces/ApiModels'
import { useAppSelector } from '../../../hooks' import { useAppSelector } from '../../../../hooks'
type ImagesProps = { type ImagesProps = {
activeSlide: RichSlide activeSlide: RichSlide
...@@ -99,32 +89,30 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => { ...@@ -99,32 +89,30 @@ const Images = ({ activeSlide, competitionId }: ImagesProps) => {
return ( return (
<SettingsList> <SettingsList>
<WhiteBackground> <ListItem divider>
<ListItem divider> <Center>
<Center> <ListItemText primary="Bilder" />
<ListItemText primary="Bilder" /> </Center>
</Center> </ListItem>
</ListItem> {images &&
{images && images.map((image) => (
images.map((image) => ( <div key={image.id}>
<div key={image.id}> <ListItem divider button>
<ListItem divider button> <ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.filename}`} />
<ImportedImage src={`http://localhost:5000/static/images/thumbnail_${image.filename}`} /> <Center>
<Center> <ListItemText primary={image.filename} />
<ListItemText primary={image.filename} /> </Center>
</Center> <CloseIcon onClick={() => handleCloseimageClick(image)} />
<CloseIcon onClick={() => handleCloseimageClick(image)} /> </ListItem>
</ListItem> </div>
</div> ))}
))}
<ListItem button> <ListItem button>
<HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} />
<AddImageButton htmlFor="contained-button-file"> <AddImageButton htmlFor="contained-button-file">
<AddButton variant="button">Lägg till bild</AddButton> <AddButton variant="button">Lägg till bild</AddButton>
</AddImageButton> </AddImageButton>
</ListItem> </ListItem>
</WhiteBackground>
</SettingsList> </SettingsList>
) )
} }
......
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
...@@ -4,20 +4,19 @@ import { green, grey } from '@material-ui/core/colors' ...@@ -4,20 +4,19 @@ import { green, grey } from '@material-ui/core/colors'
import CloseIcon from '@material-ui/icons/Close' import CloseIcon from '@material-ui/icons/Close'
import axios from 'axios' import axios from 'axios'
import React from 'react' import React from 'react'
import { getEditorCompetition } from '../../../actions/editor' import { getEditorCompetition } from '../../../../actions/editor'
import { useAppDispatch, useAppSelector } from '../../../hooks' import { useAppDispatch, useAppSelector } from '../../../../hooks'
import { QuestionAlternative } from '../../../interfaces/ApiModels' import { QuestionAlternative } from '../../../../interfaces/ApiModels'
import { RichSlide } from '../../../interfaces/ApiRichModels' import { RichSlide } from '../../../../interfaces/ApiRichModels'
import { AddButton, Center, Clickable, SettingsList, TextInput, WhiteBackground } from './styled' import { AddButton, AlternativeTextField, Center, Clickable, SettingsList } from '../styled'
type AlternativeProps = { type MultipleChoiceAlternativeProps = {
activeSlide: RichSlide activeSlide: RichSlide
competitionId: string competitionId: string
} }
const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoiceAlternativeProps) => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const competition = useAppSelector((state) => state.editor.competition)
const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
const GreenCheckbox = withStyles({ const GreenCheckbox = withStyles({
root: { root: {
...@@ -95,42 +94,40 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { ...@@ -95,42 +94,40 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => {
return ( return (
<SettingsList> <SettingsList>
<WhiteBackground> <ListItem divider>
<ListItem divider> <Center>
<Center> <ListItemText
<ListItemText primary="Svarsalternativ"
primary="Svarsalternativ" secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)"
secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)" />
/> </Center>
</Center> </ListItem>
</ListItem> {activeSlide &&
{activeSlide && activeSlide.questions[0] &&
activeSlide.questions[0] && activeSlide.questions[0].alternatives &&
activeSlide.questions[0].alternatives && activeSlide.questions[0].alternatives.map((alt) => (
activeSlide.questions[0].alternatives.map((alt) => ( <div key={alt.id}>
<div key={alt.id}> <ListItem divider>
<ListItem divider> <AlternativeTextField
<TextInput id="outlined-basic"
id="outlined-basic" defaultValue={alt.text}
defaultValue={alt.text} onChange={(event) => updateAlternativeText(alt.id, event.target.value)}
onChange={(event) => updateAlternativeText(alt.id, event.target.value)} variant="outlined"
variant="outlined" />
/> <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} />
<GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} /> <Clickable>
<Clickable> <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} />
<CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} /> </Clickable>
</Clickable> </ListItem>
</ListItem> </div>
</div> ))}
))} <ListItem button onClick={addAlternative}>
<ListItem button onClick={addAlternative}> <Center>
<Center> <AddButton variant="button">Lägg till svarsalternativ</AddButton>
<AddButton variant="button">Lägg till svarsalternativ</AddButton> </Center>
</Center> </ListItem>
</ListItem>
</WhiteBackground>
</SettingsList> </SettingsList>
) )
} }
export default Alternatives export default MultipleChoiceAlternatives
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
...@@ -13,10 +13,10 @@ import { ...@@ -13,10 +13,10 @@ import {
} from '@material-ui/core' } from '@material-ui/core'
import axios from 'axios' import axios from 'axios'
import React, { useState } from 'react' import React, { useState } from 'react'
import { getEditorCompetition } from '../../../actions/editor' import { getEditorCompetition } from '../../../../actions/editor'
import { useAppDispatch } from '../../../hooks' import { useAppDispatch } from '../../../../hooks'
import { RichSlide } from '../../../interfaces/ApiRichModels' import { RichSlide } from '../../../../interfaces/ApiRichModels'
import { Center, FormControlDropdown, SlideTypeInputLabel, WhiteBackground } from './styled' import { Center, FormControlDropdown, SlideTypeInputLabel } from '../styled'
type SlideTypeProps = { type SlideTypeProps = {
activeSlide: RichSlide activeSlide: RichSlide
...@@ -85,7 +85,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { ...@@ -85,7 +85,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
} }
} }
return ( return (
<WhiteBackground> <ListItem>
<FormControlDropdown variant="outlined"> <FormControlDropdown variant="outlined">
<SlideTypeInputLabel>Sidtyp</SlideTypeInputLabel> <SlideTypeInputLabel>Sidtyp</SlideTypeInputLabel>
<Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp">
...@@ -130,7 +130,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { ...@@ -130,7 +130,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</WhiteBackground> </ListItem>
) )
} }
......
import { Divider, ListItem, ListItemText, Typography } from '@material-ui/core' import { Divider, ListItem, ListItemText, Typography } from '@material-ui/core'
import React from 'react' import React from 'react'
import { useAppSelector } from '../../../hooks' import { useAppSelector } from '../../../../hooks'
import { TextComponent } from '../../../interfaces/ApiModels' import { TextComponent } from '../../../../interfaces/ApiModels'
import { RichSlide } from '../../../interfaces/ApiRichModels' import { RichSlide } from '../../../../interfaces/ApiRichModels'
import { AddButton, Center, SettingsList, TextCard } from './styled' import { AddButton, Center, SettingsList, TextCard } from '../styled'
import TextComponentEdit from './TextComponentEdit' import TextComponentEdit from '../TextComponentEdit'
import axios from 'axios' import axios from 'axios'
import { getEditorCompetition } from '../../../actions/editor' import { getEditorCompetition } from '../../../../actions/editor'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
type TextsProps = { type TextsProps = {
......
import { ListItem, TextField } from '@material-ui/core' import { ListItem, TextField } from '@material-ui/core'
import axios from 'axios' import axios from 'axios'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { getEditorCompetition } from '../../../actions/editor' import { getEditorCompetition } from '../../../../actions/editor'
import { useAppDispatch } from '../../../hooks' import { useAppDispatch } from '../../../../hooks'
import { RichSlide } from '../../../interfaces/ApiRichModels' import { RichSlide } from '../../../../interfaces/ApiRichModels'
import { Center, WhiteBackground } from './styled' import { Center } from '../styled'
type TimerProps = { type TimerProps = {
activeSlide: RichSlide activeSlide: RichSlide
...@@ -24,29 +24,26 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => { ...@@ -24,29 +24,26 @@ const Timer = ({ activeSlide, competitionId }: TimerProps) => {
.catch(console.log) .catch(console.log)
} }
} }
const [timer, setTimer] = useState<number | undefined>(0) const [timer, setTimer] = useState<number | undefined>(activeSlide?.timer)
useEffect(() => { useEffect(() => {
setTimer(activeSlide?.timer) setTimer(activeSlide?.timer)
}, [activeSlide]) }, [activeSlide])
return ( return (
<WhiteBackground> <ListItem>
<ListItem> <Center>
<Center> <TextField
<TextField id="standard-number"
id="standard-number" fullWidth={true}
fullWidth={true} variant="outlined"
variant="outlined" placeholder="Antal sekunder"
placeholder="Antal sekunder" helperText="Lämna blank för att inte använda timerfunktionen"
helperText="Lämna blank för att inte använda timerfunktionen" label="Timer"
label="Timer" type="number"
type="number" onChange={updateTimer}
defaultValue={activeSlide?.timer || 0} value={timer}
onChange={updateTimer} />
value={timer} </Center>
/> </ListItem>
</Center>
</ListItem>
</WhiteBackground>
) )
} }
......
...@@ -57,18 +57,13 @@ export const ToolbarPadding = styled.div` ...@@ -57,18 +57,13 @@ export const ToolbarPadding = styled.div`
export const FormControlDropdown = styled(FormControl)` export const FormControlDropdown = styled(FormControl)`
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
padding: 8px;
padding-left: 16px;
padding-right: 16px;
` `
export const SlideTypeInputLabel = styled(InputLabel)` export const SlideTypeInputLabel = styled(InputLabel)`
width: 100%; width: 100%;
padding: 10px;
padding-left: 22px;
` `
export const TextInput = styled(TextField)` export const AlternativeTextField = styled(TextField)`
width: 87%; width: 87%;
` `
...@@ -91,10 +86,6 @@ export const SlidePanel = styled.div` ...@@ -91,10 +86,6 @@ export const SlidePanel = styled.div`
width: 100%; width: 100%;
` `
export const WhiteBackground = styled.div`
background: white;
`
export const AddButton = styled(Typography)` export const AddButton = styled(Typography)`
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment