mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-29 09:53:07 +00:00
feat: W2G, persist search state tru navigation
fix: window title dissapearing lol
This commit is contained in:
parent
db2b0a738a
commit
516f26d765
24 changed files with 440 additions and 368 deletions
27
.vscode/settings.json
vendored
27
.vscode/settings.json
vendored
|
|
@ -1,13 +1,10 @@
|
||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
|
||||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "always"
|
"source.fixAll.eslint": "always"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"extensions.ignoreRecommendations": false,
|
"editor.linkedEditing": true,
|
||||||
"eslint.useESLintClass": true,
|
"editor.tabSize": 2,
|
||||||
"eslint.useFlatConfig": true,
|
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
"eslint.probe": [
|
"eslint.probe": [
|
||||||
"javascript",
|
"javascript",
|
||||||
|
|
@ -17,6 +14,8 @@
|
||||||
"svelte",
|
"svelte",
|
||||||
"html"
|
"html"
|
||||||
],
|
],
|
||||||
|
"eslint.useESLintClass": true,
|
||||||
|
"eslint.useFlatConfig": true,
|
||||||
"eslint.validate": [
|
"eslint.validate": [
|
||||||
"javascript",
|
"javascript",
|
||||||
"javascriptreact",
|
"javascriptreact",
|
||||||
|
|
@ -24,5 +23,21 @@
|
||||||
"typescriptreact",
|
"typescriptreact",
|
||||||
"svelte",
|
"svelte",
|
||||||
"html"
|
"html"
|
||||||
]
|
],
|
||||||
|
"javascript.preferences.importModuleSpecifierEnding": "minimal",
|
||||||
|
"javascript.preferences.quoteStyle": "single",
|
||||||
|
"javascript.suggest.autoImports": true,
|
||||||
|
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"javascript.validate.enable": true,
|
||||||
|
"extensions.ignoreRecommendations": false,
|
||||||
|
"svelte.plugin.svelte.format.config.singleQuote": true,
|
||||||
|
"svelte.plugin.svelte.format.enable": false,
|
||||||
|
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||||
|
"typescript.experimental.expandableHover": true,
|
||||||
|
"typescript.preferences.importModuleSpecifierEnding": "minimal",
|
||||||
|
"typescript.preferences.quoteStyle": "single",
|
||||||
|
"typescript.suggest.autoImports": true,
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||||
|
"typescript.validate.enable": true
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "6.3.16",
|
"version": "6.3.17",
|
||||||
"license": "BUSL-1.1",
|
"license": "BUSL-1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.14.4",
|
"packageManager": "pnpm@9.14.4",
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
"svelte-radix": "^1.1.1",
|
"svelte-radix": "^1.1.1",
|
||||||
"svelte-sonner": "^0.3.28",
|
"svelte-sonner": "^0.3.28",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
"vaul-svelte": "^0.3.2",
|
"vaul-svelte": "^0.3.2",
|
||||||
"vite": "^5.4.11"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
|
|
@ -63,7 +64,7 @@
|
||||||
"js-levenshtein": "^1.1.6",
|
"js-levenshtein": "^1.1.6",
|
||||||
"lucide-svelte": "^0.511.0",
|
"lucide-svelte": "^0.511.0",
|
||||||
"marked": "^15.0.11",
|
"marked": "^15.0.11",
|
||||||
"p2pt": "^1.5.1",
|
"p2pt": "github:ThaUnknown/p2pt#modernise",
|
||||||
"rollup-plugin-license": "^3.6.0",
|
"rollup-plugin-license": "^3.6.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
"simple-store-svelte": "^1.0.6",
|
"simple-store-svelte": "^1.0.6",
|
||||||
|
|
|
||||||
199
pnpm-lock.yaml
199
pnpm-lock.yaml
|
|
@ -75,8 +75,8 @@ importers:
|
||||||
specifier: ^15.0.11
|
specifier: ^15.0.11
|
||||||
version: 15.0.11
|
version: 15.0.11
|
||||||
p2pt:
|
p2pt:
|
||||||
specifier: ^1.5.1
|
specifier: github:ThaUnknown/p2pt#modernise
|
||||||
version: 1.5.1
|
version: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316
|
||||||
rollup-plugin-license:
|
rollup-plugin-license:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
|
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
|
||||||
|
|
@ -171,6 +171,9 @@ importers:
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.17
|
specifier: ^3.4.17
|
||||||
version: 3.4.17
|
version: 3.4.17
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.8.3
|
||||||
|
version: 5.8.3
|
||||||
vaul-svelte:
|
vaul-svelte:
|
||||||
specifier: ^0.3.2
|
specifier: ^0.3.2
|
||||||
version: 0.3.2(svelte@4.2.19)
|
version: 0.3.2(svelte@4.2.19)
|
||||||
|
|
@ -644,6 +647,12 @@ packages:
|
||||||
'@swc/helpers@0.5.17':
|
'@swc/helpers@0.5.17':
|
||||||
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
||||||
|
|
||||||
|
'@thaunknown/simple-peer@9.12.1':
|
||||||
|
resolution: {integrity: sha512-IS5BXvXx7cvBAzaxqotJf4s4rJCPk5JABLK6Gbnn7oAmWVcH4hYABabBBrvvJtv/xyUqR4v/H3LalnGRJJfEog==}
|
||||||
|
|
||||||
|
'@thaunknown/simple-websocket@9.1.3':
|
||||||
|
resolution: {integrity: sha512-pf/FCJsgWtLJiJmIpiSI7acOZVq3bIQCpnNo222UFc8Ph1lOUOTpe6LoYhhiOSKB9GUaWJEVUtZ+sK1/aBgU5Q==}
|
||||||
|
|
||||||
'@thaunknown/web-irc@1.0.1':
|
'@thaunknown/web-irc@1.0.1':
|
||||||
resolution: {integrity: sha512-oP+mrvD2U7gSXHTfT77+A+i2YVT5jp4qCbCXLrNU9aFzXdJ0iRsBbdhdT/AgeB7Nf4O+SC/wVCnwhnAfUoo0Fg==}
|
resolution: {integrity: sha512-oP+mrvD2U7gSXHTfT77+A+i2YVT5jp4qCbCXLrNU9aFzXdJ0iRsBbdhdT/AgeB7Nf4O+SC/wVCnwhnAfUoo0Fg==}
|
||||||
|
|
||||||
|
|
@ -770,8 +779,9 @@ packages:
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
addr-to-ip-port@1.5.4:
|
addr-to-ip-port@2.0.0:
|
||||||
resolution: {integrity: sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==}
|
resolution: {integrity: sha512-9bYbtjamtdLHZSqVIUXhilOryNPiL+x+Q5J/Unpg4VY3ZIkK3fT52UoErj1NdUeVm3J1t2iBEAur4Ywbl/bahw==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||||
|
|
@ -873,11 +883,9 @@ packages:
|
||||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
|
|
||||||
base64-js@1.5.1:
|
bencode@4.0.0:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
bencode@2.0.3:
|
|
||||||
resolution: {integrity: sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==}
|
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
binary-extensions@2.3.0:
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
|
|
@ -896,14 +904,11 @@ packages:
|
||||||
bittorrent-peerid@1.3.6:
|
bittorrent-peerid@1.3.6:
|
||||||
resolution: {integrity: sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==}
|
resolution: {integrity: sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==}
|
||||||
|
|
||||||
bittorrent-tracker@9.19.0:
|
bittorrent-tracker@10.0.12:
|
||||||
resolution: {integrity: sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==}
|
resolution: {integrity: sha512-EYQEwhOYkrRiiwkCFcM9pbzJInsAe7UVmUgevW133duwlZzjwf5ABwDE7pkkmNRS6iwN0b8LbI/94q16dYqiow==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12.20.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
bn.js@5.2.2:
|
|
||||||
resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==}
|
|
||||||
|
|
||||||
bottleneck@2.19.5:
|
bottleneck@2.19.5:
|
||||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||||
|
|
||||||
|
|
@ -925,9 +930,6 @@ packages:
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
buffer@6.0.3:
|
|
||||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
|
||||||
|
|
||||||
bufferutil@4.0.9:
|
bufferutil@4.0.9:
|
||||||
resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
|
resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
|
||||||
engines: {node: '>=6.14.2'}
|
engines: {node: '>=6.14.2'}
|
||||||
|
|
@ -1497,9 +1499,6 @@ packages:
|
||||||
idb-keyval@6.2.2:
|
idb-keyval@6.2.2:
|
||||||
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
|
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
|
||||||
|
|
||||||
ieee754@1.2.1:
|
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
@ -1893,8 +1892,9 @@ packages:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
p2pt@1.5.1:
|
p2pt@https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316:
|
||||||
resolution: {integrity: sha512-q1pkIKBRvGcQfv5Q3W0/c9pbBUnjcauWylc/qUZwIqcrQIxu3rfuDQXsqjwEJaBwdPNPWMY06jc5qxwVgJX6MA==}
|
resolution: {tarball: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316}
|
||||||
|
version: 1.5.1
|
||||||
|
|
||||||
package-json-from-dist@1.0.1:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
@ -2034,9 +2034,6 @@ packages:
|
||||||
random-iterate@1.0.1:
|
random-iterate@1.0.1:
|
||||||
resolution: {integrity: sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==}
|
resolution: {integrity: sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==}
|
||||||
|
|
||||||
randombytes@2.1.0:
|
|
||||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
|
||||||
|
|
||||||
react@19.0.0:
|
react@19.0.0:
|
||||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -2044,10 +2041,6 @@ packages:
|
||||||
read-cache@1.0.0:
|
read-cache@1.0.0:
|
||||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||||
|
|
||||||
readable-stream@3.6.2:
|
|
||||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||||
engines: {node: '>=8.10.0'}
|
engines: {node: '>=8.10.0'}
|
||||||
|
|
@ -2097,9 +2090,6 @@ packages:
|
||||||
run-series@1.1.9:
|
run-series@1.1.9:
|
||||||
resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==}
|
resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==}
|
||||||
|
|
||||||
rusha@0.8.14:
|
|
||||||
resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==}
|
|
||||||
|
|
||||||
rvfc-polyfill@1.0.7:
|
rvfc-polyfill@1.0.7:
|
||||||
resolution: {integrity: sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==}
|
resolution: {integrity: sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==}
|
||||||
|
|
||||||
|
|
@ -2111,9 +2101,6 @@ packages:
|
||||||
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
|
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
||||||
safe-buffer@5.2.1:
|
|
||||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
|
||||||
|
|
||||||
safe-push-apply@1.0.0:
|
safe-push-apply@1.0.0:
|
||||||
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
|
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -2180,18 +2167,9 @@ packages:
|
||||||
simple-get@4.0.1:
|
simple-get@4.0.1:
|
||||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||||
|
|
||||||
simple-peer@9.11.1:
|
|
||||||
resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==}
|
|
||||||
|
|
||||||
simple-sha1@3.1.0:
|
|
||||||
resolution: {integrity: sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg==}
|
|
||||||
|
|
||||||
simple-store-svelte@1.0.6:
|
simple-store-svelte@1.0.6:
|
||||||
resolution: {integrity: sha512-39TaQ2LHRAdH+cpWPmGzDfVyoAm/uZ6UkM27O3YhUtJHk0Vw09+6/jUqprns+BgOkKxOkRetKC9SH4bbj0IZ1A==}
|
resolution: {integrity: sha512-39TaQ2LHRAdH+cpWPmGzDfVyoAm/uZ6UkM27O3YhUtJHk0Vw09+6/jUqprns+BgOkKxOkRetKC9SH4bbj0IZ1A==}
|
||||||
|
|
||||||
simple-websocket@9.1.0:
|
|
||||||
resolution: {integrity: sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==}
|
|
||||||
|
|
||||||
sirv@3.0.1:
|
sirv@3.0.1:
|
||||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
@ -2262,11 +2240,9 @@ packages:
|
||||||
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
|
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
string2compact@1.3.2:
|
string2compact@2.0.1:
|
||||||
resolution: {integrity: sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==}
|
resolution: {integrity: sha512-Bm/T8lHMTRXw+u83LE+OW7fXmC/wM+Mbccfdo533ajSBNxddDHlRrvxE49NdciGHgXkUQM5WYskJ7uTkbBUI0A==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
string_decoder@1.3.0:
|
|
||||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
strip-ansi@6.0.1:
|
||||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||||
|
|
@ -2600,12 +2576,12 @@ packages:
|
||||||
bundledDependencies:
|
bundledDependencies:
|
||||||
- node-pre-gyp
|
- node-pre-gyp
|
||||||
|
|
||||||
ws@7.5.10:
|
ws@8.18.2:
|
||||||
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
|
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
|
||||||
engines: {node: '>=8.3.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
bufferutil: ^4.0.1
|
bufferutil: ^4.0.1
|
||||||
utf-8-validate: ^5.0.2
|
utf-8-validate: '>=5.0.2'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
bufferutil:
|
bufferutil:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
@ -3010,6 +2986,29 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@thaunknown/simple-peer@9.12.1':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.1
|
||||||
|
err-code: 3.0.1
|
||||||
|
get-browser-rtc: 1.1.0
|
||||||
|
queue-microtask: 1.2.3
|
||||||
|
streamx: 2.22.0
|
||||||
|
uint8-util: 2.2.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@thaunknown/simple-websocket@9.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.1
|
||||||
|
queue-microtask: 1.2.3
|
||||||
|
streamx: 2.22.0
|
||||||
|
uint8-util: 2.2.5
|
||||||
|
ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
'@thaunknown/web-irc@1.0.1':
|
'@thaunknown/web-irc@1.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
grapheme-splitter: 1.0.4
|
grapheme-splitter: 1.0.4
|
||||||
|
|
@ -3165,7 +3164,7 @@ snapshots:
|
||||||
|
|
||||||
acorn@8.14.1: {}
|
acorn@8.14.1: {}
|
||||||
|
|
||||||
addr-to-ip-port@1.5.4: {}
|
addr-to-ip-port@2.0.0: {}
|
||||||
|
|
||||||
ajv@6.12.6:
|
ajv@6.12.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -3276,9 +3275,9 @@ snapshots:
|
||||||
|
|
||||||
base64-arraybuffer@1.0.2: {}
|
base64-arraybuffer@1.0.2: {}
|
||||||
|
|
||||||
base64-js@1.5.1: {}
|
bencode@4.0.0:
|
||||||
|
dependencies:
|
||||||
bencode@2.0.3: {}
|
uint8-util: 2.2.5
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
||||||
|
|
@ -3298,11 +3297,12 @@ snapshots:
|
||||||
|
|
||||||
bittorrent-peerid@1.3.6: {}
|
bittorrent-peerid@1.3.6: {}
|
||||||
|
|
||||||
bittorrent-tracker@9.19.0:
|
bittorrent-tracker@10.0.12:
|
||||||
dependencies:
|
dependencies:
|
||||||
bencode: 2.0.3
|
'@thaunknown/simple-peer': 9.12.1
|
||||||
|
'@thaunknown/simple-websocket': 9.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
|
bencode: 4.0.0
|
||||||
bittorrent-peerid: 1.3.6
|
bittorrent-peerid: 1.3.6
|
||||||
bn.js: 5.2.2
|
|
||||||
chrome-dgram: 3.0.6
|
chrome-dgram: 3.0.6
|
||||||
clone: 2.1.2
|
clone: 2.1.2
|
||||||
compact2string: 1.4.1
|
compact2string: 1.4.1
|
||||||
|
|
@ -3313,24 +3313,20 @@ snapshots:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
random-iterate: 1.0.1
|
random-iterate: 1.0.1
|
||||||
randombytes: 2.1.0
|
|
||||||
run-parallel: 1.2.0
|
run-parallel: 1.2.0
|
||||||
run-series: 1.1.9
|
run-series: 1.1.9
|
||||||
simple-get: 4.0.1
|
simple-get: 4.0.1
|
||||||
simple-peer: 9.11.1
|
|
||||||
simple-websocket: 9.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
|
||||||
socks: 2.8.4
|
socks: 2.8.4
|
||||||
string2compact: 1.3.2
|
string2compact: 2.0.1
|
||||||
|
uint8-util: 2.2.5
|
||||||
unordered-array-remove: 1.0.2
|
unordered-array-remove: 1.0.2
|
||||||
ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
ws: 8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bufferutil: 4.0.9
|
bufferutil: 4.0.9
|
||||||
utf-8-validate: 5.0.10
|
utf-8-validate: 5.0.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
bn.js@5.2.2: {}
|
|
||||||
|
|
||||||
bottleneck@2.19.5: {}
|
bottleneck@2.19.5: {}
|
||||||
|
|
||||||
brace-expansion@1.1.11:
|
brace-expansion@1.1.11:
|
||||||
|
|
@ -3356,11 +3352,6 @@ snapshots:
|
||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
buffer@6.0.3:
|
|
||||||
dependencies:
|
|
||||||
base64-js: 1.5.1
|
|
||||||
ieee754: 1.2.1
|
|
||||||
|
|
||||||
bufferutil@4.0.9:
|
bufferutil@4.0.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp-build: 4.8.4
|
node-gyp-build: 4.8.4
|
||||||
|
|
@ -4068,8 +4059,6 @@ snapshots:
|
||||||
|
|
||||||
idb-keyval@6.2.2: {}
|
idb-keyval@6.2.2: {}
|
||||||
|
|
||||||
ieee754@1.2.1: {}
|
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
ignore@7.0.4: {}
|
ignore@7.0.4: {}
|
||||||
|
|
@ -4434,11 +4423,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
p2pt@1.5.1:
|
p2pt@https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316:
|
||||||
dependencies:
|
dependencies:
|
||||||
bittorrent-tracker: 9.19.0
|
bittorrent-tracker: 10.0.12
|
||||||
randombytes: 2.1.0
|
uint8-util: 2.2.5
|
||||||
simple-sha1: 3.1.0
|
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
wrtc: 0.4.7
|
wrtc: 0.4.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|
@ -4551,22 +4539,12 @@ snapshots:
|
||||||
|
|
||||||
random-iterate@1.0.1: {}
|
random-iterate@1.0.1: {}
|
||||||
|
|
||||||
randombytes@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
safe-buffer: 5.2.1
|
|
||||||
|
|
||||||
react@19.0.0: {}
|
react@19.0.0: {}
|
||||||
|
|
||||||
read-cache@1.0.0:
|
read-cache@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pify: 2.3.0
|
pify: 2.3.0
|
||||||
|
|
||||||
readable-stream@3.6.2:
|
|
||||||
dependencies:
|
|
||||||
inherits: 2.0.4
|
|
||||||
string_decoder: 1.3.0
|
|
||||||
util-deprecate: 1.0.2
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
@ -4651,8 +4629,6 @@ snapshots:
|
||||||
|
|
||||||
run-series@1.1.9: {}
|
run-series@1.1.9: {}
|
||||||
|
|
||||||
rusha@0.8.14: {}
|
|
||||||
|
|
||||||
rvfc-polyfill@1.0.7: {}
|
rvfc-polyfill@1.0.7: {}
|
||||||
|
|
||||||
sade@1.8.1:
|
sade@1.8.1:
|
||||||
|
|
@ -4667,8 +4643,6 @@ snapshots:
|
||||||
has-symbols: 1.1.0
|
has-symbols: 1.1.0
|
||||||
isarray: 2.0.5
|
isarray: 2.0.5
|
||||||
|
|
||||||
safe-buffer@5.2.1: {}
|
|
||||||
|
|
||||||
safe-push-apply@1.0.0:
|
safe-push-apply@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
|
|
@ -4752,37 +4726,8 @@ snapshots:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
simple-concat: 1.0.1
|
simple-concat: 1.0.1
|
||||||
|
|
||||||
simple-peer@9.11.1:
|
|
||||||
dependencies:
|
|
||||||
buffer: 6.0.3
|
|
||||||
debug: 4.4.1
|
|
||||||
err-code: 3.0.1
|
|
||||||
get-browser-rtc: 1.1.0
|
|
||||||
queue-microtask: 1.2.3
|
|
||||||
randombytes: 2.1.0
|
|
||||||
readable-stream: 3.6.2
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
simple-sha1@3.1.0:
|
|
||||||
dependencies:
|
|
||||||
queue-microtask: 1.2.3
|
|
||||||
rusha: 0.8.14
|
|
||||||
|
|
||||||
simple-store-svelte@1.0.6: {}
|
simple-store-svelte@1.0.6: {}
|
||||||
|
|
||||||
simple-websocket@9.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10):
|
|
||||||
dependencies:
|
|
||||||
debug: 4.4.1
|
|
||||||
queue-microtask: 1.2.3
|
|
||||||
randombytes: 2.1.0
|
|
||||||
readable-stream: 3.6.2
|
|
||||||
ws: 7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- bufferutil
|
|
||||||
- supports-color
|
|
||||||
- utf-8-validate
|
|
||||||
|
|
||||||
sirv@3.0.1:
|
sirv@3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@polka/url': 1.0.0-next.29
|
'@polka/url': 1.0.0-next.29
|
||||||
|
|
@ -4878,15 +4823,11 @@ snapshots:
|
||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
string2compact@1.3.2:
|
string2compact@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
addr-to-ip-port: 1.5.4
|
addr-to-ip-port: 2.0.0
|
||||||
ipaddr.js: 2.2.0
|
ipaddr.js: 2.2.0
|
||||||
|
|
||||||
string_decoder@1.3.0:
|
|
||||||
dependencies:
|
|
||||||
safe-buffer: 5.2.1
|
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
strip-ansi@6.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex: 5.0.1
|
ansi-regex: 5.0.1
|
||||||
|
|
@ -5271,7 +5212,7 @@ snapshots:
|
||||||
domexception: 1.0.1
|
domexception: 1.0.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@5.0.10):
|
ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bufferutil: 4.0.9
|
bufferutil: 4.0.9
|
||||||
utf-8-validate: 5.0.10
|
utf-8-validate: 5.0.10
|
||||||
|
|
|
||||||
6
src/app.d.ts
vendored
6
src/app.d.ts
vendored
|
|
@ -137,9 +137,9 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.svelte' {
|
// declare module '*.svelte' {
|
||||||
export default SvelteComponentTyped
|
// export default SvelteComponentTyped
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '*.svelte' {
|
declare module '*.svelte' {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
<title>Hayase</title>
|
||||||
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
|
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
import { getPFP, type ChatMessage } from '.'
|
import type { ChatMessage } from '.'
|
||||||
|
|
||||||
import type { Writable } from 'simple-store-svelte'
|
import type { Writable } from 'simple-store-svelte'
|
||||||
|
|
||||||
export let messages: Writable<ChatMessage[]>
|
export let messages: Writable<ChatMessage[]>
|
||||||
|
|
@ -9,7 +8,7 @@
|
||||||
if (!messages.length) return []
|
if (!messages.length) return []
|
||||||
const grouped = []
|
const grouped = []
|
||||||
for (const { message, user, type, date } of messages) {
|
for (const { message, user, type, date } of messages) {
|
||||||
const last = grouped[grouped.length - 1]
|
const last = grouped[grouped.length - 1]!
|
||||||
if (grouped.length && last.user.id === user.id) {
|
if (grouped.length && last.user.id === user.id) {
|
||||||
last.messages.push(message)
|
last.messages.push(message)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -23,11 +22,11 @@
|
||||||
{#each groupMessages($messages) as { type, user, date, messages }, i (i)}
|
{#each groupMessages($messages) as { type, user, date, messages }, i (i)}
|
||||||
{@const incoming = type === 'incoming'}
|
{@const incoming = type === 'incoming'}
|
||||||
<div class='message flex flex-row mt-3' class:flex-row={incoming} class:flex-row-reverse={!incoming}>
|
<div class='message flex flex-row mt-3' class:flex-row={incoming} class:flex-row-reverse={!incoming}>
|
||||||
<img src={getPFP(user)} alt='ProfilePicture' class='w-10 h-10 rounded-full p-1 mt-auto' loading='lazy' decoding='async' />
|
<img src={user.avatar?.medium ?? ''} alt='ProfilePicture' class='w-10 h-10 rounded-full p-1 mt-auto' loading='lazy' decoding='async' />
|
||||||
<div class='flex flex-col px-2 items-start flex-auto' class:items-start={incoming} class:items-end={!incoming}>
|
<div class='flex flex-col px-2 items-start flex-auto' class:items-start={incoming} class:items-end={!incoming}>
|
||||||
<div class='pb-1 flex flex-row items-center px-1'>
|
<div class='pb-1 flex flex-row items-center px-1'>
|
||||||
<div class='font-bold text-sm'>
|
<div class='font-bold text-sm'>
|
||||||
{user.nick}
|
{user.name}
|
||||||
</div>
|
</div>
|
||||||
<div class='text-muted-foreground pl-2 text-[10px] leading-relaxed'>
|
<div class='text-muted-foreground pl-2 text-[10px] leading-relaxed'>
|
||||||
{date.toLocaleTimeString()}
|
{date.toLocaleTimeString()}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,14 @@
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
import ExternalLink from 'lucide-svelte/icons/external-link'
|
import ExternalLink from 'lucide-svelte/icons/external-link'
|
||||||
|
|
||||||
import { getPFP, type ChatUser } from '.'
|
import type { ChatUser } from '.'
|
||||||
|
|
||||||
import type { Writable } from 'svelte/store'
|
|
||||||
|
|
||||||
import native from '$lib/modules/native'
|
import native from '$lib/modules/native'
|
||||||
import { click } from '$lib/modules/navigate'
|
import { click } from '$lib/modules/navigate'
|
||||||
|
|
||||||
export let users: Writable<Record<string, ChatUser>>
|
export let users: ChatUser[]
|
||||||
|
|
||||||
function processUsers (users: ChatUser[]) {
|
$: processed = Object.entries(users)
|
||||||
return users.map(user => {
|
|
||||||
return {
|
|
||||||
...user,
|
|
||||||
pfp: getPFP(user)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$: processed = processUsers(Object.values($users))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class='flex flex-col w-72 max-w-full px-5 overflow-hidden'>
|
<div class='flex flex-col w-72 max-w-full px-5 overflow-hidden'>
|
||||||
|
|
@ -27,13 +16,13 @@
|
||||||
{processed.length} Member(s)
|
{processed.length} Member(s)
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{#each processed as { id, pfp, nick } (id)}
|
{#each processed as [key, user] (key)}
|
||||||
<div class='flex items-center pb-2'>
|
<div class='flex items-center pb-2'>
|
||||||
<img src={pfp} alt='ProfilePicture' class='w-10 h-10 rounded-full p-1 mt-auto' loading='lazy' decoding='async' />
|
<img src={user.avatar?.medium} alt='ProfilePicture' class='w-10 h-10 rounded-full p-1 mt-auto' loading='lazy' decoding='async' />
|
||||||
<div class='text-md pl-2'>
|
<div class='text-md pl-2'>
|
||||||
{nick}
|
{user.name}
|
||||||
</div>
|
</div>
|
||||||
<span class='cursor-pointer flex items-center ml-auto text-blue-600' use:click={() => native.openURL('https://anilist.co/user/' + id)}>
|
<span class='cursor-pointer flex items-center ml-auto text-blue-600' use:click={() => native.openURL('https://anilist.co/user/' + user.id)}>
|
||||||
<ExternalLink size='18' />
|
<ExternalLink size='18' />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
export type UserType = 'al' | 'guest'
|
import type { Viewer } from '$lib/modules/anilist/queries'
|
||||||
|
import type { ResultOf } from 'gql.tada'
|
||||||
|
|
||||||
export interface ChatUser {
|
export type ChatUser = Omit<NonNullable<ResultOf<typeof Viewer>['Viewer']>, 'id'> & { id: string | number }
|
||||||
nick: string
|
|
||||||
id: string
|
|
||||||
pfpid: string
|
|
||||||
type: UserType
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChatMessage {
|
export interface ChatMessage {
|
||||||
message: string
|
message: string
|
||||||
|
|
@ -14,13 +10,5 @@ export interface ChatMessage {
|
||||||
date: Date
|
date: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPFP (user: ChatUser) {
|
|
||||||
if (user.type === 'al') {
|
|
||||||
return `https://s4.anilist.co/file/anilistcdn/user/avatar/medium/b${user.id}-${user.pfpid}`
|
|
||||||
} else {
|
|
||||||
return 'https://s4.anilist.co/file/anilistcdn/user/avatar/medium/default.png'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { default as UserList } from './UserList.svelte'
|
export { default as UserList } from './UserList.svelte'
|
||||||
export { default as Messages } from './Messages.svelte'
|
export { default as Messages } from './Messages.svelte'
|
||||||
|
|
|
||||||
61
src/lib/components/ui/irc/interface.svelte
Normal file
61
src/lib/components/ui/irc/interface.svelte
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script lang='ts'>
|
||||||
|
|
||||||
|
import SendHorizontal from 'lucide-svelte/icons/send-horizontal'
|
||||||
|
|
||||||
|
import { Button } from '../button'
|
||||||
|
import { Messages, UserList } from '../chat'
|
||||||
|
|
||||||
|
import type MessageClient from '$lib/modules/irc'
|
||||||
|
|
||||||
|
import { Textarea } from '$lib/components/ui/textarea'
|
||||||
|
|
||||||
|
export let client: MessageClient
|
||||||
|
|
||||||
|
let message = ''
|
||||||
|
let rows = 1
|
||||||
|
|
||||||
|
function sendMessage () {
|
||||||
|
if (message.trim()) {
|
||||||
|
client.say(message.trim())
|
||||||
|
message = ''
|
||||||
|
rows = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkInput (e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey && message.trim()) {
|
||||||
|
e.preventDefault()
|
||||||
|
sendMessage()
|
||||||
|
} else {
|
||||||
|
rows = message.split('\n').length || 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateRows () {
|
||||||
|
rows = message.split('\n').length || 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$: users = client.users
|
||||||
|
|
||||||
|
$: processedUsers = Object.values($users)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class='flex flex-col w-full relative px-md-4 h-full overflow-hidden'>
|
||||||
|
<div class='flex md:flex-row flex-col-reverse w-full h-full pt-4'>
|
||||||
|
<div class='flex flex-col justify-end overflow-hidden flex-grow px-4 md:pb-4'>
|
||||||
|
<Messages messages={client.messages} />
|
||||||
|
<div class='flex mt-4'>
|
||||||
|
<Textarea
|
||||||
|
bind:value={message}
|
||||||
|
class='h-auto px-3 w-full flex-grow-1 resize-none min-h-0 border-0 bg-background select:bg-accent select:text-accent-foreground'
|
||||||
|
{rows}
|
||||||
|
autocomplete='off'
|
||||||
|
maxlength={256}
|
||||||
|
placeholder='Message' on:keydown={checkInput} on:input={updateRows} />
|
||||||
|
<Button on:click={sendMessage} size='icon' class='mt-auto ml-2 border-0' variant='outline'>
|
||||||
|
<SendHorizontal size={18} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserList users={processedUsers} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
<script lang='ts' context='module'>
|
<script lang='ts' context='module'>
|
||||||
import SendHorizontal from 'lucide-svelte/icons/send-horizontal'
|
|
||||||
import { writable, type Writable } from 'simple-store-svelte'
|
import { writable, type Writable } from 'simple-store-svelte'
|
||||||
|
|
||||||
import { Messages, UserList } from '../chat'
|
|
||||||
|
|
||||||
import { Textarea } from '$lib/components/ui/textarea'
|
|
||||||
import { client } from '$lib/modules/anilist'
|
import { client } from '$lib/modules/anilist'
|
||||||
import MessageClient from '$lib/modules/irc'
|
import MessageClient from '$lib/modules/irc'
|
||||||
|
|
||||||
|
|
@ -12,7 +8,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang='ts'>
|
<script lang='ts'>
|
||||||
import { Button } from '../button'
|
import Interface from './interface.svelte'
|
||||||
|
|
||||||
const viewer = client.viewer.value
|
const viewer = client.viewer.value
|
||||||
|
|
||||||
|
|
@ -28,29 +24,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
irc.value ??= MessageClient.new(ident)
|
irc.value ??= MessageClient.new(ident)
|
||||||
|
|
||||||
let message = ''
|
|
||||||
let rows = 1
|
|
||||||
|
|
||||||
function sendMessage (client: MessageClient) {
|
|
||||||
if (message.trim()) {
|
|
||||||
client.say(message.trim())
|
|
||||||
message = ''
|
|
||||||
rows = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkInput (e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Enter' && !e.shiftKey && message.trim()) {
|
|
||||||
e.preventDefault()
|
|
||||||
sendMessage(await irc.value!)
|
|
||||||
} else {
|
|
||||||
rows = message.split('\n').length || 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function updateRows () {
|
|
||||||
rows = message.split('\n').length || 1
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $irc}
|
{#if $irc}
|
||||||
|
|
@ -69,26 +42,6 @@
|
||||||
Loading...
|
Loading...
|
||||||
</div>
|
</div>
|
||||||
{:then client}
|
{:then client}
|
||||||
<div class='flex flex-col w-full relative px-md-4 h-full overflow-hidden'>
|
<Interface {client} />
|
||||||
<div class='flex md:flex-row flex-col-reverse w-full h-full pt-4'>
|
|
||||||
<div class='flex flex-col justify-end overflow-hidden flex-grow px-4 md:pb-4'>
|
|
||||||
<Messages messages={client.messages} />
|
|
||||||
<div class='flex mt-4'>
|
|
||||||
<Textarea
|
|
||||||
bind:value={message}
|
|
||||||
class='h-auto px-3 w-full flex-grow-1 resize-none min-h-0 border-0 bg-background select:bg-accent select:text-accent-foreground'
|
|
||||||
{rows}
|
|
||||||
autocomplete='off'
|
|
||||||
maxlength={256}
|
|
||||||
placeholder='Message' on:keydown={checkInput} on:input={updateRows} />
|
|
||||||
<Button on:click={() => sendMessage(client)} size='icon' class='mt-auto ml-2 border-0' variant='outline'>
|
|
||||||
<SendHorizontal size={18} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<UserList users={client.users} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <Chat {client} /> -->
|
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import { fillerEpisodes } from '$lib/components/EpisodesList.svelte'
|
import { fillerEpisodes } from '$lib/components/EpisodesList.svelte'
|
||||||
import { cover, episodes, title } from '$lib/modules/anilist'
|
import { cover, episodes, title } from '$lib/modules/anilist'
|
||||||
import { settings } from '$lib/modules/settings'
|
import { settings } from '$lib/modules/settings'
|
||||||
|
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||||
|
|
||||||
export let mediaInfo: NonNullable<Awaited<ReturnType<typeof resolveFilesPoorly>>>
|
export let mediaInfo: NonNullable<Awaited<ReturnType<typeof resolveFilesPoorly>>>
|
||||||
|
|
||||||
|
|
@ -27,6 +28,14 @@
|
||||||
|
|
||||||
let current = fileToMedaInfo(mediaInfo.target)
|
let current = fileToMedaInfo(mediaInfo.target)
|
||||||
|
|
||||||
|
$: $w2globby?.mediaIndexChanged(mediaInfo.resolvedFiles.indexOf(current.file))
|
||||||
|
$: $w2globby?.on('index', index => {
|
||||||
|
const file = mediaInfo.resolvedFiles[index]
|
||||||
|
if (file) {
|
||||||
|
current = fileToMedaInfo(file)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function findEpisode (episode: number) {
|
function findEpisode (episode: number) {
|
||||||
return mediaInfo.targetAnimeFiles.find(file => file.metadata.episode === episode)
|
return mediaInfo.targetAnimeFiles.find(file => file.metadata.episode === episode)
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +79,7 @@
|
||||||
|
|
||||||
function selectFile (file: ResolvedFile) {
|
function selectFile (file: ResolvedFile) {
|
||||||
current = fileToMedaInfo(file)
|
current = fileToMedaInfo(file)
|
||||||
|
$w2globby?.mediaIndexChanged(mediaInfo.resolvedFiles.indexOf(current.file))
|
||||||
}
|
}
|
||||||
|
|
||||||
$: next = hasNext(current)
|
$: next = hasNext(current)
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
import { click } from '$lib/modules/navigate'
|
import { click } from '$lib/modules/navigate'
|
||||||
import { settings } from '$lib/modules/settings'
|
import { settings } from '$lib/modules/settings'
|
||||||
import { server } from '$lib/modules/torrent'
|
import { server } from '$lib/modules/torrent'
|
||||||
|
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||||
import { toTS, fastPrettyBits } from '$lib/utils'
|
import { toTS, fastPrettyBits } from '$lib/utils'
|
||||||
|
|
||||||
export let mediaInfo: MediaInfo
|
export let mediaInfo: MediaInfo
|
||||||
|
|
@ -652,6 +653,12 @@
|
||||||
|
|
||||||
return { destroy: () => ctrl.abort() }
|
return { destroy: () => ctrl.abort() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: $w2globby?.playerStateChanged({ paused, time: Math.floor(currentTime) })
|
||||||
|
$: $w2globby?.on('player', state => {
|
||||||
|
currentTime = state.time
|
||||||
|
paused = state.paused
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:document bind:fullscreenElement bind:visibilityState use:holdToFF={'key'} />
|
<svelte:document bind:fullscreenElement bind:visibilityState use:holdToFF={'key'} />
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,7 @@
|
||||||
<SidebarButton href='/app/schedule/'>
|
<SidebarButton href='/app/schedule/'>
|
||||||
<Calendar size={18} />
|
<Calendar size={18} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<!-- <SidebarButton href='/app/w2g/'> -->
|
<SidebarButton href='/app/w2g/'>
|
||||||
<SidebarButton disabled={true}>
|
|
||||||
<Users size={18} />
|
<Users size={18} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<SidebarButton href='/app/chat/'>
|
<SidebarButton href='/app/chat/'>
|
||||||
|
|
@ -55,7 +54,6 @@
|
||||||
<Download size={18} />
|
<Download size={18} />
|
||||||
</SidebarButton>
|
</SidebarButton>
|
||||||
<Button variant='ghost' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
|
<Button variant='ghost' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
|
||||||
<!-- <Heart size={18} fill='currentColor' class='absolute' /> -->
|
|
||||||
<Heart size={18} fill='currentColor' class={cn(active && 'donate')} />
|
<Heart size={18} fill='currentColor' class={cn(active && 'donate')} />
|
||||||
</Button>
|
</Button>
|
||||||
<SidebarButton href='/app/settings/'>
|
<SidebarButton href='/app/settings/'>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { EventEmitter } from 'events'
|
|
||||||
|
|
||||||
import Client, { createChannelConstructor } from '@thaunknown/web-irc'
|
import Client, { createChannelConstructor } from '@thaunknown/web-irc'
|
||||||
import { writable } from 'simple-store-svelte'
|
import { writable } from 'simple-store-svelte'
|
||||||
|
|
||||||
|
|
@ -7,6 +5,23 @@ import { decryptMessage, encryptMessage } from './crypt'
|
||||||
|
|
||||||
import type { ChatMessage, ChatUser } from '$lib/components/ui/chat'
|
import type { ChatMessage, ChatUser } from '$lib/components/ui/chat'
|
||||||
import type IrcChannel from '@thaunknown/web-irc/channel'
|
import type IrcChannel from '@thaunknown/web-irc/channel'
|
||||||
|
import type IrcClient from '@thaunknown/web-irc/client'
|
||||||
|
import type { EventEmitter } from 'events'
|
||||||
|
|
||||||
|
export type UserType = 'al' | 'guest'
|
||||||
|
export interface IRCChatUser {
|
||||||
|
nick: string
|
||||||
|
id: string
|
||||||
|
pfpid: string
|
||||||
|
type: UserType
|
||||||
|
}
|
||||||
|
export function getPFP (user: Pick<IRCChatUser, 'id' | 'pfpid' | 'type'>) {
|
||||||
|
if (user.type === 'al') {
|
||||||
|
return `https://s4.anilist.co/file/anilistcdn/user/avatar/medium/b${user.id}-${user.pfpid}`
|
||||||
|
} else {
|
||||||
|
return 'https://s4.anilist.co/file/anilistcdn/user/avatar/medium/default.png'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface IRCUser { nick: string, ident: string, hostname: string, modes: string[], tags: object }
|
export interface IRCUser { nick: string, ident: string, hostname: string, modes: string[], tags: object }
|
||||||
export interface PrivMessage {
|
export interface PrivMessage {
|
||||||
|
|
@ -24,30 +39,48 @@ export interface PrivMessage {
|
||||||
time: number
|
time: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class MessageClient extends EventEmitter {
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
irc = new Client(null)
|
type IRCEvents = {
|
||||||
|
userlist: [{ users: IRCUser[] }]
|
||||||
|
join: [IRCUser]
|
||||||
|
part: [IRCUser]
|
||||||
|
quit: [IRCUser]
|
||||||
|
kick: [IRCUser]
|
||||||
|
privmsg: [PrivMessage]
|
||||||
|
connected: []
|
||||||
|
}
|
||||||
|
|
||||||
|
function ircUserToChatUser ({ id, pfpid, type, nick }: IRCChatUser): ChatUser {
|
||||||
|
return { id, avatar: { medium: getPFP({ id, pfpid, type }) }, name: nick, mediaListOptions: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
function ircIdentToChatUser (user: IRCUser): ChatUser {
|
||||||
|
const [nick, pfpid, pfpex] = user.nick.split('_') as [string, string, string]
|
||||||
|
const [type, id] = user.ident.split('_') as ['al' | 'guest', string]
|
||||||
|
return ircUserToChatUser({ id, pfpid: `${pfpid}.${pfpex}`, type, nick })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MessageClient {
|
||||||
|
irc = new Client(null) as IrcClient & EventEmitter<IRCEvents>
|
||||||
users = writable<Record<string, ChatUser>>({})
|
users = writable<Record<string, ChatUser>>({})
|
||||||
messages = writable<ChatMessage[]>([])
|
messages = writable<ChatMessage[]>([])
|
||||||
channel?: IrcChannel
|
channel?: IrcChannel
|
||||||
ident
|
ident
|
||||||
|
|
||||||
constructor (ident: ChatUser) {
|
constructor (ident: IRCChatUser) {
|
||||||
super()
|
|
||||||
this.ident = ident
|
this.ident = ident
|
||||||
this.irc.on('userlist', async ({ users }: { users: IRCUser[] }) => {
|
this.irc.on('userlist', async ({ users }) => {
|
||||||
this.users.value = users.reduce((acc, user) => {
|
this.users.value = users.reduce((acc, ircuser) => {
|
||||||
const [nick, pfpid, pfpex] = user.nick.split('_') as [string, string, string]
|
const user = ircIdentToChatUser(ircuser)
|
||||||
const [type, id] = user.ident.split('_') as ['al' | 'guest', string]
|
acc[ircuser.ident] = user
|
||||||
acc[user.ident] = { nick, id, pfpid: `${pfpid}.${pfpex}`, type }
|
|
||||||
return acc
|
return acc
|
||||||
}, this.users.value)
|
}, this.users.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.irc.on('join', async (user: IRCUser) => {
|
this.irc.on('join', async ircuser => {
|
||||||
try {
|
try {
|
||||||
const [nick, pfpid, pfpex] = user.nick.split('_') as [string, string, string]
|
const user = ircIdentToChatUser(ircuser)
|
||||||
const [type, id] = user.ident.split('_') as ['al' | 'guest', string]
|
this.users.value[ircuser.ident] = user
|
||||||
this.users.value[user.ident] = { nick, id, pfpid: `${pfpid}.${pfpex}`, type }
|
|
||||||
this.users.update(users => users)
|
this.users.update(users => users)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|
@ -63,9 +96,9 @@ export default class MessageClient extends EventEmitter {
|
||||||
this.irc.on('part', deleteUser)
|
this.irc.on('part', deleteUser)
|
||||||
this.irc.on('kick', deleteUser)
|
this.irc.on('kick', deleteUser)
|
||||||
|
|
||||||
this.irc.on('privmsg', async (priv: PrivMessage) => {
|
this.irc.on('privmsg', async priv => {
|
||||||
const message = await decryptMessage(priv.message)
|
|
||||||
try {
|
try {
|
||||||
|
const message = await decryptMessage(priv.message)
|
||||||
this.messages.update(messages => [...messages, {
|
this.messages.update(messages => [...messages, {
|
||||||
message,
|
message,
|
||||||
user: this.users.value[priv.ident]!,
|
user: this.users.value[priv.ident]!,
|
||||||
|
|
@ -83,17 +116,17 @@ export default class MessageClient extends EventEmitter {
|
||||||
const encrypted = await encryptMessage(message)
|
const encrypted = await encryptMessage(message)
|
||||||
this.channel!.say(encrypted)
|
this.channel!.say(encrypted)
|
||||||
this.messages.update(messages => [...messages, {
|
this.messages.update(messages => [...messages, {
|
||||||
user: this.ident,
|
user: ircUserToChatUser(this.ident),
|
||||||
message,
|
message,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
type: 'outgoing'
|
type: 'outgoing'
|
||||||
}])
|
}])
|
||||||
}
|
}
|
||||||
|
|
||||||
static async new ({ nick, id, pfpid, type }: ChatUser) {
|
static async new ({ nick, id, pfpid, type }: IRCChatUser) {
|
||||||
const client = new this({ nick, id, pfpid, type })
|
const client = new this({ nick, id, pfpid, type })
|
||||||
|
|
||||||
await new Promise(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
client.irc.once('connected', resolve)
|
client.irc.once('connected', resolve)
|
||||||
client.irc.connect({
|
client.irc.connect({
|
||||||
version: null,
|
version: null,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { get } from 'svelte/store'
|
||||||
import { persisted } from 'svelte-persisted-store'
|
import { persisted } from 'svelte-persisted-store'
|
||||||
|
|
||||||
import native from '../native'
|
import native from '../native'
|
||||||
|
import { w2globby } from '../w2g/lobby'
|
||||||
|
|
||||||
import type { TorrentFile, TorrentInfo } from '../../../app'
|
import type { TorrentFile, TorrentInfo } from '../../../app'
|
||||||
import type { Media } from '../anilist'
|
import type { Media } from '../anilist'
|
||||||
|
|
@ -53,6 +54,7 @@ export const server = new class ServerClient {
|
||||||
play (id: string, media: Media, episode: number) {
|
play (id: string, media: Media, episode: number) {
|
||||||
this.last.set({ id, media, episode })
|
this.last.set({ id, media, episode })
|
||||||
this.active.value = this._play(id, media, episode)
|
this.active.value = this._play(id, media, episode)
|
||||||
|
w2globby.value?.mediaChange({ episode, mediaId: media.id, torrent: id })
|
||||||
return this.active.value
|
return this.active.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,23 @@
|
||||||
export default class Event<T = unknown> {
|
import type { ChatUser } from '$lib/components/ui/chat'
|
||||||
payload
|
|
||||||
type = ''
|
export default class Event<K extends keyof W2GEvents = keyof W2GEvents> {
|
||||||
constructor (type: string, payload: T) {
|
readonly type: K
|
||||||
|
readonly payload: W2GEvents[K]
|
||||||
|
|
||||||
|
constructor (type: K, payload: W2GEvents[K]) {
|
||||||
this.type = type
|
this.type = type
|
||||||
this.payload = payload
|
this.payload = payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EventTypes = {
|
export interface PlayerState { paused: boolean, time: number }
|
||||||
SessionInitEvent: 'init',
|
|
||||||
MagnetLinkEvent: 'magnet',
|
export interface MediaState { torrent: string, mediaId: number, episode: number }
|
||||||
MediaIndexEvent: 'index',
|
|
||||||
PlayerStateEvent: 'player',
|
export interface W2GEvents {
|
||||||
MessageEvent: 'message'
|
init: ChatUser
|
||||||
|
media?: MediaState
|
||||||
|
index?: number
|
||||||
|
player: PlayerState
|
||||||
|
message: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@ import P2PT, { type Peer } from 'p2pt'
|
||||||
import { writable } from 'simple-store-svelte'
|
import { writable } from 'simple-store-svelte'
|
||||||
|
|
||||||
import client from '../anilist/client.js'
|
import client from '../anilist/client.js'
|
||||||
|
import { server } from '../torrent'
|
||||||
|
|
||||||
import Event, { EventTypes } from './events.js'
|
import Event, { type MediaState, type PlayerState, type W2GEvents } from './events.js'
|
||||||
|
|
||||||
import type { Viewer } from '../anilist/queries'
|
import type { ChatMessage, ChatUser } from '$lib/components/ui/chat'
|
||||||
import type { ResultOf } from 'gql.tada'
|
|
||||||
|
|
||||||
const debug = Debug('ui:w2g')
|
const debug = Debug('ui:w2g')
|
||||||
|
|
||||||
function generateRandomHexCode (len: number) {
|
export function generateRandomHexCode (len: number) {
|
||||||
let hexCode = ''
|
let hexCode = ''
|
||||||
|
|
||||||
while (hexCode.length < len) {
|
while (hexCode.length < len) {
|
||||||
|
|
@ -23,73 +23,77 @@ function generateRandomHexCode (len: number) {
|
||||||
return hexCode
|
return hexCode
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerList = Record<string, { user: ResultOf<typeof Viewer>['Viewer'] | {id: string }, peer?: Peer }>
|
type AppEvent = {
|
||||||
|
[K in keyof W2GEvents]-?: Event<K>
|
||||||
|
}[keyof W2GEvents]
|
||||||
|
|
||||||
interface PlayerState {paused: boolean, time: number}
|
type PeerList = Record<string, { user: ChatUser, peer?: Peer }>
|
||||||
|
|
||||||
export class W2GClient extends EventEmitter {
|
const ANNOUNCE = [
|
||||||
static readonly #announce = [
|
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
||||||
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='),
|
||||||
atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='),
|
atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'),
|
||||||
atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'),
|
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
|
||||||
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
|
]
|
||||||
]
|
|
||||||
|
|
||||||
|
export class W2GClient extends EventEmitter<{index: [number], player: [PlayerState]}> {
|
||||||
player: PlayerState = {
|
player: PlayerState = {
|
||||||
paused: true,
|
paused: true,
|
||||||
time: 0
|
time: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
magnet: {magnet: string, hash: string} | null = null
|
media: MediaState | undefined
|
||||||
isHost = false
|
isHost
|
||||||
#p2pt: P2PT | null
|
readonly #p2pt
|
||||||
code
|
code
|
||||||
|
|
||||||
messages = writable<Array<{message: string, user: ResultOf<typeof Viewer>['Viewer'] | {id: string }, type: 'incoming' | 'outgoing', date: Date}>>([])
|
messages = writable<ChatMessage[]>([])
|
||||||
|
|
||||||
self = client.viewer.value?.viewer ?? { id: generateRandomHexCode(16) }
|
self: ChatUser = client.viewer.value?.viewer ?? { id: generateRandomHexCode(16), avatar: null, mediaListOptions: null, name: 'Guest' }
|
||||||
/** @type {import('simple-store-svelte').Writable<PeerList>} */
|
|
||||||
peers = writable<PeerList>({ [this.self.id]: { user: this.self } })
|
peers = writable<PeerList>({ [this.self.id]: { user: this.self } })
|
||||||
|
|
||||||
get inviteLink () {
|
get inviteLink () {
|
||||||
return `https://miru.watch/w2g/${this.code}`
|
return `https://hayas.ee/w2g/${this.code}`
|
||||||
}
|
}
|
||||||
|
|
||||||
localMediaIndexChanged (index: number) {
|
constructor (code: string, isHost: boolean) {
|
||||||
this.index = index
|
|
||||||
|
|
||||||
this.mediaIndexChanged(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
localPlayerStateChanged ({ payload }: Event<PlayerState>) {
|
|
||||||
debug(`localPlayerStateChanged: ${JSON.stringify(payload)}`)
|
|
||||||
this.player.paused = payload.paused
|
|
||||||
this.player.time = payload.time
|
|
||||||
|
|
||||||
this.playerStateChanged(this.player)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (code?: string) {
|
|
||||||
super()
|
super()
|
||||||
this.isHost = !code
|
this.isHost = isHost
|
||||||
|
|
||||||
this.code = code ?? generateRandomHexCode(16)
|
this.code = code
|
||||||
|
|
||||||
debug(`W2GClient: ${this.code}, ${this.isHost}`)
|
debug(`W2GClient: ${this.code}, ${this.isHost}`)
|
||||||
|
|
||||||
this.#p2pt = new P2PT(W2GClient.#announce, this.code)
|
this.#p2pt = new P2PT<Event>(ANNOUNCE, this.code)
|
||||||
|
|
||||||
|
this.#p2pt.on('peerconnect', peer => {
|
||||||
|
debug(`peerconnect: ${peer.id}`)
|
||||||
|
this._sendEvent(peer, new Event('init', this.self))
|
||||||
|
|
||||||
|
if (this.isHost) this._sendInitialSessionState(peer)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.#p2pt.on('peerclose', peer => {
|
||||||
|
debug(`peerclose: ${peer.id}`)
|
||||||
|
this.peers.update(peers => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete peers[peer.id]
|
||||||
|
return peers
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.#p2pt.on('msg', this._onMsg)
|
||||||
|
|
||||||
this.#wireEvents()
|
|
||||||
this.#p2pt.start()
|
this.#p2pt.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
magnetLink (magnet: { hash: string, magnet: string }) {
|
mediaChange (media: MediaState) {
|
||||||
debug(`magnetLink: ${this.magnet?.hash} ${magnet.hash}`)
|
debug(`mediaChange: ${this.media?.torrent} ${media.torrent}`)
|
||||||
if (this.magnet?.hash !== magnet.hash) {
|
if (this.media?.torrent !== media.torrent) {
|
||||||
this.magnet = magnet
|
this.media = media
|
||||||
this.isHost = true
|
this.isHost = true
|
||||||
this.#sendToPeers(new Event('magnet', magnet))
|
this._sendToPeers(new Event('media', media))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +101,7 @@ export class W2GClient extends EventEmitter {
|
||||||
debug(`mediaIndexChanged: ${this.index} ${index}`)
|
debug(`mediaIndexChanged: ${this.index} ${index}`)
|
||||||
if (this.index !== index) {
|
if (this.index !== index) {
|
||||||
this.index = index
|
this.index = index
|
||||||
this.#sendToPeers(new Event('index', index))
|
this._sendToPeers(new Event('index', index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +116,7 @@ export class W2GClient extends EventEmitter {
|
||||||
|
|
||||||
playerStateChanged (state: PlayerState) {
|
playerStateChanged (state: PlayerState) {
|
||||||
debug(`playerStateChanged: ${JSON.stringify(state)}`)
|
debug(`playerStateChanged: ${JSON.stringify(state)}`)
|
||||||
if (this._playerStateChanged(state)) this.#sendToPeers(new Event('player', state))
|
if (this._playerStateChanged(state)) this._sendToPeers(new Event('player', state))
|
||||||
}
|
}
|
||||||
|
|
||||||
message (message: string) {
|
message (message: string) {
|
||||||
|
|
@ -123,77 +127,61 @@ export class W2GClient extends EventEmitter {
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
date: new Date()
|
date: new Date()
|
||||||
})])
|
})])
|
||||||
this.#sendToPeers(new Event('message', message))
|
this._sendToPeers(new Event('message', message))
|
||||||
}
|
}
|
||||||
|
|
||||||
#wireEvents () {
|
_sendEvent (peer: Peer, event: Event) {
|
||||||
this.#p2pt?.on('peerconnect', this.#onPeerconnect.bind(this))
|
debug(`sendEvent: ${peer.id} ${JSON.stringify(event)}`)
|
||||||
this.#p2pt?.on('msg', this.#onMsg.bind(this))
|
this.#p2pt.send(peer, event)
|
||||||
this.#p2pt?.on('peerclose', this.#onPeerclose.bind(this))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendEvent (peer: Peer, event: Event) {
|
_sendInitialSessionState (peer: Peer) {
|
||||||
debug(`#sendEvent: ${peer.id} ${JSON.stringify(event)}`)
|
this._sendEvent(peer, new Event('media', this.media))
|
||||||
this.#p2pt?.send(peer, JSON.stringify(event))
|
this._sendEvent(peer, new Event('index', this.index))
|
||||||
|
this._sendEvent(peer, new Event('player', this.player))
|
||||||
}
|
}
|
||||||
|
|
||||||
#sendInitialSessionState (peer: Peer) {
|
_onMsg = async (peer: Peer, data: AppEvent) => {
|
||||||
this.#sendEvent(peer, new Event('magnet', this.magnet))
|
debug(`onMsg: ${peer.id} ${JSON.stringify(data)}`)
|
||||||
this.#sendEvent(peer, new Event('index', this.index))
|
|
||||||
this.#sendEvent(peer, new Event('player', this.player))
|
|
||||||
}
|
|
||||||
|
|
||||||
async #onPeerconnect (peer: Peer) {
|
|
||||||
debug(`#onPeerconnect: ${peer.id}`)
|
|
||||||
this.#sendEvent(peer, new Event('init', this.self))
|
|
||||||
|
|
||||||
if (this.isHost) this.#sendInitialSessionState(peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
#onMsg (peer: Peer, data: Event<PlayerState | {magnet: string, hash: string} | string | ResultOf<typeof Viewer>['Viewer'] | {index: number}> | string) {
|
|
||||||
debug(`#onMsg: ${peer.id} ${JSON.stringify(data)}`)
|
|
||||||
data = typeof data === 'string' ? JSON.parse(data) as Event<PlayerState | {magnet: string, hash: string} | string | ResultOf<typeof Viewer>['Viewer'] | {index: number}> : data
|
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case EventTypes.SessionInitEvent:
|
case 'init':
|
||||||
this.peers.update(peers => {
|
this.peers.update(peers => {
|
||||||
peers[peer.id] = {
|
peers[peer.id] = {
|
||||||
peer,
|
peer,
|
||||||
user: data.payload as ResultOf<typeof Viewer>['Viewer']
|
user: data.payload
|
||||||
}
|
}
|
||||||
return peers
|
return peers
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case EventTypes.MagnetLinkEvent: {
|
case 'media': {
|
||||||
const cast = data as Event<{magnet: string, hash: string}>
|
if (data.payload?.torrent == null || data.payload?.mediaId == null) break
|
||||||
if (cast.payload.magnet === undefined) break
|
const { torrent, mediaId, episode } = data.payload
|
||||||
const { hash, magnet } = cast.payload
|
if (torrent !== this.media?.torrent) {
|
||||||
if (hash !== this.magnet?.hash) {
|
|
||||||
this.isHost = false
|
this.isHost = false
|
||||||
this.magnet = cast.payload
|
this.media = data.payload
|
||||||
add(magnet)
|
const media = (await client.single(mediaId)).data?.Media
|
||||||
|
if (media == null) break
|
||||||
|
server.play(torrent, media, episode)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case EventTypes.MediaIndexEvent: {
|
case 'index': {
|
||||||
const cast = data as Event<{index: number}>
|
if (data.payload == null) break
|
||||||
if (cast.payload.index === undefined) break
|
if (this.index !== data.payload) {
|
||||||
if (this.index !== cast.payload.index) {
|
this.index = data.payload
|
||||||
this.index = cast.payload.index
|
this.emit('index', data.payload)
|
||||||
this.emit('index', cast.payload.index)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case EventTypes.PlayerStateEvent: {
|
case 'player': {
|
||||||
const cast = data as Event<PlayerState>
|
if (data.payload?.time == null) break
|
||||||
if (cast.payload.time === undefined) break
|
if (this._playerStateChanged(data.payload)) this.emit('player', data.payload)
|
||||||
if (this._playerStateChanged(cast.payload)) this.emit('player', data.payload)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case EventTypes.MessageEvent:{
|
case 'message': {
|
||||||
const cast = data as Event<string>
|
this.messages.update(messages => [...messages, ({ message: data.payload, user: this.peers.value[peer.id]!.user, type: 'incoming', date: new Date() })])
|
||||||
this.messages.update(messages => [...messages, ({ message: cast.payload, user: this.peers.value[peer.id].user, type: 'incoming', date: new Date() })])
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -201,27 +189,16 @@ export class W2GClient extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onPeerclose (peer: Peer) {
|
_sendToPeers (event: Event) {
|
||||||
debug(`#onPeerclose: ${peer.id}`)
|
|
||||||
this.peers.update(peers => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
||||||
delete peers[peer.id]
|
|
||||||
return peers
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#sendToPeers (event: Event) {
|
|
||||||
if (!this.#p2pt) return
|
|
||||||
for (const { peer } of Object.values(this.peers.value)) {
|
for (const { peer } of Object.values(this.peers.value)) {
|
||||||
if (peer) this.#sendEvent(peer, event)
|
if (peer) this._sendEvent(peer, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy () {
|
destroy () {
|
||||||
debug('destroy')
|
debug('destroy')
|
||||||
this.#p2pt?.destroy()
|
this.#p2pt.destroy()
|
||||||
this.removeAllListeners()
|
this.removeAllListeners()
|
||||||
this.#p2pt = null
|
|
||||||
this.isHost = false
|
this.isHost = false
|
||||||
this.peers.value = {}
|
this.peers.value = {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/lib/modules/w2g/lobby.ts
Normal file
5
src/lib/modules/w2g/lobby.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable } from 'simple-store-svelte'
|
||||||
|
|
||||||
|
import type { W2GClient } from '.'
|
||||||
|
|
||||||
|
export const w2globby = writable<W2GClient | undefined>()
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import FileImage from 'lucide-svelte/icons/file-image'
|
import FileImage from 'lucide-svelte/icons/file-image'
|
||||||
import Trash from 'lucide-svelte/icons/trash'
|
import Trash from 'lucide-svelte/icons/trash'
|
||||||
import X from 'lucide-svelte/icons/x'
|
import X from 'lucide-svelte/icons/x'
|
||||||
|
import { tick } from 'svelte'
|
||||||
import MagnifyingGlass from 'svelte-radix/MagnifyingGlass.svelte'
|
import MagnifyingGlass from 'svelte-radix/MagnifyingGlass.svelte'
|
||||||
import { toast } from 'svelte-sonner'
|
import { toast } from 'svelte-sonner'
|
||||||
|
|
||||||
|
|
@ -10,6 +11,7 @@
|
||||||
import type { Search } from '$lib/modules/anilist/queries'
|
import type { Search } from '$lib/modules/anilist/queries'
|
||||||
import type { VariablesOf } from 'gql.tada'
|
import type { VariablesOf } from 'gql.tada'
|
||||||
|
|
||||||
|
import { replaceState } from '$app/navigation'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { badgeVariants } from '$lib/components/ui/badge'
|
import { badgeVariants } from '$lib/components/ui/badge'
|
||||||
import { Button } from '$lib/components/ui/button'
|
import { Button } from '$lib/components/ui/button'
|
||||||
|
|
@ -93,6 +95,8 @@
|
||||||
onList: [] as format[]
|
onList: [] as format[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: console.log('search updated', search)
|
||||||
|
|
||||||
let pageNumber = 1
|
let pageNumber = 1
|
||||||
let inputText = ''
|
let inputText = ''
|
||||||
|
|
||||||
|
|
@ -123,7 +127,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchQuery (filter: Partial<typeof search>, page: number) {
|
function searchQuery (filter: Partial<typeof search>, page: number) {
|
||||||
return client.search({
|
const search = {
|
||||||
page,
|
page,
|
||||||
ids: filter.ids,
|
ids: filter.ids,
|
||||||
search: filter.name,
|
search: filter.name,
|
||||||
|
|
@ -134,7 +138,11 @@
|
||||||
format: filter.formats?.map(f => f.value) as Array<'MUSIC' | 'MANGA' | 'TV' | 'TV_SHORT' | 'MOVIE' | 'SPECIAL' | 'OVA' | 'ONA' | 'NOVEL' | 'ONE_SHOT'>,
|
format: filter.formats?.map(f => f.value) as Array<'MUSIC' | 'MANGA' | 'TV' | 'TV_SHORT' | 'MOVIE' | 'SPECIAL' | 'OVA' | 'ONA' | 'NOVEL' | 'ONE_SHOT'>,
|
||||||
status: filter.status?.map(s => s.value) as Array<'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS' | null>,
|
status: filter.status?.map(s => s.value) as Array<'FINISHED' | 'RELEASING' | 'NOT_YET_RELEASED' | 'CANCELLED' | 'HIATUS' | null>,
|
||||||
sort: [filter.sort?.[0]?.value ?? 'SEARCH_MATCH'] as Array<'TITLE_ROMAJI_DESC' | 'ID' | 'START_DATE_DESC' | 'SCORE_DESC' | 'POPULARITY_DESC' | 'TRENDING_DESC' | 'UPDATED_AT_DESC' | 'ID_DESC' | 'TITLE_ROMAJI' | 'TITLE_ENGLISH' | 'TITLE_ENGLISH_DESC' | null>
|
sort: [filter.sort?.[0]?.value ?? 'SEARCH_MATCH'] as Array<'TITLE_ROMAJI_DESC' | 'ID' | 'START_DATE_DESC' | 'SCORE_DESC' | 'POPULARITY_DESC' | 'TRENDING_DESC' | 'UPDATED_AT_DESC' | 'ID_DESC' | 'TITLE_ROMAJI' | 'TITLE_ENGLISH' | 'TITLE_ENGLISH_DESC' | null>
|
||||||
})
|
}
|
||||||
|
|
||||||
|
tick().then(() => replaceState('', { search }))
|
||||||
|
|
||||||
|
return client.search(search)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateText = debounce((e: FormInputEvent) => {
|
const updateText = debounce((e: FormInputEvent) => {
|
||||||
|
|
|
||||||
10
src/routes/app/w2g/+page.ts
Normal file
10
src/routes/app/w2g/+page.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
import { generateRandomHexCode, W2GClient } from '$lib/modules/w2g'
|
||||||
|
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||||
|
|
||||||
|
export function load () {
|
||||||
|
w2globby.value ??= new W2GClient(generateRandomHexCode(16), true)
|
||||||
|
|
||||||
|
redirect(302, '/app/w2g/' + w2globby.value.code)
|
||||||
|
}
|
||||||
60
src/routes/app/w2g/[id]/+page.svelte
Normal file
60
src/routes/app/w2g/[id]/+page.svelte
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script lang='ts' context='module'>
|
||||||
|
import SendHorizontal from 'lucide-svelte/icons/send-horizontal'
|
||||||
|
|
||||||
|
import { Button } from '$lib/components/ui/button'
|
||||||
|
import { Messages, UserList } from '$lib/components/ui/chat'
|
||||||
|
import { Textarea } from '$lib/components/ui/textarea'
|
||||||
|
import { W2GClient } from '$lib/modules/w2g'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang='ts'>
|
||||||
|
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||||
|
|
||||||
|
export let data
|
||||||
|
|
||||||
|
$w2globby ??= new W2GClient(data.id, false)
|
||||||
|
|
||||||
|
$: users = $w2globby!.peers
|
||||||
|
$: messages = $w2globby!.messages
|
||||||
|
|
||||||
|
let message = ''
|
||||||
|
let rows = 1
|
||||||
|
|
||||||
|
function sendMessage () {
|
||||||
|
$w2globby?.message(message.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkInput (e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey && message.trim()) {
|
||||||
|
sendMessage()
|
||||||
|
} else {
|
||||||
|
rows = message.split('\n').length || 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateRows () {
|
||||||
|
rows = message.split('\n').length || 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$: prcoessedUsers = Object.values($users).map(({ user }) => user)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class='flex flex-col w-full relative px-md-4 h-full overflow-hidden'>
|
||||||
|
<div class='flex md:flex-row flex-col-reverse w-full h-full pt-4'>
|
||||||
|
<div class='flex flex-col justify-end overflow-hidden flex-grow px-4 md:pb-4'>
|
||||||
|
<Messages {messages} />
|
||||||
|
<div class='flex mt-4'>
|
||||||
|
<Textarea
|
||||||
|
bind:value={message}
|
||||||
|
class='h-auto px-3 w-full flex-grow-1 resize-none min-h-0 border-0 bg-background select:bg-accent select:text-accent-foreground'
|
||||||
|
{rows}
|
||||||
|
autocomplete='off'
|
||||||
|
maxlength={256}
|
||||||
|
placeholder='Message' on:keydown={checkInput} on:input={updateRows} />
|
||||||
|
<Button on:click={sendMessage} size='icon' class='mt-auto ml-2 border-0' variant='outline'>
|
||||||
|
<SendHorizontal size={18} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserList users={prcoessedUsers} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
5
src/routes/app/w2g/[id]/+page.ts
Normal file
5
src/routes/app/w2g/[id]/+page.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { PageLoad } from './$types'
|
||||||
|
|
||||||
|
export const load: PageLoad = ({ params }) => {
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,8 @@ export default defineConfig({
|
||||||
'./Scripts': resolve(__dirname, 'src/patches/empty.cjs'),
|
'./Scripts': resolve(__dirname, 'src/patches/empty.cjs'),
|
||||||
// yeah they dont export this for making custom icons, sucks
|
// yeah they dont export this for making custom icons, sucks
|
||||||
'lucide-svelte/dist/Icon.svelte': resolve(__dirname, 'node_modules/lucide-svelte/dist/Icon.svelte'),
|
'lucide-svelte/dist/Icon.svelte': resolve(__dirname, 'node_modules/lucide-svelte/dist/Icon.svelte'),
|
||||||
|
// no exports :/
|
||||||
|
'bittorrent-tracker/lib/client/websocket-tracker.js': resolve(__dirname, 'node_modules/bittorrent-tracker/lib/client/websocket-tracker.js'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: { port: 7344 },
|
server: { port: 7344 },
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue