|
const { useEffect, useState } = React; |
|
const { styled } = window; |
|
const { antd } = window; |
|
const { Button, Steps, Typography } = antd; |
|
const { Step } = Steps; |
|
const { Title } = Typography; |
|
|
|
|
|
|
|
class Command { |
|
constructor(execute, undo, value, arg2, display) { |
|
this.execute = execute; |
|
this.undo = undo; |
|
this.value = value; |
|
this.arg2 = arg2; |
|
this.display = display; |
|
} |
|
}; |
|
|
|
const Calculator = ({ execute, clear }) => { |
|
|
|
const [total, setTotal] = useState('0'); |
|
const [next, setNext] = useState('0'); |
|
const [operation, setOperation] = useState(null); |
|
|
|
const add = (x, y) => { return x + y; }; |
|
const sub = (x, y) => { return x - y; }; |
|
const mul = (x, y) => { return x * y; }; |
|
const div = (x, y) => { return x / y; }; |
|
|
|
const AddCommand = (value, arg2) => { return new Command(add, sub, value, arg2, `Adding ${value} and ${arg2}`); }; |
|
const SubCommand = (value, arg2) => { return new Command(sub, add, value, arg2, `Subtracting ${value} from ${arg2}`); }; |
|
const MulCommand = (value, arg2) => { return new Command(mul, div, value, arg2, `Multiplying ${value} by ${arg2}`); }; |
|
const DivCommand = (value, arg2) => { return new Command(div, mul, value, arg2, `Dividing ${value} by ${arg2}`); }; |
|
|
|
|
|
|
|
const onClearClick = () => { |
|
clear(); |
|
setNext('0'); |
|
setTotal('0'); |
|
setOperation(null); |
|
}; |
|
|
|
const onNumberClick = (value) => { |
|
|
|
if (value === '.') { |
|
if (!next.includes('.')) setNext(`${next}${value}`); |
|
} |
|
if (!operation) { |
|
total === '0' ? setTotal(value) : setTotal(`${total}${value}`); |
|
} else { |
|
const temp = next === '0' ? value : `${next}${value}`; |
|
setNext(total); |
|
setTotal(temp); |
|
} |
|
}; |
|
|
|
const onOpClick = (op) => { |
|
|
|
// if no operation, just set the operation |
|
if (!operation) { |
|
setOperation(op); |
|
return; |
|
} |
|
|
|
const executeCmd = (op) => { |
|
switch (op) { |
|
case '+': |
|
result = execute(AddCommand(+next, +total)); |
|
setTotal(result.toString()); |
|
break; |
|
case '-': |
|
result = execute(SubCommand(+next, +total)); |
|
setTotal(result.toString()); |
|
break; |
|
case 'X': |
|
result = execute(MulCommand(+next, (total === '0' ? 1 : +total))); |
|
setTotal(result.toString()); |
|
break; |
|
case '/': |
|
result = execute(DivCommand(+next, (total === '0' ? 1 : +total))); |
|
setTotal(result.toString()); |
|
break; |
|
} |
|
}; |
|
|
|
if (op === '=') { |
|
executeCmd(operation); |
|
} else { |
|
executeCmd(op); |
|
} |
|
|
|
setOperation(null); |
|
setNext('0'); |
|
}; |
|
|
|
return ( |
|
<div style={{ margin: 'auto' }}> |
|
<div style={{ border: '1px solid #444' }} className="Row"> |
|
<Title level={1} style={{ width: '100%', display: 'flex', flexDirection: 'row', justifyContent: 'flex-end', margin: '5px' }}>{total}</Title> |
|
</div> |
|
<div style={{ display: 'flex', flexDirection: 'column', padding: '15px 5px 10px 5px', border: '1px solid #444' }}> |
|
<div className="Row"> |
|
<Button onClick={onClearClick} style={{ background: '#A5A5A5', margin: '3px' }} shape="circle">C</Button> |
|
<Button onClick={() => onOpClick('+-')} style={{ background: '#A5A5A5', margin: '3px' }} disabled shape="circle">±</Button> |
|
<Button onClick={() => onOpClick('%')} style={{ background: '#A5A5A5', margin: '3px' }} disabled shape="circle">%</Button> |
|
<Button onClick={() => onOpClick('/')} style={{ background: '#EE9327', margin: '3px' }} shape="circle">/</Button> |
|
</div> |
|
<div className="Row"> |
|
<Button onClick={() => onNumberClick('7')} style={{ background: '#333333', margin: '3px' }} shape="circle">7</Button> |
|
<Button onClick={() => onNumberClick('8')} style={{ background: '#333333', margin: '3px' }} shape="circle">8</Button> |
|
<Button onClick={() => onNumberClick('9')} style={{ background: '#333333', margin: '3px' }} shape="circle">9</Button> |
|
<Button onClick={() => onOpClick('X')} style={{ background: '#EE9327', margin: '3px' }} shape="circle">X</Button> |
|
</div> |
|
<div className="Row"> |
|
<Button onClick={() => onNumberClick('4')} style={{ background: '#333333', margin: '3px' }} shape="circle">4</Button> |
|
<Button onClick={() => onNumberClick('5')} style={{ background: '#333333', margin: '3px' }} shape="circle">5</Button> |
|
<Button onClick={() => onNumberClick('6')} style={{ background: '#333333', margin: '3px' }} shape="circle">6</Button> |
|
<Button onClick={() => onOpClick('-')} style={{ background: '#EE9327', margin: '3px' }} shape="circle">-</Button> |
|
</div> |
|
<div className="Row"> |
|
<Button onClick={() => onNumberClick('1')} style={{ background: '#333333', margin: '3px' }} shape="circle">1</Button> |
|
<Button onClick={() => onNumberClick('2')} style={{ background: '#333333', margin: '3px' }} shape="circle">2</Button> |
|
<Button onClick={() => onNumberClick('3')} style={{ background: '#333333', margin: '3px' }} shape="circle">3</Button> |
|
<Button onClick={() => onOpClick('+')} style={{ background: '#EE9327', margin: '3px' }} shape="circle">+</Button> |
|
</div> |
|
<div className="Row"> |
|
<Button onClick={() => onNumberClick('0')} style={{ background: '#333333', margin: '3px' }} shape="circle">0</Button> |
|
<Button onClick={() => {}} style={{ background: '#A5A5A5', margin: '3px' }} shape="circle"></Button> |
|
<Button onClick={() => onNumberClick('.')} style={{ background: '#333333', margin: '3px' }} shape="circle">.</Button> |
|
<Button onClick={() => onOpClick('=')} style={{ background: '#EE9327', margin: '3px' }} shape="circle">=</Button> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
const Stack = ({ commands }) => { |
|
|
|
const renderedSteps = () => { |
|
return commands.map((command) => { |
|
return ( |
|
<Step |
|
key={command.value} |
|
title={command.display} |
|
// description={command.display} |
|
/> |
|
); |
|
}); |
|
}; |
|
|
|
return ( |
|
<Steps direction="vertical" current={commands.length -1} style={{ margin: '50px' }}> |
|
{renderedSteps()} |
|
</Steps> |
|
); |
|
|
|
}; |
|
|
|
const App = () => { |
|
|
|
const [executedStack, setExecutedStack] = useState([]); |
|
const [undoneStack, setUndoneStack] = useState([]); |
|
|
|
console.log('executedStack: ', executedStack); |
|
console.log('undoneStack: ', undoneStack); |
|
|
|
const execute = (cmd) => { |
|
const result = cmd.execute(cmd.value, cmd.arg2); |
|
setExecutedStack([...executedStack, cmd]); |
|
return result; |
|
} |
|
|
|
|
|
const undo = () => { |
|
const lastExecutedCmd = executedStack.slice(-1); |
|
console.log('lastExecutedCmd: ', lastExecutedCmd); |
|
// add to redo stack |
|
if (lastExecutedCmd) { |
|
setUndoneStack([ ...undoneStack, ...lastExecutedCmd]); |
|
} |
|
setExecutedStack([...executedStack.filter((c, i) => i !== executedStack.length -1)]); |
|
} |
|
|
|
const redo = () => { |
|
const lastUndoneCmd = undoneStack.slice(-1); |
|
|
|
if (lastUndoneCmd === undefined) { |
|
lastUndoneCmd = executedStack.slice(-1); |
|
setExecutedStack([...executedStack, lastUndoneCmd]); |
|
} |
|
|
|
if (lastUndoneCmd) { |
|
const result = lastUndoneCmd.execute(lastUndoneCmd.value, lastUndoneCmd.arg2); |
|
setExecutedStack([...executedStack, lastUndoneCmd]); |
|
// return result; |
|
} |
|
}; |
|
|
|
const clear = () => { |
|
setExecutedStack([]); |
|
setUndoneStack([]); |
|
}; |
|
|
|
|
|
|
|
|
|
return ( |
|
<div className='App'> |
|
<div className="Row" style={{ borderBottom: '1px solid #444' }}> |
|
<Calculator execute={execute} clear={clear} /> |
|
</div> |
|
<div className="Row" style={{ alignItems: 'center' }}> |
|
<div className="Col"> |
|
<div><Title level={3} style={{ width: '100%', margin: '20px 5px' }}>Executed Stack</Title></div> |
|
<div className="Row" style={{ alignItems: 'center' }}> |
|
<Stack commands={executedStack} /> |
|
</div> |
|
<Button |
|
type="primary" |
|
onClick={() => undo()} |
|
style={{ margin: '15px' }} |
|
> |
|
Undo |
|
</Button> |
|
</div> |
|
<div className="Col"> |
|
<div><Title level={3} style={{ width: '100%', margin: '20px 5px' }}>Undone Stack</Title></div> |
|
<div className="Row" style={{ alignItems: 'center' }}> |
|
<Stack commands={undoneStack} /> |
|
</div> |
|
<Button |
|
onClick={() => redo()} |
|
style={{ margin: '15px' }} |
|
> |
|
Redo |
|
</Button> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
|
|
ReactDOM.render(<App />, document.getElementById('root')); |