World's Worst Spreadsheet
by alexb
HTML
<div id="root"></div>
CSS
html {
box-sizing: border-box;
font-family: Helvetica;
font-size: 14px;
}
html * {
box-sizing: inherit;
}
body {
background: #20262E;
padding: 20px;
}
#root {
background: #fff;
border-radius: 4px;
margin: 0 auto;
max-width: 900px;
padding: 20px;
transition: all 0.2s;
}
.editor {
border: 1px solid lightgray;
font-family: Helvetica;
font-size: 1rem;
padding: 0.25rem 0.5rem;
width: 100%;
}
.editor + p {
color: gray;
font-size: 10px;
height: 30px;
opacity: 0;
padding-top: 5px;
padding-left: calc(0.5rem + 1px);
transition: opacity 100ms;
}
.editor:focus + p.show {
opacity: 1;
}
.cells {
display: flex;
flex-wrap: wrap;
padding: 1px 0 0 1px;
}
.cells > * {
width: calc((100% - 30px) / 8 + 1px);
}
.cell {
align-items: center;
border: 1px solid lightgray;
cursor: pointer;
display: flex;
height: 1.75em;
margin: -1px 0 0 -1px;
overflow: hidden;
padding: 0 0.25em;
white-space: nowrap;
}
.cell.heading {
background: lightgray;
cursor: default;
justify-content: center;
}
.cell.heading-row {
width: 30px;
}
.cell.selected {
border-color: cornflowerblue;
outline: 2px solid cornflowerblue;
position: relative;
}
React
const col = cellRef => cellRef.replace(/\d+/, '');
const row = cellRef => cellRef.replace(/\D+/, '');
const pairToRef = (x, y) => String.fromCharCode(64 + x) + String(y);
class Sheet {
constructor() {
// Map of cell references to cell contents. Contents
// are strings containing either numbers or formulas.
// Examples: '2', '=A2+B3'
this._contents = new Map();
}
evaluate(cellRef) {
const cell = this._contents.get(cellRef) || '';
if (cell.substr(0, 1) !== '=') {
return cell;
}
let noRefs;
try {
const expr = cell.substr(1);
noRefs = expr.replace(
/[A-Z]+\d+/g,
ref => this.evaluate(ref));
return String(eval(noRefs));
}
catch (e) {
console.error(
'Error evaluating cell',
cellRef,
JSON.stringify(cell),
'→',
JSON.stringify(noRefs),
e.message
);
return '#ERROR';
}
}
get(cellRef) {
return this._contents.get(cellRef) || '';
}
set(cellRef, content) {
this._contents.set(cellRef, content);
}
}
const range = (lo, hi) => {
if (hi === undefined) {
hi = lo;
lo = 0;
}
return new Array(hi - lo).fill(0).map((_, i) => lo + i);
};
const SheetView = ({ sheet }) => {
const [[selX, selY], setSel] = React.useState([1, 1]);
const [editorContent, setEditorContent] =
React.useState(sheet.get(pairToRef(selX, selY)));
const editorChanged =
editorContent !== sheet.get(pairToRef(selX, selY));
const setCell = (x, y) => {
setSel([x, y]);
setEditorContent(sheet.get(pairToRef(x, y)))
}
const handleEditorChange = event =>
setEditorContent(event.currentTarget.value);
const handleEditorKeyPress = event => {
if (event.key === 'Enter') {
console.log(sheet, pairToRef(selX, selY), editorContent)
sheet.set(pairToRef(selX, selY), editorContent);
setSel([selX, selY]); // Force rerender.
}
};
return (
<div className="sheet">
<input
...