Skip to content

Instantly share code, notes, and snippets.

@sayes2x
Last active May 1, 2018 16:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sayes2x/b151e5cf30409ab74c93e8d00d6e9f1b to your computer and use it in GitHub Desktop.
Save sayes2x/b151e5cf30409ab74c93e8d00d6e9f1b to your computer and use it in GitHub Desktop.
Calculator
<div id="root"></div>
const calcMain = (key, ...que) => {
const calc = {};
// handle next key press after equal
if(/=/.test(que[que.length - 2])) {
// if operation continue calculating
if(/[\+\-x÷]/.test(key)) {
que = [que[que.length - 1]]
// if anything else, start over
} else {
que = [];
}
}
// Determine calc.que
const number = /\d/.test(key);
if (number) calc.que = keyNumber(key, ...que);
const operation = /[\+\-x÷]/.test(key);
if (operation) calc.que = keyOperation(key, ...que);
const equal = /=/.test(key);
if (equal) calc.que = keyEqual(key, ...que);
const allClear = /AC/.test(key);
if (allClear) calc.que = keyAllClear();
const clear = /^C/.test(key);
if (clear) calc.que = keyClear(...que);
const dot = /\./.test(key);
if (dot) calc.que = keyDot(key, ...que);
const percent = /%/.test(key);
if (percent) calc.que = keyPercent(key, ...que);
// Determine calc.result
if (calc.que.length === 0) {
calc.result = '0';
} else if (calc.que[calc.que.length - 1] === 'Divide by zero error!') {
calc.result = 'ERROR';
} else {
calc.result = lastNumber(...calc.que);
}
// Determine calc.displayQue
if (calc.result === 'ERROR') {
calc.displayQue = calc.que[calc.que.length - 1];
} else {
calc.displayQue = calc.que.join('').slice(-24);
}
// return calc object
return calc;
};
// returns last string that is a 'number' in an array
// this will always return what should be displayed as result
const lastNumber = (...que) => que.filter(n => /\d/.test(n)).pop();
const keyNumber = (key, ...que) => {
// Que conditions
const emptyQue = que.length === 0;
const lastElement = que.length - 1;
const lastElementNumber = /[\d\.]/.test(que[lastElement]);
const lastElementOnlyZero = /^0$/.test(que[lastElement]);
const lastElementOperation = /[\+\-x÷]/.test(que[lastElement]);
let queLengthLessTen;
emptyQue
? (queLengthLessTen = true)
: (queLengthLessTen = que[lastElement].length < 10);
if (queLengthLessTen) {
if (emptyQue || lastElementOperation) {
que.push(key);
} else if (lastElementOnlyZero) {
que.pop();
que.push(key);
} else if (lastElementNumber) {
const newNumber = que.pop().concat(key);
que.push(newNumber);
}
}
return que;
};
const keyOperation = (key, ...que) => {
//Que Conditions
const lastElement = que.length - 1;
const lastElementNumber = /[\d\.]/.test(que[lastElement]);
const lastElementEndDot = /\.$/.test(que[lastElement]);
const lastElementPercent = /%/.test(que[lastElement]);
const lastElementOperation = /[\+\-x÷]/.test(que[lastElement]);
if (lastElementEndDot) {
var numberDot = que.pop();
numberDot = numberDot.replace(/\.$/, '');
que.push(numberDot);
}
if (lastElementNumber || lastElementPercent) {
que.push(key);
}
if (lastElementOperation) {
que.pop();
que.push(key);
}
return que;
};
const keyEqual = (key, ...que) => {
//Que Conditions
const elementCount3Plus = que.length >= 3;
const lastElement = que.length - 1;
const lastElementNumber = /[\d\.]/.test(que[lastElement]);
const lastElementEndDot = /\.$/.test(que[lastElement]);
const lastElementPercent = /%/.test(que[lastElement]);
const lastElementOperation = /[\+\-x÷]/.test(que[lastElement]);
if (elementCount3Plus) {
if (lastElementEndDot) {
var numberDot = que.pop();
numberDot = numberDot.replace(/\.$/, '');
que.push(numberDot);
}
if (lastElementNumber || lastElementPercent) {
que.push(key);
que.push(calculateResult(...que));
}
if (lastElementOperation) {
que.pop();
que.push(key);
que.push(calculateResult(...que));
}
}
return que;
};
const calculateResult = (...que) => {
// Handle %
let percentResult = handlePercent(...que);
// Handle x & ÷
let timesDivideResult = [];
try {
timesDivideResult = handleTimesDivide(...percentResult);
} catch (error) {
timesDivideResult = [error.message];
}
if (timesDivideResult[0] === 'Divide by zero error!')
return timesDivideResult[0];
// Handle + & -
que = handlePlusMinus(...timesDivideResult);
return display(que[0]);
};
const display = result => {
let resultStr = '';
// to prevent numbers like 9999999999 from being rounded to 10000000000
if (result > 999999999 && result < 10000000000) {
resultStr = result.toString();
resultStr = resultStr.slice(0, 10);
// to handle all other cases
} else {
let digits = 9;
resultStr = result.toPrecision(digits);
while (resultStr.length > 10) {
digits -= 1;
resultStr = result.toPrecision(digits);
}
// get rid of unneeded zeros in scientific notation
resultStr = resultStr.replace(/.0+e/, 'e');
}
return +resultStr > 9999999999
? resultStr.toString()
: (+resultStr).toString();
};
const handlePercent = (...que) => {
while (que.findIndex(elem => /%/.test(elem)) !== -1) {
const index = que.findIndex(elem => /%/.test(elem));
que[index - 1] = percentNumber(que[index - 1], que[index - 2]);
que[index - 2] = percentOperator(que[index - 2]);
que.splice(index, 1);
}
return que;
};
const percentNumber = (number, operator) => {
number = number / 100;
if (/\+/.test(operator)) {
number += 1;
} else if (/\-/.test(operator)) {
number = 1 - number;
}
return number;
};
const percentOperator = operator => (/[\+\-]/.test(operator) ? 'x' : operator);
const handleTimesDivide = (...que) => {
while (que.findIndex(elem => /[x÷]/.test(elem)) !== -1) {
const index = que.findIndex(elem => /[x÷]/.test(elem));
if (
(que[index + 1] === '0' || que[index + 1] === 0) &&
/÷/.test(que[index])
) {
throw new Error('Divide by zero error!');
}
const func = /x/.test(que[index]) ? (x, y) => x * y : (x, y) => x / y;
const result = func(+que[index - 1], +que[index + 1]);
que.splice(index - 1, 3, result);
}
return que;
};
const handlePlusMinus = (...que) => {
while (que.findIndex(elem => /\s[\+\-]\s/.test(elem)) !== -1) {
const index = que.findIndex(elem => /\s[\+\-]\s/.test(elem));
const func = /\+/.test(que[index]) ? (x, y) => x + y : (x, y) => x - y;
const result = func(+que[index - 1], +que[index + 1]);
que.splice(index - 1, 3, result);
}
return que;
};
const keyAllClear = () => {
return [];
};
const keyClear = (...que) => {
que.pop();
return que;
};
const keyDot = (key, ...que) => {
//Que Conditions
const emptyQue = que.length === 0;
const lastElement = que.length - 1;
const lastElementOperation = /[\+\-x÷]/.test(que[lastElement]);
const lastElementNumber = /[\d\.]/.test(que[lastElement]);
const lastElementHasDot = /\./.test(que[lastElement]);
if (emptyQue || lastElementOperation) {
que.push('0.');
} else if (lastElementNumber && !lastElementHasDot) {
const newNumber = que.pop().concat(key);
que.push(newNumber);
}
return que;
};
const keyPercent = (key, ...que) => {
//Que Conditions
const elementCount3Plus = que.length >= 3;
const lastElement = que.length - 1;
const lastElementNumber = /[\d\.]/.test(que[lastElement]);
const lastElementEndDot = /\.$/.test(que[lastElement]);
if (elementCount3Plus && lastElementNumber) {
if (lastElementEndDot) {
var numberDot = que.pop();
numberDot = numberDot.replace(/\.$/, '');
que.push(numberDot);
}
que.push(key);
}
return que;
};
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
keys: ['AC', 'C', '%', ' ÷ ', '7', '8', '9', ' x ', '4', '5', '6', ' - ', '1', '2', '3', ' + ', '0', '.', ' = '],
result: 0,
que: [],
displayQue: []
}
}
handleClick = (key) => {
const { que } = this.state;
const calc = calcMain(key, ...que);
this.setState({
result: calc.result,
que: calc.que,
displayQue: calc.displayQue
});
}
handleKeyDown = (event) => {
const pressed = ['Escape', 'Delete', 'a', 'A', 'Backspace', 'c', 'C', 'Enter', '=', '+', '-', '*', '/', 'p', 'P'];
const sent = ['AC', 'AC', 'AC', 'AC', 'C', 'C', 'C', ' = ', ' = ', ' + ', ' - ', ' x ', ' ÷ ', '%', '%'];
let key = '';
if(pressed.indexOf(event.key) !== -1) {
key = sent[pressed.indexOf(event.key)];
}
if(/[0-9\.%]/.test(event.key)) {
key = event.key;
}
if (key !== '') this.handleClick(key);
}
componentDidMount() {
window.addEventListener('keydown', this.handleKeyDown);
window.focus();
}
componentWillUnmount() {
window.removeEventListener('keydown', this.handleKeyDown);
}
render() {
return(
<div>
<div className='calculatorApp'>
<div className="topLeft"></div><div className="top"></div><div className="topRight"></div>
<div className="left"></div>
<div className="calculator">
<Label />
<Screen result={this.state.result} que={this.state.displayQue} />
<Keypad onClick={this.handleClick} keys={this.state.keys} />
</div>
<div className="right"></div>
<div className="bottomLeft"></div><div className="bottom"></div><div className="bottomRight"></div>
</div>
<h3>Designed & Coded by <a href="https://www.scottaprice.com/" target="_blank">Scott A. Price</a></h3>
</div>
);
}
}
const Label = () => {
return(
<p className='label'>Digital Calculator</p>
)
}
const Screen = props => {
return(
<div className='screen'>
<Result result={props.result}/>
<Que que={props.que} />
</div>
)
}
const Result = ({ result }) => {
return(
<p className='result'>{result}</p>
)
}
const Que = ({ que }) => {
return(
<p className='que'>{que}</p>
)
}
const Keypad = props => {
handleClick = key => {
props.onClick(key);
}
keys = props.keys.map(key => {
return(
<Key onClick={handleClick} name={key} />
);
});
return(
<div className='keypad'>{keys}</div>
)
}
const Key = props => {
const { name } = props;
handleClick = () => {
props.onClick(name);
}
let className = 'key';
if(/C/.test(name)) {
className = 'key clear';
} else if(/\+/.test(name)) {
className = 'key twoRows';
} else if(/\d|\./.test(name)) {
className = 'key number';
}
return(
<button onClick={handleClick} className={className} ><span>{name}</span></button>
)
}
ReactDOM.render(
<Calculator />,
document.getElementById("root")
);
<script src="https://fb.me/react-15.1.0.min.js"></script>
<script src="https://fb.me/react-dom-15.1.0.min.js"></script>
:root {
--mult: 1;
}
body {
margin: 25px;
background-image: url("https://s26.postimg.org/s8bnp2l1l/wood-textures-high-quality-1.jpg");
}
.calculatorApp {
width: calc(250px * var(--mult));
height: 416px;
display: grid;
grid-template-rows: 3% 1fr 3%;
grid-template-columns: 3% 1fr 3%;
margin: auto;
border-radius: calc(10px * var(--mult));
box-shadow: calc(10px * var(--mult)) calc(10px * var(--mult)) calc(15px * var(--mult)) #8B642D;
}
.topLeft {
background: radial-gradient(farthest-corner at bottom right,#272C32,#4f5864 50%);
border-top-left-radius: calc(10px * var(--mult));
}
.top {
background: linear-gradient(#4f5864, #272C32);
}
.topRight {
background: linear-gradient(to bottom right, #4f5864, black);
border-top-right-radius: calc(10px * var(--mult));
}
.left {
background: linear-gradient(to right, #4f5864, #272C32);
}
.right {
background: linear-gradient(to right, #272C32, black);
}
.bottomLeft {
background: linear-gradient(to bottom right, #4f5864, black);
border-bottom-left-radius: calc(10px * var(--mult));
}
.bottom {
background: linear-gradient(#272C32, black);
}
.bottomRight {
background: radial-gradient(circle at top left, #272C32, black 50%);
border-bottom-right-radius: calc(10px * var(--mult));
}
.calculator {
background-color: #272C32;
padding: 13px 0;
}
.label {
color: white;
text-align: center;
font-family: 'Montserrat Alternates', sans-serif;
font-weight: bold;
margin: 0;
padding-bottom: 15px;
}
.screen {
background-color: #81A17C;
width: calc(200px * var(--mult));
height: 52px;
margin: auto;
font-family: 'Aldrich', sans-serif;
border-radius: calc(5px * var(--mult));
border-width: calc(3px * var(--mult));
border-style: solid;
border-left-color: #0b0d0e;
border-top-color: #0b0d0e;
border-right-color: #4f5864;
border-bottom-color: #4f5864;
}
.result {
font-weight: bold;
text-align: right;
font-size: calc(27px * var(--mult));
margin: 0;
padding: calc(5px * var(--mult)) calc(5px * var(--mult)) 0;
}
.que {
color: #404040;
text-align: right;
font-size: calc(12px * var(--mult));
margin: 0;
padding: 0 5px;
}
.keypad {
width: calc(200px * var(--mult));
height: 260px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(5, 1fr);
grid-gap: calc(10px * var(--mult));
margin: auto;
padding-top: 15px;
}
.key {
color: white;
background: radial-gradient(#474747, #595959);
font-family: 'Montserrat Alternates', sans-serif;
font-weight: bold;
border-radius: calc(5px * var(--mult));
border-width: calc(1px * var(--mult));
border-style: solid;
border-left-color: #737373;
border-top-color: #737373;
border-right-color: #1a1a1a;
border-bottom-color: #1a1a1a;
}
.key:focus {outline: 0;}
.key:active {
border-left-color: #1a1a1a;
border-top-color: #1a1a1a;
border-right-color: #737373;
border-bottom-color: #737373;
}
span {
position: relative;
}
.key:active span {
top: calc(1px * var(--mult));
left: calc(1px * var(--mult));
}
.twoRows {
grid-row: 4 / span 2;
grid-column-start: 4;
}
.clear {
color: white;
background: radial-gradient(#BF6C80, #c98393);
border-left-color: #d8a6b2;
border-top-color: #d8a6b2;
border-right-color: #8e3e51;
border-bottom-color: #8e3e51;
}
.clear:active {
border-left-color: #8e3e51;
border-top-color: #8e3e51;
border-right-color: #d8a6b2;
border-bottom-color: #d8a6b2;
}
.number {
color: white;
background-color: #88888A;
background: radial-gradient(#88888A, #98989a);
border-left-color: #b2b2b3;
border-top-color: #b2b2b3;
border-right-color: #58585a;
border-bottom-color: #58585a;
}
.number:active {
border-left-color: #58585a;
border-top-color: #58585a;
border-right-color: #b2b2b3;
border-bottom-color: #b2b2b3;
}
h3 {
text-align: center;
font-family: 'Montserrat Alternates', sans-serif;
font-size: 14px;
}
a {
text-decoration: none;
color: #8e3e51;
}
@media screen and (min-width: 600px) {
:root {
--mult: 2;
}
body {
margin: 35px;
}
.calculatorApp {
height: 771px;
}
.calculator {
padding: 17px 0;
}
.label {
font-size: 30px;
padding-bottom: 0;
}
.screen {
height: 100px;
margin: 15px auto 35px;
}
.que {
padding: 0 10px 10px;
}
.keypad {
height: 495px;
padding-top: 0;
}
.key {
font-size: 30px;
}
h3 {
font-size: 20px;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment