Add new lives mode and more test options!
Users can now choose from two modes - lives and questions. Lives mode will end when the user makes the chosen number of mistakes. Questions mode will end when the user answers all questions correctly from a list of questions the length of which the user chooses.
This commit is contained in:
78
src/ClassicTestStart.js
Normal file
78
src/ClassicTestStart.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { CloseRounded as CloseRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon } from "@material-ui/icons";
|
||||||
|
import Button from "./Button";
|
||||||
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
|
|
||||||
|
import Slider from "rc-slider";
|
||||||
|
import "rc-slider/assets/index.css";
|
||||||
|
|
||||||
|
export default function ClassicTestStart(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overlay" onClick={props.hide}></div>
|
||||||
|
<div className="overlay-content slider-overlay-content">
|
||||||
|
<h1>Number of Questions</h1>
|
||||||
|
|
||||||
|
<div className="slider-container">
|
||||||
|
<Slider
|
||||||
|
min={1}
|
||||||
|
max={props.max}
|
||||||
|
value={props.sliderValue}
|
||||||
|
onChange={props.onSliderChange}
|
||||||
|
railStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--text-color")
|
||||||
|
.trim(),
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
handleStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--primary-color")
|
||||||
|
.trim(),
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
trackStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--primary-color-tinted")
|
||||||
|
.trim(),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
onKeyDown={(event) => !isNaN(Number(event.key))}
|
||||||
|
onChange={(event) => props.onSliderChange(Number(event.target.value))}
|
||||||
|
value={props.sliderValue}
|
||||||
|
min={1}
|
||||||
|
max={props.max}
|
||||||
|
size="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<Checkbox
|
||||||
|
checked={props.switchLanguage}
|
||||||
|
onChange={props.handleSwitchLanguageChange}
|
||||||
|
inputProps={{ 'aria-label': 'checkbox' }}
|
||||||
|
/>
|
||||||
|
<span>Swap terms & definitions</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => props.startTest("questions")}
|
||||||
|
icon={<ArrowForwardRoundedIcon />}
|
||||||
|
className="button--round continue-button"
|
||||||
|
loading={props.loading}
|
||||||
|
></Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={props.hide}
|
||||||
|
icon={<CloseRoundedIcon />}
|
||||||
|
className="button--no-background popup-close-button"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
src/CountdownTestStart.js
Normal file
16
src/CountdownTestStart.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function CountdownTestStart(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overlay" onClick={props.hideTestStart}></div>
|
||||||
|
<div className="overlay-content options-list-overlay-content">
|
||||||
|
Awaiting content
|
||||||
|
|
||||||
|
<div onClick={props.hideTestStart}>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
78
src/LivesTestStart.js
Normal file
78
src/LivesTestStart.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { CloseRounded as CloseRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon } from "@material-ui/icons";
|
||||||
|
import Button from "./Button";
|
||||||
|
import Checkbox from '@material-ui/core/Checkbox';
|
||||||
|
|
||||||
|
import Slider from "rc-slider";
|
||||||
|
import "rc-slider/assets/index.css";
|
||||||
|
|
||||||
|
export default function LivesTestStart(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overlay" onClick={props.hide}></div>
|
||||||
|
<div className="overlay-content slider-overlay-content">
|
||||||
|
<h1>Number of Lives</h1>
|
||||||
|
|
||||||
|
<div className="slider-container">
|
||||||
|
<Slider
|
||||||
|
min={1}
|
||||||
|
max={props.max}
|
||||||
|
value={props.sliderValue}
|
||||||
|
onChange={props.onSliderChange}
|
||||||
|
railStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--text-color")
|
||||||
|
.trim(),
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
handleStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--primary-color")
|
||||||
|
.trim(),
|
||||||
|
border: 0,
|
||||||
|
}}
|
||||||
|
trackStyle={{
|
||||||
|
backgroundColor: getComputedStyle(
|
||||||
|
document.querySelector("#root > div")
|
||||||
|
).getPropertyValue("--primary-color-tinted")
|
||||||
|
.trim(),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
onKeyDown={(event) => !isNaN(Number(event.key))}
|
||||||
|
onChange={(event) => props.onSliderChange(Number(event.target.value))}
|
||||||
|
value={props.sliderValue}
|
||||||
|
min={1}
|
||||||
|
max={props.max}
|
||||||
|
size="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<Checkbox
|
||||||
|
checked={props.switchLanguage}
|
||||||
|
onChange={props.handleSwitchLanguageChange}
|
||||||
|
inputProps={{ 'aria-label': 'checkbox' }}
|
||||||
|
/>
|
||||||
|
<span>Swap terms & definitions</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => props.startTest("lives")}
|
||||||
|
icon={<ArrowForwardRoundedIcon />}
|
||||||
|
className="button--round continue-button"
|
||||||
|
loading={props.loading}
|
||||||
|
></Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={props.hide}
|
||||||
|
icon={<CloseRoundedIcon />}
|
||||||
|
className="button--no-background popup-close-button"
|
||||||
|
></Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,9 +12,16 @@ import "firebase/functions";
|
|||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import LinkButton from './LinkButton';
|
import LinkButton from './LinkButton';
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
|
import TestStart from './TestStart';
|
||||||
|
import ClassicTestStart from './ClassicTestStart';
|
||||||
|
import LivesTestStart from './LivesTestStart';
|
||||||
|
import CountdownTestStart from './CountdownTestStart';
|
||||||
import "./css/Form.css";
|
import "./css/Form.css";
|
||||||
import "./css/History.css";
|
import "./css/History.css";
|
||||||
import "./css/LoggedInHome.css";
|
import "./css/LoggedInHome.css";
|
||||||
|
import "./css/PopUp.css";
|
||||||
|
import "./css/OptionsListOverlay.css";
|
||||||
|
import "./css/SliderOverlay.css";
|
||||||
|
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
|
|
||||||
@@ -54,6 +61,12 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
progressHistoryIncomplete: [],
|
progressHistoryIncomplete: [],
|
||||||
|
showTestStart: false,
|
||||||
|
showClassicTestStart: false,
|
||||||
|
showLivesTestStart: false,
|
||||||
|
sliderValue: 1,
|
||||||
|
switchLanguage: false,
|
||||||
|
totalTestQuestions: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -177,15 +190,15 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startTest = () => {
|
startTest = (mode) => {
|
||||||
if (this.state.canStartTest) {
|
if (this.state.canStartTest) {
|
||||||
const selections = Object.keys(this.state.selections)
|
const selections = Object.keys(this.state.selections)
|
||||||
.filter(x => this.state.selections[x]);
|
.filter(x => this.state.selections[x]);
|
||||||
this.state.functions.createProgress({
|
this.state.functions.createProgress({
|
||||||
sets: selections,
|
sets: selections,
|
||||||
switch_language: false,
|
switch_language: this.state.switchLanguage,
|
||||||
mode: "questions",
|
mode: mode,
|
||||||
limit: 1000,
|
limit: this.state.sliderValue,
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
const progressId = result.data;
|
const progressId = result.data;
|
||||||
this.stopLoading();
|
this.stopLoading();
|
||||||
@@ -231,6 +244,94 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showTestStart = () => {
|
||||||
|
if (this.state.canStartTest) {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: true,
|
||||||
|
totalTestQuestions: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showIndividualTestPrompt = async (mode) => {
|
||||||
|
if (!this.state.loading) {
|
||||||
|
if (mode === "classic") {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
})
|
||||||
|
const setIds = Object.keys(this.state.selections)
|
||||||
|
.filter(x => this.state.selections[x]);
|
||||||
|
|
||||||
|
const totalTestQuestions = (await Promise.all(setIds.map((setId) =>
|
||||||
|
this.state.db.collection("sets")
|
||||||
|
.doc(setId)
|
||||||
|
.collection("vocab")
|
||||||
|
.get()
|
||||||
|
.then(querySnapshot => querySnapshot.docs.length)
|
||||||
|
))).reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
showClassicTestStart: true,
|
||||||
|
sliderValue: totalTestQuestions,
|
||||||
|
switchLanguage: false,
|
||||||
|
totalTestQuestions: totalTestQuestions,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else if (mode === "lives") {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
showLivesTestStart: true,
|
||||||
|
switchLanguage: false,
|
||||||
|
sliderValue: 5,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// countdown
|
||||||
|
// this.setState({
|
||||||
|
// showTestStart: false,
|
||||||
|
// showCountdownTestStart: true,
|
||||||
|
// switchLanguage: false,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideClassicTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showClassicTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLivesTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showLivesTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideCountdownTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showCountdownTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSliderValue = (value) => {
|
||||||
|
if (value >= 1 && value <= 999) this.setState({
|
||||||
|
sliderValue: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchLanguageChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
switchLanguage: event.target.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -241,8 +342,7 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
<h1>Study</h1>
|
<h1>Study</h1>
|
||||||
<div className="button-container buttons--desktop">
|
<div className="button-container buttons--desktop">
|
||||||
<Button
|
<Button
|
||||||
onClick={this.startTest}
|
onClick={this.showTestStart}
|
||||||
loading={this.state.loading}
|
|
||||||
disabled={!this.state.canStartTest}
|
disabled={!this.state.canStartTest}
|
||||||
>
|
>
|
||||||
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
||||||
@@ -264,17 +364,39 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<LinkButton id="create-set-button-mobile" to="/create-set" icon={<AddRoundedIcon />} className="button--round buttons--mobile"></LinkButton>
|
<LinkButton id="create-set-button-mobile" to="/create-set" icon={<AddRoundedIcon />} className="button--round buttons--mobile"></LinkButton>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="page-header page-header--left buttons--mobile">
|
||||||
|
<Button
|
||||||
|
onClick={this.showTestStart}
|
||||||
|
disabled={!this.state.canStartTest}
|
||||||
|
>
|
||||||
|
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
||||||
|
</Button>
|
||||||
|
<LinkButton
|
||||||
|
to="/groups"
|
||||||
|
>
|
||||||
|
Groups
|
||||||
|
</LinkButton>
|
||||||
|
{
|
||||||
|
this.state.userSets && this.state.userSets.length > 0 &&
|
||||||
|
<LinkButton
|
||||||
|
to="/my-sets"
|
||||||
|
>
|
||||||
|
My Sets
|
||||||
|
</LinkButton>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
this.state.progressHistoryIncomplete.length > 0 &&
|
this.state.progressHistoryIncomplete.length > 0 &&
|
||||||
<>
|
<>
|
||||||
<h2>Incomplete Tests</h2>
|
<h2>Incomplete Tests</h2>
|
||||||
<div className="progress-history-container">
|
<div className="progress-history-container progress-history-container--incomplete">
|
||||||
<div>
|
<div>
|
||||||
<h3>Set</h3>
|
<h3>Set</h3>
|
||||||
<h3>Progress</h3>
|
<h3>Progress</h3>
|
||||||
<h3>Mark</h3>
|
<h3>Mark</h3>
|
||||||
<h3>Grade</h3>
|
<h3>Grade</h3>
|
||||||
<h3>Mode</h3>
|
<h3>Mode</h3>
|
||||||
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.state.progressHistoryIncomplete.map((progressItem) =>
|
this.state.progressHistoryIncomplete.map((progressItem) =>
|
||||||
@@ -313,28 +435,6 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
<div className="page-header page-header--left buttons--mobile">
|
|
||||||
<Button
|
|
||||||
onClick={this.startTest}
|
|
||||||
loading={this.state.loading}
|
|
||||||
disabled={!this.state.canStartTest}
|
|
||||||
>
|
|
||||||
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
|
||||||
</Button>
|
|
||||||
<LinkButton
|
|
||||||
to="/groups"
|
|
||||||
>
|
|
||||||
Groups
|
|
||||||
</LinkButton>
|
|
||||||
{
|
|
||||||
this.state.userSets && this.state.userSets.length > 0 &&
|
|
||||||
<LinkButton
|
|
||||||
to="/my-sets"
|
|
||||||
>
|
|
||||||
My Sets
|
|
||||||
</LinkButton>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<p id="page-intro" className="page-intro">
|
<p id="page-intro" className="page-intro">
|
||||||
{
|
{
|
||||||
this.state.userSets && this.state.userSets.length > 0
|
this.state.userSets && this.state.userSets.length > 0
|
||||||
@@ -440,6 +540,48 @@ export default withRouter(class LoggedInHome extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.showTestStart &&
|
||||||
|
<TestStart
|
||||||
|
hideTestStart={this.hideTestStart}
|
||||||
|
showIndividualTestPrompt={this.showIndividualTestPrompt}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showClassicTestStart &&
|
||||||
|
<ClassicTestStart
|
||||||
|
hide={this.hideClassicTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
max={this.state.totalTestQuestions}
|
||||||
|
sliderValue={this.state.sliderValue}
|
||||||
|
onSliderChange={this.changeSliderValue}
|
||||||
|
switchLanguage={this.state.switchLanguage}
|
||||||
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showLivesTestStart &&
|
||||||
|
<LivesTestStart
|
||||||
|
hide={this.hideLivesTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
max={20}
|
||||||
|
sliderValue={this.state.sliderValue}
|
||||||
|
onSliderChange={this.changeSliderValue}
|
||||||
|
switchLanguage={this.state.switchLanguage}
|
||||||
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showCountdownTestStart &&
|
||||||
|
<CountdownTestStart
|
||||||
|
hide={this.hideCountdownTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withRouter } from "react-router-dom";
|
import { withRouter } from "react-router-dom";
|
||||||
import { HomeRounded as HomeRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, SettingsRounded as SettingsRoundedIcon, CloseRounded as CloseRoundedIcon } from "@material-ui/icons";
|
import { HomeRounded as HomeRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, SettingsRounded as SettingsRoundedIcon, CloseRounded as CloseRoundedIcon, PeopleRounded as PeopleRoundedIcon, QuestionAnswerRounded as QuestionAnswerRoundedIcon } from "@material-ui/icons";
|
||||||
import NavBar from "./NavBar";
|
import NavBar from "./NavBar";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import LinkButton from "./LinkButton";
|
import LinkButton from "./LinkButton";
|
||||||
@@ -66,6 +66,9 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
attemptHistory: {},
|
attemptHistory: {},
|
||||||
questions: [],
|
questions: [],
|
||||||
originalTotalQuestions: 1,
|
originalTotalQuestions: 1,
|
||||||
|
lives: 1,
|
||||||
|
startLives: null,
|
||||||
|
setComplete: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -102,10 +105,12 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
nextPrompt: null,
|
nextPrompt: null,
|
||||||
setIds: data.setIds,
|
setIds: data.setIds,
|
||||||
originalTotalQuestions: [...new Set(data.questions)].length,
|
originalTotalQuestions: [...new Set(data.questions)].length,
|
||||||
|
setComplete: data.duration !== null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.lives) {
|
if (data.lives) {
|
||||||
newState.lives = data.lives;
|
newState.lives = data.lives;
|
||||||
|
newState.startLives = data.start_lives;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ newState, data.duration !== null, data.incorrect, data.duration ];
|
return [ newState, data.duration !== null, data.incorrect, data.duration ];
|
||||||
@@ -152,7 +157,7 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
.get()
|
.get()
|
||||||
.then((querySnapshot) => {
|
.then((querySnapshot) => {
|
||||||
newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
|
newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
|
||||||
if (newState.attemptNumber > 1)
|
if (querySnapshot.docs.length > 1)
|
||||||
newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
|
newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
x: new Date(doc.data().start_time),
|
x: new Date(doc.data().start_time),
|
||||||
@@ -309,7 +314,9 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.correct && !data.moreAnswers && this.state.incorrectAnswers[data.currentVocabId]) {
|
if (this.state.mode === "lives") newState.lives = data.lives;
|
||||||
|
|
||||||
|
if ((data.correct || data.lives === 0) && !data.moreAnswers && this.state.incorrectAnswers[data.currentVocabId]) {
|
||||||
// all answers to question given correctly
|
// all answers to question given correctly
|
||||||
// answer was previously wrong
|
// answer was previously wrong
|
||||||
// store correct answer
|
// store correct answer
|
||||||
@@ -318,10 +325,11 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
} else if (!data.correct) {
|
} else if (!data.correct) {
|
||||||
// incorrect answer given
|
// incorrect answer given
|
||||||
// store prompt and count=0
|
// store prompt and count=0
|
||||||
|
// store answer if in lives mode and no lives left
|
||||||
newState.incorrectAnswers = this.state.incorrectAnswers;
|
newState.incorrectAnswers = this.state.incorrectAnswers;
|
||||||
newState.incorrectAnswers[data.currentVocabId] = {
|
newState.incorrectAnswers[data.currentVocabId] = {
|
||||||
prompt: this.state.currentPrompt,
|
prompt: this.state.currentPrompt,
|
||||||
answer: "",
|
answer: data.lives === 0 ? data.correctAnswers : "",
|
||||||
count: 0,
|
count: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -343,7 +351,7 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
.get()
|
.get()
|
||||||
.then((querySnapshot) => {
|
.then((querySnapshot) => {
|
||||||
newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
|
newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
|
||||||
if (newState.attemptNumber > 1)
|
if (querySnapshot.docs.length > 1)
|
||||||
newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
|
newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
|
||||||
.map((doc) => ({
|
.map((doc) => ({
|
||||||
x: new Date(doc.data().start_time),
|
x: new Date(doc.data().start_time),
|
||||||
@@ -360,7 +368,7 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data.incorrectAnswers.map((vocabId) => {
|
data.incorrectAnswers.map((vocabId) => {
|
||||||
if (newState.incorrectAnswers[vocabId]) {
|
if (newState.incorrectAnswers[vocabId] && newState.incorrectAnswers[vocabId].answer !== "") {
|
||||||
// already been logged including prompt and correct answer
|
// already been logged including prompt and correct answer
|
||||||
newState.incorrectAnswers[vocabId].count++;
|
newState.incorrectAnswers[vocabId].count++;
|
||||||
} else {
|
} else {
|
||||||
@@ -431,13 +439,14 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!this.state.moreAnswers) {
|
if (!this.state.moreAnswers) {
|
||||||
|
if (this.state.nextPrompt === null) newState.setComplete = true;
|
||||||
newState.currentCorrect = [];
|
newState.currentCorrect = [];
|
||||||
newState.currentPrompt = this.state.nextPrompt;
|
newState.currentPrompt = this.state.nextPrompt;
|
||||||
newState.currentSound = this.state.nextSound;
|
newState.currentSound = this.state.nextSound;
|
||||||
newState.currentSetOwner = this.state.nextSetOwner;
|
newState.currentSetOwner = this.state.nextSetOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(newState, () => (this.isMounted) && this.answerInput.focus());
|
this.setState(newState, () => (this.isMounted && !this.state.setComplete) && this.answerInput.focus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,7 +473,7 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
<>
|
<>
|
||||||
<NavBar items={this.state.navbarItems} />
|
<NavBar items={this.state.navbarItems} />
|
||||||
{
|
{
|
||||||
this.state.currentAnswerStatus === null
|
this.state.currentAnswerStatus === null && !this.state.setComplete
|
||||||
?
|
?
|
||||||
<main className="progress-container">
|
<main className="progress-container">
|
||||||
<div>
|
<div>
|
||||||
@@ -512,11 +521,24 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
:
|
:
|
||||||
this.state.nextPrompt === null && !this.state.moreAnswers
|
this.state.nextPrompt === null && !this.state.moreAnswers && this.state.setComplete
|
||||||
?
|
?
|
||||||
<main>
|
<main>
|
||||||
{/* DONE */}
|
{/* DONE */}
|
||||||
<h1>{this.state.setTitle}</h1>
|
<div className="page-header">
|
||||||
|
<h1>
|
||||||
|
{this.state.setTitle}
|
||||||
|
<span className="title-icon">
|
||||||
|
{
|
||||||
|
this.state.mode === "questions"
|
||||||
|
?
|
||||||
|
<QuestionAnswerRoundedIcon />
|
||||||
|
:
|
||||||
|
<PeopleRoundedIcon />
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
<div className="progress-stat-row-container">
|
<div className="progress-stat-row-container">
|
||||||
<div className="stat-row stat-row--inline">
|
<div className="stat-row stat-row--inline">
|
||||||
<p>You got</p>
|
<p>You got</p>
|
||||||
@@ -534,6 +556,14 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
<p>Attempt #</p>
|
<p>Attempt #</p>
|
||||||
<h1>{this.state.attemptNumber}</h1>
|
<h1>{this.state.attemptNumber}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
this.state.startLives &&
|
||||||
|
<div className="stat-row stat-row--inline">
|
||||||
|
<p>with</p>
|
||||||
|
<h1>{this.state.startLives}</h1>
|
||||||
|
<p>lives</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.state.incorrectAnswers && Object.keys(this.state.incorrectAnswers).length > 0 &&
|
this.state.incorrectAnswers && Object.keys(this.state.incorrectAnswers).length > 0 &&
|
||||||
@@ -560,7 +590,7 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
{this.state.attemptNumber > 1 &&
|
{Object.keys(this.state.attemptHistory).length > 1 &&
|
||||||
<>
|
<>
|
||||||
<h2 className="chart-title">History</h2>
|
<h2 className="chart-title">History</h2>
|
||||||
<LineChart data={this.state.attemptHistory} />
|
<LineChart data={this.state.attemptHistory} />
|
||||||
@@ -625,14 +655,15 @@ export default withRouter(class Progress extends React.Component {
|
|||||||
</main>
|
</main>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.currentAnswerStatus === null ||
|
!this.state.setComplete &&
|
||||||
!(this.state.nextPrompt === null && !this.state.moreAnswers)) &&
|
|
||||||
<ProgressStats
|
<ProgressStats
|
||||||
correct={this.state.correct}
|
correct={this.state.correct}
|
||||||
incorrect={this.state.incorrect}
|
incorrect={this.state.incorrect}
|
||||||
totalVocabItems={this.state.originalTotalQuestions}
|
progressNumerator={this.state.mode === "lives" ? this.state.lives : this.state.correct}
|
||||||
|
progressDenominator={this.state.mode === "lives" ? this.state.startLives : this.state.originalTotalQuestions}
|
||||||
progress={this.state.progress}
|
progress={this.state.progress}
|
||||||
grade={this.state.totalQuestions > 0 ? this.state.correct / this.state.totalQuestions * 100 : 0}
|
grade={(this.state.correct + this.state.incorrect) > 0 ? this.state.correct / (this.state.correct + this.state.incorrect) * 100 : 0}
|
||||||
|
maxQuestions={this.state.mode === "lives" ? this.state.originalTotalQuestions : null}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -10,7 +10,17 @@ export default function ProgressStats(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="stat-row stat-row--inline">
|
<div className="stat-row stat-row--inline">
|
||||||
<h1>{props.correct}</h1>
|
<h1>{props.correct}</h1>
|
||||||
<p>correct</p>
|
{
|
||||||
|
props.maxQuestions
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<p>correct of</p>
|
||||||
|
<h1>{props.maxQuestions}</h1>
|
||||||
|
<p>possible questions</p>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<p>correct</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-row stat-row--inline">
|
<div className="stat-row stat-row--inline">
|
||||||
<h1>{props.incorrect}</h1>
|
<h1>{props.incorrect}</h1>
|
||||||
@@ -22,8 +32,8 @@ export default function ProgressStats(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="progress-bar">
|
<div className="progress-bar">
|
||||||
<div style={{ width: props.correct > 0 && props.totalVocabItems > 0 ? `${(props.correct / props.totalVocabItems * 100)}%` : "0" }}>
|
<div style={{ width: props.progressNumerator > 0 && props.progressDenominator > 0 ? `${(props.progressNumerator / props.progressDenominator * 100)}%` : "0" }}>
|
||||||
<p>{props.correct}/{props.totalVocabItems}</p>
|
<p>{props.progressNumerator}/{props.progressDenominator}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
152
src/SetPage.js
152
src/SetPage.js
@@ -6,6 +6,10 @@ import Button from "./Button";
|
|||||||
import LinkButton from "./LinkButton";
|
import LinkButton from "./LinkButton";
|
||||||
import Error404 from "./Error404";
|
import Error404 from "./Error404";
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
|
import TestStart from './TestStart';
|
||||||
|
import ClassicTestStart from './ClassicTestStart';
|
||||||
|
import LivesTestStart from './LivesTestStart';
|
||||||
|
import CountdownTestStart from './CountdownTestStart';
|
||||||
|
|
||||||
import "./css/PopUp.css";
|
import "./css/PopUp.css";
|
||||||
import "./css/SetPage.css";
|
import "./css/SetPage.css";
|
||||||
@@ -43,6 +47,12 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
groups: {},
|
groups: {},
|
||||||
currentSetGroups: [],
|
currentSetGroups: [],
|
||||||
showDeleteConfirmation: false,
|
showDeleteConfirmation: false,
|
||||||
|
showTestStart: false,
|
||||||
|
showClassicTestStart: false,
|
||||||
|
showLivesTestStart: false,
|
||||||
|
sliderValue: 1,
|
||||||
|
switchLanguage: false,
|
||||||
|
totalTestQuestions: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
@@ -113,13 +123,13 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startTest = () => {
|
startTest = (mode) => {
|
||||||
if (this.state.canStartTest) {
|
if (this.state.canStartTest) {
|
||||||
this.state.functions.createProgress({
|
this.state.functions.createProgress({
|
||||||
sets: [this.props.match.params.setId],
|
sets: [this.props.match.params.setId],
|
||||||
switch_language: false,
|
switch_language: this.state.switchLanguage,
|
||||||
mode: "questions",
|
mode: mode,
|
||||||
limit: 1000,
|
limit: this.state.sliderValue,
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
const progressId = result.data;
|
const progressId = result.data;
|
||||||
this.stopLoading();
|
this.stopLoading();
|
||||||
@@ -233,6 +243,94 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showTestStart = () => {
|
||||||
|
if (this.state.canStartTest) {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: true,
|
||||||
|
totalTestQuestions: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showIndividualTestPrompt = async (mode) => {
|
||||||
|
if (!this.state.loading) {
|
||||||
|
if (mode === "classic") {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
})
|
||||||
|
const setIds = Object.keys(this.state.selections)
|
||||||
|
.filter(x => this.state.selections[x]);
|
||||||
|
|
||||||
|
const totalTestQuestions = (await Promise.all(setIds.map((setId) =>
|
||||||
|
this.state.db.collection("sets")
|
||||||
|
.doc(setId)
|
||||||
|
.collection("vocab")
|
||||||
|
.get()
|
||||||
|
.then(querySnapshot => querySnapshot.docs.length)
|
||||||
|
))).reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
showClassicTestStart: true,
|
||||||
|
sliderValue: totalTestQuestions,
|
||||||
|
switchLanguage: false,
|
||||||
|
totalTestQuestions: totalTestQuestions,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
} else if (mode === "lives") {
|
||||||
|
this.setState({
|
||||||
|
showTestStart: false,
|
||||||
|
showLivesTestStart: true,
|
||||||
|
switchLanguage: false,
|
||||||
|
sliderValue: 5,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// countdown
|
||||||
|
// this.setState({
|
||||||
|
// showTestStart: false,
|
||||||
|
// showCountdownTestStart: true,
|
||||||
|
// switchLanguage: false,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideClassicTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showClassicTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLivesTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showLivesTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hideCountdownTestStart = () => {
|
||||||
|
this.setState({
|
||||||
|
showCountdownTestStart: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSliderValue = (value) => {
|
||||||
|
if (value >= 1 && value <= 999) this.setState({
|
||||||
|
sliderValue: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchLanguageChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
switchLanguage: event.target.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -251,7 +349,7 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
{
|
{
|
||||||
this.state.set.public
|
this.state.set.public
|
||||||
?
|
?
|
||||||
<span className="set-cloud-icon"><CloudQueueRoundedIcon /></span>
|
<span className="title-icon"><CloudQueueRoundedIcon /></span>
|
||||||
:
|
:
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
@@ -259,7 +357,7 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
<div className="button-container">
|
<div className="button-container">
|
||||||
<Button
|
<Button
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
onClick={() => this.startTest()}
|
onClick={this.showTestStart}
|
||||||
icon={<PlayArrowRoundedIcon />}
|
icon={<PlayArrowRoundedIcon />}
|
||||||
disabled={!this.state.canStartTest}
|
disabled={!this.state.canStartTest}
|
||||||
className="button--round"
|
className="button--round"
|
||||||
@@ -371,6 +469,48 @@ export default withRouter(class SetPage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.showTestStart &&
|
||||||
|
<TestStart
|
||||||
|
hideTestStart={this.hideTestStart}
|
||||||
|
showIndividualTestPrompt={this.showIndividualTestPrompt}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showClassicTestStart &&
|
||||||
|
<ClassicTestStart
|
||||||
|
hide={this.hideClassicTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
max={this.state.totalTestQuestions}
|
||||||
|
sliderValue={this.state.sliderValue}
|
||||||
|
onSliderChange={this.changeSliderValue}
|
||||||
|
switchLanguage={this.state.switchLanguage}
|
||||||
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showLivesTestStart &&
|
||||||
|
<LivesTestStart
|
||||||
|
hide={this.hideLivesTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
max={20}
|
||||||
|
sliderValue={this.state.sliderValue}
|
||||||
|
onSliderChange={this.changeSliderValue}
|
||||||
|
switchLanguage={this.state.switchLanguage}
|
||||||
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
||||||
|
loading={this.state.loading}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.state.showCountdownTestStart &&
|
||||||
|
<CountdownTestStart
|
||||||
|
hide={this.hideCountdownTestStart}
|
||||||
|
startTest={this.startTest}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
36
src/TestStart.js
Normal file
36
src/TestStart.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Loader from "./puff-loader.svg";
|
||||||
|
|
||||||
|
export default function TestStart(props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overlay" onClick={props.hideTestStart}></div>
|
||||||
|
<div className="overlay-content options-list-overlay-content">
|
||||||
|
|
||||||
|
{
|
||||||
|
props.loading
|
||||||
|
?
|
||||||
|
<img className="button-loader" src={Loader} alt="Loading..." />
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
// ["Classic", "Lives", "Countdown"].map((mode) =>
|
||||||
|
["Classic", "Lives"].map((mode) =>
|
||||||
|
<h3
|
||||||
|
key={mode}
|
||||||
|
onClick={() => props.showIndividualTestPrompt(mode.toLowerCase())}
|
||||||
|
>
|
||||||
|
{mode}
|
||||||
|
</h3>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<div onClick={props.hideTestStart}>
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user