import * as React from "react"; import { Container } from "reactstrap"; import { isUndefined } from "util"; import { InternalAnswerQuestion } from "./InternalAnswerQuestion"; import { InternalResults } from "./InternalResults"; import { InternalReviewQuestion } from "./InternalReviewQuestion"; import { InternalStart } from "./InternalStart"; import { Progress } from "./Progress"; import { IQuestionProps } from "./Question"; import { QuizData } from "./QuizData"; import { toArray } from "./utils"; export interface IQuizProps { title: React.ReactChild; seed?: number; randomOrder?: boolean; autoStart?: boolean; children: | Array> | React.ReactElement; } export class Quiz extends React.PureComponent { static defaultProps = { autoStart: false }; static is(value: any): value is Quiz { return React.isValidElement(value) && value.type === Quiz; } static parse(props: IQuizProps): QuizData { return QuizData.parse(props); } data: QuizData; constructor(props: IQuizProps) { super(props); this.data = Quiz.parse(this.props); } componentWillReceiveProps(props: IQuizProps) { this.data = Quiz.parse(props); } render() { return ; } } export interface IResult { score?: number; input?: any[]; correct?: boolean; explained?: boolean; step?: number; } export const isDone = (result: IResult) => isUndefined(result.explained) && isUndefined(result.correct); export const isCorrect = (result: IResult) => result.correct === true; export const isIncorrect = (result: IResult) => result.correct === false; export const isExplained = (result: IResult) => isUndefined(result.correct) && result.explained === true; export enum QuizState { start = "start", questions = "questions", results = "results", reviewing = "reviewing" } export interface IInternalQuizProps { quiz: QuizData; } export interface IInternalQuizState { state: QuizState; input: any[]; current: IResult; currentIndex: number; results: IResult[]; } export class InternalQuiz extends React.PureComponent< IInternalQuizProps, IInternalQuizState > { constructor(props: IInternalQuizProps) { super(props); const results = this.getInitialResults(); this.state = { state: this.props.quiz.autoStart ? QuizState.questions : QuizState.start, results, currentIndex: 0, input: [], current: results[0] }; } getResults(): IResult[] { return this.state.results; } getCorrect() { return this.getResults().reduce( (acc, result) => acc + (result.correct === true ? 1 : 0), 0 ); } getCount() { return this.getResults().length; } render() { return ( {this.renderState()} ); } private renderState() { switch (this.state.state) { case QuizState.start: { return ( <>
{this.props.quiz.descriptions}
); } case QuizState.questions: { const question = this.props.quiz.questions[this.state.currentIndex]; return ( <>
{this.props.quiz.descriptions}
); } case QuizState.results: { return ( <>
{this.props.quiz.descriptions}
); } case QuizState.reviewing: { const question = this.props.quiz.questions[this.state.currentIndex]; return ( <>
{this.props.quiz.descriptions}
); } } } private setCurrent = (currentIndex: number) => { const current = this.state.results[currentIndex]; if (current) { return this.setState({ currentIndex, current, input: [] }); } }; private getInitialResults(): IResult[] { return toArray(this.props.quiz.questions).map(() => ({})); } private update = (fn: (state: any[]) => any[]) => { this.setState({ input: fn(this.state.input) }); }; private onStart = () => { this.setState({ state: QuizState.questions }); }; private onExplain = () => { this.updateCurrent({ input: this.state.input || [], explained: true }); }; private onSubmit = (score: number) => { this.updateCurrent({ score, correct: score >= 0.5, input: this.state.input || [] }); }; private onNextStep = () => { const current = this.state.current, currentQuestion = this.props.quiz.questions[this.state.currentIndex], step = (current.step || 0) + 1; if ( step < (currentQuestion && currentQuestion.explaination ? currentQuestion.explaination.children.length : -1) ) { this.updateCurrent({ step }); } }; private updateCurrent = (partialCurrent: IResult) => { const current = { ...this.state.current, ...partialCurrent }, results = this.state.results.slice(); results[this.state.currentIndex] = current; this.setState({ current, results }); }; private findIndexNextUnfinished(index: number): number { for (let i = index, il = this.state.results.length; i < il; i++) { const result = this.state.results[i]; if (isDone(result)) { return i; } } if (index !== 0) { return this.findIndexNextUnfinished(0); } return -1; } private onNext = () => { const currentIndex = this.findIndexNextUnfinished(this.state.currentIndex), current = this.state.results[currentIndex]; if (current) { return this.setState({ currentIndex, current, input: [] }); } else { return this.setState({ currentIndex, input: [], state: QuizState.results }); } }; private onReview = () => { this.setState({ currentIndex: 0, input: [], state: QuizState.reviewing, current: this.state.results[0] }); }; private onReset = () => { const results = this.getInitialResults(); this.props.quiz.sortQuestions(); this.setState({ results, state: this.props.quiz.autoStart ? QuizState.questions : QuizState.start, currentIndex: 0, input: [], current: results[0] }); }; }