First commit

This commit is contained in:
Francisco Hodge
2018-03-02 11:31:12 -05:00
commit 4f1111b49e
32 changed files with 13856 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
import React from 'react';
import Keyboard from '../lib';
const App = () => (
<div>
<Keyboard />
</div>
);
export default App;
+7
View File
@@ -0,0 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
+108
View File
@@ -0,0 +1,108 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
+61
View File
@@ -0,0 +1,61 @@
body, html {
margin: 0;
padding: 0;
}
.hodgefkeyboard {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
width: 100%;
user-select: none;
box-sizing: border-box;
overflow: hidden;
}
.hodgefkeyboard .hg-row {
display: flex;
}
.hodgefkeyboard .hg-row:not(:last-child) {
margin-bottom: 5px;
}
.hodgefkeyboard .hg-row .hg-button:not(:last-child) {
margin-right: 5px;
}
.hodgefkeyboard .hg-button {
display: inline-block;
flex-grow: 1;
cursor: pointer;
}
.hodgefkeyboard.hg-layout-default .hg-button.hg-standardBtn {
max-width: 100px;
}
/**
* hg-theme-default theme
*/
.hodgefkeyboard.hg-theme-default {
background-color: rgba(0,0,0,0.2);
padding: 5px;
border-radius: 5px;
}
.hodgefkeyboard.hg-theme-default .hg-button {
box-shadow: 0px 0px 51px -2px rgba(0,0,0,0.3);
height: 40px;
border: 1px solid rgba(0,0,0,0.25);
border-radius: 5px;
box-sizing: border-box;
padding: 5px;
background: white;
}
.hodgefkeyboard.hg-theme-default.hg-layout-numeric .hg-button {
width: 33.3%;
height: 60px;
align-items: center;
display: flex;
justify-content: center;
}
+110
View File
@@ -0,0 +1,110 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Keyboard.css';
// Services
import KeyboardLayout from '../services/KeyboardLayout';
import Utilities from '../services/Utilities';
class App extends Component {
state = {
input: ''
}
componentWillReceiveProps = (nextProps) => {
if(
this.props !== nextProps
){
this.setState({
layoutName: nextProps.layoutName,
layout: nextProps.layout,
themeClass: nextProps.theme
});
}
}
handleButtonClicked = (button) => {
let debug = this.props.debug;
/**
* Ignoring placeholder buttons
*/
if(button === '{//}')
return false;
/**
* Calling onKeyPress
*/
if(typeof this.props.onKeyPress === "function")
this.props.onKeyPress(button);
/**
* Updating input
*/
let updatedInput = Utilities.getUpdatedInput(button, this.state.input);
if(this.state.input !== updatedInput){
this.setState({
input: updatedInput
}, () => {
if(debug){
console.log('Input changed:', this.state.input);
}
/**
* Calling onChange
*/
if(typeof this.props.onChange === "function")
this.props.onChange(this.state.input);
});
}
if(debug){
console.log("Key pressed:", button);
}
}
render() {
let layoutName = this.state.layoutName || "default";
let layout = this.state.layout || KeyboardLayout.getLayout(layoutName);
let themeClass = this.state.theme || `hg-theme-default`;
return (
<div className={`hodgefkeyboard ${themeClass} hg-layout-${layoutName}`}>
{layout.default.map((row, index) => {
let rowArray = row.split(' ');
return (
<div key={`hg-row-${index}`} className="hg-row">
{rowArray.map((button, index) => {
let fctBtnClass = Utilities.getButtonClass(button);
let buttonDisplayName = Utilities.getButtonDisplayName(button, this.props.display);
return (
<div
key={`hg-button-${index}`}
className={`hg-button ${fctBtnClass}`}
onClick={() => this.handleButtonClicked(button)}
><span>{buttonDisplayName}</span></div>
);
})}
</div>
);
})}
</div>
);
}
}
App.propTypes = {
layoutName: PropTypes.string,
layout: PropTypes.array,
theme: PropTypes.string,
display: PropTypes.string,
onChange: PropTypes.func,
onKeyPress: PropTypes.func,
debug: PropTypes.bool
};
export default App;
+8
View File
@@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Keyboard from './Keyboard';
it('Keyboard renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Keyboard />, div);
});
+2
View File
@@ -0,0 +1,2 @@
import Keyboard from './components/Keyboard';
export default Keyboard;
+37
View File
@@ -0,0 +1,37 @@
class KeyboardLayout {
static getLayout = layout => {
if(layout === "qwerty"){
return {
'default': [
'` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
'{tab} q w e r t y u i o p [ ] \\',
'{lock} a s d f g h j k l ; \' {enter}',
'{shift} z x c v b n m , . / {shift}',
'.com @ {space}'
],
'shift': [
'~ ! @ # $ % ^ & * ( ) _ + {bksp}',
'{tab} Q W E R T Y U I O P { } |',
'{lock} A S D F G H J K L : " {enter}',
'{shift} Z X C V B N M < > ? {shift}',
'.com @ {space}'
]
};
} else if(layout === "numeric"){
return {
'default': [
'1 2 3',
'4 5 6',
'7 8 9',
'{//} 0 {bksp}'
]
};
} else {
return KeyboardLayout.getLayout("qwerty");
}
}
}
export default KeyboardLayout;
+83
View File
@@ -0,0 +1,83 @@
class Utilities {
static normalizeString(string){
let output;
if(string === "@")
output = 'at';
else if(string === ",")
output = 'comma';
else if(string === ".")
output = 'dot';
else if(string === "\\")
output = 'backslash';
else if(string === "/")
output = 'fordardslash';
else if(string === "*")
output = 'asterisk';
else if(string === "&")
output = 'ampersand';
else if(string === "$")
output = 'dollarsign';
else if(string === "=")
output = 'equals';
else if(string === "-")
output = 'minus';
else if(string === "'")
output = 'apostrophe';
else if(string === ";")
output = 'colon';
else if(string === "[")
output = 'openbracket';
else if(string === "]")
output = 'closebracket';
else
output = '';
return output ? ` hg-button-${output}` : '';
}
static getButtonClass = button => {
let buttonTypeClass = button.includes("{") ? "functionBtn" : "standardBtn";
let buttonWithoutBraces = button.replace("{", "").replace("}", "");
let buttonNormalized =
buttonTypeClass === "standardBtn" ?
Utilities.normalizeString(buttonWithoutBraces) : ` hg-button-${buttonWithoutBraces}`;
return `hg-${buttonTypeClass}${buttonNormalized}`;
}
static getDefaultDiplay(){
return {
'{bksp}': 'delete',
'{enter}': '< enter',
'{shift}': 'shift',
'{s}': 'shift',
'{tab}': 'tab',
'{lock}': 'caps',
'{accept}': 'Submit',
'{space}': ' ',
'{//}': ' '
};
}
static getButtonDisplayName = (button, display) => {
display = display || Utilities.getDefaultDiplay();
return display[button] || button;
}
static getUpdatedInput = (button, input) => {
let output = input;
if(button === "{bksp}" && output.length > 0)
output = output.slice(0, -1);
else if(button === "{space}")
output = output + ' ';
else if(!button.includes("{") && !button.includes("{"))
output = output + button;
return output;
}
}
export default Utilities;