From 06676ec6a22dfad6eb5b501b727d8c3a61185918 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 25 Sep 2025 10:58:05 -0700 Subject: [PATCH 01/50] added notes file --- sprint1notes.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sprint1notes.md diff --git a/sprint1notes.md b/sprint1notes.md new file mode 100644 index 0000000..e69de29 From 489532b26479766e746412b056d79dc9e24da617 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:02:09 -0700 Subject: [PATCH 02/50] added notes --- sprint1notes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sprint1notes.md b/sprint1notes.md index e69de29..9b4cac6 100644 --- a/sprint1notes.md +++ b/sprint1notes.md @@ -0,0 +1,6 @@ +OG described an app where user can choose a grid size, then choose basic color scheme and 'paint' graphic pixel by pixel to make a picture + +Then backend takes pixel grid and converts to crochet pattern + +1st: get 'hello world' app working with Node +2nd: \ No newline at end of file From 9a4667bfded4f874982a783af0151ab1af1f72e7 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 25 Sep 2025 11:12:40 -0700 Subject: [PATCH 03/50] added boilerplate express server --- .gitignore | 1 + app.js | 13 + package-lock.json | 1163 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 + views/home.ejs | 11 + 5 files changed, 1204 insertions(+) create mode 100644 .gitignore create mode 100644 app.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 views/home.ejs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..0628b50 --- /dev/null +++ b/app.js @@ -0,0 +1,13 @@ +const express = require('express'); +const PORT = 3000; + +const app = express(); + +app.use(express.urlencoded({ extended: false })); + +app.set('view engine', 'ejs'); + +app.get('/', (req, res) => { + console.log("Server available on "); + res.render('home'); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ea228f2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1163 @@ +{ + "name": "pixel-to-pattern", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pixel-to-pattern", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "express": "^5.1.0", + "nodemon": "^3.1.10" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..970ffa3 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "pixel-to-pattern", + "version": "1.0.0", + "description": "Project Name: Pixel to Pattern", + "main": "app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "nodemon app.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "express": "^5.1.0", + "nodemon": "^3.1.10" + } +} diff --git a/views/home.ejs b/views/home.ejs new file mode 100644 index 0000000..4a29874 --- /dev/null +++ b/views/home.ejs @@ -0,0 +1,11 @@ + + + + + + Document + + +

Hello World

+ + \ No newline at end of file From 344f865f68ead1e187150b7634bb437eb438c1bc Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 25 Sep 2025 11:15:03 -0700 Subject: [PATCH 04/50] Server running --- app.js | 6 +++- package-lock.json | 75 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 0628b50..f252752 100644 --- a/app.js +++ b/app.js @@ -8,6 +8,10 @@ app.use(express.urlencoded({ extended: false })); app.set('view engine', 'ejs'); app.get('/', (req, res) => { - console.log("Server available on "); res.render('home'); +}); + + +app.listen(PORT, () => { + console.log(`Running on port http://localhost:${PORT}` ); }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ea228f2..1364400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "ejs": "^3.1.10", "express": "^5.1.0", "nodemon": "^3.1.10" } @@ -39,6 +40,12 @@ "node": ">= 8" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -252,6 +259,21 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -348,6 +370,36 @@ "url": "https://opencollective.com/express" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -618,6 +670,23 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -785,6 +854,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", diff --git a/package.json b/package.json index 970ffa3..a166d75 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "author": "", "license": "ISC", "dependencies": { + "ejs": "^3.1.10", "express": "^5.1.0", "nodemon": "^3.1.10" } From bcf02ab716458ef7b8010dd458ba8def95a4438a Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 25 Sep 2025 11:26:27 -0700 Subject: [PATCH 05/50] updated Node version and added mysql2 --- app.js | 4 +- package-lock.json | 126 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index f252752..8cee579 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,5 @@ -const express = require('express'); +import express from 'express'; +import mysql from 'mysql2/promise'; const PORT = 3000; const app = express(); @@ -11,7 +12,6 @@ app.get('/', (req, res) => { res.render('home'); }); - app.listen(PORT, () => { console.log(`Running on port http://localhost:${PORT}` ); }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1364400..82e3122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "mysql2": "^3.15.1", "nodemon": "^3.1.10" } }, @@ -46,6 +47,15 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -230,6 +240,15 @@ } } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -470,6 +489,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -670,6 +698,12 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", @@ -687,6 +721,36 @@ "node": ">=10" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -756,6 +820,54 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.1.tgz", + "integrity": "sha512-WZMIRZstT2MFfouEaDz/AGFnGi1A2GwaDe7XvKTdRJEYiAHbOrh4S3d8KFmQeh11U85G+BFjIvS1Di5alusZsw==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -1034,6 +1146,11 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", @@ -1139,6 +1256,15 @@ "node": ">=10" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", diff --git a/package.json b/package.json index a166d75..ac1fedf 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "Project Name: Pixel to Pattern", "main": "app.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "nodemon app.js" @@ -12,6 +13,7 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "mysql2": "^3.15.1", "nodemon": "^3.1.10" } } From 9f360b85d5ae34f9333e6aab55e6cab69b76b5b7 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 10:37:09 -0700 Subject: [PATCH 06/50] Cleaning app.js --- app.js | 21 +++++++++++++++++++-- package-lock.json | 44 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 8cee579..8998e5c 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,5 @@ import express from 'express'; -import mysql from 'mysql2/promise'; +//import mariadb from 'mariadb'; const PORT = 3000; const app = express(); @@ -8,7 +8,24 @@ app.use(express.urlencoded({ extended: false })); app.set('view engine', 'ejs'); -app.get('/', (req, res) => { +// const pool = mariadb.createPool({ +// host: 'localhost', +// user: 'root', +// database: 'portfolio', +// password: '1234' +// }); + +// async function connect() { +// try { +// const conn = await pool.getConnection(); +// console.log("Connected to mariaDB"); +// return conn; +// } catch (err) { +// console.log('Error connecting to MariaDB: ' + err); +// } +// }; + +app.get('/', async (req, res) => { res.render('home'); }); diff --git a/package-lock.json b/package-lock.json index 82e3122..7446ead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,26 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "mariadb": "^3.4.5", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -751,6 +767,28 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/mariadb": { + "version": "3.4.5", + "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", + "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", + "license": "LGPL-2.1-or-later", + "dependencies": { + "@types/geojson": "^7946.0.16", + "@types/node": "^24.0.13", + "denque": "^2.1.0", + "iconv-lite": "^0.6.3", + "lru-cache": "^10.4.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mariadb/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1336,6 +1374,12 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index ac1fedf..4cfd5a7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "mariadb": "^3.4.5", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } From 4213deed0962aa6ce0d5dd3a23ed96227254a88f Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:37:27 -0700 Subject: [PATCH 07/50] changes to package.json --- package-lock.json | 8 ++++++++ package.json | 1 + 2 files changed, 9 insertions(+) diff --git a/package-lock.json b/package-lock.json index 82e3122..4510a6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "homebrew": "^0.1.0", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } @@ -592,6 +593,13 @@ "node": ">= 0.4" } }, + "node_modules/homebrew": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/homebrew/-/homebrew-0.1.0.tgz", + "integrity": "sha512-+Wqq+DmsqiZboVOsPpI1jYESnTFK7gSulTB5JRYuTMJghr5wHxC3LvKDs7w0ArJRRmHedDn41ZEQ0quUYq12pA==", + "hasInstallScript": true, + "license": "ISC" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", diff --git a/package.json b/package.json index ac1fedf..b8a9793 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "ejs": "^3.1.10", "express": "^5.1.0", + "homebrew": "^0.1.0", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } From f83dfd2c4025ffa6b0191e85b87a8980e3e98800 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 11:12:00 -0700 Subject: [PATCH 08/50] Changed h1 on home.ejs --- views/home.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/home.ejs b/views/home.ejs index 4a29874..d24d60e 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -6,6 +6,6 @@ Document -

Hello World

+

Pixel-To-Pattern

\ No newline at end of file From c629a543f926998bb6a98bcd4b9de8cd47d78320 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 11:42:25 -0700 Subject: [PATCH 09/50] Added .env to gitignore and added more notes --- .gitignore | 3 ++- sprint1notes.md | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b512c09..1dcef2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file diff --git a/sprint1notes.md b/sprint1notes.md index 9b4cac6..aff5906 100644 --- a/sprint1notes.md +++ b/sprint1notes.md @@ -3,4 +3,18 @@ OG described an app where user can choose a grid size, then choose basic color s Then backend takes pixel grid and converts to crochet pattern 1st: get 'hello world' app working with Node -2nd: \ No newline at end of file +2nd: get app deployed +3rd: set up mysql +4th: connect with code using pool +5th: build frontend + +Pixel To Pattern Front End + +variable grid 1-50 x 1-50 +current sprint end goals - save pattern to db with a label +extra: db view page +sticky header -> entry field for grid size (h/w don’t have to be equal); logo/index page name; + + +Back End: front end has to connect to db through workbench +data fields: ID, Label, pattern saved as an array \ No newline at end of file From e17a3afa1350f23e1e1a749b70010babf019c1ba Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 12:27:52 -0700 Subject: [PATCH 10/50] added db.js --- db.js | 15 +++++++++++++++ package-lock.json | 13 +++++++++++++ package.json | 1 + 3 files changed, 29 insertions(+) create mode 100644 db.js diff --git a/db.js b/db.js new file mode 100644 index 0000000..477cb15 --- /dev/null +++ b/db.js @@ -0,0 +1,15 @@ +import 'dotenv/config'; +import mysql from 'mysql2/promise'; + +const pool = mysql.createPool({ + host: process.env.DB_HOST, + port: Number(process.env.DB_PORT || 3306), + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, +}); + +export default pool; diff --git a/package-lock.json b/package-lock.json index 7446ead..5fe0286 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.1.0", "mariadb": "^3.4.5", @@ -274,6 +275,18 @@ "node": ">= 0.8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 4cfd5a7..08ed9b4 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "author": "", "license": "ISC", "dependencies": { + "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.1.0", "mariadb": "^3.4.5", From 122ec2832b0fa5d6c8fa7c8c7781973d912c0ad3 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 12:32:13 -0700 Subject: [PATCH 11/50] Reconfig app.js to support mysql connection --- app.js | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/app.js b/app.js index 8998e5c..6f7bc9f 100644 --- a/app.js +++ b/app.js @@ -1,34 +1,25 @@ import express from 'express'; -//import mariadb from 'mariadb'; -const PORT = 3000; +import pool from './db.js'; -const app = express(); +const PORT = process.env.PORT || 3000; +const HOST = process.env.HOST || '0.0.0.0'; +const app = express(); app.use(express.urlencoded({ extended: false })); - app.set('view engine', 'ejs'); -// const pool = mariadb.createPool({ -// host: 'localhost', -// user: 'root', -// database: 'portfolio', -// password: '1234' -// }); +app.get('/', (req, res) => res.render('home')); -// async function connect() { -// try { -// const conn = await pool.getConnection(); -// console.log("Connected to mariaDB"); -// return conn; -// } catch (err) { -// console.log('Error connecting to MariaDB: ' + err); -// } -// }; - -app.get('/', async (req, res) => { - res.render('home'); +app.get('/health/db', async (_req, res) => { + try { + const [rows] = await pool.query('SELECT 1 AS ok'); + res.json({ db: 'ok', result: rows[0].ok }); + } catch (err) { + console.error(err); + res.status(500).json({ db: 'error', message: err.message }); + } }); -app.listen(PORT, () => { - console.log(`Running on port http://localhost:${PORT}` ); -}); \ No newline at end of file +app.listen(PORT, HOST, () => { + console.log(`Running on http://${HOST}:${PORT}`); +}); From 11d7c61a1068e747728faaa423e80237cb12fe1f Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 2 Oct 2025 12:39:58 -0700 Subject: [PATCH 12/50] added test route to read from database --- app.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app.js b/app.js index 6f7bc9f..1112685 100644 --- a/app.js +++ b/app.js @@ -20,6 +20,12 @@ app.get('/health/db', async (_req, res) => { } }); +app.get('/ping', async (_req, res) => { + const [rows] = await pool.query('SELECT * FROM ping ORDER BY id DESC LIMIT 5'); + res.json(rows); +}); + + app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); }); From f3e32ac768f943d2473afe854cda9560a252c3ce Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 7 Oct 2025 11:05:03 -0700 Subject: [PATCH 13/50] removed mariadb --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 08ed9b4..8986d00 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.1.0", - "mariadb": "^3.4.5", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } From 83da097feb00fcaa10e19f5961357747284067f5 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 7 Oct 2025 11:08:55 -0700 Subject: [PATCH 14/50] changing gitignore to hide .env --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b512c09..1dcef2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file From 24993d247631b2631a13cc4c98d1e88ddfebee21 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 7 Oct 2025 11:13:37 -0700 Subject: [PATCH 15/50] new package-lock --- package-lock.json | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index b52149e..9ed21ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,21 +17,6 @@ "nodemon": "^3.1.10" } }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.12.0" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -787,28 +772,6 @@ "url": "https://github.com/sponsors/wellwelwel" } }, - "node_modules/mariadb": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", - "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", - "license": "LGPL-2.1-or-later", - "dependencies": { - "@types/geojson": "^7946.0.16", - "@types/node": "^24.0.13", - "denque": "^2.1.0", - "iconv-lite": "^0.6.3", - "lru-cache": "^10.4.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/mariadb/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1394,12 +1357,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", - "license": "MIT" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", From cfc1669a898e009e02342af40d7d059b686c9143 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:23:06 -0700 Subject: [PATCH 16/50] wtf --- package-lock.json | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5fe0286..16609b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,26 +12,10 @@ "dotenv": "^17.2.3", "ejs": "^3.1.10", "express": "^5.1.0", - "mariadb": "^3.4.5", "mysql2": "^3.15.1", "nodemon": "^3.1.10" } }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.12.0" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -780,28 +764,6 @@ "url": "https://github.com/sponsors/wellwelwel" } }, - "node_modules/mariadb": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", - "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", - "license": "LGPL-2.1-or-later", - "dependencies": { - "@types/geojson": "^7946.0.16", - "@types/node": "^24.0.13", - "denque": "^2.1.0", - "iconv-lite": "^0.6.3", - "lru-cache": "^10.4.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/mariadb/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1387,12 +1349,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "license": "MIT" }, - "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", - "license": "MIT" - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", From c5392e94a555509086bdde5ccc63a1404bf1e98e Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 7 Oct 2025 11:49:06 -0700 Subject: [PATCH 17/50] Added basic frontend functionality --- app.js | 1 + public/css/style.css | 48 ++++++++++++++++++++++++++++++++++++++++++ public/scripts/grid.js | 40 +++++++++++++++++++++++++++++++++++ views/home.ejs | 15 ++++++++----- 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 public/css/style.css create mode 100644 public/scripts/grid.js diff --git a/app.js b/app.js index 1112685..ced920d 100644 --- a/app.js +++ b/app.js @@ -6,6 +6,7 @@ const HOST = process.env.HOST || '0.0.0.0'; const app = express(); app.use(express.urlencoded({ extended: false })); +app.use(express.static("public")); app.set('view engine', 'ejs'); app.get('/', (req, res) => res.render('home')); diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..5fc272a --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,48 @@ +body { + font-family: system-ui, sans-serif; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + background-color: lightgray; +} + +h1 { + margin-bottom: 20px; +} + +.palette { + display: flex; + gap: 8px; + margin-bottom: 20px; +} + +.swatch { + width: 30px; + height: 30px; + border: 2px solid transparent; + border-radius: 6px; + cursor: pointer; + transition: transform 0.1s; +} + +.swatch.active { + border-color: #333; + transform: scale(1.1); +} + +.grid { + display: grid; + grid-template-columns: repeat(10, 30px); + grid-template-rows: repeat(10, 30px); + gap: 2px; +} + +.cell { + width: 30px; + height: 30px; + background: #fff; + border: 1px solid #ddd; + cursor: pointer; + transition: background 0.1s; +} diff --git a/public/scripts/grid.js b/public/scripts/grid.js new file mode 100644 index 0000000..6401d1b --- /dev/null +++ b/public/scripts/grid.js @@ -0,0 +1,40 @@ +const PALETTE = [ + { id: "white", hex: "#ffffff" }, + { id: "black", hex: "#000000" }, + { id: "red", hex: "#ef4444" }, + { id: "green", hex: "#22c55e" }, + { id: "blue", hex: "#3b82f6" }, + { id: "yellow", hex: "#f59e0b" }, + { id: "purple", hex: "#8b5cf6" }, +]; + +let activeColor = PALETTE[1]; // default: black + +const paletteEl = document.getElementById("palette"); +const gridEl = document.getElementById("grid"); + +// palette +PALETTE.forEach((color, i) => { + const sw = document.createElement("div"); + sw.className = "swatch" + (i === 1 ? " active" : ""); + sw.style.background = color.hex; + sw.addEventListener("click", () => { + activeColor = color; + document.querySelectorAll(".swatch").forEach(s => s.classList.remove("active")); + sw.classList.add("active"); + }); + paletteEl.appendChild(sw); +}); + +// 10x10 grid +for (let r = 0; r < 10; r++) { + for (let c = 0; c < 10; c++) { + const cell = document.createElement("div"); + cell.className = "cell"; + cell.addEventListener("click", () => { + cell.style.background = + cell.style.background === activeColor.hex ? "#ffffff" : activeColor.hex; + }); + gridEl.appendChild(cell); + } +} diff --git a/views/home.ejs b/views/home.ejs index d24d60e..a2393cc 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -1,11 +1,16 @@ - - - Document + + + Pixel-To-Pattern + -

Pixel-To-Pattern

+

Pixel-To-Pattern

+
+
+ + - \ No newline at end of file + From 07d0f0f384997c4721edc111b90c138407887325 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:22:47 -0700 Subject: [PATCH 18/50] removed trailing comma from grid array --- public/scripts/grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/grid.js b/public/scripts/grid.js index 6401d1b..94b552a 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -5,7 +5,7 @@ const PALETTE = [ { id: "green", hex: "#22c55e" }, { id: "blue", hex: "#3b82f6" }, { id: "yellow", hex: "#f59e0b" }, - { id: "purple", hex: "#8b5cf6" }, + { id: "purple", hex: "#8b5cf6" } ]; let activeColor = PALETTE[1]; // default: black From 136ea18f6baa8e21f23b6c57f3f893bdf093b2cc Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:24:05 -0700 Subject: [PATCH 19/50] cleaned up "scripts" --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8986d00..75ace4f 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "app.js", "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "nodemon app.js" + "start": "node app.js", + "dev": "nodemon app.js", + "test": "echo \"No tests yet\" && exit 0" }, "author": "", "license": "ISC", From 42e8707b7dd9ddc8260481efc1898b681e860e0e Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:42:53 -0700 Subject: [PATCH 20/50] adding drag and color fxn to grid.js --- public/scripts/grid.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/scripts/grid.js b/public/scripts/grid.js index 94b552a..4dbb953 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -8,12 +8,15 @@ const PALETTE = [ { id: "purple", hex: "#8b5cf6" } ]; +const ROWS = 10; +const COLS = 10; + let activeColor = PALETTE[1]; // default: black const paletteEl = document.getElementById("palette"); const gridEl = document.getElementById("grid"); -// palette +// Build palette PALETTE.forEach((color, i) => { const sw = document.createElement("div"); sw.className = "swatch" + (i === 1 ? " active" : ""); From 2efca462f016ca3bd7dad37a427c0ceb008eae72 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 9 Oct 2025 10:42:56 -0700 Subject: [PATCH 21/50] Fixed local dev issue with nodemon exiting --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index ced920d..be8c5c8 100644 --- a/app.js +++ b/app.js @@ -2,7 +2,7 @@ import express from 'express'; import pool from './db.js'; const PORT = process.env.PORT || 3000; -const HOST = process.env.HOST || '0.0.0.0'; +const HOST = process.env.HOST || 'localhost'; const app = express(); app.use(express.urlencoded({ extended: false })); From e377203531dcb6a099097a442509419efa0d06f0 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 9 Oct 2025 11:01:54 -0700 Subject: [PATCH 22/50] Added grid resizeablility to grid.js --- .DS_Store | Bin 0 -> 8196 bytes public/.DS_Store | Bin 0 -> 8196 bytes public/css/style.css | 11 +++++++++-- public/scripts/grid.js | 28 +++++++++++++++++----------- 4 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 .DS_Store create mode 100644 public/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1c4b21e9dc83adafe8328c640f8644fd6ff72b47 GIT binary patch literal 8196 zcmeHMOHUL*5UySx&I-tYpu|TqaW5t!qWFmM;J9K8F$TgCg#fNIyUjW>bgwhBtV&4s ztT!*-jEO(M#FIz;0ebZ6!9U=|vsOPAhXuT;M7uZLUrl#?UEMXQe#`;@$=AxK0fqp; z!Yt74!eW~w+C|=yGPrLK*J?lH+J}H8vDVD)y;v4@6Um zzG4tX$96}kBf1(J3KboQq65*EiN2u_X&rfvR0rZJG^O!?@xWFOh`W0ycu>XPNXz|Q zgi;>!$ak3}xdY1JZ>#yHMY9_iaMY$ygcT{(AjGHi7Xq|EjVB_>Yqdw zm(Oy1D6`Zz2GPT_mL9rWdf0)N5lcvVZk@=h;p{>PptRXWCi<1GUyrN$o5x$f8U}GP zI{JxZ+S)rh_gI$IWi1x&@p>Wgl3JYiqIvN&!+Gd8WzWAA(0b7xxXM}L1&l_L5R@sZ zEZh!Qnb-5Y#>%ne7F2_k&1Q@C(AwJA`3u9&*qPDwVQ1~)nbBeA?AZDB^{h3RJ9TNg zxE!u9{shO5iRk4Mderkq`4pv1^AAd^dQRj+b55UayE&`u-mdPReZ6*H|Ng##zQMu4 z0|$rt4;?;IwD;PK1>S1V{GbgAxzWq zou5@RkE*CM@Uud8dOf0)%~ztJJh`+K(_}U!&k1X#h=<`6kAo!OY;HkXPw2`7TjFR@ zj9{`Vn&MkDiV?SPW*0-xr{N^a^CaObb6ymAtU~jTW&@ms7`ZfAsCuP)1aI!EB z*U<7f+(aC~&@FEc1-T@=NG%Dy5M$s(7&HtLI4#3I5rMufTPzc4b_c8?lR|ylg<%KH ze7rZOZnLxc^_<`TPH09HB97JYYQVyFEbK#|z`)px-<* zv>Fr#We4+J%pwWBhC)ffLa2y(c^yaG_`?uoN4L2e8w#-n(;xp3AP?WWL{pc&8%Xa+O`nt{KA0XVa{7)PA@ za#Z)40nNaF$pC*pSkO4y3R@Dzs{=Yw0s!`cSqSLj;~&uC20&Y3OCnf6unYy1p;(m| zSk(?#hQqk6{96)bIKpCPxR06{s|p3H(gRz<9bsFd?ll9Nfo=wP?Vdm$icmBc9H`$5 zFmmOm5D})l+-6i)RsMUo9{5q5&ws%^iQW?@`wYYAH&%;}S+f{BaU&`?;R?T6Vk~gm zy5rvWNwZ?6W*ChfpOR1n_*DWf$;n1c~yJLfUF5Ry&E zvJ{hNanXMcL&F^bi=_H+&$P5v2cOiQLEQ9iNC;i2g?@E@Z7m{kX`jB#XY)c91Pd(k zW1rFGvY0(48*_Ax!Hj$b^L0KH-6vrL*cDS+4IGyQ^R&R?m~AXOVd&5rDQuN|$bzq2 zOj)cuo=tz`NCQS|VBy|jA|KUJ+_mZ7fye$$-{Jj_Z2pd{Iv4^9q;R?{XdG>YEs5O3 z?cx*$)aO4ZM2lwN7%`BLYt8Wb|IX3B{~x1_^inkgnt|VC0Qb%oXZh#8IMok{=BK;` t?GZFC+^;23gg|%TLjbOK|1bpI0#_bmD{M&w3yA(9K+vE&&A=aJ;1{T!AD#dJ literal 0 HcmV?d00001 diff --git a/public/css/style.css b/public/css/style.css index 5fc272a..a53e3d9 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -33,11 +33,18 @@ h1 { .grid { display: grid; - grid-template-columns: repeat(10, 30px); - grid-template-rows: repeat(10, 30px); gap: 2px; + margin-top: 10px; } +.controls { + margin-bottom: 15px; + display: flex; + gap: 10px; + align-items: center; +} + + .cell { width: 30px; height: 30px; diff --git a/public/scripts/grid.js b/public/scripts/grid.js index 4dbb953..9f2a7ed 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -8,8 +8,6 @@ const PALETTE = [ { id: "purple", hex: "#8b5cf6" } ]; -const ROWS = 10; -const COLS = 10; let activeColor = PALETTE[1]; // default: black @@ -30,14 +28,22 @@ PALETTE.forEach((color, i) => { }); // 10x10 grid -for (let r = 0; r < 10; r++) { - for (let c = 0; c < 10; c++) { - const cell = document.createElement("div"); - cell.className = "cell"; - cell.addEventListener("click", () => { - cell.style.background = - cell.style.background === activeColor.hex ? "#ffffff" : activeColor.hex; - }); - gridEl.appendChild(cell); +function buildGrid(rows, cols) { + gridEl.innerHTML = ""; + gridEl.style.gridTemplateColumns = `repeat(${cols}, 30px)`; + gridEl.style.gridTemplateRows = `repeat(${rows}, 30px)`; + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const cell = document.createElement("div"); + cell.className = "cell"; + cell.addEventListener("click", () => { + cell.style.background = + cell.style.background === activeColor.hex ? "#ffffff" : activeColor.hex; + }); + gridEl.appendChild(cell); + } } } + +buildGrid(10, 20); From 534129a5667a044a5c390771c9e2c8f4db61f9a0 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 9 Oct 2025 11:05:56 -0700 Subject: [PATCH 23/50] added grid resize controls to home.ejs --- views/home.ejs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/views/home.ejs b/views/home.ejs index a2393cc..62d2259 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -8,6 +8,11 @@

Pixel-To-Pattern

+
+ + + +
From d98c892d350f9b872837229b177b5d8a45690e87 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 9 Oct 2025 11:08:20 -0700 Subject: [PATCH 24/50] added grid resizing funcition --- public/scripts/grid.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/scripts/grid.js b/public/scripts/grid.js index 9f2a7ed..ad37cf0 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -13,6 +13,10 @@ let activeColor = PALETTE[1]; // default: black const paletteEl = document.getElementById("palette"); const gridEl = document.getElementById("grid"); +const rowsInput = document.getElementById("rows"); +const colsInput = document.getElementById("cols"); +const generateBtn = document.getElementById("generate"); + // Build palette PALETTE.forEach((color, i) => { @@ -46,4 +50,10 @@ function buildGrid(rows, cols) { } } -buildGrid(10, 20); +buildGrid(10, 10); + +generateBtn.addEventListener("click", () => { + const rows = parseInt(rowsInput.value) || 10; + const cols = parseInt(colsInput.value) || 10; + buildGrid(rows, cols); +}); From e5f9f9fce8cba3696759b937706c0201ae7c3afd Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:28:43 -0700 Subject: [PATCH 25/50] added drag.color to grid.js. this is on branch test. main content now on branch jtest --- public/scripts/grid.js | 124 ++++++++++++++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/public/scripts/grid.js b/public/scripts/grid.js index ad37cf0..ae2d9f1 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -1,14 +1,15 @@ +// public/scripts/grid.js + const PALETTE = [ - { id: "white", hex: "#ffffff" }, - { id: "black", hex: "#000000" }, - { id: "red", hex: "#ef4444" }, - { id: "green", hex: "#22c55e" }, - { id: "blue", hex: "#3b82f6" }, + { id: "white", hex: "#ffffff" }, + { id: "black", hex: "#000000" }, + { id: "red", hex: "#ef4444" }, + { id: "green", hex: "#22c55e" }, + { id: "blue", hex: "#3b82f6" }, { id: "yellow", hex: "#f59e0b" }, - { id: "purple", hex: "#8b5cf6" } + { id: "purple", hex: "#8b5cf6" }, ]; - let activeColor = PALETTE[1]; // default: black const paletteEl = document.getElementById("palette"); @@ -17,43 +18,126 @@ const rowsInput = document.getElementById("rows"); const colsInput = document.getElementById("cols"); const generateBtn = document.getElementById("generate"); - -// Build palette +// --- Palette --- PALETTE.forEach((color, i) => { const sw = document.createElement("div"); sw.className = "swatch" + (i === 1 ? " active" : ""); sw.style.background = color.hex; + sw.title = color.id; sw.addEventListener("click", () => { activeColor = color; - document.querySelectorAll(".swatch").forEach(s => s.classList.remove("active")); + document + .querySelectorAll(".swatch") + .forEach((s) => s.classList.remove("active")); sw.classList.add("active"); }); paletteEl.appendChild(sw); }); -// 10x10 grid +// --- Grid + paint state --- +let cells = []; // 2D array: cells[row][col] -> HTMLElement + +let isMouseDown = false; +let startRow = null; +let startCol = null; +let axis = null; // 'row' | 'col' | null + +function setCellColor(r, c, hex) { + const cell = cells[r][c]; + cell.dataset.color = hex; + cell.style.background = hex; +} + +function paintSegmentTo(currR, currC) { + if (axis === "row") { + const r = startRow; + const c1 = Math.min(startCol, currC); + const c2 = Math.max(startCol, currC); + for (let c = c1; c <= c2; c++) setCellColor(r, c, activeColor.hex); + } else if (axis === "col") { + const c = startCol; + const r1 = Math.min(startRow, currR); + const r2 = Math.max(startRow, currR); + for (let r = r1; r <= r2; r++) setCellColor(r, c, activeColor.hex); + } +} + +function handleEnter(cellEl) { + if (!isMouseDown) return; + const r = Number(cellEl.dataset.row); + const c = Number(cellEl.dataset.col); + + // Lock axis on first move away from the start cell + if (axis == null) { + if (r === startRow && c !== startCol) axis = "row"; + else if (c === startCol && r !== startRow) axis = "col"; + else return; // still over the start cell + } + + // Ignore diagonals / switching axis mid-drag + if ((axis === "row" && r !== startRow) || (axis === "col" && c !== startCol)) + return; + + paintSegmentTo(r, c); +} + +// --- Build (resizable) grid --- function buildGrid(rows, cols) { + // Reset container & state gridEl.innerHTML = ""; gridEl.style.gridTemplateColumns = `repeat(${cols}, 30px)`; gridEl.style.gridTemplateRows = `repeat(${rows}, 30px)`; + cells = []; for (let r = 0; r < rows; r++) { + const rowArr = []; for (let c = 0; c < cols; c++) { const cell = document.createElement("div"); cell.className = "cell"; - cell.addEventListener("click", () => { - cell.style.background = - cell.style.background === activeColor.hex ? "#ffffff" : activeColor.hex; + cell.dataset.row = String(r); + cell.dataset.col = String(c); + cell.dataset.color = "#ffffff"; + cell.style.background = "#ffffff"; + cell.draggable = false; + + // Start stroke, paint starting cell immediately + cell.addEventListener("mousedown", (e) => { + e.preventDefault(); // prevent text selection/drag ghost + isMouseDown = true; + startRow = r; + startCol = c; + axis = null; + setCellColor(r, c, activeColor.hex); }); + + // Extend stroke when moving into cells + cell.addEventListener("mouseenter", () => handleEnter(cell)); + + // Optional: prevent native drag image + cell.addEventListener("dragstart", (e) => e.preventDefault()); + + rowArr.push(cell); gridEl.appendChild(cell); } + cells.push(rowArr); } } -buildGrid(10, 10); - -generateBtn.addEventListener("click", () => { - const rows = parseInt(rowsInput.value) || 10; - const cols = parseInt(colsInput.value) || 10; - buildGrid(rows, cols); +// End drag anywhere on the page +document.addEventListener("mouseup", () => { + isMouseDown = false; + startRow = startCol = null; + axis = null; }); + +// Initial grid +buildGrid(10, 10); + +// Regenerate with new size +if (generateBtn) { + generateBtn.addEventListener("click", () => { + const rows = parseInt(rowsInput?.value, 10) || 10; + const cols = parseInt(colsInput?.value, 10) || 10; + buildGrid(rows, cols); + }); +} From 91204689247c350be77d1bdca06e9bf5bc720648 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:09:31 -0700 Subject: [PATCH 26/50] added test.sql db file --- test.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 test.sql diff --git a/test.sql b/test.sql new file mode 100644 index 0000000..5b6bf1f --- /dev/null +++ b/test.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS patterns ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + instructions MEDIUMTEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); From caa2b84e8cc22e2701f999181ee1f2c674e6e388 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:31:05 -0700 Subject: [PATCH 27/50] added instructions.js for pattern gen and transmit to db --- app.js | 57 +++++++++++++++ public/scripts/instructions.js | 124 +++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 public/scripts/instructions.js diff --git a/app.js b/app.js index be8c5c8..b6d9564 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,9 @@ const PORT = process.env.PORT || 3000; const HOST = process.env.HOST || 'localhost'; const app = express(); + +// allow JSON bodies for API routes +app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.static("public")); app.set('view engine', 'ejs'); @@ -26,6 +29,60 @@ app.get('/ping', async (_req, res) => { res.json(rows); }); +// begin crochet pattern fxn +// --- patterns API: ONLY id, name, instructions --- +// create +app.post('/api/patterns', async (req, res) => { + try { + const { name, instructions } = req.body; + if (typeof name !== 'string' || !name.trim() || + typeof instructions !== 'string' || !instructions.trim()) { + return res.status(400).json({ error: 'name and instructions are required strings' }); + } + + const [result] = await pool.query( + 'INSERT INTO patterns (name, instructions) VALUES (?, ?)', + [name.trim(), instructions] + ); + res.status(201).json({ id: result.insertId, name: name.trim() }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'insert failed', message: err.message }); + } +}); + +// read (single) +app.get('/api/patterns/:id', async (req, res) => { + try { + const pid = Number(req.params.id); + if (!Number.isInteger(pid) || pid <= 0) { + return res.status(400).json({ error: 'invalid id' }); + } + const [rows] = await pool.query( + 'SELECT id, name, instructions FROM patterns WHERE id = ?', + [pid] + ); + if (!rows.length) return res.status(404).json({ error: 'not found' }); + res.json(rows[0]); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'fetch failed', message: err.message }); + } +}); + +// optional: list recent (handy while developing) +app.get('/api/patterns', async (_req, res) => { + try { + const [rows] = await pool.query( + 'SELECT id, name FROM patterns ORDER BY id DESC LIMIT 50' + ); + res.json(rows); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'list failed', message: err.message }); + } +}); +// end crochet instructions fxn app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); diff --git a/public/scripts/instructions.js b/public/scripts/instructions.js new file mode 100644 index 0000000..597d269 --- /dev/null +++ b/public/scripts/instructions.js @@ -0,0 +1,124 @@ +// public/scripts/instructions.js + +// --- DOM refs --- +const gridEl = document.getElementById('grid'); +const nameInput = document.getElementById('patternName'); +const saveBtn = document.getElementById('savePattern'); + +// --- utils --- +function rgbToHex(rgb) { + const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(rgb); + if (!m) return rgb; // already hex or unknown format + const h = n => Number(n).toString(16).padStart(2, '0'); + return `#${h(m[1])}${h(m[2])}${h(m[3])}`; +} + +function ensureGridSizeDataAttrs() { + // Prefer what grid.js sets; if missing, derive from CSS grid columns + if (!gridEl.dataset.rows || !gridEl.dataset.cols) { + const total = gridEl.children.length; + const cols = getComputedStyle(gridEl).gridTemplateColumns.split(' ').length || Math.sqrt(total) | 0; + const rows = cols ? Math.ceil(total / cols) : 0; + gridEl.dataset.rows = String(rows); + gridEl.dataset.cols = String(cols); + } +} + +function readGrid() { + ensureGridSizeDataAttrs(); + const rows = Number(gridEl.dataset.rows); + const cols = Number(gridEl.dataset.cols); + const out = []; + + const cells = Array.from(gridEl.children); + for (let r = 0; r < rows; r++) { + const row = []; + for (let c = 0; c < cols; c++) { + const cell = cells[r * cols + c]; + // Prefer stored color from grid.js; fallback to computed style + const hex = (cell.dataset.color || rgbToHex(getComputedStyle(cell).backgroundColor)).toLowerCase(); + row.push(hex); + } + out.push(row); + } + return { rows, cols, grid: out }; +} + +// --- crochet instruction generator (flat piece, one pixel = 1 single crochet) --- +const COLOR_ABBR = { + white: 'WH', black: 'BK', red: 'RD', green: 'GR', + blue: 'BL', yellow: 'YL', purple: 'PU' +}; + +function hexToIdMap() { + // Uses PALETTE defined in grid.js; fallback if not found + const m = {}; + if (typeof PALETTE !== 'undefined' && Array.isArray(PALETTE)) { + for (const c of PALETTE) m[c.hex.toLowerCase()] = c.id; + } + return m; +} + +function abbrFor(idOrHex, hex2id) { + const id = (idOrHex?.startsWith('#')) ? (hex2id[idOrHex] || idOrHex) : idOrHex; + return COLOR_ABBR[id] || (typeof id === 'string' ? id.slice(0, 2).toUpperCase() : '??'); +} + +function rle(arr) { + const res = []; + let prev = arr[0], count = 1; + for (let i = 1; i < arr.length; i++) { + if (arr[i] === prev) count++; + else { res.push([count, prev]); prev = arr[i]; count = 1; } + } + if (arr.length) res.push([count, prev]); + return res; +} + +function gridToCrochetInstructions(grid) { + const rows = grid.length; + const cols = rows ? grid[0].length : 0; + const hex2id = hexToIdMap(); + + const lines = []; + lines.push(`Pattern: ${rows} rows × ${cols} sts (single crochet, one stitch per pixel)`); + lines.push(`Read flat, bottom to top. Row 1 (RS) goes right→left across chart; Row 2 (WS) left→right; alternate.`); + lines.push(''); + + for (let srcR = rows - 1, printed = 1; srcR >= 0; srcR--, printed++) { + const isRS = (printed % 2 === 1); + const row = grid[srcR]; + const reading = isRS ? row.slice().reverse() : row; + + const segs = rle(reading) + .map(([n, color]) => `sc ${n} ${abbrFor(color, hex2id)}`) + .join(', '); + + const arrow = isRS ? 'RS →' : 'WS ←'; + lines.push(`Row ${printed} (${arrow}): ${segs}`); + if (printed < rows) lines.push('ch 1, turn;'); + } + return lines.join('\n'); +} + +// --- save handler --- +saveBtn?.addEventListener('click', async () => { + try { + const { grid } = readGrid(); + const instructions = gridToCrochetInstructions(grid); + const name = (nameInput?.value?.trim()) || 'Untitled'; + + const res = await fetch('/api/patterns', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, instructions }) + }); + const data = await res.json(); + if (!res.ok) return alert(`Save failed: ${data.error || res.statusText}`); + + alert(`Saved! Pattern ID: ${data.id}`); + } catch (e) { + console.error(e); + alert('Unexpected error while saving.'); + } +}); From f32e92fe9480d7c94f23773d880a33a14a9e77af Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 14 Oct 2025 10:35:35 -0700 Subject: [PATCH 28/50] added pattern.js to calcuate basic crochet pattern --- public/.DS_Store | Bin 8196 -> 8196 bytes public/scripts/pattern.js | 101 ++++++++++++++++++++++++++++++++++++++ views/home.ejs | 2 + 3 files changed, 103 insertions(+) create mode 100644 public/scripts/pattern.js diff --git a/public/.DS_Store b/public/.DS_Store index 479ee518d73e6677a5a2077b5d779b3006d08a10..530a8da48c98e1f48d861b55f1fdea3c93ee184a 100644 GIT binary patch delta 47 zcmZp1XmOa}&&aVcU^hP_$7UV@W|qxdf_E4viKuU^{mQtRUE&+diW|qmnL?t$>^E_bQ%r5bbWn%?9GXUP>3!4A{ diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js new file mode 100644 index 0000000..c904802 --- /dev/null +++ b/public/scripts/pattern.js @@ -0,0 +1,101 @@ +// public/scripts/pattern.js +// Crochet pattern generation that reads directly from the DOM (no global `cells` needed) + +// Map palette hex -> single-letter yarn codes for compact patterns +const HEX_TO_CODE = { + "#ffffff": "W", // white + "#000000": "K", // black + "#ef4444": "R", // red + "#22c55e": "G", // green + "#3b82f6": "B", // blue + "#f59e0b": "Y", // yellow + "#8b5cf6": "P", // purple +}; + +// Convert "rgb(...)" to "#rrggbb" +function rgbToHex(rgb) { + if (!rgb) return null; + const m = rgb.match(/\d+/g); + if (!m || m.length < 3) return null; + const [r, g, b] = m.map(Number); + const toHex = (n) => n.toString(16).padStart(2, "0"); + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; +} + +// Normalize any color (data-color or CSS) to our canonical hex keys +function normalizeHex(hexOrRgb) { + if (!hexOrRgb) return null; + const s = hexOrRgb.trim().toLowerCase(); + if (s.startsWith("#") && (s.length === 7)) return s; + if (s.startsWith("rgb")) return rgbToHex(s); + return null; +} + +// Helper: get code from color (defaults to white) +function codeFromColor(colorStr) { + const hex = normalizeHex(colorStr) || "#ffffff"; + return HEX_TO_CODE[hex] || "W"; +} + +// Export a 2D array of codes by reading #grid .cell in row-major order +function exportCodeGridFromDOM() { + const gridEl = document.getElementById("grid"); + const colsEl = document.getElementById("cols"); + if (!gridEl) { + console.error("Grid element #grid not found."); + return []; + } + const cols = parseInt(colsEl?.value, 10) || 10; + + const cellEls = Array.from(gridEl.querySelectorAll(".cell")); + if (cellEls.length === 0) return []; + + // chunk into rows of length `cols` + const rows = []; + for (let i = 0; i < cellEls.length; i += cols) { + const rowCells = cellEls.slice(i, i + cols); + const rowCodes = rowCells.map((cell) => { + // Prefer data-color if your painter sets it; fallback to computed background + const dataColor = cell.dataset?.color; + const styleColor = getComputedStyle(cell).backgroundColor; + const chosen = dataColor || styleColor; + return codeFromColor(chosen); + }); + rows.push(rowCodes); + } + return rows; +} + +// Run-length encode a row like ["W","W","K"] -> "2W 1K" +function rleRow(codes) { + if (codes.length === 0) return ""; + let out = []; + let curr = codes[0], count = 1; + for (let i = 1; i < codes.length; i++) { + if (codes[i] === curr) count++; + else { out.push(`${count}${curr}`); curr = codes[i]; count = 1; } + } + out.push(`${count}${curr}`); + return out.join(" "); +} + +// Build multi-line crochet pattern +function buildPatternText(codeGrid) { + return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n"); +} + +// Log pattern +function logPattern() { + const codeGrid = exportCodeGridFromDOM(); + const patternText = buildPatternText(codeGrid); + + console.log("=== Crochet Pattern (2D Codes) ==="); + console.log(codeGrid); + console.log("=== Crochet Pattern (RLE by Row) ==="); + console.log(patternText); +} + +document.addEventListener("DOMContentLoaded", () => { + const patternBtn = document.getElementById("pattern"); + if (patternBtn) patternBtn.addEventListener("click", logPattern); +}); diff --git a/views/home.ejs b/views/home.ejs index 62d2259..1a998e7 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -15,7 +15,9 @@
+ + From d99598a877146d942112646487737d6b06e7376c Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:01:47 -0700 Subject: [PATCH 29/50] added 1mb clause in app.js --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index b6d9564..da41536 100644 --- a/app.js +++ b/app.js @@ -7,7 +7,7 @@ const HOST = process.env.HOST || 'localhost'; const app = express(); // allow JSON bodies for API routes -app.use(express.json()); +app.use(express.json({ limit: '1mb'})); app.use(express.urlencoded({ extended: false })); app.use(express.static("public")); app.set('view engine', 'ejs'); From f9b7fc3c9929613c433d6170f516c57f8f0e230e Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:02:40 -0700 Subject: [PATCH 30/50] added POST function to app.js --- public/scripts/pattern.js | 64 ++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js index c904802..0c1ff40 100644 --- a/public/scripts/pattern.js +++ b/public/scripts/pattern.js @@ -1,5 +1,6 @@ // public/scripts/pattern.js // Crochet pattern generation that reads directly from the DOM (no global `cells` needed) +// + POSTS the generated text to /api/patterns // Map palette hex -> single-letter yarn codes for compact patterns const HEX_TO_CODE = { @@ -26,7 +27,7 @@ function rgbToHex(rgb) { function normalizeHex(hexOrRgb) { if (!hexOrRgb) return null; const s = hexOrRgb.trim().toLowerCase(); - if (s.startsWith("#") && (s.length === 7)) return s; + if (s.startsWith("#") && s.length === 7) return s; if (s.startsWith("rgb")) return rgbToHex(s); return null; } @@ -37,15 +38,32 @@ function codeFromColor(colorStr) { return HEX_TO_CODE[hex] || "W"; } +// Derive column count robustly +function getColCount(gridEl) { + // Prefer data-cols if grid.js set it + const ds = Number(gridEl.dataset?.cols); + if (Number.isInteger(ds) && ds > 0) return ds; + + // Next try CSS gridTemplateColumns + const styleCols = getComputedStyle(gridEl).gridTemplateColumns + .trim() + .split(/\s+/) + .filter(Boolean).length; + if (styleCols > 0) return styleCols; + + // Fallback to the #cols input (your UI) + const colsEl = document.getElementById("cols"); + return parseInt(colsEl?.value, 10) || 10; +} + // Export a 2D array of codes by reading #grid .cell in row-major order function exportCodeGridFromDOM() { const gridEl = document.getElementById("grid"); - const colsEl = document.getElementById("cols"); if (!gridEl) { console.error("Grid element #grid not found."); return []; } - const cols = parseInt(colsEl?.value, 10) || 10; + const cols = getColCount(gridEl); const cellEls = Array.from(gridEl.querySelectorAll(".cell")); if (cellEls.length === 0) return []; @@ -69,7 +87,7 @@ function exportCodeGridFromDOM() { // Run-length encode a row like ["W","W","K"] -> "2W 1K" function rleRow(codes) { if (codes.length === 0) return ""; - let out = []; + const out = []; let curr = codes[0], count = 1; for (let i = 1; i < codes.length; i++) { if (codes[i] === curr) count++; @@ -79,23 +97,53 @@ function rleRow(codes) { return out.join(" "); } -// Build multi-line crochet pattern +// Build multi-line crochet pattern (top-to-bottom, as you had) function buildPatternText(codeGrid) { return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n"); } -// Log pattern -function logPattern() { +// --- NEW: POST helper --- +async function savePatternToDB(name, instructions) { + const res = await fetch('/api/patterns', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name, instructions }) + }); + const data = await res.json().catch(() => ({})); + if (!res.ok) throw new Error(data.error || res.statusText); + return data.id; +} + +// Generate + log + save +async function generateAndSavePattern() { const codeGrid = exportCodeGridFromDOM(); + if (!codeGrid.length) { + alert('No grid cells found. Click "Generate Grid" first.'); + return; + } + const patternText = buildPatternText(codeGrid); + // Keep your logs console.log("=== Crochet Pattern (2D Codes) ==="); console.log(codeGrid); console.log("=== Crochet Pattern (RLE by Row) ==="); console.log(patternText); + + // Name input is optional; fallback to timestamped default + const nameInput = document.getElementById('patternName'); + const name = (nameInput?.value?.trim()) || `Pattern_${new Date().toISOString().replace(/[:.]/g, '-')}`; + + try { + const id = await savePatternToDB(name, patternText); + alert(`Saved! Pattern ID: ${id}`); + } catch (err) { + console.error('Save failed:', err); + alert(`Save failed: ${err.message}`); + } } document.addEventListener("DOMContentLoaded", () => { const patternBtn = document.getElementById("pattern"); - if (patternBtn) patternBtn.addEventListener("click", logPattern); + if (patternBtn) patternBtn.addEventListener("click", generateAndSavePattern); }); From 1e5342aabc412a6b2ade8fc137cc663048784c34 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Tue, 14 Oct 2025 11:10:45 -0700 Subject: [PATCH 31/50] updated pattern.js to show pattern under generate pattern button. --- public/scripts/pattern.js | 40 +++++++++++++++++++++++---------------- views/home.ejs | 1 + 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js index c904802..62d2618 100644 --- a/public/scripts/pattern.js +++ b/public/scripts/pattern.js @@ -1,7 +1,3 @@ -// public/scripts/pattern.js -// Crochet pattern generation that reads directly from the DOM (no global `cells` needed) - -// Map palette hex -> single-letter yarn codes for compact patterns const HEX_TO_CODE = { "#ffffff": "W", // white "#000000": "K", // black @@ -12,7 +8,6 @@ const HEX_TO_CODE = { "#8b5cf6": "P", // purple }; -// Convert "rgb(...)" to "#rrggbb" function rgbToHex(rgb) { if (!rgb) return null; const m = rgb.match(/\d+/g); @@ -22,22 +17,19 @@ function rgbToHex(rgb) { return `#${toHex(r)}${toHex(g)}${toHex(b)}`; } -// Normalize any color (data-color or CSS) to our canonical hex keys function normalizeHex(hexOrRgb) { if (!hexOrRgb) return null; const s = hexOrRgb.trim().toLowerCase(); - if (s.startsWith("#") && (s.length === 7)) return s; + if (s.startsWith("#") && s.length === 7) return s; if (s.startsWith("rgb")) return rgbToHex(s); return null; } -// Helper: get code from color (defaults to white) function codeFromColor(colorStr) { const hex = normalizeHex(colorStr) || "#ffffff"; return HEX_TO_CODE[hex] || "W"; } -// Export a 2D array of codes by reading #grid .cell in row-major order function exportCodeGridFromDOM() { const gridEl = document.getElementById("grid"); const colsEl = document.getElementById("cols"); @@ -50,12 +42,11 @@ function exportCodeGridFromDOM() { const cellEls = Array.from(gridEl.querySelectorAll(".cell")); if (cellEls.length === 0) return []; - // chunk into rows of length `cols` const rows = []; for (let i = 0; i < cellEls.length; i += cols) { const rowCells = cellEls.slice(i, i + cols); const rowCodes = rowCells.map((cell) => { - // Prefer data-color if your painter sets it; fallback to computed background + const dataColor = cell.dataset?.color; const styleColor = getComputedStyle(cell).backgroundColor; const chosen = dataColor || styleColor; @@ -66,7 +57,6 @@ function exportCodeGridFromDOM() { return rows; } -// Run-length encode a row like ["W","W","K"] -> "2W 1K" function rleRow(codes) { if (codes.length === 0) return ""; let out = []; @@ -79,16 +69,34 @@ function rleRow(codes) { return out.join(" "); } -// Build multi-line crochet pattern function buildPatternText(codeGrid) { return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n"); } -// Log pattern -function logPattern() { +function renderPattern() { + const outEl = document.getElementById("pattern-output"); const codeGrid = exportCodeGridFromDOM(); + + if (!outEl) { + console.warn("Missing #pattern-output container."); + return; + } + + if (!codeGrid.length) { + outEl.textContent = "No grid found. Click 'Generate Grid' and color some cells first."; + return; + } + const patternText = buildPatternText(codeGrid); + // Show on page + outEl.textContent = patternText; + + // Optional niceties + outEl.setAttribute("aria-live", "polite"); + outEl.scrollIntoView({ behavior: "smooth", block: "nearest" }); + + // Still log for devs console.log("=== Crochet Pattern (2D Codes) ==="); console.log(codeGrid); console.log("=== Crochet Pattern (RLE by Row) ==="); @@ -97,5 +105,5 @@ function logPattern() { document.addEventListener("DOMContentLoaded", () => { const patternBtn = document.getElementById("pattern"); - if (patternBtn) patternBtn.addEventListener("click", logPattern); + if (patternBtn) patternBtn.addEventListener("click", renderPattern); }); diff --git a/views/home.ejs b/views/home.ejs index 1a998e7..d75e500 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -16,6 +16,7 @@
+

 
   
   

From c33a7c6d10b3325e1278c5781789db3880cfcfe0 Mon Sep 17 00:00:00 2001
From: Ben Mojo <155706745+oakes777@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:13:13 -0700
Subject: [PATCH 32/50] created sprint2notes.md file

---
 sprint2notes.md | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
 create mode 100644 sprint2notes.md

diff --git a/sprint2notes.md b/sprint2notes.md
new file mode 100644
index 0000000..425c44d
--- /dev/null
+++ b/sprint2notes.md
@@ -0,0 +1,117 @@
+# Sprint 2 Notes — (Pixel-to-Pattern)
+
+## What we shipped
+
+* **Grid UX**: resizable grid; palette; **click + drag** paints straight **rows/columns** (axis locks on first move).
+* **Pattern generation (client)**: `public/scripts/pattern.js` reads the DOM, builds per-row RLE text (e.g., `Row 1: 9W 10Y 9W`).
+* **Persistence (server)**: `POST /api/patterns` stores **{ id, name, instructions }**; `GET /api/patterns/:id` fetches it.
+
+## Branches
+
+* **`test`** — grid drag-to-paint feature.
+* **`dbtest`** — app.js with **POST instructions** route wired to DB.
+* (main untouched)
+
+## DB + Env (as actually used)
+
+**`test.sql` (no changes):**
+
+```sql
+CREATE TABLE IF NOT EXISTS patterns (
+  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+  name VARCHAR(255) NOT NULL,
+  instructions MEDIUMTEXT NOT NULL,
+  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+```
+
+**`.env` (current):**
+
+```
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_USER=p2p_user
+DB_PASS=sulesule
+DB_NAME=p2p_db
+
+HOST=0.0.0.0
+PORT=3000
+```
+
+> Note: `DB_HOST=127.0.0.1` means the app and DB are on the **same machine** (works on each MacBook and on the VM when both app+DB run there).
+
+## App routes (in `dbtest`)
+
+* `POST /api/patterns` → insert `{ name, instructions }`
+* `GET /api/patterns/:id` → return `{ id, name, instructions, created_at }`
+* `GET /api/patterns` → list recent (dev helper)
+* `GET /health/db` → quick MySQL connectivity check
+
+## Frontend wiring
+
+* `views/home.ejs` includes:
+
+  * Grid controls, palette, grid, and **Generate Pattern** button (`#pattern`)
+  * Scripts in order:
+
+    ```html
+    
+    
+    ```
+* `public/scripts/pattern.js`:
+
+  * Reads `.cell` colors → builds RLE rows → **console logs**
+  * **Also POSTs** `{ name, instructions }` to `/api/patterns`
+  * Optional `` (if present, used; else auto name)
+
+## How to run (local + VM)
+
+```bash
+# 1) Switch to the branch with the POST route
+git checkout dbtest
+git pull --rebase
+
+# 2) Install deps
+npm install
+
+# 3) Ensure DB table exists (run your test.sql on each machine)
+mysql -u p2p_user -p p2p_db < test.sql
+
+# 4) Start the server
+npm run dev   # or npm start
+
+# 5) Open the app
+# http://localhost:3000 (or VM IP:3000 if remote browser)
+```
+
+## Smoke tests
+
+1. **DB health**
+   Open `/health/db` → expect `{"db":"ok","result":1}`.
+2. **UI**
+   Generate a grid; paint using click-drag rows/columns; white erases.
+3. **Generate + Save**
+   Click **Generate Pattern** → alert shows saved **ID**.
+4. **Verify API**
+   Visit `/api/patterns/` → expect JSON with `id,name,instructions,created_at`.
+5. **cURL insert** (optional)
+
+   ```bash
+   curl -s -X POST http://localhost:3000/api/patterns \
+     -H 'Content-Type: application/json' \
+     -d '{"name":"Smoke Test","instructions":"Row 1: 10W"}'
+   ```
+
+## Known limits (Sprint 2)
+
+* Instructions are **top-to-bottom RLE** (no RS/WS alternation yet).
+* No auth; patterns are readable by ID.
+* Only `{ id, name, instructions }` stored (grid/palette not persisted yet).
+
+## Next up (Sprint 3 candidates)
+
+* Alternate instruction style (RS/WS, bottom-up).
+* Save/load full charts (store grid + palette).
+* TXT/PDF export and shareable view.
+* Auth + “My Patterns”; edit/delete.
+* Clear/Undo/Redo; freehand mode; Shift = straight line.

From c9c22eb36a010f7cafeee698404301885606cbe5 Mon Sep 17 00:00:00 2001
From: Jameson Farmer 
Date: Tue, 14 Oct 2025 11:24:10 -0700
Subject: [PATCH 33/50] added save button and label input box

---
 views/home.ejs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/views/home.ejs b/views/home.ejs
index d75e500..042a019 100644
--- a/views/home.ejs
+++ b/views/home.ejs
@@ -18,6 +18,9 @@
   
   

 
+  
+  
+
   
   
 

From 2904ce8438a227923a5b7cdd1697ff1a32fdff71 Mon Sep 17 00:00:00 2001
From: Ben Mojo <155706745+oakes777@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:32:21 -0700
Subject: [PATCH 34/50] fixed pattern.js

---
 public/scripts/pattern.js | 35 ++++++++++++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 5 deletions(-)

diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js
index f6eb86e..0c1ff40 100644
--- a/public/scripts/pattern.js
+++ b/public/scripts/pattern.js
@@ -1,5 +1,6 @@
 // public/scripts/pattern.js
 // Crochet pattern generation that reads directly from the DOM (no global `cells` needed)
+// + POSTS the generated text to /api/patterns
 
 // Map palette hex -> single-letter yarn codes for compact patterns
 const HEX_TO_CODE = {
@@ -12,6 +13,7 @@ const HEX_TO_CODE = {
   "#8b5cf6": "P", // purple
 };
 
+// Convert "rgb(...)" to "#rrggbb"
 function rgbToHex(rgb) {
   if (!rgb) return null;
   const m = rgb.match(/\d+/g);
@@ -21,6 +23,7 @@ function rgbToHex(rgb) {
   return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
 }
 
+// Normalize any color (data-color or CSS) to our canonical hex keys
 function normalizeHex(hexOrRgb) {
   if (!hexOrRgb) return null;
   const s = hexOrRgb.trim().toLowerCase();
@@ -29,6 +32,7 @@ function normalizeHex(hexOrRgb) {
   return null;
 }
 
+// Helper: get code from color (defaults to white)
 function codeFromColor(colorStr) {
   const hex = normalizeHex(colorStr) || "#ffffff";
   return HEX_TO_CODE[hex] || "W";
@@ -52,6 +56,7 @@ function getColCount(gridEl) {
   return parseInt(colsEl?.value, 10) || 10;
 }
 
+// Export a 2D array of codes by reading #grid .cell in row-major order
 function exportCodeGridFromDOM() {
   const gridEl = document.getElementById("grid");
   if (!gridEl) {
@@ -63,11 +68,12 @@ function exportCodeGridFromDOM() {
   const cellEls = Array.from(gridEl.querySelectorAll(".cell"));
   if (cellEls.length === 0) return [];
 
+  // chunk into rows of length `cols`
   const rows = [];
   for (let i = 0; i < cellEls.length; i += cols) {
     const rowCells = cellEls.slice(i, i + cols);
     const rowCodes = rowCells.map((cell) => {
-      
+      // Prefer data-color if your painter sets it; fallback to computed background
       const dataColor = cell.dataset?.color;
       const styleColor = getComputedStyle(cell).backgroundColor;
       const chosen = dataColor || styleColor;
@@ -78,6 +84,7 @@ function exportCodeGridFromDOM() {
   return rows;
 }
 
+// Run-length encode a row like ["W","W","K"] -> "2W 1K"
 function rleRow(codes) {
   if (codes.length === 0) return "";
   const out = [];
@@ -90,16 +97,34 @@ function rleRow(codes) {
   return out.join(" ");
 }
 
-// Build multi-line crochet pattern
+// Build multi-line crochet pattern (top-to-bottom, as you had)
 function buildPatternText(codeGrid) {
   return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n");
 }
 
-// Log pattern
-function logPattern() {
+// --- NEW: POST helper ---
+async function savePatternToDB(name, instructions) {
+  const res = await fetch('/api/patterns', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ name, instructions })
+  });
+  const data = await res.json().catch(() => ({}));
+  if (!res.ok) throw new Error(data.error || res.statusText);
+  return data.id;
+}
+
+// Generate + log + save
+async function generateAndSavePattern() {
   const codeGrid = exportCodeGridFromDOM();
+  if (!codeGrid.length) {
+    alert('No grid cells found. Click "Generate Grid" first.');
+    return;
+  }
+
   const patternText = buildPatternText(codeGrid);
 
+  // Keep your logs
   console.log("=== Crochet Pattern (2D Codes) ===");
   console.log(codeGrid);
   console.log("=== Crochet Pattern (RLE by Row) ===");
@@ -120,5 +145,5 @@ function logPattern() {
 
 document.addEventListener("DOMContentLoaded", () => {
   const patternBtn = document.getElementById("pattern");
-  if (patternBtn) patternBtn.addEventListener("click", logPattern);
+  if (patternBtn) patternBtn.addEventListener("click", generateAndSavePattern);
 });

From 84c886270e5d3e86e4f8b81f9106d19bca103fb6 Mon Sep 17 00:00:00 2001
From: Ben Mojo <155706745+oakes777@users.noreply.github.com>
Date: Tue, 14 Oct 2025 11:41:07 -0700
Subject: [PATCH 35/50] added naming fxn to home.ejs and pattern.js

---
 public/scripts/pattern.js | 136 +++++++++++++++++++++++++-------------
 views/home.ejs            |  58 ++++++++++------
 2 files changed, 127 insertions(+), 67 deletions(-)

diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js
index 0c1ff40..b81002e 100644
--- a/public/scripts/pattern.js
+++ b/public/scripts/pattern.js
@@ -1,8 +1,7 @@
 // public/scripts/pattern.js
-// Crochet pattern generation that reads directly from the DOM (no global `cells` needed)
-// + POSTS the generated text to /api/patterns
+// Generate + render pattern text, require a name, then POST to /api/patterns
 
-// Map palette hex -> single-letter yarn codes for compact patterns
+// --- Palette mapping (your codes) ---
 const HEX_TO_CODE = {
   "#ffffff": "W", // white
   "#000000": "K", // black
@@ -13,7 +12,7 @@ const HEX_TO_CODE = {
   "#8b5cf6": "P", // purple
 };
 
-// Convert "rgb(...)" to "#rrggbb"
+// --- Color helpers ---
 function rgbToHex(rgb) {
   if (!rgb) return null;
   const m = rgb.match(/\d+/g);
@@ -22,8 +21,6 @@ function rgbToHex(rgb) {
   const toHex = (n) => n.toString(16).padStart(2, "0");
   return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
 }
-
-// Normalize any color (data-color or CSS) to our canonical hex keys
 function normalizeHex(hexOrRgb) {
   if (!hexOrRgb) return null;
   const s = hexOrRgb.trim().toLowerCase();
@@ -31,32 +28,29 @@ function normalizeHex(hexOrRgb) {
   if (s.startsWith("rgb")) return rgbToHex(s);
   return null;
 }
-
-// Helper: get code from color (defaults to white)
 function codeFromColor(colorStr) {
   const hex = normalizeHex(colorStr) || "#ffffff";
   return HEX_TO_CODE[hex] || "W";
 }
 
-// Derive column count robustly
+// --- Grid reading ---
 function getColCount(gridEl) {
-  // Prefer data-cols if grid.js set it
+  // Prefer data-cols set by grid.js
   const ds = Number(gridEl.dataset?.cols);
   if (Number.isInteger(ds) && ds > 0) return ds;
 
-  // Next try CSS gridTemplateColumns
-  const styleCols = getComputedStyle(gridEl).gridTemplateColumns
-    .trim()
+  // Fallback: CSS grid template
+  const styleCols = getComputedStyle(gridEl)
+    .gridTemplateColumns.trim()
     .split(/\s+/)
     .filter(Boolean).length;
   if (styleCols > 0) return styleCols;
 
-  // Fallback to the #cols input (your UI)
+  // Last resort: input field
   const colsEl = document.getElementById("cols");
   return parseInt(colsEl?.value, 10) || 10;
 }
 
-// Export a 2D array of codes by reading #grid .cell in row-major order
 function exportCodeGridFromDOM() {
   const gridEl = document.getElementById("grid");
   if (!gridEl) {
@@ -64,86 +58,138 @@ function exportCodeGridFromDOM() {
     return [];
   }
   const cols = getColCount(gridEl);
-
   const cellEls = Array.from(gridEl.querySelectorAll(".cell"));
   if (cellEls.length === 0) return [];
 
-  // chunk into rows of length `cols`
   const rows = [];
   for (let i = 0; i < cellEls.length; i += cols) {
     const rowCells = cellEls.slice(i, i + cols);
     const rowCodes = rowCells.map((cell) => {
-      // Prefer data-color if your painter sets it; fallback to computed background
       const dataColor = cell.dataset?.color;
       const styleColor = getComputedStyle(cell).backgroundColor;
-      const chosen = dataColor || styleColor;
-      return codeFromColor(chosen);
+      return codeFromColor(dataColor || styleColor);
     });
     rows.push(rowCodes);
   }
   return rows;
 }
 
-// Run-length encode a row like ["W","W","K"] -> "2W 1K"
+// --- RLE + text building (your format) ---
 function rleRow(codes) {
   if (codes.length === 0) return "";
   const out = [];
-  let curr = codes[0], count = 1;
+  let curr = codes[0],
+    count = 1;
   for (let i = 1; i < codes.length; i++) {
     if (codes[i] === curr) count++;
-    else { out.push(`${count}${curr}`); curr = codes[i]; count = 1; }
+    else {
+      out.push(`${count}${curr}`);
+      curr = codes[i];
+      count = 1;
+    }
   }
   out.push(`${count}${curr}`);
   return out.join(" ");
 }
-
-// Build multi-line crochet pattern (top-to-bottom, as you had)
 function buildPatternText(codeGrid) {
   return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n");
 }
 
-// --- NEW: POST helper ---
-async function savePatternToDB(name, instructions) {
-  const res = await fetch('/api/patterns', {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json' },
-    body: JSON.stringify({ name, instructions })
-  });
-  const data = await res.json().catch(() => ({}));
-  if (!res.ok) throw new Error(data.error || res.statusText);
-  return data.id;
+// --- UI state ---
+let lastPatternText = ""; // what we rendered (and will POST)
+
+function setSaveEnabled(enabled) {
+  const saveBtn = document.getElementById("savePattern");
+  if (saveBtn) saveBtn.disabled = !enabled;
 }
 
-// Generate + log + save
-async function generateAndSavePattern() {
+// --- Render preview (Jameson’s behavior) ---
+function renderPattern() {
+  const outEl = document.getElementById("pattern-output");
   const codeGrid = exportCodeGridFromDOM();
+
+  if (!outEl) {
+    console.warn("Missing #pattern-output container.");
+    return;
+  }
   if (!codeGrid.length) {
-    alert('No grid cells found. Click "Generate Grid" first.');
+    outEl.textContent =
+      "No grid found. Click 'Generate Grid' and color some cells first.";
+    lastPatternText = "";
+    setSaveEnabled(false);
     return;
   }
 
   const patternText = buildPatternText(codeGrid);
+  lastPatternText = patternText;
+
+  outEl.textContent = patternText;
+  outEl.setAttribute("aria-live", "polite");
+  outEl.scrollIntoView({ behavior: "smooth", block: "nearest" });
 
-  // Keep your logs
+  // Dev logs
   console.log("=== Crochet Pattern (2D Codes) ===");
   console.log(codeGrid);
   console.log("=== Crochet Pattern (RLE by Row) ===");
   console.log(patternText);
 
-  // Name input is optional; fallback to timestamped default
-  const nameInput = document.getElementById('patternName');
-  const name = (nameInput?.value?.trim()) || `Pattern_${new Date().toISOString().replace(/[:.]/g, '-')}`;
+  // Enable save only if name is present
+  const name = (document.getElementById("patternName")?.value || "").trim();
+  setSaveEnabled(Boolean(name));
+}
+
+// --- POST helper ---
+async function savePatternToDB(name, instructions) {
+  const res = await fetch("/api/patterns", {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify({ name, instructions }),
+  });
+  const data = await res.json().catch(() => ({}));
+  if (!res.ok) throw new Error(data.error || res.statusText);
+  return data.id;
+}
+
+// --- Save click (requires label/name + rendered pattern) ---
+async function handleSaveClick() {
+  const nameInput = document.getElementById("patternName");
+  const name = (nameInput?.value || "").trim();
+
+  if (!lastPatternText) {
+    // No preview yet—generate it first
+    renderPattern();
+    if (!lastPatternText) return; // still nothing, bail
+  }
+  if (!name) {
+    alert("Please enter a pattern name before saving.");
+    nameInput?.focus();
+    return;
+  }
 
   try {
-    const id = await savePatternToDB(name, patternText);
+    const id = await savePatternToDB(name, lastPatternText);
     alert(`Saved! Pattern ID: ${id}`);
   } catch (err) {
-    console.error('Save failed:', err);
+    console.error("Save failed:", err);
     alert(`Save failed: ${err.message}`);
   }
 }
 
+// --- Wire up ---
 document.addEventListener("DOMContentLoaded", () => {
   const patternBtn = document.getElementById("pattern");
-  if (patternBtn) patternBtn.addEventListener("click", generateAndSavePattern);
+  const saveBtn = document.getElementById("savePattern");
+  const nameInput = document.getElementById("patternName");
+
+  if (patternBtn) patternBtn.addEventListener("click", renderPattern);
+  if (saveBtn) saveBtn.addEventListener("click", handleSaveClick);
+
+  // Live-enable Save only when both name + preview exist
+  nameInput?.addEventListener("input", () => {
+    const hasName = Boolean(nameInput.value.trim());
+    setSaveEnabled(hasName && Boolean(lastPatternText));
+  });
+
+  // Start disabled
+  setSaveEnabled(false);
 });
diff --git a/views/home.ejs b/views/home.ejs
index 042a019..15484d3 100644
--- a/views/home.ejs
+++ b/views/home.ejs
@@ -1,27 +1,41 @@
 
 
-
-  
-  
-  Pixel-To-Pattern
-  
-
-
-  

Pixel-To-Pattern

-
- - - -
-
-
- -

+  
+    
+    
+    Pixel-To-Pattern
+    
+  
+  
+    

Pixel-To-Pattern

+
+ + + +
+
- - + - - - +
+ + + + +

+
+    
+    
+ + +
+ + + + + From 0521b3f521c213012aeefebc286e85976b40b045 Mon Sep 17 00:00:00 2001 From: Ben Mojo <155706745+oakes777@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:54:21 -0700 Subject: [PATCH 36/50] merged sprint1notes and sprint2notes into README and edited for easy read through --- README.md | 211 ++++++++++++++++++++++++++++++------------------ sprint1notes.md | 20 ----- sprint2notes.md | 117 --------------------------- 3 files changed, 133 insertions(+), 215 deletions(-) delete mode 100644 sprint1notes.md delete mode 100644 sprint2notes.md diff --git a/README.md b/README.md index 7f7c59d..d86bd34 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,135 @@ Project Name: Pixel to Pattern - Tagline: Create your perfect tapestry - -Target Users: Beginner- Advanced crocheters - - - -Feature Breakdown - -MVP Features: - - Create: Pixel drawing to be converted - Pixel drawing is converted to pattern row by row: - Basically counting how many pixels in a row are a certain color. - EX: ( sc = single crochet) - Row 1: 28 sc (white) - Row 2: 9 sc (white), 10 sc (yellow), 9 sc (white) - Row 3: 8 sc (white), 10 sc (yellow), 9 sc (white) - Read: All the submitted patterns - Update: TBD - Delete: Delete pattern user has posted - - - -Extended: - - Comments under posts - Sidebar with links to crochet tutorials or pixel art tutorials - - - -Data Model - -Core Entities: Users, Posts, Comments - -Key Relationships: Each post/comment has one User, can be viewed by all Users - -CRUD Operations: - - Create: Pixel drawings, comments - - Read: All submitted drawings and comments - - Update: TBD - - Delete: Delete Patterns and comments - - - -User Experience - -User Flows: - - Land on Home Page that includes: - posts from other users - sidebar with resources/ links - Floating plus button to post - Add Post Page: - Generic canvas generated with responsive input at top of page to change canvas size - Drawing tools to left of canvas: - Pencil (fills one pixel) - Color - Eraser - Fill Canvas - Clear Canvas - View Post Page - After User posts their canvas or clicks on post - Display image of pixel drawing - Underneath drawing, generated crochet pattern is written out - - - - - -Madeleine Chance - - - -Software Development and Data Analytics Student, Green River College +Target Users: Beginner – Advanced crocheters + +ORIGINAL PLAN + +* Feature Breakdown (MVP) + + * Create: user paints a pixel drawing that is converted to a crochet pattern row-by-row (count stitches per color). + Example (sc = single crochet): + Row 1: 28 sc (white) + Row 2: 9 sc (white), 10 sc (yellow), 9 sc (white) + Row 3: 8 sc (white), 10 sc (yellow), 9 sc (white) + * Read: view all submitted patterns + * Update: TBD + * Delete: remove a pattern the user posted +* Extended Ideas + + * Comments under posts + * Sidebar with links to crochet or pixel-art tutorials +* Data Model (initial concept) + + * Core Entities: Users, Posts, Comments + * Relationships: each post/comment belongs to one user; everyone can view + * CRUD: create pixel drawings and comments; read all; update TBD; delete patterns and comments +* User Experience (planned) + + * Home Page: feed of posts, sidebar resources, floating + to post + * Add Post: responsive canvas size (width/height independent), tools (pencil, color, eraser, fill, clear) + * View Post: show pixel image; underneath, generated crochet pattern text +* Credits + + * Madeleine Chance, Software Development and Data Analytics Student, Green River College + +SPRINT1 WORK + +* Direction + + * Build a grid where users choose size (1–50 by 1–50), select basic colors, and paint pixel-by-pixel. + * Backend will convert the pixel grid into a crochet pattern. +* Milestones + + 1. Hello World in Node + 2. Deploy the app + 3. Set up MySQL + 4. Connect using a pool from code + 5. Build the frontend +* Front End + + * Variable grid (1–50 x 1–50) + * Sprint goal: save a generated pattern to the database with a label + * Extras considered: DB view page, sticky header with grid-size inputs and logo/index title +* Back End + + * Frontend connects to DB (Workbench used during setup) + * Data fields for initial persistence: ID, Label, pattern saved as an array (early concept before final Sprint 2 schema) + +SPRINT2 WORK + +* What we shipped + + * Grid UX: resizable grid; palette; click + drag paints straight rows or columns (axis locks on first move) + * Pattern generation (client): reads DOM cells and produces row-by-row RLE text (example: Row 1: 9W 10Y 9W) + * Persistence (server): POST /api/patterns stores { id, name, instructions }; GET /api/patterns/:id fetches a saved pattern +* Branches + + * test: grid drag-to-paint feature + * dbtest: POST instructions route wired to DB in app.js + * main: unchanged during this sprint +* Database and environment (current) + + * test.sql + CREATE TABLE IF NOT EXISTS patterns ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + instructions MEDIUMTEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + * .env + DB_HOST=127.0.0.1 + DB_PORT=3306 + DB_USER=p2p_user + DB_PASS=sulesule + DB_NAME=p2p_db + HOST=0.0.0.0 + PORT=3000 + * Note: DB_HOST=127.0.0.1 assumes app and DB run on the same machine (works on each MacBook and on the VM when both app and DB run there) +* App routes (in dbtest) + + * POST /api/patterns → insert { name, instructions } + * GET /api/patterns/:id → return { id, name, instructions, created_at } + * GET /api/patterns → list recent (dev helper) + * GET /health/db → simple MySQL connectivity check +* Frontend wiring + + * views/home.ejs includes grid controls, palette, grid, a Generate Pattern button + * Scripts load in order: grid.js then pattern.js + * pattern.js reads .cell colors → builds RLE rows for on-page preview → requires a name → POSTs { name, instructions } to /api/patterns +* How to run (local or VM) + + * Switch to branch with POST route: + git checkout dbtest + git pull --rebase + * Install dependencies: + npm install + * Ensure DB table exists on each machine: + mysql -u p2p_user -p p2p_db < test.sql + * Start the server: + npm run dev + or + npm start + * Open the app: + [http://localhost:3000](http://localhost:3000) (or VM_IP:3000 if accessing remotely) +* Smoke tests + + 1. Open /health/db → expect {"db":"ok","result":1} + 2. Generate grid; paint with click-drag rows/columns; white erases + 3. Click Generate Pattern to see preview, enter a name, click Save Pattern → alert shows saved ID + 4. Visit /api/patterns/ID → expect JSON { id, name, instructions, created_at } + 5. Optional cURL insert: + curl -s -X POST [http://localhost:3000/api/patterns](http://localhost:3000/api/patterns) + -H 'Content-Type: application/json' + -d '{"name":"Smoke Test","instructions":"Row 1: 10W"}' +* Known limits at end of Sprint 2 + + * Instructions are top-to-bottom RLE (no RS/WS alternating yet) + * No authentication; patterns are readable by ID + * Only { id, name, instructions } are stored (grid and palette not persisted yet) +* Next sprint candidates + + * RS/WS alternating, bottom-up reading; left-handed mode option + * Save/load full charts (persist grid + palette) + * TXT/PDF export and shareable read-only view + * Auth and “My Patterns”; edit/delete + * Clear, Undo/Redo, freehand mode, Shift for straight lines diff --git a/sprint1notes.md b/sprint1notes.md deleted file mode 100644 index aff5906..0000000 --- a/sprint1notes.md +++ /dev/null @@ -1,20 +0,0 @@ -OG described an app where user can choose a grid size, then choose basic color scheme and 'paint' graphic pixel by pixel to make a picture - -Then backend takes pixel grid and converts to crochet pattern - -1st: get 'hello world' app working with Node -2nd: get app deployed -3rd: set up mysql -4th: connect with code using pool -5th: build frontend - -Pixel To Pattern Front End - -variable grid 1-50 x 1-50 -current sprint end goals - save pattern to db with a label -extra: db view page -sticky header -> entry field for grid size (h/w don’t have to be equal); logo/index page name; - - -Back End: front end has to connect to db through workbench -data fields: ID, Label, pattern saved as an array \ No newline at end of file diff --git a/sprint2notes.md b/sprint2notes.md deleted file mode 100644 index 425c44d..0000000 --- a/sprint2notes.md +++ /dev/null @@ -1,117 +0,0 @@ -# Sprint 2 Notes — (Pixel-to-Pattern) - -## What we shipped - -* **Grid UX**: resizable grid; palette; **click + drag** paints straight **rows/columns** (axis locks on first move). -* **Pattern generation (client)**: `public/scripts/pattern.js` reads the DOM, builds per-row RLE text (e.g., `Row 1: 9W 10Y 9W`). -* **Persistence (server)**: `POST /api/patterns` stores **{ id, name, instructions }**; `GET /api/patterns/:id` fetches it. - -## Branches - -* **`test`** — grid drag-to-paint feature. -* **`dbtest`** — app.js with **POST instructions** route wired to DB. -* (main untouched) - -## DB + Env (as actually used) - -**`test.sql` (no changes):** - -```sql -CREATE TABLE IF NOT EXISTS patterns ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - instructions MEDIUMTEXT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); -``` - -**`.env` (current):** - -``` -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_USER=p2p_user -DB_PASS=sulesule -DB_NAME=p2p_db - -HOST=0.0.0.0 -PORT=3000 -``` - -> Note: `DB_HOST=127.0.0.1` means the app and DB are on the **same machine** (works on each MacBook and on the VM when both app+DB run there). - -## App routes (in `dbtest`) - -* `POST /api/patterns` → insert `{ name, instructions }` -* `GET /api/patterns/:id` → return `{ id, name, instructions, created_at }` -* `GET /api/patterns` → list recent (dev helper) -* `GET /health/db` → quick MySQL connectivity check - -## Frontend wiring - -* `views/home.ejs` includes: - - * Grid controls, palette, grid, and **Generate Pattern** button (`#pattern`) - * Scripts in order: - - ```html - - - ``` -* `public/scripts/pattern.js`: - - * Reads `.cell` colors → builds RLE rows → **console logs** - * **Also POSTs** `{ name, instructions }` to `/api/patterns` - * Optional `` (if present, used; else auto name) - -## How to run (local + VM) - -```bash -# 1) Switch to the branch with the POST route -git checkout dbtest -git pull --rebase - -# 2) Install deps -npm install - -# 3) Ensure DB table exists (run your test.sql on each machine) -mysql -u p2p_user -p p2p_db < test.sql - -# 4) Start the server -npm run dev # or npm start - -# 5) Open the app -# http://localhost:3000 (or VM IP:3000 if remote browser) -``` - -## Smoke tests - -1. **DB health** - Open `/health/db` → expect `{"db":"ok","result":1}`. -2. **UI** - Generate a grid; paint using click-drag rows/columns; white erases. -3. **Generate + Save** - Click **Generate Pattern** → alert shows saved **ID**. -4. **Verify API** - Visit `/api/patterns/` → expect JSON with `id,name,instructions,created_at`. -5. **cURL insert** (optional) - - ```bash - curl -s -X POST http://localhost:3000/api/patterns \ - -H 'Content-Type: application/json' \ - -d '{"name":"Smoke Test","instructions":"Row 1: 10W"}' - ``` - -## Known limits (Sprint 2) - -* Instructions are **top-to-bottom RLE** (no RS/WS alternation yet). -* No auth; patterns are readable by ID. -* Only `{ id, name, instructions }` stored (grid/palette not persisted yet). - -## Next up (Sprint 3 candidates) - -* Alternate instruction style (RS/WS, bottom-up). -* Save/load full charts (store grid + palette). -* TXT/PDF export and shareable view. -* Auth + “My Patterns”; edit/delete. -* Clear/Undo/Redo; freehand mode; Shift = straight line. From 3338c33c4c59a0b1fba1e63436981d9337c1bfd6 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Wed, 15 Oct 2025 14:42:14 -0700 Subject: [PATCH 37/50] Added patterns.ejs and links between pages --- .DS_Store | Bin 8196 -> 8196 bytes app.js | 4 ++++ .../instructions.js => instructions.js | 0 public/.DS_Store | Bin 8196 -> 8196 bytes public/scripts/grid.js | 2 -- views/home.ejs | 1 + views/patterns.ejs | 13 +++++++++++++ 7 files changed, 18 insertions(+), 2 deletions(-) rename public/scripts/instructions.js => instructions.js (100%) create mode 100644 views/patterns.ejs diff --git a/.DS_Store b/.DS_Store index 1c4b21e9dc83adafe8328c640f8644fd6ff72b47..814014c7a28293bad284f0ded9230df257c56940 100644 GIT binary patch delta 112 zcmZp1XmOa}&nU1lU^hRbz-AtSBa9qoMwU7X#wMndJw@#|iwl)9DRMBBFr+dR14%s~ xS;&y%nUkNKl#`#tz`!8Dz`!_Za;?Y_xW3JvqJ2!8*(JWQOg<}8f-Kz41OQ_O9V!3- delta 84 zcmZp1XmOa}&&a res.render('home')); +app.get("/patterns", (req, res) => { + res.render("patterns"); // This looks for views/patterns.ejs +}); + app.get('/health/db', async (_req, res) => { try { const [rows] = await pool.query('SELECT 1 AS ok'); diff --git a/public/scripts/instructions.js b/instructions.js similarity index 100% rename from public/scripts/instructions.js rename to instructions.js diff --git a/public/.DS_Store b/public/.DS_Store index 530a8da48c98e1f48d861b55f1fdea3c93ee184a..201a2d8d1047dba5bd34128d3233a36a706601c8 100644 GIT binary patch delta 21 ccmZp1XmQxEQ<%fd$Wlka*u-@6L1AYe08H}+YybcN delta 15 WcmZp1XmQxEQ+V

Pixel-To-Pattern

+ Patterns Page
diff --git a/views/patterns.ejs b/views/patterns.ejs new file mode 100644 index 0000000..c9f78f4 --- /dev/null +++ b/views/patterns.ejs @@ -0,0 +1,13 @@ + + + + + + Patterns + + + +

Patterns

+ Home Page + + \ No newline at end of file From 5537899a76cf16cfd82e95bc5c978f2a271bba04 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Wed, 15 Oct 2025 14:46:12 -0700 Subject: [PATCH 38/50] finished merge conflicts --- views/home.ejs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/views/home.ejs b/views/home.ejs index 5355006..7f5783d 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -18,23 +18,10 @@

-
-    
-
-    
- - - - -

-
-    
-    
+ -
From d14961a36b91eee87175144edf691699fe7c8376 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Wed, 15 Oct 2025 14:52:59 -0700 Subject: [PATCH 39/50] updates patterns route to select from db --- app.js | 13 +++++++++++-- views/home.ejs | 5 ++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app.js b/app.js index 3c9c4e5..7e24ee4 100644 --- a/app.js +++ b/app.js @@ -14,10 +14,19 @@ app.set('view engine', 'ejs'); app.get('/', (req, res) => res.render('home')); -app.get("/patterns", (req, res) => { - res.render("patterns"); // This looks for views/patterns.ejs +app.get("/patterns", async (req, res) => { + try { + const [rows] = await pool.query( + "SELECT id, name, instructions, created_at FROM patterns ORDER BY id DESC" + ); + res.render("patterns", { patterns: rows }); + } catch (err) { + console.error(err); + res.status(500).send("Failed to load patterns"); + } }); + app.get('/health/db', async (_req, res) => { try { const [rows] = await pool.query('SELECT 1 AS ok'); diff --git a/views/home.ejs b/views/home.ejs index 7f5783d..31676a0 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -19,9 +19,8 @@

     
-      
-      
+  
+  
 
     
     

From 76e688733251d8911f7582510e35693a932f7fae Mon Sep 17 00:00:00 2001
From: Jameson Farmer 
Date: Wed, 15 Oct 2025 23:23:09 -0700
Subject: [PATCH 40/50] added rendering for completed patterns on pattern.js

---
 app.js             | 11 ++++++++---
 views/patterns.ejs | 31 ++++++++++++++++++++++++-------
 2 files changed, 32 insertions(+), 10 deletions(-)

diff --git a/app.js b/app.js
index 7e24ee4..75ba7cd 100644
--- a/app.js
+++ b/app.js
@@ -15,18 +15,23 @@ app.set('view engine', 'ejs');
 app.get('/', (req, res) => res.render('home'));
 
 app.get("/patterns", async (req, res) => {
+  let rows = [];
+  let error = null;
+
   try {
-    const [rows] = await pool.query(
+    const [result] = await pool.query(
       "SELECT id, name, instructions, created_at FROM patterns ORDER BY id DESC"
     );
-    res.render("patterns", { patterns: rows });
+    rows = result;
   } catch (err) {
     console.error(err);
-    res.status(500).send("Failed to load patterns");
   }
+
+  res.render("patterns", { patterns: rows, error });
 });
 
 
+
 app.get('/health/db', async (_req, res) => {
   try {
     const [rows] = await pool.query('SELECT 1 AS ok');
diff --git a/views/patterns.ejs b/views/patterns.ejs
index c9f78f4..3e0fe3b 100644
--- a/views/patterns.ejs
+++ b/views/patterns.ejs
@@ -1,13 +1,30 @@
 
 
 
-    
-    
-    Patterns
-    
+  
+  
+  Saved Patterns
+  
 
 
-    

Patterns

- Home Page +

Saved Crochet Patterns

+ Back to Home + + <% if (patterns && patterns.length) { %> +
    + <% patterns.forEach(p => { %> +
  • +

    <%= p.name %> (ID: <%= p.id %>)

    +

    + Created: + <%= p.created_at.toLocaleString ? p.created_at.toLocaleString() : p.created_at %> +

    +
    <%= p.instructions %>
    +
  • + <% }) %> +
+ <% } else { %> +

No patterns found yet. Try saving one from the home page.

+ <% } %> - \ No newline at end of file + From a47aa9c111c4712f698b370c0e14d613ed984015 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Wed, 15 Oct 2025 23:35:39 -0700 Subject: [PATCH 41/50] added patterns.css to style db results --- public/css/patterns.css | 76 +++++++++++++++++++++++++++++++++++++++++ views/patterns.ejs | 43 ++++++++++++----------- 2 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 public/css/patterns.css diff --git a/public/css/patterns.css b/public/css/patterns.css new file mode 100644 index 0000000..ba86c10 --- /dev/null +++ b/public/css/patterns.css @@ -0,0 +1,76 @@ +.page-patterns { + max-width: 800px; + margin: 0 auto; + padding: 1.5rem 1rem 3rem; +} + +.page-patterns h1 { + font-size: 2rem; + letter-spacing: 0.2px; + margin-bottom: 1rem; +} + +.page-patterns .back-link { + display: inline-block; + margin-bottom: 1.25rem; + font-weight: 500; + text-decoration: none; +} + +.page-patterns .back-link:hover { + text-decoration: underline; +} + +.pattern-list { + list-style: none; + display: grid; + gap: 1rem; + margin-top: 0.5rem; +} + +.pattern-item { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 10px; + padding: 1rem 1rem 0.75rem; + box-shadow: 0 1px 3px rgba(0,0,0,0.06); +} + +.pattern-item h2 { + font-size: 1.2rem; + margin: 0 0 0.25rem 0; +} + +.pattern-item h2 small { + font-weight: 500; + opacity: 0.7; +} + +.pattern-meta { + color: #6b7280; + font-size: 0.95rem; + margin-bottom: 0.75rem; +} + +.pattern-box { + background: #f9fafb; + border: 1px dashed #d1d5db; + border-radius: 8px; + padding: 0.75rem; + white-space: pre-wrap; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + line-height: 1.4; + color: #1f2937; +} + +.alert { + padding: 0.9rem 1rem; + border-radius: 8px; + margin-bottom: 1rem; +} + +.alert-error { + background: #fee2e2; + border: 1px solid #ef4444; + color: #991b1b; +} diff --git a/views/patterns.ejs b/views/patterns.ejs index 3e0fe3b..b66f1d3 100644 --- a/views/patterns.ejs +++ b/views/patterns.ejs @@ -1,30 +1,33 @@ - - - - Saved Patterns - - - -

Saved Crochet Patterns

- Back to Home - - <% if (patterns && patterns.length) { %> -
    - <% patterns.forEach(p => { %> + + + + Saved Patterns + + + + +

    Saved Crochet Patterns

    + Back to Home +
    + <% if (patterns && patterns.length) { %> +
      + <% patterns.forEach(p => { %>
    • <%= p.name %> (ID: <%= p.id %>)

      Created: - <%= p.created_at.toLocaleString ? p.created_at.toLocaleString() : p.created_at %> + <%= p.created_at.toLocaleString ? p.created_at.toLocaleString() : + p.created_at %>

      <%= p.instructions %>
    • - <% }) %> -
    - <% } else { %> -

    No patterns found yet. Try saving one from the home page.

    - <% } %> - + <% }) %> +
+ <% } else { %> +

No patterns found yet. Try saving one from the home page.

+ <% } %> +
+ From b9a4e0fa66051b829d22c6827c26649eab9e2c0f Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 00:09:01 -0700 Subject: [PATCH 42/50] styling changes --- public/css/style.css | 49 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index a53e3d9..3d6df03 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -34,17 +34,16 @@ h1 { .grid { display: grid; gap: 2px; - margin-top: 10px; + margin: 16px 0 20px 0; /* added spacing above and below the grid */ } .controls { - margin-bottom: 15px; + margin-bottom: 16px; display: flex; gap: 10px; align-items: center; } - .cell { width: 30px; height: 30px; @@ -53,3 +52,47 @@ h1 { cursor: pointer; transition: background 0.1s; } + +a { + display: inline-block; + padding: 0.5rem 1rem; + background-color: gray; + color: white; + border-radius: 6px; + text-decoration: none; + font-weight: 500; + transition: background-color 0.2s ease, transform 0.1s ease; + margin-bottom: 16px; /* space below the link */ +} + +a:hover { + background-color: darkgray; + transform: translateY(-1px); +} + +a:active { + transform: translateY(0); +} + +a:visited { + color: white; +} + +/* spacing for generate pattern and save section */ +#pattern { + margin: 12px 0 16px 0; /* gap between grid and output */ +} + +#pattern-output { + margin-top: 12px; + margin-bottom: 16px; /* gap before save section */ +} + +#pattern-name { + margin-top: 8px; + margin-bottom: 8px; +} + +#save { + margin-top: 8px; +} From f67085461003dfd8cef507f802649433a369224b Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 00:10:17 -0700 Subject: [PATCH 43/50] cleaned app.js --- app.js | 58 ---------------------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/app.js b/app.js index 75ba7cd..b717af6 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,6 @@ const HOST = process.env.HOST || 'localhost'; const app = express(); -// allow JSON bodies for API routes app.use(express.json({ limit: '1mb'})); app.use(express.urlencoded({ extended: false })); app.use(express.static("public")); @@ -30,8 +29,6 @@ app.get("/patterns", async (req, res) => { res.render("patterns", { patterns: rows, error }); }); - - app.get('/health/db', async (_req, res) => { try { const [rows] = await pool.query('SELECT 1 AS ok'); @@ -47,61 +44,6 @@ app.get('/ping', async (_req, res) => { res.json(rows); }); -// begin crochet pattern fxn -// --- patterns API: ONLY id, name, instructions --- -// create -app.post('/api/patterns', async (req, res) => { - try { - const { name, instructions } = req.body; - if (typeof name !== 'string' || !name.trim() || - typeof instructions !== 'string' || !instructions.trim()) { - return res.status(400).json({ error: 'name and instructions are required strings' }); - } - - const [result] = await pool.query( - 'INSERT INTO patterns (name, instructions) VALUES (?, ?)', - [name.trim(), instructions] - ); - res.status(201).json({ id: result.insertId, name: name.trim() }); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'insert failed', message: err.message }); - } -}); - -// read (single) -app.get('/api/patterns/:id', async (req, res) => { - try { - const pid = Number(req.params.id); - if (!Number.isInteger(pid) || pid <= 0) { - return res.status(400).json({ error: 'invalid id' }); - } - const [rows] = await pool.query( - 'SELECT id, name, instructions FROM patterns WHERE id = ?', - [pid] - ); - if (!rows.length) return res.status(404).json({ error: 'not found' }); - res.json(rows[0]); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'fetch failed', message: err.message }); - } -}); - -// optional: list recent (handy while developing) -app.get('/api/patterns', async (_req, res) => { - try { - const [rows] = await pool.query( - 'SELECT id, name FROM patterns ORDER BY id DESC LIMIT 50' - ); - res.json(rows); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'list failed', message: err.message }); - } -}); -// end crochet instructions fxn - app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); }); From 06cf25e7b8f11df00582999e9c39468c91bf63f7 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 00:12:59 -0700 Subject: [PATCH 44/50] fixed app.js --- app.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/app.js b/app.js index b717af6..c7bf3a2 100644 --- a/app.js +++ b/app.js @@ -44,6 +44,57 @@ app.get('/ping', async (_req, res) => { res.json(rows); }); +app.post('/api/patterns', async (req, res) => { + try { + const { name, instructions } = req.body; + if (typeof name !== 'string' || !name.trim() || + typeof instructions !== 'string' || !instructions.trim()) { + return res.status(400).json({ error: 'name and instructions are required strings' }); + } + + const [result] = await pool.query( + 'INSERT INTO patterns (name, instructions) VALUES (?, ?)', + [name.trim(), instructions] + ); + res.status(201).json({ id: result.insertId, name: name.trim() }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'insert failed', message: err.message }); + } +}); + +// read (single) +app.get('/api/patterns/:id', async (req, res) => { + try { + const pid = Number(req.params.id); + if (!Number.isInteger(pid) || pid <= 0) { + return res.status(400).json({ error: 'invalid id' }); + } + const [rows] = await pool.query( + 'SELECT id, name, instructions FROM patterns WHERE id = ?', + [pid] + ); + if (!rows.length) return res.status(404).json({ error: 'not found' }); + res.json(rows[0]); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'fetch failed', message: err.message }); + } +}); + +// optional: list recent (handy while developing) +app.get('/api/patterns', async (_req, res) => { + try { + const [rows] = await pool.query( + 'SELECT id, name FROM patterns ORDER BY id DESC LIMIT 50' + ); + res.json(rows); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'list failed', message: err.message }); + } +}); + app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); }); From 10b003b5caa4534fa101997618088b21644f2692 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 00:25:29 -0700 Subject: [PATCH 45/50] app.js rework --- app.js | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/app.js b/app.js index c7bf3a2..b4092b0 100644 --- a/app.js +++ b/app.js @@ -63,38 +63,6 @@ app.post('/api/patterns', async (req, res) => { } }); -// read (single) -app.get('/api/patterns/:id', async (req, res) => { - try { - const pid = Number(req.params.id); - if (!Number.isInteger(pid) || pid <= 0) { - return res.status(400).json({ error: 'invalid id' }); - } - const [rows] = await pool.query( - 'SELECT id, name, instructions FROM patterns WHERE id = ?', - [pid] - ); - if (!rows.length) return res.status(404).json({ error: 'not found' }); - res.json(rows[0]); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'fetch failed', message: err.message }); - } -}); - -// optional: list recent (handy while developing) -app.get('/api/patterns', async (_req, res) => { - try { - const [rows] = await pool.query( - 'SELECT id, name FROM patterns ORDER BY id DESC LIMIT 50' - ); - res.json(rows); - } catch (err) { - console.error(err); - res.status(500).json({ error: 'list failed', message: err.message }); - } -}); - app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); }); From 2b3db4a5085be5aac8e1768652789e86f84a7405 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 00:42:14 -0700 Subject: [PATCH 46/50] reworked post route for better understanding --- app.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app.js b/app.js index b4092b0..429c567 100644 --- a/app.js +++ b/app.js @@ -45,24 +45,27 @@ app.get('/ping', async (_req, res) => { }); app.post('/api/patterns', async (req, res) => { - try { - const { name, instructions } = req.body; - if (typeof name !== 'string' || !name.trim() || - typeof instructions !== 'string' || !instructions.trim()) { - return res.status(400).json({ error: 'name and instructions are required strings' }); - } + const name = req.body.name ? req.body.name.trim() : ''; + const instructions = req.body.instructions || ''; + + if (!name) { + return res.status(400).json({ error: 'name is required' }); + } + try { const [result] = await pool.query( 'INSERT INTO patterns (name, instructions) VALUES (?, ?)', - [name.trim(), instructions] + [name, instructions] ); - res.status(201).json({ id: result.insertId, name: name.trim() }); + + res.status(201).json({ id: result.insertId, name }); } catch (err) { console.error(err); - res.status(500).json({ error: 'insert failed', message: err.message }); + res.status(500).json({ error: 'insert failed' }); } }); + app.listen(PORT, HOST, () => { console.log(`Running on http://${HOST}:${PORT}`); }); From a983d9b041b8fff660a3e1f7c5abb44aa57009c4 Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 01:01:41 -0700 Subject: [PATCH 47/50] pattern.js simplified --- public/scripts/pattern.js | 200 ++++++++------------------------------ 1 file changed, 42 insertions(+), 158 deletions(-) diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js index b81002e..90f64d6 100644 --- a/public/scripts/pattern.js +++ b/public/scripts/pattern.js @@ -1,195 +1,79 @@ -// public/scripts/pattern.js -// Generate + render pattern text, require a name, then POST to /api/patterns - -// --- Palette mapping (your codes) --- -const HEX_TO_CODE = { - "#ffffff": "W", // white - "#000000": "K", // black - "#ef4444": "R", // red - "#22c55e": "G", // green - "#3b82f6": "B", // blue - "#f59e0b": "Y", // yellow - "#8b5cf6": "P", // purple -}; - -// --- Color helpers --- -function rgbToHex(rgb) { - if (!rgb) return null; - const m = rgb.match(/\d+/g); - if (!m || m.length < 3) return null; - const [r, g, b] = m.map(Number); - const toHex = (n) => n.toString(16).padStart(2, "0"); - return `#${toHex(r)}${toHex(g)}${toHex(b)}`; -} -function normalizeHex(hexOrRgb) { - if (!hexOrRgb) return null; - const s = hexOrRgb.trim().toLowerCase(); - if (s.startsWith("#") && s.length === 7) return s; - if (s.startsWith("rgb")) return rgbToHex(s); - return null; -} -function codeFromColor(colorStr) { - const hex = normalizeHex(colorStr) || "#ffffff"; - return HEX_TO_CODE[hex] || "W"; -} - -// --- Grid reading --- -function getColCount(gridEl) { - // Prefer data-cols set by grid.js - const ds = Number(gridEl.dataset?.cols); - if (Number.isInteger(ds) && ds > 0) return ds; - - // Fallback: CSS grid template - const styleCols = getComputedStyle(gridEl) - .gridTemplateColumns.trim() - .split(/\s+/) - .filter(Boolean).length; - if (styleCols > 0) return styleCols; - - // Last resort: input field - const colsEl = document.getElementById("cols"); - return parseInt(colsEl?.value, 10) || 10; -} - -function exportCodeGridFromDOM() { - const gridEl = document.getElementById("grid"); - if (!gridEl) { - console.error("Grid element #grid not found."); - return []; - } - const cols = getColCount(gridEl); - const cellEls = Array.from(gridEl.querySelectorAll(".cell")); - if (cellEls.length === 0) return []; - - const rows = []; - for (let i = 0; i < cellEls.length; i += cols) { - const rowCells = cellEls.slice(i, i + cols); - const rowCodes = rowCells.map((cell) => { - const dataColor = cell.dataset?.color; - const styleColor = getComputedStyle(cell).backgroundColor; - return codeFromColor(dataColor || styleColor); - }); - rows.push(rowCodes); - } - return rows; -} - -// --- RLE + text building (your format) --- function rleRow(codes) { - if (codes.length === 0) return ""; + if (!codes.length) return ""; const out = []; - let curr = codes[0], - count = 1; + let curr = codes[0], count = 1; for (let i = 1; i < codes.length; i++) { if (codes[i] === curr) count++; - else { - out.push(`${count}${curr}`); - curr = codes[i]; - count = 1; - } + else { out.push(`${count}${curr}`); curr = codes[i]; count = 1; } } out.push(`${count}${curr}`); return out.join(" "); } + function buildPatternText(codeGrid) { return codeGrid.map((row, i) => `Row ${i + 1}: ${rleRow(row)}`).join("\n"); } -// --- UI state --- -let lastPatternText = ""; // what we rendered (and will POST) +let lastPatternText = ""; -function setSaveEnabled(enabled) { - const saveBtn = document.getElementById("savePattern"); - if (saveBtn) saveBtn.disabled = !enabled; -} - -// --- Render preview (Jameson’s behavior) --- function renderPattern() { const outEl = document.getElementById("pattern-output"); - const codeGrid = exportCodeGridFromDOM(); + if (!outEl) return; - if (!outEl) { - console.warn("Missing #pattern-output container."); - return; - } - if (!codeGrid.length) { - outEl.textContent = - "No grid found. Click 'Generate Grid' and color some cells first."; + const grid = window.cells; + if (!Array.isArray(grid) || grid.length === 0) { + outEl.textContent = "No grid found. Click 'Generate Grid' and color some cells first."; lastPatternText = ""; - setSaveEnabled(false); + updateSaveEnabled(); return; } - const patternText = buildPatternText(codeGrid); - lastPatternText = patternText; - - outEl.textContent = patternText; - outEl.setAttribute("aria-live", "polite"); - outEl.scrollIntoView({ behavior: "smooth", block: "nearest" }); - - // Dev logs - console.log("=== Crochet Pattern (2D Codes) ==="); - console.log(codeGrid); - console.log("=== Crochet Pattern (RLE by Row) ==="); - console.log(patternText); - - // Enable save only if name is present - const name = (document.getElementById("patternName")?.value || "").trim(); - setSaveEnabled(Boolean(name)); + const codeGrid = grid.map(row => row.map(cell => cell.dataset.code || "W")); + lastPatternText = buildPatternText(codeGrid); + outEl.textContent = lastPatternText; + updateSaveEnabled(); } -// --- POST helper --- -async function savePatternToDB(name, instructions) { +async function savePattern(name, instructions) { const res = await fetch("/api/patterns", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name, instructions }), + body: JSON.stringify({ name, instructions }) }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || res.statusText); return data.id; } -// --- Save click (requires label/name + rendered pattern) --- -async function handleSaveClick() { - const nameInput = document.getElementById("patternName"); - const name = (nameInput?.value || "").trim(); - - if (!lastPatternText) { - // No preview yet—generate it first - renderPattern(); - if (!lastPatternText) return; // still nothing, bail - } - if (!name) { - alert("Please enter a pattern name before saving."); - nameInput?.focus(); - return; - } - - try { - const id = await savePatternToDB(name, lastPatternText); - alert(`Saved! Pattern ID: ${id}`); - } catch (err) { - console.error("Save failed:", err); - alert(`Save failed: ${err.message}`); - } +function updateSaveEnabled() { + const saveBtn = document.getElementById("save"); + const nameInput = document.getElementById("pattern-name"); + const hasName = Boolean(nameInput?.value.trim()); + const hasPattern = Boolean(lastPatternText); + saveBtn.disabled = !(hasName && hasPattern); } -// --- Wire up --- document.addEventListener("DOMContentLoaded", () => { - const patternBtn = document.getElementById("pattern"); - const saveBtn = document.getElementById("savePattern"); - const nameInput = document.getElementById("patternName"); - - if (patternBtn) patternBtn.addEventListener("click", renderPattern); - if (saveBtn) saveBtn.addEventListener("click", handleSaveClick); - - // Live-enable Save only when both name + preview exist - nameInput?.addEventListener("input", () => { - const hasName = Boolean(nameInput.value.trim()); - setSaveEnabled(hasName && Boolean(lastPatternText)); + const genBtn = document.getElementById("pattern"); + const saveBtn = document.getElementById("save"); + const nameInput = document.getElementById("pattern-name"); + + genBtn?.addEventListener("click", renderPattern); + nameInput?.addEventListener("input", updateSaveEnabled); + + saveBtn?.addEventListener("click", async () => { + const name = (nameInput?.value || "").trim(); + if (!name) return alert("Please enter a name before saving."); + if (!lastPatternText) return alert("Please generate a pattern first."); + + try { + const id = await savePattern(name, lastPatternText); + alert(`Saved! Pattern ID: ${id}`); + } catch (err) { + console.error(err); + alert(`Save failed: ${err.message}`); + } }); - // Start disabled - setSaveEnabled(false); + updateSaveEnabled(); // start disabled }); From 55f972cb75bd5c7334fe3a3c4e2059b9a64f519c Mon Sep 17 00:00:00 2001 From: Jameson Farmer Date: Thu, 16 Oct 2025 01:04:40 -0700 Subject: [PATCH 48/50] edited grid.js and home to work better with pattern.js --- public/scripts/grid.js | 59 ++++++++++++++++++++++++++---------------- views/home.ejs | 24 +++++++++++------ 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/public/scripts/grid.js b/public/scripts/grid.js index a739758..91d3bbe 100644 --- a/public/scripts/grid.js +++ b/public/scripts/grid.js @@ -1,22 +1,32 @@ const PALETTE = [ - { id: "white", hex: "#ffffff" }, - { id: "black", hex: "#000000" }, - { id: "red", hex: "#ef4444" }, - { id: "green", hex: "#22c55e" }, - { id: "blue", hex: "#3b82f6" }, + { id: "white", hex: "#ffffff" }, + { id: "black", hex: "#000000" }, + { id: "red", hex: "#ef4444" }, + { id: "green", hex: "#22c55e" }, + { id: "blue", hex: "#3b82f6" }, { id: "yellow", hex: "#f59e0b" }, { id: "purple", hex: "#8b5cf6" }, ]; -let activeColor = PALETTE[1]; // default: black + +const HEX_TO_CODE = { + "#ffffff": "W", + "#000000": "K", + "#ef4444": "R", + "#22c55e": "G", + "#3b82f6": "B", + "#f59e0b": "Y", + "#8b5cf6": "P", +}; + +let activeColor = PALETTE[1]; const paletteEl = document.getElementById("palette"); -const gridEl = document.getElementById("grid"); +const gridEl = document.getElementById("grid"); const rowsInput = document.getElementById("rows"); const colsInput = document.getElementById("cols"); const generateBtn = document.getElementById("generate"); -// --- Palette --- PALETTE.forEach((color, i) => { const sw = document.createElement("div"); sw.className = "swatch" + (i === 1 ? " active" : ""); @@ -24,25 +34,24 @@ PALETTE.forEach((color, i) => { sw.title = color.id; sw.addEventListener("click", () => { activeColor = color; - document - .querySelectorAll(".swatch") - .forEach((s) => s.classList.remove("active")); + document.querySelectorAll(".swatch").forEach(s => s.classList.remove("active")); sw.classList.add("active"); }); paletteEl.appendChild(sw); }); -// --- Grid + paint state --- -let cells = []; // 2D array: cells[row][col] -> HTMLElement +let cells = []; +// Drag/paint state let isMouseDown = false; let startRow = null; let startCol = null; -let axis = null; // 'row' | 'col' | null +let axis = null; // "row" | "col" | null function setCellColor(r, c, hex) { const cell = cells[r][c]; cell.dataset.color = hex; + cell.dataset.code = HEX_TO_CODE[hex] || "W"; // <- single source of truth for yarn code cell.style.background = hex; } @@ -69,22 +78,22 @@ function handleEnter(cellEl) { if (axis == null) { if (r === startRow && c !== startCol) axis = "row"; else if (c === startCol && r !== startRow) axis = "col"; - else return; // still over the start cell + else return; // still on the start cell } - // Ignore diagonals / switching axis mid-drag - if ((axis === "row" && r !== startRow) || (axis === "col" && c !== startCol)) - return; + // Ignore diagonals or switching axis mid-drag + if ((axis === "row" && r !== startRow) || (axis === "col" && c !== startCol)) return; paintSegmentTo(r, c); } -// --- Build (resizable) grid --- +// Build (and replace) the grid to a given size function buildGrid(rows, cols) { - // Reset container & state gridEl.innerHTML = ""; gridEl.style.gridTemplateColumns = `repeat(${cols}, 30px)`; - gridEl.style.gridTemplateRows = `repeat(${rows}, 30px)`; + gridEl.style.gridTemplateRows = `repeat(${rows}, 30px)`; + gridEl.dataset.rows = String(rows); + gridEl.dataset.cols = String(cols); cells = []; for (let r = 0; r < rows; r++) { @@ -95,12 +104,13 @@ function buildGrid(rows, cols) { cell.dataset.row = String(r); cell.dataset.col = String(c); cell.dataset.color = "#ffffff"; + cell.dataset.code = "W"; cell.style.background = "#ffffff"; cell.draggable = false; // Start stroke, paint starting cell immediately cell.addEventListener("mousedown", (e) => { - e.preventDefault(); // prevent text selection/drag ghost + e.preventDefault(); // avoid text selection / drag ghost isMouseDown = true; startRow = r; startCol = c; @@ -111,7 +121,7 @@ function buildGrid(rows, cols) { // Extend stroke when moving into cells cell.addEventListener("mouseenter", () => handleEnter(cell)); - // Optional: prevent native drag image + // Prevent native drag image cell.addEventListener("dragstart", (e) => e.preventDefault()); rowArr.push(cell); @@ -139,3 +149,6 @@ if (generateBtn) { buildGrid(rows, cols); }); } + +// Expose cells for pattern.js (if needed globally) +window.cells = cells; diff --git a/views/home.ejs b/views/home.ejs index 31676a0..6963d2f 100644 --- a/views/home.ejs +++ b/views/home.ejs @@ -8,22 +8,30 @@

Pixel-To-Pattern

+ + Patterns Page +
- +
+
- + + +

-    
-  
-  
 
-    
-    
+  
+  
+
+  
 
-  
+  
+  
+  
+
 

From 236cc4f30033090b20582016187e545c6e62ac8f Mon Sep 17 00:00:00 2001
From: Jameson Farmer 
Date: Thu, 16 Oct 2025 09:07:23 -0700
Subject: [PATCH 49/50] changed addEventListeners

---
 public/scripts/pattern.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/public/scripts/pattern.js b/public/scripts/pattern.js
index 90f64d6..89aafe0 100644
--- a/public/scripts/pattern.js
+++ b/public/scripts/pattern.js
@@ -58,10 +58,10 @@ document.addEventListener("DOMContentLoaded", () => {
   const saveBtn = document.getElementById("save");
   const nameInput = document.getElementById("pattern-name");
 
-  genBtn?.addEventListener("click", renderPattern);
-  nameInput?.addEventListener("input", updateSaveEnabled);
+  genBtn.addEventListener("click", renderPattern);
+  nameInput.addEventListener("input", updateSaveEnabled);
 
-  saveBtn?.addEventListener("click", async () => {
+  saveBtn.addEventListener("click", async () => {
     const name = (nameInput?.value || "").trim();
     if (!name) return alert("Please enter a name before saving.");
     if (!lastPatternText) return alert("Please generate a pattern first.");

From 73baf7cf5be0f2f8f405312ca0258868d6182283 Mon Sep 17 00:00:00 2001
From: Jameson Farmer 
Date: Mon, 20 Oct 2025 10:01:32 -0700
Subject: [PATCH 50/50] removed instrustions.js

---
 instructions.js | 124 ------------------------------------------------
 1 file changed, 124 deletions(-)
 delete mode 100644 instructions.js

diff --git a/instructions.js b/instructions.js
deleted file mode 100644
index 597d269..0000000
--- a/instructions.js
+++ /dev/null
@@ -1,124 +0,0 @@
-// public/scripts/instructions.js
-
-// --- DOM refs ---
-const gridEl = document.getElementById('grid');
-const nameInput = document.getElementById('patternName');
-const saveBtn = document.getElementById('savePattern');
-
-// --- utils ---
-function rgbToHex(rgb) {
-  const m = /^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/.exec(rgb);
-  if (!m) return rgb; // already hex or unknown format
-  const h = n => Number(n).toString(16).padStart(2, '0');
-  return `#${h(m[1])}${h(m[2])}${h(m[3])}`;
-}
-
-function ensureGridSizeDataAttrs() {
-  // Prefer what grid.js sets; if missing, derive from CSS grid columns
-  if (!gridEl.dataset.rows || !gridEl.dataset.cols) {
-    const total = gridEl.children.length;
-    const cols = getComputedStyle(gridEl).gridTemplateColumns.split(' ').length || Math.sqrt(total) | 0;
-    const rows = cols ? Math.ceil(total / cols) : 0;
-    gridEl.dataset.rows = String(rows);
-    gridEl.dataset.cols = String(cols);
-  }
-}
-
-function readGrid() {
-  ensureGridSizeDataAttrs();
-  const rows = Number(gridEl.dataset.rows);
-  const cols = Number(gridEl.dataset.cols);
-  const out = [];
-
-  const cells = Array.from(gridEl.children);
-  for (let r = 0; r < rows; r++) {
-    const row = [];
-    for (let c = 0; c < cols; c++) {
-      const cell = cells[r * cols + c];
-      // Prefer stored color from grid.js; fallback to computed style
-      const hex = (cell.dataset.color || rgbToHex(getComputedStyle(cell).backgroundColor)).toLowerCase();
-      row.push(hex);
-    }
-    out.push(row);
-  }
-  return { rows, cols, grid: out };
-}
-
-// --- crochet instruction generator (flat piece, one pixel = 1 single crochet) ---
-const COLOR_ABBR = {
-  white: 'WH', black: 'BK', red: 'RD', green: 'GR',
-  blue: 'BL', yellow: 'YL', purple: 'PU'
-};
-
-function hexToIdMap() {
-  // Uses PALETTE defined in grid.js; fallback if not found
-  const m = {};
-  if (typeof PALETTE !== 'undefined' && Array.isArray(PALETTE)) {
-    for (const c of PALETTE) m[c.hex.toLowerCase()] = c.id;
-  }
-  return m;
-}
-
-function abbrFor(idOrHex, hex2id) {
-  const id = (idOrHex?.startsWith('#')) ? (hex2id[idOrHex] || idOrHex) : idOrHex;
-  return COLOR_ABBR[id] || (typeof id === 'string' ? id.slice(0, 2).toUpperCase() : '??');
-}
-
-function rle(arr) {
-  const res = [];
-  let prev = arr[0], count = 1;
-  for (let i = 1; i < arr.length; i++) {
-    if (arr[i] === prev) count++;
-    else { res.push([count, prev]); prev = arr[i]; count = 1; }
-  }
-  if (arr.length) res.push([count, prev]);
-  return res;
-}
-
-function gridToCrochetInstructions(grid) {
-  const rows = grid.length;
-  const cols = rows ? grid[0].length : 0;
-  const hex2id = hexToIdMap();
-
-  const lines = [];
-  lines.push(`Pattern: ${rows} rows × ${cols} sts (single crochet, one stitch per pixel)`);
-  lines.push(`Read flat, bottom to top. Row 1 (RS) goes right→left across chart; Row 2 (WS) left→right; alternate.`);
-  lines.push('');
-
-  for (let srcR = rows - 1, printed = 1; srcR >= 0; srcR--, printed++) {
-    const isRS = (printed % 2 === 1);
-    const row = grid[srcR];
-    const reading = isRS ? row.slice().reverse() : row;
-
-    const segs = rle(reading)
-      .map(([n, color]) => `sc ${n} ${abbrFor(color, hex2id)}`)
-      .join(', ');
-
-    const arrow = isRS ? 'RS →' : 'WS ←';
-    lines.push(`Row ${printed} (${arrow}): ${segs}`);
-    if (printed < rows) lines.push('ch 1, turn;');
-  }
-  return lines.join('\n');
-}
-
-// --- save handler ---
-saveBtn?.addEventListener('click', async () => {
-  try {
-    const { grid } = readGrid();
-    const instructions = gridToCrochetInstructions(grid);
-    const name = (nameInput?.value?.trim()) || 'Untitled';
-
-    const res = await fetch('/api/patterns', {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({ name, instructions })
-    });
-    const data = await res.json();
-    if (!res.ok) return alert(`Save failed: ${data.error || res.statusText}`);
-
-    alert(`Saved! Pattern ID: ${data.id}`);
-  } catch (e) {
-    console.error(e);
-    alert('Unexpected error while saving.');
-  }
-});