Cleanup code
This commit is contained in:
parent
eaf95c395e
commit
f6bc13bfb9
|
@ -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/
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/anzzstuff/kanaquiz#readme",
|
"homepage": "https://github.com/anzzstuff/kanaquiz#readme",
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 0.25%",
|
">0.25%",
|
||||||
"not dead"
|
"not dead"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -7,57 +7,50 @@ 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'});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
componentWillUpdate(nextProps, nextState) {
|
||||||
// This is primarily for demo site purposes. Hides #footer when game is on.
|
// This is primarily for demo site purposes. Hides #footer when game is on.
|
||||||
if(document.getElementById('footer')) {
|
if(document.getElementById('footer')) {
|
||||||
if(nextState.gameState=='chooseCharacters')
|
if(nextState.gameState=='chooseCharacters')
|
||||||
document.getElementById('footer').style.display = "block";
|
document.getElementById('footer').style.display = "block";
|
||||||
else
|
else
|
||||||
document.getElementById('footer').style.display = "none";
|
document.getElementById('footer').style.display = "none";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if(document.getElementById('footer'))
|
if(document.getElementById('footer'))
|
||||||
document.getElementById('footer').style.display = "block";
|
document.getElementById('footer').style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Navbar
|
<Navbar
|
||||||
gameState={this.state.gameState}
|
gameState={this.state.gameState}
|
||||||
handleEndGame={this.endGame}
|
handleEndGame={this.endGame}
|
||||||
/>
|
/>
|
||||||
<div className="outercontainer">
|
<div className="outercontainer">
|
||||||
<div className="container game">
|
<div className="container game">
|
||||||
<GameContainer
|
<GameContainer
|
||||||
gameState={this.state.gameState}
|
gameState={this.state.gameState}
|
||||||
handleStartGame={this.startGame}
|
handleStartGame={this.startGame}
|
||||||
handleEndGame={this.endGame}
|
handleEndGame={this.endGame}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
|
@ -1,41 +1,41 @@
|
||||||
html {
|
html {
|
||||||
height: 100%; /* height sets are for android chrome background fix */
|
height: 100%; /* height sets are for android chrome background fix */
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
font-family: "Trebuchet MS","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Tahoma,sans-serif;
|
font-family: "Trebuchet MS","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Tahoma,sans-serif;
|
||||||
background-color: #e5e5e5;
|
background-color: #e5e5e5;
|
||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
.outercontainer {
|
.outercontainer {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
border-bottom: 1px #dadada solid;
|
border-bottom: 1px #dadada solid;
|
||||||
min-height: 500px; /* 690 */
|
min-height: 500px; /* 690 */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 968px;
|
max-width: 968px;
|
||||||
}
|
}
|
||||||
.row {
|
.row {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
.login-button {
|
.login-button {
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
a {
|
a {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.game {
|
.game {
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
}
|
}
|
||||||
.glyphicon-none:before {
|
.glyphicon-none:before {
|
||||||
content: "\2122";
|
content: "\2122";
|
||||||
color: transparent !important;
|
color: transparent !important;
|
||||||
}
|
}
|
|
@ -1,55 +1,50 @@
|
||||||
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;
|
||||||
else return strKanaCharacters;
|
else return strKanaCharacters;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
this.changeShownChars(this.getShowableCharacters('romaji'));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'choose-row' + (this.props.groupName.endsWith('_a') || this.props.groupName.endsWith('_s') ? ' alt-row' : '')
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
this.props.handleToggleSelect(this.props.groupName);
|
||||||
this.changeShownChars(this.getShowableCharacters('romaji'));
|
this.changeShownChars(this.getShowableCharacters('romaji'));
|
||||||
}
|
}}
|
||||||
|
onMouseDown={()=>this.changeShownChars(this.getShowableCharacters('kana'))}
|
||||||
render() {
|
onMouseOut={()=>this.changeShownChars(this.getShowableCharacters('romaji'))}
|
||||||
return (
|
onTouchStart={()=>this.changeShownChars(this.getShowableCharacters('kana'))}
|
||||||
<div className={
|
onTouchEnd={()=>this.changeShownChars(this.getShowableCharacters('romaji'))}
|
||||||
'choose-row' + (this.props.groupName.endsWith('_a') || this.props.groupName.endsWith('_s') ? ' alt-row' : '')
|
>
|
||||||
}
|
<span className={this.props.selected ?
|
||||||
onClick={()=>{
|
'glyphicon glyphicon-small glyphicon-check' :
|
||||||
this.props.handleToggleSelect(this.props.groupName);
|
'glyphicon glyphicon-small glyphicon-unchecked'}></span> {this.state.shownChars}
|
||||||
this.changeShownChars(this.getShowableCharacters('romaji'));
|
</div>
|
||||||
}}
|
);
|
||||||
onMouseDown={()=>this.changeShownChars(this.getShowableCharacters('kana'))}
|
}
|
||||||
onMouseOut={()=>this.changeShownChars(this.getShowableCharacters('romaji'))}
|
|
||||||
onTouchStart={()=>this.changeShownChars(this.getShowableCharacters('kana'))}
|
|
||||||
onTouchEnd={()=>this.changeShownChars(this.getShowableCharacters('romaji'))}
|
|
||||||
>
|
|
||||||
<span className={this.props.selected ?
|
|
||||||
'glyphicon glyphicon-small glyphicon-check' :
|
|
||||||
'glyphicon glyphicon-small glyphicon-unchecked'}></span> {this.state.shownChars}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CharacterGroup;
|
export default CharacterGroup;
|
||||||
|
|
|
@ -5,279 +5,273 @@ import './ChooseCharacters.scss';
|
||||||
import CharacterGroup from './CharacterGroup';
|
import CharacterGroup from './CharacterGroup';
|
||||||
|
|
||||||
class ChooseCharacters extends Component {
|
class ChooseCharacters extends Component {
|
||||||
constructor(props) {
|
state = {
|
||||||
super(props);
|
errMsg : '',
|
||||||
this.state = {
|
selectedGroups: this.props.selectedGroups,
|
||||||
errMsg : '',
|
showAlternatives: [],
|
||||||
selectedGroups: this.props.selectedGroups,
|
showSimilars: [],
|
||||||
showAlternatives: [],
|
startIsVisible: true
|
||||||
showSimilars: [],
|
}
|
||||||
startIsVisible: true
|
|
||||||
}
|
componentDidMount() {
|
||||||
this.toggleSelect = this.toggleSelect.bind(this);
|
this.testIsStartVisible();
|
||||||
this.startGame = this.startGame.bind(this);
|
window.addEventListener('resize', this.testIsStartVisible);
|
||||||
this.testIsStartVisible = this.testIsStartVisible.bind(this);
|
window.addEventListener('scroll', this.testIsStartVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('resize', this.testIsStartVisible);
|
||||||
|
window.removeEventListener('scroll', this.testIsStartVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
this.testIsStartVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
testIsStartVisible = () => {
|
||||||
|
if(this.startRef) {
|
||||||
|
const rect = this.startRef.getBoundingClientRect();
|
||||||
|
if(rect.y > window.innerHeight && this.state.startIsVisible)
|
||||||
|
this.setState({ startIsVisible: false });
|
||||||
|
else if(rect.y <= window.innerHeight && !this.state.startIsVisible)
|
||||||
|
this.setState({ startIsVisible: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
scrollToStart() {
|
||||||
this.testIsStartVisible();
|
if(this.startRef) {
|
||||||
window.addEventListener('resize', this.testIsStartVisible);
|
const rect = this.startRef.getBoundingClientRect();
|
||||||
window.addEventListener('scroll', this.testIsStartVisible);
|
const absTop = rect.top + window.pageYOffset;
|
||||||
|
const scrollPos = absTop - window.innerHeight + 50;
|
||||||
|
window.scrollTo(0, scrollPos > 0 ? scrollPos : 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
getIndex(groupName) {
|
||||||
window.removeEventListener('resize', this.testIsStartVisible);
|
return this.state.selectedGroups.indexOf(groupName);
|
||||||
window.removeEventListener('scroll', this.testIsStartVisible);
|
}
|
||||||
|
|
||||||
|
isSelected(groupName) {
|
||||||
|
return this.getIndex(groupName) > -1 ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSelect(groupName) {
|
||||||
|
if(this.getIndex(groupName)<0)
|
||||||
|
return;
|
||||||
|
let newSelectedGroups = this.state.selectedGroups.slice();
|
||||||
|
newSelectedGroups.splice(this.getIndex(groupName), 1);
|
||||||
|
this.setState({selectedGroups: newSelectedGroups});
|
||||||
|
}
|
||||||
|
|
||||||
|
addSelect(groupName) {
|
||||||
|
this.setState({errMsg: '', selectedGroups: this.state.selectedGroups.concat(groupName)});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSelect = groupName => {
|
||||||
|
if(this.getIndex(groupName) > -1)
|
||||||
|
this.removeSelect(groupName);
|
||||||
|
else
|
||||||
|
this.addSelect(groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAll(whichKana, altOnly=false, similarOnly=false) {
|
||||||
|
const thisKana = kanaDictionary[whichKana];
|
||||||
|
let newSelectedGroups = this.state.selectedGroups.slice();
|
||||||
|
Object.keys(thisKana).forEach(groupName => {
|
||||||
|
if(!this.isSelected(groupName) && (
|
||||||
|
(altOnly && groupName.endsWith('_a')) ||
|
||||||
|
(similarOnly && groupName.endsWith('_s')) ||
|
||||||
|
(!altOnly && !similarOnly)
|
||||||
|
))
|
||||||
|
newSelectedGroups.push(groupName);
|
||||||
|
});
|
||||||
|
this.setState({errMsg: '', selectedGroups: newSelectedGroups});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNone(whichKana, altOnly=false, similarOnly=false) {
|
||||||
|
let newSelectedGroups = [];
|
||||||
|
this.state.selectedGroups.forEach(groupName => {
|
||||||
|
let mustBeRemoved = false;
|
||||||
|
Object.keys(kanaDictionary[whichKana]).forEach(removableGroupName => {
|
||||||
|
if(removableGroupName === groupName && (
|
||||||
|
(altOnly && groupName.endsWith('_a')) ||
|
||||||
|
(similarOnly && groupName.endsWith('_s')) ||
|
||||||
|
(!altOnly && !similarOnly)
|
||||||
|
))
|
||||||
|
mustBeRemoved = true;
|
||||||
|
});
|
||||||
|
if(!mustBeRemoved)
|
||||||
|
newSelectedGroups.push(groupName);
|
||||||
|
});
|
||||||
|
this.setState({selectedGroups: newSelectedGroups});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAlternative(whichKana, postfix) {
|
||||||
|
let show = postfix == '_a' ? this.state.showAlternatives : this.state.showSimilars;
|
||||||
|
const idx = show.indexOf(whichKana);
|
||||||
|
if(idx >= 0)
|
||||||
|
show.splice(idx, 1);
|
||||||
|
else
|
||||||
|
show.push(whichKana)
|
||||||
|
if(postfix == '_a')
|
||||||
|
this.setState({showAlternatives: show});
|
||||||
|
if(postfix == '_s')
|
||||||
|
this.setState({showSimilars: show});
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedAlternatives(whichKana, postfix) {
|
||||||
|
return this.state.selectedGroups.filter(groupName => {
|
||||||
|
return groupName.startsWith(whichKana == 'hiragana' ? 'h_' : 'k_') &&
|
||||||
|
groupName.endsWith(postfix);
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAmountOfAlternatives(whichKana, postfix) {
|
||||||
|
return Object.keys(kanaDictionary[whichKana]).filter(groupName => {
|
||||||
|
return groupName.endsWith(postfix);
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
alternativeToggleRow(whichKana, postfix, show) {
|
||||||
|
let checkBtn = "glyphicon glyphicon-small glyphicon-"
|
||||||
|
let status;
|
||||||
|
if(this.getSelectedAlternatives(whichKana, postfix) >= this.getAmountOfAlternatives(whichKana, postfix))
|
||||||
|
status = 'check';
|
||||||
|
else if(this.getSelectedAlternatives(whichKana, postfix) > 0)
|
||||||
|
status = 'check half';
|
||||||
|
else
|
||||||
|
status = 'unchecked'
|
||||||
|
checkBtn += status
|
||||||
|
|
||||||
|
return <div
|
||||||
|
key={'alt_toggle_' + whichKana + postfix}
|
||||||
|
onClick={() => this.toggleAlternative(whichKana, postfix)}
|
||||||
|
className="choose-row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={checkBtn}
|
||||||
|
onClick={ e => {
|
||||||
|
if(status == 'check')
|
||||||
|
this.selectNone(whichKana, postfix == '_a', postfix == '_s');
|
||||||
|
else if(status == 'check half' || status == 'unchecked')
|
||||||
|
this.selectAll(whichKana, postfix == '_a', postfix == '_s');
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
{
|
||||||
|
show ? <span className="toggle-caret">▲</span>
|
||||||
|
: <span className="toggle-caret">▼</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
postfix == '_a' ? 'Alternative characters (ga · ba · kya..)' :
|
||||||
|
'Look-alike characters'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
showGroupRows(whichKana, showAlternatives, showSimilars = false) {
|
||||||
|
const thisKana = kanaDictionary[whichKana];
|
||||||
|
let rows = [];
|
||||||
|
Object.keys(thisKana).forEach((groupName, idx) => {
|
||||||
|
if(groupName == "h_group11_a" || groupName == "k_group13_a")
|
||||||
|
rows.push(this.alternativeToggleRow(whichKana, "_a", showAlternatives));
|
||||||
|
if(groupName == "k_group11_s")
|
||||||
|
rows.push(this.alternativeToggleRow(whichKana, "_s", showSimilars));
|
||||||
|
|
||||||
|
if((!groupName.endsWith("a") || showAlternatives) &&
|
||||||
|
(!groupName.endsWith("s") || showSimilars)) {
|
||||||
|
rows.push(<CharacterGroup
|
||||||
|
key={idx}
|
||||||
|
groupName={groupName}
|
||||||
|
selected={this.isSelected(groupName)}
|
||||||
|
characters={thisKana[groupName].characters}
|
||||||
|
handleToggleSelect={this.toggleSelect}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
startGame() {
|
||||||
|
if(this.state.selectedGroups.length < 1) {
|
||||||
|
this.setState({ errMsg: 'Choose at least one group!'});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.props.handleStartGame(this.state.selectedGroups);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
render() {
|
||||||
this.testIsStartVisible();
|
return (
|
||||||
}
|
<div className="choose-characters">
|
||||||
|
<div className="row">
|
||||||
testIsStartVisible() {
|
<div className="col-xs-12">
|
||||||
if(this.startRef) {
|
<div className="panel panel-default">
|
||||||
const rect = this.startRef.getBoundingClientRect();
|
<div className="panel-body welcome">
|
||||||
if(rect.y > window.innerHeight && this.state.startIsVisible)
|
<h4>Welcome to Kana Pro!</h4>
|
||||||
this.setState({ startIsVisible: false });
|
<p>Please choose the groups of characters that you'd like to be studying.</p>
|
||||||
else if(rect.y <= window.innerHeight && !this.state.startIsVisible)
|
</div>
|
||||||
this.setState({ startIsVisible: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollToStart() {
|
|
||||||
if(this.startRef) {
|
|
||||||
const rect = this.startRef.getBoundingClientRect();
|
|
||||||
const absTop = rect.top + window.pageYOffset;
|
|
||||||
const scrollPos = absTop - window.innerHeight + 50;
|
|
||||||
window.scrollTo(0, scrollPos > 0 ? scrollPos : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getIndex(groupName) {
|
|
||||||
return this.state.selectedGroups.indexOf(groupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelected(groupName) {
|
|
||||||
return this.getIndex(groupName) > -1 ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeSelect(groupName) {
|
|
||||||
if(this.getIndex(groupName)<0)
|
|
||||||
return;
|
|
||||||
let newSelectedGroups = this.state.selectedGroups.slice();
|
|
||||||
newSelectedGroups.splice(this.getIndex(groupName), 1);
|
|
||||||
this.setState({selectedGroups: newSelectedGroups});
|
|
||||||
}
|
|
||||||
|
|
||||||
addSelect(groupName) {
|
|
||||||
this.setState({errMsg: '', selectedGroups: this.state.selectedGroups.concat(groupName)});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelect(groupName) {
|
|
||||||
if(this.getIndex(groupName) > -1)
|
|
||||||
this.removeSelect(groupName);
|
|
||||||
else
|
|
||||||
this.addSelect(groupName);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectAll(whichKana, altOnly=false, similarOnly=false) {
|
|
||||||
const thisKana = kanaDictionary[whichKana];
|
|
||||||
let newSelectedGroups = this.state.selectedGroups.slice();
|
|
||||||
Object.keys(thisKana).forEach(groupName => {
|
|
||||||
if(!this.isSelected(groupName) && (
|
|
||||||
(altOnly && groupName.endsWith('_a')) ||
|
|
||||||
(similarOnly && groupName.endsWith('_s')) ||
|
|
||||||
(!altOnly && !similarOnly)
|
|
||||||
))
|
|
||||||
newSelectedGroups.push(groupName);
|
|
||||||
});
|
|
||||||
this.setState({errMsg: '', selectedGroups: newSelectedGroups});
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNone(whichKana, altOnly=false, similarOnly=false) {
|
|
||||||
let newSelectedGroups = [];
|
|
||||||
this.state.selectedGroups.forEach(groupName => {
|
|
||||||
let mustBeRemoved = false;
|
|
||||||
Object.keys(kanaDictionary[whichKana]).forEach(removableGroupName => {
|
|
||||||
if(removableGroupName === groupName && (
|
|
||||||
(altOnly && groupName.endsWith('_a')) ||
|
|
||||||
(similarOnly && groupName.endsWith('_s')) ||
|
|
||||||
(!altOnly && !similarOnly)
|
|
||||||
))
|
|
||||||
mustBeRemoved = true;
|
|
||||||
});
|
|
||||||
if(!mustBeRemoved)
|
|
||||||
newSelectedGroups.push(groupName);
|
|
||||||
});
|
|
||||||
this.setState({selectedGroups: newSelectedGroups});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAlternative(whichKana, postfix) {
|
|
||||||
let show = postfix == '_a' ? this.state.showAlternatives : this.state.showSimilars;
|
|
||||||
const idx = show.indexOf(whichKana);
|
|
||||||
if(idx >= 0)
|
|
||||||
show.splice(idx, 1);
|
|
||||||
else
|
|
||||||
show.push(whichKana)
|
|
||||||
if(postfix == '_a')
|
|
||||||
this.setState({showAlternatives: show});
|
|
||||||
if(postfix == '_s')
|
|
||||||
this.setState({showSimilars: show});
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedAlternatives(whichKana, postfix) {
|
|
||||||
return this.state.selectedGroups.filter(groupName => {
|
|
||||||
return groupName.startsWith(whichKana == 'hiragana' ? 'h_' : 'k_') &&
|
|
||||||
groupName.endsWith(postfix);
|
|
||||||
}).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAmountOfAlternatives(whichKana, postfix) {
|
|
||||||
return Object.keys(kanaDictionary[whichKana]).filter(groupName => {
|
|
||||||
return groupName.endsWith(postfix);
|
|
||||||
}).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
alternativeToggleRow(whichKana, postfix, show) {
|
|
||||||
let checkBtn = "glyphicon glyphicon-small glyphicon-"
|
|
||||||
let status;
|
|
||||||
if(this.getSelectedAlternatives(whichKana, postfix) >= this.getAmountOfAlternatives(whichKana, postfix))
|
|
||||||
status = 'check';
|
|
||||||
else if(this.getSelectedAlternatives(whichKana, postfix) > 0)
|
|
||||||
status = 'check half';
|
|
||||||
else
|
|
||||||
status = 'unchecked'
|
|
||||||
checkBtn += status
|
|
||||||
|
|
||||||
return <div
|
|
||||||
key={'alt_toggle_' + whichKana + postfix}
|
|
||||||
onClick={() => this.toggleAlternative(whichKana, postfix)}
|
|
||||||
className="choose-row"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={checkBtn}
|
|
||||||
onClick={ e => {
|
|
||||||
if(status == 'check')
|
|
||||||
this.selectNone(whichKana, postfix == '_a', postfix == '_s');
|
|
||||||
else if(status == 'check half' || status == 'unchecked')
|
|
||||||
this.selectAll(whichKana, postfix == '_a', postfix == '_s');
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
></span>
|
|
||||||
{
|
|
||||||
show ? <span className="toggle-caret">▲</span>
|
|
||||||
: <span className="toggle-caret">▼</span>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
postfix == '_a' ? 'Alternative characters (ga · ba · kya..)' :
|
|
||||||
'Look-alike characters'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
showGroupRows(whichKana, showAlternatives, showSimilars = false) {
|
|
||||||
const thisKana = kanaDictionary[whichKana];
|
|
||||||
let rows = [];
|
|
||||||
Object.keys(thisKana).forEach((groupName, idx) => {
|
|
||||||
if(groupName == "h_group11_a" || groupName == "k_group13_a")
|
|
||||||
rows.push(this.alternativeToggleRow(whichKana, "_a", showAlternatives));
|
|
||||||
if(groupName == "k_group11_s")
|
|
||||||
rows.push(this.alternativeToggleRow(whichKana, "_s", showSimilars));
|
|
||||||
|
|
||||||
if((!groupName.endsWith("a") || showAlternatives) &&
|
|
||||||
(!groupName.endsWith("s") || showSimilars)) {
|
|
||||||
rows.push(<CharacterGroup
|
|
||||||
key={idx}
|
|
||||||
groupName={groupName}
|
|
||||||
selected={this.isSelected(groupName)}
|
|
||||||
characters={thisKana[groupName].characters}
|
|
||||||
handleToggleSelect={this.toggleSelect}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
startGame() {
|
|
||||||
if(this.state.selectedGroups.length < 1) {
|
|
||||||
this.setState({ errMsg: 'Choose at least one group!'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.handleStartGame(this.state.selectedGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="choose-characters">
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-xs-12">
|
|
||||||
<div className="panel panel-default">
|
|
||||||
<div className="panel-body welcome">
|
|
||||||
<h4>Welcome to Kana Quiz!</h4>
|
|
||||||
<p>Please choose the groups of characters that you'd like to be studying.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<div className="panel panel-default">
|
|
||||||
<div className="panel-heading">Hiragana · ひらがな</div>
|
|
||||||
<div className="panel-body selection-areas">
|
|
||||||
{this.showGroupRows('hiragana', this.state.showAlternatives.indexOf('hiragana') >= 0)}
|
|
||||||
</div>
|
|
||||||
<div className="panel-footer text-center">
|
|
||||||
<a href="javascript:;" onClick={()=>this.selectAll('hiragana')}>All</a> · <a href="javascript:;"
|
|
||||||
onClick={()=>this.selectNone('hiragana')}>None</a>
|
|
||||||
· <a href="javascript:;" onClick={()=>this.selectAll('hiragana', true)}>All alternative</a>
|
|
||||||
· <a href="javascript:;" onClick={()=>this.selectNone('hiragana', true)}>No alternative</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-6">
|
|
||||||
<div className="panel panel-default">
|
|
||||||
<div className="panel-heading">Katakana · カタカナ</div>
|
|
||||||
<div className="panel-body selection-areas">
|
|
||||||
{this.showGroupRows('katakana', this.state.showAlternatives.indexOf('katakana') >= 0, this.state.showSimilars.indexOf('katakana') >= 0)}
|
|
||||||
</div>
|
|
||||||
<div className="panel-footer text-center">
|
|
||||||
<a href="javascript:;" onClick={()=>this.selectAll('katakana')}>All</a> · <a href="javascript:;"
|
|
||||||
onClick={()=>this.selectNone('katakana')}>None
|
|
||||||
</a>
|
|
||||||
· <a href="javascript:;" onClick={()=>this.selectAll('katakana', true)}>All alternative</a>
|
|
||||||
· <a href="javascript:;" onClick={()=>this.selectNone('katakana', true)}>No alternative</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-3 col-xs-12 pull-right">
|
|
||||||
<span className="pull-right lock">Lock to stage
|
|
||||||
{
|
|
||||||
this.props.isLocked &&
|
|
||||||
<input className="stage-choice" type="number" min="1" max="4" maxLength="1" size="1"
|
|
||||||
onChange={(e)=>this.props.lockStage(e.target.value, true)}
|
|
||||||
value={this.props.stage}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<Switch onClick={()=>this.props.lockStage(1)} on={this.props.isLocked} /></span>
|
|
||||||
</div>
|
|
||||||
<div className="col-sm-offset-3 col-sm-6 col-xs-12 text-center">
|
|
||||||
{
|
|
||||||
this.state.errMsg != '' &&
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
<div className="down-arrow"
|
|
||||||
style={{display: this.state.startIsVisible ? 'none' : 'block'}}
|
|
||||||
onClick={(e) => this.scrollToStart(e)}
|
|
||||||
>
|
|
||||||
Start
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
</div>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading">Hiragana · ひらがな</div>
|
||||||
|
<div className="panel-body selection-areas">
|
||||||
|
{this.showGroupRows('hiragana', this.state.showAlternatives.indexOf('hiragana') >= 0)}
|
||||||
|
</div>
|
||||||
|
<div className="panel-footer text-center">
|
||||||
|
<a href="javascript:;" onClick={()=>this.selectAll('hiragana')}>All</a> · <a href="javascript:;"
|
||||||
|
onClick={()=>this.selectNone('hiragana')}>None</a>
|
||||||
|
· <a href="javascript:;" onClick={()=>this.selectAll('hiragana', true)}>All alternative</a>
|
||||||
|
· <a href="javascript:;" onClick={()=>this.selectNone('hiragana', true)}>No alternative</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<div className="panel panel-default">
|
||||||
|
<div className="panel-heading">Katakana · カタカナ</div>
|
||||||
|
<div className="panel-body selection-areas">
|
||||||
|
{this.showGroupRows('katakana', this.state.showAlternatives.indexOf('katakana') >= 0, this.state.showSimilars.indexOf('katakana') >= 0)}
|
||||||
|
</div>
|
||||||
|
<div className="panel-footer text-center">
|
||||||
|
<a href="javascript:;" onClick={()=>this.selectAll('katakana')}>All</a> · <a href="javascript:;"
|
||||||
|
onClick={()=>this.selectNone('katakana')}>None
|
||||||
|
</a>
|
||||||
|
· <a href="javascript:;" onClick={()=>this.selectAll('katakana', true)}>All alternative</a>
|
||||||
|
· <a href="javascript:;" onClick={()=>this.selectNone('katakana', true)}>No alternative</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-3 col-xs-12 pull-right">
|
||||||
|
<span className="pull-right lock">Lock to stage
|
||||||
|
{
|
||||||
|
this.props.isLocked &&
|
||||||
|
<input className="stage-choice" type="number" min="1" max="4" maxLength="1" size="1"
|
||||||
|
onChange={(e)=>this.props.lockStage(e.target.value, true)}
|
||||||
|
value={this.props.stage}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<Switch onClick={()=>this.props.lockStage(1)} on={this.props.isLocked} /></span>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-offset-3 col-sm-6 col-xs-12 text-center">
|
||||||
|
{
|
||||||
|
this.state.errMsg != '' &&
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="down-arrow"
|
||||||
|
style={{display: this.state.startIsVisible ? 'none' : 'block'}}
|
||||||
|
onClick={(e) => this.scrollToStart(e)}
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChooseCharacters;
|
export default ChooseCharacters;
|
||||||
|
|
|
@ -1,143 +1,143 @@
|
||||||
.welcome {
|
.welcome {
|
||||||
p:last-child {
|
p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
h4 {
|
h4 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.choose-characters
|
.choose-characters
|
||||||
{
|
{
|
||||||
.panel-heading {
|
.panel-heading {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.panel-heading span {
|
.panel-heading span {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
.panel-footer a {
|
.panel-footer a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #337ab7;
|
color: #337ab7;
|
||||||
}
|
}
|
||||||
.choose-row {
|
.choose-row {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
.choose-row:not(:last-child) {
|
.choose-row:not(:last-child) {
|
||||||
border-bottom: 1px #eee solid;
|
border-bottom: 1px #eee solid;
|
||||||
}
|
}
|
||||||
.alt-row {
|
.alt-row {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
}
|
}
|
||||||
.toggle-caret {
|
.toggle-caret {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.choose-row:hover {
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.choose-row:hover {
|
.choose-row:hover {
|
||||||
cursor: pointer;
|
background-color: #f4f4f4;
|
||||||
}
|
|
||||||
.glyphicon {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.glyphicon-check {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.glyphicon-check.half {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.glyphicon-unchecked {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
.selection-areas {
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
.success-percent {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.choose-row:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.glyphicon {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.glyphicon-check {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.glyphicon-check.half {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
.glyphicon-unchecked {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
.selection-areas {
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
.success-percent {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.error-message {
|
.error-message {
|
||||||
color: #d9534f;
|
color: #d9534f;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
.lock {
|
.lock {
|
||||||
color: #888;
|
color: #888;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.stage-choice {
|
.stage-choice {
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.switch {
|
.switch {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
background: #e5e5e5;
|
background: #e5e5e5;
|
||||||
width: 44px;
|
width: 44px;
|
||||||
height: 19px;
|
height: 19px;
|
||||||
border-radius: 13px;
|
border-radius: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-toggle {
|
.switch-toggle {
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
box-shadow: 1px 1px 1px #ccc;
|
box-shadow: 1px 1px 1px #ccc;
|
||||||
width: 19px;
|
width: 19px;
|
||||||
height: 17px;
|
height: 17px;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #eee;
|
background: #eee;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: left .2s ease-in-out;
|
transition: left .2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch.on {
|
.switch.on {
|
||||||
border: 1px solid #4da94f;
|
border: 1px solid #4da94f;
|
||||||
background: #5cb85c;
|
background: #5cb85c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch.on .switch-toggle {
|
.switch.on .switch-toggle {
|
||||||
left: 23px;
|
left: 23px;
|
||||||
border: 1px solid #b8ffb2;
|
border: 1px solid #b8ffb2;
|
||||||
background: #b8ffb2;
|
background: #b8ffb2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch.disabled {
|
.switch.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.down-arrow {
|
.down-arrow {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px 4px 0 0;
|
border-radius: 4px 4px 0 0;
|
||||||
display: block;
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #d9534f;
|
background: #d9534f;
|
||||||
padding: 7px 0 2px;
|
padding: 7px 0 2px;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.down-arrow:after {
|
.down-arrow:after {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-top: 10px solid #d9534f;
|
border-top: 10px solid #d9534f;
|
||||||
border-right: 30px solid transparent;
|
border-right: 30px solid transparent;
|
||||||
border-bottom: 0 solid transparent;
|
border-bottom: 0 solid transparent;
|
||||||
border-left: 30px solid transparent;
|
border-left: 30px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,42 +4,40 @@ 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 = {
|
componentWillMount() {
|
||||||
showScreen: ''
|
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() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.showScreen==='stage' &&
|
||||||
|
<ShowStage lockStage={this.lockStage} handleShowQuestion={this.showQuestion} handleEndGame={this.props.handleEndGame} stage={this.props.stage} />
|
||||||
}
|
}
|
||||||
this.showQuestion = this.showQuestion.bind(this);
|
{
|
||||||
this.stageUp = this.stageUp.bind(this);
|
this.state.showScreen==='question' &&
|
||||||
this.lockStage = this.lockStage.bind(this);
|
<Question isLocked={this.props.isLocked} handleStageUp={this.stageUp} stage={this.props.stage} decidedGroups={this.props.decidedGroups} />
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
stageUp() {
|
);
|
||||||
this.props.stageUp();
|
}
|
||||||
this.setState({showScreen: 'stage'});
|
|
||||||
}
|
|
||||||
|
|
||||||
lockStage(stage) {
|
|
||||||
this.setState({showScreen: 'question'});
|
|
||||||
this.props.lockStage(stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
showQuestion() {
|
|
||||||
this.setState({showScreen: 'question'})
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.setState({showScreen: 'stage'});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<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} /> : '' }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Game;
|
export default Game;
|
|
@ -5,264 +5,272 @@ 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);
|
previousQuestion: [],
|
||||||
this.state = {
|
previousAnswer: '',
|
||||||
previousQuestion: [],
|
currentAnswer: '',
|
||||||
previousAnswer: '',
|
currentQuestion: [],
|
||||||
currentAnswer: '',
|
answerOptions: [],
|
||||||
currentQuestion: [],
|
stageProgress: 0
|
||||||
answerOptions: [],
|
}
|
||||||
stageProgress: 0
|
// this.setNewQuestion = this.setNewQuestion.bind(this);
|
||||||
|
// this.handleAnswer = this.handleAnswer.bind(this);
|
||||||
|
// this.handleAnswerChange = this.handleAnswerChange.bind(this);
|
||||||
|
// this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
// }
|
||||||
|
|
||||||
|
getRandomKanas(amount, include, exclude) {
|
||||||
|
let randomizedKanas = this.askableKanaKeys.slice();
|
||||||
|
|
||||||
|
if(exclude && exclude.length > 0) {
|
||||||
|
// we're excluding previous question when deciding a new question
|
||||||
|
randomizedKanas = removeFromArray(exclude, randomizedKanas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(include && include.length > 0) {
|
||||||
|
// we arrive here when we're deciding answer options (included = currentQuestion)
|
||||||
|
|
||||||
|
// remove included kana
|
||||||
|
randomizedKanas = removeFromArray(include, randomizedKanas);
|
||||||
|
shuffle(randomizedKanas);
|
||||||
|
|
||||||
|
// cut the size to make looping quicker
|
||||||
|
randomizedKanas = randomizedKanas.slice(0,20);
|
||||||
|
|
||||||
|
// let's remove kanas that have the same answer as included
|
||||||
|
let searchFor = findRomajisAtKanaKey(include, kanaDictionary)[0];
|
||||||
|
randomizedKanas = randomizedKanas.filter(character => {
|
||||||
|
return searchFor!=findRomajisAtKanaKey(character, kanaDictionary)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
// now let's remove "duplicate" kanas (if two kanas have same answers)
|
||||||
|
let tempRandomizedKanas = randomizedKanas.slice();
|
||||||
|
randomizedKanas = randomizedKanas.filter(r => {
|
||||||
|
let dupeFound = false;
|
||||||
|
searchFor = findRomajisAtKanaKey(r, kanaDictionary)[0];
|
||||||
|
tempRandomizedKanas.shift();
|
||||||
|
tempRandomizedKanas.forEach(w => {
|
||||||
|
if(findRomajisAtKanaKey(w, kanaDictionary)[0]==searchFor)
|
||||||
|
dupeFound = true;
|
||||||
|
});
|
||||||
|
return !dupeFound;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.push(include);
|
||||||
|
shuffle(randomizedKanas);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
shuffle(randomizedKanas);
|
||||||
|
randomizedKanas = randomizedKanas.slice(0, amount);
|
||||||
|
}
|
||||||
|
return randomizedKanas;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewQuestion() {
|
||||||
|
if(this.props.stage!=4)
|
||||||
|
this.currentQuestion = this.getRandomKanas(1, false, this.previousQuestion);
|
||||||
|
else
|
||||||
|
this.currentQuestion = this.getRandomKanas(3, false, this.previousQuestion);
|
||||||
|
this.setState({currentQuestion: this.currentQuestion});
|
||||||
|
this.setAnswerOptions();
|
||||||
|
this.setAllowedAnswers();
|
||||||
|
// console.log(this.currentQuestion);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnswerOptions() {
|
||||||
|
this.answerOptions = this.getRandomKanas(3, this.currentQuestion[0], false);
|
||||||
|
this.setState({answerOptions: this.answerOptions});
|
||||||
|
// console.log(this.answerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAllowedAnswers() {
|
||||||
|
// console.log(this.currentQuestion);
|
||||||
|
this.allowedAnswers = [];
|
||||||
|
if(this.props.stage==1 || this.props.stage==3)
|
||||||
|
this.allowedAnswers = findRomajisAtKanaKey(this.currentQuestion, kanaDictionary);
|
||||||
|
else if(this.props.stage==2)
|
||||||
|
this.allowedAnswers = this.currentQuestion;
|
||||||
|
else if(this.props.stage==4) {
|
||||||
|
let tempAllowedAnswers = [];
|
||||||
|
|
||||||
|
this.currentQuestion.forEach(key => {
|
||||||
|
tempAllowedAnswers.push(findRomajisAtKanaKey(key, kanaDictionary));
|
||||||
|
});
|
||||||
|
|
||||||
|
cartesianProduct(tempAllowedAnswers).forEach(answer => {
|
||||||
|
this.allowedAnswers.push(answer.join(''));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// console.log(this.allowedAnswers);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAnswer = answer => {
|
||||||
|
if(this.props.stage<=2) document.activeElement.blur(); // reset answer button's :active
|
||||||
|
this.previousQuestion = this.currentQuestion;
|
||||||
|
this.setState({previousQuestion: this.previousQuestion});
|
||||||
|
this.previousAnswer = answer;
|
||||||
|
this.setState({previousAnswer: this.previousAnswer});
|
||||||
|
this.previousAllowedAnswers = this.allowedAnswers;
|
||||||
|
if(this.isInAllowedAnswers(this.previousAnswer))
|
||||||
|
this.stageProgress = this.stageProgress+1;
|
||||||
|
else
|
||||||
|
this.stageProgress = this.stageProgress > 0 ? this.stageProgress - 1 : 0;
|
||||||
|
this.setState({stageProgress: this.stageProgress});
|
||||||
|
if(this.stageProgress >= quizSettings.stageLength[this.props.stage] && !this.props.isLocked) {
|
||||||
|
setTimeout(() => { this.props.handleStageUp() }, 300);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.setNewQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeCharacters() {
|
||||||
|
this.askableKanas = {};
|
||||||
|
this.askableKanaKeys = [];
|
||||||
|
this.askableRomajis = [];
|
||||||
|
this.previousQuestion = '';
|
||||||
|
this.previousAnswer = '';
|
||||||
|
this.stageProgress = 0;
|
||||||
|
Object.keys(kanaDictionary).forEach(whichKana => {
|
||||||
|
// console.log(whichKana); // 'hiragana' or 'katakana'
|
||||||
|
Object.keys(kanaDictionary[whichKana]).forEach(groupName => {
|
||||||
|
// console.log(groupName); // 'h_group1', ...
|
||||||
|
// do we want to include this group?
|
||||||
|
if(arrayContains(groupName, this.props.decidedGroups)) {
|
||||||
|
// let's merge the group to our askableKanas
|
||||||
|
this.askableKanas = Object.assign(this.askableKanas, kanaDictionary[whichKana][groupName]['characters']);
|
||||||
|
Object.keys(kanaDictionary[whichKana][groupName]['characters']).forEach(key => {
|
||||||
|
// let's add all askable kana keys to array
|
||||||
|
this.askableKanaKeys.push(key);
|
||||||
|
this.askableRomajis.push(kanaDictionary[whichKana][groupName]['characters'][key][0]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.setNewQuestion = this.setNewQuestion.bind(this);
|
});
|
||||||
this.handleAnswer = this.handleAnswer.bind(this);
|
});
|
||||||
this.handleAnswerChange = this.handleAnswerChange.bind(this);
|
// console.log(this.askableKanas);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getRandomKanas(amount, include, exclude) {
|
getAnswerType() {
|
||||||
let randomizedKanas = this.askableKanaKeys.slice();
|
if(this.props.stage==2) return 'kana';
|
||||||
|
else return 'romaji';
|
||||||
|
}
|
||||||
|
|
||||||
if(exclude && exclude.length > 0) {
|
getShowableQuestion() {
|
||||||
// we're excluding previous question when deciding a new question
|
if(this.getAnswerType()=='kana')
|
||||||
randomizedKanas = removeFromArray(exclude, randomizedKanas);
|
return findRomajisAtKanaKey(this.state.currentQuestion, kanaDictionary)[0];
|
||||||
}
|
else return this.state.currentQuestion;
|
||||||
|
}
|
||||||
|
|
||||||
if(include && include.length > 0) {
|
getPreviousResult() {
|
||||||
// we arrive here when we're deciding answer options (included = currentQuestion)
|
let resultString='';
|
||||||
|
// console.log(this.previousAnswer);
|
||||||
|
if(this.previousQuestion=='')
|
||||||
|
resultString = <div className="previous-result none">Let's go! Which character is this?</div>
|
||||||
|
else {
|
||||||
|
let rightAnswer = (
|
||||||
|
this.props.stage==2 ?
|
||||||
|
findRomajisAtKanaKey(this.previousQuestion, kanaDictionary)[0]
|
||||||
|
: this.previousQuestion.join('')
|
||||||
|
)+' = '+ this.previousAllowedAnswers[0];
|
||||||
|
|
||||||
// remove included kana
|
if(this.isInAllowedAnswers(this.previousAnswer))
|
||||||
randomizedKanas = removeFromArray(include, randomizedKanas);
|
resultString = (
|
||||||
shuffle(randomizedKanas);
|
<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>
|
||||||
// cut the size to make looping quicker
|
</div>
|
||||||
randomizedKanas = randomizedKanas.slice(0,20);
|
);
|
||||||
|
else
|
||||||
// let's remove kanas that have the same answer as included
|
resultString = (
|
||||||
let searchFor = findRomajisAtKanaKey(include, kanaDictionary)[0];
|
<div className="previous-result wrong" title="Wrong answer!">
|
||||||
randomizedKanas = randomizedKanas.filter(function(character) {
|
<span className="pull-left glyphicon glyphicon-none"></span>{rightAnswer}<span className="pull-right glyphicon glyphicon-remove"></span>
|
||||||
return searchFor!=findRomajisAtKanaKey(character, kanaDictionary)[0];
|
</div>
|
||||||
}, this);
|
|
||||||
|
|
||||||
// now let's remove "duplicate" kanas (if two kanas have same answers)
|
|
||||||
let tempRandomizedKanas = randomizedKanas.slice();
|
|
||||||
randomizedKanas = randomizedKanas.filter(function(r) {
|
|
||||||
let dupeFound = false;
|
|
||||||
searchFor = findRomajisAtKanaKey(r, kanaDictionary)[0];
|
|
||||||
tempRandomizedKanas.shift();
|
|
||||||
tempRandomizedKanas.map(function(w) {
|
|
||||||
if(findRomajisAtKanaKey(w, kanaDictionary)[0]==searchFor)
|
|
||||||
dupeFound = true;
|
|
||||||
}, this);
|
|
||||||
return !dupeFound;
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
// 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.push(include);
|
|
||||||
shuffle(randomizedKanas);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
shuffle(randomizedKanas);
|
|
||||||
randomizedKanas = randomizedKanas.slice(0, amount);
|
|
||||||
}
|
|
||||||
return randomizedKanas;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewQuestion() {
|
|
||||||
if(this.props.stage!=4)
|
|
||||||
this.currentQuestion = this.getRandomKanas(1, false, this.previousQuestion);
|
|
||||||
else
|
|
||||||
this.currentQuestion = this.getRandomKanas(3, false, this.previousQuestion);
|
|
||||||
this.setState({currentQuestion: this.currentQuestion});
|
|
||||||
this.setAnswerOptions();
|
|
||||||
this.setAllowedAnswers();
|
|
||||||
// console.log(this.currentQuestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnswerOptions() {
|
|
||||||
this.answerOptions = this.getRandomKanas(3, this.currentQuestion[0], false);
|
|
||||||
this.setState({answerOptions: this.answerOptions});
|
|
||||||
// console.log(this.answerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAllowedAnswers() {
|
|
||||||
// console.log(this.currentQuestion);
|
|
||||||
this.allowedAnswers = [];
|
|
||||||
if(this.props.stage==1 || this.props.stage==3)
|
|
||||||
this.allowedAnswers = findRomajisAtKanaKey(this.currentQuestion, kanaDictionary);
|
|
||||||
else if(this.props.stage==2)
|
|
||||||
this.allowedAnswers = this.currentQuestion;
|
|
||||||
else if(this.props.stage==4) {
|
|
||||||
let tempAllowedAnswers = [];
|
|
||||||
|
|
||||||
this.currentQuestion.map(function(key, idx) {
|
|
||||||
tempAllowedAnswers.push(findRomajisAtKanaKey(key, kanaDictionary));
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
cartesianProduct(tempAllowedAnswers).map(function(answer) {
|
|
||||||
this.allowedAnswers.push(answer.join(''));
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
// console.log(this.allowedAnswers);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAnswer(answer) {
|
|
||||||
if(this.props.stage<=2) document.activeElement.blur(); // reset answer button's :active
|
|
||||||
this.previousQuestion = this.currentQuestion;
|
|
||||||
this.setState({previousQuestion: this.previousQuestion});
|
|
||||||
this.previousAnswer = answer;
|
|
||||||
this.setState({previousAnswer: this.previousAnswer});
|
|
||||||
this.previousAllowedAnswers = this.allowedAnswers;
|
|
||||||
if(this.isInAllowedAnswers(this.previousAnswer))
|
|
||||||
this.stageProgress = this.stageProgress+1;
|
|
||||||
else
|
|
||||||
this.stageProgress = this.stageProgress > 0 ? this.stageProgress - 1 : 0;
|
|
||||||
this.setState({stageProgress: this.stageProgress});
|
|
||||||
if(this.stageProgress >= quizSettings.stageLength[this.props.stage] &&
|
|
||||||
!this.props.isLocked) {
|
|
||||||
let that = this;
|
|
||||||
setTimeout(function() { that.props.handleStageUp(); }, 300);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.setNewQuestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeCharacters() {
|
|
||||||
this.askableKanas = {};
|
|
||||||
this.askableKanaKeys = [];
|
|
||||||
this.askableRomajis = [];
|
|
||||||
this.previousQuestion = '';
|
|
||||||
this.previousAnswer = '';
|
|
||||||
this.stageProgress = 0;
|
|
||||||
Object.keys(kanaDictionary).map(function(whichKana) {
|
|
||||||
// console.log(whichKana); // 'hiragana' or 'katakana'
|
|
||||||
Object.keys(kanaDictionary[whichKana]).map(function(groupName) {
|
|
||||||
// console.log(groupName); // 'h_group1', ...
|
|
||||||
// do we want to include this group?
|
|
||||||
if(arrayContains(groupName, this.props.decidedGroups)) {
|
|
||||||
// let's merge the group to our askableKanas
|
|
||||||
this.askableKanas = Object.assign(this.askableKanas, kanaDictionary[whichKana][groupName]['characters']);
|
|
||||||
Object.keys(kanaDictionary[whichKana][groupName]['characters']).map(function(key) {
|
|
||||||
// let's add all askable kana keys to array
|
|
||||||
this.askableKanaKeys.push(key);
|
|
||||||
this.askableRomajis.push(kanaDictionary[whichKana][groupName]['characters'][key][0]);
|
|
||||||
}, this);
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
}, this);
|
|
||||||
// console.log(this.askableKanas);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAnswerType() {
|
|
||||||
if(this.props.stage==2) return 'kana';
|
|
||||||
else return 'romaji';
|
|
||||||
}
|
|
||||||
|
|
||||||
getShowableQuestion() {
|
|
||||||
if(this.getAnswerType()=='kana')
|
|
||||||
return findRomajisAtKanaKey(this.state.currentQuestion, kanaDictionary)[0];
|
|
||||||
else return this.state.currentQuestion;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreviousResult() {
|
|
||||||
let resultString='';
|
|
||||||
// console.log(this.previousAnswer);
|
|
||||||
if(this.previousQuestion=='')
|
|
||||||
resultString = <div className="previous-result none">Let's go! Which character is this?</div>
|
|
||||||
else {
|
|
||||||
let rightAnswer = (this.props.stage==2?findRomajisAtKanaKey(this.previousQuestion, kanaDictionary)[0]:this.previousQuestion.join(''))+' = '+
|
|
||||||
this.previousAllowedAnswers[0];
|
|
||||||
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>
|
|
||||||
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>
|
|
||||||
}
|
|
||||||
return resultString;
|
|
||||||
}
|
|
||||||
|
|
||||||
isInAllowedAnswers(previousAnswer) {
|
|
||||||
// console.log(previousAnswer);
|
|
||||||
// console.log(this.allowedAnswers);
|
|
||||||
if(arrayContains(previousAnswer, this.previousAllowedAnswers))
|
|
||||||
return true;
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAnswerChange(e) {
|
|
||||||
this.setState({currentAnswer: e.target.value.replace(/\s+/g, '')});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if(this.state.currentAnswer!='') {
|
|
||||||
this.handleAnswer(this.state.currentAnswer.toLowerCase());
|
|
||||||
this.setState({currentAnswer: ''});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.initializeCharacters();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setNewQuestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let btnClass = "btn btn-default answer-button";
|
|
||||||
if ('ontouchstart' in window)
|
|
||||||
btnClass += " no-hover"; // disables hover effect on touch screens
|
|
||||||
let stageProgressPercentage = Math.round((this.state.stageProgress/quizSettings.stageLength[this.props.stage])*100)+'%';
|
|
||||||
let stageProgressPercentageStyle = { width: stageProgressPercentage }
|
|
||||||
return (
|
|
||||||
<div className="text-center question col-xs-12">
|
|
||||||
{this.getPreviousResult()}
|
|
||||||
<div className="big-character">{this.getShowableQuestion()}</div>
|
|
||||||
<div className="answer-container">
|
|
||||||
{this.props.stage<3?this.state.answerOptions.map(function(answer, idx) {
|
|
||||||
return <AnswerButton answer={answer}
|
|
||||||
className={btnClass}
|
|
||||||
key={idx}
|
|
||||||
answertype={this.getAnswerType()}
|
|
||||||
handleAnswer={this.handleAnswer} />
|
|
||||||
}, this):
|
|
||||||
<div className="answer-form-container">
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<input autoFocus className="answer-input" type="text" value={this.state.currentAnswer} onChange={this.handleAnswerChange} />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className="progress">
|
|
||||||
<div className="progress-bar progress-bar-info"
|
|
||||||
role="progressbar"
|
|
||||||
aria-valuenow={this.state.stageProgress}
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax={quizSettings.stageLength[this.props.stage]}
|
|
||||||
style={stageProgressPercentageStyle}
|
|
||||||
>
|
|
||||||
<span>Stage {this.props.stage} {this.props.isLocked?' (Locked)':''}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return resultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInAllowedAnswers(previousAnswer) {
|
||||||
|
// console.log(previousAnswer);
|
||||||
|
// console.log(this.allowedAnswers);
|
||||||
|
if(arrayContains(previousAnswer, this.previousAllowedAnswers))
|
||||||
|
return true;
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAnswerChange = e => {
|
||||||
|
this.setState({currentAnswer: e.target.value.replace(/\s+/g, '')});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
if(this.state.currentAnswer!='') {
|
||||||
|
this.handleAnswer(this.state.currentAnswer.toLowerCase());
|
||||||
|
this.setState({currentAnswer: ''});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.initializeCharacters();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setNewQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let btnClass = "btn btn-default answer-button";
|
||||||
|
if ('ontouchstart' in window)
|
||||||
|
btnClass += " no-hover"; // disables hover effect on touch screens
|
||||||
|
let stageProgressPercentage = Math.round((this.state.stageProgress/quizSettings.stageLength[this.props.stage])*100)+'%';
|
||||||
|
let stageProgressPercentageStyle = { width: stageProgressPercentage }
|
||||||
|
return (
|
||||||
|
<div className="text-center question col-xs-12">
|
||||||
|
{this.getPreviousResult()}
|
||||||
|
<div className="big-character">{this.getShowableQuestion()}</div>
|
||||||
|
<div className="answer-container">
|
||||||
|
{this.props.stage<3?this.state.answerOptions.map(function(answer, idx) {
|
||||||
|
return <AnswerButton answer={answer}
|
||||||
|
className={btnClass}
|
||||||
|
key={idx}
|
||||||
|
answertype={this.getAnswerType()}
|
||||||
|
handleAnswer={this.handleAnswer} />
|
||||||
|
}, this):
|
||||||
|
<div className="answer-form-container">
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<input autoFocus className="answer-input" type="text" value={this.state.currentAnswer} onChange={this.handleAnswerChange} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="progress">
|
||||||
|
<div className="progress-bar progress-bar-info"
|
||||||
|
role="progressbar"
|
||||||
|
aria-valuenow={this.state.stageProgress}
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax={quizSettings.stageLength[this.props.stage]}
|
||||||
|
style={stageProgressPercentageStyle}
|
||||||
|
>
|
||||||
|
<span>Stage {this.props.stage} {this.props.isLocked?' (Locked)':''}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnswerButton extends Component {
|
class AnswerButton extends Component {
|
||||||
getShowableAnswer() {
|
getShowableAnswer() {
|
||||||
if(this.props.answertype=='romaji')
|
if(this.props.answertype=='romaji')
|
||||||
return findRomajisAtKanaKey(this.props.answer, kanaDictionary)[0];
|
return findRomajisAtKanaKey(this.props.answer, kanaDictionary)[0];
|
||||||
else return this.props.answer;
|
else return this.props.answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<button className={this.props.className} onClick={()=>this.props.handleAnswer(this.getShowableAnswer())}>{this.getShowableAnswer()}</button>
|
<button className={this.props.className} onClick={()=>this.props.handleAnswer(this.getShowableAnswer())}>{this.getShowableAnswer()}</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default Question;
|
export default Question;
|
|
@ -1,87 +1,87 @@
|
||||||
.question {
|
.question {
|
||||||
.progress {
|
.progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.progress span {
|
}
|
||||||
position: absolute;
|
.progress span {
|
||||||
top: 5px;
|
position: absolute;
|
||||||
color: #444;
|
top: 5px;
|
||||||
display: block;
|
color: #444;
|
||||||
width: 100%;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.previous-result{
|
||||||
|
max-width: 360px;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 30px auto 28px;
|
||||||
|
border-radius: 3px;
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
margin: 0px auto 0px;
|
||||||
}
|
}
|
||||||
.previous-result{
|
}
|
||||||
max-width: 360px;
|
.size-up {
|
||||||
padding: 8px;
|
font-size: 1.1em;
|
||||||
margin: 30px auto 28px;
|
}
|
||||||
border-radius: 3px;
|
.none {
|
||||||
@media (max-width: 768px) {
|
background-color: #aaa;
|
||||||
margin: 0px auto 0px;
|
color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
.correct {
|
||||||
|
color: #f5f5f5;
|
||||||
|
background-color: #5cb85c;
|
||||||
|
}
|
||||||
|
.wrong {
|
||||||
|
color: #f5f5f5;
|
||||||
|
background-color: #d9534f;
|
||||||
|
}
|
||||||
|
.previous-result-none {
|
||||||
|
max-width: 360px;
|
||||||
|
padding: 6px;
|
||||||
|
margin: 30px auto;
|
||||||
|
color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.big-character {
|
||||||
|
font-size: 5em;
|
||||||
|
}
|
||||||
|
.answer-container {
|
||||||
|
max-width: 360px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.answer-form-container {
|
||||||
|
max-width: 100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.answer-button {
|
||||||
|
min-width: 90px;
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 30px 0 60px;
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
margin: 0px 0 15px;
|
||||||
}
|
}
|
||||||
.size-up {
|
}
|
||||||
font-size: 1.1em;
|
.answer-input, .answer-input:focus {
|
||||||
}
|
outline: none;
|
||||||
.none {
|
width: 110px;
|
||||||
background-color: #aaa;
|
text-align: center;
|
||||||
color: #f5f5f5;
|
font-size: 2em;
|
||||||
}
|
margin: 25px 0 60px;
|
||||||
.correct {
|
background: none;
|
||||||
color: #f5f5f5;
|
border: none;
|
||||||
background-color: #5cb85c;
|
border-bottom: solid 1px #aaa;
|
||||||
}
|
@media (max-width: 768px) {
|
||||||
.wrong {
|
margin: 0px 0 25px;
|
||||||
color: #f5f5f5;
|
|
||||||
background-color: #d9534f;
|
|
||||||
}
|
|
||||||
.previous-result-none {
|
|
||||||
max-width: 360px;
|
|
||||||
padding: 6px;
|
|
||||||
margin: 30px auto;
|
|
||||||
color: #f5f5f5;
|
|
||||||
}
|
|
||||||
.big-character {
|
|
||||||
font-size: 5em;
|
|
||||||
}
|
|
||||||
.answer-container {
|
|
||||||
max-width: 360px;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.answer-form-container {
|
|
||||||
max-width: 100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.answer-button {
|
|
||||||
min-width: 90px;
|
|
||||||
font-size: 2em;
|
|
||||||
margin: 30px 0 60px;
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
margin: 0px 0 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.answer-input, .answer-input:focus {
|
|
||||||
outline: none;
|
|
||||||
width: 110px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 2em;
|
|
||||||
margin: 25px 0 60px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
border-bottom: solid 1px #aaa;
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
margin: 0px 0 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.no-hover {
|
|
||||||
/* disables hover effect on touch screens */
|
|
||||||
background-color: #fff;
|
|
||||||
border-color: #ccc;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.no-hover {
|
||||||
|
/* disables hover effect on touch screens */
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,66 +4,63 @@ 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);
|
stage:1,
|
||||||
this.startGame = this.startGame.bind(this);
|
isLocked: false,
|
||||||
this.state = {
|
decidedGroups: JSON.parse(localStorage.getItem('decidedGroups') || null) || []
|
||||||
stage:1,
|
}
|
||||||
isLocked: false,
|
|
||||||
decidedGroups: ['h_group1']
|
|
||||||
}
|
|
||||||
this.stageUp = this.stageUp.bind(this);
|
|
||||||
this.lockStage = this.lockStage.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
startGame(decidedGroups) {
|
componentWillReceiveProps() {
|
||||||
if(parseInt(this.state.stage)<1 || isNaN(parseInt(this.state.stage)))
|
if(!this.state.isLocked)
|
||||||
this.setState({stage: 1});
|
this.setState({stage: 1});
|
||||||
else if(parseInt(this.state.stage)>4)
|
}
|
||||||
this.setState({stage: 4});
|
|
||||||
|
|
||||||
this.setState({decidedGroups: decidedGroups});
|
startGame = decidedGroups => {
|
||||||
this.props.handleStartGame();
|
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});
|
||||||
|
|
||||||
stageUp() {
|
this.setState({decidedGroups: decidedGroups});
|
||||||
this.setState({stage: this.state.stage+1});
|
localStorage.setItem('decidedGroups', JSON.stringify(decidedGroups));
|
||||||
}
|
this.props.handleStartGame();
|
||||||
|
}
|
||||||
|
|
||||||
lockStage(stage, forceLock) {
|
stageUp = () => {
|
||||||
// if(stage<1 || stage>4) stage=1; // don't use this to allow backspace
|
this.setState({stage: this.state.stage+1});
|
||||||
if(forceLock)
|
}
|
||||||
this.setState({stage: stage, isLocked: true});
|
|
||||||
else
|
|
||||||
this.setState({stage: stage, isLocked: !this.state.isLocked});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps() {
|
lockStage = (stage, forceLock) => {
|
||||||
if(!this.state.isLocked)
|
// if(stage<1 || stage>4) stage=1; // don't use this to allow backspace
|
||||||
this.setState({stage: 1});
|
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' ?
|
}
|
||||||
<Game decidedGroups={this.state.decidedGroups}
|
{ this.props.gameState==='game' &&
|
||||||
handleEndGame={this.props.handleEndGame}
|
<Game decidedGroups={this.state.decidedGroups}
|
||||||
stageUp={this.stageUp}
|
handleEndGame={this.props.handleEndGame}
|
||||||
stage={this.state.stage}
|
stageUp={this.stageUp}
|
||||||
isLocked={this.state.isLocked}
|
stage={this.state.stage}
|
||||||
lockStage={this.lockStage}
|
isLocked={this.state.isLocked}
|
||||||
/> : '' }
|
lockStage={this.lockStage}
|
||||||
</div>
|
/>
|
||||||
)
|
}
|
||||||
}
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GameContainer;
|
export default GameContainer;
|
|
@ -2,28 +2,27 @@ import React, { Component } from 'react';
|
||||||
import './Navbar.scss';
|
import './Navbar.scss';
|
||||||
|
|
||||||
class Navbar extends Component {
|
class Navbar extends Component {
|
||||||
render() {
|
render() {
|
||||||
let leftLink;
|
return (
|
||||||
switch(this.props.gameState) {
|
<nav className="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||||
case 'chooseCharacters':
|
<div className="container">
|
||||||
default:
|
<div id="navbar">
|
||||||
leftLink = <li id="nav-kanaquiz"><p className="nav navbar-text">Kana Quiz <span>2</span></p></li>
|
<ul className="nav navbar-nav">
|
||||||
break;
|
{
|
||||||
case 'game':
|
this.props.gameState == '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>
|
<li id="nav-choosecharacters">
|
||||||
}
|
<a href="javascript:;" onClick={this.props.handleEndGame}>
|
||||||
return (
|
<span className="glyphicon glyphicon-small glyphicon-arrow-left"></span> Back to menu
|
||||||
<nav className="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
</a>
|
||||||
<div className="container">
|
</li>
|
||||||
<div id="navbar">
|
) : <li id="nav-kanaquiz"><p className="nav navbar-text">Kana Pro</p></li>
|
||||||
<ul className="nav navbar-nav">
|
}
|
||||||
{leftLink}
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Navbar;
|
export default Navbar;
|
|
@ -1,15 +1,15 @@
|
||||||
#navbar ul li {
|
#navbar ul li {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
font-size: 1.0em;
|
font-size: 1.0em;
|
||||||
}
|
}
|
||||||
.glyphicon {
|
.glyphicon {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: #d9534f;
|
color: #d9534f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navbar-inverse {
|
.navbar-inverse {
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
.hidden-nano {
|
.hidden-nano {
|
||||||
@media (max-width: 390px) {
|
@media (max-width: 390px) {
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
border-width: 0 1px 1px;
|
border-width: 0 1px 1px;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||||
}
|
}
|
||||||
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
|
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
|
@ -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>
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue