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 LinkButton from './LinkButton';
|
||||
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/History.css";
|
||||
import "./css/LoggedInHome.css";
|
||||
import "./css/PopUp.css";
|
||||
import "./css/OptionsListOverlay.css";
|
||||
import "./css/SliderOverlay.css";
|
||||
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
@@ -54,6 +61,12 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
}
|
||||
],
|
||||
progressHistoryIncomplete: [],
|
||||
showTestStart: false,
|
||||
showClassicTestStart: false,
|
||||
showLivesTestStart: false,
|
||||
sliderValue: 1,
|
||||
switchLanguage: false,
|
||||
totalTestQuestions: 1,
|
||||
};
|
||||
|
||||
let isMounted = true;
|
||||
@@ -177,15 +190,15 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
startTest = () => {
|
||||
startTest = (mode) => {
|
||||
if (this.state.canStartTest) {
|
||||
const selections = Object.keys(this.state.selections)
|
||||
.filter(x => this.state.selections[x]);
|
||||
this.state.functions.createProgress({
|
||||
sets: selections,
|
||||
switch_language: false,
|
||||
mode: "questions",
|
||||
limit: 1000,
|
||||
switch_language: this.state.switchLanguage,
|
||||
mode: mode,
|
||||
limit: this.state.sliderValue,
|
||||
}).then((result) => {
|
||||
const progressId = result.data;
|
||||
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() {
|
||||
return (
|
||||
<div>
|
||||
@@ -241,8 +342,7 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
<h1>Study</h1>
|
||||
<div className="button-container buttons--desktop">
|
||||
<Button
|
||||
onClick={this.startTest}
|
||||
loading={this.state.loading}
|
||||
onClick={this.showTestStart}
|
||||
disabled={!this.state.canStartTest}
|
||||
>
|
||||
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
||||
@@ -264,17 +364,39 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
</div>
|
||||
<LinkButton id="create-set-button-mobile" to="/create-set" icon={<AddRoundedIcon />} className="button--round buttons--mobile"></LinkButton>
|
||||
</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 &&
|
||||
<>
|
||||
<h2>Incomplete Tests</h2>
|
||||
<div className="progress-history-container">
|
||||
<div className="progress-history-container progress-history-container--incomplete">
|
||||
<div>
|
||||
<h3>Set</h3>
|
||||
<h3>Progress</h3>
|
||||
<h3>Mark</h3>
|
||||
<h3>Grade</h3>
|
||||
<h3>Mode</h3>
|
||||
<span></span>
|
||||
</div>
|
||||
{
|
||||
this.state.progressHistoryIncomplete.map((progressItem) =>
|
||||
@@ -313,28 +435,6 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
</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">
|
||||
{
|
||||
this.state.userSets && this.state.userSets.length > 0
|
||||
@@ -440,6 +540,48 @@ export default withRouter(class LoggedInHome extends React.Component {
|
||||
</div>
|
||||
</main>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
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 Button from "./Button";
|
||||
import LinkButton from "./LinkButton";
|
||||
@@ -66,6 +66,9 @@ export default withRouter(class Progress extends React.Component {
|
||||
attemptHistory: {},
|
||||
questions: [],
|
||||
originalTotalQuestions: 1,
|
||||
lives: 1,
|
||||
startLives: null,
|
||||
setComplete: false,
|
||||
};
|
||||
|
||||
let isMounted = true;
|
||||
@@ -102,10 +105,12 @@ export default withRouter(class Progress extends React.Component {
|
||||
nextPrompt: null,
|
||||
setIds: data.setIds,
|
||||
originalTotalQuestions: [...new Set(data.questions)].length,
|
||||
setComplete: data.duration !== null,
|
||||
};
|
||||
|
||||
if (data.lives) {
|
||||
newState.lives = data.lives;
|
||||
newState.startLives = data.start_lives;
|
||||
}
|
||||
|
||||
return [ newState, data.duration !== null, data.incorrect, data.duration ];
|
||||
@@ -152,7 +157,7 @@ export default withRouter(class Progress extends React.Component {
|
||||
.get()
|
||||
.then((querySnapshot) => {
|
||||
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)
|
||||
.map((doc) => ({
|
||||
x: new Date(doc.data().start_time),
|
||||
@@ -308,8 +313,10 @@ export default withRouter(class Progress extends React.Component {
|
||||
progress_id: this.props.match.params.progressId,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.mode === "lives") newState.lives = data.lives;
|
||||
|
||||
if (data.correct && !data.moreAnswers && this.state.incorrectAnswers[data.currentVocabId]) {
|
||||
if ((data.correct || data.lives === 0) && !data.moreAnswers && this.state.incorrectAnswers[data.currentVocabId]) {
|
||||
// all answers to question given correctly
|
||||
// answer was previously wrong
|
||||
// store correct answer
|
||||
@@ -318,10 +325,11 @@ export default withRouter(class Progress extends React.Component {
|
||||
} else if (!data.correct) {
|
||||
// incorrect answer given
|
||||
// store prompt and count=0
|
||||
// store answer if in lives mode and no lives left
|
||||
newState.incorrectAnswers = this.state.incorrectAnswers;
|
||||
newState.incorrectAnswers[data.currentVocabId] = {
|
||||
prompt: this.state.currentPrompt,
|
||||
answer: "",
|
||||
answer: data.lives === 0 ? data.correctAnswers : "",
|
||||
count: 0,
|
||||
};
|
||||
}
|
||||
@@ -343,7 +351,7 @@ export default withRouter(class Progress extends React.Component {
|
||||
.get()
|
||||
.then((querySnapshot) => {
|
||||
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)
|
||||
.map((doc) => ({
|
||||
x: new Date(doc.data().start_time),
|
||||
@@ -360,7 +368,7 @@ export default withRouter(class Progress extends React.Component {
|
||||
}
|
||||
|
||||
data.incorrectAnswers.map((vocabId) => {
|
||||
if (newState.incorrectAnswers[vocabId]) {
|
||||
if (newState.incorrectAnswers[vocabId] && newState.incorrectAnswers[vocabId].answer !== "") {
|
||||
// already been logged including prompt and correct answer
|
||||
newState.incorrectAnswers[vocabId].count++;
|
||||
} else {
|
||||
@@ -429,15 +437,16 @@ export default withRouter(class Progress extends React.Component {
|
||||
loading: false,
|
||||
canProceed: true,
|
||||
};
|
||||
|
||||
|
||||
if (!this.state.moreAnswers) {
|
||||
if (this.state.nextPrompt === null) newState.setComplete = true;
|
||||
newState.currentCorrect = [];
|
||||
newState.currentPrompt = this.state.nextPrompt;
|
||||
newState.currentSound = this.state.nextSound;
|
||||
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} />
|
||||
{
|
||||
this.state.currentAnswerStatus === null
|
||||
this.state.currentAnswerStatus === null && !this.state.setComplete
|
||||
?
|
||||
<main className="progress-container">
|
||||
<div>
|
||||
@@ -512,11 +521,24 @@ export default withRouter(class Progress extends React.Component {
|
||||
</div>
|
||||
</main>
|
||||
:
|
||||
this.state.nextPrompt === null && !this.state.moreAnswers
|
||||
this.state.nextPrompt === null && !this.state.moreAnswers && this.state.setComplete
|
||||
?
|
||||
<main>
|
||||
{/* 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="stat-row stat-row--inline">
|
||||
<p>You got</p>
|
||||
@@ -534,6 +556,14 @@ export default withRouter(class Progress extends React.Component {
|
||||
<p>Attempt #</p>
|
||||
<h1>{this.state.attemptNumber}</h1>
|
||||
</div>
|
||||
{
|
||||
this.state.startLives &&
|
||||
<div className="stat-row stat-row--inline">
|
||||
<p>with</p>
|
||||
<h1>{this.state.startLives}</h1>
|
||||
<p>lives</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
{
|
||||
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>
|
||||
<LineChart data={this.state.attemptHistory} />
|
||||
@@ -625,14 +655,15 @@ export default withRouter(class Progress extends React.Component {
|
||||
</main>
|
||||
}
|
||||
{
|
||||
(this.state.currentAnswerStatus === null ||
|
||||
!(this.state.nextPrompt === null && !this.state.moreAnswers)) &&
|
||||
!this.state.setComplete &&
|
||||
<ProgressStats
|
||||
correct={this.state.correct}
|
||||
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}
|
||||
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 />
|
||||
|
||||
@@ -10,7 +10,17 @@ export default function ProgressStats(props) {
|
||||
</div>
|
||||
<div className="stat-row stat-row--inline">
|
||||
<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 className="stat-row stat-row--inline">
|
||||
<h1>{props.incorrect}</h1>
|
||||
@@ -22,8 +32,8 @@ export default function ProgressStats(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="progress-bar">
|
||||
<div style={{ width: props.correct > 0 && props.totalVocabItems > 0 ? `${(props.correct / props.totalVocabItems * 100)}%` : "0" }}>
|
||||
<p>{props.correct}/{props.totalVocabItems}</p>
|
||||
<div style={{ width: props.progressNumerator > 0 && props.progressDenominator > 0 ? `${(props.progressNumerator / props.progressDenominator * 100)}%` : "0" }}>
|
||||
<p>{props.progressNumerator}/{props.progressDenominator}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
152
src/SetPage.js
152
src/SetPage.js
@@ -6,6 +6,10 @@ import Button from "./Button";
|
||||
import LinkButton from "./LinkButton";
|
||||
import Error404 from "./Error404";
|
||||
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/SetPage.css";
|
||||
@@ -43,6 +47,12 @@ export default withRouter(class SetPage extends React.Component {
|
||||
groups: {},
|
||||
currentSetGroups: [],
|
||||
showDeleteConfirmation: false,
|
||||
showTestStart: false,
|
||||
showClassicTestStart: false,
|
||||
showLivesTestStart: false,
|
||||
sliderValue: 1,
|
||||
switchLanguage: false,
|
||||
totalTestQuestions: 1,
|
||||
};
|
||||
|
||||
let isMounted = true;
|
||||
@@ -113,13 +123,13 @@ export default withRouter(class SetPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
startTest = () => {
|
||||
startTest = (mode) => {
|
||||
if (this.state.canStartTest) {
|
||||
this.state.functions.createProgress({
|
||||
sets: [this.props.match.params.setId],
|
||||
switch_language: false,
|
||||
mode: "questions",
|
||||
limit: 1000,
|
||||
switch_language: this.state.switchLanguage,
|
||||
mode: mode,
|
||||
limit: this.state.sliderValue,
|
||||
}).then((result) => {
|
||||
const progressId = result.data;
|
||||
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() {
|
||||
return (
|
||||
<div>
|
||||
@@ -251,7 +349,7 @@ export default withRouter(class SetPage extends React.Component {
|
||||
{
|
||||
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">
|
||||
<Button
|
||||
loading={this.state.loading}
|
||||
onClick={() => this.startTest()}
|
||||
onClick={this.showTestStart}
|
||||
icon={<PlayArrowRoundedIcon />}
|
||||
disabled={!this.state.canStartTest}
|
||||
className="button--round"
|
||||
@@ -371,6 +469,48 @@ export default withRouter(class SetPage extends React.Component {
|
||||
</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>
|
||||
|
||||
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