Skip to content
Snippets Groups Projects
Commit 80acc71a authored by Emil's avatar Emil
Browse files

fix: adaptive alternatives, feat: question settings

parent 4e4c3488
No related branches found
No related tags found
1 merge request!98fix: adaptive alternatives, feat: question settings
Pipeline #42631 passed with warnings
This commit is part of merge request !98. Comments created here will be created in the context of that merge request.
Showing
with 283 additions and 138 deletions
......@@ -97,7 +97,6 @@ export interface QuestionAlternativeComponent extends Component {
question_id: number
text: string
value: number
question_alternative_id: number
font: string
}
}
......@@ -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}`, {
data: { ...component, text: a },
data: { ...component, 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>
)
}
......
......@@ -59,18 +59,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%;
`
......@@ -93,10 +88,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.
Please register or to comment