Implement keypads module in React
@@ -13,11 +13,6 @@
|
||||
|
||||
<h1>Keep Talking and Nobody Explodes</h1>
|
||||
|
||||
<section id="sectionKeypads">
|
||||
<h2>Keypads</h2>
|
||||
<button class="jsResetSection">Reset</button>
|
||||
</section>
|
||||
|
||||
<section id="sectionSimon">
|
||||
<h2>Simon Says</h2>
|
||||
|
||||
|
||||
@@ -39,54 +39,6 @@ $('section').each(function () {
|
||||
id && $('#navList').append($('<li></li>').append($a));
|
||||
});
|
||||
|
||||
(function () {
|
||||
/**
|
||||
* Keypads
|
||||
*/
|
||||
var columns = [
|
||||
['balloon', 'at', 'upsidedowny', 'squigglyn', 'squidknife', 'hookn', 'leftc'],
|
||||
['euro', 'balloon', 'leftc', 'cursive', 'hollowstar', 'hookn', 'questionmark'],
|
||||
['copyright', 'pumpkin', 'cursive', 'doublek', 'meltedthree', 'upsidedowny', 'hollowstar'],
|
||||
['six', 'paragraph', 'bt', 'squidknife', 'doublek', 'questionmark', 'smileyface'],
|
||||
['pitchfork', 'smileyface', 'bt', 'rightc', 'paragraph', 'dragon', 'filledstar'],
|
||||
['six', 'euro', 'tracks', 'ae', 'pitchfork', 'nwithhat', 'omega']
|
||||
],
|
||||
$section = $('#sectionKeypads'),
|
||||
$imageLists = {}; // name -> $('<img />')[]
|
||||
|
||||
$.each(columns, function (i, column) {
|
||||
var $column = $('<ul data-count="0"></ul>');
|
||||
$section.append($column);
|
||||
|
||||
$.each(column, function (i, name) {
|
||||
var $img = $('<img data-name="' + name + '" src="keypads/' + name + '.png" width="64" height="64" />');
|
||||
$column.append($('<li></li>').append($img));
|
||||
|
||||
name in $imageLists
|
||||
? $imageLists[name].push($img)
|
||||
: $imageLists[name] = [$img];
|
||||
});
|
||||
});
|
||||
|
||||
$section.on('click', 'img', function () {
|
||||
var $img = $(this),
|
||||
name = $img.data('name'),
|
||||
endState = !$img.hasClass('selected'),
|
||||
offset = endState ? 1 : -1;
|
||||
|
||||
$.each($imageLists[name], function (i, $img) {
|
||||
var $column = $img.parent().parent();
|
||||
$img.toggleClass('selected', endState);
|
||||
$column.attr('data-count', parseInt($column.attr('data-count'), 10) + offset);
|
||||
});
|
||||
});
|
||||
|
||||
$section.find('.jsResetSection').on('click', function () {
|
||||
// Also need to reset the indices after removing .selected
|
||||
$section.find('ul').attr('data-count', 0);
|
||||
});
|
||||
})();
|
||||
|
||||
(function () {
|
||||
/**
|
||||
* Simon
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -93,10 +93,6 @@ h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.jsResetSection {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: white;
|
||||
border-left: 1px solid black;
|
||||
@@ -112,28 +108,6 @@ nav {
|
||||
padding: 0.1em 0.5em;
|
||||
}
|
||||
|
||||
#sectionKeypads ul {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
margin-right: 2em;
|
||||
padding-left: 0;
|
||||
}
|
||||
#sectionKeypads li {
|
||||
border: 1px solid black;
|
||||
border-bottom: 0;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
}
|
||||
#sectionKeypads li:last-child {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
#sectionKeypads .selected {
|
||||
background-color: #ff6;
|
||||
}
|
||||
#sectionKeypads ul[data-count='4'] .selected {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
#simonInputs {
|
||||
float: left;
|
||||
margin-right: 5em;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import './App.css';
|
||||
import ButtonModule from "./components/ButtonModule";
|
||||
import KeypadsModule from "./components/KeypadsModule";
|
||||
import WiresModule from "./components/WiresModule";
|
||||
|
||||
function App() {
|
||||
@@ -12,6 +13,7 @@ function App() {
|
||||
|
||||
<WiresModule id="sectionWires" />
|
||||
<ButtonModule id="sectionButton" />
|
||||
<KeypadsModule id="sectionKeypads" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ export default class ButtonModule extends KtaneModule {
|
||||
)
|
||||
}
|
||||
|
||||
resetState() {
|
||||
getInitialState() {
|
||||
// @TODO Put string literals into constants
|
||||
this.state = {
|
||||
return {
|
||||
colour: "blue",
|
||||
text: "Abort"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import KtaneModule from "./KtaneModule";
|
||||
import React from "react";
|
||||
import ResetButton from "./ResetButton";
|
||||
import "../css/KeypadsModule.css";
|
||||
|
||||
export default class KeypadsModule extends KtaneModule {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.toggleHighlight = this.toggleHighlight.bind(this);
|
||||
}
|
||||
|
||||
computeKeyColumnMap() {
|
||||
const map = {};
|
||||
|
||||
this.state.columns.forEach((column, index) => {
|
||||
column.keys.forEach(key => {
|
||||
if (!map.hasOwnProperty(key)) {
|
||||
map[key] = [];
|
||||
}
|
||||
map[key].push(index);
|
||||
});
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["balloon", "at", "upsidedowny", "squigglyn", "squidknife", "hookn", "leftc"]
|
||||
},
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["euro", "balloon", "leftc", "cursive", "hollowstar", "hookn", "questionmark"]
|
||||
},
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["copyright", "pumpkin", "cursive", "doublek", "meltedthree", "upsidedowny", "hollowstar"]
|
||||
},
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["six", "paragraph", "bt", "squidknife", "doublek", "questionmark", "smileyface"]
|
||||
},
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["pitchfork", "smileyface", "bt", "rightc", "paragraph", "dragon", "filledstar"]
|
||||
},
|
||||
{
|
||||
highlightCount: 0,
|
||||
keys: ["six", "euro", "tracks", "ae", "pitchfork", "nwithhat", "omega"]
|
||||
}
|
||||
],
|
||||
highlights: {
|
||||
ae: false,
|
||||
at: false,
|
||||
balloon: false,
|
||||
bt: false,
|
||||
copyright: false,
|
||||
cursive: false,
|
||||
doublek: false,
|
||||
dragon: false,
|
||||
euro: false,
|
||||
filledstar: false,
|
||||
hollowstar: false,
|
||||
hookn: false,
|
||||
leftc: false,
|
||||
meltedthree: false,
|
||||
paragraph: false,
|
||||
pumpkin: false,
|
||||
questionmark: false,
|
||||
rightc: false,
|
||||
six: false,
|
||||
smileyface: false,
|
||||
squidknife: false,
|
||||
squigglyn: false,
|
||||
upsidedowny: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return "Keypads";
|
||||
}
|
||||
|
||||
mainRender() {
|
||||
return (
|
||||
<>
|
||||
<ResetButton onClick={this.resetState} />
|
||||
{
|
||||
this.state.columns.map((column, index) => {
|
||||
return <ul className={column.highlightCount === 4 ? "matching" : ""} key={index}>
|
||||
{
|
||||
column.keys.map(key => {
|
||||
return (
|
||||
<li
|
||||
className={this.state.highlights[key] ? "selected" : ""}
|
||||
data-key={key}
|
||||
key={key}
|
||||
onClick={this.toggleHighlight}
|
||||
>
|
||||
<img alt={key} height="64" width="64" src={`keypads/${key}.png`}/>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>;
|
||||
})
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
|
||||
toggleHighlight(event) {
|
||||
this.keyColumnMap = this.keyColumnMap || this.computeKeyColumnMap();
|
||||
|
||||
const key = event.currentTarget.dataset.key;
|
||||
|
||||
this.setState(state => {
|
||||
const step = state.highlights[key] ? -1 : 1;
|
||||
|
||||
this.keyColumnMap[key].forEach(columnIndex => {
|
||||
state.columns[columnIndex].highlightCount += step;
|
||||
});
|
||||
|
||||
state.highlights[key] = !state.highlights[key];
|
||||
return state;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,13 @@ class KtaneModule extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.resetState();
|
||||
this.state = this.getInitialState();
|
||||
|
||||
this.resetState = this.resetState.bind(this);
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
throw new Error("getInitialState() not implemented.");
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
@@ -27,7 +33,7 @@ class KtaneModule extends Component {
|
||||
}
|
||||
|
||||
resetState() {
|
||||
throw new Error("mainRender() not implemented.");
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import PropTypes from "prop-types";
|
||||
import React, {Component} from "react";
|
||||
import "../css/ResetButton.css";
|
||||
|
||||
export default class ResetButton extends Component {
|
||||
render() {
|
||||
return <button className="jsResetSection" onClick={this.props.onClick}>Reset</button>;
|
||||
}
|
||||
}
|
||||
|
||||
ResetButton.propTypes = {
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
@@ -174,8 +174,8 @@ export default class WiresModule extends KtaneModule {
|
||||
)
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.state = {
|
||||
getInitialState() {
|
||||
return {
|
||||
// @TODO Convert this.allColours to a constant that is available here, i.e., before the constructor's super call
|
||||
counts: ["red", "yellow", "blue", "white", "black"].reduce((counts, colour) => {
|
||||
counts[colour] = 0;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#sectionKeypads ul {
|
||||
display: inline-block;
|
||||
list-style-type: none;
|
||||
margin-right: 2em;
|
||||
padding-left: 0;
|
||||
}
|
||||
#sectionKeypads li {
|
||||
border: 1px solid black;
|
||||
border-bottom: 0;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
}
|
||||
#sectionKeypads li:last-child {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
#sectionKeypads .selected {
|
||||
background-color: #ff6;
|
||||
}
|
||||
#sectionKeypads .matching .selected {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.jsResetSection {
|
||||
display: block;
|
||||
}
|
||||