Skip to content

Commit 44b1b06

Browse files
authored
Merge pull request #1685 from ccnmtl/FOL-21-progress
Adds a progress tracker to the FOL multiple choice mode
2 parents 05d09be + 0a21dc4 commit 44b1b06

File tree

4 files changed

+100
-10
lines changed

4 files changed

+100
-10
lines changed

media/js/src/firstOrderLogic/firstOrderLogic.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, { useEffect, useState } from 'react';
22
import { shuffleArray, getRandomElement, GridItem, GridStatement,
3-
GridTemplate } from './utils';
3+
GridTemplate, Score } from './utils';
44
import { getTemplatesByDifficulty } from './statementGenerator';
55
import { Grid } from './grid';
66
import { Options } from './options';
77
import { StatementInput } from './statementInput';
8+
import { Progress } from './progress';
89

910
export const STATIC_URL = LogicLearner.staticUrl;
1011

@@ -21,6 +22,11 @@ export const FirstOrderLogic: React.FC = () => {
2122
const [isCorrect, setIsCorrect] = useState<boolean>(false);
2223
const [selected, setSelected] = useState<number|null>()
2324
const [mode, setMode] = useState<number>(0);
25+
const [isDone, setIsDone] = useState<boolean>(false);
26+
const [attempt, setAttempt] = useState<number>(4);
27+
28+
const scoreDefault = {easy:[], medium: [], hard: []}
29+
const [score, setScore] = useState<Score>(scoreDefault);
2430

2531
const [correctTemplate, setCorrectTemplate] =
2632
useState<GridTemplate>(getRandomElement(templateBank));
@@ -74,14 +80,31 @@ export const FirstOrderLogic: React.FC = () => {
7480
setMode(Number(e.target.value));
7581
}
7682

83+
const handleAttempt = (result:boolean) => {
84+
if (!isDone) {
85+
if (result) {
86+
setIsDone(true);
87+
setScore({...score, [difficulty]: [...(score[difficulty]), attempt]});
88+
} else {
89+
setAttempt(Math.max(attempt - 1, 1));
90+
}
91+
}
92+
};
93+
7794
const handleNewGrid = () => {
7895
let newArr = getRandomElement(templateBank)
7996
while (newArr === correctTemplate) {
8097
newArr = getRandomElement(templateBank)
8198
}
99+
if (!isDone) {
100+
setScore({...score,
101+
[difficulty]: [...(score[difficulty]), -attempt]});
102+
}
82103
setCorrectTemplate(newArr);
83104
setSelected(null);
84105
setText('');
106+
setAttempt(4);
107+
setIsDone(false);
85108
}
86109

87110
const mkSelect = (options, action) => <select className='form-select mt-2'
@@ -138,8 +161,19 @@ export const FirstOrderLogic: React.FC = () => {
138161
}
139162
}, [correctStatement]);
140163

164+
useEffect(() => {
165+
if (score != scoreDefault) {
166+
localStorage.setItem('fol', JSON.stringify({score, attempt}));
167+
}
168+
}, [score, attempt]);
169+
141170
useEffect(() => {
142171
setSelected(null);
172+
const store = JSON.parse(localStorage.getItem('fol'));
173+
if (store) {
174+
setAttempt(store.attempt ?? 4);
175+
setScore(store.score ?? scoreDefault);
176+
}
143177
}, []);
144178

145179
useEffect(() => {
@@ -158,10 +192,13 @@ export const FirstOrderLogic: React.FC = () => {
158192
{setting}
159193
</li>)}
160194
</ul>
195+
{mode === 0 &&
196+
<Progress difficulty={difficulty} score={score} />}
161197
</div>
162-
{mode === 0 && <Options options={options} correctIndex={correctIndex}
163-
isCorrect={isCorrect} setIsCorrect={setIsCorrect}
164-
selected={selected} setSelected={setSelected} />}
198+
{mode === 0 && <Options options={options}
199+
correctIndex={correctIndex} isCorrect={isCorrect}
200+
setIsCorrect={setIsCorrect} selected={selected}
201+
setSelected={setSelected} handleAttempt={handleAttempt}/>}
165202
{mode === 1 && <StatementInput isCorrect={isCorrect}
166203
correctStatement={correctStatement} difficulty={difficulty}
167204
setIsCorrect={setIsCorrect} text={text} setText={setText} />}

media/js/src/firstOrderLogic/options.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,34 @@ interface OptionProps {
88
setIsCorrect: Function
99
selected: number|null
1010
setSelected: React.Dispatch<React.SetStateAction<number|null>>
11+
handleAttempt: (isCorrect:boolean) => void
1112
}
1213

1314
export const Options: React.FC<OptionProps> = ({
14-
options, correctIndex, isCorrect, setIsCorrect, selected, setSelected
15+
options, correctIndex, isCorrect, setIsCorrect, selected, setSelected,
16+
handleAttempt
1517
}:OptionProps) => {
1618
const showResult = (i:number) => {
1719
if (selected != null && selected === i)
1820
if (isCorrect) return 'success'
1921
else return 'danger'
2022
else return 'outline-primary'
21-
}
23+
};
2224

2325
useEffect(() => {
24-
setIsCorrect(correctIndex === selected);
26+
const result = correctIndex === selected;
27+
setIsCorrect(result);
28+
if (selected != null) {
29+
handleAttempt(result);
30+
}
2531
}, [selected]);
2632

2733
return <section className='col-4'>
2834
<div className="row">
29-
{options.map((option, i) =>
35+
{options.map((option, i) =>
3036
<div className='my-1' key={i}>
31-
<button className={`btn btn-large w-100 my-1 btn-${showResult(i)}`}
32-
onClick={() => setSelected(i)}
37+
<button className={`btn btn-large w-100 my-1
38+
btn-${showResult(i)}`} onClick={() => setSelected(i)}
3339
>
3440
{selected === i &&
3541
<p>{option.naturalLanguageStatement}</p>}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Score } from './utils';
3+
4+
interface ProgressProps {
5+
difficulty: string
6+
score: Score,
7+
}
8+
9+
export const Progress: React.FC<ProgressProps> = ({
10+
difficulty,
11+
score
12+
}:ProgressProps) => {
13+
/**
14+
* EVAN'S NOTE: This was made for my own convenience I imagine you might want
15+
* to be more explicit with your expression of the progress shapes.
16+
* @returns An array of SVG text elements corresponding to the different
17+
* progress states
18+
*/
19+
const mkArr = (length: number, sign:1|-1) => Array.from({length}, (_, i) =>
20+
<text color="black" x='50%' y='55%' dominantBaseline='middle'
21+
textAnchor='middle' fontSize={10}
22+
>
23+
{sign * (i+1)}
24+
</text>
25+
);
26+
const success = mkArr(4, 1);
27+
const skip = mkArr(4, -1);
28+
29+
return <svg
30+
viewBox={`0 0 120 ${10 * Math.ceil(score[difficulty].length/12)}`}
31+
width={'100%'}
32+
>
33+
{// UNCOMMENT WHEN YOU ARE READY TO WORK ON THE PROGRESS DISPLAY
34+
/* {score[difficulty].map((val, i) => <svg x={10 * (i % 12)}
35+
y={10 * Math.floor(i/12)} width={10} height={10} key={i}
36+
>
37+
{val > 0 ? success[val-1]: skip[-val-1]}
38+
</svg>
39+
)} */}
40+
</svg>
41+
}

media/js/src/firstOrderLogic/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export interface GridTemplate {
2121
checkIfViolates: Function
2222
}
2323

24+
export interface Score {
25+
easy: number[]
26+
medium: number[]
27+
hard: number[]
28+
}
29+
2430
// Map hex color codes to color names for convenience
2531
const colorMap = {
2632
'#77dd77': 'Green',

0 commit comments

Comments
 (0)