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 }