mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-23 14:16:26 -08:00
this code SUUUUCKs but i dont CARE
This commit is contained in:
parent
55e10f5408
commit
b95a33e39d
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
.data
|
.data
|
||||||
out
|
out
|
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
|
@ -1,23 +1,23 @@
|
||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command":"tsc\nsass src/style:out/style\nrollup -c",
|
"command":"tsc\nsass src/style:out/style\nrollup -c",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"label": "Build (Bot Server)"
|
"label": "Build (Bot Server)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse",
|
"command":"tsc\nsass src/style:out/style\nrollup -c\nnode ./out/server/index.js\ndel ./out/* -Recurse",
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"label": "Build & Test"
|
"label": "Build & Test"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
46
LICENSE
46
LICENSE
|
@ -1,24 +1,24 @@
|
||||||
This is free and unencumbered software released into the public domain.
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
distribute this software, either in source code form or as a compiled
|
distribute this software, either in source code form or as a compiled
|
||||||
binary, for any purpose, commercial or non-commercial, and by any
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
means.
|
means.
|
||||||
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
of this software dedicate any and all copyright interest in the
|
of this software dedicate any and all copyright interest in the
|
||||||
software to the public domain. We make this dedication for the benefit
|
software to the public domain. We make this dedication for the benefit
|
||||||
of the public at large and to the detriment of our heirs and
|
of the public at large and to the detriment of our heirs and
|
||||||
successors. We intend this dedication to be an overt act of
|
successors. We intend this dedication to be an overt act of
|
||||||
relinquishment in perpetuity of all present and future rights to this
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
software under copyright law.
|
software under copyright law.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
For more information, please refer to <http://unlicense.org/>
|
For more information, please refer to <http://unlicense.org/>
|
64
README.md
64
README.md
|
@ -1,33 +1,33 @@
|
||||||
# monofile
|
# monofile
|
||||||
File sharing via Discord
|
File sharing via Discord
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
## .env
|
## .env
|
||||||
|
|
||||||
```
|
```
|
||||||
TOKEN=KILL-YOURSELF.NOW
|
TOKEN=KILL-YOURSELF.NOW
|
||||||
```
|
```
|
||||||
|
|
||||||
## versions & planned updates
|
## versions & planned updates
|
||||||
|
|
||||||
- [X] 1.0.0 initial release
|
- [X] 1.0.0 initial release
|
||||||
- [X] 1.1.0 add file cloning endpoint
|
- [X] 1.1.0 add file cloning endpoint
|
||||||
- [X] 1.1.1 add file cloning webpage
|
- [X] 1.1.1 add file cloning webpage
|
||||||
- [X] 1.1.2 fix file cloning with binary data
|
- [X] 1.1.2 fix file cloning with binary data
|
||||||
- [X] 1.1.3 display current version on pages
|
- [X] 1.1.3 display current version on pages
|
||||||
- [X] 1.1.4 serve /assets as static files & make /server endpoint
|
- [X] 1.1.4 serve /assets as static files & make /server endpoint
|
||||||
- [X] 1.2.0 add file parameters section + custom ids
|
- [X] 1.2.0 add file parameters section + custom ids
|
||||||
- [X] 1.2.1 add file counter to main page
|
- [X] 1.2.1 add file counter to main page
|
||||||
- [X] 1.2.2 clean up this shitty code
|
- [X] 1.2.2 clean up this shitty code
|
||||||
- [X] 1.2.3 bugfixes
|
- [X] 1.2.3 bugfixes
|
||||||
- [ ] 1.3.0 new ui; collections; accounts; utility endpoints; multi file uploads
|
- [ ] 1.3.0 new ui; collections; accounts; utility endpoints; multi file uploads
|
||||||
- [ ] 1.3.1 self-destructing files
|
- [ ] 1.3.1 self-destructing files
|
||||||
- [ ] 1.3.2 disable cloning of local ips
|
- [ ] 1.3.2 disable cloning of local ips
|
||||||
- [ ] 1.4.0 admin panel
|
- [ ] 1.4.0 admin panel
|
||||||
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core
|
||||||
|
|
||||||
also todo: monofile-core (written in eris)
|
also todo: monofile-core (written in eris)
|
||||||
|
|
||||||
## Disclaimer!
|
## Disclaimer!
|
||||||
This project does some stuff that can be considered questionable. Discord may not like you uploading files this way, and it's a grey area in Discord's TOS. We take no responsibility if Discord locks your account for API abuse.
|
This project does some stuff that can be considered questionable. Discord may not like you uploading files this way, and it's a grey area in Discord's TOS. We take no responsibility if Discord locks your account for API abuse.
|
1
assets/icons/README.md
Normal file
1
assets/icons/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Icons are part of Microsoft's Fluent icons
|
1
assets/icons/change_password.svg
Normal file
1
assets/icons/change_password.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.95 8.6a6.554 6.554 0 0 1 6.55-6.55c3.596 0 6.55 2.819 6.55 6.45a6.554 6.554 0 0 1-6.55 6.55c-.531 0-1.055-.076-1.552-.204A1.25 1.25 0 0 1 12.7 16.05h-1.75v1.75c0 .69-.56 1.25-1.25 1.25H7.95v1.25a1.75 1.75 0 0 1-1.75 1.75H3.7a1.75 1.75 0 0 1-1.75-1.75v-2.172c0-.73.29-1.429.806-1.944L8.99 9.948a.275.275 0 0 0 .07-.244A6.386 6.386 0 0 1 8.95 8.6Zm9.3-1.6a1.25 1.25 0 1 0-2.5 0 1.25 1.25 0 0 0 2.5 0Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 529 B |
1
assets/icons/delete_account.svg
Normal file
1
assets/icons/delete_account.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11Zm-5.478 2A6.47 6.47 0 0 0 11 17.5c0 1.644.61 3.145 1.617 4.29-.802.141-1.675.21-2.617.21-2.89 0-5.128-.656-6.691-2a3.75 3.75 0 0 1-1.305-2.843v-.907A2.25 2.25 0 0 1 4.254 14h7.768Zm3.071.966-.07.058-.057.07a.5.5 0 0 0 0 .568l.058.069 1.77 1.77-1.768 1.766-.057.07a.5.5 0 0 0 0 .568l.058.07.069.057a.5.5 0 0 0 .568 0l.07-.058 1.766-1.767 1.77 1.77.069.058a.5.5 0 0 0 .568 0l.07-.058.058-.07a.5.5 0 0 0 0-.568l-.058-.07-1.77-1.768 1.772-1.77.058-.07a.5.5 0 0 0 0-.568l-.058-.069-.069-.058a.5.5 0 0 0-.569 0l-.069.058-1.771 1.77-1.77-1.77-.07-.058a.5.5 0 0 0-.492-.043l-.076.043ZM10 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 791 B |
1
assets/icons/logout.svg
Normal file
1
assets/icons/logout.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M6.25 2.75a1.5 1.5 0 0 0-1.5 1.5v15.5a1.5 1.5 0 0 0 1.5 1.5h5.94a6.5 6.5 0 0 1 7.06-10.012V4.25a1.5 1.5 0 0 0-1.5-1.5H6.25Zm2.25 10.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Zm9 9.75a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11Zm3.5-5.5a.5.5 0 0 1-.5.5h-4.793l1.647 1.646a.5.5 0 0 1-.708.708l-2.5-2.5a.5.5 0 0 1 0-.708l2.5-2.5a.5.5 0 0 1 .708.708L15.707 17H20.5a.5.5 0 0 1 .5.5Z" fill="#DDDDDD"/></svg>
|
After Width: | Height: | Size: 494 B |
17
config.json
17
config.json
|
@ -1,7 +1,12 @@
|
||||||
{
|
{
|
||||||
"maxDiscordFiles": 20,
|
"maxDiscordFiles": 20,
|
||||||
"maxDiscordFileSize": 8388608,
|
"maxDiscordFileSize": 8388608,
|
||||||
"targetGuild": "1024080490677936248",
|
"targetGuild": "1024080490677936248",
|
||||||
"targetChannel": "1024080525993971913",
|
"targetChannel": "1024080525993971913",
|
||||||
"requestTimeout":120000
|
"requestTimeout":120000,
|
||||||
|
|
||||||
|
"accounts": {
|
||||||
|
"registrationEnabled": true,
|
||||||
|
"requiredForUpload": true
|
||||||
|
}
|
||||||
}
|
}
|
5684
package-lock.json
generated
5684
package-lock.json
generated
File diff suppressed because it is too large
Load diff
72
package.json
72
package.json
|
@ -1,35 +1,37 @@
|
||||||
{
|
{
|
||||||
"name": "monofile",
|
"name": "monofile",
|
||||||
"version": "1.3.0-pa",
|
"version": "1.3.0-pa",
|
||||||
"description": "Discord-based file sharing",
|
"description": "Discord-based file sharing",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./out/server/index.js",
|
"start": "node ./out/server/index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "nbitzz",
|
"author": "nbitzz",
|
||||||
"license": "Unlicense",
|
"license": "Unlicense",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=v18"
|
"node": ">=v18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.0",
|
||||||
"discord.js": "^14.7.1",
|
"cookie-parser": "^1.4.6",
|
||||||
"dotenv": "^16.0.2",
|
"discord.js": "^14.7.1",
|
||||||
"express": "^4.18.1",
|
"dotenv": "^16.0.2",
|
||||||
"multer": "^1.4.5-lts.1",
|
"express": "^4.18.1",
|
||||||
"typescript": "^4.8.3"
|
"multer": "^1.4.5-lts.1",
|
||||||
},
|
"typescript": "^4.8.3"
|
||||||
"devDependencies": {
|
},
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"devDependencies": {
|
||||||
"rollup": "^3.11.0",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"rollup-plugin-svelte": "^7.1.0",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"sass": "^1.57.1",
|
"rollup": "^3.11.0",
|
||||||
"svelte": "^3.55.1"
|
"rollup-plugin-svelte": "^7.1.0",
|
||||||
}
|
"sass": "^1.57.1",
|
||||||
}
|
"svelte": "^3.55.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>monofile</title>
|
<title>monofile</title>
|
||||||
|
|
||||||
<meta name="og:site_name" content="monofile $Version">
|
<meta name="og:site_name" content="monofile $Version">
|
||||||
<meta name="title" content="$CollectionName">
|
<meta name="title" content="$CollectionName">
|
||||||
<meta name="description" content="$CollectionId - $Managers manager(s), $Files file(s)">
|
<meta name="description" content="$CollectionId - $Managers manager(s), $Files file(s)">
|
||||||
|
|
||||||
<!-- downloads.css is good eenough for this -->
|
<!-- downloads.css is good eenough for this -->
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="/static/style/downloads.css"
|
href="/static/style/downloads.css"
|
||||||
>
|
>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,40 +1,40 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>monofile</title>
|
<title>monofile</title>
|
||||||
|
|
||||||
$metaTags
|
$metaTags
|
||||||
|
|
||||||
<meta name="og:site_name" content="monofile $Version">
|
<meta name="og:site_name" content="monofile $Version">
|
||||||
<meta name="title" content="$FileName">
|
<meta name="title" content="$FileName">
|
||||||
<meta name="description" content="ID: $FileId">
|
<meta name="description" content="ID: $FileId">
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="/static/style/downloads.css"
|
href="/static/style/downloads.css"
|
||||||
>
|
>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="appContent">
|
<div id="appContent">
|
||||||
<div id="uploadWindow">
|
<div id="uploadWindow">
|
||||||
<h1>
|
<h1>
|
||||||
$FileName
|
$FileName
|
||||||
</h1>
|
</h1>
|
||||||
<p style="color:#999999">
|
<p style="color:#999999">
|
||||||
file id <span class="number">$FileId</span>
|
file id <span class="number">$FileId</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button style="position:relative;width:100%;top:10px;">
|
<button style="position:relative;width:100%;top:10px;">
|
||||||
<a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a>
|
<a id="dlbtn" href="/file/$FileId" download="$FileName" style="position:absolute;left:0px;top:0px;height:100%;width:100%;"></a>
|
||||||
download
|
download
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div style="min-height:20px" />
|
<div style="min-height:20px" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,34 +1,34 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="/static/style/error.css"
|
href="/static/style/error.css"
|
||||||
>
|
>
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
rel="icon"
|
||||||
type="image/png"
|
type="image/png"
|
||||||
href="/static/assets/monofile-circ.png"
|
href="/static/assets/monofile-circ.png"
|
||||||
>
|
>
|
||||||
|
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||||
>
|
>
|
||||||
|
|
||||||
<title>$code</title>
|
<title>$code</title>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<p class="error">
|
<p class="error">
|
||||||
<span class="code">$code</span>
|
<span class="code">$code</span>
|
||||||
$text
|
$text
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,31 +1,32 @@
|
||||||
<html lang="en">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
<head>
|
|
||||||
|
<head>
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
<link
|
||||||
href="/static/style/app.css"
|
rel="stylesheet"
|
||||||
>
|
href="/static/style/app.css"
|
||||||
|
>
|
||||||
<link
|
|
||||||
rel="icon"
|
<link
|
||||||
type="image/png"
|
rel="icon"
|
||||||
href="/static/assets/monofile-circ.png"
|
type="image/png"
|
||||||
>
|
href="/static/assets/monofile-circ.png"
|
||||||
|
>
|
||||||
<meta
|
|
||||||
name="viewport"
|
<meta
|
||||||
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
name="viewport"
|
||||||
>
|
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||||
|
>
|
||||||
<script type="module" src="/static/js/index.js"></script>
|
|
||||||
|
<script type="module" src="/static/js/index.js"></script>
|
||||||
<title>monofile</title>
|
|
||||||
|
<title>monofile</title>
|
||||||
</head>
|
|
||||||
|
</head>
|
||||||
<body>
|
|
||||||
|
<body>
|
||||||
</body>
|
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,29 +1,29 @@
|
||||||
import svelte from 'rollup-plugin-svelte'
|
import svelte from 'rollup-plugin-svelte'
|
||||||
import resolve from "@rollup/plugin-node-resolve"
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: "src/client/index.js",
|
input: "src/client/index.js",
|
||||||
output: {
|
output: {
|
||||||
file: 'out/client/index.js',
|
file: 'out/client/index.js',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
sourcemap:true
|
sourcemap:true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
resolve(),
|
resolve(),
|
||||||
svelte({})
|
svelte({})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "src/client/collection.js",
|
input: "src/client/collection.js",
|
||||||
output: {
|
output: {
|
||||||
file: 'out/client/collection.js',
|
file: 'out/client/collection.js',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
sourcemap:true
|
sourcemap:true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
resolve(),
|
resolve(),
|
||||||
svelte({})
|
svelte({})
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -1,5 +1,5 @@
|
||||||
import App from "../svelte/Collections.svelte"
|
import App from "../svelte/Collections.svelte"
|
||||||
|
|
||||||
new App({
|
new App({
|
||||||
target: document.body
|
target: document.body
|
||||||
})
|
})
|
|
@ -1,5 +1,5 @@
|
||||||
import App from "../svelte/App.svelte"
|
import App from "../svelte/App.svelte"
|
||||||
|
|
||||||
new App({
|
new App({
|
||||||
target: document.body
|
target: document.body
|
||||||
})
|
})
|
|
@ -1,165 +1,191 @@
|
||||||
/*
|
import bodyParser from "body-parser"
|
||||||
i really should split this up into different modules
|
import multer, {memoryStorage} from "multer"
|
||||||
*/
|
import cookieParser from "cookie-parser";
|
||||||
|
import Discord, { IntentsBitField, Client } from "discord.js"
|
||||||
import bodyParser from "body-parser"
|
import express from "express"
|
||||||
import multer, {memoryStorage} from "multer"
|
import fs, { link } from "fs"
|
||||||
import Discord, { IntentsBitField, Client } from "discord.js"
|
import axios, { AxiosResponse } from "axios"
|
||||||
import express from "express"
|
|
||||||
import fs, { link } from "fs"
|
import ServeError from "./lib/errors"
|
||||||
import axios, { AxiosResponse } from "axios"
|
import Files from "./lib/files"
|
||||||
import ServeError from "./lib/errors"
|
import * as auth from "./lib/auth"
|
||||||
|
import * as Accounts from "./lib/accounts"
|
||||||
import Files from "./lib/files"
|
|
||||||
require("dotenv").config()
|
import { authRoutes } from "./routes/authRoutes";
|
||||||
|
require("dotenv").config()
|
||||||
const multerSetup = multer({storage:memoryStorage()})
|
|
||||||
let pkg = require(`${process.cwd()}/package.json`)
|
const multerSetup = multer({storage:memoryStorage()})
|
||||||
let app = express()
|
let pkg = require(`${process.cwd()}/package.json`)
|
||||||
let config = require(`${process.cwd()}/config.json`)
|
let app = express()
|
||||||
|
let config = require(`${process.cwd()}/config.json`)
|
||||||
app.use("/static/assets",express.static("assets"))
|
|
||||||
app.use("/static/style",express.static("out/style"))
|
app.use("/static/assets",express.static("assets"))
|
||||||
app.use("/static/js",express.static("out/client"))
|
app.use("/static/style",express.static("out/style"))
|
||||||
|
app.use("/static/js",express.static("out/client"))
|
||||||
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
|
||||||
// funcs
|
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
|
||||||
|
app.use(cookieParser())
|
||||||
// init data
|
|
||||||
|
app.use("/auth",authRoutes)
|
||||||
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
// funcs
|
||||||
|
|
||||||
|
// init data
|
||||||
|
|
||||||
// discord
|
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
|
||||||
|
|
||||||
let client = new Client({intents:[
|
|
||||||
IntentsBitField.Flags.GuildMessages,
|
|
||||||
IntentsBitField.Flags.MessageContent
|
// discord
|
||||||
],rest:{timeout:config.requestTimeout}})
|
|
||||||
|
let client = new Client({intents:[
|
||||||
let files = new Files(client,config)
|
IntentsBitField.Flags.GuildMessages,
|
||||||
|
IntentsBitField.Flags.MessageContent
|
||||||
// routes (could probably make these use routers)
|
],rest:{timeout:config.requestTimeout}})
|
||||||
|
|
||||||
// index, clone
|
let files = new Files(client,config)
|
||||||
|
|
||||||
app.get("/", function(req,res) {
|
// routes (could probably make these use routers)
|
||||||
res.sendFile(process.cwd()+"/pages/index.html")
|
|
||||||
})
|
// index, clone
|
||||||
|
|
||||||
// upload handlers
|
app.get("/", function(req,res) {
|
||||||
|
res.sendFile(process.cwd()+"/pages/index.html")
|
||||||
app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
})
|
||||||
if (req.file) {
|
|
||||||
try {
|
// upload handlers
|
||||||
let prm = req.header("monofile-params")
|
|
||||||
let params:{[key:string]:any} = {}
|
app.post("/upload",multerSetup.single('file'),async (req,res) => {
|
||||||
if (prm) {
|
if (req.file) {
|
||||||
params = JSON.parse(prm)
|
try {
|
||||||
}
|
let prm = req.header("monofile-params")
|
||||||
|
let params:{[key:string]:any} = {}
|
||||||
files.uploadFile({uploadId:params.uploadId,name:req.file.originalname,mime:req.file.mimetype},req.file.buffer)
|
if (prm) {
|
||||||
.then((uID) => res.send(uID))
|
params = JSON.parse(prm)
|
||||||
.catch((stat) => {
|
}
|
||||||
res.status(stat.status);
|
|
||||||
res.send(`[err] ${stat.message}`)
|
files.uploadFile({
|
||||||
})
|
owner: auth.validate(req.cookies.auth),
|
||||||
} catch {
|
|
||||||
res.status(400)
|
uploadId:params.uploadId,
|
||||||
res.send("[err] bad request")
|
name:req.file.originalname,
|
||||||
}
|
mime:req.file.mimetype
|
||||||
} else {
|
},req.file.buffer)
|
||||||
res.status(400)
|
.then((uID) => res.send(uID))
|
||||||
res.send("[err] bad request")
|
.catch((stat) => {
|
||||||
}
|
res.status(stat.status);
|
||||||
})
|
res.send(`[err] ${stat.message}`)
|
||||||
|
})
|
||||||
app.post("/clone",(req,res) => {
|
} catch {
|
||||||
try {
|
res.status(400)
|
||||||
let j = JSON.parse(req.body)
|
res.send("[err] bad request")
|
||||||
if (!j.url) {
|
}
|
||||||
res.status(400)
|
} else {
|
||||||
res.send("[err] invalid url")
|
res.status(400)
|
||||||
}
|
res.send("[err] bad request")
|
||||||
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
}
|
||||||
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
|
})
|
||||||
.then((uID) => res.send(uID))
|
|
||||||
.catch((stat) => {
|
app.post("/clone",(req,res) => {
|
||||||
res.status(stat.status);
|
try {
|
||||||
res.send(`[err] ${stat.message}`)
|
let j = JSON.parse(req.body)
|
||||||
})
|
if (!j.url) {
|
||||||
}).catch((err) => {
|
res.status(400)
|
||||||
console.log(err)
|
res.send("[err] invalid url")
|
||||||
res.status(400)
|
}
|
||||||
res.send(`[err] failed to fetch data`)
|
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
|
||||||
})
|
|
||||||
} catch {
|
files.uploadFile({
|
||||||
res.status(500)
|
owner: auth.validate(req.cookies.auth),
|
||||||
res.send("[err] an error occured")
|
|
||||||
}
|
name:j.url.split("/")[req.body.split("/").length-1] || "generic",
|
||||||
})
|
mime:data.headers["content-type"],
|
||||||
|
uploadId:j.uploadId
|
||||||
// serve files & download page
|
},Buffer.from(data.data))
|
||||||
|
.then((uID) => res.send(uID))
|
||||||
app.get("/download/:fileId",(req,res) => {
|
.catch((stat) => {
|
||||||
if (files.getFilePointer(req.params.fileId)) {
|
res.status(stat.status);
|
||||||
let file = files.getFilePointer(req.params.fileId)
|
res.send(`[err] ${stat.message}`)
|
||||||
|
})
|
||||||
fs.readFile(process.cwd()+"/pages/download.html",(err,buf) => {
|
|
||||||
if (err) {res.sendStatus(500);console.log(err);return}
|
}).catch((err) => {
|
||||||
res.send(
|
console.log(err)
|
||||||
buf.toString()
|
res.status(400)
|
||||||
.replace(/\$FileId/g,req.params.fileId)
|
res.send(`[err] failed to fetch data`)
|
||||||
.replace(/\$Version/g,pkg.version)
|
})
|
||||||
.replace(/\$FileName/g,
|
} catch {
|
||||||
file.filename
|
res.status(500)
|
||||||
.replace(/\&/g,"&")
|
res.send("[err] an error occured")
|
||||||
.replace(/\</g,"<")
|
}
|
||||||
.replace(/\>/g,">")
|
})
|
||||||
)
|
|
||||||
.replace(/\$metaTags/g,file.mime.startsWith("image/") ? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />` : "")
|
// serve files & download page
|
||||||
)
|
|
||||||
})
|
app.get("/download/:fileId",(req,res) => {
|
||||||
} else {
|
if (files.getFilePointer(req.params.fileId)) {
|
||||||
ServeError(res,404,"file not found")
|
let file = files.getFilePointer(req.params.fileId)
|
||||||
}
|
|
||||||
})
|
fs.readFile(process.cwd()+"/pages/download.html",(err,buf) => {
|
||||||
|
if (err) {res.sendStatus(500);console.log(err);return}
|
||||||
let fgRQH = async (req:express.Request,res:express.Response) => {
|
res.send(
|
||||||
files.readFileStream(req.params.fileId).then(f => {
|
buf.toString()
|
||||||
res.setHeader("Content-Type",f.contentType)
|
.replace(/\$FileId/g,req.params.fileId)
|
||||||
res.status(200)
|
.replace(/\$Version/g,pkg.version)
|
||||||
f.dataStream.pipe(res)
|
.replace(/\$FileName/g,
|
||||||
}).catch((err) => {
|
file.filename
|
||||||
ServeError(res,err.status,err.message)
|
.replace(/\&/g,"&")
|
||||||
})
|
.replace(/\</g,"<")
|
||||||
}
|
.replace(/\>/g,">")
|
||||||
|
)
|
||||||
app.get("/server",(req,res) => {
|
.replace(/\$metaTags/g,
|
||||||
res.send(JSON.stringify({
|
file.mime.startsWith("image/")
|
||||||
...config,
|
? `<meta name="og:image" content="https://${req.headers.host}/file/${req.params.fileId}" />`
|
||||||
version:pkg.version,
|
: (
|
||||||
files:Object.keys(files.files).length
|
file.mime.startsWith("video/")
|
||||||
}))
|
? `<meta name="og:video:url" content="https://${req.headers.host}/file/${req.params.fileId}" />\n<meta name="og:video:type" content="${file.mime.replace(/\"/g,"")}">`
|
||||||
})
|
: ""
|
||||||
|
)
|
||||||
app.get("/file/:fileId",fgRQH)
|
)
|
||||||
app.get("/:fileId",fgRQH)
|
)
|
||||||
|
})
|
||||||
/*
|
} else {
|
||||||
routes should be in this order:
|
ServeError(res,404,"file not found")
|
||||||
|
}
|
||||||
index
|
})
|
||||||
api
|
|
||||||
dl pages
|
let fgRQH = async (req:express.Request,res:express.Response) => {
|
||||||
file serving
|
files.readFileStream(req.params.fileId).then(f => {
|
||||||
*/
|
res.setHeader("Content-Type",f.contentType)
|
||||||
|
res.status(200)
|
||||||
// listen on 3000 or MONOFILE_PORT
|
f.dataStream.pipe(res)
|
||||||
|
}).catch((err) => {
|
||||||
app.listen(process.env.MONOFILE_PORT || 3000,function() {
|
ServeError(res,err.status,err.message)
|
||||||
console.log("Web OK!")
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
app.get("/server",(req,res) => {
|
||||||
|
res.send(JSON.stringify({
|
||||||
|
...config,
|
||||||
|
version:pkg.version,
|
||||||
|
files:Object.keys(files.files).length
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get("/file/:fileId",fgRQH)
|
||||||
|
app.get("/:fileId",fgRQH)
|
||||||
|
|
||||||
|
/*
|
||||||
|
routes should be in this order:
|
||||||
|
|
||||||
|
index
|
||||||
|
api
|
||||||
|
dl pages
|
||||||
|
file serving
|
||||||
|
*/
|
||||||
|
|
||||||
|
// listen on 3000 or MONOFILE_PORT
|
||||||
|
|
||||||
|
app.listen(process.env.MONOFILE_PORT || 3000,function() {
|
||||||
|
console.log("Web OK!")
|
||||||
|
})
|
||||||
|
|
||||||
client.login(process.env.TOKEN)
|
client.login(process.env.TOKEN)
|
|
@ -1,131 +1,100 @@
|
||||||
import crypto from "crypto"
|
import crypto from "crypto"
|
||||||
import * as auth from "./auth";
|
import * as auth from "./auth";
|
||||||
import { readFile, writeFile } from "fs/promises"
|
import { readFile, writeFile } from "fs/promises"
|
||||||
|
|
||||||
// this is probably horrible
|
// this is probably horrible
|
||||||
// but i don't even care anymore
|
// but i don't even care anymore
|
||||||
|
|
||||||
export let Accounts: Account[] = []
|
export let Accounts: Account[] = []
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
id : string
|
id : string
|
||||||
username: string
|
username : string
|
||||||
password: {
|
password : {
|
||||||
hash: string
|
hash : string
|
||||||
salt: string
|
salt : string
|
||||||
}
|
}
|
||||||
accounts: string[]
|
files : string[]
|
||||||
admin : boolean
|
collections : string[]
|
||||||
}
|
admin : boolean
|
||||||
|
}
|
||||||
export function create(username:string,pwd:string,admin:boolean=false) {
|
|
||||||
let accId = crypto.randomBytes(12).toString("hex")
|
export function create(username:string,pwd:string,admin:boolean=false) {
|
||||||
|
let accId = crypto.randomBytes(12).toString("hex")
|
||||||
Accounts.push(
|
|
||||||
{
|
Accounts.push(
|
||||||
id: accId,
|
{
|
||||||
username: username,
|
id: accId,
|
||||||
password: password.hash(pwd),
|
username: username,
|
||||||
accounts: [],
|
password: password.hash(pwd),
|
||||||
admin: admin
|
files: [],
|
||||||
}
|
collections: [],
|
||||||
)
|
admin: admin
|
||||||
|
}
|
||||||
save()
|
)
|
||||||
|
|
||||||
return accId
|
save()
|
||||||
}
|
|
||||||
|
return accId
|
||||||
export function getFromUsername(username:string) {
|
}
|
||||||
return Accounts.find(e => e.username == username)
|
|
||||||
}
|
export function getFromUsername(username:string) {
|
||||||
|
return Accounts.find(e => e.username == username)
|
||||||
export function getFromId(id:string) {
|
}
|
||||||
return Accounts.find(e => e.id == id)
|
|
||||||
}
|
export function getFromId(id:string) {
|
||||||
|
return Accounts.find(e => e.id == id)
|
||||||
export function getFromToken(token:string) {
|
}
|
||||||
let accId = auth.validate(token)
|
|
||||||
if (!accId) return
|
export function getFromToken(token:string) {
|
||||||
return getFromId(accId)
|
let accId = auth.validate(token)
|
||||||
}
|
if (!accId) return
|
||||||
|
return getFromId(accId)
|
||||||
export function deleteAccount(id:string) {
|
}
|
||||||
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
|
|
||||||
save()
|
export function deleteAccount(id:string) {
|
||||||
}
|
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
|
||||||
|
save()
|
||||||
export namespace password {
|
}
|
||||||
export function hash(password:string,_salt?:string) {
|
|
||||||
let salt = _salt || crypto.randomBytes(12).toString('base64')
|
export namespace password {
|
||||||
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
|
export function hash(password:string,_salt?:string) {
|
||||||
|
let salt = _salt || crypto.randomBytes(12).toString('base64')
|
||||||
return {
|
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
|
||||||
salt:salt,
|
|
||||||
hash:hash
|
return {
|
||||||
}
|
salt:salt,
|
||||||
}
|
hash:hash
|
||||||
|
}
|
||||||
export function set(id:string,password:string) {
|
}
|
||||||
let acc = Accounts.find(e => e.id == id)
|
|
||||||
if (!acc) return
|
export function set(id:string,password:string) {
|
||||||
|
let acc = Accounts.find(e => e.id == id)
|
||||||
acc.password = hash(password)
|
if (!acc) return
|
||||||
save()
|
|
||||||
}
|
acc.password = hash(password)
|
||||||
|
save()
|
||||||
export function check(id:string,password:string) {
|
}
|
||||||
let acc = Accounts.find(e => e.id == id)
|
|
||||||
if (!acc) return
|
export function check(id:string,password:string) {
|
||||||
|
let acc = Accounts.find(e => e.id == id)
|
||||||
return acc.password.hash == hash(password,acc.password.salt).hash
|
if (!acc) return
|
||||||
}
|
|
||||||
}
|
return acc.password.hash == hash(password,acc.password.salt).hash
|
||||||
|
}
|
||||||
export namespace rbxaccounts {
|
}
|
||||||
export function add(id:string,name:string) {
|
|
||||||
let acc = getFromId(id)
|
export function save() {
|
||||||
if (!acc) return
|
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
|
||||||
|
.catch((err) => console.error(err))
|
||||||
/* check for account that already has name */
|
}
|
||||||
let idx = acc.accounts.findIndex(e=>e==name)
|
|
||||||
if (idx > -1) return
|
readFile(`${process.cwd()}/.data/accounts.json`)
|
||||||
|
.then((buf) => {
|
||||||
acc.accounts = [...acc.accounts,name]
|
Accounts = JSON.parse(buf.toString())
|
||||||
save()
|
}).catch(err => console.error(err))
|
||||||
return
|
.finally(() => {
|
||||||
}
|
if (!Accounts.find(e => e.admin)) {
|
||||||
|
create("admin","admin",true)
|
||||||
export function remove(id:string,name:string) {
|
}
|
||||||
let acc = getFromId(id)
|
|
||||||
if (!acc) return
|
|
||||||
let idx = acc.accounts.findIndex(e=>e==name)
|
|
||||||
if (idx < 0) return
|
|
||||||
acc.accounts.splice(idx,1)
|
|
||||||
save()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clear(id:string) {
|
|
||||||
let acc = getFromId(id)
|
|
||||||
if (!acc) return
|
|
||||||
acc.accounts = []
|
|
||||||
save()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function save() {
|
|
||||||
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
|
|
||||||
.catch((err) => console.error(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
readFile(`${process.cwd()}/.data/accounts.json`)
|
|
||||||
.then((buf) => {
|
|
||||||
Accounts = JSON.parse(buf.toString())
|
|
||||||
}).catch(err => console.error(err))
|
|
||||||
.finally(() => {
|
|
||||||
if (!Accounts.find(e => e.admin)) {
|
|
||||||
create("admin","admin",true)
|
|
||||||
}
|
|
||||||
})
|
})
|
|
@ -1,58 +1,58 @@
|
||||||
import crypto from "crypto"
|
import crypto from "crypto"
|
||||||
import { readFile, writeFile } from "fs/promises"
|
import { readFile, writeFile } from "fs/promises"
|
||||||
export let AuthTokens: AuthToken[] = []
|
export let AuthTokens: AuthToken[] = []
|
||||||
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
|
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
|
||||||
|
|
||||||
export interface AuthToken {
|
export interface AuthToken {
|
||||||
account: string,
|
account: string,
|
||||||
token: string,
|
token: string,
|
||||||
expire: number
|
expire: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function create(id:string,expire:number=(24*60*60*1000)) {
|
export function create(id:string,expire:number=(24*60*60*1000)) {
|
||||||
let token = {
|
let token = {
|
||||||
account:id,
|
account:id,
|
||||||
token:crypto.randomBytes(12).toString('hex'),
|
token:crypto.randomBytes(12).toString('hex'),
|
||||||
expire:Date.now()+expire
|
expire:Date.now()+expire
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthTokens.push(token)
|
AuthTokens.push(token)
|
||||||
tokenTimer(token)
|
tokenTimer(token)
|
||||||
|
|
||||||
save()
|
save()
|
||||||
|
|
||||||
return token.token
|
return token.token
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validate(token:string) {
|
export function validate(token:string) {
|
||||||
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
|
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenTimer(token:AuthToken) {
|
export function tokenTimer(token:AuthToken) {
|
||||||
if (Date.now() >= token.expire) {
|
if (Date.now() >= token.expire) {
|
||||||
invalidate(token.token)
|
invalidate(token.token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
|
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invalidate(token:string) {
|
export function invalidate(token:string) {
|
||||||
if (AuthTokenTO[token]) {
|
if (AuthTokenTO[token]) {
|
||||||
clearTimeout(AuthTokenTO[token])
|
clearTimeout(AuthTokenTO[token])
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
|
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function save() {
|
export function save() {
|
||||||
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
|
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
|
||||||
.catch((err) => console.error(err))
|
.catch((err) => console.error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
readFile(`${process.cwd()}/.data/tokens.json`)
|
readFile(`${process.cwd()}/.data/tokens.json`)
|
||||||
.then((buf) => {
|
.then((buf) => {
|
||||||
AuthTokens = JSON.parse(buf.toString())
|
AuthTokens = JSON.parse(buf.toString())
|
||||||
AuthTokens.forEach(e => tokenTimer(e))
|
AuthTokens.forEach(e => tokenTimer(e))
|
||||||
}).catch(err => console.error(err))
|
}).catch(err => console.error(err))
|
|
@ -1,35 +1,36 @@
|
||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
import { readFile } from "fs/promises"
|
import { readFile } from "fs/promises"
|
||||||
|
|
||||||
let errorPage:string
|
let errorPage:string
|
||||||
|
|
||||||
export default async function ServeError(
|
export default async function ServeError(
|
||||||
res:Response,
|
res:Response,
|
||||||
code:number,
|
code:number,
|
||||||
reason:string
|
reason:string
|
||||||
) {
|
) {
|
||||||
// fetch error page if not cached
|
// fetch error page if not cached
|
||||||
if (!errorPage) {
|
if (!errorPage) {
|
||||||
errorPage =
|
errorPage =
|
||||||
(
|
(
|
||||||
await readFile(`${process.cwd()}/pages/error.html`)
|
await readFile(`${process.cwd()}/pages/error.html`)
|
||||||
.catch((err) => console.error(err))
|
.catch((err) => console.error(err))
|
||||||
|| "<pre>$code $text</pre>"
|
|| "<pre>$code $text</pre>"
|
||||||
)
|
)
|
||||||
.toString()
|
.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve error
|
// serve error
|
||||||
res.status(code)
|
res.statusMessage = reason
|
||||||
res.send(
|
res.status(code)
|
||||||
errorPage
|
res.send(
|
||||||
.replace(/\$code/g,code.toString())
|
errorPage
|
||||||
.replace(/\$text/g,reason)
|
.replace(/\$code/g,code.toString())
|
||||||
)
|
.replace(/\$text/g,reason)
|
||||||
}
|
)
|
||||||
|
}
|
||||||
export function Redirect(res:Response,url:string) {
|
|
||||||
res.status(302)
|
export function Redirect(res:Response,url:string) {
|
||||||
res.header("Location",url)
|
res.status(302)
|
||||||
res.send()
|
res.header("Location",url)
|
||||||
|
res.send()
|
||||||
}
|
}
|
|
@ -1,256 +1,270 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Discord, { Client, TextBasedChannel } from "discord.js";
|
import Discord, { Client, TextBasedChannel } from "discord.js";
|
||||||
import { readFile, writeFile } from "fs";
|
import { readFile, writeFile } from "fs";
|
||||||
import { Readable } from "node:stream"
|
import { Readable } from "node:stream"
|
||||||
|
|
||||||
export let id_check_regex = /[A-Za-z0-9_\-\.]+/
|
export let id_check_regex = /[A-Za-z0-9_\-\.]+/
|
||||||
export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
export let alphanum = Array.from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
||||||
|
|
||||||
// bad solution but whatever
|
// bad solution but whatever
|
||||||
|
|
||||||
export function generateFileId() {
|
export function generateFileId() {
|
||||||
let fid = ""
|
let fid = ""
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
fid += alphanum[Math.floor(Math.random()*alphanum.length)]
|
fid += alphanum[Math.floor(Math.random()*alphanum.length)]
|
||||||
}
|
}
|
||||||
return fid
|
return fid
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileUploadSettings {
|
export interface FileUploadSettings {
|
||||||
name?: string,
|
name?: string,
|
||||||
mime: string,
|
mime: string,
|
||||||
uploadId?: string
|
uploadId?: string,
|
||||||
}
|
owner?:string
|
||||||
|
}
|
||||||
export interface Configuration {
|
|
||||||
maxDiscordFiles: number,
|
export interface Configuration {
|
||||||
maxDiscordFileSize: number,
|
maxDiscordFiles: number,
|
||||||
targetGuild: string,
|
maxDiscordFileSize: number,
|
||||||
targetChannel: string,
|
targetGuild: string,
|
||||||
requestTimeout: number
|
targetChannel: string,
|
||||||
}
|
requestTimeout: number,
|
||||||
|
|
||||||
export interface FilePointer {
|
accounts: {
|
||||||
filename:string,
|
registrationEnabled: boolean,
|
||||||
mime:string,
|
requiredForUpload: boolean
|
||||||
messageids:string[]
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StatusCodeError {
|
export interface FilePointer {
|
||||||
status: number,
|
filename:string,
|
||||||
message: string
|
mime:string,
|
||||||
}
|
messageids:string[],
|
||||||
|
owner?:string
|
||||||
/* */
|
}
|
||||||
|
|
||||||
export default class Files {
|
export interface StatusCodeError {
|
||||||
|
status: number,
|
||||||
config: Configuration
|
message: string
|
||||||
client: Client
|
}
|
||||||
files: {[key:string]:FilePointer} = {}
|
|
||||||
uploadChannel?: TextBasedChannel
|
/* */
|
||||||
|
|
||||||
constructor(client: Client, config: Configuration) {
|
export default class Files {
|
||||||
|
|
||||||
this.config = config;
|
config: Configuration
|
||||||
this.client = client;
|
client: Client
|
||||||
|
files: {[key:string]:FilePointer} = {}
|
||||||
client.on("ready",() => {
|
uploadChannel?: TextBasedChannel
|
||||||
console.log("Discord OK!")
|
|
||||||
|
constructor(client: Client, config: Configuration) {
|
||||||
client.guilds.fetch(config.targetGuild).then((g) => {
|
|
||||||
g.channels.fetch(config.targetChannel).then((a) => {
|
this.config = config;
|
||||||
if (a?.isTextBased()) {
|
this.client = client;
|
||||||
this.uploadChannel = a
|
|
||||||
}
|
client.on("ready",() => {
|
||||||
})
|
console.log("Discord OK!")
|
||||||
})
|
|
||||||
})
|
client.guilds.fetch(config.targetGuild).then((g) => {
|
||||||
|
g.channels.fetch(config.targetChannel).then((a) => {
|
||||||
readFile(process.cwd()+"/.data/files.json",(err,buf) => {
|
if (a?.isTextBased()) {
|
||||||
if (err) {console.log(err);return}
|
this.uploadChannel = a
|
||||||
this.files = JSON.parse(buf.toString() || "{}")
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise<string|StatusCodeError> {
|
readFile(process.cwd()+"/.data/files.json",(err,buf) => {
|
||||||
return new Promise<string>(async (resolve,reject) => {
|
if (err) {console.log(err);return}
|
||||||
if (!this.uploadChannel) {
|
this.files = JSON.parse(buf.toString() || "{}")
|
||||||
reject({status:503,message:"server is not ready - please try again later"})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.name || !settings.mime) {
|
uploadFile(settings:FileUploadSettings,fBuffer:Buffer):Promise<string|StatusCodeError> {
|
||||||
reject({status:400,message:"missing name/mime"});
|
return new Promise<string>(async (resolve,reject) => {
|
||||||
return
|
if (!this.uploadChannel) {
|
||||||
}
|
reject({status:503,message:"server is not ready - please try again later"})
|
||||||
|
return
|
||||||
let uploadId = (settings.uploadId || generateFileId()).toString();
|
}
|
||||||
|
|
||||||
if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) {
|
if (!settings.name || !settings.mime) {
|
||||||
reject({status:400,message:"invalid id"});return
|
reject({status:400,message:"missing name/mime"});
|
||||||
}
|
return
|
||||||
|
}
|
||||||
if (this.files[uploadId]) {
|
|
||||||
reject({status:400,message:"a file with this id already exists"});
|
if (!settings.owner && this.config.accounts.requiredForUpload) {
|
||||||
return
|
reject({status:401,message:"an account is required for upload"});
|
||||||
}
|
return
|
||||||
|
}
|
||||||
if (settings.name.length > 128) {
|
|
||||||
reject({status:400,message:"name too long"});
|
let uploadId = (settings.uploadId || generateFileId()).toString();
|
||||||
return
|
|
||||||
}
|
if ((uploadId.match(id_check_regex) || [])[0] != uploadId || uploadId.length > 30) {
|
||||||
|
reject({status:400,message:"invalid id"});return
|
||||||
if (settings.mime.length > 128) {
|
}
|
||||||
reject({status:400,message:"mime too long"});
|
|
||||||
return
|
if (this.files[uploadId]) {
|
||||||
}
|
reject({status:400,message:"a file with this id already exists"});
|
||||||
|
return
|
||||||
// get buffer
|
}
|
||||||
if (fBuffer.byteLength >= (this.config.maxDiscordFileSize*this.config.maxDiscordFiles)) {
|
|
||||||
reject({status:400,message:"file too large"});
|
if (settings.name.length > 128) {
|
||||||
return
|
reject({status:400,message:"name too long"});
|
||||||
}
|
return
|
||||||
|
}
|
||||||
// generate buffers to upload
|
|
||||||
let toUpload = []
|
if (settings.mime.length > 128) {
|
||||||
for (let i = 0; i < Math.ceil(fBuffer.byteLength/this.config.maxDiscordFileSize); i++) {
|
reject({status:400,message:"mime too long"});
|
||||||
toUpload.push(
|
return
|
||||||
fBuffer.subarray(
|
}
|
||||||
i*this.config.maxDiscordFileSize,
|
|
||||||
Math.min(
|
// get buffer
|
||||||
fBuffer.byteLength,
|
if (fBuffer.byteLength >= (this.config.maxDiscordFileSize*this.config.maxDiscordFiles)) {
|
||||||
(i+1)*this.config.maxDiscordFileSize
|
reject({status:400,message:"file too large"});
|
||||||
)
|
return
|
||||||
)
|
}
|
||||||
)
|
|
||||||
}
|
// generate buffers to upload
|
||||||
|
let toUpload = []
|
||||||
// begin uploading
|
for (let i = 0; i < Math.ceil(fBuffer.byteLength/this.config.maxDiscordFileSize); i++) {
|
||||||
let uploadTmplt:Discord.AttachmentBuilder[] = toUpload.map((e) => {
|
toUpload.push(
|
||||||
return new Discord.AttachmentBuilder(e)
|
fBuffer.subarray(
|
||||||
.setName(Math.random().toString().slice(2))
|
i*this.config.maxDiscordFileSize,
|
||||||
})
|
Math.min(
|
||||||
let uploadGroups = []
|
fBuffer.byteLength,
|
||||||
for (let i = 0; i < Math.ceil(uploadTmplt.length/10); i++) {
|
(i+1)*this.config.maxDiscordFileSize
|
||||||
uploadGroups.push(uploadTmplt.slice(i*10,((i+1)*10)))
|
)
|
||||||
}
|
)
|
||||||
|
)
|
||||||
let msgIds = []
|
}
|
||||||
|
|
||||||
for (let i = 0; i < uploadGroups.length; i++) {
|
// begin uploading
|
||||||
|
let uploadTmplt:Discord.AttachmentBuilder[] = toUpload.map((e) => {
|
||||||
let ms = await this.uploadChannel.send({
|
return new Discord.AttachmentBuilder(e)
|
||||||
files:uploadGroups[i]
|
.setName(Math.random().toString().slice(2))
|
||||||
}).catch((e) => {console.error(e)})
|
})
|
||||||
|
let uploadGroups = []
|
||||||
if (ms) {
|
for (let i = 0; i < Math.ceil(uploadTmplt.length/10); i++) {
|
||||||
msgIds.push(ms.id)
|
uploadGroups.push(uploadTmplt.slice(i*10,((i+1)*10)))
|
||||||
} else {
|
}
|
||||||
reject({status:500,message:"please try again"}); return
|
|
||||||
}
|
let msgIds = []
|
||||||
}
|
|
||||||
|
for (let i = 0; i < uploadGroups.length; i++) {
|
||||||
// save
|
|
||||||
|
let ms = await this.uploadChannel.send({
|
||||||
resolve(await this.writeFile(
|
files:uploadGroups[i]
|
||||||
uploadId,
|
}).catch((e) => {console.error(e)})
|
||||||
{
|
|
||||||
filename:settings.name,
|
if (ms) {
|
||||||
messageids:msgIds,
|
msgIds.push(ms.id)
|
||||||
mime:settings.mime
|
} else {
|
||||||
}
|
reject({status:500,message:"please try again"}); return
|
||||||
))
|
}
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
// save
|
||||||
// fs
|
|
||||||
|
resolve(await this.writeFile(
|
||||||
writeFile(uploadId: string, file: FilePointer):Promise<string> {
|
uploadId,
|
||||||
return new Promise((resolve, reject) => {
|
{
|
||||||
|
filename:settings.name,
|
||||||
this.files[uploadId] = file
|
messageids:msgIds,
|
||||||
|
mime:settings.mime,
|
||||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
|
||||||
|
owner:settings.owner
|
||||||
if (err) {
|
}
|
||||||
reject({status:500,message:"please try again"});
|
))
|
||||||
delete this.files[uploadId];
|
})
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
// fs
|
||||||
resolve(uploadId)
|
|
||||||
|
writeFile(uploadId: string, file: FilePointer):Promise<string> {
|
||||||
})
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
})
|
this.files[uploadId] = file
|
||||||
}
|
|
||||||
|
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||||
// todo: move read code here
|
|
||||||
|
if (err) {
|
||||||
readFileStream(uploadId: string):Promise<{dataStream:Readable,contentType:string}> {
|
reject({status:500,message:"please try again"});
|
||||||
return new Promise(async (resolve,reject) => {
|
delete this.files[uploadId];
|
||||||
if (!this.uploadChannel) {
|
return
|
||||||
reject({status:503,message:"server is not ready - please try again later"})
|
}
|
||||||
return
|
|
||||||
}
|
resolve(uploadId)
|
||||||
|
|
||||||
if (this.files[uploadId]) {
|
})
|
||||||
let file = this.files[uploadId]
|
|
||||||
|
})
|
||||||
let dataStream = new Readable({
|
}
|
||||||
read(){}
|
|
||||||
})
|
// todo: move read code here
|
||||||
|
|
||||||
resolve({
|
readFileStream(uploadId: string):Promise<{dataStream:Readable,contentType:string}> {
|
||||||
contentType: file.mime,
|
return new Promise(async (resolve,reject) => {
|
||||||
dataStream: dataStream
|
if (!this.uploadChannel) {
|
||||||
})
|
reject({status:503,message:"server is not ready - please try again later"})
|
||||||
|
return
|
||||||
for (let i = 0; i < file.messageids.length; i++) {
|
}
|
||||||
let msg = await this.uploadChannel.messages.fetch(file.messageids[i]).catch(() => {return null})
|
|
||||||
if (msg?.attachments) {
|
if (this.files[uploadId]) {
|
||||||
let attach = Array.from(msg.attachments.values())
|
let file = this.files[uploadId]
|
||||||
for (let i = 0; i < attach.length; i++) {
|
|
||||||
let d = await axios.get(attach[i].url,{responseType:"arraybuffer"}).catch((e:Error) => {console.error(e)})
|
let dataStream = new Readable({
|
||||||
if (d) {
|
read(){}
|
||||||
dataStream.push(d.data)
|
})
|
||||||
} else {
|
|
||||||
reject({status:500,message:"internal server error"})
|
resolve({
|
||||||
dataStream.destroy(new Error("file read error"))
|
contentType: file.mime,
|
||||||
return
|
dataStream: dataStream
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
for (let i = 0; i < file.messageids.length; i++) {
|
||||||
}
|
let msg = await this.uploadChannel.messages.fetch(file.messageids[i]).catch(() => {return null})
|
||||||
|
if (msg?.attachments) {
|
||||||
dataStream.push(null)
|
let attach = Array.from(msg.attachments.values())
|
||||||
|
for (let i = 0; i < attach.length; i++) {
|
||||||
} else {
|
let d = await axios.get(attach[i].url,{responseType:"arraybuffer"}).catch((e:Error) => {console.error(e)})
|
||||||
reject({status:404,message:"not found"})
|
if (d) {
|
||||||
}
|
dataStream.push(d.data)
|
||||||
})
|
} else {
|
||||||
}
|
reject({status:500,message:"internal server error"})
|
||||||
|
dataStream.destroy(new Error("file read error"))
|
||||||
unlink(uploadId:string):Promise<void> {
|
return
|
||||||
return new Promise((resolve,reject) => {
|
}
|
||||||
let tmp = this.files[uploadId];
|
}
|
||||||
delete this.files[uploadId];
|
}
|
||||||
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
}
|
||||||
if (err) {
|
|
||||||
this.files[uploadId] = tmp
|
dataStream.push(null)
|
||||||
reject()
|
|
||||||
} else {
|
} else {
|
||||||
resolve()
|
reject({status:404,message:"not found"})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
|
||||||
}
|
unlink(uploadId:string):Promise<void> {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
getFilePointer(uploadId:string):FilePointer {
|
let tmp = this.files[uploadId];
|
||||||
return this.files[uploadId]
|
delete this.files[uploadId];
|
||||||
}
|
writeFile(process.cwd()+"/.data/files.json",JSON.stringify(this.files),(err) => {
|
||||||
|
if (err) {
|
||||||
}
|
this.files[uploadId] = tmp
|
||||||
|
reject()
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilePointer(uploadId:string):FilePointer {
|
||||||
|
return this.files[uploadId]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
137
src/server/routes/authRoutes.ts
Normal file
137
src/server/routes/authRoutes.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import bodyParser from "body-parser";
|
||||||
|
import { Router } from "express";
|
||||||
|
import * as Accounts from "../lib/accounts";
|
||||||
|
import * as auth from "../lib/auth";
|
||||||
|
|
||||||
|
import ServeError from "../lib/errors";
|
||||||
|
|
||||||
|
let parser = bodyParser.json({
|
||||||
|
type: ["text/plain","application/json"]
|
||||||
|
})
|
||||||
|
|
||||||
|
export let authRoutes = Router();
|
||||||
|
|
||||||
|
let config = require(`${process.cwd()}/config.json`)
|
||||||
|
|
||||||
|
|
||||||
|
authRoutes.post("/login", parser, (req,res) => {
|
||||||
|
let body:{[key:string]:any}
|
||||||
|
try {
|
||||||
|
body = JSON.parse(req.body)
|
||||||
|
} catch {
|
||||||
|
ServeError(res,400,"bad request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof body.username != "string" || typeof body.password != "string") {
|
||||||
|
ServeError(res,400,"please provide a username or password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.validate(req.cookies.auth)) return
|
||||||
|
|
||||||
|
/*
|
||||||
|
check if account exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
let acc = Accounts.getFromUsername(body.username)
|
||||||
|
|
||||||
|
if (!acc) {
|
||||||
|
ServeError(res,401,"username or password incorrect")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Accounts.password.check(acc.id,body.password)) {
|
||||||
|
ServeError(res,401,"username or password incorrect")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
assign token
|
||||||
|
*/
|
||||||
|
|
||||||
|
res.cookie("auth",auth.create(acc.id,(3*24*60*60*1000)))
|
||||||
|
res.status(200)
|
||||||
|
res.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
authRoutes.post("/create", parser, (req,res) => {
|
||||||
|
if (!config.accounts.registrationEnabled) {
|
||||||
|
ServeError(res,403,"account registration disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let body:{[key:string]:any}
|
||||||
|
try {
|
||||||
|
body = JSON.parse(req.body)
|
||||||
|
} catch {
|
||||||
|
ServeError(res,400,"bad request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth.validate(req.cookies.auth)) return
|
||||||
|
|
||||||
|
if (typeof body.username != "string" || typeof body.password != "string") {
|
||||||
|
ServeError(res,400,"please provide a username or password")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
check if account exists
|
||||||
|
*/
|
||||||
|
|
||||||
|
let acc = Accounts.getFromUsername(body.username)
|
||||||
|
|
||||||
|
if (acc) {
|
||||||
|
ServeError(res,400,"account with this username already exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.username.length < 3 || body.username.length > 20) {
|
||||||
|
ServeError(res,400,"username must be over or equal to 3 characters or under or equal to 20 characters in length")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((body.username.match(/[A-Za-z0-9_\-\.]+/) || [])[0] != body.username) {
|
||||||
|
ServeError(res,400,"username contains invalid characters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.password.length < 8) {
|
||||||
|
ServeError(res,400,"password must be 8 characters or longer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let newAcc = Accounts.create(body.username,body.password)
|
||||||
|
|
||||||
|
/*
|
||||||
|
assign token
|
||||||
|
*/
|
||||||
|
|
||||||
|
res.cookie("auth",auth.create(newAcc,(3*24*60*60*1000)))
|
||||||
|
res.status(200)
|
||||||
|
res.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
authRoutes.post("/logout", (req,res) => {
|
||||||
|
if (!auth.validate(req.cookies.auth)) {
|
||||||
|
ServeError(res, 401, "not logged in")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.invalidate(req.cookies.auth)
|
||||||
|
res.send("logged out")
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
authRoutes.get("/me", (req,res) => {
|
||||||
|
if (!auth.validate(req.cookies.auth)) {
|
||||||
|
ServeError(res, 401, "not logged in")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy rn so
|
||||||
|
|
||||||
|
let acc = Accounts.getFromToken(req.cookies.auth)
|
||||||
|
res.send(acc)
|
||||||
|
})
|
|
@ -1,83 +1,83 @@
|
||||||
/*
|
/*
|
||||||
could probably replace this with fonts served directly
|
could probably replace this with fonts served directly
|
||||||
from the server but it's fine for now
|
from the server but it's fine for now
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
|
||||||
|
|
||||||
$FallbackFonts:
|
$FallbackFonts:
|
||||||
-apple-system,
|
-apple-system,
|
||||||
system-ui,
|
system-ui,
|
||||||
BlinkMacSystemFont,
|
BlinkMacSystemFont,
|
||||||
"Segoe UI",
|
"Segoe UI",
|
||||||
Roboto,
|
Roboto,
|
||||||
sans-serif;
|
sans-serif;
|
||||||
|
|
||||||
%normal {
|
%normal {
|
||||||
font-family: "Source Sans Pro", $FallbackFonts
|
font-family: "Source Sans Pro", $FallbackFonts
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
everything that's not a span
|
everything that's not a span
|
||||||
and/or has the normal class
|
and/or has the normal class
|
||||||
(it's just in case)
|
(it's just in case)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
*:not(span), .normal { @extend %normal; }
|
*:not(span), .normal { @extend %normal; }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
for code blocks / terminal
|
for code blocks / terminal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.monospace {
|
.monospace {
|
||||||
font-family: "Fira Code", monospace
|
font-family: "Fira Code", monospace
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
colors
|
colors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$Background: #252525;
|
$Background: #252525;
|
||||||
/* hsl(210,12.9,24.3) */
|
/* hsl(210,12.9,24.3) */
|
||||||
$darkish: rgb(54, 62, 70);
|
$darkish: rgb(54, 62, 70);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
then other stuff
|
then other stuff
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: rgb(30, 33, 36); // this is here so that
|
background-color: rgb(30, 33, 36); // this is here so that
|
||||||
// pulling down to refresh
|
// pulling down to refresh
|
||||||
// on mobile looks good
|
// on mobile looks good
|
||||||
}
|
}
|
||||||
|
|
||||||
#appContent {
|
#appContent {
|
||||||
background-color: $Background
|
background-color: $Background
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
scrollbars
|
scrollbars
|
||||||
*/
|
*/
|
||||||
|
|
||||||
* {
|
* {
|
||||||
/* nice scrollbars aren't needed on mobile so */
|
/* nice scrollbars aren't needed on mobile so */
|
||||||
@media screen and (min-width:500px) {
|
@media screen and (min-width:500px) {
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width:5px;
|
width:5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background-color:#191919;
|
background-color:#191919;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color:#333;
|
background-color:#333;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color:#373737;
|
background-color:#373737;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,37 +1,37 @@
|
||||||
@use "base";
|
@use "base";
|
||||||
@use "app/topbar";
|
@use "app/topbar";
|
||||||
@use "app/pulldown";
|
@use "app/pulldown";
|
||||||
@use "app/uploads";
|
@use "app/uploads";
|
||||||
|
|
||||||
.menuBtn {
|
.menuBtn {
|
||||||
text-decoration:none;
|
text-decoration:none;
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
transition-duration: 100ms;
|
transition-duration: 100ms;
|
||||||
|
|
||||||
color:#555555;
|
color:#555555;
|
||||||
background-color: #00000000;
|
background-color: #00000000;
|
||||||
border:none;
|
border:none;
|
||||||
margin:0 0 0 0;
|
margin:0 0 0 0;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
position:relative;
|
position:relative;
|
||||||
top:-1px;
|
top:-1px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color:slategray;
|
color:slategray;
|
||||||
transition-duration: 100ms;
|
transition-duration: 100ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#appContent {
|
#appContent {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:40px;
|
top:40px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height: calc( 100% - 40px );
|
height: calc( 100% - 40px );
|
||||||
background-image: linear-gradient(#333,base.$Background);
|
background-image: linear-gradient(#333,base.$Background);
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
background-image: linear-gradient(#303030,base.$Background);
|
background-image: linear-gradient(#303030,base.$Background);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,49 +1,49 @@
|
||||||
@use "../base";
|
@use "../base";
|
||||||
@use "pulldown/help";
|
@use "pulldown/help";
|
||||||
@use "pulldown/accounts";
|
@use "pulldown/accounts";
|
||||||
@use "pulldown/files";
|
@use "pulldown/files";
|
||||||
|
|
||||||
#overlay {
|
#overlay {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width:100%;
|
width:100%;
|
||||||
top:0px;
|
top:0px;
|
||||||
opacity:0.25;
|
opacity:0.25;
|
||||||
border:none;
|
border:none;
|
||||||
outline:none;
|
outline:none;
|
||||||
background-color:#AAAAAA;
|
background-color:#AAAAAA;
|
||||||
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulldown {
|
.pulldown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background-color: #191919;
|
background-color: #191919;
|
||||||
color: #dddddd;
|
color: #dddddd;
|
||||||
|
|
||||||
top:0px;
|
top:0px;
|
||||||
left:50%;
|
left:50%;
|
||||||
transform:translateX(-50%);
|
transform:translateX(-50%);
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, h1, h2 {
|
p, h1, h2 {
|
||||||
margin:0px;
|
margin:0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pulldown_display {
|
.pulldown_display {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:0px;
|
top:0px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
}
|
}
|
|
@ -1,120 +1,188 @@
|
||||||
.pulldown_display[name=accounts] {
|
.pulldown_display[name=accounts] {
|
||||||
.notLoggedIn {
|
.notLoggedIn {
|
||||||
.container_div {
|
.container_div {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
top:50%;
|
top:50%;
|
||||||
transform:translateY(-50%);
|
transform:translateY(-50%);
|
||||||
width:100%;
|
width:100%;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
font-size:24px;
|
font-size:24px;
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
font-size:30px;
|
font-size:30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.flavor {
|
.flavor {
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
|
|
||||||
/* good enoough */
|
/* good enoough */
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
color:#999999;
|
color:#999999;
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
background-color:#393939;
|
background-color:#393939;
|
||||||
color:#DDDDDD;
|
color:#DDDDDD;
|
||||||
border:none;
|
border:none;
|
||||||
outline:none;
|
outline:none;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
/*overflow:clip;*/
|
/*overflow:clip;*/
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
background-color:#434343;
|
background-color:#434343;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
flex-basis:50%;
|
flex-basis:50%;
|
||||||
flex-grow:1;
|
flex-grow:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text],input[type=password] {
|
input[type=text],input[type=password] {
|
||||||
border:none;
|
border:none;
|
||||||
border-radius:0;
|
border-radius:0;
|
||||||
width:100%;
|
width:100%;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
color:#dddddd;
|
color:#dddddd;
|
||||||
outline:none;
|
outline:none;
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.lgBtnContainer {
|
.pwError {
|
||||||
display:flex;
|
div {
|
||||||
position:relative;
|
border:none;
|
||||||
left:20px;
|
border-radius:0;
|
||||||
width:calc( 100% - 40px );
|
width:100%;
|
||||||
gap:10px;
|
padding:5px;
|
||||||
overflow:clip;
|
background-color:#663333;
|
||||||
}
|
color:#dddddd;
|
||||||
|
outline:none;
|
||||||
.fields {
|
font-size:14px;
|
||||||
display:flex;
|
text-align:left;
|
||||||
flex-direction:column;
|
|
||||||
position:relative;
|
@media screen and (max-width: 500px) {
|
||||||
left:20px;
|
font-size:16px;
|
||||||
width:calc( 100% - 40px );
|
padding:10px;
|
||||||
gap:5px;
|
}
|
||||||
overflow:clip;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
.lgBtnContainer {
|
||||||
a {
|
display:flex;
|
||||||
text-decoration: none;
|
position:relative;
|
||||||
color:#999999;
|
left:20px;
|
||||||
font-size:14px;
|
width:calc( 100% - 40px );
|
||||||
|
gap:10px;
|
||||||
@media screen and (max-width:500px) {
|
overflow:clip;
|
||||||
font-size:16px;
|
}
|
||||||
}
|
|
||||||
|
.fields {
|
||||||
&::after {
|
display:flex;
|
||||||
content:" ➜";
|
flex-direction:column;
|
||||||
font-size:0px;
|
position:relative;
|
||||||
opacity: 0;
|
left:20px;
|
||||||
transition-duration:250ms;
|
width:calc( 100% - 40px );
|
||||||
}
|
gap:5px;
|
||||||
|
overflow:clip;
|
||||||
&:hover {
|
}
|
||||||
&::after {
|
|
||||||
font-size:13px;
|
/*
|
||||||
opacity: 1;
|
a {
|
||||||
transition-duration:250ms;
|
text-decoration: none;
|
||||||
}
|
color:#999999;
|
||||||
}
|
font-size:14px;
|
||||||
}
|
|
||||||
*/
|
@media screen and (max-width:500px) {
|
||||||
}
|
font-size:16px;
|
||||||
|
}
|
||||||
}
|
|
||||||
|
&::after {
|
||||||
|
content:" ➜";
|
||||||
|
font-size:0px;
|
||||||
|
opacity: 0;
|
||||||
|
transition-duration:250ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::after {
|
||||||
|
font-size:13px;
|
||||||
|
opacity: 1;
|
||||||
|
transition-duration:250ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.loggedIn {
|
||||||
|
position:absolute;
|
||||||
|
left:10px;
|
||||||
|
top:10px;
|
||||||
|
width:calc( 100% - 20px );
|
||||||
|
height:calc( 100% - 20px );
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight:600;
|
||||||
|
font-size:20px;
|
||||||
|
color: #AAAAAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accountOptions {
|
||||||
|
button {
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
cursor:pointer;
|
||||||
|
height:50px;
|
||||||
|
background-color: #191919;
|
||||||
|
border:none;
|
||||||
|
border-bottom:1px solid #AAAAAA;
|
||||||
|
transition-duration:150ms;
|
||||||
|
|
||||||
|
img {
|
||||||
|
position:absolute;
|
||||||
|
left:13px;
|
||||||
|
top:50%;
|
||||||
|
transform:translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
position:absolute;
|
||||||
|
top:50%;
|
||||||
|
left:50px;
|
||||||
|
color:#DDDDDD;
|
||||||
|
transform:translateY(-50%);
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transition-duration:150ms;
|
||||||
|
background-color: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,34 +1,34 @@
|
||||||
.pulldown_display[name=files] {
|
.pulldown_display[name=files] {
|
||||||
.notLoggedIn {
|
.notLoggedIn {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
top:100%;
|
top:100%;
|
||||||
transform:translateY(-100%);
|
transform:translateY(-100%);
|
||||||
width:100%;
|
width:100%;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
background-color:#202020;
|
background-color:#202020;
|
||||||
|
|
||||||
.flavor {
|
.flavor {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
color:#999999;
|
color:#999999;
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
--col: #999999;
|
--col: #999999;
|
||||||
|
|
||||||
background-color: #232323;
|
background-color: #232323;
|
||||||
color:var(--col);
|
color:var(--col);
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
border:1px solid var(--col);
|
border:1px solid var(--col);
|
||||||
padding:2px 20px 2px 20px;
|
padding:2px 20px 2px 20px;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
--col:#BBBBBB;
|
--col:#BBBBBB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
.pulldown_display[name=help] {
|
.pulldown_display[name=help] {
|
||||||
|
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
|
|
||||||
.faqGroup {
|
.faqGroup {
|
||||||
padding:6px 10px 4px 10px;
|
padding:6px 10px 4px 10px;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color:#DDDDDD;
|
color:#DDDDDD;
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
margin:0 0 0 0;
|
margin:0 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color:#999999;
|
color:#999999;
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
margin:0 0 0 0;
|
margin:0 0 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
@use "../base";
|
@use "../base";
|
||||||
|
|
||||||
#topbar {
|
#topbar {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:0px;
|
top:0px;
|
||||||
|
|
||||||
width:100%;
|
width:100%;
|
||||||
height:40px;
|
height:40px;
|
||||||
|
|
||||||
/* hsl(210,9.1,12.9) */
|
/* hsl(210,9.1,12.9) */
|
||||||
background-color: rgb(30, 33, 36);
|
background-color: rgb(30, 33, 36);
|
||||||
|
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
column-gap:5px;
|
column-gap:5px;
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,115 +1,115 @@
|
||||||
#uploadWindow {
|
#uploadWindow {
|
||||||
#add_new_files {
|
#add_new_files {
|
||||||
background-color:#191919;
|
background-color:#191919;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
padding: 0px 0px 10px 0px;
|
padding: 0px 0px 10px 0px;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: "Fira Code", monospace;
|
font-family: "Fira Code", monospace;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0px 0px 0px 10px;
|
margin: 0px 0px 0px 10px;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
position:relative;
|
position:relative;
|
||||||
|
|
||||||
&._add_files_txt {
|
&._add_files_txt {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
top:-4px;
|
top:-4px;
|
||||||
left:10px;
|
left:10px;
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
font-size:20px;
|
font-size:20px;
|
||||||
top:-6px;
|
top:-6px;
|
||||||
left:10px;
|
left:10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
|
||||||
span._add_files_txt {
|
span._add_files_txt {
|
||||||
font-size:20px;
|
font-size:20px;
|
||||||
top:-6px;
|
top:-6px;
|
||||||
left:10px;
|
left:10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#file_add_btns {
|
#file_add_btns {
|
||||||
width:calc( 100% - 20px );
|
width:calc( 100% - 20px );
|
||||||
margin:auto;
|
margin:auto;
|
||||||
position:relative;
|
position:relative;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction:row;
|
flex-direction:row;
|
||||||
column-gap:10px;
|
column-gap:10px;
|
||||||
|
|
||||||
button, input[type=text] {
|
button, input[type=text] {
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
color:#DDDDDD;
|
color:#DDDDDD;
|
||||||
border:none;
|
border:none;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
outline:none;
|
outline:none;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
|
|
||||||
flex-basis:50%;
|
flex-basis:50%;
|
||||||
flex-grow:1;
|
flex-grow:1;
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@media screen and (min-width: 500px) {
|
@media screen and (min-width: 500px) {
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
flex-basis: 60%;
|
flex-basis: 60%;
|
||||||
}
|
}
|
||||||
background-color:#393939;
|
background-color:#393939;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fileUpload {
|
.fileUpload {
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100px;
|
height:100px;
|
||||||
position:relative;
|
position:relative;
|
||||||
|
|
||||||
background-color:#262626;
|
background-color:#262626;
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
|
|
||||||
input[type=file] {
|
input[type=file] {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:0px;
|
top:0px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
top:50%;
|
top:50%;
|
||||||
transform:translateY(-50%);
|
transform:translateY(-50%);
|
||||||
font-size:12px;
|
font-size:12px;
|
||||||
width:100%;
|
width:100%;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
padding:0px;
|
padding:0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
background-color:#292929;
|
background-color:#292929;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,59 +1,59 @@
|
||||||
// should probably start using mixins for thingss like this
|
// should probably start using mixins for thingss like this
|
||||||
|
|
||||||
#uploadWindow {
|
#uploadWindow {
|
||||||
.file {
|
.file {
|
||||||
background-color:#191919;
|
background-color:#191919;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow:clip;
|
overflow:clip;
|
||||||
position:relative;
|
position:relative;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
width:calc( 100% - 20px );
|
width:calc( 100% - 20px );
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text] {
|
input[type=text] {
|
||||||
background-color:#333333;
|
background-color:#333333;
|
||||||
color:#DDDDDD;
|
color:#DDDDDD;
|
||||||
border:none;
|
border:none;
|
||||||
outline:none;
|
outline:none;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
position:relative;
|
position:relative;
|
||||||
|
|
||||||
width:100%;
|
width:100%;
|
||||||
transition-duration:250ms;
|
transition-duration:250ms;
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonContainer {
|
.buttonContainer {
|
||||||
display:flex;
|
display:flex;
|
||||||
column-gap:10px;
|
column-gap:10px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
flex-basis: 50%;
|
flex-basis: 50%;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadingContainer {
|
.uploadingContainer {
|
||||||
color: #AAAAAA;
|
color: #AAAAAA;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hitbox {
|
.hitbox {
|
||||||
opacity:0;
|
opacity:0;
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:0px;
|
top:0px;
|
||||||
height:100%;
|
height:100%;
|
||||||
width:100%;
|
width:100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,76 +1,76 @@
|
||||||
@use "uploader/add_new_files";
|
@use "uploader/add_new_files";
|
||||||
@use "uploader/file";
|
@use "uploader/file";
|
||||||
|
|
||||||
#uploadWindow {
|
#uploadWindow {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:50%;
|
left:50%;
|
||||||
top:50%;
|
top:50%;
|
||||||
transform:translate(-50%,-50%);
|
transform:translate(-50%,-50%);
|
||||||
padding:10px 15px 10px 15px;
|
padding:10px 15px 10px 15px;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
width:350px;
|
width:350px;
|
||||||
@media screen and (min-width:500px) {
|
@media screen and (min-width:500px) {
|
||||||
max-height: calc( 100% - 80px );
|
max-height: calc( 100% - 80px );
|
||||||
}
|
}
|
||||||
|
|
||||||
background-color:#222222;
|
background-color:#222222;
|
||||||
color:#ddd;
|
color:#ddd;
|
||||||
|
|
||||||
h1, p, a {
|
h1, p, a {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color:#999;
|
color:#999;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Inconsolata", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uploadContainer {
|
.uploadContainer {
|
||||||
overflow:auto;
|
overflow:auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor:pointer;
|
cursor:pointer;
|
||||||
background-color:#393939;
|
background-color:#393939;
|
||||||
color:#DDDDDD;
|
color:#DDDDDD;
|
||||||
border:none;
|
border:none;
|
||||||
outline:none;
|
outline:none;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
/*overflow:clip;*/
|
/*overflow:clip;*/
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
padding:10px;
|
padding:10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transition-duration: 250ms;
|
transition-duration: 250ms;
|
||||||
background-color:#434343;
|
background-color:#434343;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
width: calc( 100% - 20px );
|
width: calc( 100% - 20px );
|
||||||
height: calc( 100% - 20px );
|
height: calc( 100% - 20px );
|
||||||
border-radius:0px;
|
border-radius:0px;
|
||||||
background-color:#00000000;
|
background-color:#00000000;
|
||||||
|
|
||||||
transform:none;
|
transform:none;
|
||||||
left:10px;
|
left:10px;
|
||||||
top:10px;
|
top:10px;
|
||||||
padding:0px;
|
padding:0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
// probably dont need to import the entire
|
// probably dont need to import the entire
|
||||||
// uploads css file
|
// uploads css file
|
||||||
// so i might just make a separate file with mixins
|
// so i might just make a separate file with mixins
|
||||||
// and import them
|
// and import them
|
||||||
|
|
||||||
@use "app/uploads";
|
@use "app/uploads";
|
||||||
@use "base";
|
@use "base";
|
||||||
|
|
||||||
#appContent {
|
#appContent {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
left:0px;
|
left:0px;
|
||||||
top:0px;
|
top:0px;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:100%;
|
height:100%;
|
||||||
background-image: linear-gradient(#333,base.$Background);
|
background-image: linear-gradient(#333,base.$Background);
|
||||||
|
|
||||||
@media screen and (max-width:500px) {
|
@media screen and (max-width:500px) {
|
||||||
background-image: linear-gradient(#303030,base.$Background);
|
background-image: linear-gradient(#303030,base.$Background);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
@use "_base";
|
@use "_base";
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
font-size:20px;
|
font-size:20px;
|
||||||
color: lightslategray;
|
color: lightslategray;
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%,-50%);
|
transform: translate(-50%,-50%);
|
||||||
|
|
||||||
text-align:center;
|
text-align:center;
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
font-size:25px;
|
font-size:25px;
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Inconsolata", monospace;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import Topbar from "./elem/Topbar.svelte";
|
import Topbar from "./elem/Topbar.svelte";
|
||||||
import PulldownManager from "./elem/PulldownManager.svelte";
|
import PulldownManager from "./elem/PulldownManager.svelte";
|
||||||
import UploadWindow from "./elem/UploadWindow.svelte";
|
import UploadWindow from "./elem/UploadWindow.svelte";
|
||||||
import { pulldownManager } from "./elem/stores.mjs";
|
import { pulldownManager } from "./elem/stores.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type Topbar
|
* @type Topbar
|
||||||
*/
|
*/
|
||||||
let topbar;
|
let topbar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type PulldownManager
|
* @type PulldownManager
|
||||||
*/
|
*/
|
||||||
let pulldown;
|
let pulldown;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
pulldownManager.set(pulldown)
|
pulldownManager.set(pulldown)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Topbar bind:this={topbar} pulldown={pulldown} />
|
<Topbar bind:this={topbar} pulldown={pulldown} />
|
||||||
<div id="appContent">
|
<div id="appContent">
|
||||||
<PulldownManager bind:this={pulldown} />
|
<PulldownManager bind:this={pulldown} />
|
||||||
|
|
||||||
<UploadWindow/>
|
<UploadWindow/>
|
||||||
</div>
|
</div>
|
|
@ -1,12 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let collection = {}
|
let collection = {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="appContent">
|
<div id="appContent">
|
||||||
<div id="uploadWindow">
|
<div id="uploadWindow">
|
||||||
<h2>{collection.name}</h2>
|
<h2>{collection.name}</h2>
|
||||||
<p><span class="number">{collection.id}</span>  — by <strong>@{collection.owner}</strong></p>
|
<p><span class="number">{collection.id}</span>  — by <strong>@{collection.owner}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,49 +1,49 @@
|
||||||
<script context="module">
|
<script context="module">
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
// can't find a better way to do this
|
// can't find a better way to do this
|
||||||
import Files from "./pulldowns/Files.svelte";
|
import Files from "./pulldowns/Files.svelte";
|
||||||
import Accounts from "./pulldowns/Accounts.svelte";
|
import Accounts from "./pulldowns/Accounts.svelte";
|
||||||
import Help from "./pulldowns/Help.svelte";
|
import Help from "./pulldowns/Help.svelte";
|
||||||
|
|
||||||
export let allPulldowns = new Map()
|
export let allPulldowns = new Map()
|
||||||
|
|
||||||
allPulldowns
|
allPulldowns
|
||||||
.set("account",Accounts)
|
.set("account",Accounts)
|
||||||
.set("help",Help)
|
.set("help",Help)
|
||||||
.set("files",Files)
|
.set("files",Files)
|
||||||
|
|
||||||
export const pulldownOpen = writable(false);
|
export const pulldownOpen = writable(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { fade, scale } from "svelte/transition";
|
import { fade, scale } from "svelte/transition";
|
||||||
|
|
||||||
export function isOpen() {
|
export function isOpen() {
|
||||||
return $pulldownOpen
|
return $pulldownOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openPulldown(name) {
|
export function openPulldown(name) {
|
||||||
pulldownOpen.set(name)
|
pulldownOpen.set(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closePulldown() {
|
export function closePulldown() {
|
||||||
pulldownOpen.set(false)
|
pulldownOpen.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{#if $pulldownOpen}
|
{#if $pulldownOpen}
|
||||||
<div class="pulldown" transition:fade={{duration:200}}>
|
<div class="pulldown" transition:fade={{duration:200}}>
|
||||||
<svelte:component this={allPulldowns.get($pulldownOpen)} />
|
<svelte:component this={allPulldowns.get($pulldownOpen)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="overlay"
|
id="overlay"
|
||||||
on:click={closePulldown}
|
on:click={closePulldown}
|
||||||
transition:fade={{duration:200}}
|
transition:fade={{duration:200}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
|
@ -1,30 +1,31 @@
|
||||||
<script>
|
<script>
|
||||||
import { circOut } from "svelte/easing";
|
import { circOut } from "svelte/easing";
|
||||||
import { scale } from "svelte/transition";
|
import { scale } from "svelte/transition";
|
||||||
import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte";
|
import PulldownManager, {pulldownOpen} from "./PulldownManager.svelte";
|
||||||
import { _void } from "./transition/_void";
|
import { account } from "./stores.mjs";
|
||||||
|
import { _void } from "./transition/_void";
|
||||||
/**
|
|
||||||
* @type PulldownManager
|
/**
|
||||||
*/
|
* @type PulldownManager
|
||||||
export let pulldown;
|
*/
|
||||||
</script>
|
export let pulldown;
|
||||||
|
</script>
|
||||||
<div id="topbar">
|
|
||||||
{#if $pulldownOpen}
|
<div id="topbar">
|
||||||
<button
|
{#if $pulldownOpen}
|
||||||
class="menuBtn"
|
<button
|
||||||
on:click={pulldown.closePulldown}
|
class="menuBtn"
|
||||||
transition:_void={{duration:200,prop:"width",easingFunc:circOut}}
|
on:click={pulldown.closePulldown}
|
||||||
>close</button>
|
transition:_void={{duration:200,prop:"width",easingFunc:circOut}}
|
||||||
{/if}
|
>close</button>
|
||||||
|
{/if}
|
||||||
<!-- too lazy to make this better -->
|
|
||||||
|
<!-- too lazy to make this better -->
|
||||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button>
|
|
||||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>account</button>
|
<button class="menuBtn" on:click={() => pulldown.openPulldown("files")}>files</button>
|
||||||
<button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
|
<button class="menuBtn" on:click={() => pulldown.openPulldown("account")}>{$account.username ? `@${$account.username}` : "account"}</button>
|
||||||
|
<button class="menuBtn" on:click={() => pulldown.openPulldown("help")}>help</button>
|
||||||
<div /> <!-- not sure what's offcenter but something is
|
|
||||||
so this div is here to ""fix"" that -->
|
<div /> <!-- not sure what's offcenter but something is
|
||||||
|
so this div is here to ""fix"" that -->
|
||||||
</div>
|
</div>
|
|
@ -1,217 +1,222 @@
|
||||||
<script>
|
<script>
|
||||||
import { _void } from "./transition/_void.js";
|
import { _void } from "./transition/_void.js";
|
||||||
import { padding_scaleY } from "./transition/padding_scaleY.js"
|
import { padding_scaleY } from "./transition/padding_scaleY.js"
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
import { circIn, circOut } from "svelte/easing";
|
import { circIn, circOut } from "svelte/easing";
|
||||||
|
import { serverStats, refresh_stats, account } from "./stores.mjs";
|
||||||
import AttachmentZone from "./uploader/AttachmentZone.svelte";
|
|
||||||
|
import AttachmentZone from "./uploader/AttachmentZone.svelte";
|
||||||
// stats
|
|
||||||
|
// stats
|
||||||
let ServerStats = {}
|
|
||||||
|
refresh_stats()
|
||||||
let refresh_stats = () => {
|
|
||||||
fetch("/server").then(async (data) => {
|
// uploads
|
||||||
ServerStats = await data.json()
|
|
||||||
})
|
let attachmentZone;
|
||||||
}
|
let uploads = {};
|
||||||
|
let uploadInProgress = false;
|
||||||
refresh_stats()
|
|
||||||
|
let handle_file_upload = (ev) => {
|
||||||
// uploads
|
if (ev.detail.type == "clone") {
|
||||||
|
uploads[Math.random().toString().slice(2)] = {
|
||||||
let attachmentZone;
|
type: "clone",
|
||||||
let uploads = {};
|
name: ev.detail.url,
|
||||||
let uploadInProgress = false;
|
url: ev.detail.url,
|
||||||
|
|
||||||
let handle_file_upload = (ev) => {
|
params: {
|
||||||
if (ev.detail.type == "clone") {
|
uploadId: ""
|
||||||
uploads[Math.random().toString().slice(2)] = {
|
},
|
||||||
type: "clone",
|
|
||||||
name: ev.detail.url,
|
uploadStatus:{
|
||||||
url: ev.detail.url,
|
fileId: null,
|
||||||
|
error: null,
|
||||||
params: {
|
}
|
||||||
uploadId: ""
|
}
|
||||||
},
|
|
||||||
|
uploads = uploads
|
||||||
uploadStatus:{
|
} else if (ev.detail.type == "upload") {
|
||||||
fileId: null,
|
ev.detail.files.forEach((v,x) => {
|
||||||
error: null,
|
uploads[Math.random().toString().slice(2)] = {
|
||||||
}
|
type: "upload",
|
||||||
}
|
name: v.name,
|
||||||
|
file: v,
|
||||||
uploads = uploads
|
|
||||||
} else if (ev.detail.type == "upload") {
|
params: {
|
||||||
ev.detail.files.forEach((v,x) => {
|
uploadId: ""
|
||||||
uploads[Math.random().toString().slice(2)] = {
|
},
|
||||||
type: "upload",
|
|
||||||
name: v.name,
|
uploadStatus:{
|
||||||
file: v,
|
fileId: null,
|
||||||
|
error: null,
|
||||||
params: {
|
}
|
||||||
uploadId: ""
|
}
|
||||||
},
|
})
|
||||||
|
|
||||||
uploadStatus:{
|
uploads = uploads
|
||||||
fileId: null,
|
}
|
||||||
error: null,
|
}
|
||||||
}
|
|
||||||
}
|
let handle_fetch_promise = (x,prom) => {
|
||||||
})
|
return prom.then(async (res) => {
|
||||||
|
let txt = await res.text()
|
||||||
uploads = uploads
|
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
|
||||||
}
|
else {
|
||||||
}
|
uploads[x].uploadStatus.fileId = txt;
|
||||||
|
|
||||||
let handle_fetch_promise = (x,prom) => {
|
refresh_stats();
|
||||||
return prom.then(async (res) => {
|
}
|
||||||
let txt = await res.text()
|
}).catch((err) => {
|
||||||
if (txt.startsWith("[err]")) uploads[x].uploadStatus.error = txt;
|
uploads[x].uploadStatus.error = err.toString();
|
||||||
else {
|
})
|
||||||
uploads[x].uploadStatus.fileId = txt;
|
}
|
||||||
|
|
||||||
refresh_stats();
|
let upload_files = () => {
|
||||||
}
|
uploadInProgress = true
|
||||||
}).catch((err) => {
|
|
||||||
uploads[x].uploadStatus.error = err.toString();
|
// go through all files
|
||||||
})
|
Object.entries(uploads).forEach(([x,v]) => {
|
||||||
}
|
switch(v.type) {
|
||||||
|
case "upload":
|
||||||
let upload_files = () => {
|
let fd = new FormData()
|
||||||
uploadInProgress = true
|
fd.append("file",v.file)
|
||||||
|
|
||||||
// go through all files
|
handle_fetch_promise(x,fetch("/upload",{
|
||||||
Object.entries(uploads).forEach(([x,v]) => {
|
headers: {
|
||||||
switch(v.type) {
|
"monofile-params": JSON.stringify(v.params)
|
||||||
case "upload":
|
},
|
||||||
let fd = new FormData()
|
method: "POST",
|
||||||
fd.append("file",v.file)
|
body: fd
|
||||||
|
}))
|
||||||
handle_fetch_promise(x,fetch("/upload",{
|
break
|
||||||
headers: {
|
case "clone":
|
||||||
"monofile-params": JSON.stringify(v.params)
|
handle_fetch_promise(x,fetch("/clone",{
|
||||||
},
|
method: "POST",
|
||||||
method: "POST",
|
body: JSON.stringify({
|
||||||
body: fd
|
url: v.url,
|
||||||
}))
|
...v.params
|
||||||
break
|
})
|
||||||
case "clone":
|
}))
|
||||||
handle_fetch_promise(x,fetch("/clone",{
|
break
|
||||||
method: "POST",
|
}
|
||||||
body: JSON.stringify({
|
})
|
||||||
url: v.url,
|
}
|
||||||
...v.params
|
|
||||||
})
|
// animation
|
||||||
}))
|
|
||||||
break
|
function fileTransition(node) {
|
||||||
}
|
return {
|
||||||
})
|
duration: 300,
|
||||||
}
|
css: t => {
|
||||||
|
let eased = circOut(t)
|
||||||
// animation
|
|
||||||
|
return `
|
||||||
function fileTransition(node) {
|
height: ${eased*(node.offsetHeight-22)}px;
|
||||||
return {
|
padding: ${eased*10}px 10px;
|
||||||
duration: 300,
|
`
|
||||||
css: t => {
|
}
|
||||||
let eased = circOut(t)
|
}
|
||||||
|
}
|
||||||
return `
|
|
||||||
height: ${eased*(node.offsetHeight-20)}px;
|
</script>
|
||||||
padding: ${eased*10}px 10px;
|
|
||||||
`
|
<div id="uploadWindow">
|
||||||
}
|
<h1>monofile</h1>
|
||||||
}
|
<p style:color="#999999">
|
||||||
}
|
<span class="number">{$serverStats.version ? `v${$serverStats.version}` : "•••"}</span>  — Discord based file sharing
|
||||||
|
</p>
|
||||||
</script>
|
|
||||||
|
<div style:min-height="10px" />
|
||||||
<div id="uploadWindow">
|
|
||||||
<h1>monofile</h1>
|
<!-- consider splitting the file thing into a separate element maybe -->
|
||||||
<p style:color="#999999">
|
|
||||||
<span class="number">{ServerStats.version ? `v${ServerStats.version}` : "•••"}</span>  — Discord based file sharing
|
<div class="uploadContainer">
|
||||||
</p>
|
{#each Object.entries(uploads) as upload (upload[0])}
|
||||||
|
<!-- container to allow for animate directive -->
|
||||||
<div style:min-height="10px" />
|
<div>
|
||||||
|
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
|
||||||
<!-- consider splitting the file thing into a separate element maybe -->
|
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? ` (${Math.round(upload[1].file.size/1048576)}MB)` : ""}</span></h2>
|
||||||
|
|
||||||
<div class="uploadContainer">
|
{#if upload[1].maximized && !uploadInProgress}
|
||||||
{#each Object.entries(uploads) as upload (upload[0])}
|
<div transition:padding_scaleY|local>
|
||||||
<!-- container to allow for animate directive -->
|
<div style:height="10px" />
|
||||||
<div>
|
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
|
||||||
<div class="file" transition:fileTransition style:border={upload[1].uploadStatus.error ? "1px solid #BB7070" : ""}>
|
<div style:height="10px" />
|
||||||
<h2>{upload[1].name} <span style:color="#999999" style:font-weight="400">{upload[1].type}{@html upload[1].type == "upload" ? ` (${Math.round(upload[1].file.size/1048576)}MB)` : ""}</span></h2>
|
<div class="buttonContainer">
|
||||||
|
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
|
||||||
{#if upload[1].maximized && !uploadInProgress}
|
delete
|
||||||
<div transition:padding_scaleY|local>
|
</button>
|
||||||
<div style:height="10px" />
|
<button on:click={() => uploads[upload[0]].maximized = false}>
|
||||||
<input placeholder="custom id" type="text" bind:value={ uploads[upload[0]].params.uploadId }>
|
minimize
|
||||||
<div style:height="10px" />
|
</button>
|
||||||
<div class="buttonContainer">
|
</div>
|
||||||
<button on:click={() => {delete uploads[upload[0]];uploads=uploads;}}>
|
</div>
|
||||||
delete
|
{:else if !uploadInProgress}
|
||||||
</button>
|
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
|
||||||
<button on:click={() => uploads[upload[0]].maximized = false}>
|
{:else}
|
||||||
minimize
|
<div transition:padding_scaleY|local class="uploadingContainer">
|
||||||
</button>
|
{#if !upload[1].uploadStatus.fileId}
|
||||||
</div>
|
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
|
||||||
</div>
|
{/if}
|
||||||
{:else if !uploadInProgress}
|
|
||||||
<button on:click={() => uploads[upload[0]].maximized = true} class="hitbox"></button>
|
{#if upload[1].uploadStatus.fileId}
|
||||||
{:else}
|
<div style:height="10px" transition:padding_scaleY />
|
||||||
<div transition:padding_scaleY|local class="uploadingContainer">
|
{#if !upload[1].viewingUrl}
|
||||||
{#if !upload[1].uploadStatus.fileId}
|
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||||
<p in:fade={{duration:300, delay:400, easingFunc:circOut}} out:padding_scaleY={{easingFunc:circIn,op:true}}>{upload[1].uploadStatus.error ?? "Uploading..."}</p>
|
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
|
||||||
{/if}
|
view url
|
||||||
|
</button>
|
||||||
{#if upload[1].uploadStatus.fileId}
|
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
|
||||||
<div style:height="10px" transition:padding_scaleY />
|
copy url
|
||||||
{#if !upload[1].viewingUrl}
|
</button>
|
||||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
</div>
|
||||||
<button on:click={() => uploads[upload[0]].viewingUrl = true}>
|
{:else}
|
||||||
view url
|
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
||||||
</button>
|
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
|
||||||
<button on:click={() => navigator.clipboard.writeText(`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`)}>
|
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
|
||||||
copy url
|
ok
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{/if}
|
||||||
<div class="buttonContainer" out:_void in:_void={{easingFunc:circOut}}>
|
{/if}
|
||||||
<input type="text" readonly value={`https://${window.location.host}/download/${upload[1].uploadStatus.fileId}`} style:flex-basis="80%">
|
</div>
|
||||||
<button on:click={() => uploads[upload[0]].viewingUrl = false} style:flex-basis="20%">
|
{/if}
|
||||||
ok
|
</div>
|
||||||
</button>
|
<div style:height="10px" transition:padding_scaleY />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/each}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
{/if}
|
{#if uploadInProgress == false}
|
||||||
</div>
|
|
||||||
<div style:height="10px" transition:padding_scaleY />
|
<!-- if required for upload, check if logged in -->
|
||||||
</div>
|
{#if ($serverStats.accounts||{}).requiredForUpload ? !!$account.username : true}
|
||||||
{/each}
|
|
||||||
</div>
|
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
|
||||||
|
<div style:min-height="10px" transition:_void={{rTarg:"height",prop:"min-height"}} />
|
||||||
{#if uploadInProgress == false}
|
{#if Object.keys(uploads).length > 0}
|
||||||
<AttachmentZone bind:this={attachmentZone} on:addFiles={handle_file_upload}/>
|
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
|
||||||
<div style:min-height="10px" transition:padding_scaleY />
|
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||||
{#if Object.keys(uploads).length > 0}
|
{/if}
|
||||||
<button in:padding_scaleY={{easingFunc:circOut}} out:_void on:click={upload_files}>upload</button>
|
|
||||||
<div transition:_void style:min-height="10px" />
|
{:else}
|
||||||
{/if}
|
|
||||||
{/if}
|
<p transition:_void style:color="#999999" style:text-align="center">Please log in to upload files.</p>
|
||||||
|
<div transition:_void={{rTarg:"height",prop:"min-height"}} style:min-height="10px" />
|
||||||
<p style:color="#999999" style:text-align="center">
|
|
||||||
Hosting <span class="number" style:font-weight="600">{ServerStats.files || "•••"}</span> files
|
{/if}
|
||||||
—
|
|
||||||
Maximum filesize is <span class="number" style:font-weight="600">{((ServerStats.maxDiscordFileSize || 0)*(ServerStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span>
|
{/if}
|
||||||
<br />
|
|
||||||
</p>
|
<p style:color="#999999" style:text-align="center">
|
||||||
|
Hosting <span class="number" style:font-weight="600">{$serverStats.files || "•••"}</span> files
|
||||||
<p style:color="#999999" style:text-align="center" style:font-size="12px">
|
—
|
||||||
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://github.com/nbitzz" style:font-size="12px">@nbitzz</a> — <a href="https://github.com/nbitzz/monofile" style:font-size="12px">source</a>
|
Maximum filesize is <span class="number" style:font-weight="600">{(($serverStats.maxDiscordFileSize || 0)*($serverStats.maxDiscordFiles || 0))/1048576 || "•••"}MB</span>
|
||||||
</p>
|
<br />
|
||||||
<div style:height="10px" />
|
</p>
|
||||||
|
|
||||||
|
<p style:color="#999999" style:text-align="center" style:font-size="12px">
|
||||||
|
Made with {Math.floor(Math.random()*10)==0 ? "🐟" : "❤"} by <a href="https://github.com/nbitzz" style:font-size="12px">@nbitzz</a> — <a href="https://github.com/nbitzz/monofile" style:font-size="12px">source</a>
|
||||||
|
</p>
|
||||||
|
<div style:height="10px" />
|
||||||
</div>
|
</div>
|
|
@ -1,33 +1,133 @@
|
||||||
<script>
|
<script>
|
||||||
import Pulldown from "./Pulldown.svelte"
|
import Pulldown from "./Pulldown.svelte"
|
||||||
import { padding_scaleY } from "../transition/padding_scaleY"
|
import { padding_scaleY } from "../transition/padding_scaleY"
|
||||||
import { circIn,circOut } from "svelte/easing"
|
import { circIn,circOut } from "svelte/easing"
|
||||||
|
import { account, fetchAccountData, serverStats } from "../stores.mjs";
|
||||||
let targetAction
|
import { fade } from "svelte/transition";
|
||||||
</script>
|
|
||||||
|
let targetAction
|
||||||
<Pulldown name="accounts">
|
let inProgress
|
||||||
<div class="notLoggedIn">
|
let authError
|
||||||
<div class="container_div">
|
|
||||||
<h1>monofile <span style:color="#999999">accounts</span></h1>
|
let pwErr
|
||||||
<p class="flavor">Gain control of your uploads.</p>
|
|
||||||
|
// lazy
|
||||||
{#if targetAction}
|
|
||||||
|
let username
|
||||||
<div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
let password
|
||||||
<input placeholder="username" type="text">
|
|
||||||
<input placeholder="password" type="password">
|
let execute = () => {
|
||||||
<button>{targetAction=="login" ? "Log in" : "Create account"}</button>
|
if (inProgress) return
|
||||||
</div>
|
|
||||||
|
inProgress = true
|
||||||
{:else}
|
|
||||||
|
fetch(`/auth/${targetAction}`, {
|
||||||
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
method: "POST",
|
||||||
<button on:click={() => targetAction="login"}>Log in</button>
|
body: JSON.stringify({
|
||||||
<button on:click={() => targetAction="create"}>Sign up</button>
|
username, password
|
||||||
</div>
|
})
|
||||||
|
}).then(async (res) => {
|
||||||
{/if}
|
inProgress = false
|
||||||
</div>
|
|
||||||
</div>
|
if (res.status != 200) {
|
||||||
|
authError = await res.json().catch(() => {
|
||||||
|
return {
|
||||||
|
status: res.status,
|
||||||
|
message: res.statusText
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAccountData();
|
||||||
|
|
||||||
|
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (pwErr && authError) {
|
||||||
|
pwErr.animate({
|
||||||
|
backgroundColor: ["#885555","#663333"],
|
||||||
|
easing: "ease-out"
|
||||||
|
},650)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actual account menu
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Pulldown name="accounts">
|
||||||
|
{#if Object.keys($account).length == 0}
|
||||||
|
|
||||||
|
<div class="notLoggedIn" transition:fade={{duration:200}}>
|
||||||
|
<div class="container_div">
|
||||||
|
<h1>monofile <span style:color="#999999">accounts</span></h1>
|
||||||
|
<p class="flavor">Gain control of your uploads.</p>
|
||||||
|
|
||||||
|
{#if targetAction}
|
||||||
|
|
||||||
|
<div class="fields" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||||
|
{#if !$serverStats.accounts.registrationEnabled && targetAction == "create"}
|
||||||
|
<div class="pwError">
|
||||||
|
<div style:background-color="#554C33">
|
||||||
|
<p>Account registration has been disabled by this instance's owner</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if authError}
|
||||||
|
<div class="pwError" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||||
|
<div bind:this={pwErr}>
|
||||||
|
<p><strong>{authError.status}</strong> {authError.message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<input placeholder="username" type="text" bind:value={username}>
|
||||||
|
<input placeholder="password" type="password" bind:value={password}>
|
||||||
|
<button on:click={execute}>{ inProgress ? "• • •" : (targetAction=="login" ? "Log in" : "Create account") }</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<div class="lgBtnContainer" out:padding_scaleY|local={{easingFunc:circIn}} in:padding_scaleY|local>
|
||||||
|
<button on:click={() => targetAction="login"}>Log in</button>
|
||||||
|
<button on:click={() => targetAction="create"}>Sign up</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<div class="loggedIn" transition:fade={{duration:200}}>
|
||||||
|
<h1>
|
||||||
|
Hey there, <span class="monospace" style:font-size="18px">@{$account.username}</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div style:min-height="10px" style:border-bottom="1px solid #AAAAAA" />
|
||||||
|
|
||||||
|
<div class="accountOptions">
|
||||||
|
<button>
|
||||||
|
<img src="/static/assets/icons/change_password.svg" alt="change password">
|
||||||
|
<p>Change password</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button>
|
||||||
|
<img src="/static/assets/icons/delete_account.svg" alt="delete account">
|
||||||
|
<p>Delete account</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button on:click={() => fetch(`/auth/logout`,{method:"POST"}).then(() => fetchAccountData())}>
|
||||||
|
<img src="/static/assets/icons/logout.svg" alt="logout">
|
||||||
|
<p>Log out</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/if}
|
||||||
</Pulldown>
|
</Pulldown>
|
|
@ -1,31 +1,31 @@
|
||||||
<script>
|
<script>
|
||||||
import Pulldown from "./Pulldown.svelte"
|
import Pulldown from "./Pulldown.svelte"
|
||||||
import { pulldownManager } from "../stores.mjs";
|
import { pulldownManager } from "../stores.mjs";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Pulldown name="files">
|
<Pulldown name="files">
|
||||||
|
|
||||||
<div class="notLoggedIn">
|
<div class="notLoggedIn">
|
||||||
<div style:height="2px" style:background-color="#66AAFF" />
|
<div style:height="2px" style:background-color="#66AAFF" />
|
||||||
<div style:height="10px" />
|
<div style:height="10px" />
|
||||||
<p class="flavor">Log in to view uploads & collections</p>
|
<p class="flavor">Log in to view uploads & collections</p>
|
||||||
<button on:click={$pulldownManager.openPulldown("account")}>OK</button>
|
<button on:click={$pulldownManager.openPulldown("account")}>OK</button>
|
||||||
<div style:height="14px" />
|
<div style:height="14px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
put scrolling div containing options here
|
put scrolling div containing options here
|
||||||
if not logged in, most options will be hidden
|
if not logged in, most options will be hidden
|
||||||
& the div containing the options will be resized
|
& the div containing the options will be resized
|
||||||
(actually, maybe we could use flexbox for this)
|
(actually, maybe we could use flexbox for this)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<div>
|
<div>
|
||||||
<h2>Anonymous file deletion</h2>
|
<h2>Anonymous file deletion</h2>
|
||||||
<p>Enter your deletion code</p>
|
<p>Enter your deletion code</p>
|
||||||
<input placeholder="0000 0000 0000 0000">
|
<input placeholder="0000 0000 0000 0000">
|
||||||
</div>
|
</div>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
</Pulldown>
|
</Pulldown>
|
|
@ -1,25 +1,25 @@
|
||||||
<!-- i'm lazy, could probably just use plain html here but hwatever, mgiht make this grab from the server idk -->
|
<!-- i'm lazy, could probably just use plain html here but hwatever, mgiht make this grab from the server idk -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Pulldown from "./Pulldown.svelte"
|
import Pulldown from "./Pulldown.svelte"
|
||||||
|
|
||||||
let faq = [
|
let faq = [
|
||||||
{
|
{
|
||||||
question : "Are my files compressed on upload?",
|
question : "Are my files compressed on upload?",
|
||||||
answer : "No. Files should stay completely unchanged on download."
|
answer : "No. Files should stay completely unchanged on download."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question : "How do I replace a file that I have previously uploaded?",
|
question : "How do I replace a file that I have previously uploaded?",
|
||||||
answer : "You can modify the content of a file that is linked to a file ID by reuploading the file using the same custom ID."
|
answer : "You can modify the content of a file that is linked to a file ID by reuploading the file using the same custom ID."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Pulldown name="help">
|
<Pulldown name="help">
|
||||||
{#each faq as question}
|
{#each faq as question}
|
||||||
<div class="faqGroup">
|
<div class="faqGroup">
|
||||||
<h2>{question.question}</h2>
|
<h2>{question.question}</h2>
|
||||||
<p>{question.answer}</p>
|
<p>{question.answer}</p>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</Pulldown>
|
</Pulldown>
|
|
@ -1,14 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
export let name;
|
export let name;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div
|
<div
|
||||||
class="pulldown_display"
|
class="pulldown_display"
|
||||||
name={name}
|
name={name}
|
||||||
transition:fade={{duration:150}}
|
transition:fade={{duration:150}}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
|
@ -1,3 +1,23 @@
|
||||||
import { writable } from "svelte/store"
|
import { writable } from "svelte/store"
|
||||||
|
|
||||||
export let pulldownManager = writable(0)
|
export let pulldownManager = writable(0)
|
||||||
|
export let account = writable({})
|
||||||
|
export let serverStats = writable({})
|
||||||
|
|
||||||
|
export let fetchAccountData = function() {
|
||||||
|
fetch("/auth/me").then(async (response) => {
|
||||||
|
if (response.status == 200) {
|
||||||
|
account.set(await response.json())
|
||||||
|
} else {
|
||||||
|
account.set({})
|
||||||
|
}
|
||||||
|
}).catch((err) => { console.error(err) })
|
||||||
|
}
|
||||||
|
|
||||||
|
export let refresh_stats = () => {
|
||||||
|
fetch("/server").then(async (data) => {
|
||||||
|
serverStats.set(await data.json())
|
||||||
|
}).catch((err) => { console.error(err) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAccountData()
|
|
@ -1,20 +1,20 @@
|
||||||
import { circIn, circOut } from "svelte/easing"
|
import { circIn, circOut } from "svelte/easing"
|
||||||
|
|
||||||
export function _void(node, { duration, easingFunc, op, prop }) {
|
export function _void(node, { duration, easingFunc, op, prop, rTarg }) {
|
||||||
let rect = node.getBoundingClientRect()
|
let rect = node.getBoundingClientRect()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
duration: duration||300,
|
duration: duration||300,
|
||||||
css: t => {
|
css: t => {
|
||||||
let eased = (easingFunc || circIn)(t)
|
let eased = (easingFunc || circIn)(t)
|
||||||
|
|
||||||
return `
|
return `
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
${prop||"height"}: ${(eased)*(rect[prop||"height"])}px;
|
${prop||"height"}: ${(eased)*(rect[rTarg||prop||"height"])}px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
opacity:${eased};
|
opacity:${eased};
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
import { circIn, circOut } from "svelte/easing"
|
import { circIn, circOut } from "svelte/easing"
|
||||||
|
|
||||||
export function padding_scaleY(node, { duration, easingFunc, padY, padX, op }) {
|
export function padding_scaleY(node, { duration, easingFunc, padY, padX, op }) {
|
||||||
let rect = node.getBoundingClientRect()
|
let rect = node.getBoundingClientRect()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
duration: duration||300,
|
duration: duration||300,
|
||||||
css: t => {
|
css: t => {
|
||||||
let eased = (easingFunc || circOut)(t)
|
let eased = (easingFunc || circOut)(t)
|
||||||
|
|
||||||
return `
|
return `
|
||||||
height: ${eased*(rect.height-(padY||0))}px;
|
height: ${eased*(rect.height-(padY||0))}px;
|
||||||
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
|
${padX&&padY ? `padding: ${(eased)*(padY)}px ${(padX)}px;` : ""}
|
||||||
${op ? `opacity: ${eased};` : ""}
|
${op ? `opacity: ${eased};` : ""}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,93 +1,93 @@
|
||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import { circIn, circOut } from "svelte/easing"
|
import { circIn, circOut } from "svelte/easing"
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
import { _void } from "../transition/_void"
|
import { _void } from "../transition/_void"
|
||||||
|
|
||||||
let uploadTypes = {
|
let uploadTypes = {
|
||||||
files: 1,
|
files: 1,
|
||||||
clone: 2
|
clone: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadType = undefined
|
let uploadType = undefined
|
||||||
let dispatch = createEventDispatcher();
|
let dispatch = createEventDispatcher();
|
||||||
|
|
||||||
// file upload
|
// file upload
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type HTMLInputElement
|
* @type HTMLInputElement
|
||||||
*/
|
*/
|
||||||
let fileUpload;
|
let fileUpload;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (fileUpload) {
|
if (fileUpload) {
|
||||||
fileUpload.addEventListener("change",() => {
|
fileUpload.addEventListener("change",() => {
|
||||||
dispatch("addFiles",{
|
dispatch("addFiles",{
|
||||||
type: "upload",
|
type: "upload",
|
||||||
files: Array.from(fileUpload.files)
|
files: Array.from(fileUpload.files)
|
||||||
})
|
})
|
||||||
uploadType = undefined
|
uploadType = undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// file clone
|
// file clone
|
||||||
/**
|
/**
|
||||||
* @type HTMLButtonElement
|
* @type HTMLButtonElement
|
||||||
*/
|
*/
|
||||||
let cloneButton;
|
let cloneButton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type HTMLInputElement
|
* @type HTMLInputElement
|
||||||
*/
|
*/
|
||||||
let cloneUrlTextbox;
|
let cloneUrlTextbox;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (cloneButton && cloneUrlTextbox) {
|
if (cloneButton && cloneUrlTextbox) {
|
||||||
cloneButton.addEventListener("click",() => {
|
cloneButton.addEventListener("click",() => {
|
||||||
if (cloneUrlTextbox.value) {
|
if (cloneUrlTextbox.value) {
|
||||||
dispatch("addFiles",{
|
dispatch("addFiles",{
|
||||||
type: "clone",
|
type: "clone",
|
||||||
url: cloneUrlTextbox.value
|
url: cloneUrlTextbox.value
|
||||||
})
|
})
|
||||||
uploadType = undefined;
|
uploadType = undefined;
|
||||||
} else {
|
} else {
|
||||||
cloneUrlTextbox.animate([
|
cloneUrlTextbox.animate([
|
||||||
{"transform":"translateX(0px)"},
|
{"transform":"translateX(0px)"},
|
||||||
{"transform":"translateX(-3px)"},
|
{"transform":"translateX(-3px)"},
|
||||||
{"transform":"translateX(3px)"},
|
{"transform":"translateX(3px)"},
|
||||||
{"transform":"translateX(0px)"}
|
{"transform":"translateX(0px)"}
|
||||||
],100)
|
],100)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- there are 100% better ways to do this but idgaf, it's still easier to manage than <1.3 lmao -->
|
<!-- there are 100% better ways to do this but idgaf, it's still easier to manage than <1.3 lmao -->
|
||||||
|
|
||||||
<div id="add_new_files" transition:_void={{duration:200}}>
|
<div id="add_new_files" transition:_void={{duration:200}}>
|
||||||
<p>
|
<p>
|
||||||
+<span class="_add_files_txt">add files</span>
|
+<span class="_add_files_txt">add files</span>
|
||||||
</p>
|
</p>
|
||||||
{#if !uploadType}
|
{#if !uploadType}
|
||||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||||
<button on:click={() => uploadType = uploadTypes.files} >upload files...</button>
|
<button on:click={() => uploadType = uploadTypes.files} >upload files...</button>
|
||||||
<button on:click={() => uploadType = uploadTypes.clone} >clone url...</button>
|
<button on:click={() => uploadType = uploadTypes.clone} >clone url...</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
{#if uploadType == uploadTypes.files}
|
{#if uploadType == uploadTypes.files}
|
||||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||||
<div class="fileUpload">
|
<div class="fileUpload">
|
||||||
<p>click/tap to browse<br/>or drag files into this box</p>
|
<p>click/tap to browse<br/>or drag files into this box</p>
|
||||||
<input type="file" multiple bind:this={fileUpload}>
|
<input type="file" multiple bind:this={fileUpload}>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if uploadType == uploadTypes.clone}
|
{:else if uploadType == uploadTypes.clone}
|
||||||
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
<div id="file_add_btns" out:_void in:_void={{easingFunc:circOut}}>
|
||||||
<input placeholder="url" type="text" bind:this={cloneUrlTextbox}>
|
<input placeholder="url" type="text" bind:this={cloneUrlTextbox}>
|
||||||
<button style:flex-basis="30%" bind:this={cloneButton}>add file</button>
|
<button style:flex-basis="30%" bind:this={cloneButton}>add file</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
208
tsconfig.json
208
tsconfig.json
|
@ -1,104 +1,104 @@
|
||||||
{
|
{
|
||||||
"include":["src/server/**/*"],
|
"include":["src/server/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
/* Projects */
|
/* Projects */
|
||||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* Language and Environment */
|
||||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
/* Modules */
|
/* Modules */
|
||||||
"module": "commonjs", /* Specify what module code is generated. */
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
// "rootDir": "./src/", /* Specify the root folder within your source files. */
|
// "rootDir": "./src/", /* Specify the root folder within your source files. */
|
||||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
/* JavaScript Support */
|
/* JavaScript Support */
|
||||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
/* Emit */
|
/* Emit */
|
||||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
"outDir": "./out/server", /* Specify an output folder for all emitted files. */
|
"outDir": "./out/server", /* Specify an output folder for all emitted files. */
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
/* Interop Constraints */
|
/* Interop Constraints */
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
/* Type Checking */
|
/* Type Checking */
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
/* Completeness */
|
/* Completeness */
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue