From e8471b60c6c72cd9f9856062f6c80a138be4080b Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Sat, 23 Apr 2022 23:23:50 +0200 Subject: [PATCH] WIP --- .gitignore | 2 + notes.txt | 3 + package.json | 32 + src/client/components/app/index.jsx | 35 + .../autoresize-textarea.css | 39 + .../components/autoresize-textarea/index.jsx | 39 + src/client/components/table/index.jsx | 242 + src/client/components/table/table.css | 45 + src/client/global.css | 24 + src/client/index.jsx | 11 + src/server/app.js | 10 + src/server/index.js | 37 + src/static/css/bundle.css | 127 + src/static/index.html | 13 + yarn.lock | 5472 +++++++++++++++++ 15 files changed, 6131 insertions(+) create mode 100644 .gitignore create mode 100644 notes.txt create mode 100644 package.json create mode 100644 src/client/components/app/index.jsx create mode 100644 src/client/components/autoresize-textarea/autoresize-textarea.css create mode 100644 src/client/components/autoresize-textarea/index.jsx create mode 100644 src/client/components/table/index.jsx create mode 100644 src/client/components/table/table.css create mode 100644 src/client/global.css create mode 100644 src/client/index.jsx create mode 100644 src/server/app.js create mode 100755 src/server/index.js create mode 100644 src/static/css/bundle.css create mode 100644 src/static/index.html create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93cab34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +yarn-error.log diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..1cfe90a --- /dev/null +++ b/notes.txt @@ -0,0 +1,3 @@ +TODO: +- Keyboard navigation of the active table; arrow keys and enter-to-edit, as well as escape cancellation +- Reworking event handling to work with nested tables, where necessary diff --git a/package.json b/package.json new file mode 100644 index 0000000..205e6e4 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "websheets", + "version": "1.0.0", + "main": "index.js", + "repository": "git@git.cryto.net:joepie91/websheets.git", + "author": "Sven Slootweg ", + "license": "WTFPL OR CC0-1.0", + "dependencies": { + "as-expression": "^1.0.0", + "classnames": "^2.3.1", + "css-extract": "^2.0.0", + "express": "^4.17.3", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@babel/core": "^7.17.9", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "autoprefixer": "^10.4.4", + "babelify": "^10.0.0", + "browserify": "^17.0.0", + "budo-express": "^1.0.8", + "icssify": "^1.2.1", + "nodemon": "^2.0.15", + "postcss-nested": "^5.0.6", + "postcss-nested-props": "^2.0.0" + }, + "scripts": { + "dev": "NODE_ENV=development nodemon ./src/server/index.js" + } +} diff --git a/src/client/components/app/index.jsx b/src/client/components/app/index.jsx new file mode 100644 index 0000000..c5ba560 --- /dev/null +++ b/src/client/components/app/index.jsx @@ -0,0 +1,35 @@ +"use strict"; + +const React = require("react"); + +const Table = require("../table"); + +let sheet = { + columnCount: 15, + rowCount: 15, + cells: [ + [ "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" ], + [ "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz" ], + [ "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo" ], + [ "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" ], + [ "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz" ], + [ "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo" ], + [ "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" ], + [ "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz" ], + [ "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo" ], + [ "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" ], + [ "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz" ], + [ "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo" ], + [ "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" ], + [ "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz", "asdf", "Qux", "Quz" ], + [ "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo", "FooBar", "BazQux", "QuzFoo" ] + ] +}; + +module.exports = function App() { + return ( +
+ + + ); +}; diff --git a/src/client/components/autoresize-textarea/autoresize-textarea.css b/src/client/components/autoresize-textarea/autoresize-textarea.css new file mode 100644 index 0000000..29de8eb --- /dev/null +++ b/src/client/components/autoresize-textarea/autoresize-textarea.css @@ -0,0 +1,39 @@ +/* HACK: https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/ */ + +.wrapper { + display: grid; + width: 100%; + height: 100%; +} + +/* No nesting here, because we want to keep precedence as low as possible */ + +.editable, .dummy { + grid-area: 1 / 1 / 2 / 2; + + display: inline-block; + box-sizing: border-box; + white-space: normal; + overflow: hidden; + overflow-wrap: normal; + + font: { + family: inherit; + size: inherit; + weight: inherit; + style: inherit; + }; +} + +.dummy { + visibility: hidden; + width: max-content; /* NOTE: This prevents the pseudo-element from line-wrapping when the overall page content is bigger than the viewport, which is necessary to get consistent behaviour with regular elements. */ +} + +.editable { + border: none; + resize: none; + margin: 0; + width: 100%; + height: 100%; +} diff --git a/src/client/components/autoresize-textarea/index.jsx b/src/client/components/autoresize-textarea/index.jsx new file mode 100644 index 0000000..ede613d --- /dev/null +++ b/src/client/components/autoresize-textarea/index.jsx @@ -0,0 +1,39 @@ +"use strict"; + +const React = require("react"); +const classnames = require("classnames"); + +const style = require("./autoresize-textarea.css"); + +const NBSP = String.fromCharCode(160); + +module.exports = React.forwardRef(function AutosizeTextarea({ value, className, onChange, ... args }, ref) { + // TODO: Handle this outside of the React state cycle using raw JS events, for improved performance + let [ currentValue, setCurrentValue ] = React.useState(value); + + // This is mainly to ensure that an empty newline is taken into account for the size measurement, otherwise we get a scrollbar in the textarea + let displayValue = currentValue + (currentValue.endsWith("\n") ? NBSP : ""); + + return ( +
+
{displayValue}
+