Skip to content

Instantly share code, notes, and snippets.

@tedp
Last active March 28, 2020 15:18
Show Gist options
  • Save tedp/f45dbc8ae355f10c532fb5359c3672b3 to your computer and use it in GitHub Desktop.
Save tedp/f45dbc8ae355f10c532fb5359c3672b3 to your computer and use it in GitHub Desktop.
Calculator
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
const KeyType = Object.freeze({
numeric: Symbol("numeric"),
operator: Symbol("operator"),
clear: Symbol("clear"),
dot: Symbol("dot"),
result: Symbol("result"),
});
class Button extends React.Component {
handleClick = () => {
this.props.onClick(this.props.value);
}
render() {
return (
<div onClick={this.handleClick}>
{this.props.value}
</div>
);
}
}
class CalculatorInput extends React.Component {
createButtons(buttons) {
return buttons.map(button =>
<Button
value={button.keyValue}
onClick={() => this.props.onInput(button)}
/>
);
}
render() {
const topPanelButtons = [
{ keyValue: '+', keyType: KeyType.operator },
{ keyValue: '-', keyType: KeyType.operator },
{ keyValue: '\u00d7', keyType: KeyType.operator },
{ keyValue: '\u00f7', keyType: KeyType.operator }
];
const centrePanelButtons = [
{ keyValue: '9', keyType: KeyType.numeric },
{ keyValue: '8', keyType: KeyType.numeric },
{ keyValue: '7', keyType: KeyType.numeric },
{ keyValue: '6', keyType: KeyType.numeric },
{ keyValue: '5', keyType: KeyType.numeric },
{ keyValue: '4', keyType: KeyType.numeric },
{ keyValue: '3', keyType: KeyType.numeric },
{ keyValue: '2', keyType: KeyType.numeric },
{ keyValue: '1', keyType: KeyType.numeric },
{ keyValue: '0', keyType: KeyType.numeric },
{ keyValue: '.', keyType: KeyType.dot },
{ keyValue: 'C', keyType: KeyType.clear }
];
const topPanelButtonDivs = this.createButtons(topPanelButtons);
const centrePanelButtonDivs = this.createButtons(centrePanelButtons);
return (
<>
<div class="top-panel">
{topPanelButtonDivs}
</div>
<div class="centre-panel">
{centrePanelButtonDivs}
</div>
<div class="bottom-panel" id="result">
<Button
value="="
onClick={() => this.props.onInput({ keyValue: '=', keyType: KeyType.result })}
/>
</div>
</>
);
}
}
class ResultDisplay extends React.Component {
render() {
return (
<div class="input">{this.props.displayValue}</div>
);
}
}
const CalcState = Object.freeze({
resulted: Symbol("resulted"),
cleared: Symbol("cleared"),
input: Symbol("input")
});
const InputState = Object.freeze({
acceptInput: Symbol("acceptInput"),
acceptNumeric: Symbol("acceptNumeric"),
acceptNumericOrOperator: Symbol("acceptNumericOrOperator")
});
function nextInputState(currentInputState, keyType) {
if (currentInputState === InputState.acceptNumeric) {
return InputState.acceptNumericOrOperator;
}
if (currentInputState === InputState.acceptNumericOrOperator) {
if (keyType === KeyType.operator) {
return InputState.acceptInput;
}
if (keyType === KeyType.numeric) {
return InputState.acceptNumericOrOperator;
}
}
if (keyType === KeyType.dot) {
return InputState.acceptNumeric;
}
if (keyType === KeyType.operator) {
return InputState.acceptInput;
}
if (keyType === KeyType.dot) {
return InputState.acceptNumericOrOperator;
}
return InputState.acceptInput;
}
function initial(state, keyValue, keyType) {
return {
...state,
result: {display: keyValue, keyTypes: [KeyType.numeric], inputStates: [InputState.acceptInput]},
lastKeyType: keyType,
calcState: CalcState.input,
inputState: InputState.acceptInput
}
}
function concat(state, keyValue, keyType) {
const display = state.result.display += keyValue;
const keyTypes = [...state.result.keyTypes, keyType];
const inputState = nextInputState(state.inputState, keyType)
const inputStates = [...state.result.inputStates, inputState]
return {
...state,
inputState,
result: {display, keyTypes, inputStates},
lastKeyType: keyType,
calcState: CalcState.input
}
}
function clearLast(state) {
let display;
let keyTypes;
let inputStates;
let lastKeyType;
let calcState;
let inputState;
if (state.result.display.length > 1) {
display = state.result.display.slice(0, -1);
keyTypes = state.result.keyTypes.slice(0, -1);
lastKeyType = keyTypes[keyTypes.length -1];
calcState = state.calcState;
inputStates = state.result.inputStates.slice(0, -1);;
inputState = inputStates[inputStates.length -1]
} else {
display = '0';
keyTypes = [KeyType.numeric];
lastKeyType: KeyType.numeric;
calcState = CalcState.cleared;
inputState = InputState.acceptInput;
inputStates = [inputState];
};
return {
...state,
result: {display, keyTypes, inputStates },
lastKeyType,
calcState,
inputState
}
}
function calulateResultState(state) {
const display = calculateResult(state.result.display);
const keyTypes = [KeyType.numeric];
const inputStates = [InputState.acceptInput];
return {
...state,
result: {display, keyTypes, inputStates},
calcState: CalcState.resulted,
inputState: InputState.acceptInput,
lastKeyType: KeyType.numeric
};
function calculateResult(inputString) {
// forming an array of numbers. eg for above string it will be: numbers = ["10", "26", "33", "56", "34", "23"]
var numbers = inputString.split(/\+|\-|\×|\÷/g);
// forming an array of operators. for above string it will be: operators = ["+", "+", "-", "*", "/"]
// first we replace all the numbers and dot with empty string and then split
var operators = inputString.replace(/[0-9]|\./g, "").split("");
console.log(inputString);
console.log(operators);
console.log(numbers);
console.log("----------------------------");
// now we are looping through the array and doing one operation at a time.
// first divide, then multiply, then subtraction and then addition
// as we move we are alterning the original numbers and operators array
// the final element remaining in the array will be the output
var divide = operators.indexOf("÷");
while (divide != -1) {
numbers.splice(divide, 2, numbers[divide] / numbers[divide + 1]);
operators.splice(divide, 1);
divide = operators.indexOf("÷");
}
var multiply = operators.indexOf("×");
while (multiply != -1) {
numbers.splice(multiply, 2, numbers[multiply] * numbers[multiply + 1]);
operators.splice(multiply, 1);
multiply = operators.indexOf("×");
}
var subtract = operators.indexOf("-");
while (subtract != -1) {
numbers.splice(subtract, 2, numbers[subtract] - numbers[subtract + 1]);
operators.splice(subtract, 1);
subtract = operators.indexOf("-");
}
var add = operators.indexOf("+");
while (add != -1) {
// using parseFloat is necessary, otherwise it will result in string concatenation :)
numbers.splice(add, 2, parseFloat(numbers[add]) + parseFloat(numbers[add + 1]));
operators.splice(add, 1);
add = operators.indexOf("+");
}
return numbers[0];
}
}
function calculateState(state, keyValue, keyType) {
if (keyType === KeyType.result) {
return calulateResultState(state)
}
if (keyType === KeyType.clear) {
return clearLast(state);
}
if (state.calcState === CalcState.resulted && keyType === KeyType.operator) {
return concat(state, keyValue, keyType);
}
if ((state.calcState === CalcState.cleared || state.calcState === CalcState.resulted) && keyType === KeyType.numeric ) {
return initial(state, keyValue, keyType);
}
if (state.calcState === CalcState.input) {
if (keyType === KeyType.clear) {
return clearLast(state);
}
if (state.inputState === InputState.acceptInput) {
return concat(state, keyValue, keyType);
}
if (state.inputState === InputState.acceptNumeric && keyType === KeyType.numeric) {
return concat(state, keyValue, keyType);
}
if (state.inputState === InputState.acceptNumericOrOperator && (keyType === KeyType.numeric || keyType === KeyType.operator)) {
return concat(state, keyValue, keyType);
}
}
}
function add(num1, num2) {
return num1 + num2;
}
function subtract(num1, num2) {
return num1 - num2;
}
function multiply(num1, num2) {
return num1 * num2;
}
function divide(num1, num2) {
return num1 / num2;
}
function clear() {
return 0;
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleInput = this.handleInput.bind(this);
this.state = {
result: { display: '0', keyTypes: [KeyType.numeric], inputStates: [InputState.acceptInput] },
calcState: CalcState.cleared,
inputState: InputState.acceptInput,
lastKeyType: KeyType.numeric
};
console.log(this.state);
}
handleInput(button) {
this.setState(calculateState(this.state, button.keyValue, button.keyType));
}
render() {
return (
<div class="calculator">
<ResultDisplay displayValue={this.state.result.display}/>
<CalculatorInput onInput={this.handleInput} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('container')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
$padding: 10px;
$width: 500px;
$dark-grey: #ddd;
$mid-grey: #bbb;
$light-grey: #aaa;
$light-blue: #1857bb;
$dark-blue: #4d90fe;
$button-width: 17%;
@mixin box-shadow($top: 0px, $left: 1px, $blur: 4px, $spread: 0px, $color: rgba(0, 0, 0, 0.2)) {
box-shadow: $top $left $blur $spread $color;
}
body {
width: $width;
margin: 4% auto;
font-family: "Source Sans Pro", sans-serif;
letter-spacing: 5px;
font-size: 1.8rem;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.calculator {
padding: $padding * 2;
@include box-shadow();
border-radius: 1px;
}
.input {
border: 1px solid $dark-grey;
border-radius: 5px;
height: 60px;
padding-right: 15px;
padding-top: $padding;
text-align: right;
margin: 0 6px 5px 5px;
font-size: 2.5rem;
overflow-x: auto;
}
.centre-panel {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
div {
display: inline-block;
border: 1px solid $dark-grey;
border-radius: 5px;
flex-basis: $button-width;
text-align: center;
padding: $padding;
margin: 10px 4px 10px 0;
cursor: pointer;
background-color: #f9f9f9;
&:hover {
background-color: #f1f1f1;
@include box-shadow();
border-color: $mid-grey;
}
}
}
.top-panel {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
div {
display: inline-block;
border: 1px solid $mid-grey;
border-radius: 5px;
width: $button-width;
text-align: center;
padding: $padding;
margin: 20px 4px 10px 0;
cursor: pointer;
background-color: $dark-grey;
&:hover {
background-color: $dark-grey;
border-color: $light-grey;
@include box-shadow();
}
&:active {
font-weight: bold;
}
}
}
div.bottom-panel {
display: flex;
justify-content: space-around;
border: 1px solid #3079ed;
border-radius: 5px;
flex-basis: 100%;
text-align: center;
padding: $padding;
margin: 10px 6px 10px 5px;
vertical-align: top;
cursor: pointer;
color: #fff;
background-color: #4d90fe;
&:hover {
background-color: #307cf9;
@include box-shadow();
border-color: $light-blue;
}
&:active {
font-weight: bold;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment