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
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} />}
......
......@@ -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)
......
......@@ -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>
)
}
......
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'
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
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 {
} 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>
)
}
......
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 = {
......
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>
)
}
......
......@@ -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;
......
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