diff --git a/package.json b/package.json index 8232b8f..4004507 100644 --- a/package.json +++ b/package.json @@ -14,18 +14,14 @@ "license": "MIT", "devDependencies": { "@biomejs/biome": "1.9.4", - "@eslint/js": "^9.16.0", "@types/node": "^22.10.1", "@types/sql.js": "^1.4.9", - "@typescript-eslint/eslint-plugin": "^8.17.0", - "@typescript-eslint/parser": "^8.17.0", "autoprefixer": "^10.4.20", "better-sqlite3": "^11.7.0", "kysely-codegen": "^0.17.0", "postcss": "^8.4.49", "tailwindcss": "^3.4.16", "typescript": "^5.7.2", - "typescript-eslint": "^8.17.0", "vite": "^6.0.3", "vite-plugin-solid": "^2.11.0" }, @@ -33,7 +29,8 @@ "@kobalte/core": "^0.13.7", "@kobalte/tailwindcss": "^0.9.0", "@solid-primitives/refs": "^1.0.8", - "@solidjs/router": "^0.15.1", + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.15.2", "@tanstack/solid-table": "^8.20.5", "chart.js": "^4.4.7", "chartjs-chart-wordcloud": "^4.4.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e125e..250f157 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,12 @@ importers: '@solid-primitives/refs': specifier: ^1.0.8 version: 1.0.8(solid-js@1.9.3) + '@solidjs/meta': + specifier: ^0.29.4 + version: 0.29.4(solid-js@1.9.3) '@solidjs/router': - specifier: ^0.15.1 - version: 0.15.1(solid-js@1.9.3) + specifier: ^0.15.2 + version: 0.15.2(solid-js@1.9.3) '@tanstack/solid-table': specifier: ^8.20.5 version: 8.20.5(solid-js@1.9.3) @@ -69,21 +72,12 @@ importers: '@biomejs/biome': specifier: 1.9.4 version: 1.9.4 - '@eslint/js': - specifier: ^9.16.0 - version: 9.16.0 '@types/node': specifier: ^22.10.1 version: 22.10.1 '@types/sql.js': specifier: ^1.4.9 version: 1.4.9 - '@typescript-eslint/eslint-plugin': - specifier: ^8.17.0 - version: 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/parser': - specifier: ^8.17.0 - version: 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.4.49) @@ -102,9 +96,6 @@ importers: typescript: specifier: ^5.7.2 version: 5.7.2 - typescript-eslint: - specifier: ^8.17.0 - version: 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) vite: specifier: ^6.0.3 version: 6.0.3(@types/node@22.10.1)(jiti@1.21.6)(yaml@2.6.1) @@ -401,40 +392,6 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - 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.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/config-array@0.19.1': - resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.9.1': - resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.2.0': - resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.16.0': - resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.5': - resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.2.4': - resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -444,26 +401,6 @@ packages: '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.1': - resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} - engines: {node: '>=18.18'} - '@internationalized/date@3.6.0': resolution: {integrity: sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==} @@ -676,8 +613,13 @@ packages: peerDependencies: solid-js: ^1.6.12 - '@solidjs/router@0.15.1': - resolution: {integrity: sha512-lb5BRBqQqii/1dQCglx2K68xLkgu7QcrcajWKuuEx6FHTsK/hp5IgVhjy6RzPMLj+SFyrrRi/ldirCFNxtzh0Q==} + '@solidjs/meta@0.29.4': + resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==} + peerDependencies: + solid-js: '>=1.8.4' + + '@solidjs/router@0.15.2': + resolution: {integrity: sha512-UWtliRvOnjfYMONQcTGwtf6BEud5QlF0oHC5L+kcSGYn0jARH5KzC3+3LLZ0al7oQo/5Rc50ssMswPuAuxFvAA==} peerDependencies: solid-js: ^1.8.6 @@ -721,90 +663,12 @@ packages: '@types/hammerjs@2.0.46': resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.10.1': resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} '@types/sql.js@1.4.9': resolution: {integrity: sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==} - '@typescript-eslint/eslint-plugin@8.17.0': - resolution: {integrity: sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@8.17.0': - resolution: {integrity: sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@8.17.0': - resolution: {integrity: sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.17.0': - resolution: {integrity: sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@8.17.0': - resolution: {integrity: sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.17.0': - resolution: {integrity: sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@8.17.0': - resolution: {integrity: sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/visitor-keys@8.17.0': - resolution: {integrity: sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==} - 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.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -835,9 +699,6 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -892,10 +753,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -1005,9 +862,6 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -1062,76 +916,17 @@ packages: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-scope@8.2.0: - resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - 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.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.16.0: - resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - 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'} - expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -1139,17 +934,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.2: - resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -1202,13 +986,6 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - hammerjs@2.0.8: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} @@ -1231,18 +1008,6 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -1298,32 +1063,16 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} hasBin: true - 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==} - json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kysely-codegen@0.17.0: resolution: {integrity: sha512-C36g6epial8cIOSBEWGI9sRfkKSsEzTcivhjPivtYFQnhMdXnrVFaUe7UMZHeSdXaHiWDqDOkReJgWLD8nPKdg==} hasBin: true @@ -1367,10 +1116,6 @@ packages: resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==} engines: {node: '>=14.0.0'} - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1378,13 +1123,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - loglevel@1.9.2: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} @@ -1447,9 +1185,6 @@ packages: napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-abi@3.71.0: resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} engines: {node: '>=10'} @@ -1476,32 +1211,12 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - 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'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1582,17 +1297,9 @@ packages: engines: {node: '>=10'} hasBin: true - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1615,10 +1322,6 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -1732,10 +1435,6 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1784,12 +1483,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1799,20 +1492,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - typescript-eslint@8.17.0: - resolution: {integrity: sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} @@ -1827,9 +1506,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -1899,10 +1575,6 @@ packages: 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'} @@ -1922,10 +1594,6 @@ packages: engines: {node: '>= 14'} hasBin: true - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2156,47 +1824,6 @@ snapshots: '@esbuild/win32-x64@0.24.0': optional: true - '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0(jiti@1.21.6))': - dependencies: - eslint: 9.16.0(jiti@1.21.6) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/config-array@0.19.1': - dependencies: - '@eslint/object-schema': 2.1.5 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/core@0.9.1': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.2.0': - dependencies: - ajv: 6.12.6 - debug: 4.4.0 - espree: 10.3.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.16.0': {} - - '@eslint/object-schema@2.1.5': {} - - '@eslint/plugin-kit@0.2.4': - dependencies: - levn: 0.4.1 - '@floating-ui/core@1.6.8': dependencies: '@floating-ui/utils': 0.2.8 @@ -2208,19 +1835,6 @@ snapshots: '@floating-ui/utils@0.2.8': {} - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.1': {} - '@internationalized/date@3.6.0': dependencies: '@swc/helpers': 0.5.15 @@ -2415,7 +2029,11 @@ snapshots: dependencies: solid-js: 1.9.3 - '@solidjs/router@0.15.1(solid-js@1.9.3)': + '@solidjs/meta@0.29.4(solid-js@1.9.3)': + dependencies: + solid-js: 1.9.3 + + '@solidjs/router@0.15.2(solid-js@1.9.3)': dependencies: solid-js: 1.9.3 @@ -2463,8 +2081,6 @@ snapshots: '@types/hammerjs@2.0.46': {} - '@types/json-schema@7.0.15': {} - '@types/node@22.10.1': dependencies: undici-types: 6.20.0 @@ -2474,101 +2090,6 @@ snapshots: '@types/emscripten': 1.39.13 '@types/node': 22.10.1 - '@typescript-eslint/eslint-plugin@8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/type-utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.17.0 - eslint: 9.16.0(jiti@1.21.6) - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.7.2) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) - '@typescript-eslint/visitor-keys': 8.17.0 - debug: 4.4.0 - eslint: 9.16.0(jiti@1.21.6) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.17.0': - dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 - - '@typescript-eslint/type-utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': - dependencies: - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - debug: 4.4.0 - eslint: 9.16.0(jiti@1.21.6) - ts-api-utils: 1.4.3(typescript@5.7.2) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.17.0': {} - - '@typescript-eslint/typescript-estree@8.17.0(typescript@5.7.2)': - dependencies: - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/visitor-keys': 8.17.0 - debug: 4.4.0 - fast-glob: 3.3.2 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.6.3 - ts-api-utils: 1.4.3(typescript@5.7.2) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2)': - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6)) - '@typescript-eslint/scope-manager': 8.17.0 - '@typescript-eslint/types': 8.17.0 - '@typescript-eslint/typescript-estree': 8.17.0(typescript@5.7.2) - eslint: 9.16.0(jiti@1.21.6) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.17.0': - dependencies: - '@typescript-eslint/types': 8.17.0 - eslint-visitor-keys: 4.2.0 - - acorn-jsx@5.3.2(acorn@8.14.0): - dependencies: - acorn: 8.14.0 - - acorn@8.14.0: {} - - ajv@6.12.6: - 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: {} ansi-regex@6.1.0: {} @@ -2592,8 +2113,6 @@ snapshots: arg@5.0.2: {} - argparse@2.0.1: {} - autoprefixer@10.4.20(postcss@8.4.49): dependencies: browserslist: 4.24.2 @@ -2665,8 +2184,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - callsites@3.1.0: {} - camelcase-css@2.0.1: {} caniuse-lite@1.0.30001687: {} @@ -2768,8 +2285,6 @@ snapshots: deep-extend@0.6.0: {} - deep-is@0.1.4: {} - detect-libc@2.0.3: {} didyoumean@1.2.2: {} @@ -2829,80 +2344,8 @@ snapshots: escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} - - eslint-scope@8.2.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.0: {} - - eslint@9.16.0(jiti@1.21.6): - dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@1.21.6)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 - '@eslint/core': 0.9.1 - '@eslint/eslintrc': 3.2.0 - '@eslint/js': 9.16.0 - '@eslint/plugin-kit': 0.2.4 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.1 - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.0 - escape-string-regexp: 4.0.0 - eslint-scope: 8.2.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - esquery: 1.6.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 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 1.21.6 - transitivePeerDependencies: - - supports-color - - espree@10.3.0: - dependencies: - acorn: 8.14.0 - acorn-jsx: 5.3.2(acorn@8.14.0) - eslint-visitor-keys: 4.2.0 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - esutils@2.0.3: {} - expand-template@2.0.3: {} - fast-deep-equal@3.1.3: {} - fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2911,36 +2354,16 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - fastq@1.17.1: dependencies: reusify: 1.0.4 - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - file-uri-to-path@1.0.0: {} fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.2 - keyv: 4.5.4 - - flatted@3.3.2: {} - foreground-child@3.3.0: dependencies: cross-spawn: 7.0.6 @@ -2997,10 +2420,6 @@ snapshots: globals@11.12.0: {} - globals@14.0.0: {} - - graphemer@1.4.0: {} - hammerjs@2.0.8: {} has-flag@3.0.0: {} @@ -3015,15 +2434,6 @@ snapshots: ieee754@1.2.1: {} - ignore@5.3.2: {} - - import-fresh@3.3.0: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -3067,24 +2477,10 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - jsesc@3.0.2: {} - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - json5@2.2.3: {} - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - kysely-codegen@0.17.0(better-sqlite3@11.7.0)(kysely@0.27.5): dependencies: chalk: 4.1.2 @@ -3104,21 +2500,10 @@ snapshots: kysely@0.27.5: {} - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - loglevel@1.9.2: {} lru-cache@10.4.3: {} @@ -3170,8 +2555,6 @@ snapshots: napi-build-utils@1.0.2: {} - natural-compare@1.4.0: {} - node-abi@3.71.0: dependencies: semver: 7.6.3 @@ -3190,35 +2573,12 @@ snapshots: dependencies: wrappy: 1.0.2 - 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 - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parse5@7.2.1: dependencies: entities: 4.5.0 - path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -3292,15 +2652,11 @@ snapshots: tar-fs: 2.1.1 tunnel-agent: 0.6.0 - prelude-ls@1.2.1: {} - pump@3.0.2: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - punycode@2.3.1: {} - queue-microtask@1.2.3: {} rc@1.2.8: @@ -3328,8 +2684,6 @@ snapshots: dependencies: resolve: 1.22.8 - resolve-from@4.0.0: {} - resolve@1.22.8: dependencies: is-core-module: 2.15.1 @@ -3458,8 +2812,6 @@ snapshots: strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} - sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -3540,10 +2892,6 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@1.4.3(typescript@5.7.2): - dependencies: - typescript: 5.7.2 - ts-interface-checker@0.1.13: {} tslib@2.8.1: {} @@ -3552,21 +2900,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2))(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - '@typescript-eslint/utils': 8.17.0(eslint@9.16.0(jiti@1.21.6))(typescript@5.7.2) - eslint: 9.16.0(jiti@1.21.6) - optionalDependencies: - typescript: 5.7.2 - transitivePeerDependencies: - - supports-color - typescript@5.7.2: {} undici-types@6.20.0: {} @@ -3577,10 +2910,6 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - util-deprecate@1.0.2: {} validate-html-nesting@1.2.2: {} @@ -3617,8 +2946,6 @@ snapshots: dependencies: isexe: 2.0.0 - word-wrap@1.2.5: {} - wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3636,5 +2963,3 @@ snapshots: yallist@3.1.1: {} yaml@2.6.1: {} - - yocto-queue@0.1.0: {} diff --git a/src/App.tsx b/src/App.tsx index 8b233ac..32ba558 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,23 +13,6 @@ const App: Component = () => { - { - console.time("first"); - console.log(allThreadsOverviewQuery()); - void allThreadsOverviewQuery().then((result) => { - console.log(result); - console.timeEnd("first"); - console.time("second"); - void allThreadsOverviewQuery().then((result) => { - console.log(result); - console.timeEnd("second"); - }); - }); - return ""; - }} - /> ); }; diff --git a/src/db.ts b/src/db.ts index 940a7cc..4dea490 100644 --- a/src/db.ts +++ b/src/db.ts @@ -153,59 +153,14 @@ const dmPartnerRecipientQueryRaw = (dmId: number) => export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw); -const dmOverviewQueryRaw = (dmId: number) => - kyselyDb() - .selectFrom("message") - .select((eb) => [ - eb.fn.countAll().as("message_count"), - eb.fn.min("date_sent").as("first_message_date"), - eb.fn.max("date_sent").as("last_message_date"), - ]) - .where((eb) => - eb.and([ - eb("thread_id", "=", dmId), - eb("body", "is not", null), - eb("body", "!=", ""), - ]) - ) - .executeTakeFirst(); - -export const dmOverviewQuery = cached(dmOverviewQueryRaw); - -const threadSentMessagesPerPersonOverviewQueryRaw = (threadId: number) => - kyselyDb() - .selectFrom("message") - .select((eb) => [ - "from_recipient_id", - sql`DATE(datetime(message.date_sent / 1000, 'unixepoch'))`.as( - "message_date" - ), - eb.fn.countAll().as("message_count"), - ]) - .groupBy(["from_recipient_id", "message_date"]) - .orderBy(["message_date"]) - .where((eb) => - eb.and([ - eb("body", "is not", null), - eb("body", "!=", ""), - eb("thread_id", "=", threadId), - ]) - ) - .$narrowType<{ - message_count: number; - }>() - .execute(); - -export const dmSentMessagesPerPersonOverviewQuery = cached( - threadSentMessagesPerPersonOverviewQueryRaw -); - const threadSentMessagesOverviewQueryRaw = (threadId: number) => kyselyDb() .selectFrom("message") .select([ "from_recipient_id", - sql`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime"), + sql`datetime(date_sent / 1000, 'unixepoch')`.as( + "message_datetime" + ), ]) .orderBy(["message_datetime"]) .where((eb) => diff --git a/src/index.tsx b/src/index.tsx index 53b6f79..a903f1d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,7 @@ /* @refresh reload */ import { render } from "solid-js/web"; import { Router } from "@solidjs/router"; +import { MetaProvider } from "@solidjs/meta"; import App from "./App"; @@ -16,9 +17,11 @@ if (root) { render( () => (
- - - + + + + +
), root, diff --git a/src/lib/date.ts b/src/lib/date.ts index 14de29e..6019974 100644 --- a/src/lib/date.ts +++ b/src/lib/date.ts @@ -8,3 +8,86 @@ export const getDistanceBetweenDatesInDays = (a: Date, b: Date) => { return Math.floor((utc2 - utc1) / _MS_PER_DAY); }; + +// https://dev.to/pretaporter/how-to-get-month-list-in-your-language-4lfb +export const getMonthList = ( + locales?: string | string[], + format: "long" | "short" = "long" +): string[] => { + const year = new Date().getFullYear(); // 2020 + const monthList = [...Array(12).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + const formatter = new Intl.DateTimeFormat(locales, { + month: format, + }); + + const getMonthName = (monthIndex: number) => + formatter.format(new Date(year, monthIndex)); + + return monthList.map(getMonthName); +}; + +export const getDateList = (startDate: Date, endDate: Date): Date[] => { + const dateArray = new Array(); + + // end date for loop has to be one date after because we increment after adding the date in the loop + const endDateForLoop = new Date(endDate); + + endDateForLoop.setDate(endDateForLoop.getDate() + 1); + + let currentDate = startDate; + + while (currentDate <= endDateForLoop) { + dateArray.push(new Date(currentDate)); + + const newDate = new Date(currentDate); + newDate.setDate(newDate.getDate() + 1); + + currentDate = newDate; + } + + return dateArray; +}; + +export const getHourList = ( + locales?: string | string[], + format: "numeric" | "2-digit" = "numeric" +): string[] => { + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + const day = now.getDate(); + + const hourList = [...Array(24).keys()]; // [0, 1, 2, 3, 4, 5, 6, ..., 23] + const formatter = new Intl.DateTimeFormat(locales, { + hour: format, + hourCycle: "h11", + }); + + const getHourName = (hourIndex: number) => + formatter.format(new Date(year, month, day, hourIndex)); + + return hourList.map(getHourName); +}; + +export const getWeekdayList = ( + locales?: string | string[], + format: "long" | "short" | "narrow" = "long" +): string[] => { + const monday = new Date(); + // set day to monday (w/o +1 it would be sunday) + monday.setDate(monday.getDate() - monday.getDay() + 1); + + const year = monday.getFullYear(); + const month = monday.getMonth(); + const mondayDate = monday.getDate(); + + const hourList = [...Array(7).keys()]; // [0, 1, 2, 3, 4, 5, 6] + const formatter = new Intl.DateTimeFormat(locales, { + weekday: format, + }); + + const getWeekDayName = (weekDayIndex: number) => + formatter.format(new Date(year, month, mondayDate + weekDayIndex)); + + return hourList.map(getWeekDayName); +}; diff --git a/src/lib/db-cache.ts b/src/lib/db-cache.ts index 5546bc2..a4dd86e 100644 --- a/src/lib/db-cache.ts +++ b/src/lib/db-cache.ts @@ -1,4 +1,4 @@ -import { createEffect, createRoot, on } from "solid-js"; +import { createRoot, on, createDeferred } from "solid-js"; const DATABASE_HASH_PREFIX = "database"; @@ -27,35 +27,36 @@ const hashString = (str: string) => { return hash; }; -const HASH_STORE_KEY = "database_hash"; +const HASH_STORE_KEY = `${DATABASE_HASH_PREFIX}_hash`; // cannot import `db` the normal way because this file is imported in ~/db.ts before the initialisation of `db` has happened -queueMicrotask(() => { - createRoot(() => { - void import("~/db").then(({ db }) => { - createEffect( - on(db, (currentDb) => { - if (currentDb) { - const newHash = hashString(new TextDecoder().decode(currentDb.export())).toString(); +createRoot(() => { + void import("~/db").then(({ db }) => { + // we use create deferred because hasing can take very long and we don't want to block the mainthread + createDeferred( + on(db, (currentDb) => { + if (currentDb) { + const newHash = hashString( + new TextDecoder().decode(currentDb.export()) + ).toString(); - const oldHash = localStorage.getItem(HASH_STORE_KEY); + const oldHash = localStorage.getItem(HASH_STORE_KEY); - console.log(newHash, oldHash); + if (newHash !== oldHash) { + clearDbCache(); - if (newHash !== oldHash) { - clearDbCache(); - - localStorage.setItem(HASH_STORE_KEY, newHash); - } + localStorage.setItem(HASH_STORE_KEY, newHash); } - }), - ); - }); + } + }) + ); }); }); class LocalStorageCacheAdapter { - keys = new Set(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix))); + keys = new Set( + Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)) + ); prefix = "database"; #createKey(cacheName: string, key: string): string { @@ -66,7 +67,16 @@ class LocalStorageCacheAdapter { const fullKey = this.#createKey(cacheName, key); this.keys.add(fullKey); - localStorage.setItem(fullKey, JSON.stringify(value)); + try { + localStorage.setItem(fullKey, JSON.stringify(value)); + } catch (error: unknown) { + if ( + error instanceof DOMException && + error.name === "QUOTA_EXCEEDED_ERR" + ) { + console.error("Storage quota exceeded, not caching new function calls"); + } + } } has(cacheName: string, key: string): boolean { @@ -84,14 +94,44 @@ class LocalStorageCacheAdapter { const cache = new LocalStorageCacheAdapter(); -export const cached = (fn: (...args: T) => R, self?: ThisType): ((...args: T) => R) => { +const createHashKey = (...args: unknown[]) => { + let stringToHash = ""; + + for (const arg of args) { + switch (typeof arg) { + case "string": + stringToHash += arg; + break; + case "number": + case "bigint": + case "symbol": + case "function": + stringToHash += arg.toString(); + break; + case "boolean": + case "undefined": + stringToHash += String(arg); + break; + case "object": + stringToHash += JSON.stringify(arg); + break; + } + } + + return hashString(stringToHash); +}; + +export const cached = ( + fn: (...args: T) => R, + self?: ThisType +): ((...args: T) => R) => { const cacheName = hashString(fn.toString()).toString(); // important to return a promise on follow-up calls even if the data is immediately available let isPromise: boolean; return (...args: T) => { - const cacheKey = JSON.stringify(args); + const cacheKey = createHashKey(...args).toString(); const cachedValue = cache.get(cacheName, cacheKey); diff --git a/src/lib/messages.ts b/src/lib/messages.ts new file mode 100644 index 0000000..f6d211d --- /dev/null +++ b/src/lib/messages.ts @@ -0,0 +1,89 @@ +import { createEffect, createMemo, on, type Accessor } from "solid-js"; +import { getDateList, getHourList, getMonthList, getWeekdayList } from "./date"; +import { cached } from "./db-cache"; +import type { MessageOverview, MessageStats, Recipients } from "~/types"; +import { isSameDay } from "date-fns"; + +export const hourNames = getHourList(); + +const initialHoursMap = [...hourNames.keys()]; + +export const monthNames = getMonthList(); + +const initialMonthMap = [...monthNames.keys()]; + +export const weekdayNames = getWeekdayList(); + +const initialWeekdayMap = [...weekdayNames.keys()]; + +export const createMessageStatsSources = ( + messageOverview: Accessor, + recipients: Accessor +) => { + const initialRecipientMap = () => + Object.fromEntries(recipients().map(({ recipientId }) => [recipientId, 0])); + + const dateList = () => { + const currentDmMessagesOverview = messageOverview(); + const firstDate = currentDmMessagesOverview?.at(0)?.messageDate; + const lastDate = currentDmMessagesOverview?.at(-1)?.messageDate; + if (firstDate && lastDate) { + return getDateList(firstDate, lastDate).map((date) => ({ + totalMessages: 0, + date, + ...initialRecipientMap(), + })); + } + }; + + return createMemo(() => { + const currentMessageOverview = messageOverview(); + const currentDateList = dateList(); + const currentInitialRecipientMap = initialRecipientMap(); + + const messageStats: MessageStats = { + person: { ...currentInitialRecipientMap }, + month: initialMonthMap.map(() => ({ ...currentInitialRecipientMap })), + date: currentDateList ?? [], + weekday: initialWeekdayMap.map(() => ({ ...currentInitialRecipientMap })), + daytime: initialHoursMap.map(() => ({ ...currentInitialRecipientMap })), + }; + + if (currentMessageOverview && currentDateList) { + const { person, month, date, weekday, daytime } = messageStats; + + for (const message of currentMessageOverview) { + const { messageDate } = message; + + // increment overall message count of a person + person[message.fromRecipientId] += 1; + + // increment the message count of the message's month for this recipient + // months are an array from 0 - 11 + month[messageDate.getMonth() - 1][message.fromRecipientId] += 1; + + // biome-ignore lint/style/noNonNullAssertion: + const dateStatsEntry = date.find(({ date }) => + isSameDay(date, messageDate) + )!; + + // increment the message count of the message's date for this recipient + dateStatsEntry[message.fromRecipientId] += 1; + + // increment the overall message count of the message's date + dateStatsEntry.totalMessages += 1; + + const weekdayOfDate = messageDate.getDay(); + // we index starting with monday while the `Date` object indexes starting with Sunday + const weekdayIndex = weekdayOfDate === 0 ? 6 : weekdayOfDate - 1; + + // increment the message count of the message's weekday for this recipient + weekday[weekdayIndex][message.fromRecipientId] += 1; + + daytime[messageDate.getHours()][message.fromRecipientId] += 1; + } + } + + return messageStats; + }); +}; diff --git a/src/pages/dm/dm-id.tsx b/src/pages/dm/dm-id.tsx index 2361dcd..bc39a25 100644 --- a/src/pages/dm/dm-id.tsx +++ b/src/pages/dm/dm-id.tsx @@ -1,63 +1,24 @@ -import { type Accessor, type Component, createEffect, createResource, Show } from "solid-js"; +import { type Component, createResource, Show } from "solid-js"; import type { RouteSectionProps } from "@solidjs/router"; -import { type ChartData } from "chart.js"; - -import { LineChart, RadarChart, WordCloudChart } from "~/components/ui/charts"; - -import { - dmOverviewQuery, - dmPartnerRecipientQuery, - dmSentMessagesPerPersonOverviewQuery, - SELF_ID, - threadMostUsedWordsQuery, - threadSentMessagesOverviewQuery, -} from "~/db"; +import { dmPartnerRecipientQuery, SELF_ID, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db"; import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; import { Heading } from "~/components/ui/heading"; import { Grid } from "~/components/ui/grid"; -import { Flex } from "~/components/ui/flex"; -import { CalendarArrowUp, CalendarArrowDown, CalendarClock, MessagesSquare } from "lucide-solid"; -import { getDistanceBetweenDatesInDays } from "~/lib/date"; - -type MonthIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; - -const monthNames: Record = { - 1: "January", - 2: "February", - 3: "March", - 4: "April", - 5: "May", - 6: "June", - 7: "July", - 8: "August", - 9: "September", - 10: "October", - 11: "November", - 12: "December", -}; - -const initialMonthMap = Object.fromEntries( - Array(12) - .fill(0) - .map((_value, index) => [index + 1, 0]), -) as Record; +import { Title } from "@solidjs/meta"; +import { DmMessagesPerDate } from "./dm-messages-per-date"; +import { DmOverview } from "./dm-overview"; +import { DmWordCloud } from "./dm-wordcloud"; +import { DmMessagesPerMonth } from "./dm-messages-per-month"; +import { DmMessagesPerDaytime } from "./dm-messages-per-daytime"; +import { DmMessagesPerRecipient } from "./dm-messages-per-recipients"; +import { DmMessagesPerWeekday } from "./dm-messages-per-weekday"; +import type { MessageOverview } from "~/types"; +import { createMessageStatsSources } from "~/lib/messages"; export const DmId: Component = (props) => { const dmId = () => Number(props.params.dmid); - const [dmOverview] = createResource(async () => { - const dmOverview = await dmOverviewQuery(dmId()); - - if (dmOverview) { - return { - messageCount: dmOverview.message_count, - firstMessageDate: new Date(dmOverview.first_message_date), - lastMessageDate: new Date(dmOverview.last_message_date), - }; - } - }); - // the other person in the chat with name and id const [dmPartner] = createResource(async () => { const dmPartner = await dmPartnerRecipientQuery(dmId()); @@ -74,66 +35,18 @@ export const DmId: Component = (props) => { } }); - const [dmMessagesPerPerson] = createResource(() => dmSentMessagesPerPersonOverviewQuery(dmId())); - - const [dmMessagesOverview] = createResource(async () => { + const [dmMessagesOverview] = createResource(async () => { const dmMessageOverview = await threadSentMessagesOverviewQuery(dmId()); if (dmMessageOverview) { return dmMessageOverview.map((row) => { return { - messageDate: new Date(row.message_datetime), - recipientId: row.from_recipient_id, + messageDate: new Date(row.message_datetime + "Z"), + fromRecipientId: row.from_recipient_id, }; }); } }); - const dmMessagesPerMonth = () => { - const currentDmMessagesOverview = dmMessagesOverview(); - - if (currentDmMessagesOverview) { - return currentDmMessagesOverview.reduce>( - (prev, curr) => { - const month = curr.messageDate.getMonth() as MonthIndex; - - prev[month as MonthIndex] += 1; - - return prev; - }, - { ...initialMonthMap }, - ); - } - }; - - // maps all the message counts to dates - const dmMessagesPerDateOverview = () => { - return dmMessagesPerPerson()?.reduce< - { - rawDate: string; - date: Date; - totalMessages: number; - [recipientId: number]: number; - }[] - >((prev, curr) => { - const existingDate = prev.find(({ rawDate }) => rawDate === curr.message_date); - - if (existingDate) { - existingDate[curr.from_recipient_id] = curr.message_count; - - existingDate.totalMessages += curr.message_count; - } else { - prev.push({ - rawDate: curr.message_date, - date: new Date(curr.message_date), - totalMessages: curr.message_count, - [curr.from_recipient_id]: curr.message_count, - }); - } - - return prev; - }, []); - }; - const [mostUsedWordCounts] = createResource(async () => threadMostUsedWordsQuery(dmId(), 300)); const recipients = () => { @@ -157,218 +70,40 @@ export const DmId: Component = (props) => { ]; }; - const maxWordSize = 100; - - const mostUsedWordChartData: Accessor | undefined> = () => { - const currentMostUsedWordCounts = mostUsedWordCounts(); - - if (currentMostUsedWordCounts) { - // ordered descending in db query - const highestWordCount = currentMostUsedWordCounts[0].count; - - const calcWordSizeInPixels = (count: number) => { - return 10 + Math.round((maxWordSize / highestWordCount) * count); - }; - - return { - labels: currentMostUsedWordCounts.map(({ word }) => word), - datasets: [ - { - label: "Used", - data: currentMostUsedWordCounts.map(({ count }) => calcWordSizeInPixels(count)), - }, - ], - }; - } - }; - - const dateChartData: Accessor | undefined> = () => { - const currentDmMessages = dmMessagesPerDateOverview(); - const currentRecipients = recipients(); - - if (currentDmMessages) { - return { - labels: currentDmMessages.map((row) => row.date.toDateString()), - datasets: [ - { - label: "Total number of messages", - data: currentDmMessages.map((row) => row.totalMessages), - borderWidth: 2, - }, - ...currentDmMessages.reduce<{ id: number; label: string; data: number[] }[]>( - (prev, curr) => { - for (const recipient of currentRecipients) { - prev.find(({ id }) => id === recipient.recipientId)?.data.push(curr[recipient.recipientId] ?? 0); - } - - return prev; - }, - currentRecipients.map((recipient) => { - return { - id: recipient.recipientId, - label: `Number of messages from ${recipient.name.toString()}`, - data: [], - borderWidth: 2, - }; - }), - ), - ], - }; - } - }; - - const monthChartData: Accessor | undefined> = () => { - const currentMessagesPerMonth = dmMessagesPerMonth(); - - if (currentMessagesPerMonth) { - return { - labels: Object.values(monthNames), - datasets: [ - { - label: "Number of messages", - data: Object.values(currentMessagesPerMonth), - }, - ], - }; - } - }; + const dmMessageStats = createMessageStatsSources(dmMessagesOverview, recipients); return ( -
- DM with {dmPartner()?.name} - Chat timeline - - {(currentDateChartData) => ( - - )} - - - - - - - - Your first message is from - - {(currentDmOverview) => ( - {currentDmOverview().firstMessageDate.toDateString()} - )} - - - - - - - - - Your last message is from - - {(currentDmOverview) => ( - {currentDmOverview().lastMessageDate.toDateString()} - )} - - - - - - - - - You have been chatting for - - {(currentDmOverview) => ( - - {getDistanceBetweenDatesInDays( - currentDmOverview().firstMessageDate, - currentDmOverview().lastMessageDate, - )} - - )} - - days - - - - - - - - You have written - - {(currentDmOverview) => ( - {currentDmOverview().messageCount.toString()} - )} - - messages - - - - Messages per -
- Month - - - {(currentMonthChartData) => ( - - )} - - -
- Word cloud - - {(currentMostUsedWordChartData) => ( - // without a container this will scale in height infinitely somehow -
- + <> + Dm with {dmPartner()?.name} +
+ DM with {dmPartner()?.name} + Chat timeline + + + Messages per + + +
+ Person +
- )} - -
+
+ Daytime + +
+
+ Month + +
+
+ Weekday + +
+ + Word cloud + +
+ ); }; diff --git a/src/pages/dm/dm-messages-per-date.tsx b/src/pages/dm/dm-messages-per-date.tsx new file mode 100644 index 0000000..4f3932c --- /dev/null +++ b/src/pages/dm/dm-messages-per-date.tsx @@ -0,0 +1,68 @@ +import { createEffect, Show, type Accessor, type Component } from "solid-js"; +import type { ChartData } from "chart.js"; +import { LineChart } from "~/components/ui/charts"; +import type { MessageStats, Recipients } from "~/types"; + +export const DmMessagesPerDate: Component<{ + dateStats: MessageStats["date"]; + recipients: Recipients; +}> = (props) => { + const dateChartData: Accessor | undefined> = () => { + const currentDmMessages = props.dateStats; + const currentRecipients = props.recipients; + + if (currentDmMessages) { + const currentDmMessagesValues = Object.values(currentDmMessages); + + return { + labels: currentDmMessages.map(({ date }) => date.toDateString()), + datasets: [ + { + label: "Total", + data: currentDmMessagesValues.map((row) => row.totalMessages), + borderWidth: 2, + }, + ...currentRecipients.map((recipient) => { + return { + id: recipient.recipientId, + label: recipient.name.toString(), + data: currentDmMessagesValues.map((date) => date[recipient.recipientId]), + borderWidth: 2, + }; + }), + ], + }; + } + }; + + return ( + + {(currentDateChartData) => ( + + )} + + ); +}; diff --git a/src/pages/dm/dm-messages-per-daytime.tsx b/src/pages/dm/dm-messages-per-daytime.tsx new file mode 100644 index 0000000..765f070 --- /dev/null +++ b/src/pages/dm/dm-messages-per-daytime.tsx @@ -0,0 +1,53 @@ +import { Show, type Accessor, type Component } from "solid-js"; +import type { ChartData } from "chart.js"; +import { BarChart } from "~/components/ui/charts"; +import type { MessageStats, Recipients } from "~/types"; +import { hourNames } from "~/lib/messages"; + +export const DmMessagesPerDaytime: Component<{ + daytimeStats: MessageStats["daytime"]; + recipients: Recipients; +}> = (props) => { + const daytimeChartData: Accessor | undefined> = () => { + const currentMessagesPerHour = props.daytimeStats; + const currentRecipients = props.recipients; + + if (currentMessagesPerHour && currentRecipients) { + return { + labels: Object.values(hourNames), + datasets: [ + ...currentRecipients.map((recipient) => { + return { + id: recipient.recipientId, + label: `Number of messages from ${recipient.name.toString()}`, + data: currentMessagesPerHour.map((hour) => hour[recipient.recipientId]), + borderWidth: 1, + }; + }), + ], + }; + } + }; + + return ( + + {(currentDaytimeChartData) => ( + + )} + + ); +}; diff --git a/src/pages/dm/dm-messages-per-month.tsx b/src/pages/dm/dm-messages-per-month.tsx new file mode 100644 index 0000000..bec4f2e --- /dev/null +++ b/src/pages/dm/dm-messages-per-month.tsx @@ -0,0 +1,53 @@ +import { Show, type Accessor, type Component } from "solid-js"; +import type { ChartData } from "chart.js"; +import { RadarChart } from "~/components/ui/charts"; +import { monthNames } from "~/lib/messages"; +import type { MessageStats, Recipients } from "~/types"; + +export const DmMessagesPerMonth: Component<{ + monthStats: MessageStats["month"]; + recipients: Recipients; +}> = (props) => { + const monthChartData: Accessor | undefined> = () => { + const currentMessagesPerMonth = props.monthStats; + const currentRecipients = props.recipients; + + if (currentMessagesPerMonth && currentRecipients) { + return { + labels: Object.values(monthNames), + datasets: [ + ...currentRecipients.map((recipient) => { + return { + id: recipient.recipientId, + label: `Number of messages from ${recipient.name.toString()}`, + data: currentMessagesPerMonth.map((month) => month[recipient.recipientId]), + }; + }), + ], + }; + } + }; + + return ( + + {(currentMonthChartData) => ( + + )} + + ); +}; diff --git a/src/pages/dm/dm-messages-per-recipients.tsx b/src/pages/dm/dm-messages-per-recipients.tsx new file mode 100644 index 0000000..cd0e8de --- /dev/null +++ b/src/pages/dm/dm-messages-per-recipients.tsx @@ -0,0 +1,47 @@ +import { Show, type Accessor, type Component } from "solid-js"; +import type { ChartData } from "chart.js"; +import { PieChart } from "~/components/ui/charts"; +import type { MessageStats, Recipients } from "~/types"; + +export const DmMessagesPerRecipient: Component<{ + personStats: MessageStats["person"]; + recipients: Recipients; +}> = (props) => { + const recipientChartData: Accessor | undefined> = () => { + const currentMessagesPerRecipient = props.personStats; + const currentRecipients = props.recipients; + + if (currentMessagesPerRecipient && currentRecipients) { + return { + labels: Object.keys(currentMessagesPerRecipient).map( + (id) => currentRecipients.find(({ recipientId }) => recipientId === Number(id))?.name, + ), + datasets: [ + { + label: "Number of messages", + data: Object.values(currentMessagesPerRecipient), + }, + ], + }; + } + }; + + return ( + + {(currentRecipientChartData) => ( + + )} + + ); +}; diff --git a/src/pages/dm/dm-messages-per-weekday.tsx b/src/pages/dm/dm-messages-per-weekday.tsx new file mode 100644 index 0000000..4699c8c --- /dev/null +++ b/src/pages/dm/dm-messages-per-weekday.tsx @@ -0,0 +1,51 @@ +import { Show, type Accessor, type Component } from "solid-js"; +import type { ChartData } from "chart.js"; +import { RadarChart } from "~/components/ui/charts"; +import { weekdayNames } from "~/lib/messages"; +import type { MessageStats, Recipients } from "~/types"; + +export const DmMessagesPerWeekday: Component<{ + weekdayStats: MessageStats["weekday"]; + recipients: Recipients; +}> = (props) => { + const weekdayChartData: Accessor | undefined> = () => { + const currentMessagesPerWeekday = props.weekdayStats; + const currentRecipients = props.recipients; + + if (currentMessagesPerWeekday && currentRecipients) { + return { + labels: Object.values(weekdayNames), + datasets: [ + ...currentRecipients.map((recipient) => { + return { + id: recipient.recipientId, + label: `Number of messages from ${recipient.name.toString()}`, + data: currentMessagesPerWeekday.map((weekday) => weekday[recipient.recipientId]), + }; + }), + ], + }; + } + }; + + return ( + + {(currentWeekdayChartData) => ( + + )} + + ); +}; diff --git a/src/pages/dm/dm-overview.tsx b/src/pages/dm/dm-overview.tsx new file mode 100644 index 0000000..038144c --- /dev/null +++ b/src/pages/dm/dm-overview.tsx @@ -0,0 +1,88 @@ +import { Show, type Component } from "solid-js"; +import { Flex } from "~/components/ui/flex"; +import { Grid } from "~/components/ui/grid"; +import { CalendarArrowDown, CalendarArrowUp, CalendarClock, MessagesSquare } from "lucide-solid"; +import { getDistanceBetweenDatesInDays } from "~/lib/date"; +import type { MessageOverview } from "~/types"; + +export const DmOverview: Component<{ + messages: MessageOverview; +}> = (props) => { + const dmOverview = () => { + const firstMessageDate = props.messages?.at(0)?.messageDate; + const lastMessageDate = props.messages?.at(-1)?.messageDate; + const messageCount = props.messages?.length; + + if (firstMessageDate && lastMessageDate && messageCount) { + return { + firstMessageDate, + lastMessageDate, + messageCount, + }; + } + }; + + return ( + + + + + + + Your first message is from + + {(currentDmOverview) => ( + {currentDmOverview().firstMessageDate.toDateString()} + )} + + + + + + + + + Your last message is from + + {(currentDmOverview) => ( + {currentDmOverview().lastMessageDate.toDateString()} + )} + + + + + + + + + You have been chatting for + + {(currentDmOverview) => ( + + {getDistanceBetweenDatesInDays( + currentDmOverview().firstMessageDate, + currentDmOverview().lastMessageDate, + )} + + )} + + days + + + + + + + + You have written + + {(currentDmOverview) => ( + {currentDmOverview().messageCount.toString()} + )} + + messages + + + + ); +}; diff --git a/src/pages/dm/dm-wordcloud.tsx b/src/pages/dm/dm-wordcloud.tsx new file mode 100644 index 0000000..bd24624 --- /dev/null +++ b/src/pages/dm/dm-wordcloud.tsx @@ -0,0 +1,58 @@ +import type { ChartData } from "chart.js"; +import { Show, type Accessor, type Component } from "solid-js"; +import { WordCloudChart } from "~/components/ui/charts"; +import type { threadMostUsedWordsQuery } from "~/db"; + +const maxWordSize = 100; + +export const DmWordCloud: Component<{ + wordCounts: Awaited> | undefined; +}> = (props) => { + const mostUsedWordChartData: Accessor | undefined> = () => { + const currentMostUsedWordCounts = props.wordCounts; + + if (currentMostUsedWordCounts) { + // ordered descending in db query + const highestWordCount = currentMostUsedWordCounts[0].count; + + const calcWordSizeInPixels = (count: number) => { + return 10 + Math.round((maxWordSize / highestWordCount) * count); + }; + + return { + labels: currentMostUsedWordCounts.map(({ word }) => word), + datasets: [ + { + label: "Used", + data: currentMostUsedWordCounts.map(({ count }) => calcWordSizeInPixels(count)), + }, + ], + }; + } + }; + + return ( + + {(currentMostUsedWordChartData) => ( + // without a container this will scale in height infinitely somehow +
+ +
+ )} +
+ ); +}; diff --git a/src/pages/overview/index.tsx b/src/pages/overview/index.tsx index 528bf57..dfb2544 100644 --- a/src/pages/overview/index.tsx +++ b/src/pages/overview/index.tsx @@ -5,6 +5,7 @@ import { allThreadsOverviewQuery, overallSentMessagesQuery, SELF_ID } from "~/db import { OverviewTable, type RoomOverview } from "./overview-table"; import { getNameFromRecipient } from "~/lib/get-name-from-recipient"; +import { Title } from "@solidjs/meta"; export const Overview: Component = () => { const [allSelfSentMessagesCount] = createResource(() => overallSentMessagesQuery(SELF_ID)); @@ -34,12 +35,16 @@ export const Overview: Component = () => { }); return ( -
-

All messages: {allSelfSentMessagesCount()?.messageCount as number}

- - {(currentRoomOverview) => } - -
+ <> + Signal statistics overview + +
+

All messages: {allSelfSentMessagesCount()?.messageCount as number}

+ + {(currentRoomOverview) => } + +
+ ); }; diff --git a/src/pages/overview/overview-table.tsx b/src/pages/overview/overview-table.tsx index e479072..5e896fb 100644 --- a/src/pages/overview/overview-table.tsx +++ b/src/pages/overview/overview-table.tsx @@ -47,6 +47,14 @@ const archivedFilterFn: FilterFn = (row, _columnId, filterValue) = return !row.original.archived; }; +const isGroupFilterFn: FilterFn = (row, _columnId, filterValue) => { + if (filterValue === true) { + return true; + } + + return !row.original.isGroup; +}; + const SortingDisplay: Component<{ sorting: false | SortDirection; class?: string; activeClass?: string }> = (props) => { return ( @@ -166,7 +174,7 @@ export const columns = [ filterFn: archivedFilterFn, }), columnHelper.accessor("isGroup", { - header: "Group", + header: "isGroup", cell: (props) => { return ( @@ -174,6 +182,7 @@ export const columns = [ ); }, + filterFn: isGroupFilterFn, }), ]; @@ -193,6 +202,10 @@ export const OverviewTable = (props: OverviewTableProps) => { id: "archived", value: false, }, + { + id: "isGroup", + value: false, + }, ]); const table = createSolidTable({ @@ -250,6 +263,16 @@ export const OverviewTable = (props: OverviewTableProps) => {
+
+ table.getColumn("isGroup")?.setFilterValue(value)} + /> +
+ +
+
diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..99ee51e --- /dev/null +++ b/src/types.ts @@ -0,0 +1,36 @@ +export type MessageOverview = + | { + messageDate: Date; + fromRecipientId: number; + }[] + | undefined; + +export type Recipients = { + recipientId: number; + name: string; +}[]; + +export type MessageStats = { + // indexed by recipientId + person: { + [recipientId: number]: number; + }; + // month from 0 to 11 = from January to December, each month indexed by recipientId + month: { + [recipientId: number]: number; + }[]; + // every date of the chat history, indexed by the date string + date: { + [recipientId: number]: number; + date: Date; + totalMessages: number; + }[]; + // weekdays from 0 to 6 = from Monday to Sunday (not from Sunday to Saturday as in the `Date` object), each weekday indexed by recipientId + weekday: { + [recipientId: number]: number; + }[]; + // hours of the day from 0 - 23, each hour indexed by recipientId + daytime: { + [recipientId: number]: number; + }[]; +};