perf: offload long running tasks to workers, preloading dm page data
This commit is contained in:
parent
cad305aa66
commit
df218b9a56
18 changed files with 524 additions and 297 deletions
|
@ -1 +1,7 @@
|
||||||
|
# pnpm
|
||||||
|
export PNPM_HOME="/home/samuel/.local/share/pnpm"
|
||||||
|
case ":$PATH:" in
|
||||||
|
*":$PNPM_HOME:"*) ;;
|
||||||
|
*) export PATH="$PNPM_HOME:$PATH" ;;
|
||||||
|
esac
|
||||||
pnpm dlx lint-staged
|
pnpm dlx lint-staged
|
|
@ -17,7 +17,7 @@
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@commitlint/cli": "^19.6.1",
|
"@commitlint/cli": "^19.6.1",
|
||||||
"@commitlint/config-conventional": "^19.6.0",
|
"@commitlint/config-conventional": "^19.6.0",
|
||||||
"@types/node": "^22.10.1",
|
"@types/node": "^22.10.2",
|
||||||
"@types/sql.js": "^1.4.9",
|
"@types/sql.js": "^1.4.9",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"better-sqlite3": "^11.7.0",
|
"better-sqlite3": "^11.7.0",
|
||||||
|
@ -25,15 +25,16 @@
|
||||||
"kysely-codegen": "^0.17.0",
|
"kysely-codegen": "^0.17.0",
|
||||||
"lint-staged": "^15.2.11",
|
"lint-staged": "^15.2.11",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"tailwindcss": "^3.4.16",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.4",
|
||||||
"vite-plugin-solid": "^2.11.0"
|
"vite-plugin-solid": "^2.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.7",
|
"@kobalte/core": "^0.13.7",
|
||||||
"@kobalte/tailwindcss": "^0.9.0",
|
"@kobalte/tailwindcss": "^0.9.0",
|
||||||
"@solid-primitives/refs": "^1.0.8",
|
"@solid-primitives/refs": "^1.0.8",
|
||||||
|
"@solid-primitives/workers": "^0.3.0",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.2",
|
"@solidjs/router": "^0.15.2",
|
||||||
"@tanstack/solid-table": "^8.20.5",
|
"@tanstack/solid-table": "^8.20.5",
|
||||||
|
|
128
pnpm-lock.yaml
generated
128
pnpm-lock.yaml
generated
|
@ -13,10 +13,13 @@ importers:
|
||||||
version: 0.13.7(solid-js@1.9.3)
|
version: 0.13.7(solid-js@1.9.3)
|
||||||
'@kobalte/tailwindcss':
|
'@kobalte/tailwindcss':
|
||||||
specifier: ^0.9.0
|
specifier: ^0.9.0
|
||||||
version: 0.9.0(tailwindcss@3.4.16)
|
version: 0.9.0(tailwindcss@3.4.17)
|
||||||
'@solid-primitives/refs':
|
'@solid-primitives/refs':
|
||||||
specifier: ^1.0.8
|
specifier: ^1.0.8
|
||||||
version: 1.0.8(solid-js@1.9.3)
|
version: 1.0.8(solid-js@1.9.3)
|
||||||
|
'@solid-primitives/workers':
|
||||||
|
specifier: ^0.3.0
|
||||||
|
version: 0.3.0(solid-js@1.9.3)
|
||||||
'@solidjs/meta':
|
'@solidjs/meta':
|
||||||
specifier: ^0.29.4
|
specifier: ^0.29.4
|
||||||
version: 0.29.4(solid-js@1.9.3)
|
version: 0.29.4(solid-js@1.9.3)
|
||||||
|
@ -70,20 +73,20 @@ importers:
|
||||||
version: 2.5.5
|
version: 2.5.5
|
||||||
tailwindcss-animate:
|
tailwindcss-animate:
|
||||||
specifier: ^1.0.7
|
specifier: ^1.0.7
|
||||||
version: 1.0.7(tailwindcss@3.4.16)
|
version: 1.0.7(tailwindcss@3.4.17)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.9.4
|
specifier: 1.9.4
|
||||||
version: 1.9.4
|
version: 1.9.4
|
||||||
'@commitlint/cli':
|
'@commitlint/cli':
|
||||||
specifier: ^19.6.1
|
specifier: ^19.6.1
|
||||||
version: 19.6.1(@types/node@22.10.1)(typescript@5.7.2)
|
version: 19.6.1(@types/node@22.10.2)(typescript@5.7.2)
|
||||||
'@commitlint/config-conventional':
|
'@commitlint/config-conventional':
|
||||||
specifier: ^19.6.0
|
specifier: ^19.6.0
|
||||||
version: 19.6.0
|
version: 19.6.0
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.10.1
|
specifier: ^22.10.2
|
||||||
version: 22.10.1
|
version: 22.10.2
|
||||||
'@types/sql.js':
|
'@types/sql.js':
|
||||||
specifier: ^1.4.9
|
specifier: ^1.4.9
|
||||||
version: 1.4.9
|
version: 1.4.9
|
||||||
|
@ -106,17 +109,17 @@ importers:
|
||||||
specifier: ^8.4.49
|
specifier: ^8.4.49
|
||||||
version: 8.4.49
|
version: 8.4.49
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.16
|
specifier: ^3.4.17
|
||||||
version: 3.4.16
|
version: 3.4.17
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.7.2
|
specifier: ^5.7.2
|
||||||
version: 5.7.2
|
version: 5.7.2
|
||||||
vite:
|
vite:
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.4
|
||||||
version: 6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1)
|
version: 6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1)
|
||||||
vite-plugin-solid:
|
vite-plugin-solid:
|
||||||
specifier: ^2.11.0
|
specifier: ^2.11.0
|
||||||
version: 2.11.0(solid-js@1.9.3)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1))
|
version: 2.11.0(solid-js@1.9.3)(vite@6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
@ -499,6 +502,10 @@ packages:
|
||||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.8':
|
||||||
|
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2':
|
'@jridgewell/resolve-uri@3.1.2':
|
||||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
|
@ -697,6 +704,11 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
solid-js: ^1.6.12
|
solid-js: ^1.6.12
|
||||||
|
|
||||||
|
'@solid-primitives/workers@0.3.0':
|
||||||
|
resolution: {integrity: sha512-NdfdHHNn4ut6zWoL/xSAZbIuzdrefwc2jin9Oaa/bI1o1QDKdOBQpR07ig7CzpiVdctyeSk6iE8ab6gnZVSSzQ==}
|
||||||
|
peerDependencies:
|
||||||
|
solid-js: ^1.6.12
|
||||||
|
|
||||||
'@solidjs/meta@0.29.4':
|
'@solidjs/meta@0.29.4':
|
||||||
resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==}
|
resolution: {integrity: sha512-zdIWBGpR9zGx1p1bzIPqF5Gs+Ks/BH8R6fWhmUa/dcK1L2rUC8BAcZJzNRYBQv74kScf1TSOs0EY//Vd/I0V8g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -750,8 +762,8 @@ packages:
|
||||||
'@types/hammerjs@2.0.46':
|
'@types/hammerjs@2.0.46':
|
||||||
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
|
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
|
||||||
|
|
||||||
'@types/node@22.10.1':
|
'@types/node@22.10.2':
|
||||||
resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
|
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
|
||||||
|
|
||||||
'@types/sql.js@1.4.9':
|
'@types/sql.js@1.4.9':
|
||||||
resolution: {integrity: sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==}
|
resolution: {integrity: sha512-ep8b36RKHlgWPqjNG9ToUrPiwkhwh0AEzy883mO5Xnd+cL6VBH1EvSjBAAuxLUFF2Vn/moE3Me6v9E1Lo+48GQ==}
|
||||||
|
@ -1277,6 +1289,10 @@ packages:
|
||||||
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
|
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-core-module@2.16.0:
|
||||||
|
resolution: {integrity: sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
is-extglob@2.1.1:
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -1323,8 +1339,8 @@ packages:
|
||||||
jackspeak@3.4.3:
|
jackspeak@3.4.3:
|
||||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||||
|
|
||||||
jiti@1.21.6:
|
jiti@1.21.7:
|
||||||
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
jiti@2.4.2:
|
jiti@2.4.2:
|
||||||
|
@ -1727,6 +1743,10 @@ packages:
|
||||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
resolve@1.22.9:
|
||||||
|
resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
restore-cursor@5.1.0:
|
restore-cursor@5.1.0:
|
||||||
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
@ -1892,8 +1912,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=3.0.0 || insiders'
|
tailwindcss: '>=3.0.0 || insiders'
|
||||||
|
|
||||||
tailwindcss@3.4.16:
|
tailwindcss@3.4.17:
|
||||||
resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==}
|
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
@ -1968,8 +1988,8 @@ packages:
|
||||||
'@testing-library/jest-dom':
|
'@testing-library/jest-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vite@6.0.3:
|
vite@6.0.4:
|
||||||
resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==}
|
resolution: {integrity: sha512-zwlH6ar+6o6b4Wp+ydhtIKLrGM/LoqZzcdVmkGAFun0KHTzIzjh+h0kungEx7KJg/PYnC80I4TII9WkjciSR6Q==}
|
||||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2213,11 +2233,11 @@ snapshots:
|
||||||
'@biomejs/cli-win32-x64@1.9.4':
|
'@biomejs/cli-win32-x64@1.9.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@commitlint/cli@19.6.1(@types/node@22.10.1)(typescript@5.7.2)':
|
'@commitlint/cli@19.6.1(@types/node@22.10.2)(typescript@5.7.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@commitlint/format': 19.5.0
|
'@commitlint/format': 19.5.0
|
||||||
'@commitlint/lint': 19.6.0
|
'@commitlint/lint': 19.6.0
|
||||||
'@commitlint/load': 19.6.1(@types/node@22.10.1)(typescript@5.7.2)
|
'@commitlint/load': 19.6.1(@types/node@22.10.2)(typescript@5.7.2)
|
||||||
'@commitlint/read': 19.5.0
|
'@commitlint/read': 19.5.0
|
||||||
'@commitlint/types': 19.5.0
|
'@commitlint/types': 19.5.0
|
||||||
tinyexec: 0.3.1
|
tinyexec: 0.3.1
|
||||||
|
@ -2264,7 +2284,7 @@ snapshots:
|
||||||
'@commitlint/rules': 19.6.0
|
'@commitlint/rules': 19.6.0
|
||||||
'@commitlint/types': 19.5.0
|
'@commitlint/types': 19.5.0
|
||||||
|
|
||||||
'@commitlint/load@19.6.1(@types/node@22.10.1)(typescript@5.7.2)':
|
'@commitlint/load@19.6.1(@types/node@22.10.2)(typescript@5.7.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@commitlint/config-validator': 19.5.0
|
'@commitlint/config-validator': 19.5.0
|
||||||
'@commitlint/execute-rule': 19.5.0
|
'@commitlint/execute-rule': 19.5.0
|
||||||
|
@ -2272,7 +2292,7 @@ snapshots:
|
||||||
'@commitlint/types': 19.5.0
|
'@commitlint/types': 19.5.0
|
||||||
chalk: 5.3.0
|
chalk: 5.3.0
|
||||||
cosmiconfig: 9.0.0(typescript@5.7.2)
|
cosmiconfig: 9.0.0(typescript@5.7.2)
|
||||||
cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
|
cosmiconfig-typescript-loader: 6.1.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
|
||||||
lodash.isplainobject: 4.0.6
|
lodash.isplainobject: 4.0.6
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
lodash.uniq: 4.5.0
|
lodash.uniq: 4.5.0
|
||||||
|
@ -2434,6 +2454,12 @@ snapshots:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.8':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/set-array': 1.2.1
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2': {}
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
'@jridgewell/set-array@1.2.1': {}
|
'@jridgewell/set-array@1.2.1': {}
|
||||||
|
@ -2457,9 +2483,9 @@ snapshots:
|
||||||
solid-presence: 0.1.8(solid-js@1.9.3)
|
solid-presence: 0.1.8(solid-js@1.9.3)
|
||||||
solid-prevent-scroll: 0.1.10(solid-js@1.9.3)
|
solid-prevent-scroll: 0.1.10(solid-js@1.9.3)
|
||||||
|
|
||||||
'@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.16)':
|
'@kobalte/tailwindcss@0.9.0(tailwindcss@3.4.17)':
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.16
|
tailwindcss: 3.4.17
|
||||||
|
|
||||||
'@kobalte/utils@0.9.1(solid-js@1.9.3)':
|
'@kobalte/utils@0.9.1(solid-js@1.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2605,6 +2631,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
solid-js: 1.9.3
|
solid-js: 1.9.3
|
||||||
|
|
||||||
|
'@solid-primitives/workers@0.3.0(solid-js@1.9.3)':
|
||||||
|
dependencies:
|
||||||
|
solid-js: 1.9.3
|
||||||
|
|
||||||
'@solidjs/meta@0.29.4(solid-js@1.9.3)':
|
'@solidjs/meta@0.29.4(solid-js@1.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
solid-js: 1.9.3
|
solid-js: 1.9.3
|
||||||
|
@ -2647,7 +2677,7 @@ snapshots:
|
||||||
|
|
||||||
'@types/conventional-commits-parser@5.0.1':
|
'@types/conventional-commits-parser@5.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.10.1
|
'@types/node': 22.10.2
|
||||||
|
|
||||||
'@types/d3-cloud@1.2.9':
|
'@types/d3-cloud@1.2.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2661,14 +2691,14 @@ snapshots:
|
||||||
|
|
||||||
'@types/hammerjs@2.0.46': {}
|
'@types/hammerjs@2.0.46': {}
|
||||||
|
|
||||||
'@types/node@22.10.1':
|
'@types/node@22.10.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.20.0
|
undici-types: 6.20.0
|
||||||
|
|
||||||
'@types/sql.js@1.4.9':
|
'@types/sql.js@1.4.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/emscripten': 1.39.13
|
'@types/emscripten': 1.39.13
|
||||||
'@types/node': 22.10.1
|
'@types/node': 22.10.2
|
||||||
|
|
||||||
JSONStream@1.3.5:
|
JSONStream@1.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2900,9 +2930,9 @@ snapshots:
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.1)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
|
cosmiconfig-typescript-loader@6.1.0(@types/node@22.10.2)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.10.1
|
'@types/node': 22.10.2
|
||||||
cosmiconfig: 9.0.0(typescript@5.7.2)
|
cosmiconfig: 9.0.0(typescript@5.7.2)
|
||||||
jiti: 2.4.2
|
jiti: 2.4.2
|
||||||
typescript: 5.7.2
|
typescript: 5.7.2
|
||||||
|
@ -3183,6 +3213,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
is-core-module@2.16.0:
|
||||||
|
dependencies:
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
@ -3217,7 +3251,7 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@pkgjs/parseargs': 0.11.0
|
'@pkgjs/parseargs': 0.11.0
|
||||||
|
|
||||||
jiti@1.21.6: {}
|
jiti@1.21.7: {}
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
|
|
||||||
|
@ -3460,7 +3494,7 @@ snapshots:
|
||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
read-cache: 1.0.0
|
read-cache: 1.0.0
|
||||||
resolve: 1.22.8
|
resolve: 1.22.9
|
||||||
|
|
||||||
postcss-js@4.0.1(postcss@8.4.49):
|
postcss-js@4.0.1(postcss@8.4.49):
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3553,6 +3587,12 @@ snapshots:
|
||||||
path-parse: 1.0.7
|
path-parse: 1.0.7
|
||||||
supports-preserve-symlinks-flag: 1.0.0
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
|
resolve@1.22.9:
|
||||||
|
dependencies:
|
||||||
|
is-core-module: 2.16.0
|
||||||
|
path-parse: 1.0.7
|
||||||
|
supports-preserve-symlinks-flag: 1.0.0
|
||||||
|
|
||||||
restore-cursor@5.1.0:
|
restore-cursor@5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
onetime: 7.0.0
|
onetime: 7.0.0
|
||||||
|
@ -3706,7 +3746,7 @@ snapshots:
|
||||||
|
|
||||||
sucrase@3.35.0:
|
sucrase@3.35.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': 0.3.5
|
'@jridgewell/gen-mapping': 0.3.8
|
||||||
commander: 4.1.1
|
commander: 4.1.1
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
@ -3726,11 +3766,11 @@ snapshots:
|
||||||
|
|
||||||
tailwind-merge@2.5.5: {}
|
tailwind-merge@2.5.5: {}
|
||||||
|
|
||||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.16):
|
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.16
|
tailwindcss: 3.4.17
|
||||||
|
|
||||||
tailwindcss@3.4.16:
|
tailwindcss@3.4.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@alloc/quick-lru': 5.2.0
|
'@alloc/quick-lru': 5.2.0
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
|
@ -3740,7 +3780,7 @@ snapshots:
|
||||||
fast-glob: 3.3.2
|
fast-glob: 3.3.2
|
||||||
glob-parent: 6.0.2
|
glob-parent: 6.0.2
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
jiti: 1.21.6
|
jiti: 1.21.7
|
||||||
lilconfig: 3.1.3
|
lilconfig: 3.1.3
|
||||||
micromatch: 4.0.8
|
micromatch: 4.0.8
|
||||||
normalize-path: 3.0.0
|
normalize-path: 3.0.0
|
||||||
|
@ -3752,7 +3792,7 @@ snapshots:
|
||||||
postcss-load-config: 4.0.2(postcss@8.4.49)
|
postcss-load-config: 4.0.2(postcss@8.4.49)
|
||||||
postcss-nested: 6.2.0(postcss@8.4.49)
|
postcss-nested: 6.2.0(postcss@8.4.49)
|
||||||
postcss-selector-parser: 6.1.2
|
postcss-selector-parser: 6.1.2
|
||||||
resolve: 1.22.8
|
resolve: 1.22.9
|
||||||
sucrase: 3.35.0
|
sucrase: 3.35.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- ts-node
|
- ts-node
|
||||||
|
@ -3814,7 +3854,7 @@ snapshots:
|
||||||
|
|
||||||
validate-html-nesting@1.2.2: {}
|
validate-html-nesting@1.2.2: {}
|
||||||
|
|
||||||
vite-plugin-solid@2.11.0(solid-js@1.9.3)(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1)):
|
vite-plugin-solid@2.11.0(solid-js@1.9.3)(vite@6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.26.0
|
'@babel/core': 7.26.0
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
|
@ -3822,25 +3862,25 @@ snapshots:
|
||||||
merge-anything: 5.1.7
|
merge-anything: 5.1.7
|
||||||
solid-js: 1.9.3
|
solid-js: 1.9.3
|
||||||
solid-refresh: 0.6.3(solid-js@1.9.3)
|
solid-refresh: 0.6.3(solid-js@1.9.3)
|
||||||
vite: 6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1)
|
vite: 6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1)
|
||||||
vitefu: 1.0.4(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1))
|
vitefu: 1.0.4(vite@6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1))
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
vite@6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1):
|
vite@6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.24.0
|
esbuild: 0.24.0
|
||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
rollup: 4.28.1
|
rollup: 4.28.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 22.10.1
|
'@types/node': 22.10.2
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.4.2
|
jiti: 2.4.2
|
||||||
yaml: 2.6.1
|
yaml: 2.6.1
|
||||||
|
|
||||||
vitefu@1.0.4(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1)):
|
vitefu@1.0.4(vite@6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 6.0.3(@types/node@22.10.1)(jiti@2.4.2)(yaml@2.6.1)
|
vite: 6.0.4(@types/node@22.10.2)(jiti@2.4.2)(yaml@2.6.1)
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js";
|
||||||
import { Route } from "@solidjs/router";
|
import { Route } from "@solidjs/router";
|
||||||
import { DmId, GroupId, Home, Overview } from "./pages";
|
import { DmId, GroupId, Home, Overview, preloadDmId } from "./pages";
|
||||||
|
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ const App: Component = () => {
|
||||||
<>
|
<>
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/overview" component={Overview} />
|
<Route path="/overview" component={Overview} />
|
||||||
<Route path="/dm/:dmid" component={DmId} />
|
<Route path="/dm/:dmid" component={DmId} preload={preloadDmId} />
|
||||||
<Route path="/group/:groupid" component={GroupId} />
|
<Route path="/group/:groupid" component={GroupId} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
117
src/db-queries.ts
Normal file
117
src/db-queries.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { sql, type NotNull } from "kysely";
|
||||||
|
import { cached } from "./lib/db-cache";
|
||||||
|
import { kyselyDb, SELF_ID } from "./db";
|
||||||
|
|
||||||
|
const allThreadsOverviewQueryRaw = () =>
|
||||||
|
kyselyDb()
|
||||||
|
?.selectFrom("thread")
|
||||||
|
.innerJoin(
|
||||||
|
(eb) =>
|
||||||
|
eb
|
||||||
|
.selectFrom("message")
|
||||||
|
.select((eb) => ["message.thread_id", eb.fn.countAll().as("message_count")])
|
||||||
|
.where((eb) => {
|
||||||
|
return eb.and([eb("message.body", "is not", null), eb("message.body", "is not", "")]);
|
||||||
|
})
|
||||||
|
.groupBy("message.thread_id")
|
||||||
|
.as("message"),
|
||||||
|
(join) => join.onRef("message.thread_id", "=", "thread._id"),
|
||||||
|
)
|
||||||
|
.innerJoin("recipient", "thread.recipient_id", "recipient._id")
|
||||||
|
.leftJoin("groups", "recipient._id", "groups.recipient_id")
|
||||||
|
.select([
|
||||||
|
"thread._id as thread_id",
|
||||||
|
"thread.recipient_id",
|
||||||
|
"thread.archived",
|
||||||
|
"recipient.profile_joined_name",
|
||||||
|
"recipient.system_joined_name",
|
||||||
|
"groups.title",
|
||||||
|
"message_count",
|
||||||
|
"thread.date as last_message_date",
|
||||||
|
"recipient.nickname_joined_name",
|
||||||
|
])
|
||||||
|
.where("message_count", ">", 0)
|
||||||
|
.$narrowType<{
|
||||||
|
thread_id: NotNull;
|
||||||
|
archived: NotNull;
|
||||||
|
message_count: number;
|
||||||
|
}>()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
export const allThreadsOverviewQuery = cached(allThreadsOverviewQueryRaw);
|
||||||
|
|
||||||
|
const overallSentMessagesQueryRaw = (recipientId: number) =>
|
||||||
|
kyselyDb()
|
||||||
|
?.selectFrom("message")
|
||||||
|
.select((eb) => eb.fn.countAll().as("messageCount"))
|
||||||
|
.where((eb) =>
|
||||||
|
eb.and([
|
||||||
|
eb("message.from_recipient_id", "=", recipientId),
|
||||||
|
eb("message.body", "is not", null),
|
||||||
|
eb("message.body", "!=", ""),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
export const overallSentMessagesQuery = cached(overallSentMessagesQueryRaw);
|
||||||
|
|
||||||
|
const dmPartnerRecipientQueryRaw = (dmId: number) =>
|
||||||
|
kyselyDb()
|
||||||
|
?.selectFrom("recipient")
|
||||||
|
.select([
|
||||||
|
"recipient._id",
|
||||||
|
"recipient.system_joined_name",
|
||||||
|
"recipient.profile_joined_name",
|
||||||
|
"recipient.nickname_joined_name",
|
||||||
|
])
|
||||||
|
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
||||||
|
.where((eb) => eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]))
|
||||||
|
.$narrowType<{
|
||||||
|
_id: number;
|
||||||
|
}>()
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw);
|
||||||
|
|
||||||
|
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
||||||
|
kyselyDb()
|
||||||
|
?.selectFrom("message")
|
||||||
|
.select(["from_recipient_id", sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime")])
|
||||||
|
.orderBy(["message_datetime"])
|
||||||
|
.where((eb) => eb.and([eb("body", "is not", null), eb("body", "!=", ""), eb("thread_id", "=", threadId)]))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
export const threadSentMessagesOverviewQuery = cached(threadSentMessagesOverviewQueryRaw);
|
||||||
|
|
||||||
|
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
||||||
|
kyselyDb()
|
||||||
|
?.withRecursive("words", (eb) => {
|
||||||
|
return eb
|
||||||
|
.selectFrom("message")
|
||||||
|
.select([
|
||||||
|
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
||||||
|
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
||||||
|
])
|
||||||
|
.where((eb) => eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]))
|
||||||
|
.unionAll((ebInner) => {
|
||||||
|
return ebInner
|
||||||
|
.selectFrom("words")
|
||||||
|
.select([
|
||||||
|
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as("word"),
|
||||||
|
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
||||||
|
])
|
||||||
|
.where("rest", "<>", "");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.selectFrom("words")
|
||||||
|
.select((eb) => ["word", eb.fn.countAll().as("count")])
|
||||||
|
.where("word", "<>", "")
|
||||||
|
.groupBy("word")
|
||||||
|
.orderBy("count desc")
|
||||||
|
.limit(limit)
|
||||||
|
.$narrowType<{
|
||||||
|
count: number;
|
||||||
|
}>()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
export const threadMostUsedWordsQuery = cached(threadMostUsedWordsQueryRaw);
|
119
src/db.ts
119
src/db.ts
|
@ -1,12 +1,11 @@
|
||||||
import { createEffect, createMemo, createRoot, createSignal } from "solid-js";
|
import { createEffect, createMemo, createRoot, createSignal } from "solid-js";
|
||||||
|
|
||||||
import { Kysely, sql, type NotNull } from "kysely";
|
import { Kysely } from "kysely";
|
||||||
import type { DB } from "kysely-codegen";
|
import type { DB } from "kysely-codegen";
|
||||||
import { SqlJsDialect } from "kysely-wasm";
|
import { SqlJsDialect } from "kysely-wasm";
|
||||||
import initSqlJS, { type Database } from "sql.js";
|
import initSqlJS, { type Database } from "sql.js";
|
||||||
|
|
||||||
import wasmURL from "./assets/sql-wasm.wasm?url";
|
import wasmURL from "./assets/sql-wasm.wasm?url";
|
||||||
import { cached } from "./lib/db-cache";
|
|
||||||
|
|
||||||
export const SELF_ID = 2;
|
export const SELF_ID = 2;
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ const sqlJsDialect = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const kyselyDb = createRoot(() => {
|
export const kyselyDb = createRoot(() => {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const currentDb = db();
|
const currentDb = db();
|
||||||
|
|
||||||
|
@ -49,117 +48,3 @@ const kyselyDb = createRoot(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const allThreadsOverviewQueryRaw = () =>
|
|
||||||
kyselyDb()
|
|
||||||
?.selectFrom("thread")
|
|
||||||
.innerJoin(
|
|
||||||
(eb) =>
|
|
||||||
eb
|
|
||||||
.selectFrom("message")
|
|
||||||
.select((eb) => ["message.thread_id", eb.fn.countAll().as("message_count")])
|
|
||||||
.where((eb) => {
|
|
||||||
return eb.and([eb("message.body", "is not", null), eb("message.body", "is not", "")]);
|
|
||||||
})
|
|
||||||
.groupBy("message.thread_id")
|
|
||||||
.as("message"),
|
|
||||||
(join) => join.onRef("message.thread_id", "=", "thread._id"),
|
|
||||||
)
|
|
||||||
.innerJoin("recipient", "thread.recipient_id", "recipient._id")
|
|
||||||
.leftJoin("groups", "recipient._id", "groups.recipient_id")
|
|
||||||
.select([
|
|
||||||
"thread._id as thread_id",
|
|
||||||
"thread.recipient_id",
|
|
||||||
"thread.archived",
|
|
||||||
"recipient.profile_joined_name",
|
|
||||||
"recipient.system_joined_name",
|
|
||||||
"groups.title",
|
|
||||||
"message_count",
|
|
||||||
"thread.date as last_message_date",
|
|
||||||
"recipient.nickname_joined_name",
|
|
||||||
])
|
|
||||||
.where("message_count", ">", 0)
|
|
||||||
.$narrowType<{
|
|
||||||
thread_id: NotNull;
|
|
||||||
archived: NotNull;
|
|
||||||
message_count: number;
|
|
||||||
}>()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
export const allThreadsOverviewQuery = cached(allThreadsOverviewQueryRaw);
|
|
||||||
|
|
||||||
const overallSentMessagesQueryRaw = (recipientId: number) =>
|
|
||||||
kyselyDb()
|
|
||||||
?.selectFrom("message")
|
|
||||||
.select((eb) => eb.fn.countAll().as("messageCount"))
|
|
||||||
.where((eb) =>
|
|
||||||
eb.and([
|
|
||||||
eb("message.from_recipient_id", "=", recipientId),
|
|
||||||
eb("message.body", "is not", null),
|
|
||||||
eb("message.body", "!=", ""),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
export const overallSentMessagesQuery = cached(overallSentMessagesQueryRaw);
|
|
||||||
|
|
||||||
const dmPartnerRecipientQueryRaw = (dmId: number) =>
|
|
||||||
kyselyDb()
|
|
||||||
?.selectFrom("recipient")
|
|
||||||
.select([
|
|
||||||
"recipient._id",
|
|
||||||
"recipient.system_joined_name",
|
|
||||||
"recipient.profile_joined_name",
|
|
||||||
"recipient.nickname_joined_name",
|
|
||||||
])
|
|
||||||
.innerJoin("thread", "recipient._id", "thread.recipient_id")
|
|
||||||
.where((eb) => eb.and([eb("thread._id", "=", dmId), eb("recipient._id", "!=", SELF_ID)]))
|
|
||||||
.$narrowType<{
|
|
||||||
_id: number;
|
|
||||||
}>()
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
export const dmPartnerRecipientQuery = cached(dmPartnerRecipientQueryRaw);
|
|
||||||
|
|
||||||
const threadSentMessagesOverviewQueryRaw = (threadId: number) =>
|
|
||||||
kyselyDb()
|
|
||||||
?.selectFrom("message")
|
|
||||||
.select(["from_recipient_id", sql<string>`datetime(date_sent / 1000, 'unixepoch')`.as("message_datetime")])
|
|
||||||
.orderBy(["message_datetime"])
|
|
||||||
.where((eb) => eb.and([eb("body", "is not", null), eb("body", "!=", ""), eb("thread_id", "=", threadId)]))
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
export const threadSentMessagesOverviewQuery = cached(threadSentMessagesOverviewQueryRaw);
|
|
||||||
|
|
||||||
const threadMostUsedWordsQueryRaw = (threadId: number, limit = 10) =>
|
|
||||||
kyselyDb()
|
|
||||||
?.withRecursive("words", (eb) => {
|
|
||||||
return eb
|
|
||||||
.selectFrom("message")
|
|
||||||
.select([
|
|
||||||
sql`LOWER(substr(body, 1, instr(body || " ", " ") - 1))`.as("word"),
|
|
||||||
sql`(substr(body, instr(body || " ", " ") + 1))`.as("rest"),
|
|
||||||
])
|
|
||||||
.where((eb) => eb.and([eb("body", "is not", null), eb("thread_id", "=", threadId)]))
|
|
||||||
.unionAll((ebInner) => {
|
|
||||||
return ebInner
|
|
||||||
.selectFrom("words")
|
|
||||||
.select([
|
|
||||||
sql`LOWER(substr(rest, 1, instr(rest || " ", " ") - 1))`.as("word"),
|
|
||||||
sql`(substr(rest, instr(rest || " ", " ") + 1))`.as("rest"),
|
|
||||||
])
|
|
||||||
.where("rest", "<>", "");
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.selectFrom("words")
|
|
||||||
.select((eb) => ["word", eb.fn.countAll().as("count")])
|
|
||||||
.where("word", "<>", "")
|
|
||||||
.groupBy("word")
|
|
||||||
.orderBy("count desc")
|
|
||||||
.limit(limit)
|
|
||||||
.$narrowType<{
|
|
||||||
count: number;
|
|
||||||
}>()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
export const threadMostUsedWordsQuery = cached(threadMostUsedWordsQueryRaw);
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { render } from "solid-js/web";
|
import { render } from "solid-js/web";
|
||||||
import { Router } from "@solidjs/router";
|
import { Router, useNavigate } from "@solidjs/router";
|
||||||
import { MetaProvider } from "@solidjs/meta";
|
import { MetaProvider } from "@solidjs/meta";
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import { createEffect } from "solid-js";
|
||||||
|
import { db } from "./db";
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
|
|
||||||
|
@ -18,7 +20,20 @@ if (root) {
|
||||||
() => (
|
() => (
|
||||||
<div class="mx-auto max-w-screen-2xl">
|
<div class="mx-auto max-w-screen-2xl">
|
||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Router>
|
<Router
|
||||||
|
root={(props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { pathname } = props.location;
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!db() && pathname !== "/") {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return props.children;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<App />
|
<App />
|
||||||
</Router>
|
</Router>
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { createRoot, on, createDeferred } from "solid-js";
|
import { on, createSignal, createEffect, createRoot, createMemo } from "solid-js";
|
||||||
import { serialize, deserialize } from "seroval";
|
import { serialize, deserialize } from "seroval";
|
||||||
|
import { createSignaledWorker } from "@solid-primitives/workers";
|
||||||
|
import { db } from "~/db";
|
||||||
|
|
||||||
const DATABASE_HASH_PREFIX = "database";
|
const DATABASE_HASH_PREFIX = "database";
|
||||||
|
|
||||||
|
@ -30,31 +32,48 @@ const hashString = (str: string) => {
|
||||||
|
|
||||||
const HASH_STORE_KEY = `${DATABASE_HASH_PREFIX}_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
|
|
||||||
createRoot(() => {
|
createRoot(() => {
|
||||||
void import("~/db").then(({ db }) => {
|
const [dbHash, setDbHash] = createSignal(localStorage.getItem(HASH_STORE_KEY));
|
||||||
// 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);
|
// offloaded because this can take a long time (>1s easily) and would block the mainthread
|
||||||
|
createSignaledWorker({
|
||||||
|
input: db,
|
||||||
|
output: setDbHash,
|
||||||
|
func: function hashDb(currentDb: ReturnType<typeof db>) {
|
||||||
|
const hashString = (str: string) => {
|
||||||
|
let hash = 0,
|
||||||
|
i,
|
||||||
|
chr;
|
||||||
|
if (str.length === 0) return hash;
|
||||||
|
for (i = 0; i < str.length; i++) {
|
||||||
|
chr = str.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + chr;
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
if (newHash !== oldHash) {
|
if (currentDb?.export) {
|
||||||
|
return hashString(new TextDecoder().decode(currentDb.export())).toString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
on(dbHash, (currentDbHash) => {
|
||||||
|
if (currentDbHash) {
|
||||||
clearDbCache();
|
clearDbCache();
|
||||||
|
|
||||||
localStorage.setItem(HASH_STORE_KEY, newHash);
|
localStorage.setItem(HASH_STORE_KEY, currentDbHash);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
class LocalStorageCacheAdapter {
|
class LocalStorageCacheAdapter {
|
||||||
keys = new Set<string>(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)));
|
keys = new Set<string>(Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)));
|
||||||
prefix = "database";
|
prefix = "database";
|
||||||
|
#dbLoaded = createMemo(() => !!db());
|
||||||
|
|
||||||
#createKey(cacheName: string, key: string): string {
|
#createKey(cacheName: string, key: string): string {
|
||||||
return `${this.prefix}-${cacheName}-${key}`;
|
return `${this.prefix}-${cacheName}-${key}`;
|
||||||
|
@ -76,15 +95,25 @@ class LocalStorageCacheAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
has(cacheName: string, key: string): boolean {
|
has(cacheName: string, key: string): boolean {
|
||||||
|
if (this.#dbLoaded()) {
|
||||||
return this.keys.has(this.#createKey(cacheName, key));
|
return this.keys.has(this.#createKey(cacheName, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
console.info("No database loaded");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
get<R>(cacheName: string, key: string): R | undefined {
|
get<R>(cacheName: string, key: string): R | undefined {
|
||||||
|
if (this.#dbLoaded()) {
|
||||||
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
const item = localStorage.getItem(this.#createKey(cacheName, key));
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
return deserialize(item) as R;
|
return deserialize(item) as R;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.info("No database loaded");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
109
src/lib/messages-worker.ts
Normal file
109
src/lib/messages-worker.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { getHourList, getMonthList, getWeekdayList } from "./date";
|
||||||
|
import type { MessageOverview, MessageStats, Recipients } from "~/types";
|
||||||
|
|
||||||
|
const hourNames = getHourList();
|
||||||
|
|
||||||
|
const initialHourMap = [...hourNames.keys()];
|
||||||
|
|
||||||
|
const monthNames = getMonthList();
|
||||||
|
|
||||||
|
const initialMonthMap = [...monthNames.keys()];
|
||||||
|
|
||||||
|
const weekdayNames = getWeekdayList();
|
||||||
|
|
||||||
|
const initialWeekdayMap = [...weekdayNames.keys()];
|
||||||
|
|
||||||
|
function createMessageStatsSourcesRaw(messageOverview: MessageOverview, recipients: Recipients) {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialRecipientMap = () => {
|
||||||
|
return Object.fromEntries(recipients.map(({ recipientId }) => [recipientId, 0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateList = () => {
|
||||||
|
const firstDate = messageOverview?.at(0)?.messageDate;
|
||||||
|
const lastDate = messageOverview?.at(-1)?.messageDate;
|
||||||
|
if (firstDate && lastDate) {
|
||||||
|
return getDateList(firstDate, lastDate).map((date) => ({
|
||||||
|
totalMessages: 0,
|
||||||
|
date,
|
||||||
|
...initialRecipientMap(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentDateList = dateList();
|
||||||
|
const currentInitialRecipientMap = initialRecipientMap();
|
||||||
|
|
||||||
|
const messageStats: MessageStats = {
|
||||||
|
person: { ...currentInitialRecipientMap },
|
||||||
|
month: initialMonthMap.map(() => ({ ...currentInitialRecipientMap })),
|
||||||
|
date: currentDateList ?? [],
|
||||||
|
weekday: initialWeekdayMap.map(() => ({ ...currentInitialRecipientMap })),
|
||||||
|
daytime: initialHourMap.map(() => ({ ...currentInitialRecipientMap })),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentDateList) {
|
||||||
|
const { person, month, date, weekday, daytime } = messageStats;
|
||||||
|
|
||||||
|
for (const message of messageOverview) {
|
||||||
|
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
|
||||||
|
month[messageDate.getMonth()][message.fromRecipientId] += 1;
|
||||||
|
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||||
|
const dateStatsEntry = date.find(({ date }) => date.toDateString() === messageDate.toDateString())!;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// increment the message count of the message's daytime for this recipient
|
||||||
|
daytime[messageDate.getHours()][message.fromRecipientId] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = (event: MessageEvent<{ dmId: number; messageOverview: MessageOverview; recipients: Recipients }>) => {
|
||||||
|
const result = createMessageStatsSourcesRaw(event.data.messageOverview, event.data.recipients);
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
dmId: event.data.dmId,
|
||||||
|
messageStatsSources: result,
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,82 +1,37 @@
|
||||||
import { getDateList, getHourList, getMonthList, getWeekdayList } from "./date";
|
|
||||||
import type { MessageOverview, MessageStats, Recipients } from "~/types";
|
import type { MessageOverview, MessageStats, Recipients } from "~/types";
|
||||||
import { isSameDay } from "date-fns";
|
|
||||||
import { cached } from "./db-cache";
|
import { cached } from "./db-cache";
|
||||||
|
import { getHourList, getMonthList, getWeekdayList } from "./date";
|
||||||
|
import MessageStatsWorker from "./messages-worker?worker";
|
||||||
|
|
||||||
export const hourNames = getHourList();
|
export const hourNames = getHourList();
|
||||||
|
|
||||||
const initialHoursMap = [...hourNames.keys()];
|
|
||||||
|
|
||||||
export const monthNames = getMonthList();
|
export const monthNames = getMonthList();
|
||||||
|
|
||||||
const initialMonthMap = [...monthNames.keys()];
|
|
||||||
|
|
||||||
export const weekdayNames = getWeekdayList();
|
export const weekdayNames = getWeekdayList();
|
||||||
|
|
||||||
const initialWeekdayMap = [...weekdayNames.keys()];
|
const messageStatsWorker = new MessageStatsWorker();
|
||||||
|
|
||||||
const createMessageStatsSourcesRaw = (messageOverview: MessageOverview, recipients: Recipients) => {
|
export const createMessageStatsSources = cached(
|
||||||
const initialRecipientMap = () => {
|
(dmId: number, messageOverview: MessageOverview, recipients: Recipients) => {
|
||||||
return Object.fromEntries(recipients.map(({ recipientId }) => [recipientId, 0]));
|
messageStatsWorker.postMessage({
|
||||||
};
|
dmId,
|
||||||
|
messageOverview,
|
||||||
|
recipients,
|
||||||
|
});
|
||||||
|
|
||||||
const dateList = () => {
|
return new Promise<MessageStats>((resolve) => {
|
||||||
const firstDate = messageOverview?.at(0)?.messageDate;
|
const listener = (
|
||||||
const lastDate = messageOverview?.at(-1)?.messageDate;
|
event: MessageEvent<{
|
||||||
if (firstDate && lastDate) {
|
dmId: number;
|
||||||
return getDateList(firstDate, lastDate).map((date) => ({
|
messageStatsSources: MessageStats;
|
||||||
totalMessages: 0,
|
}>,
|
||||||
date,
|
) => {
|
||||||
...initialRecipientMap(),
|
if (event.data.dmId === dmId) {
|
||||||
}));
|
resolve(event.data.messageStatsSources);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentDateList = dateList();
|
messageStatsWorker.addEventListener("message", listener);
|
||||||
const currentInitialRecipientMap = initialRecipientMap();
|
});
|
||||||
|
},
|
||||||
const messageStats: MessageStats = {
|
);
|
||||||
person: { ...currentInitialRecipientMap },
|
|
||||||
month: initialMonthMap.map(() => ({ ...currentInitialRecipientMap })),
|
|
||||||
date: currentDateList ?? [],
|
|
||||||
weekday: initialWeekdayMap.map(() => ({ ...currentInitialRecipientMap })),
|
|
||||||
daytime: initialHoursMap.map(() => ({ ...currentInitialRecipientMap })),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (currentDateList) {
|
|
||||||
const { person, month, date, weekday, daytime } = messageStats;
|
|
||||||
|
|
||||||
for (const message of messageOverview) {
|
|
||||||
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
|
|
||||||
month[messageDate.getMonth()][message.fromRecipientId] += 1;
|
|
||||||
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
|
||||||
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;
|
|
||||||
|
|
||||||
// increment the message count of the message's daytime for this recipient
|
|
||||||
daytime[messageDate.getHours()][message.fromRecipientId] += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageStats;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createMessageStatsSources = cached(createMessageStatsSourcesRaw);
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { type Component, createMemo, createResource } from "solid-js";
|
import { Suspense, type Component } from "solid-js";
|
||||||
import type { RouteSectionProps } from "@solidjs/router";
|
import { createAsync, type RoutePreloadFunc, type RouteSectionProps } from "@solidjs/router";
|
||||||
|
|
||||||
import { dmPartnerRecipientQuery, SELF_ID, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db";
|
import { dmPartnerRecipientQuery, threadMostUsedWordsQuery, threadSentMessagesOverviewQuery } from "~/db-queries";
|
||||||
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
||||||
import { Heading } from "~/components/ui/heading";
|
import { Heading } from "~/components/ui/heading";
|
||||||
import { Grid } from "~/components/ui/grid";
|
import { Grid } from "~/components/ui/grid";
|
||||||
|
@ -15,13 +15,13 @@ import { DmMessagesPerRecipient } from "./dm-messages-per-recipients";
|
||||||
import { DmMessagesPerWeekday } from "./dm-messages-per-weekday";
|
import { DmMessagesPerWeekday } from "./dm-messages-per-weekday";
|
||||||
import type { MessageOverview } from "~/types";
|
import type { MessageOverview } from "~/types";
|
||||||
import { createMessageStatsSources } from "~/lib/messages";
|
import { createMessageStatsSources } from "~/lib/messages";
|
||||||
|
import { SELF_ID } from "~/db";
|
||||||
|
import { Flex } from "~/components/ui/flex";
|
||||||
|
|
||||||
export const DmId: Component<RouteSectionProps> = (props) => {
|
const getDmIdData = (dmId: number) => {
|
||||||
const dmId = () => Number(props.params.dmid);
|
|
||||||
|
|
||||||
// the other person in the chat with name and id
|
// the other person in the chat with name and id
|
||||||
const [dmPartner] = createResource(async () => {
|
const dmPartner = createAsync(async () => {
|
||||||
const dmPartner = await dmPartnerRecipientQuery(dmId());
|
const dmPartner = await dmPartnerRecipientQuery(dmId);
|
||||||
|
|
||||||
if (dmPartner) {
|
if (dmPartner) {
|
||||||
return {
|
return {
|
||||||
|
@ -35,8 +35,8 @@ export const DmId: Component<RouteSectionProps> = (props) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [dmMessagesOverview] = createResource<MessageOverview | undefined>(async () => {
|
const dmMessagesOverview = createAsync<MessageOverview | undefined>(async () => {
|
||||||
const dmMessageOverview = await threadSentMessagesOverviewQuery(dmId());
|
const dmMessageOverview = await threadSentMessagesOverviewQuery(dmId);
|
||||||
if (dmMessageOverview) {
|
if (dmMessageOverview) {
|
||||||
return dmMessageOverview.map((row) => {
|
return dmMessageOverview.map((row) => {
|
||||||
return {
|
return {
|
||||||
|
@ -47,7 +47,7 @@ export const DmId: Component<RouteSectionProps> = (props) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [mostUsedWordCounts] = createResource(async () => threadMostUsedWordsQuery(dmId(), 300));
|
const mostUsedWordCounts = createAsync(async () => threadMostUsedWordsQuery(dmId, 300));
|
||||||
|
|
||||||
const recipients = () => {
|
const recipients = () => {
|
||||||
const currentDmPartner = dmPartner();
|
const currentDmPartner = dmPartner();
|
||||||
|
@ -63,25 +63,58 @@ export const DmId: Component<RouteSectionProps> = (props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const dmMessageStats = createMemo(() => {
|
const dmMessageStats = createAsync(async () => {
|
||||||
const currentDmMessagesOverview = dmMessagesOverview();
|
const currentDmMessagesOverview = dmMessagesOverview();
|
||||||
const currentRecipients = recipients();
|
const currentRecipients = recipients();
|
||||||
|
|
||||||
if (currentDmMessagesOverview && currentRecipients) {
|
if (currentDmMessagesOverview && currentRecipients) {
|
||||||
return createMessageStatsSources(currentDmMessagesOverview, currentRecipients);
|
return await createMessageStatsSources(dmId, currentDmMessagesOverview, currentRecipients);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
dmPartner,
|
||||||
|
dmMessagesOverview,
|
||||||
|
mostUsedWordCounts,
|
||||||
|
recipients,
|
||||||
|
dmMessageStats,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const preloadDmId: RoutePreloadFunc = (props) => {
|
||||||
|
void getDmIdData(Number(props.params.dmid));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DmId: Component<RouteSectionProps> = (props) => {
|
||||||
|
const { dmPartner, dmMessagesOverview, mostUsedWordCounts, recipients, dmMessageStats } = getDmIdData(
|
||||||
|
Number(props.params.dmid),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title>Dm with {dmPartner()?.name}</Title>
|
<Title>Dm with {dmPartner()?.name}</Title>
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
<Heading level={1}>DM with {dmPartner()?.name}</Heading>
|
<Heading level={1}>DM with {dmPartner()?.name}</Heading>
|
||||||
<Heading level={2}>Chat timeline</Heading>
|
<Heading level={2}>Chat timeline</Heading>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Flex alignItems="center" justifyContent="center" class="h-64">
|
||||||
|
<p class="text-4xl">Loading...</p>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DmMessagesPerDate dateStats={dmMessageStats()?.date} recipients={recipients()} />
|
<DmMessagesPerDate dateStats={dmMessageStats()?.date} recipients={recipients()} />
|
||||||
|
</Suspense>
|
||||||
<DmOverview messages={dmMessagesOverview()} />
|
<DmOverview messages={dmMessagesOverview()} />
|
||||||
<Heading level={2}>Messages per</Heading>
|
<Heading level={2}>Messages per</Heading>
|
||||||
|
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Flex alignItems="center" justifyContent="center" class="h-64">
|
||||||
|
<p class="text-4xl">Loading...</p>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Grid cols={1} colsMd={2} class="gap-x-16 gap-y-16">
|
<Grid cols={1} colsMd={2} class="gap-x-16 gap-y-16">
|
||||||
<div>
|
<div>
|
||||||
<Heading level={3}>Person</Heading>
|
<Heading level={3}>Person</Heading>
|
||||||
|
@ -100,8 +133,17 @@ export const DmId: Component<RouteSectionProps> = (props) => {
|
||||||
<DmMessagesPerWeekday weekdayStats={dmMessageStats()?.weekday} recipients={recipients()} />
|
<DmMessagesPerWeekday weekdayStats={dmMessageStats()?.weekday} recipients={recipients()} />
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Suspense>
|
||||||
<Heading level={2}>Word cloud</Heading>
|
<Heading level={2}>Word cloud</Heading>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<Flex alignItems="center" justifyContent="center" class="h-64">
|
||||||
|
<p class="text-4xl">Loading...</p>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DmWordCloud wordCounts={mostUsedWordCounts()} />
|
<DmWordCloud wordCounts={mostUsedWordCounts()} />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ChartData } from "chart.js";
|
import type { ChartData } from "chart.js";
|
||||||
import { Show, type Accessor, type Component } from "solid-js";
|
import { Show, type Accessor, type Component } from "solid-js";
|
||||||
import { WordCloudChart } from "~/components/ui/charts";
|
import { WordCloudChart } from "~/components/ui/charts";
|
||||||
import type { threadMostUsedWordsQuery } from "~/db";
|
import type { threadMostUsedWordsQuery } from "~/db-queries";
|
||||||
|
|
||||||
const maxWordSize = 100;
|
const maxWordSize = 100;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { createSignal, Show, type Component, type JSX } from "solid-js";
|
import { createSignal, Show, type Component, type JSX } from "solid-js";
|
||||||
import { type RouteSectionProps, useNavigate } from "@solidjs/router";
|
import { type RouteSectionProps, useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
import { setDb, SQL } from "~/db";
|
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
import { Flex } from "~/components/ui/flex";
|
import { Flex } from "~/components/ui/flex";
|
||||||
import { Title } from "@solidjs/meta";
|
import { Title } from "@solidjs/meta";
|
||||||
|
import { setDb, SQL } from "~/db";
|
||||||
|
|
||||||
export const Home: Component<RouteSectionProps> = () => {
|
export const Home: Component<RouteSectionProps> = () => {
|
||||||
const [isLoadingDb, setIsLoadingDb] = createSignal(false);
|
const [isLoadingDb, setIsLoadingDb] = createSignal(false);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { lazy } from "solid-js";
|
||||||
|
|
||||||
export { Home } from "./home";
|
export { Home } from "./home";
|
||||||
|
|
||||||
|
export { preloadDmId } from "./dm/dm-id";
|
||||||
export const GroupId = lazy(() => import("./group/group-id"));
|
export const GroupId = lazy(() => import("./group/group-id"));
|
||||||
export const DmId = lazy(() => import("./dm/dm-id"));
|
export const DmId = lazy(() => import("./dm/dm-id"));
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { type Component, createResource, Show } from "solid-js";
|
import { type Component, createResource, Show } from "solid-js";
|
||||||
import type { RouteSectionProps } from "@solidjs/router";
|
import type { RouteSectionProps } from "@solidjs/router";
|
||||||
|
|
||||||
import { allThreadsOverviewQuery, overallSentMessagesQuery, SELF_ID } from "~/db";
|
import { allThreadsOverviewQuery, overallSentMessagesQuery } from "~/db-queries";
|
||||||
|
|
||||||
import { OverviewTable, type RoomOverview } from "./overview-table";
|
import { OverviewTable, type RoomOverview } from "./overview-table";
|
||||||
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
import { getNameFromRecipient } from "~/lib/get-name-from-recipient";
|
||||||
import { Title } from "@solidjs/meta";
|
import { Title } from "@solidjs/meta";
|
||||||
|
import { SELF_ID } from "~/db";
|
||||||
|
|
||||||
export const Overview: Component<RouteSectionProps> = () => {
|
export const Overview: Component<RouteSectionProps> = () => {
|
||||||
const [allSelfSentMessagesCount] = createResource(() => overallSentMessagesQuery(SELF_ID));
|
const [allSelfSentMessagesCount] = createResource(() => overallSentMessagesQuery(SELF_ID));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { type Component, createSignal, For, Match, Show, Switch } from "solid-js";
|
import { type Component, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate, usePreloadRoute } from "@solidjs/router";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ColumnFiltersState,
|
type ColumnFiltersState,
|
||||||
|
@ -191,6 +191,8 @@ interface OverviewTableProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OverviewTable = (props: OverviewTableProps) => {
|
export const OverviewTable = (props: OverviewTableProps) => {
|
||||||
|
const preload = usePreloadRoute();
|
||||||
|
|
||||||
const [sorting, setSorting] = createSignal<SortingState>([
|
const [sorting, setSorting] = createSignal<SortingState>([
|
||||||
{
|
{
|
||||||
id: "messageCount",
|
id: "messageCount",
|
||||||
|
@ -314,6 +316,26 @@ export const OverviewTable = (props: OverviewTableProps) => {
|
||||||
<TableRow
|
<TableRow
|
||||||
class="cursor-pointer [&:last-of-type>td:first-of-type]:rounded-bl-md [&:last-of-type>td:last-of-type]:rounded-br-md"
|
class="cursor-pointer [&:last-of-type>td:first-of-type]:rounded-bl-md [&:last-of-type>td:last-of-type]:rounded-br-md"
|
||||||
data-state={row.getIsSelected() && "selected"}
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
onPointerEnter={(event) => {
|
||||||
|
const threadId = row.original.threadId;
|
||||||
|
const isGroup = row.original.isGroup;
|
||||||
|
|
||||||
|
const preloadTimeout = setTimeout(() => {
|
||||||
|
preload(`/${isGroup ? "group" : "dm"}/${threadId.toString()}`, {
|
||||||
|
preloadData: true,
|
||||||
|
});
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
event.currentTarget.addEventListener(
|
||||||
|
"pointerout",
|
||||||
|
() => {
|
||||||
|
clearTimeout(preloadTimeout);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
once: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const threadId = row.original.threadId;
|
const threadId = row.original.threadId;
|
||||||
const isGroup = row.original.isGroup;
|
const isGroup = row.original.isGroup;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"lib": ["ESNext", "DOM", "WebWorker"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"~/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,7 @@ export default defineConfig({
|
||||||
"~": path.resolve(__dirname, "./src"),
|
"~": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
worker: {
|
||||||
|
format: "es",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue