Skip to content

Instantly share code, notes, and snippets.

@ChristopherHButler
Created October 5, 2020 21:16
Show Gist options
  • Save ChristopherHButler/8e2a546d3897f9f4899e1859c17ff72e to your computer and use it in GitHub Desktop.
Save ChristopherHButler/8e2a546d3897f9f4899e1859c17ff72e to your computer and use it in GitHub Desktop.
Command Pattern

Command Pattern

This Gist was generated by Contrived.

Do not modify the metadata file if you want to open in Contrived again. Otherwise, it is safe to delete.

Happy Hacking!

{"user":"5f0c542a4a2ce5e528e01fdf","templateVersion":"1","templateId":"reactjs","resources":["<meta charset=\"UTF-8\" />","<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">"],"dependencies":[{"name":"react","version":"16.13.1","type":"js","url":"https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"},{"name":"react-dom","version":"16.13.1","type":"js","url":"https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"},{"name":"antd-css","type":"css","url":"https://cdnjs.cloudflare.com/ajax/libs/antd/4.6.6/antd.dark.min.css","version":"4.6.6"},{"name":"antd","type":"js","url":"https://cdnjs.cloudflare.com/ajax/libs/antd/4.6.6/antd.min.js","version":"4.6.6"}],"files":[{"id":1,"parentId":0,"name":"public","path":"/public","type":"folder","isRoot":true,"selected":false,"expanded":false,"children":[{"id":2,"name":"index.html"}]},{"id":2,"parentId":1,"name":"index.html","path":"/src/index.html","type":"file","mimeType":"html","isRoot":false,"open":false,"selected":false,"content":""},{"id":3,"parentId":0,"name":"src","path":"/src","type":"folder","isRoot":true,"selected":false,"expanded":true,"children":[{"id":4,"name":"index.js"},{"id":5,"name":"styles.css"}]},{"id":4,"parentId":3,"name":"index.js","path":"/src/index.js","type":"file","mimeType":"es6","isRoot":false,"open":true,"selected":true,"content":""},{"id":5,"parentId":3,"name":"style.css","path":"/src/style.css","type":"file","mimeType":"css","isRoot":false,"open":true,"selected":false,"content":""}],"experimentId":"5f7b88745556113114e18d8e"}
.App {
display: flex;
flex-direction: column;
height: 100%;
font-family: sans-serif;
text-align: center;
}
.Row {
display: flex;
flex-direction: row;
flex: 1;
}
.Col {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
}
.Centered {
justify-content: enter;
}
<div id="root"></div>
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">&plusmn;</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'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment