From 80e7c24811e83d66e5e3daa0e50a90419adf24b5 Mon Sep 17 00:00:00 2001 From: Matthew Grove Date: Sun, 3 Oct 2021 15:59:24 +0100 Subject: [PATCH] Add loader until page loads completely --- src/App.js | 208 +++++++++++++++++++++++++++--------------- src/CreateSet.js | 3 + src/EditSet.js | 3 + src/Error404.js | 17 +++- src/GroupPage.js | 11 +-- src/History.js | 105 ++++++++++----------- src/Home.js | 11 ++- src/LoggedInHome.js | 9 +- src/Login.js | 6 +- src/PrivacyPolicy.js | 11 ++- src/Progress.js | 3 + src/SetPage.js | 3 + src/Settings.js | 3 + src/TermsOfService.js | 11 ++- src/UserGroups.js | 2 + src/UserSets.js | 4 +- src/css/App.css | 12 +++ src/css/PopUp.css | 5 + 18 files changed, 280 insertions(+), 147 deletions(-) diff --git a/src/App.js b/src/App.js index a4ecf62..f636e3b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,6 @@ import React from 'react'; import './css/App.css'; +import './css/PopUp.css'; import { BrowserRouter as Router, Route, Switch, Redirect, Link } from 'react-router-dom'; import Home from "./Home"; import LoggedInHome from "./LoggedInHome"; @@ -18,11 +19,14 @@ import TermsOfService from "./TermsOfService"; import PrivacyPolicy from "./PrivacyPolicy"; import Button from "./Button"; import { CheckRounded as CheckRoundedIcon } from "@material-ui/icons"; +import Loader from "./puff-loader.svg"; import RouteChangeTracker from './RouteChangeTracker'; import Cookies from 'universal-cookie'; +import styled, { keyframes } from "styled-components"; + import firebase from "firebase/app"; import "firebase/auth"; import "firebase/functions"; @@ -48,10 +52,37 @@ appCheck.activate( true ); -// firebase.functions().useEmulator("localhost", 5001); -// firebase.auth().useEmulator("http://localhost:9099"); -// firebase.firestore().useEmulator("localhost", 8080); -const functions = firebase.app().functions("europe-west2");//firebase.functions(); +firebase.functions().useEmulator("localhost", 5001); +firebase.auth().useEmulator("http://localhost:9099"); +firebase.firestore().useEmulator("localhost", 8080); +const functions = firebase.functions();//firebase.app().functions("europe-west2"); + +const fadeIn = keyframes` + from { + opacity: 0; + } + + to { + opacity: 0.65; + } +`; + +const fadeOut = keyframes` + from { + opacity: 0.65; + } + + to { + opacity: 0; + } +`; + +const Fade = styled.div` + display: inline-block; + visibility: ${props => props.out ? 'hidden' : 'visible'}; + animation: ${props => props.out ? fadeOut : fadeIn} 0.1s linear; + transition: visibility 0.1s linear; +`; firebase.firestore().enablePersistence() .catch((err) => { @@ -85,10 +116,27 @@ const analytics = firebase.analytics(); class App extends React.Component { constructor(props) { super(props); + this.state = { user: null, + userDataPresent: false, sound: true, theme: "default", + pageLoading: true, + }; + + this.page = { + loaded: !this.state.pageLoading, + load: () => { + this.setState({ + pageLoading: false, + }); + }, + unload: () => { + this.setState({ + pageLoading: true, + }); + }, }; } @@ -96,6 +144,7 @@ class App extends React.Component { firebase.auth().onAuthStateChanged(async (userData) => { let newState = { user: userData, + userDataPresent: true, }; if (userData) { @@ -107,16 +156,16 @@ class App extends React.Component { await firebase.firestore() - .collection("users") - .doc(userData.uid) - .get() - .then((userDoc) => { - newState.sound = userDoc.data().sound; - newState.theme = userDoc.data().theme; - }).catch((error) => { - newState.sound = true; - newState.theme = "default"; - }); + .collection("users") + .doc(userData.uid) + .get() + .then((userDoc) => { + newState.sound = userDoc.data().sound; + newState.theme = userDoc.data().theme; + }).catch((error) => { + newState.sound = true; + newState.theme = "default"; + }); } this.setState(newState); @@ -217,73 +266,76 @@ class App extends React.Component {
- { - this.state.user !== null - ? - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - : - <> - - - - - + { + this.state.userDataPresent && + ( + this.state.user !== null + ? + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + : + <> + + + + + - + - - - - - - - + + + + + + + + ) } ); } diff --git a/src/CreateSet.js b/src/CreateSet.js index c11b3f8..e883716 100644 --- a/src/CreateSet.js +++ b/src/CreateSet.js @@ -48,11 +48,14 @@ export default withRouter(class CreateSet extends React.Component { document.title = "Create Set | Parandum"; this.setNameInput.focus(); + this.props.page.load(); + this.props.logEvent("page_view"); } componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } stopLoading = () => { diff --git a/src/EditSet.js b/src/EditSet.js index 115e309..3f3a8d7 100644 --- a/src/EditSet.js +++ b/src/EditSet.js @@ -103,11 +103,13 @@ export default withRouter(class EditSet extends Component { } this.setState(newState); + this.props.page.load(); }); }).catch(() => { this.setState({ setInaccessible: true, }); + this.props.page.load(); }); this.props.logEvent("select_content", { @@ -119,6 +121,7 @@ export default withRouter(class EditSet extends Component { componentWillUnmount = () => { window.removeEventListener('beforeunload', this.alertLeavingWithoutSaving); this.isMounted = false; + this.props.page.unload(); } stopLoading = () => { diff --git a/src/Error404.js b/src/Error404.js index c090d7b..f1a65e2 100644 --- a/src/Error404.js +++ b/src/Error404.js @@ -1,9 +1,9 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import NavBar from './NavBar'; import Footer from "./Footer"; import { HomeRounded as HomeRoundedIcon } from "@material-ui/icons"; -export default function PageNotFound() { +export default function PageNotFound(props) { const navbarItems = [ { type: "link", @@ -15,7 +15,18 @@ export default function PageNotFound() { document.title = "Error 404 | Parandum"; + const page = props.page; + + useEffect(() => { + if (page) { + page.load(); + return () => page.unload(); + } + }, [page]); + return ( + !page.loaded + ?
@@ -24,5 +35,7 @@ export default function PageNotFound() {
+ : + null ) } diff --git a/src/GroupPage.js b/src/GroupPage.js index a0c10bd..362ae1d 100644 --- a/src/GroupPage.js +++ b/src/GroupPage.js @@ -9,8 +9,6 @@ import "./css/GroupPage.css"; import "./css/ConfirmationDialog.css"; import "./css/OptionsListOverlay.css"; -import Loader from "./puff-loader.svg" - export default withRouter(class GroupPage extends Component { constructor(props) { super(props); @@ -118,9 +116,10 @@ export default withRouter(class GroupPage extends Component { } this.setState(newState); + this.props.page.load(); }); }); - + this.props.logEvent("select_content", { content_type: "group", item_id: this.props.match.params.groupId, @@ -129,6 +128,7 @@ export default withRouter(class GroupPage extends Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } editGroupName = () => { @@ -340,10 +340,7 @@ export default withRouter(class GroupPage extends Component {
{ - (this.state.role === null) - ? - Loading... - : + !(this.state.role === null) && <>
{ diff --git a/src/History.js b/src/History.js index ca361d6..af0ae39 100644 --- a/src/History.js +++ b/src/History.js @@ -61,64 +61,67 @@ export default class History extends Component { .then(async (querySnapshot) => { let complete = []; let incomplete = []; - let totalCorrect = 0; - let totalIncorrect = 0; - let totalMarks = 0; - let totalTime = 0; - let totalPercentage = 0; - let userMarkHistory = []; + let totalCorrect = 0; + let totalIncorrect = 0; + let totalMarks = 0; + let totalTime = 0; + let totalPercentage = 0; + let userMarkHistory = []; + + querySnapshot.docs.map((doc) => { + const data = doc.data(); + const pushData = { + 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, + }; - querySnapshot.docs.map((doc) => { - const data = doc.data(); - const pushData = { - 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, - }; - - totalCorrect += data.correct.length; - totalIncorrect += data.incorrect.length; - totalMarks += data.progress; - - if (data.duration !== null) { - totalPercentage += (data.correct.length / data.questions.length * 100); - totalTime += data.duration; - userMarkHistory.push({ - x: new Date(data.start_time), - y: (data.correct.length / data.questions.length * 100), - }); - return complete.push(pushData); - } else { - return incomplete.push(pushData); - } - }); + totalCorrect += data.correct.length; + totalIncorrect += data.incorrect.length; + totalMarks += data.progress; - this.setState({ - progressHistoryComplete: complete, - progressHistoryIncomplete: incomplete, - totalCorrect: totalCorrect, - totalIncorrect: totalIncorrect, - totalMarks: totalMarks, - totalTime: totalTime, - totalPercentage: totalPercentage, - totalCompleteTests: complete.length, - userMarkHistory: userMarkHistory, - personalSetsCount: (await userSets).docs.length, - }); - }).catch((error) => { - console.log(`Couldn't retrieve progress history: ${error}`); + if (data.duration !== null) { + totalPercentage += (data.correct.length / data.questions.length * 100); + totalTime += data.duration; + userMarkHistory.push({ + x: new Date(data.start_time), + y: (data.correct.length / data.questions.length * 100), + }); + return complete.push(pushData); + } else { + return incomplete.push(pushData); + } }); - - this.props.logEvent("page_view"); + + this.setState({ + progressHistoryComplete: complete, + progressHistoryIncomplete: incomplete, + totalCorrect: totalCorrect, + totalIncorrect: totalIncorrect, + totalMarks: totalMarks, + totalTime: totalTime, + totalPercentage: totalPercentage, + totalCompleteTests: complete.length, + userMarkHistory: userMarkHistory, + personalSetsCount: (await userSets).docs.length, + }); + this.props.page.load(); + }).catch((error) => { + console.log(`Couldn't retrieve progress history: ${error}`); + this.props.page.load(); + }); + + this.props.logEvent("page_view"); } componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } deleteProgress = (progressId) => { diff --git a/src/Home.js b/src/Home.js index 2f25298..7a19656 100644 --- a/src/Home.js +++ b/src/Home.js @@ -18,9 +18,16 @@ export default function Home(props) { document.title = "Parandum"; + const page = props.page; + const logEvent = props.logEvent; + useEffect(() => { - if (props.logEvent) props.logEvent("page_view"); - }); + if (page) { + page.load(); + return () => page.unload(); + } + if (logEvent) logEvent("page_view"); + }, [logEvent, page]); return (
diff --git a/src/LoggedInHome.js b/src/LoggedInHome.js index 3c78174..3b570ae 100644 --- a/src/LoggedInHome.js +++ b/src/LoggedInHome.js @@ -121,9 +121,12 @@ export default withRouter(class LoggedInHome extends React.Component { var userGroupSets = []; return Promise.all(userGroupsQuerySnapshot.docs.map((group) => { - newState.user.groups.push(group.id); + const groupData = groupRef.doc(group.id).get().catch((error) => { + console.log(`Couldn't get group data: ${error}`); + return true; + }); - const groupData = groupRef.doc(group.id).get(); + newState.user.groups.push(group.id); return userGroupSetsRef .where("public", "==", true) @@ -175,6 +178,7 @@ export default withRouter(class LoggedInHome extends React.Component { progressQuery ]).then(() => { this.setState(newState); + this.props.page.load(); }); this.props.logEvent("page_view"); @@ -182,6 +186,7 @@ export default withRouter(class LoggedInHome extends React.Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } stopLoading = () => { diff --git a/src/Login.js b/src/Login.js index 9745fd3..7810d8b 100644 --- a/src/Login.js +++ b/src/Login.js @@ -25,9 +25,11 @@ export default function Login(props) { document.title = "Login | Parandum"; + const logEvent = props.logEvent; + useEffect(() => { - props.logEvent("page_view"); - }); + if (logEvent) logEvent("page_view"); + }, [logEvent]); return ( <> diff --git a/src/PrivacyPolicy.js b/src/PrivacyPolicy.js index 77d798c..a02dbb8 100644 --- a/src/PrivacyPolicy.js +++ b/src/PrivacyPolicy.js @@ -13,9 +13,16 @@ export default function PrivacyPolicy(props) { } ]; + const page = props.page; + const logEvent = props.logEvent; + useEffect(() => { - props.logEvent("page_view"); - }); + if (page) { + page.load(); + return () => page.unload(); + } + if (logEvent) logEvent("page_view"); + }, [logEvent, page]); return (
diff --git a/src/Progress.js b/src/Progress.js index bcb50a0..8a5f4d6 100644 --- a/src/Progress.js +++ b/src/Progress.js @@ -201,6 +201,8 @@ export default withRouter(class Progress extends React.Component { if (!setDone) this.answerInput.focus(); }); + this.props.page.load(); + this.props.logEvent("select_content", { content_type: "progress", item_id: this.props.match.params.progressId, @@ -209,6 +211,7 @@ export default withRouter(class Progress extends React.Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } showSettings = () => { diff --git a/src/SetPage.js b/src/SetPage.js index 67d5a2f..9210212 100644 --- a/src/SetPage.js +++ b/src/SetPage.js @@ -98,11 +98,13 @@ export default withRouter(class SetPage extends React.Component { }, currentSetGroups: setDoc.data().groups, }); + this.props.page.load(); }); }).catch((error) => { this.setState({ setInaccessible: true, }); + this.props.page.load(); console.log(`Can't access set: ${error}`); }); @@ -114,6 +116,7 @@ export default withRouter(class SetPage extends React.Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } stopLoading = () => { diff --git a/src/Settings.js b/src/Settings.js index 89359ee..804f09a 100644 --- a/src/Settings.js +++ b/src/Settings.js @@ -38,11 +38,14 @@ export default withRouter(class Settings extends Component { componentDidMount() { document.title = "Settings | Parandum"; + this.props.page.load(); + this.props.logEvent("page_view"); } componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } handleSoundInputChange = (event) => { diff --git a/src/TermsOfService.js b/src/TermsOfService.js index b82b855..afa53e9 100644 --- a/src/TermsOfService.js +++ b/src/TermsOfService.js @@ -14,9 +14,16 @@ export default function TermsOfService(props) { } ]; + const page = props.page; + const logEvent = props.logEvent; + useEffect(() => { - props.logEvent("page_view"); - }); + if (page) { + page.load(); + return () => page.unload(); + } + if (logEvent) logEvent("page_view"); + }, [logEvent, page]); return (
diff --git a/src/UserGroups.js b/src/UserGroups.js index 58568f4..813f3ab 100644 --- a/src/UserGroups.js +++ b/src/UserGroups.js @@ -75,6 +75,7 @@ export default withRouter(class UserGroups extends Component { })); this.setState(newState); + this.props.page.load(); }); this.props.logEvent("page_view"); @@ -82,6 +83,7 @@ export default withRouter(class UserGroups extends Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } showJoinGroup = () => { diff --git a/src/UserSets.js b/src/UserSets.js index 71b8743..83e8af9 100644 --- a/src/UserSets.js +++ b/src/UserSets.js @@ -49,7 +49,8 @@ export default withRouter(class UserSets extends Component { userSetsRef.get().then((querySnapshot) => { this.setState({ userSets: querySnapshot.docs, - }) + }); + this.props.page.load(); }); this.props.logEvent("page_view"); @@ -57,6 +58,7 @@ export default withRouter(class UserSets extends Component { componentWillUnmount() { this.isMounted = false; + this.props.page.unload(); } render() { diff --git a/src/css/App.css b/src/css/App.css index 27a7470..d0fbc85 100644 --- a/src/css/App.css +++ b/src/css/App.css @@ -292,6 +292,14 @@ label .MuiIconButton-label > input { column-gap: 2px; } +.page-loader-container { + width: 30%; + height: min-content; + line-height: 0; + border-radius: 150px; + background: var(--primary-color-dark); +} + .page-loader { margin: auto; width: 30%; @@ -395,6 +403,10 @@ label .MuiIconButton-label > input { visibility: hidden; } +.transparent { + opacity: 0 !important; +} + @media screen and (max-width: 420px) { .progress-history-container > div > *:nth-child(2), .progress-history-container--complete > div > *:nth-last-child(3), .progress-history-container--incomplete > div > *:nth-last-child(4) { display: none; diff --git a/src/css/PopUp.css b/src/css/PopUp.css index f41b915..0e4d179 100644 --- a/src/css/PopUp.css +++ b/src/css/PopUp.css @@ -10,6 +10,11 @@ cursor: default; } +.overlay--black { + background-color: var(--background-color); + opacity: 0.8; +} + .popup-close-button { position: absolute; top: 24px;