mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-01-12 02:21:49 +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": {
|
||||
"source.fixAll.eslint": "always"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"extensions.ignoreRecommendations": false,
|
||||
"eslint.useESLintClass": true,
|
||||
"eslint.useFlatConfig": true,
|
||||
"editor.linkedEditing": true,
|
||||
"editor.tabSize": 2,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.probe": [
|
||||
"javascript",
|
||||
|
|
@ -17,6 +14,8 @@
|
|||
"svelte",
|
||||
"html"
|
||||
],
|
||||
"eslint.useESLintClass": true,
|
||||
"eslint.useFlatConfig": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
|
|
@ -24,5 +23,21 @@
|
|||
"typescriptreact",
|
||||
"svelte",
|
||||
"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",
|
||||
"version": "6.3.16",
|
||||
"version": "6.3.17",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
"svelte-radix": "^1.1.1",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"vaul-svelte": "^0.3.2",
|
||||
"vite": "^5.4.11"
|
||||
},
|
||||
|
|
@ -63,7 +64,7 @@
|
|||
"js-levenshtein": "^1.1.6",
|
||||
"lucide-svelte": "^0.511.0",
|
||||
"marked": "^15.0.11",
|
||||
"p2pt": "^1.5.1",
|
||||
"p2pt": "github:ThaUnknown/p2pt#modernise",
|
||||
"rollup-plugin-license": "^3.6.0",
|
||||
"semver": "^7.7.2",
|
||||
"simple-store-svelte": "^1.0.6",
|
||||
|
|
|
|||
199
pnpm-lock.yaml
199
pnpm-lock.yaml
|
|
@ -75,8 +75,8 @@ importers:
|
|||
specifier: ^15.0.11
|
||||
version: 15.0.11
|
||||
p2pt:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
specifier: github:ThaUnknown/p2pt#modernise
|
||||
version: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316
|
||||
rollup-plugin-license:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
|
||||
|
|
@ -171,6 +171,9 @@ importers:
|
|||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.17
|
||||
typescript:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
vaul-svelte:
|
||||
specifier: ^0.3.2
|
||||
version: 0.3.2(svelte@4.2.19)
|
||||
|
|
@ -644,6 +647,12 @@ packages:
|
|||
'@swc/helpers@0.5.17':
|
||||
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':
|
||||
resolution: {integrity: sha512-oP+mrvD2U7gSXHTfT77+A+i2YVT5jp4qCbCXLrNU9aFzXdJ0iRsBbdhdT/AgeB7Nf4O+SC/wVCnwhnAfUoo0Fg==}
|
||||
|
||||
|
|
@ -770,8 +779,9 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
addr-to-ip-port@1.5.4:
|
||||
resolution: {integrity: sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==}
|
||||
addr-to-ip-port@2.0.0:
|
||||
resolution: {integrity: sha512-9bYbtjamtdLHZSqVIUXhilOryNPiL+x+Q5J/Unpg4VY3ZIkK3fT52UoErj1NdUeVm3J1t2iBEAur4Ywbl/bahw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
|
@ -873,11 +883,9 @@ packages:
|
|||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
bencode@2.0.3:
|
||||
resolution: {integrity: sha512-D/vrAD4dLVX23NalHwb8dSvsUsxeRPO8Y7ToKA015JQYq69MLDOMkC0uGZYA/MPpltLO8rt8eqFC2j8DxjTZ/w==}
|
||||
bencode@4.0.0:
|
||||
resolution: {integrity: sha512-AERXw18df0pF3ziGOCyUjqKZBVNH8HV3lBxnx5w0qtgMIk4a1wb9BkcCQbkp9Zstfrn/dzRwl7MmUHHocX3sRQ==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
|
|
@ -896,14 +904,11 @@ packages:
|
|||
bittorrent-peerid@1.3.6:
|
||||
resolution: {integrity: sha512-VyLcUjVMEOdSpHaCG/7odvCdLbAB1y3l9A2V6WIje24uV7FkJPrQrH/RrlFmKxP89pFVDEnE+YlHaFujlFIZsg==}
|
||||
|
||||
bittorrent-tracker@9.19.0:
|
||||
resolution: {integrity: sha512-09d0aD2b+MC+zWvWajkUAKkYMynYW4tMbTKiRSthKtJZbafzEoNQSUHyND24SoCe3ZOb2fKfa6fu2INAESL9wA==}
|
||||
engines: {node: '>=12'}
|
||||
bittorrent-tracker@10.0.12:
|
||||
resolution: {integrity: sha512-EYQEwhOYkrRiiwkCFcM9pbzJInsAe7UVmUgevW133duwlZzjwf5ABwDE7pkkmNRS6iwN0b8LbI/94q16dYqiow==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
hasBin: true
|
||||
|
||||
bn.js@5.2.2:
|
||||
resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==}
|
||||
|
||||
bottleneck@2.19.5:
|
||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||
|
||||
|
|
@ -925,9 +930,6 @@ packages:
|
|||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
bufferutil@4.0.9:
|
||||
resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
|
|
@ -1497,9 +1499,6 @@ packages:
|
|||
idb-keyval@6.2.2:
|
||||
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
ignore@5.3.2:
|
||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||
engines: {node: '>= 4'}
|
||||
|
|
@ -1893,8 +1892,9 @@ packages:
|
|||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p2pt@1.5.1:
|
||||
resolution: {integrity: sha512-q1pkIKBRvGcQfv5Q3W0/c9pbBUnjcauWylc/qUZwIqcrQIxu3rfuDQXsqjwEJaBwdPNPWMY06jc5qxwVgJX6MA==}
|
||||
p2pt@https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316:
|
||||
resolution: {tarball: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316}
|
||||
version: 1.5.1
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
|
@ -2034,9 +2034,6 @@ packages:
|
|||
random-iterate@1.0.1:
|
||||
resolution: {integrity: sha512-Jdsdnezu913Ot8qgKgSgs63XkAjEsnMcS1z+cC6D6TNXsUXsMxy0RpclF2pzGZTEiTXL9BiArdGTEexcv4nqcA==}
|
||||
|
||||
randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
|
||||
react@19.0.0:
|
||||
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -2044,10 +2041,6 @@ packages:
|
|||
read-cache@1.0.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
|
@ -2097,9 +2090,6 @@ packages:
|
|||
run-series@1.1.9:
|
||||
resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==}
|
||||
|
||||
rusha@0.8.14:
|
||||
resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==}
|
||||
|
||||
rvfc-polyfill@1.0.7:
|
||||
resolution: {integrity: sha512-seBl7J1J3/k0LuzW2T9fG6JIOpni5AbU+/87LA+zTYKgTVhsfShmS8K/yOo1eeEjGJHnAdkVAUUM+PEjN9Mpkw==}
|
||||
|
||||
|
|
@ -2111,9 +2101,6 @@ packages:
|
|||
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
|
||||
safe-push-apply@1.0.0:
|
||||
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -2180,18 +2167,9 @@ packages:
|
|||
simple-get@4.0.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-39TaQ2LHRAdH+cpWPmGzDfVyoAm/uZ6UkM27O3YhUtJHk0Vw09+6/jUqprns+BgOkKxOkRetKC9SH4bbj0IZ1A==}
|
||||
|
||||
simple-websocket@9.1.0:
|
||||
resolution: {integrity: sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==}
|
||||
|
||||
sirv@3.0.1:
|
||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -2262,11 +2240,9 @@ packages:
|
|||
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string2compact@1.3.2:
|
||||
resolution: {integrity: sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==}
|
||||
|
||||
string_decoder@1.3.0:
|
||||
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||
string2compact@2.0.1:
|
||||
resolution: {integrity: sha512-Bm/T8lHMTRXw+u83LE+OW7fXmC/wM+Mbccfdo533ajSBNxddDHlRrvxE49NdciGHgXkUQM5WYskJ7uTkbBUI0A==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||
|
|
@ -2600,12 +2576,12 @@ packages:
|
|||
bundledDependencies:
|
||||
- node-pre-gyp
|
||||
|
||||
ws@7.5.10:
|
||||
resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
|
||||
engines: {node: '>=8.3.0'}
|
||||
ws@8.18.2:
|
||||
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ^5.0.2
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
|
|
@ -3010,6 +2986,29 @@ snapshots:
|
|||
dependencies:
|
||||
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':
|
||||
dependencies:
|
||||
grapheme-splitter: 1.0.4
|
||||
|
|
@ -3165,7 +3164,7 @@ snapshots:
|
|||
|
||||
acorn@8.14.1: {}
|
||||
|
||||
addr-to-ip-port@1.5.4: {}
|
||||
addr-to-ip-port@2.0.0: {}
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
|
|
@ -3276,9 +3275,9 @@ snapshots:
|
|||
|
||||
base64-arraybuffer@1.0.2: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
bencode@2.0.3: {}
|
||||
bencode@4.0.0:
|
||||
dependencies:
|
||||
uint8-util: 2.2.5
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
|
|
@ -3298,11 +3297,12 @@ snapshots:
|
|||
|
||||
bittorrent-peerid@1.3.6: {}
|
||||
|
||||
bittorrent-tracker@9.19.0:
|
||||
bittorrent-tracker@10.0.12:
|
||||
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
|
||||
bn.js: 5.2.2
|
||||
chrome-dgram: 3.0.6
|
||||
clone: 2.1.2
|
||||
compact2string: 1.4.1
|
||||
|
|
@ -3313,24 +3313,20 @@ snapshots:
|
|||
once: 1.4.0
|
||||
queue-microtask: 1.2.3
|
||||
random-iterate: 1.0.1
|
||||
randombytes: 2.1.0
|
||||
run-parallel: 1.2.0
|
||||
run-series: 1.1.9
|
||||
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
|
||||
string2compact: 1.3.2
|
||||
string2compact: 2.0.1
|
||||
uint8-util: 2.2.5
|
||||
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:
|
||||
bufferutil: 4.0.9
|
||||
utf-8-validate: 5.0.10
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
bn.js@5.2.2: {}
|
||||
|
||||
bottleneck@2.19.5: {}
|
||||
|
||||
brace-expansion@1.1.11:
|
||||
|
|
@ -3356,11 +3352,6 @@ snapshots:
|
|||
buffer-from@1.1.2:
|
||||
optional: true
|
||||
|
||||
buffer@6.0.3:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
bufferutil@4.0.9:
|
||||
dependencies:
|
||||
node-gyp-build: 4.8.4
|
||||
|
|
@ -4068,8 +4059,6 @@ snapshots:
|
|||
|
||||
idb-keyval@6.2.2: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
||||
ignore@7.0.4: {}
|
||||
|
|
@ -4434,11 +4423,10 @@ snapshots:
|
|||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
p2pt@1.5.1:
|
||||
p2pt@https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316:
|
||||
dependencies:
|
||||
bittorrent-tracker: 9.19.0
|
||||
randombytes: 2.1.0
|
||||
simple-sha1: 3.1.0
|
||||
bittorrent-tracker: 10.0.12
|
||||
uint8-util: 2.2.5
|
||||
optionalDependencies:
|
||||
wrtc: 0.4.7
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -4551,22 +4539,12 @@ snapshots:
|
|||
|
||||
random-iterate@1.0.1: {}
|
||||
|
||||
randombytes@2.1.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
react@19.0.0: {}
|
||||
|
||||
read-cache@1.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
|
@ -4651,8 +4629,6 @@ snapshots:
|
|||
|
||||
run-series@1.1.9: {}
|
||||
|
||||
rusha@0.8.14: {}
|
||||
|
||||
rvfc-polyfill@1.0.7: {}
|
||||
|
||||
sade@1.8.1:
|
||||
|
|
@ -4667,8 +4643,6 @@ snapshots:
|
|||
has-symbols: 1.1.0
|
||||
isarray: 2.0.5
|
||||
|
||||
safe-buffer@5.2.1: {}
|
||||
|
||||
safe-push-apply@1.0.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
|
@ -4752,37 +4726,8 @@ snapshots:
|
|||
once: 1.4.0
|
||||
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-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:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.29
|
||||
|
|
@ -4878,15 +4823,11 @@ snapshots:
|
|||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
string2compact@1.3.2:
|
||||
string2compact@2.0.1:
|
||||
dependencies:
|
||||
addr-to-ip-port: 1.5.4
|
||||
addr-to-ip-port: 2.0.0
|
||||
ipaddr.js: 2.2.0
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
strip-ansi@6.0.1:
|
||||
dependencies:
|
||||
ansi-regex: 5.0.1
|
||||
|
|
@ -5271,7 +5212,7 @@ snapshots:
|
|||
domexception: 1.0.1
|
||||
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:
|
||||
bufferutil: 4.0.9
|
||||
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' {
|
||||
export default SvelteComponentTyped
|
||||
}
|
||||
// declare module '*.svelte' {
|
||||
// export default SvelteComponentTyped
|
||||
// }
|
||||
}
|
||||
|
||||
declare module '*.svelte' {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Hayase</title>
|
||||
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
|
||||
%sveltekit.head%
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang='ts'>
|
||||
import { getPFP, type ChatMessage } from '.'
|
||||
|
||||
import type { ChatMessage } from '.'
|
||||
import type { Writable } from 'simple-store-svelte'
|
||||
|
||||
export let messages: Writable<ChatMessage[]>
|
||||
|
|
@ -9,7 +8,7 @@
|
|||
if (!messages.length) return []
|
||||
const grouped = []
|
||||
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) {
|
||||
last.messages.push(message)
|
||||
} else {
|
||||
|
|
@ -23,11 +22,11 @@
|
|||
{#each groupMessages($messages) as { type, user, date, messages }, i (i)}
|
||||
{@const incoming = type === '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='pb-1 flex flex-row items-center px-1'>
|
||||
<div class='font-bold text-sm'>
|
||||
{user.nick}
|
||||
{user.name}
|
||||
</div>
|
||||
<div class='text-muted-foreground pl-2 text-[10px] leading-relaxed'>
|
||||
{date.toLocaleTimeString()}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,14 @@
|
|||
<script lang='ts'>
|
||||
import ExternalLink from 'lucide-svelte/icons/external-link'
|
||||
|
||||
import { getPFP, type ChatUser } from '.'
|
||||
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { ChatUser } from '.'
|
||||
|
||||
import native from '$lib/modules/native'
|
||||
import { click } from '$lib/modules/navigate'
|
||||
|
||||
export let users: Writable<Record<string, ChatUser>>
|
||||
export let users: ChatUser[]
|
||||
|
||||
function processUsers (users: ChatUser[]) {
|
||||
return users.map(user => {
|
||||
return {
|
||||
...user,
|
||||
pfp: getPFP(user)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$: processed = processUsers(Object.values($users))
|
||||
$: processed = Object.entries(users)
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col w-72 max-w-full px-5 overflow-hidden'>
|
||||
|
|
@ -27,13 +16,13 @@
|
|||
{processed.length} Member(s)
|
||||
</div>
|
||||
<div>
|
||||
{#each processed as { id, pfp, nick } (id)}
|
||||
{#each processed as [key, user] (key)}
|
||||
<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'>
|
||||
{nick}
|
||||
{user.name}
|
||||
</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' />
|
||||
</span>
|
||||
</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 {
|
||||
nick: string
|
||||
id: string
|
||||
pfpid: string
|
||||
type: UserType
|
||||
}
|
||||
export type ChatUser = Omit<NonNullable<ResultOf<typeof Viewer>['Viewer']>, 'id'> & { id: string | number }
|
||||
|
||||
export interface ChatMessage {
|
||||
message: string
|
||||
|
|
@ -14,13 +10,5 @@ export interface ChatMessage {
|
|||
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 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'>
|
||||
import SendHorizontal from 'lucide-svelte/icons/send-horizontal'
|
||||
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 MessageClient from '$lib/modules/irc'
|
||||
|
||||
|
|
@ -12,7 +8,7 @@
|
|||
</script>
|
||||
|
||||
<script lang='ts'>
|
||||
import { Button } from '../button'
|
||||
import Interface from './interface.svelte'
|
||||
|
||||
const viewer = client.viewer.value
|
||||
|
||||
|
|
@ -28,29 +24,6 @@
|
|||
}
|
||||
|
||||
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>
|
||||
|
||||
{#if $irc}
|
||||
|
|
@ -69,26 +42,6 @@
|
|||
Loading...
|
||||
</div>
|
||||
{:then client}
|
||||
<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(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} /> -->
|
||||
<Interface {client} />
|
||||
{/await}
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import { fillerEpisodes } from '$lib/components/EpisodesList.svelte'
|
||||
import { cover, episodes, title } from '$lib/modules/anilist'
|
||||
import { settings } from '$lib/modules/settings'
|
||||
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||
|
||||
export let mediaInfo: NonNullable<Awaited<ReturnType<typeof resolveFilesPoorly>>>
|
||||
|
||||
|
|
@ -27,6 +28,14 @@
|
|||
|
||||
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) {
|
||||
return mediaInfo.targetAnimeFiles.find(file => file.metadata.episode === episode)
|
||||
}
|
||||
|
|
@ -70,6 +79,7 @@
|
|||
|
||||
function selectFile (file: ResolvedFile) {
|
||||
current = fileToMedaInfo(file)
|
||||
$w2globby?.mediaIndexChanged(mediaInfo.resolvedFiles.indexOf(current.file))
|
||||
}
|
||||
|
||||
$: next = hasNext(current)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
import { click } from '$lib/modules/navigate'
|
||||
import { settings } from '$lib/modules/settings'
|
||||
import { server } from '$lib/modules/torrent'
|
||||
import { w2globby } from '$lib/modules/w2g/lobby'
|
||||
import { toTS, fastPrettyBits } from '$lib/utils'
|
||||
|
||||
export let mediaInfo: MediaInfo
|
||||
|
|
@ -652,6 +653,12 @@
|
|||
|
||||
return { destroy: () => ctrl.abort() }
|
||||
}
|
||||
|
||||
$: $w2globby?.playerStateChanged({ paused, time: Math.floor(currentTime) })
|
||||
$: $w2globby?.on('player', state => {
|
||||
currentTime = state.time
|
||||
paused = state.paused
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:document bind:fullscreenElement bind:visibilityState use:holdToFF={'key'} />
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@
|
|||
<SidebarButton href='/app/schedule/'>
|
||||
<Calendar size={18} />
|
||||
</SidebarButton>
|
||||
<!-- <SidebarButton href='/app/w2g/'> -->
|
||||
<SidebarButton disabled={true}>
|
||||
<SidebarButton href='/app/w2g/'>
|
||||
<Users size={18} />
|
||||
</SidebarButton>
|
||||
<SidebarButton href='/app/chat/'>
|
||||
|
|
@ -55,7 +54,6 @@
|
|||
<Download size={18} />
|
||||
</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]'>
|
||||
<!-- <Heart size={18} fill='currentColor' class='absolute' /> -->
|
||||
<Heart size={18} fill='currentColor' class={cn(active && 'donate')} />
|
||||
</Button>
|
||||
<SidebarButton href='/app/settings/'>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { EventEmitter } from 'events'
|
||||
|
||||
import Client, { createChannelConstructor } from '@thaunknown/web-irc'
|
||||
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 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 PrivMessage {
|
||||
|
|
@ -24,30 +39,48 @@ export interface PrivMessage {
|
|||
time: number
|
||||
}
|
||||
|
||||
export default class MessageClient extends EventEmitter {
|
||||
irc = new Client(null)
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
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>>({})
|
||||
messages = writable<ChatMessage[]>([])
|
||||
channel?: IrcChannel
|
||||
ident
|
||||
|
||||
constructor (ident: ChatUser) {
|
||||
super()
|
||||
constructor (ident: IRCChatUser) {
|
||||
this.ident = ident
|
||||
this.irc.on('userlist', async ({ users }: { users: IRCUser[] }) => {
|
||||
this.users.value = users.reduce((acc, user) => {
|
||||
const [nick, pfpid, pfpex] = user.nick.split('_') as [string, string, string]
|
||||
const [type, id] = user.ident.split('_') as ['al' | 'guest', string]
|
||||
acc[user.ident] = { nick, id, pfpid: `${pfpid}.${pfpex}`, type }
|
||||
this.irc.on('userlist', async ({ users }) => {
|
||||
this.users.value = users.reduce((acc, ircuser) => {
|
||||
const user = ircIdentToChatUser(ircuser)
|
||||
acc[ircuser.ident] = user
|
||||
return acc
|
||||
}, this.users.value)
|
||||
})
|
||||
|
||||
this.irc.on('join', async (user: IRCUser) => {
|
||||
this.irc.on('join', async ircuser => {
|
||||
try {
|
||||
const [nick, pfpid, pfpex] = user.nick.split('_') as [string, string, string]
|
||||
const [type, id] = user.ident.split('_') as ['al' | 'guest', string]
|
||||
this.users.value[user.ident] = { nick, id, pfpid: `${pfpid}.${pfpex}`, type }
|
||||
const user = ircIdentToChatUser(ircuser)
|
||||
this.users.value[ircuser.ident] = user
|
||||
this.users.update(users => users)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
|
@ -63,9 +96,9 @@ export default class MessageClient extends EventEmitter {
|
|||
this.irc.on('part', deleteUser)
|
||||
this.irc.on('kick', deleteUser)
|
||||
|
||||
this.irc.on('privmsg', async (priv: PrivMessage) => {
|
||||
const message = await decryptMessage(priv.message)
|
||||
this.irc.on('privmsg', async priv => {
|
||||
try {
|
||||
const message = await decryptMessage(priv.message)
|
||||
this.messages.update(messages => [...messages, {
|
||||
message,
|
||||
user: this.users.value[priv.ident]!,
|
||||
|
|
@ -83,17 +116,17 @@ export default class MessageClient extends EventEmitter {
|
|||
const encrypted = await encryptMessage(message)
|
||||
this.channel!.say(encrypted)
|
||||
this.messages.update(messages => [...messages, {
|
||||
user: this.ident,
|
||||
user: ircUserToChatUser(this.ident),
|
||||
message,
|
||||
date: new Date(),
|
||||
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 })
|
||||
|
||||
await new Promise(resolve => {
|
||||
await new Promise<void>(resolve => {
|
||||
client.irc.once('connected', resolve)
|
||||
client.irc.connect({
|
||||
version: null,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { get } from 'svelte/store'
|
|||
import { persisted } from 'svelte-persisted-store'
|
||||
|
||||
import native from '../native'
|
||||
import { w2globby } from '../w2g/lobby'
|
||||
|
||||
import type { TorrentFile, TorrentInfo } from '../../../app'
|
||||
import type { Media } from '../anilist'
|
||||
|
|
@ -53,6 +54,7 @@ export const server = new class ServerClient {
|
|||
play (id: string, media: Media, episode: number) {
|
||||
this.last.set({ id, media, episode })
|
||||
this.active.value = this._play(id, media, episode)
|
||||
w2globby.value?.mediaChange({ episode, mediaId: media.id, torrent: id })
|
||||
return this.active.value
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
export default class Event<T = unknown> {
|
||||
payload
|
||||
type = ''
|
||||
constructor (type: string, payload: T) {
|
||||
import type { ChatUser } from '$lib/components/ui/chat'
|
||||
|
||||
export default class Event<K extends keyof W2GEvents = keyof W2GEvents> {
|
||||
readonly type: K
|
||||
readonly payload: W2GEvents[K]
|
||||
|
||||
constructor (type: K, payload: W2GEvents[K]) {
|
||||
this.type = type
|
||||
this.payload = payload
|
||||
}
|
||||
}
|
||||
|
||||
export const EventTypes = {
|
||||
SessionInitEvent: 'init',
|
||||
MagnetLinkEvent: 'magnet',
|
||||
MediaIndexEvent: 'index',
|
||||
PlayerStateEvent: 'player',
|
||||
MessageEvent: 'message'
|
||||
export interface PlayerState { paused: boolean, time: number }
|
||||
|
||||
export interface MediaState { torrent: string, mediaId: number, episode: number }
|
||||
|
||||
export interface W2GEvents {
|
||||
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 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 { ResultOf } from 'gql.tada'
|
||||
import type { ChatMessage, ChatUser } from '$lib/components/ui/chat'
|
||||
|
||||
const debug = Debug('ui:w2g')
|
||||
|
||||
function generateRandomHexCode (len: number) {
|
||||
export function generateRandomHexCode (len: number) {
|
||||
let hexCode = ''
|
||||
|
||||
while (hexCode.length < len) {
|
||||
|
|
@ -23,73 +23,77 @@ function generateRandomHexCode (len: number) {
|
|||
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 {
|
||||
static readonly #announce = [
|
||||
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
||||
atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='),
|
||||
atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'),
|
||||
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
|
||||
]
|
||||
const ANNOUNCE = [
|
||||
atob('d3NzOi8vdHJhY2tlci5vcGVud2VidG9ycmVudC5jb20='),
|
||||
atob('d3NzOi8vdHJhY2tlci53ZWJ0b3JyZW50LmRldg=='),
|
||||
atob('d3NzOi8vdHJhY2tlci5maWxlcy5mbTo3MDczL2Fubm91bmNl'),
|
||||
atob('d3NzOi8vdHJhY2tlci5idG9ycmVudC54eXov')
|
||||
]
|
||||
|
||||
export class W2GClient extends EventEmitter<{index: [number], player: [PlayerState]}> {
|
||||
player: PlayerState = {
|
||||
paused: true,
|
||||
time: 0
|
||||
}
|
||||
|
||||
index = 0
|
||||
magnet: {magnet: string, hash: string} | null = null
|
||||
isHost = false
|
||||
#p2pt: P2PT | null
|
||||
media: MediaState | undefined
|
||||
isHost
|
||||
readonly #p2pt
|
||||
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) }
|
||||
/** @type {import('simple-store-svelte').Writable<PeerList>} */
|
||||
self: ChatUser = client.viewer.value?.viewer ?? { id: generateRandomHexCode(16), avatar: null, mediaListOptions: null, name: 'Guest' }
|
||||
peers = writable<PeerList>({ [this.self.id]: { user: this.self } })
|
||||
|
||||
get inviteLink () {
|
||||
return `https://miru.watch/w2g/${this.code}`
|
||||
return `https://hayas.ee/w2g/${this.code}`
|
||||
}
|
||||
|
||||
localMediaIndexChanged (index: number) {
|
||||
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) {
|
||||
constructor (code: string, isHost: boolean) {
|
||||
super()
|
||||
this.isHost = !code
|
||||
this.isHost = isHost
|
||||
|
||||
this.code = code ?? generateRandomHexCode(16)
|
||||
this.code = code
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
magnetLink (magnet: { hash: string, magnet: string }) {
|
||||
debug(`magnetLink: ${this.magnet?.hash} ${magnet.hash}`)
|
||||
if (this.magnet?.hash !== magnet.hash) {
|
||||
this.magnet = magnet
|
||||
mediaChange (media: MediaState) {
|
||||
debug(`mediaChange: ${this.media?.torrent} ${media.torrent}`)
|
||||
if (this.media?.torrent !== media.torrent) {
|
||||
this.media = media
|
||||
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}`)
|
||||
if (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) {
|
||||
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) {
|
||||
|
|
@ -123,77 +127,61 @@ export class W2GClient extends EventEmitter {
|
|||
type: 'outgoing',
|
||||
date: new Date()
|
||||
})])
|
||||
this.#sendToPeers(new Event('message', message))
|
||||
this._sendToPeers(new Event('message', message))
|
||||
}
|
||||
|
||||
#wireEvents () {
|
||||
this.#p2pt?.on('peerconnect', this.#onPeerconnect.bind(this))
|
||||
this.#p2pt?.on('msg', this.#onMsg.bind(this))
|
||||
this.#p2pt?.on('peerclose', this.#onPeerclose.bind(this))
|
||||
_sendEvent (peer: Peer, event: Event) {
|
||||
debug(`sendEvent: ${peer.id} ${JSON.stringify(event)}`)
|
||||
this.#p2pt.send(peer, event)
|
||||
}
|
||||
|
||||
#sendEvent (peer: Peer, event: Event) {
|
||||
debug(`#sendEvent: ${peer.id} ${JSON.stringify(event)}`)
|
||||
this.#p2pt?.send(peer, JSON.stringify(event))
|
||||
_sendInitialSessionState (peer: Peer) {
|
||||
this._sendEvent(peer, new Event('media', this.media))
|
||||
this._sendEvent(peer, new Event('index', this.index))
|
||||
this._sendEvent(peer, new Event('player', this.player))
|
||||
}
|
||||
|
||||
#sendInitialSessionState (peer: Peer) {
|
||||
this.#sendEvent(peer, new Event('magnet', this.magnet))
|
||||
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
|
||||
_onMsg = async (peer: Peer, data: AppEvent) => {
|
||||
debug(`onMsg: ${peer.id} ${JSON.stringify(data)}`)
|
||||
|
||||
switch (data.type) {
|
||||
case EventTypes.SessionInitEvent:
|
||||
case 'init':
|
||||
this.peers.update(peers => {
|
||||
peers[peer.id] = {
|
||||
peer,
|
||||
user: data.payload as ResultOf<typeof Viewer>['Viewer']
|
||||
user: data.payload
|
||||
}
|
||||
return peers
|
||||
})
|
||||
break
|
||||
case EventTypes.MagnetLinkEvent: {
|
||||
const cast = data as Event<{magnet: string, hash: string}>
|
||||
if (cast.payload.magnet === undefined) break
|
||||
const { hash, magnet } = cast.payload
|
||||
if (hash !== this.magnet?.hash) {
|
||||
case 'media': {
|
||||
if (data.payload?.torrent == null || data.payload?.mediaId == null) break
|
||||
const { torrent, mediaId, episode } = data.payload
|
||||
if (torrent !== this.media?.torrent) {
|
||||
this.isHost = false
|
||||
this.magnet = cast.payload
|
||||
add(magnet)
|
||||
this.media = data.payload
|
||||
const media = (await client.single(mediaId)).data?.Media
|
||||
if (media == null) break
|
||||
server.play(torrent, media, episode)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
case EventTypes.MediaIndexEvent: {
|
||||
const cast = data as Event<{index: number}>
|
||||
if (cast.payload.index === undefined) break
|
||||
if (this.index !== cast.payload.index) {
|
||||
this.index = cast.payload.index
|
||||
this.emit('index', cast.payload.index)
|
||||
case 'index': {
|
||||
if (data.payload == null) break
|
||||
if (this.index !== data.payload) {
|
||||
this.index = data.payload
|
||||
this.emit('index', data.payload)
|
||||
}
|
||||
break
|
||||
}
|
||||
case EventTypes.PlayerStateEvent: {
|
||||
const cast = data as Event<PlayerState>
|
||||
if (cast.payload.time === undefined) break
|
||||
if (this._playerStateChanged(cast.payload)) this.emit('player', data.payload)
|
||||
case 'player': {
|
||||
if (data.payload?.time == null) break
|
||||
if (this._playerStateChanged(data.payload)) this.emit('player', data.payload)
|
||||
break
|
||||
}
|
||||
case EventTypes.MessageEvent:{
|
||||
const cast = data as Event<string>
|
||||
this.messages.update(messages => [...messages, ({ message: cast.payload, user: this.peers.value[peer.id].user, type: 'incoming', date: new Date() })])
|
||||
case 'message': {
|
||||
this.messages.update(messages => [...messages, ({ message: data.payload, user: this.peers.value[peer.id]!.user, type: 'incoming', date: new Date() })])
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
|
@ -201,27 +189,16 @@ export class W2GClient extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
#onPeerclose (peer: Peer) {
|
||||
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
|
||||
_sendToPeers (event: Event) {
|
||||
for (const { peer } of Object.values(this.peers.value)) {
|
||||
if (peer) this.#sendEvent(peer, event)
|
||||
if (peer) this._sendEvent(peer, event)
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
debug('destroy')
|
||||
this.#p2pt?.destroy()
|
||||
this.#p2pt.destroy()
|
||||
this.removeAllListeners()
|
||||
this.#p2pt = null
|
||||
this.isHost = false
|
||||
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 Trash from 'lucide-svelte/icons/trash'
|
||||
import X from 'lucide-svelte/icons/x'
|
||||
import { tick } from 'svelte'
|
||||
import MagnifyingGlass from 'svelte-radix/MagnifyingGlass.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
import type { Search } from '$lib/modules/anilist/queries'
|
||||
import type { VariablesOf } from 'gql.tada'
|
||||
|
||||
import { replaceState } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { badgeVariants } from '$lib/components/ui/badge'
|
||||
import { Button } from '$lib/components/ui/button'
|
||||
|
|
@ -93,6 +95,8 @@
|
|||
onList: [] as format[]
|
||||
}
|
||||
|
||||
$: console.log('search updated', search)
|
||||
|
||||
let pageNumber = 1
|
||||
let inputText = ''
|
||||
|
||||
|
|
@ -123,7 +127,7 @@
|
|||
}
|
||||
|
||||
function searchQuery (filter: Partial<typeof search>, page: number) {
|
||||
return client.search({
|
||||
const search = {
|
||||
page,
|
||||
ids: filter.ids,
|
||||
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'>,
|
||||
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>
|
||||
})
|
||||
}
|
||||
|
||||
tick().then(() => replaceState('', { search }))
|
||||
|
||||
return client.search(search)
|
||||
}
|
||||
|
||||
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'),
|
||||
// yeah they dont export this for making custom icons, sucks
|
||||
'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 },
|
||||
|
|
|
|||
Loading…
Reference in a new issue