Cleanup code

This commit is contained in:
Antti Pilto 2018-11-17 16:09:14 +02:00
parent eaf95c395e
commit f6bc13bfb9
15 changed files with 944 additions and 957 deletions

View file

@ -1,4 +1,4 @@
# Kana Quiz 2 # Kana Quiz
Kana Quiz made with React.js. Kana Quiz made with React.js.
See live at https://kana.pro/ See live at https://kana.pro/

View file

@ -7,20 +7,13 @@ import { removeHash } from '../../data/helperFuncs';
const options = {}; const options = {};
class App extends Component { class App extends Component {
constructor(props) { state = { gameState: 'chooseCharacters' };
super(props);
this.state = {
gameState: 'chooseCharacters'
}
this.startGame = this.startGame.bind(this);
this.endGame = this.endGame.bind(this);
}
startGame() { startGame = () => {
this.setState({gameState: 'game'}); this.setState({gameState: 'game'});
} }
endGame() { endGame = () => {
this.setState({gameState: 'chooseCharacters'}); this.setState({gameState: 'chooseCharacters'});
} }

View file

@ -1,25 +1,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
class CharacterGroup extends Component { class CharacterGroup extends Component {
constructor(props) { state = { shownChars: '' }
super(props);
this.state = {
shownChars : '',
}
this.changeShownChars = this.changeShownChars.bind(this);
}
changeShownChars(newString, e) { changeShownChars(newString) {
this.setState({shownChars: newString}) this.setState({shownChars: newString})
} }
getShowableCharacters(whichKana) { getShowableCharacters(whichKana) {
let strRomajiCharacters = ''; let strRomajiCharacters = '';
let strKanaCharacters = ''; let strKanaCharacters = '';
Object.keys(this.props.characters).map(function(character) { Object.keys(this.props.characters).map(character => {
strRomajiCharacters+=this.props.characters[character][0]+' · '; strRomajiCharacters+=this.props.characters[character][0]+' · ';
strKanaCharacters+=character+' · '; strKanaCharacters+=character+' · ';
}, this); });
strRomajiCharacters = strRomajiCharacters.slice(0, -2); strRomajiCharacters = strRomajiCharacters.slice(0, -2);
strKanaCharacters = strKanaCharacters.slice(0, -2); strKanaCharacters = strKanaCharacters.slice(0, -2);
if(whichKana=='romaji') return strRomajiCharacters; if(whichKana=='romaji') return strRomajiCharacters;
@ -32,7 +26,8 @@ class CharacterGroup extends Component {
render() { render() {
return ( return (
<div className={ <div
className={
'choose-row' + (this.props.groupName.endsWith('_a') || this.props.groupName.endsWith('_s') ? ' alt-row' : '') 'choose-row' + (this.props.groupName.endsWith('_a') || this.props.groupName.endsWith('_s') ? ' alt-row' : '')
} }
onClick={() => { onClick={() => {

View file

@ -5,19 +5,13 @@ import './ChooseCharacters.scss';
import CharacterGroup from './CharacterGroup'; import CharacterGroup from './CharacterGroup';
class ChooseCharacters extends Component { class ChooseCharacters extends Component {
constructor(props) { state = {
super(props);
this.state = {
errMsg : '', errMsg : '',
selectedGroups: this.props.selectedGroups, selectedGroups: this.props.selectedGroups,
showAlternatives: [], showAlternatives: [],
showSimilars: [], showSimilars: [],
startIsVisible: true startIsVisible: true
} }
this.toggleSelect = this.toggleSelect.bind(this);
this.startGame = this.startGame.bind(this);
this.testIsStartVisible = this.testIsStartVisible.bind(this);
}
componentDidMount() { componentDidMount() {
this.testIsStartVisible(); this.testIsStartVisible();
@ -34,7 +28,7 @@ class ChooseCharacters extends Component {
this.testIsStartVisible(); this.testIsStartVisible();
} }
testIsStartVisible() { testIsStartVisible = () => {
if(this.startRef) { if(this.startRef) {
const rect = this.startRef.getBoundingClientRect(); const rect = this.startRef.getBoundingClientRect();
if(rect.y > window.innerHeight && this.state.startIsVisible) if(rect.y > window.innerHeight && this.state.startIsVisible)
@ -73,7 +67,7 @@ class ChooseCharacters extends Component {
this.setState({errMsg: '', selectedGroups: this.state.selectedGroups.concat(groupName)}); this.setState({errMsg: '', selectedGroups: this.state.selectedGroups.concat(groupName)});
} }
toggleSelect(groupName) { toggleSelect = groupName => {
if(this.getIndex(groupName) > -1) if(this.getIndex(groupName) > -1)
this.removeSelect(groupName); this.removeSelect(groupName);
else else
@ -214,7 +208,7 @@ class ChooseCharacters extends Component {
<div className="col-xs-12"> <div className="col-xs-12">
<div className="panel panel-default"> <div className="panel panel-default">
<div className="panel-body welcome"> <div className="panel-body welcome">
<h4>Welcome to Kana Quiz!</h4> <h4>Welcome to Kana Pro!</h4>
<p>Please choose the groups of characters that you'd like to be studying.</p> <p>Please choose the groups of characters that you'd like to be studying.</p>
</div> </div>
</div> </div>
@ -266,7 +260,7 @@ class ChooseCharacters extends Component {
this.state.errMsg != '' && this.state.errMsg != '' &&
<div className="error-message">{this.state.errMsg}</div> <div className="error-message">{this.state.errMsg}</div>
} }
<button ref={c => this.startRef = c} className="btn btn-danger startgame-button" onClick={this.startGame}>Start the Quiz!</button> <button ref={c => this.startRef = c} className="btn btn-danger startgame-button" onClick={() => this.startGame()}>Start the Quiz!</button>
</div> </div>
<div className="down-arrow" <div className="down-arrow"
style={{display: this.state.startIsVisible ? 'none' : 'block'}} style={{display: this.state.startIsVisible ? 'none' : 'block'}}

View file

@ -4,39 +4,37 @@ import ShowStage from './ShowStage';
import Question from './Question'; import Question from './Question';
class Game extends Component { class Game extends Component {
constructor(props) { state = { showScreen: '' }
super(props);
this.state = {
showScreen: ''
}
this.showQuestion = this.showQuestion.bind(this);
this.stageUp = this.stageUp.bind(this);
this.lockStage = this.lockStage.bind(this);
}
stageUp() {
this.props.stageUp();
this.setState({showScreen: 'stage'});
}
lockStage(stage) {
this.setState({showScreen: 'question'});
this.props.lockStage(stage);
}
showQuestion() {
this.setState({showScreen: 'question'})
}
componentWillMount() { componentWillMount() {
this.setState({showScreen: 'stage'}); this.setState({showScreen: 'stage'});
} }
stageUp = () => {
this.props.stageUp();
this.setState({showScreen: 'stage'});
}
lockStage = stage => {
this.setState({showScreen: 'question'});
this.props.lockStage(stage);
}
showQuestion = () => {
this.setState({showScreen: 'question'})
}
render() { render() {
return ( return (
<div> <div>
{ this.state.showScreen==='stage' ? <ShowStage lockStage={this.lockStage} handleShowQuestion={this.showQuestion} handleEndGame={this.props.handleEndGame} stage={this.props.stage} /> : '' } {
{ this.state.showScreen==='question' ? <Question isLocked={this.props.isLocked} handleStageUp={this.stageUp} stage={this.props.stage} decidedGroups={this.props.decidedGroups} /> : '' } this.state.showScreen==='stage' &&
<ShowStage lockStage={this.lockStage} handleShowQuestion={this.showQuestion} handleEndGame={this.props.handleEndGame} stage={this.props.stage} />
}
{
this.state.showScreen==='question' &&
<Question isLocked={this.props.isLocked} handleStageUp={this.stageUp} stage={this.props.stage} decidedGroups={this.props.decidedGroups} />
}
</div> </div>
); );
} }

View file

@ -5,9 +5,7 @@ import { findRomajisAtKanaKey, removeFromArray, arrayContains, shuffle, cartesia
import './Question.scss'; import './Question.scss';
class Question extends Component { class Question extends Component {
constructor(props) { state = {
super(props);
this.state = {
previousQuestion: [], previousQuestion: [],
previousAnswer: '', previousAnswer: '',
currentAnswer: '', currentAnswer: '',
@ -15,11 +13,11 @@ class Question extends Component {
answerOptions: [], answerOptions: [],
stageProgress: 0 stageProgress: 0
} }
this.setNewQuestion = this.setNewQuestion.bind(this); // this.setNewQuestion = this.setNewQuestion.bind(this);
this.handleAnswer = this.handleAnswer.bind(this); // this.handleAnswer = this.handleAnswer.bind(this);
this.handleAnswerChange = this.handleAnswerChange.bind(this); // this.handleAnswerChange = this.handleAnswerChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); // this.handleSubmit = this.handleSubmit.bind(this);
} // }
getRandomKanas(amount, include, exclude) { getRandomKanas(amount, include, exclude) {
let randomizedKanas = this.askableKanaKeys.slice(); let randomizedKanas = this.askableKanaKeys.slice();
@ -41,22 +39,22 @@ class Question extends Component {
// let's remove kanas that have the same answer as included // let's remove kanas that have the same answer as included
let searchFor = findRomajisAtKanaKey(include, kanaDictionary)[0]; let searchFor = findRomajisAtKanaKey(include, kanaDictionary)[0];
randomizedKanas = randomizedKanas.filter(function(character) { randomizedKanas = randomizedKanas.filter(character => {
return searchFor!=findRomajisAtKanaKey(character, kanaDictionary)[0]; return searchFor!=findRomajisAtKanaKey(character, kanaDictionary)[0];
}, this); });
// now let's remove "duplicate" kanas (if two kanas have same answers) // now let's remove "duplicate" kanas (if two kanas have same answers)
let tempRandomizedKanas = randomizedKanas.slice(); let tempRandomizedKanas = randomizedKanas.slice();
randomizedKanas = randomizedKanas.filter(function(r) { randomizedKanas = randomizedKanas.filter(r => {
let dupeFound = false; let dupeFound = false;
searchFor = findRomajisAtKanaKey(r, kanaDictionary)[0]; searchFor = findRomajisAtKanaKey(r, kanaDictionary)[0];
tempRandomizedKanas.shift(); tempRandomizedKanas.shift();
tempRandomizedKanas.map(function(w) { tempRandomizedKanas.forEach(w => {
if(findRomajisAtKanaKey(w, kanaDictionary)[0]==searchFor) if(findRomajisAtKanaKey(w, kanaDictionary)[0]==searchFor)
dupeFound = true; dupeFound = true;
}, this); });
return !dupeFound; return !dupeFound;
}, this); });
// alright, let's cut the array and add included to the end // alright, let's cut the array and add included to the end
randomizedKanas = randomizedKanas.slice(0, amount-1); // -1 so we have room to add included randomizedKanas = randomizedKanas.slice(0, amount-1); // -1 so we have room to add included
@ -97,18 +95,18 @@ class Question extends Component {
else if(this.props.stage==4) { else if(this.props.stage==4) {
let tempAllowedAnswers = []; let tempAllowedAnswers = [];
this.currentQuestion.map(function(key, idx) { this.currentQuestion.forEach(key => {
tempAllowedAnswers.push(findRomajisAtKanaKey(key, kanaDictionary)); tempAllowedAnswers.push(findRomajisAtKanaKey(key, kanaDictionary));
}, this); });
cartesianProduct(tempAllowedAnswers).map(function(answer) { cartesianProduct(tempAllowedAnswers).forEach(answer => {
this.allowedAnswers.push(answer.join('')); this.allowedAnswers.push(answer.join(''));
}, this); });
} }
// console.log(this.allowedAnswers); // console.log(this.allowedAnswers);
} }
handleAnswer(answer) { handleAnswer = answer => {
if(this.props.stage<=2) document.activeElement.blur(); // reset answer button's :active if(this.props.stage<=2) document.activeElement.blur(); // reset answer button's :active
this.previousQuestion = this.currentQuestion; this.previousQuestion = this.currentQuestion;
this.setState({previousQuestion: this.previousQuestion}); this.setState({previousQuestion: this.previousQuestion});
@ -120,10 +118,8 @@ class Question extends Component {
else else
this.stageProgress = this.stageProgress > 0 ? this.stageProgress - 1 : 0; this.stageProgress = this.stageProgress > 0 ? this.stageProgress - 1 : 0;
this.setState({stageProgress: this.stageProgress}); this.setState({stageProgress: this.stageProgress});
if(this.stageProgress >= quizSettings.stageLength[this.props.stage] && if(this.stageProgress >= quizSettings.stageLength[this.props.stage] && !this.props.isLocked) {
!this.props.isLocked) { setTimeout(() => { this.props.handleStageUp() }, 300);
let that = this;
setTimeout(function() { that.props.handleStageUp(); }, 300);
} }
else else
this.setNewQuestion(); this.setNewQuestion();
@ -136,22 +132,22 @@ class Question extends Component {
this.previousQuestion = ''; this.previousQuestion = '';
this.previousAnswer = ''; this.previousAnswer = '';
this.stageProgress = 0; this.stageProgress = 0;
Object.keys(kanaDictionary).map(function(whichKana) { Object.keys(kanaDictionary).forEach(whichKana => {
// console.log(whichKana); // 'hiragana' or 'katakana' // console.log(whichKana); // 'hiragana' or 'katakana'
Object.keys(kanaDictionary[whichKana]).map(function(groupName) { Object.keys(kanaDictionary[whichKana]).forEach(groupName => {
// console.log(groupName); // 'h_group1', ... // console.log(groupName); // 'h_group1', ...
// do we want to include this group? // do we want to include this group?
if(arrayContains(groupName, this.props.decidedGroups)) { if(arrayContains(groupName, this.props.decidedGroups)) {
// let's merge the group to our askableKanas // let's merge the group to our askableKanas
this.askableKanas = Object.assign(this.askableKanas, kanaDictionary[whichKana][groupName]['characters']); this.askableKanas = Object.assign(this.askableKanas, kanaDictionary[whichKana][groupName]['characters']);
Object.keys(kanaDictionary[whichKana][groupName]['characters']).map(function(key) { Object.keys(kanaDictionary[whichKana][groupName]['characters']).forEach(key => {
// let's add all askable kana keys to array // let's add all askable kana keys to array
this.askableKanaKeys.push(key); this.askableKanaKeys.push(key);
this.askableRomajis.push(kanaDictionary[whichKana][groupName]['characters'][key][0]); this.askableRomajis.push(kanaDictionary[whichKana][groupName]['characters'][key][0]);
}, this); });
} }
}, this); });
}, this); });
// console.log(this.askableKanas); // console.log(this.askableKanas);
} }
@ -172,12 +168,24 @@ class Question extends Component {
if(this.previousQuestion=='') if(this.previousQuestion=='')
resultString = <div className="previous-result none">Let's go! Which character is this?</div> resultString = <div className="previous-result none">Let's go! Which character is this?</div>
else { else {
let rightAnswer = (this.props.stage==2?findRomajisAtKanaKey(this.previousQuestion, kanaDictionary)[0]:this.previousQuestion.join(''))+' = '+ let rightAnswer = (
this.previousAllowedAnswers[0]; this.props.stage==2 ?
findRomajisAtKanaKey(this.previousQuestion, kanaDictionary)[0]
: this.previousQuestion.join('')
)+' = '+ this.previousAllowedAnswers[0];
if(this.isInAllowedAnswers(this.previousAnswer)) if(this.isInAllowedAnswers(this.previousAnswer))
resultString = <div className="previous-result correct" title="Correct answer!"><span className="pull-left glyphicon glyphicon-none"></span>{rightAnswer}<span className="pull-right glyphicon glyphicon-ok"></span></div> resultString = (
<div className="previous-result correct" title="Correct answer!">
<span className="pull-left glyphicon glyphicon-none"></span>{rightAnswer}<span className="pull-right glyphicon glyphicon-ok"></span>
</div>
);
else else
resultString = <div className="previous-result wrong" title="Wrong answer!"><span className="pull-left glyphicon glyphicon-none"></span>{rightAnswer}<span className="pull-right glyphicon glyphicon-remove"></span></div> resultString = (
<div className="previous-result wrong" title="Wrong answer!">
<span className="pull-left glyphicon glyphicon-none"></span>{rightAnswer}<span className="pull-right glyphicon glyphicon-remove"></span>
</div>
);
} }
return resultString; return resultString;
} }
@ -190,11 +198,11 @@ class Question extends Component {
else return false; else return false;
} }
handleAnswerChange(e) { handleAnswerChange = e => {
this.setState({currentAnswer: e.target.value.replace(/\s+/g, '')}); this.setState({currentAnswer: e.target.value.replace(/\s+/g, '')});
} }
handleSubmit(e) { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
if(this.state.currentAnswer!='') { if(this.state.currentAnswer!='') {
this.handleAnswer(this.state.currentAnswer.toLowerCase()); this.handleAnswer(this.state.currentAnswer.toLowerCase());

View file

@ -4,38 +4,10 @@ import ChooseCharacters from '../ChooseCharacters/ChooseCharacters';
import Game from '../Game/Game'; import Game from '../Game/Game';
class GameContainer extends Component { class GameContainer extends Component {
constructor(props) { state = {
super(props);
this.startGame = this.startGame.bind(this);
this.state = {
stage:1, stage:1,
isLocked: false, isLocked: false,
decidedGroups: ['h_group1'] decidedGroups: JSON.parse(localStorage.getItem('decidedGroups') || null) || []
}
this.stageUp = this.stageUp.bind(this);
this.lockStage = this.lockStage.bind(this);
}
startGame(decidedGroups) {
if(parseInt(this.state.stage)<1 || isNaN(parseInt(this.state.stage)))
this.setState({stage: 1});
else if(parseInt(this.state.stage)>4)
this.setState({stage: 4});
this.setState({decidedGroups: decidedGroups});
this.props.handleStartGame();
}
stageUp() {
this.setState({stage: this.state.stage+1});
}
lockStage(stage, forceLock) {
// if(stage<1 || stage>4) stage=1; // don't use this to allow backspace
if(forceLock)
this.setState({stage: stage, isLocked: true});
else
this.setState({stage: stage, isLocked: !this.state.isLocked});
} }
componentWillReceiveProps() { componentWillReceiveProps() {
@ -43,24 +15,49 @@ class GameContainer extends Component {
this.setState({stage: 1}); this.setState({stage: 1});
} }
startGame = decidedGroups => {
if(parseInt(this.state.stage)<1 || isNaN(parseInt(this.state.stage)))
this.setState({stage: 1});
else if(parseInt(this.state.stage)>4)
this.setState({stage: 4});
this.setState({decidedGroups: decidedGroups});
localStorage.setItem('decidedGroups', JSON.stringify(decidedGroups));
this.props.handleStartGame();
}
stageUp = () => {
this.setState({stage: this.state.stage+1});
}
lockStage = (stage, forceLock) => {
// if(stage<1 || stage>4) stage=1; // don't use this to allow backspace
if(forceLock)
this.setState({stage: stage, isLocked: true});
else
this.setState({stage: stage, isLocked: !this.state.isLocked});
}
render() { render() {
return ( return (
<div> <div>
{ this.props.gameState==='chooseCharacters' ? { this.props.gameState==='chooseCharacters' &&
<ChooseCharacters selectedGroups={this.state.decidedGroups} <ChooseCharacters selectedGroups={this.state.decidedGroups}
handleStartGame={this.startGame} handleStartGame={this.startGame}
stage={this.state.stage} stage={this.state.stage}
isLocked={this.state.isLocked} isLocked={this.state.isLocked}
lockStage={this.lockStage} lockStage={this.lockStage}
/> : '' } />
{ this.props.gameState==='game' ? }
{ this.props.gameState==='game' &&
<Game decidedGroups={this.state.decidedGroups} <Game decidedGroups={this.state.decidedGroups}
handleEndGame={this.props.handleEndGame} handleEndGame={this.props.handleEndGame}
stageUp={this.stageUp} stageUp={this.stageUp}
stage={this.state.stage} stage={this.state.stage}
isLocked={this.state.isLocked} isLocked={this.state.isLocked}
lockStage={this.lockStage} lockStage={this.lockStage}
/> : '' } />
}
</div> </div>
) )
} }

View file

@ -3,21 +3,20 @@ import './Navbar.scss';
class Navbar extends Component { class Navbar extends Component {
render() { render() {
let leftLink;
switch(this.props.gameState) {
case 'chooseCharacters':
default:
leftLink = <li id="nav-kanaquiz"><p className="nav navbar-text">Kana Quiz <span>2</span></p></li>
break;
case 'game':
leftLink = <li id="nav-choosecharacters"><a href="javascript:;" onClick={this.props.handleEndGame}><span className="glyphicon glyphicon-small glyphicon-arrow-left"></span> Back to menu</a></li>
}
return ( return (
<nav className="navbar navbar-inverse navbar-fixed-top" role="navigation"> <nav className="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div className="container"> <div className="container">
<div id="navbar"> <div id="navbar">
<ul className="nav navbar-nav"> <ul className="nav navbar-nav">
{leftLink} {
this.props.gameState == 'game' ? (
<li id="nav-choosecharacters">
<a href="javascript:;" onClick={this.props.handleEndGame}>
<span className="glyphicon glyphicon-small glyphicon-arrow-left"></span> Back to menu
</a>
</li>
) : <li id="nav-kanaquiz"><p className="nav navbar-text">Kana Pro</p></li>
}
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Kana Quiz 2: Learn hiragana & katakana fast and easy</title> <title>Kana Pro: Learn hiragana & katakana fast and easy</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="Description" content="Application for studying hiragana & katakana characters."> <meta name="Description" content="Application for studying hiragana & katakana characters.">
</head> </head>
<body> <body>
<div class="app"></div> <div id="app"></div>
</body> </body>
</html> </html>

View file

@ -1,7 +1,10 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import ReactDOM from 'react-dom';
import Bootstrap from './assets/stylesheets/bootstrap.min.css'; import Bootstrap from './assets/stylesheets/bootstrap.min.css';
import App from './components/App/App'; import App from './components/App/App';
let element = React.createElement(App, {}); let appEl = document.getElementById('app');
render(element, document.querySelector('.app')); if(!appEl) // in case of old index.html in cache
appEl = document.querySelector('.app');
ReactDOM.render(<App />, appEl);