mirror of
https://github.com/hodgef/simple-keyboard.git
synced 2026-04-30 00:00:04 +08:00
First commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import Keyboard from '../lib';
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<Keyboard />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
import Keyboard from './components/Keyboard';
|
||||
export default Keyboard;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user