diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..7fe0373 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +echo "pre-commit..." +npm run lint \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..85dadca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.formatOnSave": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, +} \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..dd78a04 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,20 @@ +import js from '@eslint/js' +import globals from 'globals' +import { defineConfig, globalIgnores } from 'eslint/config' +import stylistic from '@stylistic/eslint-plugin' + +export default defineConfig([ + { + files: ['**/*.{js,mjs,cjs}'], + plugins: { js, '@stylistic': stylistic}, + extends: ['js/recommended'], + languageOptions: { globals: globals.browser }, + rules: { + '@stylistic/indent': ['error', 2], + '@stylistic/quotes': ['error', 'single'], + '@stylistic/semi': ['error', 'never'], + '@stylistic/comma-dangle': ['error', 'never'] + } + }, + globalIgnores(['dist']) +]) diff --git a/package.json b/package.json index 5c7b2b1..9455b75 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,19 @@ "build": "vite build", "preview": "vite preview", "build:preview": "npm run build && npm run preview", - "prepare": "husky" + "prepare": "husky", + "lint": "eslint .", + "lint:fix": "eslint . --fix" }, "license": "BSD-2-Clause", "dependencies": { "firebase": "^12.11.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.10.0", + "eslint": "^10.1.0", + "globals": "^17.4.0", "husky": "^9.1.7", "vite": "^8.0.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fb3720..34cb5b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,6 +12,18 @@ importers: specifier: ^12.11.0 version: 12.11.0 devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.1.0) + '@stylistic/eslint-plugin': + specifier: ^5.10.0 + version: 5.10.0(eslint@10.1.0) + eslint: + specifier: ^10.1.0 + version: 10.1.0 + globals: + specifier: ^17.4.0 + version: 17.4.0 husky: specifier: ^9.1.7 version: 9.1.7 @@ -30,6 +42,45 @@ packages: '@emnapi/wasi-threads@1.2.0': resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.3': + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.3': + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.1.1': + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.3': + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.1': + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@firebase/ai@2.10.0': resolution: {integrity: sha512-1lI6HomyoO/8RSJb6ItyHLpHnB2z27m5F4aX/Vpi1nhwWoxdNjkq+6UQOykHyCE0KairojOE5qQ20i1tnF0nNA==} engines: {node: '>=20.0.0'} @@ -249,6 +300,22 @@ packages: engines: {node: '>=6'} hasBin: true + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -396,17 +463,44 @@ packages: '@rolldown/pluginutils@1.0.0-rc.12': resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@stylistic/eslint-plugin@5.10.0': + resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.0.0 || ^10.0.0 + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@typescript-eslint/types@8.58.0': + resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -415,6 +509,14 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -432,6 +534,22 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -443,6 +561,69 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.1.0: + resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + faye-websocket@0.11.4: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} engines: {node: '>=0.8.0'} @@ -456,9 +637,24 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + firebase@12.11.0: resolution: {integrity: sha512-W9f3Y+cgQYgF9gvCGxt0upec8zwAtiQVcHuU8MfzUIgVU/9fRQWtu48Geiv1lsigtBz9QHML++Km9xAKO5GB5Q==} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -468,6 +664,14 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.4.0: + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + engines: {node: '>=18'} + http-parser-js@0.5.10: resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} @@ -479,10 +683,45 @@ packages: idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -553,17 +792,51 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -575,10 +848,18 @@ packages: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + protobufjs@7.5.4: resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} engines: {node: '>=12.0.0'} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -591,6 +872,14 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -622,9 +911,16 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + vite@8.0.3: resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -679,6 +975,15 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -695,6 +1000,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: '@emnapi/core@1.9.1': @@ -713,6 +1022,40 @@ snapshots: tslib: 2.8.1 optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0)': + dependencies: + eslint: 10.1.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.3': + dependencies: + '@eslint/object-schema': 3.0.3 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.3': + dependencies: + '@eslint/core': 1.1.1 + + '@eslint/core@1.1.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.1.0)': + optionalDependencies: + eslint: 10.1.0 + + '@eslint/object-schema@3.0.3': {} + + '@eslint/plugin-kit@0.6.1': + dependencies: + '@eslint/core': 1.1.1 + levn: 0.4.1 + '@firebase/ai@2.10.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.10)': dependencies: '@firebase/app': 0.14.10 @@ -1043,6 +1386,17 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1151,17 +1505,45 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.12': {} + '@stylistic/eslint-plugin@5.10.0(eslint@10.1.0)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0) + '@typescript-eslint/types': 8.58.0 + eslint: 10.1.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + estraverse: 5.3.0 + picomatch: 4.0.4 + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + '@types/node@25.5.0': dependencies: undici-types: 7.18.2 - acorn@8.16.0: - optional: true + '@typescript-eslint/types@8.58.0': {} + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 ansi-regex@5.0.1: {} @@ -1169,6 +1551,12 @@ snapshots: dependencies: color-convert: 2.0.1 + balanced-match@4.0.4: {} + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + buffer-from@1.1.2: optional: true @@ -1187,12 +1575,104 @@ snapshots: commander@2.20.3: optional: true + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + detect-libc@2.1.2: {} emoji-regex@8.0.0: {} escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.1.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.3 + '@eslint/config-helpers': 0.5.3 + '@eslint/core': 1.1.1 + '@eslint/plugin-kit': 0.6.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + faye-websocket@0.11.4: dependencies: websocket-driver: 0.7.4 @@ -1201,6 +1681,15 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + firebase@12.11.0: dependencies: '@firebase/ai': 2.10.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.10) @@ -1234,19 +1723,59 @@ snapshots: transitivePeerDependencies: - '@react-native-async-storage/async-storage' + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + fsevents@2.3.3: optional: true get-caller-file@2.0.5: {} + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.4.0: {} + http-parser-js@0.5.10: {} husky@9.1.7: {} idb@7.1.1: {} + ignore@5.3.2: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: optional: true @@ -1296,12 +1825,45 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} long@5.3.2: {} + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + ms@2.1.3: {} + nanoid@3.3.11: {} + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -1312,6 +1874,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + protobufjs@7.5.4: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -1327,6 +1891,8 @@ snapshots: '@types/node': 25.5.0 long: 5.3.2 + punycode@2.3.1: {} + require-directory@2.1.1: {} rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1): @@ -1355,6 +1921,12 @@ snapshots: safe-buffer@5.2.1: {} + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -1391,8 +1963,16 @@ snapshots: tslib@2.8.1: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + undici-types@7.18.2: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(terser@5.46.0): dependencies: lightningcss: 1.32.0 @@ -1418,6 +1998,12 @@ snapshots: websocket-extensions@0.1.4: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -1437,3 +2023,5 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/src/levels.js b/src/levels.js index 5b55add..647e547 100644 --- a/src/levels.js +++ b/src/levels.js @@ -1,27 +1,27 @@ export const levels = { - beginner: { - rows: 9, - cols: 9, - mines: 10, - name: 'beginner' - }, - intermediate: { - rows: 16, - cols: 16, - mines: 40, - name: 'intermediate' - }, - expert: { - rows: 16, - cols: 30, - mines: 99, - name: 'expert' - }, - nightmare: { - rows: 20, - cols: 30, - mines: 150, - name: 'nightmare' - } + beginner: { + rows: 9, + cols: 9, + mines: 10, + name: 'beginner' + }, + intermediate: { + rows: 16, + cols: 16, + mines: 40, + name: 'intermediate' + }, + expert: { + rows: 16, + cols: 30, + mines: 99, + name: 'expert' + }, + nightmare: { + rows: 20, + cols: 30, + mines: 150, + name: 'nightmare' + } } diff --git a/src/main.js b/src/main.js index 2fd0799..c185e0e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,7 @@ -import './index.css'; -import './modules/loading/loading.css'; -import '../public/favicon.ico'; -import Minesweeper from './minesweeper.js'; +import './index.css' +import './modules/loading/loading.css' +import '../public/favicon.ico' +import Minesweeper from './minesweeper.js' -const myMinesweeper = new Minesweeper('app'); -myMinesweeper.initialize(); +const myMinesweeper = new Minesweeper('app') +myMinesweeper.initialize() diff --git a/src/minesweeper.js b/src/minesweeper.js index c23f798..59a180c 100644 --- a/src/minesweeper.js +++ b/src/minesweeper.js @@ -1,897 +1,900 @@ // @ts-check import { - LeaderBoardService, - LoggerService, - StorageService, - TimerService -} from './modules'; -import { levels } from './levels.js'; + LeaderBoardService, + LoggerService, + StorageService, + TimerService +} from './modules' +import { levels } from './levels.js' import * as pkg from '../package.json' -const TEST_MODE = false; // set to true if you want to test the game with visual hints and separate leaderboard -const VERSION = pkg.version; -const MOBILE_BUSY_DELAY = 250; -const PC_BUSY_DELAY = 500; -const CASUAL_MODE = false; +const TEST_MODE = false // set to true if you want to test the game with visual hints and separate leaderboard +const VERSION = pkg.version +const MOBILE_BUSY_DELAY = 250 +const PC_BUSY_DELAY = 500 +const CASUAL_MODE = false /** * Create Minesweeper game board * @param {String} appId */ export const Minesweeper = function(appId) { - const _this = this; - const storageService = new StorageService(); - const timerService = new TimerService(); - const loggerService = new LoggerService(); - const leaderBoard = new LeaderBoardService(); + const _this = this + const storageService = new StorageService() + const timerService = new TimerService() + const loggerService = new LoggerService() + const leaderBoard = new LeaderBoardService() - let grid = document.createElement('table'); - grid.setAttribute('id', 'grid'); - let flagsDisplay = document.createElement('span'); - let smileyDisplay = document.createElement('span'); - let timerDisplay = document.createElement('span'); - let footbar = document.createElement('div'); - let customWrapper = document.createElement('div'); - customWrapper.setAttribute('id', 'custom-wrapper'); - let appElement = document.getElementById(appId); - if (!appElement) { - const body = document.getElementsByTagName('body')[0]; - appElement = document.createElement('div'); - body.append(appElement); + let grid = document.createElement('table') + grid.setAttribute('id', 'grid') + let flagsDisplay = document.createElement('span') + let smileyDisplay = document.createElement('span') + let timerDisplay = document.createElement('span') + let footbar = document.createElement('div') + let customWrapper = document.createElement('div') + customWrapper.setAttribute('id', 'custom-wrapper') + let appElement = document.getElementById(appId) + if (!appElement) { + const body = document.getElementsByTagName('body')[0] + appElement = document.createElement('div') + body.append(appElement) + } + let leaderWrapper = document.createElement('div') + + let isMobile = false + let isLeft = false + let isRight = false + let pressed = undefined + let bothPressed = undefined + let skip = false + let skipCondition = false + let mouseUpCallBackArray = [ + clickCell, + middleClickCell + ] + let mouseDownCallBackArray = [ + highlightCell, // left-click down + highlightSurroundingCell, // middle-click down + rightClickCell // right-click down + ] + let firstClick = true + let isBusy = false + let clickedCell + let cachedSetting = storageService.getFromLocal('setting') + let setting = cachedSetting || levels.beginner + if (TEST_MODE) { + setting = { + rows: 10, + cols: 10, + mines: 10, + name: 'test' } - let leaderWrapper = document.createElement('div'); + } + storageService.saveToLocal('setting', setting) + let flagsCount = setting.mines + let minesArray = [] + + this.initialize = function() { + const headingElement = document.createElement('h1') + const gameBoard = document.createElement('div') + + headingElement.innerText = `Minesweeper v${VERSION}` + gameBoard.setAttribute('id', 'game-board') + gameBoard.append(initializeToolbar(), grid, initializeFootbar()) + if(appElement) { + appElement.innerHTML = '' + appElement.append(headingElement, gameBoard) + appElement.append(initializeSourceLink()) + } + generateGrid() + } + + function initializeSourceLink() { + const sourceLink = document.createElement('a') + sourceLink.href = 'https://github.com/ayoayco/mnswpr' + sourceLink.innerText = 'Source code' + sourceLink.target = '_blank' + sourceLink.style.color = 'white' + + return sourceLink + } + + function initializeLeaderBoard() { + const title = `Best Times (${setting.name})` + leaderBoard.update(setting.name, leaderWrapper, title) + + if(appElement) + appElement.append(leaderWrapper) + } + + function initializeFootbar() { + const footBar = document.createElement('div') + + const resetButton = document.createElement('button') + resetButton.innerText = 'Reset' + resetButton.onmousedown = () => generateGrid() + footBar.append(resetButton) + + let levelsDropdown = document.createElement('select') + levelsDropdown.onchange = () => updateSetting(levelsDropdown.value) + + const levelsKeys = Object.keys(levels) + levelsKeys.forEach(key => { + const levelOption = document.createElement('option') + levelOption.value = levels[key].name + levelOption.text = capitalize(levels[key].name) + if (setting.name === levelOption.value) { + levelOption.selected = true + } + levelsDropdown.add(levelOption, null) + }) + + // custom level + // const customOption = document.createElement('option'); + // customOption.onmousedown = () => {} + // customOption.value = 'custom'; + // customOption.text = 'Custom'; + // levelsDropdown.add(customOption); - let isMobile = false; - let isLeft = false; - let isRight = false; - let pressed = undefined; - let bothPressed = undefined; - let skip = false; - let skipCondition = false; - let mouseUpCallBackArray = [ - clickCell, - middleClickCell, - ]; - let mouseDownCallBackArray = [ - highlightCell, // left-click down - highlightSurroundingCell, // middle-click down - rightClickCell // right-click down - ] - let firstClick = true; - let isBusy = false; - let clickedCell; - let cachedSetting = storageService.getFromLocal('setting'); - let setting = cachedSetting || levels.beginner; if (TEST_MODE) { - setting = { - rows: 10, - cols: 10, - mines: 10, - name: 'test' - } - } - storageService.saveToLocal('setting', setting); - let flagsCount = setting.mines; - let minesArray = []; - - this.initialize = function() { - const headingElement = document.createElement('h1'); - const gameBoard = document.createElement('div'); - - headingElement.innerText = `Minesweeper v${VERSION}`; - gameBoard.setAttribute('id', 'game-board'); - gameBoard.append(initializeToolbar(), grid, initializeFootbar()); - if(appElement) { - appElement.innerHTML = ''; - appElement.append(headingElement, gameBoard); - appElement.append(initializeSourceLink()); - } - generateGrid(); + const testLevel = document.createElement('span') + testLevel.innerText = 'Test Mode' + footBar.append(testLevel) + } else { + footBar.append(levelsDropdown) } - function initializeSourceLink() { - const sourceLink = document.createElement('a'); - sourceLink.href = 'https://github.com/ayoayco/mnswpr'; - sourceLink.innerText = 'Source code'; - sourceLink.target = '_blank'; - sourceLink.style.color = 'white'; + return footBar + } - return sourceLink; + function removeCustomOptions() { + const customCopy = document.getElementById('custom-wrapper') + if (customCopy) { + footbar.removeChild(customWrapper) } + } - function initializeLeaderBoard() { - const title = `Best Times (${setting.name})`; - leaderBoard.update(setting.name, leaderWrapper, title); - - if(appElement) - appElement.append(leaderWrapper); - } - - function initializeFootbar() { - const footBar = document.createElement('div'); - - const resetButton = document.createElement('button'); - resetButton.innerText = 'Reset'; - resetButton.onmousedown = () => generateGrid(); - footBar.append(resetButton); - - let levelsDropdown = document.createElement('select'); - levelsDropdown.onchange = () => updateSetting(levelsDropdown.value); - - const levelsKeys = Object.keys(levels); - levelsKeys.forEach(key => { - const levelOption = document.createElement('option'); - levelOption.value = levels[key].name; - levelOption.text = capitalize(levels[key].name); - if (setting.name === levelOption.value) { - levelOption.selected = true; - } - levelsDropdown.add(levelOption, null); - }); - - // custom level - // const customOption = document.createElement('option'); - // customOption.onmousedown = () => {} - // customOption.value = 'custom'; - // customOption.text = 'Custom'; - // levelsDropdown.add(customOption); - - if (TEST_MODE) { - const testLevel = document.createElement('span'); - testLevel.innerText = 'Test Mode'; - footBar.append(testLevel); - } else { - footBar.append(levelsDropdown); - } - - return footBar; - } - - function removeCustomOptions() { - const customCopy = document.getElementById('custom-wrapper'); - if (customCopy) { - footbar.removeChild(customWrapper); - } - } - - function insertCustomOptions() { + function insertCustomOptions() { - const inputElements = []; + const inputElements = [] - const rowsInput = document.createElement('input'); - rowsInput.placeholder = 'Rows'; - inputElements.push(rowsInput); + const rowsInput = document.createElement('input') + rowsInput.placeholder = 'Rows' + inputElements.push(rowsInput) - const colsInput = document.createElement('input'); - colsInput.placeholder = 'Columns'; - inputElements.push(colsInput); + const colsInput = document.createElement('input') + colsInput.placeholder = 'Columns' + inputElements.push(colsInput) - const bombsInput = document.createElement('input'); - bombsInput.placeholder = 'Bombs'; - inputElements.push(bombsInput); + const bombsInput = document.createElement('input') + bombsInput.placeholder = 'Bombs' + inputElements.push(bombsInput) - const okButton = document.createElement('button'); - okButton.innerText = 'Okay'; - const setting = {rows: rowsInput.value, cols: colsInput.value, bombs: bombsInput.value}; - okButton.onmousedown = () => updateSetting('custom-action', setting); + const okButton = document.createElement('button') + okButton.innerText = 'Okay' + const setting = {rows: rowsInput.value, cols: colsInput.value, bombs: bombsInput.value} + okButton.onmousedown = () => updateSetting('custom-action', setting) - inputElements.forEach(input => { - input.style.marginRight = '15px'; - input.style.width = '100px'; - input.maxLength = 3; - input.type = 'number'; - input.width = 50; - }); + inputElements.forEach(input => { + input.style.marginRight = '15px' + input.style.width = '100px' + input.maxLength = 3 + input.type = 'number' + input.width = 50 + }) - customWrapper.append(...inputElements, okButton); - footbar.append(customWrapper); + customWrapper.append(...inputElements, okButton) + footbar.append(customWrapper) + } + + function capitalize(str) { + if (!str) return '' + return `${str[0].toUpperCase()}${str.slice(1, str.length)}` + } + + function initializeToolbar() { + const toolbar = document.createElement('div') + const toolbarItems = [] + + const flagsWrapper = document.createElement('div') + flagsWrapper.append(flagsDisplay) + flagsWrapper.style.height = '20px' + toolbar.append(flagsWrapper) + toolbarItems.push(flagsWrapper) + + const smileyWrapper = document.createElement('div') + smileyWrapper.append(smileyDisplay) + // toolbar.append(smileyWrapper); + // toolbarItems.push(smileyWrapper); + + const timerWrapper = document.createElement('div') + timerWrapper.append(timerDisplay) + timerWrapper.style.height = '20px' + toolbar.append(timerWrapper) + toolbarItems.push(timerWrapper) + + toolbar.style.cursor = 'pointer' + toolbar.style.padding = '10px 35px' + toolbar.style.display = 'flex' + toolbar.style.justifyContent = 'space-between' + toolbar.onmousedown = () => generateGrid() + + return toolbar + } + + function updateSetting(key, custom) { + if (key === 'custom') { + insertCustomOptions() + } else if (key === 'custom-action') { + console.log('custom', custom) + } else { + setting = levels[key] + storageService.saveToLocal('setting', setting) + removeCustomOptions() + generateGrid() + } + } + + + function generateGrid() { + + //generate 10 by 10 grid + firstClick = true + grid.innerHTML = '' + grid.oncontextmenu = () => false + flagsCount = setting.mines + minesArray = [] + + for (let i = 0; i < setting.rows; i++) { + let row = grid.insertRow(i) + row.oncontextmenu = () => false + for (let j=0; j generateGrid(); - - return toolbar; + if (!CASUAL_MODE) { + initializeLeaderBoard() } - function updateSetting(key, custom) { - if (key === 'custom') { - insertCustomOptions(); - } else if (key === 'custom-action') { - console.log('custom', custom); + timerService.initialize(timerDisplay) + updateFlagsCountDisplay() + addMines(setting.mines) + + } + + function setBusy() { + isBusy = true + if (isMobile) { + setTimeout(() => isBusy = false, MOBILE_BUSY_DELAY) + } else { + setTimeout(() => isBusy = false, PC_BUSY_DELAY) + } + } + + function updateFlagsCountDisplay(count = flagsCount) { + if (grid.getAttribute('game-status') != 'win') { + flagsDisplay.innerHTML = `${count}` + return + } + flagsDisplay.innerHTML = '😁' + } + + /** + * + * @param {HTMLTableCellElement} cell + */ + function initializeTouchEventHandlers(cell) { + let ontouchleave = function() { + if (clickedCell === this) { + clickedCell = undefined + } + } + cell.addEventListener('touchleave', ontouchleave) + + let ontouchend = function() { + endTouchTimer() + } + cell.addEventListener('touchend', ontouchend) + + let ontouchstart = function(e) { + isMobile = true + if (!isBusy && typeof e === 'object') { + startTouchTimer(this) + } + } + cell.addEventListener('touchstart', ontouchstart) + + } + + function initializeEventHandlers(_cell) { + + let cell = _cell + skip = false + skipCondition = false + + resetMouseEventFlags() + + document.onkeydown = function(e) { + if (e.keyCode == 32 || e.keyCode == 113) { + generateGrid() + if ('preventDefault' in e) { + e.preventDefault() } else { - setting = levels[key]; - storageService.saveToLocal('setting', setting); - removeCustomOptions(); - generateGrid(); + return false } + } + resetMouseEventFlags() + } + + window.onblur = function() { + resetMouseEventFlags() + } + + grid.onmouseleave = function() { + removeHighlights() + } + document.oncontextmenu = () => false + document.onmouseup = function() { + resetMouseEventFlags() + } + document.onmousedown = function(e) { + isMobile = false + switch (e.button) { + case 0: pressed = 'left'; isLeft = true; break + case 1: pressed = 'middle'; break + case 2: isRight = true; break + } + } + + // Set grid status to active on first click + cell.onmouseup = function(e) { + pressed = undefined + let dont = false + + if (bothPressed) { + bothPressed = false + if (e.button == '2') { + skipCondition = true + } else if (e.button == '0') { + dont = true + } + if (getStatus(this) == 'clicked') { + middleClickCell(this) + return + } + } + switch(e.button) { + case 0: { + isLeft = false + if (skipCondition) { + skip = true + } + break + } + case 2: isRight = false; break + } + removeHighlights() + if (skip || dont) { + skip = false + skipCondition = false + return + } + if (!isBusy && typeof e === 'object' && e.button != 2) { + mouseUpCallBackArray[e.button].call(_this, this) + } } - function generateGrid() { - - //generate 10 by 10 grid - firstClick = true; - grid.innerHTML = ''; - grid.oncontextmenu = () => false; - flagsCount = setting.mines; - minesArray = []; - - for (let i = 0; i < setting.rows; i++) { - let row = grid.insertRow(i); - row.oncontextmenu = () => false; - for (let j=0; j isBusy = false, MOBILE_BUSY_DELAY); - } else { - setTimeout(() => isBusy = false, PC_BUSY_DELAY); - } - } - - function updateFlagsCountDisplay(count = flagsCount) { - if (grid.getAttribute('game-status') != 'win') { - flagsDisplay.innerHTML = `${count}`; - return; - } - flagsDisplay.innerHTML = '😁'; - } - - function initializeTouchEventHandlers(_cell) { - let cell = document.createElement('td'); - cell = _cell; - - let ontouchleave = function(e) { - if (clickedCell === this) { - clickedCell = undefined - } - } - cell.addEventListener('touchleave', ontouchleave); - - let ontouchend = function(e) { - endTouchTimer(); - } - cell.addEventListener('touchend', ontouchend); - - let ontouchstart = function(e) { - isMobile = true; - if (!isBusy && typeof e === 'object') { - startTouchTimer(this); - } - } - cell.addEventListener('touchstart', ontouchstart); - - } - - function initializeEventHandlers(_cell) { - - let cell = _cell; - skip = false; - skipCondition = false; - - resetMouseEventFlags(); - - document.onkeydown = function(e) { - if (e.keyCode == 32 || e.keyCode == 113) { - generateGrid(); - if ('preventDefault' in e) { - e.preventDefault(); - } else { - return false; - } - } - resetMouseEventFlags(); - } - - window.onblur = function() { - resetMouseEventFlags(); - } - - grid.onmouseleave = function() { - removeHighlights(); - } - document.oncontextmenu = () => false; - document.onmouseup = function() { - resetMouseEventFlags(); - } - document.onmousedown = function(e) { - isMobile = false; - switch (e.button) { - case 0: pressed = 'left'; isLeft = true; break; - case 1: pressed = 'middle'; break; - case 2: isRight = true; break; - } - } - - // Set grid status to active on first click - cell.onmouseup = function(e) { - pressed = undefined; - let dont = false; - - if (bothPressed) { - bothPressed = false; - if (e.button == '2') { - skipCondition = true; - } else if (e.button == '0') { - dont = true; - } - if (getStatus(this) == 'clicked') { - middleClickCell(this); - return; - } - } - switch(e.button) { - case 0: { - isLeft = false; - if (skipCondition) { - skip = true; - } - break; - } - case 2: isRight = false; break - } - removeHighlights(); - if (skip || dont) { - skip = false; - skipCondition = false; - return; - } - if (!isBusy && typeof e === 'object' && e.button != 2) { - mouseUpCallBackArray[e.button].call(_this, this); - } - } - - - cell.onmousedown = function(e) { - skip = false; - if (!isBusy && typeof e === 'object') { - switch(e.button) { - case 0: isLeft = true; break; - case 2: isRight = true; break - } - - if (isLeft && isRight) { - bothPressed = true; - highlightSurroundingCell(this); - return; - } - - if (e.button == '1') { - pressed = 'middle'; - highlightSurroundingCell(this); - } else if (e.button == '0') { - pressed = 'left'; - if (getStatus(this) == 'clicked') { - highlightSurroundingCell(this); - } else { - highlightCell(this); - } - } - - if (e.button == '2') mouseDownCallBackArray[e.button].call(_this, this); - } - } - - cell.onmousemove = function(e) { - if ((pressed || bothPressed) && typeof e === 'object') { - removeHighlights(); - /* + cell.onmousemove = function(e) { + if ((pressed || bothPressed) && typeof e === 'object') { + removeHighlights() + /* if (!isEqual(clickedCell, cell)) { clickedCell = undefined; } */ - if (pressed == 'middle' || (isLeft && isRight)) { - highlightSurroundingCell(this); - } else if (pressed == 'left') { - if (getStatus(this) == 'clicked') { - highlightSurroundingCell(this); - } else { - highlightCell(this); - } - } - } + if (pressed == 'middle' || (isLeft && isRight)) { + highlightSurroundingCell(this) + } else if (pressed == 'left') { + if (getStatus(this) == 'clicked') { + highlightSurroundingCell(this) + } else { + highlightCell(this) + } } - - cell.oncontextmenu = () => false; - cell.onselectstart = () => false; - cell.setAttribute('unselectable', 'on'); + } } - function isEqual(x, y) { - if (!x) return false; - return x === y; + cell.oncontextmenu = () => false + cell.onselectstart = () => false + cell.setAttribute('unselectable', 'on') + } + + function isEqual(x, y) { + if (!x) return false + return x === y + } + + function startTouchTimer(cell) { + if (isEqual(clickedCell, cell)) { + return } + clickedCell = cell + setTimeout(() => { + if (isEqual(clickedCell, cell)) { + rightClickCell(cell) + setBusy() + } + }, 500) + } - function startTouchTimer(cell) { - if (isEqual(clickedCell, cell)) { - return; - } - clickedCell = cell; - setTimeout(() => { - if (isEqual(clickedCell, cell)) { - rightClickCell(cell); - setBusy(); - } - }, 500); + function endTouchTimer() { + clickedCell = undefined + } + + function resetMouseEventFlags() { + pressed = undefined + bothPressed = undefined + isLeft = false + isRight = false + removeHighlights() + skip = true + } + + function addMines(minesCount) { + //Add mines randomly + for (let i=0; i -1 + } + + function removeItem(arr, cell) { + const index = getIndex(arr, cell) + if (index > -1) { + arr.splice(index, 1) + } + } + + function getIndex(arr, cell) { + const row = cell.parentNode.rowIndex + const col = cell.cellIndex + let index = -1 + for (let i = 0; i < arr.length; i++) { + let rowCol = arr[i] + if (rowCol[0] === row && rowCol[1] === col) { + index = i + break + } } - function isMine(cell) { - return getIndex(minesArray, cell) > -1; + return index + } + + function checkLevelCompletion() { + let levelComplete = true + for (let i=0; i -1) { - arr.splice(index, 1); + openCell(cell) + } + + function printMines() { + let count = 0 + for (let i = 0; i < setting.rows; i++) { + for (let j = 0; j < setting.cols; j++) { + if (isMine(grid.rows[i].cells[j])) { + loggerService.debug(count++ + ' - mine: [' + i + ',' + j + ']') } + } } + } - function getIndex(arr, cell) { - const row = cell.parentNode.rowIndex; - const col = cell.cellIndex; - let index = -1; - for (let i = 0; i < arr.length; i++) { - let rowCol = arr[i] - if (rowCol[0] === row && rowCol[1] === col) { - index = i; - break; - } + function transferMine(cell = undefined) { + let found = false + do { + let row = Math.floor(Math.random() * setting.rows) + let col = Math.floor(Math.random() * setting.cols) + const transferMineToCell = grid.rows[row].cells[col] + if (isMine(transferMineToCell) || isNeighbor(cell, transferMineToCell)) { + continue + } else { + minesArray.push([row, col]) + if (TEST_MODE){ + transferMineToCell.innerHTML = 'X' + if (TEST_MODE) loggerService.debug('transferred mine to: ' + row + ', ' + col) } + // TODO: refactor maybe + // eslint-disable-next-line no-useless-assignment + found = true + return + } + } while(!found) + } - return index; + function isNeighbor(cell, nextCell) { + if (cell === undefined) { + return } + const rowDifference = Math.abs(getRow(cell) - getRow(nextCell)) + const colDifference = Math.abs(getCol(cell) - getCol(nextCell)) - function checkLevelCompletion() { - let levelComplete = true; - for (let i=0; i { - this.configuration = res.data() - }) + const configRef = doc(this.store, 'mw-config', 'configuration') + getDoc(configRef) + .then(res => { + this.configuration = res.data() + }) + } + + get store() { + return this._store + } + + async update(level, displayElement, title) { + + if (level !== this.previousLevel) { + this.loadingService.addLoading(displayElement) + this.previousLevel = level + this.lastPlace = Number.MAX_SAFE_INTEGER + + const q = query( + collection(this.store, 'mw-leaders', level, 'games'), + orderBy('time'), + limit(10) + ) + this.topListSnapshot = await getDocs(q) + this.renderList(displayElement, title, this.topListSnapshot.docs) } - get store() { - return this._store; - } + } - async update(level, displayElement, title) { + renderList(displayElement, title, docs) { + if (!displayElement) return - if (level !== this.previousLevel) { - this.loadingService.addLoading(displayElement); - this.previousLevel = level; - this.lastPlace = Number.MAX_SAFE_INTEGER; - - const q = query( - collection(this.store, "mw-leaders", level, 'games'), - orderBy('time'), - limit(10) - ); - this.topListSnapshot = await getDocs(q) - this.renderList(displayElement, title, this.topListSnapshot.docs) - } - - } - - renderList(displayElement, title, docs) { - if (!displayElement) return; - - displayElement.innerHTML = ''; - const leaderHeading = document.createElement('h3'); - leaderHeading.innerText = title; - leaderHeading.style.borderBottom = '1px solid #c0c0c0'; - leaderHeading.style.paddingBottom = '10px'; + displayElement.innerHTML = '' + const leaderHeading = document.createElement('h3') + leaderHeading.innerText = title + leaderHeading.style.borderBottom = '1px solid #c0c0c0' + leaderHeading.style.paddingBottom = '10px' - displayElement.style.maxWidth = '270px'; - displayElement.style.margin = '0 auto'; + displayElement.style.maxWidth = '270px' + displayElement.style.margin = '0 auto' - const leaderList = document.createElement('div'); + const leaderList = document.createElement('div') - leaderList.innerHTML = ''; - leaderList.style.listStyle = 'none'; - leaderList.style.textAlign = 'left'; - leaderList.style.marginTop = '-15px'; + leaderList.innerHTML = '' + leaderList.style.listStyle = 'none' + leaderList.style.textAlign = 'left' + leaderList.style.marginTop = '-15px' - if (docs && docs.length) { - let i = 1; - docs.forEach(game => { - if (game) { - const prettyTime = this.timerService.pretty(game.data().time); - const name = game.data().name || 'Anonymous'; - const item = document.createElement('div'); - item.style.display = 'flex'; - const nameElement =document.createElement('div'); - nameElement.innerHTML = name; - nameElement.setAttribute('title', name); - nameElement.style.textOverflow = 'ellipsis'; - nameElement.style.whiteSpace = 'nowrap'; - nameElement.style.overflow = 'hidden'; - nameElement.style.padding = '0 5px'; - nameElement.style.cursor = 'pointer'; - nameElement.style.fontWeight = 'bold'; - nameElement.style.fontStyle = 'italic'; - // nameElement.onmousedown = () => console.log(game.data()); + if (docs && docs.length) { + let i = 1 + docs.forEach(game => { + if (game) { + const prettyTime = this.timerService.pretty(game.data().time) + const name = game.data().name || 'Anonymous' + const item = document.createElement('div') + item.style.display = 'flex' + const nameElement =document.createElement('div') + nameElement.innerHTML = name + nameElement.setAttribute('title', name) + nameElement.style.textOverflow = 'ellipsis' + nameElement.style.whiteSpace = 'nowrap' + nameElement.style.overflow = 'hidden' + nameElement.style.padding = '0 5px' + nameElement.style.cursor = 'pointer' + nameElement.style.fontWeight = 'bold' + nameElement.style.fontStyle = 'italic' + // nameElement.onmousedown = () => console.log(game.data()); - const indexElement = document.createElement('div'); - indexElement.innerText = `#${i++}`; + const indexElement = document.createElement('div') + indexElement.innerText = `#${i++}` - const timeElement = document.createElement('div'); - timeElement.innerText = prettyTime; + const timeElement = document.createElement('div') + timeElement.innerText = prettyTime - item.append(indexElement, nameElement, timeElement); - leaderList.append(item); - } - }) - if (docs.length >= 10) { - this.lastPlace = docs[9].data().time; - } - - displayElement.append(leaderHeading, leaderList); - } else { - const message = document.createElement('em'); - message.innerText = 'Be the first to the top!'; - displayElement.append(leaderHeading, message); + item.append(indexElement, nameElement, timeElement) + leaderList.append(item) } + }) + if (docs.length >= 10) { + this.lastPlace = docs[9].data().time + } + + displayElement.append(leaderHeading, leaderList) + } else { + const message = document.createElement('em') + message.innerText = 'Be the first to the top!' + displayElement.append(leaderHeading, message) } + } - async send(game, key) { - const sessionId = new Date().toDateString().replace(/\s/g, '_'); - const gameId = new Date().toTimeString().replace(/\s/g, '_'); - const data = {}; - data[gameId] = game; + async send(game, key) { + const sessionId = new Date().toDateString().replace(/\s/g, '_') + const gameId = new Date().toTimeString().replace(/\s/g, '_') + const data = {} + data[gameId] = game - const sessionRef = doc(this.store, 'mw-all', this.user.browserId, 'games', sessionId) - await setDoc(sessionRef, data, {merge: true}) + const sessionRef = doc(this.store, 'mw-all', this.user.browserId, 'games', sessionId) + await setDoc(sessionRef, data, {merge: true}) - if (this.configuration && game.status === this.configuration.passingStatus && game[key] < this.lastPlace) { - let name = window.prompt(this.configuration.message); - if (!name) { - name = 'Anonymous'; - } + if (this.configuration && game.status === this.configuration.passingStatus && game[key] < this.lastPlace) { + let name = window.prompt(this.configuration.message) + if (!name) { + name = 'Anonymous' + } - const newGame = { - name, - browserId: this.user.browserId, - ...game - } + const newGame = { + name, + browserId: this.user.browserId, + ...game + } - const gameScoreRef = doc(collection(this.store, 'mw-leaders', game.level, 'games')) - await setDoc(gameScoreRef, newGame) - } + const gameScoreRef = doc(collection(this.store, 'mw-leaders', game.level, 'games')) + await setDoc(gameScoreRef, newGame) } + } - configurationPromt() { - if (!this.configuration) { - loggerService.debug('Failed to fetch server configuration. Please contact your developer.'); - } + configurationPromt() { + if (!this.configuration) { + this.loggerService.debug('Failed to fetch server configuration. Please contact your developer.') } + } } diff --git a/src/modules/leader-board/timestamp-fix.js b/src/modules/leader-board/timestamp-fix.js deleted file mode 100644 index 9176235..0000000 --- a/src/modules/leader-board/timestamp-fix.js +++ /dev/null @@ -1,40 +0,0 @@ - - - - updateTimeStampsLeaders() { - const levels = ['beginner', 'intermediate', 'expert']; - - levels.forEach(level => { - const collection = this.leaders.doc(level).collection('games'); - collection.get() - .then(res => { - const levelArray = res.docs.map(doc => ({id: doc.id, ...doc.data()})) - // console.log(level+": ", levelArray); - - levelArray.forEach(leaderGame => { - // const leaderGame = levelArray[0]; - const leaderTime = leaderGame.time; - const browser = leaderGame.browserId; - this.all.doc(browser).collection('games') - .get().then(games => { - const allGames = games.docs.map(doc => ({id: doc.id, games: {...doc.data()}})); - console.log(level + '...........' + browser); - allGames.forEach(day => { - const keys = Object.keys(day.games); - const winningKeys = keys.filter(key => day.games[key].status === 'win'); - winningKeys.forEach(key => { - const game = day.games[key]; - const dateString = [day.id, key].join(' ').replace(/_/g, ' '); - const newGame = {time_stamp: new Date(dateString), ...leaderGame}; - if (game.time === leaderTime) { - console.log('updated', newGame); - // collection.doc(leaderGame.id).get().then(res => console.log(res.data())); - collection.doc(leaderGame.id).set(newGame); - } - }) - }); - }); - }) - }); - }) - } \ No newline at end of file diff --git a/src/modules/loading/loading.js b/src/modules/loading/loading.js index a9ac6c7..ee87f58 100644 --- a/src/modules/loading/loading.js +++ b/src/modules/loading/loading.js @@ -1,8 +1,8 @@ export class LoadingService { - addLoading(element) { - element.innerHTML = '
'; - } - removeLoading(element) { - element.innerHTML = ''; - } + addLoading(element) { + element.innerHTML = '
' + } + removeLoading(element) { + element.innerHTML = '' + } } \ No newline at end of file diff --git a/src/modules/logger/logger.js b/src/modules/logger/logger.js index feade34..eafad27 100644 --- a/src/modules/logger/logger.js +++ b/src/modules/logger/logger.js @@ -1,13 +1,13 @@ export class LoggerService { - debug(message, data) { - if (typeof message === 'string') { - if (data) { - console.log(message, data); - } else { - console.log(message); - } - } else { - console.warn(`LoggerService.debug expects a string as first parameter but got a ${typeof message}`, message); - } + debug(message, data) { + if (typeof message === 'string') { + if (data) { + console.log(message, data) + } else { + console.log(message) + } + } else { + console.warn(`LoggerService.debug expects a string as first parameter but got a ${typeof message}`, message) } + } } \ No newline at end of file diff --git a/src/modules/storage/storage.js b/src/modules/storage/storage.js index 5c413ff..1f8aaa0 100644 --- a/src/modules/storage/storage.js +++ b/src/modules/storage/storage.js @@ -1,23 +1,23 @@ export class StorageService { - constructor() { - } + constructor() { + } - saveToLocal(key, value) { - localStorage.setItem(key, JSON.stringify(value)); - } + saveToLocal(key, value) { + localStorage.setItem(key, JSON.stringify(value)) + } - saveToSession(key, value) { - sessionStorage.setItem(key, JSON.stringify(value)); - } + saveToSession(key, value) { + sessionStorage.setItem(key, JSON.stringify(value)) + } - getFromLocal(key) { - const data = localStorage.getItem(key); - if (data !== 'undefined') return JSON.parse(data); - } + getFromLocal(key) { + const data = localStorage.getItem(key) + if (data !== 'undefined') return JSON.parse(data) + } - getFromSession(key) { - const data = sessionStorage.getItem(key); - if (data !== 'undefined') return JSON.parse(data); - } + getFromSession(key) { + const data = sessionStorage.getItem(key) + if (data !== 'undefined') return JSON.parse(data) + } } diff --git a/src/modules/timer/timer.js b/src/modules/timer/timer.js index 3e8ddd7..4bf644a 100644 --- a/src/modules/timer/timer.js +++ b/src/modules/timer/timer.js @@ -1,63 +1,63 @@ -import { LoggerService } from "../logger/logger"; +import { LoggerService } from '../logger/logger' -const INTERVAL = 1; +const INTERVAL = 1 export class TimerService { - constructor() { - this.loggerService = new LoggerService(); + constructor() { + this.loggerService = new LoggerService() + } + + initialize(el) { + if (!el) return + + this.display = el + this.startTime = undefined + if (this.id) { + this.stop() } + this.updateDisplay() + } - initialize(el) { - if (!el) return; + start() { + if (this.running || !this.display) return - this.display = el; - this.startTime = undefined; - if (this.id) { - this.stop() - } - this.updateDisplay(); - } + this.running = true + this.startTime = new Date().getTime() + this.id = window.setInterval(() => this.updateDisplay(), INTERVAL) + this.loggerService.debug(`started timer id: ${this.id}`) + } - start() { - if (this.running || !this.display) return; + stop() { + this.running = false + clearInterval(this.id) + this.loggerService.debug(`stopped timer id: ${this.id}`) + this.id = undefined + return this.time + } - this.running = true; - this.startTime = new Date().getTime(); - this.id = window.setInterval(() => this.updateDisplay(), INTERVAL); - this.loggerService.debug(`started timer id: ${this.id}`); - } + updateDisplay() { + let currentTime = new Date().getTime() - this.startTime + this.time = Math.floor(currentTime / INTERVAL) + this.display.innerHTML = this.pretty(this.time) || '0' + } - stop() { - this.running = false; - clearInterval(this.id); - this.loggerService.debug(`stopped timer id: ${this.id}`); - this.id = undefined; - return this.time; - } + pretty(duration) { + if (!duration) return undefined + var milliseconds = parseInt((duration % 1000) / 100), + seconds = Math.floor((duration / 1000) % 60), + minutes = Math.floor((duration / (1000 * 60)) % 60), + hours = Math.floor((duration / (1000 * 60 * 60)) % 24) - updateDisplay() { - let currentTime = new Date().getTime() - this.startTime; - this.time = Math.floor(currentTime / INTERVAL); - this.display.innerHTML = this.pretty(this.time) || '0'; - } + hours = (hours < 10) ? `0${hours}` : hours + minutes = (minutes < 10) ? `0${minutes}` : minutes + seconds = (seconds < 10) ? `0${seconds}` : seconds - pretty(duration) { - if (!duration) return undefined; - var milliseconds = parseInt((duration % 1000) / 100), - seconds = Math.floor((duration / 1000) % 60), - minutes = Math.floor((duration / (1000 * 60)) % 60), - hours = Math.floor((duration / (1000 * 60 * 60)) % 24); + return `${this.clean(hours, ':')}${this.clean(minutes, ':')}${this.clean(seconds, '.')}${this.clean(milliseconds, '')}` + } - hours = (hours < 10) ? `0${hours}` : hours; - minutes = (minutes < 10) ? `0${minutes}` : minutes; - seconds = (seconds < 10) ? `0${seconds}` : seconds; - - return `${this.clean(hours, ':')}${this.clean(minutes, ':')}${this.clean(seconds, '.')}${this.clean(milliseconds, '')}`; - } - - clean(str, separator) { - return (str === '00') ? '' : `${str}${separator}`; - } + clean(str, separator) { + return (str === '00') ? '' : `${str}${separator}` + } } diff --git a/src/modules/user/user.js b/src/modules/user/user.js index 81f56d7..0f5cd01 100644 --- a/src/modules/user/user.js +++ b/src/modules/user/user.js @@ -1,21 +1,21 @@ export class UserService { - constructor() { - if (!this.id) { - this.browserId = this.generateId(); - } + constructor() { + if (!this.id) { + this.browserId = this.generateId() } + } - generateId() { + generateId() { - var nav = window.navigator; - var screen = window.screen; - var guid = nav.mimeTypes.length; - guid += nav.userAgent.replace(/\D+/g, ''); - guid += nav.plugins.length; - guid += screen.height || ''; - guid += screen.width || ''; - guid += screen.pixelDepth || ''; + var nav = window.navigator + var screen = window.screen + var guid = nav.mimeTypes.length + guid += nav.userAgent.replace(/\D+/g, '') + guid += nav.plugins.length + guid += screen.height || '' + guid += screen.width || '' + guid += screen.pixelDepth || '' - return guid; - } + return guid + } } \ No newline at end of file