diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..43056e9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,84 @@ +module.exports = { + env: { + browser: true, + es2021: true, + es6: true, + }, + extends: [ + "plugin:react/recommended", + "prettier", + "plugin:prettier/recommended", + "plugin:react-hooks/recommended", + "plugin:import/recommended", + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: ["react", "prettier", "react-hooks", "import"], + rules: { + "react/prop-types": "off", + "react/no-unescaped-entities": "warn", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "no-unused-vars": "warn", + "func-names": "warn", + "object-shorthand": "off", + "class-methods-use-this": "off", + "consistent-return": "off", + "import/no-dynamic-require": "off", + "global-require": "off", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/mouse-events-have-key-events": "off", + "max-classes-per-file": "off", + "no-nested-ternary": "off", + "no-new": "off", + "no-plusplus": "off", + "no-process-exit": "off", + "no-restricted-globals": "off", + "node/exports-style": "off", + "node/no-unpublished-import": "off", + "node/no-unsupported-features/es-syntax": "off", + "no-console": ["warn", {allow: ["warn", "debug", "error", "info"]}], + "no-unused-expressions": [ + "error", + { + allowShortCircuit: true, + allowTernary: true, + }, + ], + "react/no-array-index-key": "warn", + "no-shadow": "off", + "linebreak-style": ["error", "unix"], + "prettier/prettier": [ + "error", + { + singleQuote: false, + parser: "flow", + bracketSpacing: false, + jsxBracketSameLine: false, + printWidth: 80, + semi: true, + tabWidth: 2, + trailingComma: "es5", + useTabs: false, + proseWrap: "always", + jsxSingleQuote: true, + importOrder: ["^[./]"], + importOrderSeparation: true, + importOrderSortSpecifiers: true, + }, + ], + }, + settings: { + "import/resolver": { + node: { + extensions: [".js", ".json"], + }, + }, + "import/ignore": ["node_modules", ".json$", ".(scss|css)$"], + }, +}; diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..97ce882 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "files.autoSave": "onFocusChange", + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + "editor.formatOnPaste": true, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "eslint.format.enable": true, + "[javascript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + }, + "[javascriptreact]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint", + }, +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 00274de..0de69a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3070,6 +3070,11 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, "array.prototype.flat": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", @@ -3100,6 +3105,11 @@ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=" }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -3664,6 +3674,48 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -4484,6 +4536,11 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4502,6 +4559,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.92.tgz", "integrity": "sha512-YAVbvQIcDE/IJ/vzDMjD484/hsRbFPW2qXJPaYTfOhtligmfYEYOep+5QojpaEU9kq6bMvNeC2aG7arYvTHYsA==" }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -4777,6 +4839,43 @@ } } }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true + }, "eslint-config-react-app": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.0.tgz", @@ -4879,6 +4978,16 @@ } } }, + "eslint-plugin-eslint-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-4.1.0.tgz", + "integrity": "sha512-QJVw+WYXJuG2469gx5G929bz7crfxySDlK1i569FkuT6dpeHDeP7MmDrKaswCx17snG25LRFD6wmVX+AO5x7Qg==", + "dev": true, + "requires": { + "eslint-utils": "^3.0.0", + "estraverse": "^5.2.0" + } + }, "eslint-plugin-flowtype": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", @@ -4958,6 +5067,15 @@ "minimatch": "^3.0.4" } }, + "eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-plugin-react": { "version": "7.29.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", @@ -5217,6 +5335,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -5298,6 +5422,21 @@ "minimatch": "^3.0.4" } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=" + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -5586,6 +5725,70 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", + "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + } + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5906,6 +6109,11 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" }, + "husky": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", + "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5942,6 +6150,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.12.tgz", "integrity": "sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==" }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==" + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -7731,6 +7944,101 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "lint-staged": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.3.7.tgz", + "integrity": "sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ==", + "requires": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.16", + "commander": "^8.3.0", + "debug": "^4.3.3", + "execa": "^5.1.1", + "lilconfig": "2.0.4", + "listr2": "^4.0.1", + "micromatch": "^4.0.4", + "normalize-path": "^3.0.0", + "object-inspect": "^1.12.0", + "pidtree": "^0.5.0", + "string-argv": "^0.3.1", + "supports-color": "^9.2.1", + "yaml": "^1.10.2" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, + "lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==" + }, + "supports-color": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz", + "integrity": "sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==" + } + } + }, + "listr2": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", + "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.5", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + } + } + }, "loader-runner": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", @@ -7784,6 +8092,60 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8393,6 +8755,29 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "pidtree": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz", + "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -9173,6 +9558,21 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "prettier": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz", + "integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -9789,6 +10189,15 @@ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==" }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -9799,6 +10208,11 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -9867,6 +10281,21 @@ "queue-microtask": "^1.2.2" } }, + "rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -9882,6 +10311,16 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" }, + "sass": { + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, "sass-loader": { "version": "12.6.0", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", @@ -10103,6 +10542,27 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz", + "integrity": "sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==" + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==" + } + } + }, "sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -10238,6 +10698,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==" + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10356,6 +10821,14 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -10609,6 +11082,11 @@ "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==" }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, "thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", @@ -10667,6 +11145,14 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", diff --git a/package.json b/package.json index 8f99129..3a7a535 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,23 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "gh-pages": "^3.2.3", + "husky": "^7.0.4", + "lint-staged": "^12.3.7", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0", + "sass": "^1.49.9", "web-vitals": "^2.1.4" }, + "lint-staged": { + "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] + }, "scripts": { + "predeploy": "npm run build", + "deploy": "gh-pages -d build", "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", @@ -34,5 +45,23 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "homepage": "https://ameygohil.github.io/slither-link", + "devDependencies": { + "eslint": "^8.11.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-plugin": "^4.1.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.3.0", + "prettier": "^2.6.0" } } diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..643ac30 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index aa069f2..05a8ed4 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ - React App + SlitherLink diff --git a/public/logo192.png b/public/logo192.png index fc44b0a..c175075 100644 Binary files a/public/logo192.png and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png index a4e47a6..3132b31 100644 Binary files a/public/logo512.png and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..b063c6e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "React App", - "name": "Create React App Sample", + "short_name": "SlitherLink", + "name": "SlitherLink React App", "icons": [ { "src": "favicon.ico", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index 3784575..a766fa1 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,35 @@ -import logo from './logo.svg'; -import './App.css'; +import * as React from "react"; -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+import styles from "./App.styles.module.scss"; +import CheckSolution from "./components/CheckSolution"; +import Grid from "./components/Grid"; +import Info from "./components/Info"; +import {examples} from "./examples"; + +const App = () => { + const [matrix, setMatirx] = React.useState([]); + const [{dim, numbers, solution}] = React.useState( + examples[Math.floor(Math.random() * examples.length)] ); -} + const [check, setCheck] = React.useState("none"); + + return ( +
+

+ Slither + Link +

+ + + +
+ ); +}; export default App; diff --git a/src/App.styles.module.scss b/src/App.styles.module.scss new file mode 100644 index 0000000..802b7e5 --- /dev/null +++ b/src/App.styles.module.scss @@ -0,0 +1,32 @@ +main { + font-size: 24px; + font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #23262f; + height: 100%; + width: 100%; + color: #fafafa; + display: flex; + align-items: center; + justify-content: center; + gap: 20px; + flex-direction: column; + box-sizing: border-box; + padding: 0 22px; +} + +.heading { + font-size: 25px; + text-align: center; + + span { + color: #7986cb; + + &:nth-of-type(even) { + color: #8c9eff; + } + + &:nth-of-type(odd) { + color: #64b5f6; + } + } +} diff --git a/src/App.test.js b/src/App.test.js index 1f03afe..ed977f8 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,7 +1,9 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; +import * as React from "react"; -test('renders learn react link', () => { +import {render, screen} from "@testing-library/react"; +import App from "./App"; + +test("renders learn react link", () => { render(); const linkElement = screen.getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); diff --git a/src/components/AddPuzzleScreen/index.js b/src/components/AddPuzzleScreen/index.js new file mode 100644 index 0000000..bea4d52 --- /dev/null +++ b/src/components/AddPuzzleScreen/index.js @@ -0,0 +1,116 @@ +import * as React from "react"; +import {getNumbers, multiStyles} from "../../utils"; +import Grid from "../Grid"; +import N from "../Grid/components/N"; + +import styles from "./styles.module.scss"; + +const AddPuzzleScreen = ({open, onClose}) => { + const [dim, setDim] = React.useState([3, 3]); + const [gridMat, setGridMat] = React.useState([]); + + const [gridNumbers, setGridNumbers] = React.useState([]); + React.useEffect(() => { + setGridNumbers(getNumbers(gridMat)); + }, [JSON.stringify(gridMat)]); + + const [gridReset, setGridReset] = React.useState(false); + React.useEffect(() => { + if (gridReset) { + setGridReset(false); + } + }, [gridReset]); + + const [submitState, setSubmitState] = React.useState("none"); + const [submitText, setSubmitText] = React.useState("Submit"); + const onSubmit = () => { + setSubmitState("passed"); + setSubmitText("Thanks for submitting! 😀"); + }; + React.useEffect(() => { + let timeout; + if (submitState === "passed") { + timeout = setTimeout(() => { + setSubmitState("none"); + setSubmitText("Submit"); + }, 5000); + } + + return () => !isNaN(timeout) && clearTimeout(timeout); + }, [submitState]); + + const shouldShowGrid = + !isNaN(dim[0]) && dim[0] > 0 && !isNaN(dim[1]) && dim[1] > 0 && !gridReset; + + return ( + <> + {open && ( +
+ + +
+
Set dimensions
+
+
+ { + setGridReset(true); + setDim([n, dim[1]]); + }} + editorMode + className={styles.input} + /> + X + { + setGridReset(true); + setDim([dim[0], n]); + }} + editorMode + className={styles.input} + /> +
+
+
+
+
Set matrix
+
+
+ {shouldShowGrid ? ( + { + setGridNumbers([]); + setGridReset(true); + }} + editorMode + /> + ) : ( + "Please set valid dimensions above" + )} +
+
+
+ + {submitText} + +
+
+
+ )} + + ); +}; + +export default AddPuzzleScreen; diff --git a/src/components/AddPuzzleScreen/styles.module.scss b/src/components/AddPuzzleScreen/styles.module.scss new file mode 100644 index 0000000..d04b397 --- /dev/null +++ b/src/components/AddPuzzleScreen/styles.module.scss @@ -0,0 +1,159 @@ +* { + box-sizing: border-box; +} + +.screen { + position: fixed; + height: 100vh; + width: 100vw; + background-color: #212121; + z-index: 3; + font-family: monospace; + display: flex; + padding: 50px 40px; + align-items: flex-start; + justify-content: flex-start; + overflow-y: auto; + flex-direction: column; + gap: 50px; + + .cross { + $size: 20px; + height: $size; + width: $size; + position: fixed; + top: 40px; + right: 40px; + background-color: inherit; + padding: 3 * $size / 4; + z-index: 3; + cursor: pointer; + + &:after, &:before { + content: ""; + height: $size / 8; + width: $size; + background-color: #b0bec5; + position: absolute; + top: 50%; + left: 50%; + border-radius: $size / 8; + } + + &:after { + transform: translate(-50%, -50%) rotate(45deg); + } + + &:before { + transform: translate(-50%, -50%) rotate(-45deg); + } + } + + section { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 20px; + font-size: 17px; + color: #e0e0e0; + width: 100%; + + & > span { + padding-left: 20px; + } + + a { + color: #00b0ff; + text-decoration: none; + } + } + + .title { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + margin-bottom: 10px; + font-size: 26px; + text-transform: uppercase; + font-weight: 600; + color: #f5f5f5; + } + + .content { + line-height: inherit; + font-size: inherit; + padding-left: 20px; + color: inherit; + position: relative; + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + + .matrixGrid { + display: flex; + align-self: center; + justify-self: center; + gap: 20px; + align-items: center; + justify-content: center; + width: 100%; + } + + .submitButton { + display: flex; + align-items: center; + justify-content: center; + background-color: #263238; + height: auto; + width: auto; + padding: 10px 15px; + font-size: 20px; + border: 2px solid #455a64; + margin-top: 20px; + color: #fafafa; + + align-self: center; + justify-self: center; + border-radius: 10px; + cursor: pointer; + transition: all 0.2s; + + &:hover { + border-color: #78909c; + transform: scale(1.01); + color: white; + } + + &.failed { + color: #ff5722; + border-color: #ff5722; + } + + &.passed { + color: #00e676; + border-color: #00e676; + } + } + } + + .dimInputWrapper { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + + .input { + background-color: #282c34; + border-radius: 6px; + display: inline; + + input { + border-radius: 6px; + font-family: monospace; + } + } + } +} diff --git a/src/components/CheckSolution/index.js b/src/components/CheckSolution/index.js new file mode 100644 index 0000000..70baedf --- /dev/null +++ b/src/components/CheckSolution/index.js @@ -0,0 +1,44 @@ +import * as React from "react"; +import {getActiveEdges, multiStyles} from "../../utils"; + +import styles from "./styles.module.scss"; + +const CheckSolution = ({matrix, solution, setCheck}) => { + const [text, setText] = React.useState("Check Solution"); + const [state, setState] = React.useState("none"); + + React.useEffect(() => { + let timeout; + if (state !== "none") { + timeout = setTimeout(() => { + setState("none"); + setCheck("none"); + setText("Check Solution"); + }, 4000); + } + + return () => !isNaN(timeout) && clearTimeout(timeout); + }, [state]); + return ( +
{ + let check = + JSON.stringify(getActiveEdges(matrix)) === JSON.stringify(solution); + if (check) { + setText("That is correct! 😀"); + setState("passed"); + setCheck("passed"); + } else { + setText("Wrong Solution 😔"); + setState("failed"); + setCheck("failed"); + } + }} + > + {text} +
+ ); +}; + +export default CheckSolution; diff --git a/src/components/CheckSolution/styles.module.scss b/src/components/CheckSolution/styles.module.scss new file mode 100644 index 0000000..c2a9cb5 --- /dev/null +++ b/src/components/CheckSolution/styles.module.scss @@ -0,0 +1,38 @@ +* { + box-sizing: border-box; +} + +.button { + display: flex; + align-items: center; + justify-content: center; + background-color: #263238; + height: 50px; + width: auto; + padding: 10px 15px; + font-size: 20px; + border: 2px solid #455a64; + margin-top: 20px; + color: #fafafa; + + border-radius: 10px; + cursor: pointer; + transition: all 0.2s; + + + &:hover { + border-color: #78909c; + transform: scale(1.01); + color: white; + } + + &.failed { + color: #ff5722; + border-color: #ff5722; + } + + &.passed { + color: #00e676; + border-color: #00e676; + } +} diff --git a/src/components/Controls/index.js b/src/components/Controls/index.js new file mode 100644 index 0000000..b679069 --- /dev/null +++ b/src/components/Controls/index.js @@ -0,0 +1,37 @@ +import * as React from "react"; +import AddPuzzleScreen from "../AddPuzzleScreen"; +import {Plus, Refresh, Reset} from "../Icons"; + +import styles from "./styles.module.scss"; + +const Controls = ({onRefresh, onReset, editorMode}) => { + const [showAddScreen, setShowAddScreen] = React.useState(false); + return ( + <> +
+ {!editorMode && ( +
+ + Refresh +
+ )} +
+ + Reset +
+ {!editorMode && ( +
setShowAddScreen(true)}> + + Add +
+ )} +
+ setShowAddScreen(false)} + /> + + ); +}; + +export default Controls; diff --git a/src/components/Controls/styles.module.scss b/src/components/Controls/styles.module.scss new file mode 100644 index 0000000..4106fa1 --- /dev/null +++ b/src/components/Controls/styles.module.scss @@ -0,0 +1,48 @@ +* { + box-sizing: border-box; +} + +.controls { + position: fixed; + bottom: 10px; + left: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + + & > div { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 12px; + color: #90a4ae; + gap: 2px; + + & > svg { + background-color: #263238; + height: 40px; + width: 40px; + padding: 9px; + border: 2px solid #455a64; + font-family: Georgia, 'Times New Roman', Times, serif; + color: #fafafa; + + border-radius: 50%; + cursor: pointer; + transition: all 0.2s; + font-style: italic; + + &:hover { + border-color: #78909c; + font-size: 22px; + color: white; + } + } + + &:hover { + transform: scale(1.1); + } + } +} diff --git a/src/components/Grid/components/Cross.js b/src/components/Grid/components/Cross.js new file mode 100644 index 0000000..209c0eb --- /dev/null +++ b/src/components/Grid/components/Cross.js @@ -0,0 +1,37 @@ +import * as React from "react"; +import {multiStyles} from "../../../utils"; +import styles from "../styles.module.scss"; + +const Cross = ({ + orientation = "h", + className, + onRightClick, + onMiddleClick, + ...props +}) => { + return ( +
{ + e.preventDefault(); + onRightClick(); + }} + onMouseDown={(e) => { + if (e.button === 1) { + onMiddleClick(); + } + }} + > + + +
+ ); +}; + +export default Cross; diff --git a/src/components/Grid/components/Edge.js b/src/components/Grid/components/Edge.js new file mode 100644 index 0000000..0694b5a --- /dev/null +++ b/src/components/Grid/components/Edge.js @@ -0,0 +1,9 @@ +import * as React from "react"; +import Cross from "./Cross"; +import Line from "./Line"; + +const Edge = ({notAllowed, ...props}) => { + return notAllowed ? : ; +}; + +export default Edge; diff --git a/src/components/Grid/components/Line.js b/src/components/Grid/components/Line.js new file mode 100644 index 0000000..57dfebc --- /dev/null +++ b/src/components/Grid/components/Line.js @@ -0,0 +1,36 @@ +import * as React from "react"; +import {multiStyles} from "../../../utils"; +import styles from "../styles.module.scss"; + +const Line = ({ + orientation = "h", + hovered, + active, + className, + onRightClick, + onMiddleClick, + ...props +}) => { + return ( +
{ + e.preventDefault(); + onRightClick(); + }} + onMouseDown={(e) => { + if (e.button === 1) { + onMiddleClick(); + } + }} + /> + ); +}; + +export default Line; diff --git a/src/components/Grid/components/LineRow.js b/src/components/Grid/components/LineRow.js new file mode 100644 index 0000000..05e99a5 --- /dev/null +++ b/src/components/Grid/components/LineRow.js @@ -0,0 +1,58 @@ +import * as React from "react"; +import {EDGE_STATE} from "../../../constants"; +import {findNeighbor, isNodeActive} from "../../../utils"; +import Edge from "./Edge"; +import Node from "./Node"; +import Row from "./Row"; + +const LineRow = ({ + matX, + mat = [], + n = 1, + onLineClick, + onLineRightClick, + onLineMiddleClick, +}) => { + return ( + + {mat.length && + Array(n - 1) + .fill(0) + .map((_, i) => { + const edgeIdx = findNeighbor([matX, i], [matX, i + 1], mat); + const showEdge = edgeIdx !== -1; + return [ + , + onLineClick(matX, i, "horizontal")} + onRightClick={() => onLineRightClick(matX, i, "horizontal")} + onMiddleClick={() => onLineMiddleClick(matX, i, "horizontal")} + hovered={ + showEdge && + mat[matX][i].neigh[edgeIdx].state === EDGE_STATE.HOVERED + ? 1 + : 0 + } + key={`edge_${matX + i}`} + />, + ]; + }) + .concat([ + , + ])} + + ); +}; + +export default LineRow; diff --git a/src/components/Grid/components/N.js b/src/components/Grid/components/N.js new file mode 100644 index 0000000..cb48447 --- /dev/null +++ b/src/components/Grid/components/N.js @@ -0,0 +1,17 @@ +import * as React from "react"; +import {multiStyles} from "../../../utils"; +import styles from "../styles.module.scss"; + +const N = ({className, value, setNum, editorMode, ...props}) => { + return ( + + setNum(parseInt(e.target.value))} + value={value >= 0 && !isNaN(value) ? value : undefined} + /> + + ); +}; + +export default N; diff --git a/src/components/Grid/components/Node.js b/src/components/Grid/components/Node.js new file mode 100644 index 0000000..f16be0f --- /dev/null +++ b/src/components/Grid/components/Node.js @@ -0,0 +1,14 @@ +import * as React from "react"; +import {multiStyles} from "../../../utils"; +import styles from "../styles.module.scss"; + +const Node = ({className, active, ...props}) => { + return ( +
+ ); +}; + +export default Node; diff --git a/src/components/Grid/components/NumberRow.js b/src/components/Grid/components/NumberRow.js new file mode 100644 index 0000000..60bbf2c --- /dev/null +++ b/src/components/Grid/components/NumberRow.js @@ -0,0 +1,81 @@ +import * as React from "react"; +import {EDGE_STATE} from "../../../constants"; +import {findNeighbor} from "../../../utils"; +import Edge from "./Edge"; +import N from "./N"; +import Row from "./Row"; + +const NumberRow = ({ + matX, + mat = [], + n = 1, + onLineClick, + onLineRightClick, + onLineMiddleClick, + onNumberChange, + editorMode, +}) => { + const endEdgeIdx = + mat.length && findNeighbor([matX, n - 1], [matX + 1, n - 1], mat); + const showEndEdge = endEdgeIdx !== -1; + return ( + + {mat.length && + Array(n - 1) + .fill(0) + .map((_, i) => { + const edgeIdx = findNeighbor([matX, i], [matX + 1, i], mat); + const showEdge = edgeIdx !== -1; + return [ + onLineClick(matX, i, "vertical")} + onRightClick={() => onLineRightClick(matX, i, "vertical")} + onMiddleClick={() => onLineMiddleClick(matX, i, "vertical")} + hovered={ + showEdge && + mat[matX][i].neigh[edgeIdx].state === EDGE_STATE.HOVERED + ? 1 + : 0 + } + key={`edge_${matX + i}`} + />, + onNumberChange(matX, i, n)} + editorMode={editorMode} + key={`number_${matX + i}`} + />, + ]; + }) + .concat([ + onLineClick(matX, n - 1, "vertical")} + onRightClick={() => onLineRightClick(matX, n - 1, "vertical")} + onMiddleClick={() => onLineMiddleClick(matX, n - 1, "vertical")} + hovered={ + showEndEdge && + mat[matX][n - 1].neigh[endEdgeIdx].state === EDGE_STATE.HOVERED + ? 1 + : 0 + } + key={`edge_${matX + n - 1}`} + />, + ])} + + ); +}; + +export default NumberRow; diff --git a/src/components/Grid/components/Row.js b/src/components/Grid/components/Row.js new file mode 100644 index 0000000..e7381f9 --- /dev/null +++ b/src/components/Grid/components/Row.js @@ -0,0 +1,6 @@ +import * as React from "react"; +import styles from "../styles.module.scss"; + +const Row = ({children}) =>
{children}
; + +export default Row; diff --git a/src/components/Grid/index.js b/src/components/Grid/index.js new file mode 100644 index 0000000..11cd724 --- /dev/null +++ b/src/components/Grid/index.js @@ -0,0 +1,291 @@ +import * as React from "react"; +import {EDGE_STATE} from "../../constants"; +import {areValid, findNeighbor, getNeighbors, multiStyles} from "../../utils"; +import Controls from "../Controls"; +import LineRow from "./components/LineRow"; +import NumberRow from "./components/NumberRow"; +import styles from "./styles.module.scss"; + +/* + Grid traversal + (0, 0) -- (0, 1) -- (0, 2) --> + | | | + | | | + (1, 0) -- (1, 1) -- (1, 2) --> + | | | + | | | + (2, 0) -- (2, 1) -- (2, 2) --> + | | | + | | | + V V V +*/ + +const DEFAULT_NODE = {neigh: [], n: -1}; +/* + structure for neigh elements: + {state: , loc: [x, y]} +*/ + +const Grid = ({ + dim = [3, 3], + edges = [], + numbers = [], + matrix: propMatrix = [], + updateMatrix: updatePropMatrix = () => {}, + readOnly, + onReset: propOnReset, + editorMode, + check, + className, +}) => { + const nRows = dim[0]; + const nCols = dim[1]; + const nHorLine = nRows + 1; + const nVerLine = nCols + 1; + const [matrix, setMatrix] = React.useState([]); + + const setPropMatrix = (matrix) => { + if (Array.isArray(propMatrix)) { + updatePropMatrix(matrix); + } + }; + + // initializing matrix with prop edges and numbers + React.useEffect(() => { + let temp = new Array(nHorLine) + .fill(0) + .map(() => new Array(nVerLine).fill(DEFAULT_NODE)); + numbers.forEach(({r, c, n}) => { + if (r <= nRows && c <= nCols) { + temp[r - 1][c - 1] = {...temp[r - 1][c - 1], n}; + } + }); + edges.forEach(({a: [sx, sy], b: [ex, ey], notAllowed, hovered}) => { + const edgeState = notAllowed + ? EDGE_STATE.NOT_ALLOWED + : hovered + ? EDGE_STATE.HOVERED + : EDGE_STATE.ACTIVE; + let startX, startY, endX, endY, hor, ver; + hor = sx === ex; + ver = sy === ey; + + // excluding cases where both points are same + // or where edge is not hor | ver + // Assumption: all edges are between two adjacent nodes + if ((hor && ver) || (!hor && !ver)) { + return; + } + + // horizonal line + if (hor) { + startX = endX = sx; + if (sy > ey) { + startY = ey; + endY = sy; + } + if (sy < ey) { + startY = sy; + endY = ey; + } + } + // vertical line + if (ver) { + startY = endY = sy; + if (sx > ex) { + startX = ex; + endX = sx; + } + if (sx < ex) { + startX = sx; + endX = ex; + } + } + + // check if all nodes are valid nodes + if (areValid([startX, endX], nRows) && areValid([startY, endY], nCols)) { + temp[startX][startY] = { + ...temp[startX][startY], + neigh: [ + ...temp[startX][startY].neigh, + {state: edgeState, loc: [endX, endY]}, + ], + }; + temp[endX][endY] = { + ...temp[endX][endY], + neigh: [ + ...temp[endX][endY].neigh, + {state: edgeState, loc: [startX, startY]}, + ], + }; + } + }); + + setMatrix(temp); + setPropMatrix(temp); + }, []); + + const updateMatrix = (dataArray) => { + let temp = matrix.slice(); + dataArray.forEach(({node: [x, y], data}) => { + temp[x][y] = {...temp[x][y], ...data}; + }); + setMatrix(temp); + setPropMatrix(temp); + }; + + const onLineClick = (x, y, direction, click) => { + const isMiddleClick = click === "middle"; + const node = [x, y]; + const otherNode = direction === "horizontal" ? [x, y + 1] : [x + 1, y]; + let nodeUpdateData = {}; + let otherNodeUpdateData = {}; + const neighborIndex = findNeighbor(node, otherNode, matrix); + const otherNeighborIndex = findNeighbor(otherNode, node, matrix); + + // if there was already an edge (ACTIVE | NOT_ALLOWED) + if (neighborIndex !== -1) { + // if the edge was NOT_ALLOWED + const isNewEdgeStateEmpty = + matrix[x][y].neigh[neighborIndex].state === EDGE_STATE.NOT_ALLOWED; + let newNeighbors = getNeighbors(node, matrix).slice(); + newNeighbors.splice(neighborIndex, 1); + nodeUpdateData = { + neigh: [ + ...newNeighbors, + ...(isNewEdgeStateEmpty || isMiddleClick + ? [] + : [{state: EDGE_STATE.NOT_ALLOWED, loc: otherNode}]), + ], + }; + let newOtherNeighbors = getNeighbors(otherNode, matrix).slice(); + newOtherNeighbors.splice(otherNeighborIndex, 1); + otherNodeUpdateData = { + neigh: [ + ...newOtherNeighbors, + ...(isNewEdgeStateEmpty || isMiddleClick + ? [] + : [{state: EDGE_STATE.NOT_ALLOWED, loc: node}]), + ], + }; + } else if (!isMiddleClick) { + nodeUpdateData = { + neigh: [ + ...matrix[x][y].neigh, + { + state: + click === "left" ? EDGE_STATE.ACTIVE : EDGE_STATE.NOT_ALLOWED, + loc: otherNode, + }, + ], + }; + otherNodeUpdateData = { + neigh: [ + ...matrix[otherNode[0]][otherNode[1]].neigh, + { + state: + click === "left" ? EDGE_STATE.ACTIVE : EDGE_STATE.NOT_ALLOWED, + loc: node, + }, + ], + }; + } + updateMatrix([ + {node, data: nodeUpdateData}, + {node: otherNode, data: otherNodeUpdateData}, + ]); + }; + + const onLineLeftClick = (x, y, direction) => + onLineClick(x, y, direction, "left"); + const onLineRightClick = (x, y, direction) => + onLineClick(x, y, direction, "right"); + const onLineMiddleClick = (x, y, direction) => + onLineClick(x, y, direction, "middle"); + + const onNumberChange = (x, y, num) => { + updateMatrix([{node: [x, y], data: {n: num}}]); + }; + + const onReset = () => { + let temp = new Array(nHorLine) + .fill(0) + .map(() => new Array(nVerLine).fill(DEFAULT_NODE)); + numbers.forEach(({r, c, n}) => { + if (r <= nRows && c <= nCols) { + temp[r - 1][c - 1] = {...temp[r - 1][c - 1], n}; + } + }); + if (editorMode) { + propOnReset(); + } + setMatrix(temp); + setPropMatrix(temp); + }; + + const onRefresh = () => { + window.location.reload(); + }; + + return ( + <> + {matrix.length > 0 && ( +
+ {Array(nHorLine - 1) + .fill(0) + .map((_, i) => { + return [ + , + , + ]; + }) + .concat([ + , + ])} +
+ )} + {!readOnly && ( + + )} + + ); +}; + +export default Grid; diff --git a/src/components/Grid/styles.module.scss b/src/components/Grid/styles.module.scss new file mode 100644 index 0000000..3c1c03d --- /dev/null +++ b/src/components/Grid/styles.module.scss @@ -0,0 +1,296 @@ +$gridColor: #607d8b; +$gridActiveColor: #90a4ae; +$gridHoverColor: #eceff1; +$invalidColor: #ff8a80; +$validColor: #00e676; + +$transitionDuration: 0.2s; + +$gridScale: 1; + +$nodeSize: $gridScale * 6px; +@media (max-width: 500px) { + $gridScale: 0.5; +} +$nodeActiveSize: $gridScale * 7px; + +$edgeLength: $gridScale * 50px; +$edgeBreadth: $gridScale * 2px; + +$fontSize: $gridScale * 24px; + +$animationSeperation: 0.075s; +$animationScale: 1.5; +$animationDuration: 0.25s; +$animationTimingFunction: linear; +$animationFillMode: forwards; + +// canvas +.row, .canvas { + display: flex; + justify-content: center; + align-items: center; +} + +.canvas { + align-items: flex-start; + flex-direction: column; + overflow-x: auto; + box-sizing: border-box; + max-width: 100%; + padding: 50px; + border-radius: 10px; + background-color: #282c34; + + &.readOnly, &.passed { + * { + pointer-events: none; + cursor: default; + } + } +} + +// grid elements +.node { + display: flex; + height: $nodeSize; + width: $nodeSize; + background-color: $gridColor; + border-radius: 50%; + z-index: 1; + transition: background-color $transitionDuration; + + &.active { + background-color: $gridActiveColor; + width: $nodeActiveSize; + height: $nodeActiveSize; + margin: - ($nodeActiveSize - $nodeSize) / 2; + } +} + +.line { + display: flex; + $lineColor: $gridColor; + background-repeat: repeat; + opacity: 0; + position: relative; + cursor: pointer; + + &:after, &:before { + content: ""; + background-color: transparent; + position: absolute; + } + + &.horizontal { + background-size: 4*$edgeBreadth 100%; + background-image: linear-gradient(90deg, $lineColor 50%, transparent 50%); + height: $edgeBreadth; + width: $edgeLength; + + &:after, &:before { + height: ($nodeSize - $edgeBreadth); + width: 100%; + left: 0; + } + + &:after { + top: 100%; + } + + &:before { + bottom: 100%; + } + } + + &.vertical { + background-size: 100% 4*$edgeBreadth; + background-image: linear-gradient(180deg, $lineColor 50%, transparent 50%); + height: $edgeLength; + width: $edgeBreadth; + margin: 0 ($nodeSize - $edgeBreadth) / 2; + + &:after, &:before { + width: ($nodeSize - $edgeBreadth); + height: 100%; + top: 0; + } + + &:after { + right: 100%; + } + + &:before { + left: 100%; + } + } + + &:hover, &.hovered { + transition: opacity $transitionDuration; + opacity: 1; + } + + &.active { + opacity: 1; + background: $gridActiveColor; + transition: background-color $transitionDuration; + + &:hover { + background: $gridHoverColor; + + &.horizontal { + height: 1.5 * $edgeBreadth; + } + + &.vertical { + width: 1.5 * $edgeBreadth; + margin: 0 (($nodeSize - 1.5 * $edgeBreadth) / 2); + } + } + } + + &.cross { + opacity: 1; + background: none; + display: flex; + align-items: center; + justify-content: center; + + & > span { + position: absolute; + width: 6 * $edgeBreadth; + height: 3 * $edgeBreadth / 4; + background-color: $invalidColor; + border-radius: 2 * $edgeBreadth; + transition: all $transitionDuration; + + &:first-child { + transform: rotate(45deg); + } + + &:last-child { + transform: rotate(-45deg); + } + } + + &:hover { + & > span { + width: 7 * $edgeBreadth; + height: $edgeBreadth; + background-color: $gridHoverColor; + } + } + } +} + +.number { + font-size: $fontSize; + display: flex; + align-items: center; + justify-content: center; + height: $edgeLength; + width: $edgeLength; + + .invalid { + color: $invalidColor; + } + + input { + text-align: center; + height: inherit; + width: inherit; + background: inherit; + outline: none; + box-shadow: none; + border: inherit; + color: inherit; + font-size: inherit; + display: inherit; + align-items: inherit; + justify-content: inherit; + cursor: inherit; + } +} + +.canvas.failed { + .node.active { + background-color: $invalidColor; + } + + .line { + &:not(.cross){ + &:not(:hover) { + background-color: $invalidColor; + } + } + } +} + +.canvas.passed { + + .node.active { + animation: node $animationDuration $animationTimingFunction $animationFillMode; + } + + .line:not(.cross) { + &.horizontal { + animation: lineX $animationDuration $animationTimingFunction $animationFillMode; + } + + &.vertical { + animation: lineY $animationDuration $animationTimingFunction $animationFillMode; + } + } + + .node.active, .line:not(.cross) { + @for $i from 1 through 50 { + &:nth-child(#{$i}) { + animation-delay: ($i - 1) * $animationSeperation; + } + } + } + +} + +@keyframes node { + 0% { + background-color: $validColor; + transform: scale(1); + } + 50% { + transform: scale($animationScale); + } + 100% { + background-color: $validColor; + transform: scale(1); + } +} + +@keyframes lineX { + 0% { + background-color: $validColor; + transform: scaleY(1); + } + 50% { + transform: scaleY(1.5 * $animationScale); + } + 100% { + background-color: $validColor; + transform: scaleY(1); + } +} + + +@keyframes lineY { + 0% { + background-color: $validColor; + transform: scaleX(1); + } + 50% { + transform: scaleX(1.5 * $animationScale); + } + 100% { + background-color: $validColor; + transform: scaleX(1); + } +} diff --git a/src/components/Icons.js b/src/components/Icons.js new file mode 100644 index 0000000..fc9a769 --- /dev/null +++ b/src/components/Icons.js @@ -0,0 +1,89 @@ +import * as React from "react"; + +export const Undo = ({className, onClick}) => { + return ( + + + + ); +}; + +export const Redo = ({className, onClick}) => { + return ( + + + + + ); +}; + +export const Refresh = ({className, onClick}) => { + return ( + + + + + ); +}; + +export const Reset = ({className, onClick}) => { + return ( + + + + + ); +}; + +export const Plus = ({className, onClick}) => { + return ( + + + + + ); +}; diff --git a/src/components/Info/index.js b/src/components/Info/index.js new file mode 100644 index 0000000..64d77ac --- /dev/null +++ b/src/components/Info/index.js @@ -0,0 +1,127 @@ +import * as React from "react"; +import { + EXAMPLE_GRID_PROPS, + NOT_ALLOWED_1_GRID_PROPS, + NOT_ALLOWED_2_GRID_PROPS, +} from "../../constants"; +import Grid from "../Grid"; + +import styles from "./styles.module.scss"; + +const Info = () => { + const [open, setOpen] = React.useState(false); + + return ( + <> +
setOpen(true)}> + i +
+ {open && ( +
+ setOpen(false)} /> + +
+
Rules
+
+ Connect adjacent dots with vertical or horizontal lines, creating + a single loop. +
+ +
+
+
+ Crossovers or branches are not allowed (as shown by dotted lines + below). +
+ + +
+
+
+ Numbers in the puzzle indicate the number of lines that should + surround it. +
+
+ You can't draw lines around zeroes. +
+
+ Each puzzle has just one uniques solution. +
+
+
+
How to begin
+
+ Begin with the zero next to 3. If no such case is present, then + use one of the rules in the{" "} + + wikipedia link + {" "} + to start off. +
+
+ Since no lines can be drawn around zero, mark crosses around it as + shown. +
+
+ Now there's a cross in one space around 3. So we know the three + lines of 3 can only be drawn in the remaining three spaces. +
+
+ Next, these lines can only be extended in one direction each. +
+
Continue using the same logic.
+
+
+
Hints
+ + Keep eliminating possibilities by marking crosses in spaces + between dots where a line isn't possible, i.e., if you have + already completed required lines or where a line creation may + create a branch or a deadend. Visit the{" "} + + wikipedia link + {" "} + for more hints. + +
+
+
Controls
+ + + + + + + + + + + + + + + +
+ Left Mouse Button + Toggle line
+ Right Mouse Button + Mark line as crossed/not allowed
+ Middle Mouse Button + Reset line state
+
+
+ )} + + ); +}; + +export default Info; diff --git a/src/components/Info/styles.module.scss b/src/components/Info/styles.module.scss new file mode 100644 index 0000000..fe869bd --- /dev/null +++ b/src/components/Info/styles.module.scss @@ -0,0 +1,171 @@ +* { + box-sizing: border-box; +} + +.button { + position: fixed; + bottom: 10px; + right: 10px; + display: flex; + align-items: center; + justify-content: center; + background-color: #263238; + height: 40px; + width: 40px; + padding: 10px; + font-size: 20px; + border: 2px solid #455a64; + font-family: Georgia, 'Times New Roman', Times, serif; + + border-radius: 50%; + cursor: pointer; + transition: all 0.2s; + font-style: italic; + + &:hover { + border-color: #78909c; + font-size: 22px; + transform: scale(1.1); + color: white; + } +} + +.info { + position: fixed; + height: 100vh; + width: 100vw; + background-color: #212121; + z-index: 2; + font-family: monospace; + display: flex; + padding: 50px 40px; + align-items: flex-start; + justify-content: flex-start; + overflow-y: auto; + flex-direction: column; + gap: 50px; + + .cross { + $size: 20px; + height: $size; + width: $size; + position: fixed; + top: 40px; + right: 40px; + background-color: inherit; + padding: 3 * $size / 4; + z-index: 3; + cursor: pointer; + + &:after, &:before { + content: ""; + height: $size / 8; + width: $size; + background-color: #b0bec5; + position: absolute; + top: 50%; + left: 50%; + border-radius: $size / 8; + } + + &:after { + transform: translate(-50%, -50%) rotate(45deg); + } + + &:before { + transform: translate(-50%, -50%) rotate(-45deg); + } + } + + section { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + gap: 20px; + font-size: 17px; + color: #e0e0e0; + width: 100%; + + & > span { + padding-left: 20px; + } + + a { + color: #00b0ff; + text-decoration: none; + } + } + + .title { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin-bottom: 10px; + font-size: 26px; + text-transform: uppercase; + font-weight: 600; + color: #f5f5f5; + } + + .item { + line-height: inherit; + font-size: inherit; + padding-left: 20px; + color: inherit; + position: relative; + display: flex; + flex-direction: column; + gap: 20px; + width: 100%; + + &:before { + content: ""; + position: absolute; + width: 7px; + height: 7px; + top: 7px; + left: 0; + box-sizing: border-box; + border-radius: 50%; + background-color: #424242; + border: 1.5px solid #9e9e9e; + } + + .eg { + display: flex; + flex-wrap: wrap; + align-self: center; + justify-self: center; + gap: 20px; + align-items: center; + justify-content: center; + width: 100%; + } + } + + table { + width: 100%; + padding-left: 20px; + + tr { + width: 100%; + + td { + width: 50%; + padding-bottom: 10px; + + &:last-child { + padding-left: 10px; + } + } + + &:last-child { + td { + padding-bottom: 0; + } + } + } + } +} diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..2b1ae2c --- /dev/null +++ b/src/constants.js @@ -0,0 +1,162 @@ +export const EDGE_STATE = { + ACTIVE: "ACTIVE", + NOT_ALLOWED: "NOT_ALLOWED", + HOVERED: "HOOVERED", +}; + +export const EXAMPLE_GRID_PROPS = { + readOnly: true, + dim: [7, 7], + edges: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2], notAllowed: true}, + {a: [0, 1], b: [1, 1]}, + {a: [0, 2], b: [0, 3], notAllowed: true}, + {a: [0, 2], b: [1, 2], notAllowed: true}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 3], b: [1, 3]}, + {a: [0, 4], b: [0, 5]}, + {a: [0, 4], b: [1, 4], notAllowed: true}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 5], b: [1, 5], notAllowed: true}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 6], b: [1, 6], notAllowed: true}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [1, 1], notAllowed: true}, + {a: [1, 0], b: [2, 0]}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 2], b: [1, 3], notAllowed: true}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [1, 4]}, + {a: [1, 3], b: [2, 3], notAllowed: true}, + {a: [1, 4], b: [1, 5]}, + {a: [1, 4], b: [2, 4], notAllowed: true}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 5], b: [2, 5], notAllowed: true}, + {a: [1, 6], b: [1, 7], notAllowed: true}, + {a: [1, 6], b: [2, 6]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [2, 1]}, + {a: [2, 1], b: [3, 1]}, + {a: [2, 2], b: [2, 3]}, + {a: [2, 2], b: [3, 2], notAllowed: true}, + {a: [2, 3], b: [2, 4], notAllowed: true}, + {a: [2, 3], b: [3, 3]}, + {a: [2, 4], b: [2, 5], notAllowed: true}, + {a: [2, 4], b: [3, 4], notAllowed: true}, + {a: [2, 5], b: [2, 6]}, + {a: [2, 5], b: [3, 5]}, + {a: [2, 6], b: [2, 7], notAllowed: true}, + {a: [2, 6], b: [3, 6], notAllowed: true}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [3, 1], notAllowed: true}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 1], b: [4, 1], notAllowed: true}, + {a: [3, 2], b: [4, 2]}, + {a: [3, 3], b: [3, 4]}, + {a: [3, 4], b: [3, 5], notAllowed: true}, + {a: [3, 4], b: [4, 4]}, + {a: [3, 5], b: [3, 6], notAllowed: true}, + {a: [3, 5], b: [4, 5]}, + {a: [3, 6], b: [3, 7]}, + {a: [3, 6], b: [4, 6]}, + {a: [4, 0], b: [4, 1], notAllowed: true}, + {a: [4, 1], b: [4, 2], notAllowed: true}, + {a: [4, 1], b: [5, 1], notAllowed: true}, + {a: [4, 2], b: [4, 3]}, + {a: [4, 2], b: [5, 2], notAllowed: true}, + {a: [4, 3], b: [4, 4], notAllowed: true}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 4], b: [4, 5]}, + {a: [4, 4], b: [5, 4], notAllowed: true}, + {a: [4, 5], b: [4, 6], notAllowed: true}, + {a: [4, 5], b: [5, 5], notAllowed: true}, + {a: [4, 6], b: [4, 7]}, + {a: [4, 6], b: [5, 6], notAllowed: true}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 0], b: [5, 1], notAllowed: true}, + {a: [5, 1], b: [5, 2]}, + {a: [5, 1], b: [6, 1]}, + {a: [5, 2], b: [5, 3]}, + {a: [5, 2], b: [6, 2], notAllowed: true}, + {a: [5, 3], b: [5, 4], notAllowed: true}, + {a: [5, 3], b: [6, 3], notAllowed: true}, + {a: [5, 4], b: [5, 5]}, + {a: [5, 4], b: [6, 4]}, + {a: [5, 5], b: [5, 6], notAllowed: true}, + {a: [5, 5], b: [6, 5]}, + {a: [5, 6], b: [5, 7]}, + {a: [5, 6], b: [6, 6]}, + {a: [6, 0], b: [6, 1]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 1], b: [7, 1], notAllowed: true}, + {a: [6, 2], b: [6, 3]}, + {a: [6, 2], b: [7, 2]}, + {a: [6, 3], b: [6, 4], notAllowed: true}, + {a: [6, 3], b: [7, 3]}, + {a: [6, 4], b: [6, 5], notAllowed: true}, + {a: [6, 4], b: [7, 4]}, + {a: [6, 5], b: [6, 6], notAllowed: true}, + {a: [6, 5], b: [7, 5]}, + {a: [6, 6], b: [6, 7]}, + {a: [6, 6], b: [7, 6], notAllowed: true}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 3], b: [7, 4]}, + {a: [7, 5], b: [7, 6]}, + {a: [7, 6], b: [7, 7]}, + ], + numbers: [ + {r: 1, c: 4, n: 3}, + {r: 1, c: 5, n: 2}, + {r: 1, c: 6, n: 2}, + {r: 2, c: 1, n: 2}, + {r: 2, c: 2, n: 2}, + {r: 2, c: 3, n: 2}, + {r: 2, c: 6, n: 3}, + {r: 3, c: 1, n: 2}, + {r: 3, c: 2, n: 2}, + {r: 3, c: 5, n: 1}, + {r: 3, c: 6, n: 2}, + {r: 4, c: 3, n: 2}, + {r: 4, c: 5, n: 3}, + {r: 4, c: 7, n: 3}, + {r: 5, c: 1, n: 0}, + {r: 5, c: 6, n: 0}, + {r: 5, c: 7, n: 3}, + {r: 6, c: 1, n: 2}, + {r: 6, c: 7, n: 3}, + {r: 7, c: 2, n: 2}, + {r: 7, c: 3, n: 3}, + {r: 7, c: 5, n: 2}, + ], +}; + +export const NOT_ALLOWED_1_GRID_PROPS = { + readOnly: true, + dim: [2, 2], + edges: [ + {a: [0, 1], b: [1, 1], hovered: true}, + {a: [1, 0], b: [1, 1], hovered: true}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 1], b: [2, 1]}, + ], + numbers: [{r: 2, c: 2, n: 2}], +}; + +export const NOT_ALLOWED_2_GRID_PROPS = { + readOnly: true, + dim: [2, 3], + edges: [ + {a: [0, 1], b: [1, 1]}, + {a: [0, 2], b: [1, 2]}, + {a: [1, 0], b: [1, 1], hovered: true}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 1], b: [2, 1], hovered: true}, + {a: [1, 2], b: [1, 3], hovered: true}, + {a: [1, 2], b: [2, 2], hovered: true}, + ], + numbers: [{r: 1, c: 2, n: 3}], +}; diff --git a/src/examples.js b/src/examples.js new file mode 100644 index 0000000..7b93bf0 --- /dev/null +++ b/src/examples.js @@ -0,0 +1,608 @@ +export const examples = [ + { + dim: [7, 7], + numbers: [ + {r: 1, c: 2, n: 2}, + {r: 1, c: 7, n: 3}, + {r: 2, c: 2, n: 3}, + {r: 2, c: 4, n: 2}, + {r: 2, c: 5, n: 2}, + {r: 2, c: 7, n: 1}, + {r: 3, c: 2, n: 3}, + {r: 3, c: 3, n: 2}, + {r: 3, c: 4, n: 2}, + {r: 3, c: 6, n: 2}, + {r: 3, c: 7, n: 2}, + {r: 4, c: 3, n: 2}, + {r: 4, c: 4, n: 0}, + {r: 4, c: 5, n: 3}, + {r: 4, c: 6, n: 2}, + {r: 5, c: 3, n: 2}, + {r: 5, c: 5, n: 3}, + {r: 5, c: 6, n: 2}, + {r: 6, c: 2, n: 2}, + {r: 6, c: 3, n: 1}, + {r: 6, c: 5, n: 1}, + {r: 7, c: 5, n: 3}, + {r: 7, c: 7, n: 3}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [1, 1]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 3], b: [1, 3]}, + {a: [0, 4], b: [1, 4]}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 6], b: [1, 6]}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [2, 0]}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 4], b: [2, 4]}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 5], b: [2, 5]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [3, 0]}, + {a: [2, 1], b: [2, 2]}, + {a: [2, 1], b: [3, 1]}, + {a: [2, 3], b: [3, 3]}, + {a: [2, 4], b: [3, 4]}, + {a: [2, 5], b: [2, 6]}, + {a: [2, 6], b: [3, 6]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [4, 0]}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 2], b: [3, 3]}, + {a: [3, 4], b: [3, 5]}, + {a: [3, 5], b: [4, 5]}, + {a: [3, 6], b: [4, 6]}, + {a: [3, 7], b: [4, 7]}, + {a: [4, 0], b: [5, 0]}, + {a: [4, 1], b: [4, 2]}, + {a: [4, 1], b: [5, 1]}, + {a: [4, 2], b: [4, 3]}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 4], b: [4, 5]}, + {a: [4, 4], b: [5, 4]}, + {a: [4, 6], b: [5, 6]}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 0], b: [6, 0]}, + {a: [5, 1], b: [6, 1]}, + {a: [5, 3], b: [6, 3]}, + {a: [5, 4], b: [5, 5]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 7], b: [6, 7]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 1], b: [6, 2]}, + {a: [6, 2], b: [7, 2]}, + {a: [6, 3], b: [6, 4]}, + {a: [6, 4], b: [7, 4]}, + {a: [6, 5], b: [6, 6]}, + {a: [6, 5], b: [7, 5]}, + {a: [6, 6], b: [7, 6]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 6], b: [7, 7]}, + ], + }, + { + dim: [7, 7], + numbers: [ + {r: 1, c: 4, n: 1}, + {r: 1, c: 7, n: 3}, + {r: 2, c: 1, n: 2}, + {r: 2, c: 2, n: 2}, + {r: 2, c: 3, n: 3}, + {r: 2, c: 4, n: 3}, + {r: 2, c: 5, n: 2}, + {r: 3, c: 1, n: 2}, + {r: 3, c: 2, n: 2}, + {r: 3, c: 5, n: 2}, + {r: 3, c: 6, n: 2}, + {r: 3, c: 7, n: 3}, + {r: 4, c: 1, n: 3}, + {r: 4, c: 2, n: 0}, + {r: 4, c: 3, n: 2}, + {r: 4, c: 7, n: 3}, + {r: 5, c: 1, n: 3}, + {r: 5, c: 5, n: 3}, + {r: 5, c: 6, n: 2}, + {r: 6, c: 2, n: 1}, + {r: 6, c: 4, n: 2}, + {r: 6, c: 6, n: 3}, + {r: 7, c: 2, n: 2}, + {r: 7, c: 6, n: 2}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2]}, + {a: [0, 2], b: [0, 3]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 4], b: [0, 5]}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [1, 1]}, + {a: [1, 1], b: [2, 1]}, + {a: [1, 2], b: [1, 3]}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 4], b: [1, 5]}, + {a: [1, 4], b: [2, 4]}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 6], b: [1, 7]}, + {a: [2, 1], b: [3, 1]}, + {a: [2, 2], b: [3, 2]}, + {a: [2, 3], b: [2, 4]}, + {a: [2, 5], b: [2, 6]}, + {a: [2, 5], b: [3, 5]}, + {a: [2, 6], b: [2, 7]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [3, 1]}, + {a: [3, 0], b: [4, 0]}, + {a: [3, 2], b: [3, 3]}, + {a: [3, 3], b: [3, 4]}, + {a: [3, 4], b: [3, 5]}, + {a: [3, 6], b: [3, 7]}, + {a: [3, 6], b: [4, 6]}, + {a: [4, 0], b: [4, 1]}, + {a: [4, 1], b: [5, 1]}, + {a: [4, 2], b: [4, 3]}, + {a: [4, 2], b: [5, 2]}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 4], b: [4, 5]}, + {a: [4, 4], b: [5, 4]}, + {a: [4, 5], b: [5, 5]}, + {a: [4, 6], b: [4, 7]}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 0], b: [5, 1]}, + {a: [5, 0], b: [6, 0]}, + {a: [5, 2], b: [6, 2]}, + {a: [5, 3], b: [5, 4]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 6], b: [6, 6]}, + {a: [5, 7], b: [6, 7]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 2], b: [7, 2]}, + {a: [6, 3], b: [6, 4]}, + {a: [6, 3], b: [7, 3]}, + {a: [6, 4], b: [6, 5]}, + {a: [6, 5], b: [6, 6]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 3], b: [7, 4]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 5], b: [7, 6]}, + {a: [7, 6], b: [7, 7]}, + ], + }, + { + dim: [7, 7], + numbers: [ + {r: 1, c: 2, n: 2}, + {r: 1, c: 4, n: 3}, + {r: 1, c: 5, n: 2}, + {r: 1, c: 6, n: 3}, + {r: 2, c: 2, n: 1}, + {r: 2, c: 3, n: 3}, + {r: 2, c: 4, n: 2}, + {r: 2, c: 6, n: 2}, + {r: 3, c: 2, n: 2}, + {r: 3, c: 4, n: 0}, + {r: 3, c: 5, n: 3}, + {r: 4, c: 1, n: 1}, + {r: 4, c: 2, n: 2}, + {r: 4, c: 3, n: 3}, + {r: 4, c: 5, n: 3}, + {r: 5, c: 1, n: 2}, + {r: 5, c: 2, n: 2}, + {r: 5, c: 3, n: 1}, + {r: 5, c: 4, n: 1}, + {r: 5, c: 5, n: 1}, + {r: 5, c: 6, n: 2}, + {r: 6, c: 1, n: 2}, + {r: 6, c: 3, n: 2}, + {r: 7, c: 4, n: 2}, + {r: 7, c: 5, n: 3}, + {r: 7, c: 7, n: 3}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2]}, + {a: [0, 2], b: [1, 2]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 3], b: [1, 3]}, + {a: [0, 4], b: [1, 4]}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 5], b: [1, 5]}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [2, 0]}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 4], b: [2, 4]}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 6], b: [2, 6]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [2, 1]}, + {a: [2, 1], b: [3, 1]}, + {a: [2, 2], b: [2, 3]}, + {a: [2, 4], b: [2, 5]}, + {a: [2, 5], b: [3, 5]}, + {a: [2, 6], b: [3, 6]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 2], b: [3, 3]}, + {a: [3, 3], b: [4, 3]}, + {a: [3, 4], b: [3, 5]}, + {a: [3, 4], b: [4, 4]}, + {a: [3, 6], b: [4, 6]}, + {a: [3, 7], b: [4, 7]}, + {a: [4, 0], b: [4, 1]}, + {a: [4, 0], b: [5, 0]}, + {a: [4, 1], b: [4, 2]}, + {a: [4, 2], b: [4, 3]}, + {a: [4, 4], b: [4, 5]}, + {a: [4, 5], b: [4, 6]}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 0], b: [6, 0]}, + {a: [5, 1], b: [5, 2]}, + {a: [5, 1], b: [6, 1]}, + {a: [5, 2], b: [6, 2]}, + {a: [5, 3], b: [5, 4]}, + {a: [5, 3], b: [6, 3]}, + {a: [5, 4], b: [6, 4]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 5], b: [6, 5]}, + {a: [5, 6], b: [6, 6]}, + {a: [5, 7], b: [6, 7]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 1], b: [7, 1]}, + {a: [6, 2], b: [7, 2]}, + {a: [6, 3], b: [7, 3]}, + {a: [6, 4], b: [7, 4]}, + {a: [6, 5], b: [7, 5]}, + {a: [6, 6], b: [7, 6]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 2], b: [7, 3]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 6], b: [7, 7]}, + ], + }, + { + dim: [7, 7], + numbers: [ + {r: 1, c: 2, n: 2}, + {r: 1, c: 3, n: 2}, + {r: 1, c: 4, n: 2}, + {r: 1, c: 5, n: 2}, + {r: 1, c: 6, n: 2}, + {r: 1, c: 7, n: 2}, + {r: 2, c: 1, n: 2}, + {r: 2, c: 7, n: 2}, + {r: 3, c: 1, n: 2}, + {r: 3, c: 3, n: 2}, + {r: 3, c: 4, n: 2}, + {r: 3, c: 5, n: 2}, + {r: 3, c: 7, n: 2}, + {r: 4, c: 1, n: 3}, + {r: 4, c: 5, n: 2}, + {r: 4, c: 7, n: 2}, + {r: 5, c: 2, n: 1}, + {r: 5, c: 4, n: 3}, + {r: 5, c: 5, n: 0}, + {r: 5, c: 6, n: 2}, + {r: 5, c: 7, n: 1}, + {r: 6, c: 1, n: 2}, + {r: 6, c: 2, n: 3}, + {r: 6, c: 4, n: 3}, + {r: 6, c: 5, n: 2}, + {r: 7, c: 5, n: 2}, + {r: 7, c: 6, n: 2}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2]}, + {a: [0, 2], b: [0, 3]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 4], b: [0, 5]}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [1, 1]}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 2], b: [1, 3]}, + {a: [1, 3], b: [1, 4]}, + {a: [1, 4], b: [1, 5]}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 6], b: [2, 6]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [2, 1]}, + {a: [2, 0], b: [3, 0]}, + {a: [2, 1], b: [2, 2]}, + {a: [2, 2], b: [2, 3]}, + {a: [2, 3], b: [2, 4]}, + {a: [2, 4], b: [2, 5]}, + {a: [2, 5], b: [3, 5]}, + {a: [2, 6], b: [3, 6]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [4, 0]}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 1], b: [4, 1]}, + {a: [3, 2], b: [3, 3]}, + {a: [3, 3], b: [3, 4]}, + {a: [3, 4], b: [4, 4]}, + {a: [3, 5], b: [4, 5]}, + {a: [3, 6], b: [4, 6]}, + {a: [3, 7], b: [4, 7]}, + {a: [4, 0], b: [4, 1]}, + {a: [4, 3], b: [4, 4]}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 5], b: [4, 6]}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 1], b: [5, 2]}, + {a: [5, 1], b: [6, 1]}, + {a: [5, 2], b: [6, 2]}, + {a: [5, 3], b: [5, 4]}, + {a: [5, 4], b: [6, 4]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 5], b: [6, 5]}, + {a: [5, 6], b: [6, 6]}, + {a: [5, 7], b: [6, 7]}, + {a: [6, 0], b: [6, 1]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 2], b: [6, 3]}, + {a: [6, 3], b: [6, 4]}, + {a: [6, 5], b: [7, 5]}, + {a: [6, 6], b: [7, 6]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 2], b: [7, 3]}, + {a: [7, 3], b: [7, 4]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 6], b: [7, 7]}, + ], + }, + { + dim: [5, 5], + numbers: [ + {r: 1, c: 2, n: 3}, + {r: 1, c: 3, n: 1}, + {r: 1, c: 4, n: 2}, + {r: 2, c: 2, n: 3}, + {r: 2, c: 5, n: 2}, + {r: 3, c: 2, n: 2}, + {r: 3, c: 5, n: 3}, + {r: 4, c: 1, n: 2}, + {r: 4, c: 3, n: 1}, + {r: 4, c: 5, n: 2}, + {r: 5, c: 1, n: 0}, + {r: 5, c: 4, n: 3}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2]}, + {a: [0, 2], b: [1, 2]}, + {a: [0, 4], b: [0, 5]}, + {a: [0, 4], b: [1, 4]}, + {a: [0, 5], b: [1, 5]}, + {a: [1, 0], b: [2, 0]}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 1], b: [2, 1]}, + {a: [1, 3], b: [1, 4]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 5], b: [2, 5]}, + {a: [2, 0], b: [3, 0]}, + {a: [2, 1], b: [2, 2]}, + {a: [2, 2], b: [3, 2]}, + {a: [2, 3], b: [3, 3]}, + {a: [2, 4], b: [2, 5]}, + {a: [2, 4], b: [3, 4]}, + {a: [3, 0], b: [3, 1]}, + {a: [3, 1], b: [4, 1]}, + {a: [3, 2], b: [3, 3]}, + {a: [3, 4], b: [3, 5]}, + {a: [3, 5], b: [4, 5]}, + {a: [4, 1], b: [4, 2]}, + {a: [4, 2], b: [5, 2]}, + {a: [4, 3], b: [4, 4]}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 4], b: [5, 4]}, + {a: [4, 5], b: [5, 5]}, + {a: [5, 2], b: [5, 3]}, + {a: [5, 4], b: [5, 5]}, + ], + }, + { + dim: [7, 7], + numbers: [ + {r: 1, c: 3, n: 2}, + {r: 2, c: 1, n: 2}, + {r: 2, c: 2, n: 3}, + {r: 2, c: 4, n: 2}, + {r: 2, c: 6, n: 2}, + {r: 3, c: 3, n: 1}, + {r: 3, c: 5, n: 3}, + {r: 4, c: 3, n: 2}, + {r: 4, c: 4, n: 3}, + {r: 4, c: 6, n: 3}, + {r: 4, c: 7, n: 2}, + {r: 5, c: 1, n: 3}, + {r: 5, c: 3, n: 0}, + {r: 5, c: 4, n: 2}, + {r: 6, c: 1, n: 2}, + {r: 6, c: 2, n: 3}, + {r: 6, c: 6, n: 2}, + {r: 7, c: 3, n: 1}, + {r: 7, c: 5, n: 3}, + {r: 7, c: 7, n: 3}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [1, 1]}, + {a: [0, 2], b: [0, 3]}, + {a: [0, 2], b: [1, 2]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 4], b: [1, 4]}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 5], b: [1, 5]}, + {a: [0, 6], b: [0, 7]}, + {a: [0, 7], b: [1, 7]}, + {a: [1, 0], b: [2, 0]}, + {a: [1, 1], b: [2, 1]}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [1, 4]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 5], b: [1, 6]}, + {a: [1, 6], b: [2, 6]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [3, 0]}, + {a: [2, 1], b: [2, 2]}, + {a: [2, 3], b: [3, 3]}, + {a: [2, 4], b: [2, 5]}, + {a: [2, 4], b: [3, 4]}, + {a: [2, 5], b: [3, 5]}, + {a: [2, 6], b: [3, 6]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [3, 1]}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 2], b: [4, 2]}, + {a: [3, 3], b: [4, 3]}, + {a: [3, 4], b: [4, 4]}, + {a: [3, 5], b: [4, 5]}, + {a: [3, 6], b: [4, 6]}, + {a: [3, 7], b: [4, 7]}, + {a: [4, 0], b: [4, 1]}, + {a: [4, 0], b: [5, 0]}, + {a: [4, 1], b: [4, 2]}, + {a: [4, 3], b: [4, 4]}, + {a: [4, 5], b: [4, 6]}, + {a: [4, 7], b: [5, 7]}, + {a: [5, 0], b: [5, 1]}, + {a: [5, 1], b: [5, 2]}, + {a: [5, 2], b: [6, 2]}, + {a: [5, 3], b: [5, 4]}, + {a: [5, 3], b: [6, 3]}, + {a: [5, 4], b: [5, 5]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 6], b: [6, 6]}, + {a: [5, 7], b: [6, 7]}, + {a: [6, 0], b: [6, 1]}, + {a: [6, 0], b: [7, 0]}, + {a: [6, 1], b: [6, 2]}, + {a: [6, 3], b: [6, 4]}, + {a: [6, 4], b: [6, 5]}, + {a: [6, 5], b: [7, 5]}, + {a: [6, 6], b: [7, 6]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 0], b: [7, 1]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 2], b: [7, 3]}, + {a: [7, 3], b: [7, 4]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 6], b: [7, 7]}, + ], + }, + { + dim: [7, 7], + numbers: [ + {r: 1, c: 3, n: 1}, + {r: 1, c: 6, n: 3}, + {r: 1, c: 7, n: 2}, + {r: 2, c: 1, n: 2}, + {r: 2, c: 4, n: 2}, + {r: 2, c: 7, n: 2}, + {r: 3, c: 2, n: 2}, + {r: 3, c: 3, n: 1}, + {r: 3, c: 4, n: 2}, + {r: 3, c: 5, n: 0}, + {r: 3, c: 6, n: 3}, + {r: 4, c: 1, n: 2}, + {r: 4, c: 4, n: 2}, + {r: 4, c: 5, n: 3}, + {r: 5, c: 1, n: 3}, + {r: 5, c: 4, n: 2}, + {r: 5, c: 6, n: 2}, + {r: 6, c: 1, n: 2}, + {r: 6, c: 2, n: 2}, + {r: 6, c: 3, n: 2}, + {r: 6, c: 7, n: 1}, + {r: 7, c: 2, n: 2}, + {r: 7, c: 5, n: 1}, + ], + solution: [ + {a: [0, 0], b: [0, 1]}, + {a: [0, 0], b: [1, 0]}, + {a: [0, 1], b: [0, 2]}, + {a: [0, 2], b: [0, 3]}, + {a: [0, 3], b: [0, 4]}, + {a: [0, 4], b: [1, 4]}, + {a: [0, 5], b: [0, 6]}, + {a: [0, 5], b: [1, 5]}, + {a: [0, 6], b: [1, 6]}, + {a: [1, 0], b: [1, 1]}, + {a: [1, 1], b: [1, 2]}, + {a: [1, 2], b: [2, 2]}, + {a: [1, 3], b: [1, 4]}, + {a: [1, 3], b: [2, 3]}, + {a: [1, 5], b: [2, 5]}, + {a: [1, 6], b: [1, 7]}, + {a: [1, 7], b: [2, 7]}, + {a: [2, 0], b: [2, 1]}, + {a: [2, 0], b: [3, 0]}, + {a: [2, 1], b: [2, 2]}, + {a: [2, 3], b: [3, 3]}, + {a: [2, 5], b: [2, 6]}, + {a: [2, 6], b: [3, 6]}, + {a: [2, 7], b: [3, 7]}, + {a: [3, 0], b: [3, 1]}, + {a: [3, 1], b: [3, 2]}, + {a: [3, 2], b: [4, 2]}, + {a: [3, 3], b: [3, 4]}, + {a: [3, 4], b: [4, 4]}, + {a: [3, 5], b: [3, 6]}, + {a: [3, 5], b: [4, 5]}, + {a: [3, 7], b: [4, 7]}, + {a: [4, 0], b: [4, 1]}, + {a: [4, 0], b: [5, 0]}, + {a: [4, 1], b: [5, 1]}, + {a: [4, 2], b: [4, 3]}, + {a: [4, 3], b: [5, 3]}, + {a: [4, 4], b: [4, 5]}, + {a: [4, 6], b: [4, 7]}, + {a: [4, 6], b: [5, 6]}, + {a: [5, 0], b: [6, 0]}, + {a: [5, 1], b: [5, 2]}, + {a: [5, 2], b: [6, 2]}, + {a: [5, 3], b: [5, 4]}, + {a: [5, 4], b: [6, 4]}, + {a: [5, 5], b: [5, 6]}, + {a: [5, 5], b: [6, 5]}, + {a: [6, 0], b: [6, 1]}, + {a: [6, 1], b: [7, 1]}, + {a: [6, 2], b: [6, 3]}, + {a: [6, 3], b: [6, 4]}, + {a: [6, 5], b: [6, 6]}, + {a: [6, 6], b: [6, 7]}, + {a: [6, 7], b: [7, 7]}, + {a: [7, 1], b: [7, 2]}, + {a: [7, 2], b: [7, 3]}, + {a: [7, 3], b: [7, 4]}, + {a: [7, 4], b: [7, 5]}, + {a: [7, 5], b: [7, 6]}, + {a: [7, 6], b: [7, 7]}, + ], + }, +]; diff --git a/src/index.css b/src/index.css index ec2585e..8684abe 100644 --- a/src/index.css +++ b/src/index.css @@ -11,3 +11,9 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +body, html, #root { + height: 100%; + width: 100%; + box-sizing: border-box; +} diff --git a/src/index.js b/src/index.js index ef2edf8..26419cd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import * as React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; ReactDOM.render( , - document.getElementById('root') + document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js index 5253d3a..81d1046 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.js @@ -1,6 +1,6 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/src/setupTests.js b/src/setupTests.js index 8f2609b..1dd407a 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom"; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..d4c44d1 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,105 @@ +import {EDGE_STATE} from "./constants"; + +// to get multiple styles in space-seperated format +export const multiStyles = (styles, classNames) => + classNames + .reduce( + (classNames, className) => [ + ...classNames, + ...(!className ? [] : [styles?.[className] || className]), + ], + [] + ) + .join(" "); + +// to check if number is valid number +export const isValid = (n, lim) => n >= 0 && n <= lim; + +// to check all elements are valid numbers +export const areValid = (n, lim) => n.every((n) => isValid(n, lim)); + +// to check if a node has an active edge +export const isNodeActive = (n) => + Array.isArray(n?.neigh) && + n.neigh.some((neighbor) => neighbor.state === EDGE_STATE.ACTIVE); + +// to get neighbors from matrix +export const getNeighbors = ([ax, ay], mat) => { + return mat[ax][ay]?.neigh || []; +}; + +// to check if two nodes are neighbor in a matrix +export const findNeighbor = ([ax, ay], [bx, by], mat) => { + const index = mat?.[ax]?.[ay]?.neigh.findIndex( + ({loc}) => loc[0] === bx && loc[1] === by + ); + return isNaN(index) ? -1 : index; +}; + +// get an array of all numbers present in the matrix +export const getNumbers = (mat) => { + let numbers = []; + let nRow = mat?.length; + let nCol = mat?.[0]?.length; + if (!nRow || !nCol) { + return; + } + + mat.forEach((row, r) => { + if (r >= nRow - 1) return; + row.forEach((node, c) => { + if (c >= nCol - 1) return; + numbers = [ + ...numbers, + ...(node.n > -1 ? [{r: r + 1, c: c + 1, n: node.n}] : []), + ]; + }); + }); + return numbers; +}; + +// get an array of all edges present in the matrix +export const getEdges = (mat) => { + let edges = []; + mat.forEach((row, sX) => { + row.forEach((node, sY) => { + let horEdges = []; + let verEdges = []; + node.neigh.forEach((edge) => { + const [eX, eY] = edge.loc; + if (eX > sX && eY === sY) { + verEdges = [ + ...verEdges, + { + a: [sX, sY], + b: [eX, eY], + ...(edge?.state === EDGE_STATE.NOT_ALLOWED + ? {notAllowed: true} + : {}), + ...(edge?.state === EDGE_STATE.HOVERED ? {hovered: true} : {}), + }, + ]; + } else if (eY > sY && eX === sX) { + horEdges = [ + ...horEdges, + { + a: [sX, sY], + b: [eX, eY], + ...(edge?.state === EDGE_STATE.NOT_ALLOWED + ? {notAllowed: true} + : {}), + ...(edge?.state === EDGE_STATE.HOVERED ? {hovered: true} : {}), + }, + ]; + } + }); + edges = [...edges, ...horEdges, ...verEdges]; + }); + }); + return edges; +}; + +// get an array of all active edges present in the matrix +export const getActiveEdges = (mat) => { + return getEdges(mat).filter((edge) => !edge?.notAllowed && !edge?.hovered); +};