Happens when a set is in a group and the group is deleted, and user navigates to home page
623 lines
18 KiB
JavaScript
623 lines
18 KiB
JavaScript
import React from 'react';
|
|
import NavBar from "./NavBar";
|
|
import { Link } from "react-router-dom";
|
|
import { TimelineRounded as TimelineRoundedIcon, ExitToAppRounded as ExitToAppRoundedIcon, HistoryRounded as HistoryRoundedIcon, SettingsRounded as SettingsRoundedIcon, PersonRounded as PersonRoundedIcon, GroupRounded as GroupRoundedIcon, AddRounded as AddRoundedIcon, SwapHorizRounded as SwapHorizRoundedIcon, PeopleRounded as PeopleRoundedIcon, DeleteRounded as DeleteRoundedIcon, QuestionAnswerRounded as QuestionAnswerRoundedIcon, SearchRounded as SearchRoundedIcon } from "@material-ui/icons";
|
|
import Checkbox from '@material-ui/core/Checkbox';
|
|
|
|
import Xarrow from 'react-xarrows';
|
|
|
|
import "firebase/auth";
|
|
import "firebase/firestore";
|
|
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 { withRouter } from "react-router-dom";
|
|
|
|
export default withRouter(class LoggedInHome extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
user: props.user,
|
|
db: props.db,
|
|
functions: {
|
|
createProgress: props.functions.httpsCallable("createProgress"),
|
|
},
|
|
canStartTest: false,
|
|
loading: false,
|
|
selections: {},
|
|
navbarItems: [
|
|
{
|
|
type: "link",
|
|
name: "Mistakes",
|
|
link: "/history/mistakes",
|
|
icon: <TimelineRoundedIcon />,
|
|
hideTextMobile: true,
|
|
},
|
|
{
|
|
type: "link",
|
|
name: "History",
|
|
link: "/history",
|
|
icon: <HistoryRoundedIcon />,
|
|
hideTextMobile: true,
|
|
},
|
|
{
|
|
type: "link",
|
|
name: "Settings",
|
|
link: "/settings",
|
|
icon: <SettingsRoundedIcon />,
|
|
hideTextMobile: true,
|
|
},
|
|
{
|
|
type: "button",
|
|
onClick: () => this.props.firebase.auth().signOut(),
|
|
icon: <ExitToAppRoundedIcon />,
|
|
hideTextMobile: true,
|
|
}
|
|
],
|
|
progressHistoryIncomplete: [],
|
|
showTestStart: false,
|
|
showClassicTestStart: false,
|
|
showLivesTestStart: false,
|
|
sliderValue: 1,
|
|
switchLanguage: false,
|
|
ignoreCaps: false,
|
|
showNumberOfAnswers: false,
|
|
totalTestQuestions: 1,
|
|
pendingDeletions: {},
|
|
};
|
|
|
|
let isMounted = true;
|
|
Object.defineProperty(this, "isMounted", {
|
|
get: () => isMounted,
|
|
set: (value) => isMounted = value,
|
|
});
|
|
}
|
|
|
|
setState = (state, callback = null) => {
|
|
if (this.isMounted) super.setState(state, callback);
|
|
}
|
|
|
|
componentDidMount() {
|
|
document.title = "Parandum";
|
|
|
|
const userSetsRef = this.state.db.collection("sets")
|
|
.where("owner", "==", this.state.user.uid)
|
|
.orderBy("title");
|
|
const userGroupsRef = this.state.db.collection("users")
|
|
.doc(this.state.user.uid)
|
|
.collection("groups");
|
|
const userGroupSetsRef = this.state.db.collection("sets");
|
|
const groupRef = this.state.db.collection("groups");
|
|
const progressRef = this.state.db.collection("progress")
|
|
.where("uid", "==", this.state.user.uid)
|
|
.where("duration", "==", null)
|
|
.orderBy("start_time", "desc")
|
|
|
|
let newState = this.state;
|
|
|
|
const userSetsQuery = userSetsRef.get().then((userSetsQuerySnapshot) => {
|
|
newState.userSets = userSetsQuerySnapshot.docs;
|
|
|
|
userSetsQuerySnapshot.docs.map((doc) => newState.selections[doc.id] = false);
|
|
|
|
});
|
|
const userGroupsQuery = userGroupsRef.get().then(async (userGroupsQuerySnapshot) => {
|
|
newState.user.groups = [];
|
|
var userGroupSets = [];
|
|
|
|
return Promise.all(userGroupsQuerySnapshot.docs.map((group) => {
|
|
const groupData = groupRef.doc(group.id).get().catch((error) => {
|
|
console.log(`Couldn't get group data, possibly due to outdated cached data: ${error}`);
|
|
return true;
|
|
});
|
|
|
|
return userGroupSetsRef
|
|
.where("public", "==", true)
|
|
.where("groups", "array-contains", group.id)
|
|
.get().then(async (userGroupSetsQuerySnapshot) => {
|
|
groupData.then((result) => {
|
|
if (typeof result !== "undefined" && typeof result.data === "function" && userGroupSetsQuerySnapshot.docs.length > 0) {
|
|
newState.user.groups.push(group.id);
|
|
|
|
userGroupSets.push({
|
|
group: result,
|
|
sets: userGroupSetsQuerySnapshot.docs,
|
|
});
|
|
|
|
userGroupSetsQuerySnapshot.docs.map((doc) => newState.selections[doc.id] = false);
|
|
}
|
|
});
|
|
});
|
|
})).then(() => {
|
|
newState.userGroupSets = userGroupSets.sort((a,b) => {
|
|
if (a.group.data().display_name < b.group.data().display_name) {
|
|
return -1;
|
|
}
|
|
if (a.group.data().display_name > b.group.data().display_name) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
})
|
|
});
|
|
});
|
|
const progressQuery = progressRef.get().then((progressQuerySnapshot) => {
|
|
progressQuerySnapshot.docs.map((doc) => {
|
|
const data = doc.data();
|
|
return newState.progressHistoryIncomplete.push({
|
|
id: doc.id,
|
|
setTitle: data.set_title,
|
|
switchLanguage: data.switch_language,
|
|
percentageProgress: (data.progress / data.questions.length * 100).toFixed(2),
|
|
grade: (data.progress > 0 ? data.correct.length / data.progress * 100 : 0).toFixed(2),
|
|
mode: data.mode,
|
|
correct: data.correct.length,
|
|
progress: data.progress,
|
|
});
|
|
})
|
|
})
|
|
|
|
Promise.all([
|
|
userSetsQuery,
|
|
userGroupsQuery,
|
|
progressQuery
|
|
]).then(() => {
|
|
this.setState(newState);
|
|
this.props.page.load();
|
|
});
|
|
|
|
this.props.logEvent("page_view");
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.isMounted = false;
|
|
this.props.page.unload();
|
|
}
|
|
|
|
stopLoading = () => {
|
|
this.setState({
|
|
canStartTest: true,
|
|
loading: false,
|
|
});
|
|
}
|
|
|
|
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: this.state.switchLanguage,
|
|
mode: mode,
|
|
limit: this.state.sliderValue,
|
|
ignoreCaps: this.state.ignoreCaps,
|
|
showNumberOfAnswers: this.state.showNumberOfAnswers,
|
|
}).then((result) => {
|
|
const progressId = result.data;
|
|
this.stopLoading();
|
|
this.props.history.push("/progress/" + progressId);
|
|
|
|
this.props.logEvent("start_test", {
|
|
progress_id: progressId,
|
|
});
|
|
}).catch((error) => {
|
|
console.log(`Couldn't start test: ${error}`);
|
|
this.stopLoading();
|
|
});
|
|
|
|
this.setState({
|
|
canStartTest: false,
|
|
loading: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
handleSetSelectionChange = (event) => {
|
|
let newState = { ...this.state };
|
|
newState.selections[event.target.name] = event.target.checked;
|
|
this.setState(newState);
|
|
if (Object.values(this.state.selections).indexOf(true) > -1) {
|
|
this.setState({ canStartTest: true });
|
|
} else {
|
|
this.setState({ canStartTest: false });
|
|
}
|
|
}
|
|
|
|
deleteProgress = (progressId) => {
|
|
if (this.state.pendingDeletions[progressId] !== true) {
|
|
let pendingDeletions = this.state.pendingDeletions;
|
|
pendingDeletions[progressId] = true;
|
|
|
|
this.setState({
|
|
pendingDeletions: pendingDeletions,
|
|
});
|
|
|
|
this.state.db.collection("progress")
|
|
.doc(progressId)
|
|
.delete()
|
|
.then(() => {
|
|
const progressIndex = this.state.progressHistoryIncomplete.map((obj) => obj.id).indexOf(progressId);
|
|
let newState = {
|
|
progressHistoryIncomplete: this.state.progressHistoryIncomplete,
|
|
};
|
|
newState.progressHistoryIncomplete.splice(progressIndex, 1);
|
|
newState.pendingDeletions = this.state.pendingDeletions;
|
|
delete pendingDeletions[progressId];
|
|
this.setState(newState);
|
|
})
|
|
.catch((error) => {
|
|
console.log(`Couldn't delete progress: ${error}`);
|
|
let pendingDeletions = this.state.pendingDeletions;
|
|
delete pendingDeletions[progressId];
|
|
this.setState({
|
|
pendingDeletions: pendingDeletions,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
handleIgnoreCapsChange = (event) => {
|
|
this.setState({
|
|
ignoreCaps: event.target.checked,
|
|
});
|
|
}
|
|
|
|
handleShowNumberOfAnswersChange = (event) => {
|
|
this.setState({
|
|
showNumberOfAnswers: event.target.checked,
|
|
});
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
<NavBar items={this.state.navbarItems} />
|
|
|
|
<main>
|
|
<div className="page-header page-header--home">
|
|
<h1>Study</h1>
|
|
<div className="button-container buttons--desktop">
|
|
<Button
|
|
onClick={this.showTestStart}
|
|
disabled={!this.state.canStartTest}
|
|
>
|
|
Test ({Object.values(this.state.selections).filter(x => x === true).length})
|
|
</Button>
|
|
{
|
|
(!this.props.page.loaded() || (this.state.userSets && this.state.userSets.length > 0)) &&
|
|
<LinkButton
|
|
to="/my-sets"
|
|
>
|
|
My Sets
|
|
</LinkButton>
|
|
}
|
|
<LinkButton to="/create-set" icon={<AddRoundedIcon/>} className="button--round" title="Create set"></LinkButton>
|
|
<LinkButton to="/search" icon={<SearchRoundedIcon/>} className="button--round" title="Search sets"></LinkButton>
|
|
<LinkButton to="/groups" icon={<GroupRoundedIcon/>} className="button--round" title="Groups"></LinkButton>
|
|
</div>
|
|
<div className="button-container buttons--mobile">
|
|
<LinkButton id="create-set-button-mobile" to="/create-set" icon={<AddRoundedIcon />} className="button--round buttons--mobile" title="Create set"></LinkButton>
|
|
<LinkButton to="/search" icon={<SearchRoundedIcon/>} className="button--round" title="Search sets"></LinkButton>
|
|
<LinkButton to="/groups" icon={<GroupRoundedIcon/>} className="button--round" title="Groups"></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>
|
|
{
|
|
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 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) =>
|
|
<div key={progressItem.id}>
|
|
<Link
|
|
to={`/progress/${progressItem.id}`}
|
|
>
|
|
{progressItem.setTitle}
|
|
{
|
|
progressItem.switchLanguage &&
|
|
<SwapHorizRoundedIcon />
|
|
}
|
|
</Link>
|
|
<p>{progressItem.percentageProgress}%</p>
|
|
<p>{progressItem.correct}/{progressItem.progress}</p>
|
|
<p>{progressItem.grade}%</p>
|
|
<p>
|
|
{
|
|
progressItem.mode === "questions"
|
|
?
|
|
<QuestionAnswerRoundedIcon />
|
|
:
|
|
<PeopleRoundedIcon />
|
|
}
|
|
</p>
|
|
<p>
|
|
<Button
|
|
className="button--no-background"
|
|
onClick={() => this.deleteProgress(progressItem.id)}
|
|
icon={<DeleteRoundedIcon />}
|
|
disabled={this.state.pendingDeletions[progressItem.id] === true}
|
|
loading={this.state.pendingDeletions[progressItem.id] === true}
|
|
title="Delete"
|
|
></Button>
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</>
|
|
}
|
|
<p id="page-intro" className="page-intro">
|
|
{
|
|
this.state.userSets && this.state.userSets.length > 0
|
|
?
|
|
"Choose sets to study"
|
|
:
|
|
"Create a set to start studying!"
|
|
}
|
|
</p>
|
|
{
|
|
(this.state.userSets && this.state.userSets.length === 0) && (this.state.userGroupSets && this.state.userGroupSets.length === 0) && this.state.progressHistoryIncomplete.length === 0 &&
|
|
<>
|
|
<Xarrow
|
|
start="page-intro"
|
|
end="create-set-button-mobile"
|
|
curveness={0.5}
|
|
color="white"
|
|
divContainerProps={{ className: "buttons--mobile" }}
|
|
/>
|
|
</>
|
|
}
|
|
<div className="form set-list">
|
|
{this.state.userSets && this.state.userSets.length > 0 &&
|
|
<div className="checkbox-list-container">
|
|
<Link
|
|
to="/my-sets"
|
|
>
|
|
<h3><PersonRoundedIcon /> My Sets</h3>
|
|
</Link>
|
|
<div className="checkbox-list">
|
|
{this.state.userSets
|
|
.sort((a, b) => {
|
|
if (a.data().title < b.data().title) {
|
|
return -1;
|
|
}
|
|
if (a.data().title > b.data().title) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
})
|
|
.map(set =>
|
|
<div key={set.id}>
|
|
<label>
|
|
<Checkbox
|
|
name={set.id}
|
|
checked={this.state.selections[set.id]}
|
|
onChange={this.handleSetSelectionChange}
|
|
inputProps={{ 'aria-label': 'checkbox' }}
|
|
/>
|
|
<Link to={`/sets/${set.id}`}>
|
|
{set.data().title}
|
|
</Link>
|
|
</label>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
}
|
|
{this.state.userGroupSets && this.state.userGroupSets.length > 0 && typeof this.state.userGroupSets.group.data === "function" && this.state.userGroupSets
|
|
.map(data =>
|
|
data.sets && data.sets.length > 0 &&
|
|
<div key={data.group.id} className="checkbox-list-container">
|
|
<Link to={`/groups/${data.group.id}`}>
|
|
<h3><GroupRoundedIcon /> {data.group.data().display_name}</h3>
|
|
</Link>
|
|
|
|
<div className="checkbox-list">
|
|
{data.sets
|
|
.sort((a, b) => {
|
|
if (a.data().title < b.data().title) {
|
|
return -1;
|
|
}
|
|
if (a.data().title > b.data().title) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
})
|
|
.map(set =>
|
|
<div key={set.id}>
|
|
<label>
|
|
<Checkbox
|
|
name={set.id}
|
|
checked={this.state.selections[set.id]}
|
|
onChange={this.handleSetSelectionChange}
|
|
inputProps={{ 'aria-label': 'checkbox' }}
|
|
/>
|
|
<Link to={`/sets/${set.id}`}>
|
|
{set.data().title}
|
|
</Link>
|
|
</label>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</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}
|
|
ignoreCaps={this.state.ignoreCaps}
|
|
showNumberOfAnswers={this.state.showNumberOfAnswers}
|
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
|
handleIgnoreCapsChange={this.handleIgnoreCapsChange}
|
|
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
|
|
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}
|
|
ignoreCaps={this.state.ignoreCaps}
|
|
showNumberOfAnswers={this.state.showNumberOfAnswers}
|
|
handleSwitchLanguageChange={this.handleSwitchLanguageChange}
|
|
handleIgnoreCapsChange={this.handleIgnoreCapsChange}
|
|
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
|
|
loading={this.state.loading}
|
|
/>
|
|
}
|
|
{
|
|
this.state.showCountdownTestStart &&
|
|
<CountdownTestStart
|
|
hide={this.hideCountdownTestStart}
|
|
startTest={this.startTest}
|
|
/>
|
|
}
|
|
</div>
|
|
)
|
|
}
|
|
}) |