import { StateTypeReturnType } from '@edtr-io/plugin'
import { Feedback, styled, SubmitButton } from '@edtr-io/renderer-ui/internal'
import * as R from 'ramda'
import * as React from 'react'
import { ScMcExercisePluginConfig, ScMcExercisePluginState } from '.'
import { ScMcAnswersRenderer } from './answers-renderer'
import { ScMcExerciseChoiceRenderer } from './choice-renderer'
import { ScMcRendererProps } from './renderer'
enum ExerciseState {
Default = 1,
SolvedRight,
SolvedWrong,
}
export class ScMcRendererInteractive extends React.Component<
ScMcRendererInteractiveProps,
ScMcRendererState
> {
public static defaultProps = {
getFeedback: () => undefined,
}
public constructor(props: ScMcRendererInteractiveProps) {
super(props)
this.state = ScMcRendererInteractive.initialStateFromProps(props)
}
static getDerivedStateFromProps(
nextProps: ScMcRendererInteractiveProps,
prevState: ScMcRendererState
) {
if (nextProps.state.answers.length !== prevState.buttons.length) {
return ScMcRendererInteractive.initialStateFromProps(nextProps)
}
return {}
}
static initialStateFromProps(props: ScMcRendererInteractiveProps) {
return {
buttons: props.state.answers.map(() => {
return {
selected: false,
showFeedback: false,
}
}),
globalFeedback: '',
showGlobalFeedback: false,
solved: false,
exerciseState: ExerciseState.Default,
}
}
public render() {
return (
{this.showGlobalFeedback()}
)
}
private showAnswer = (
answer: StateTypeReturnType['answers'][0],
index: number,
centered: boolean
): React.ReactNode => {
const button = this.state.buttons[index]
return (
{answer.content.render()}
{this.props.showFeedback ? this.showFeedback({ button, answer }) : null}
)
}
private showFeedback({
answer,
button,
}: {
answer: StateTypeReturnType['answers'][0]
button: Button
}): React.ReactNode {
if (!button.showFeedback) {
return null
}
if (!this.props.isEmpty(answer.feedback.id)) {
return (
{answer.feedback.render()}
)
}
return (
{answer.isCorrect.value
? ''
: this.props.config.i18n.answer.fallbackFeedback.wrong}
)
}
private showGlobalFeedback(): React.ReactNode {
const { showGlobalFeedback, globalFeedback, solved } = this.state
if (showGlobalFeedback) {
return (
{globalFeedback}
)
}
return null
}
private handleWrongAnswer = () => {
setTimeout(
() => this.setState({ exerciseState: ExerciseState.Default }),
3000
)
return ExerciseState.SolvedWrong
}
private submitAnswer = () => {
const { buttons } = this.state
const temp = R.zip(buttons, this.props.state.answers)
const mistakes = R.reduce(
(acc, [button, answer]) => {
return acc + (answer.isCorrect.value !== button.selected ? 1 : 0)
},
0,
temp
)
const missingSolutions = R.reduce(
(acc, [button, answer]) => {
return acc + (answer.isCorrect.value && !button.selected ? 1 : 0)
},
0,
temp
)
const nextButtonStates = buttons.map((button, i) => {
return this.props.nextButtonStateAfterSubmit({
button,
answer: this.props.state.answers[i],
mistakes,
missingSolutions,
})
})
this.setState({
showGlobalFeedback: true,
buttons: nextButtonStates,
solved: mistakes === 0,
globalFeedback: this.getGlobalFeedback({ mistakes, missingSolutions }),
exerciseState:
mistakes === 0 ? ExerciseState.SolvedRight : this.handleWrongAnswer(),
})
}
private selectButton = (selectedIndex: number) => () => {
const { buttons } = this.state
if (this.props.state.isSingleChoice.value) {
this.setState({
buttons: buttons.map((button, index) => {
return R.assoc('selected', index === selectedIndex, button)
}),
})
} else {
this.setState({
buttons: R.adjust(
selectedIndex,
(button) => R.assoc('selected', !button.selected, button),
buttons
),
globalFeedback: '',
})
}
}
private getGlobalFeedback({
mistakes,
missingSolutions,
}: {
mistakes: number
missingSolutions: number
}): string {
const { getFeedback } = this.props
const feedback =
typeof getFeedback === 'function' &&
getFeedback({
mistakes,
missingSolutions,
})
if (feedback) {
return feedback
}
if (mistakes === 0) {
return this.props.config.i18n.globalFeedback.correct
} else {
return this.props.config.i18n.globalFeedback.wrong
}
}
private SubmitButton = styled.button({ float: 'right', margin: '10px 0px' })
}
export type ScMcRendererInteractiveProps = Omit & {
config: ScMcExercisePluginConfig
getFeedback?: (params: {
mistakes: number
missingSolutions: number
}) => string | undefined
nextButtonStateAfterSubmit: (params: {
button: Button
answer: StateTypeReturnType['answers'][0]
mistakes: number
missingSolutions: number
}) => Button
showFeedback?: boolean
}
export interface ScMcRendererState {
buttons: Button[]
globalFeedback: string
showGlobalFeedback: boolean
solved: boolean
exerciseState: ExerciseState
}
export interface Button {
selected: boolean
showFeedback: boolean
}